WordPress / wordpress-playground

Run WordPress in the browser via WebAssembly PHP
https://w.org/playground/
GNU General Public License v2.0
1.58k stars 223 forks source link

Multiple 404 and 428 errors #1485

Open gigitux opened 1 month ago

gigitux commented 1 month ago

We tried to use WP Playground for testing Customize Your Store project. Unfortunately, we noticed that WP Playground gave some errors while testing this feature. The errors are shown in the attached image:

image

During my investigation to debug this issue, I noticed that the culprit is this one. For this project, we are using an high customized version of Gutenberg editor, so I think that we have to wait that this PR will be merged in trunk.

image (iframe URL)

Reproduction steps

  1. Click on this link
  2. Click on "skip guided setup" on the right corner.
  3. Continue the setup flow.
  4. Click on "Customize your Store".
  5. Click on "Start designing".
  6. Wait that the loading screen finish.
  7. See the errors in the console
brandonpayton commented 1 month ago

There is something funny going on here involving blob URLs. Screenshot 2024-06-07 at 12 25 42 PM

It looks like the failing requests are not being intercepted or at least handled by the Service Worker. Screenshot 2024-06-07 at 12 52 52 PM

The 404s are real because the resources do not exist. The 428s appear to be caused by WP Cloud rate-limiting after frequent 404s. I think it is safe to treat these 428s we're seeing as 404s.

brandonpayton commented 1 month ago

The blobs are apparently full HTML documents.

Sample: blob:https://playground.wordpress.net/79f5d210-568f-4fba-8dd8-3a0f5c64890d Content:

<!doctype html>
<html>
    <head>
        <script>window.frameElement._load()</script>
        <style>html{height:auto!important;min-height:100%;}body{margin:0}</style>
        <link rel='stylesheet' id='wp-components-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-includes/css/dist/components/style.min.css?ver=6.5.4' media='all' />
<link rel='stylesheet' id='wp-preferences-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-includes/css/dist/preferences/style.min.css?ver=6.5.4' media='all' />
<link rel='stylesheet' id='wp-block-editor-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-includes/css/dist/block-editor/style.min.css?ver=6.5.4' media='all' />
<link rel='stylesheet' id='wp-reusable-blocks-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-includes/css/dist/reusable-blocks/style.min.css?ver=6.5.4' media='all' />
<link rel='stylesheet' id='wp-patterns-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-includes/css/dist/patterns/style.min.css?ver=6.5.4' media='all' />
<link rel='stylesheet' id='wp-editor-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-includes/css/dist/editor/style.min.css?ver=6.5.4' media='all' />
<link rel='stylesheet' id='wp-block-library-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-includes/css/dist/block-library/style.min.css?ver=6.5.4' media='all' />
<style id='wp-block-library-inline-css'>

                .is-style-arrow-icon-details {
                    padding-top: var(--wp--preset--spacing--10);
                    padding-bottom: var(--wp--preset--spacing--10);
                }

                .is-style-arrow-icon-details summary {
                    list-style-type: "\2193\00a0\00a0\00a0";
                }

                .is-style-arrow-icon-details[open]>summary {
                    list-style-type: "\2192\00a0\00a0\00a0";
                }

                .is-style-pill a,
                .is-style-pill span:not([class], [data-rich-text-placeholder]) {
                    display: inline-block;
                    background-color: var(--wp--preset--color--base-2);
                    padding: 0.375rem 0.875rem;
                    border-radius: var(--wp--preset--spacing--20);
                }

                .is-style-pill a:hover {
                    background-color: var(--wp--preset--color--contrast-3);
                }

                ul.is-style-checkmark-list {
                    list-style-type: "\2713";
                }

                ul.is-style-checkmark-list li {
                    padding-inline-start: 1ch;
                }

                .is-style-arrow-link .wp-block-navigation-item__label:after {
                    content: "\2197";
                    padding-inline-start: 0.25rem;
                    vertical-align: middle;
                    text-decoration: none;
                    display: inline-block;
                }

                .is-style-asterisk:before {
                    content: '';
                    width: 1.5rem;
                    height: 3rem;
                    background: var(--wp--preset--color--contrast-2, currentColor);
                    clip-path: path('M11.93.684v8.039l5.633-5.633 1.216 1.23-5.66 5.66h8.04v1.737H13.2l5.701 5.701-1.23 1.23-5.742-5.742V21h-1.737v-8.094l-5.77 5.77-1.23-1.217 5.743-5.742H.842V9.98h8.162l-5.701-5.7 1.23-1.231 5.66 5.66V.684h1.737Z');
                    display: block;
                }

                /* Hide the asterisk if the heading has no content, to avoid using empty headings to display the asterisk only, which is an A11Y issue */
                .is-style-asterisk:empty:before {
                    content: none;
                }

                .is-style-asterisk:-moz-only-whitespace:before {
                    content: none;
                }

                .is-style-asterisk.has-text-align-center:before {
                    margin: 0 auto;
                }

                .is-style-asterisk.has-text-align-right:before {
                    margin-left: auto;
                }

                .rtl .is-style-asterisk.has-text-align-left:before {
                    margin-right: auto;
                }
</style>
<link rel='stylesheet' id='wp-block-editor-content-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-includes/css/dist/block-editor/content.min.css?ver=6.5.4' media='all' />
<link rel='stylesheet' id='wp-edit-blocks-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-includes/css/dist/block-library/editor.min.css?ver=6.5.4' media='all' />
<link rel='stylesheet' id='wc-blocks-style-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/wc-blocks.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-active-filters-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/active-filters.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-add-to-cart-form-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/add-to-cart-form.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-all-products-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/all-products.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-all-reviews-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/all-reviews.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-attribute-filter-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/attribute-filter.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-packages-style-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/packages-style.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-breadcrumbs-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/breadcrumbs.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-catalog-sorting-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/catalog-sorting.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-customer-account-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/customer-account.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-featured-category-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/featured-category.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-featured-product-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/featured-product.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-mini-cart-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/mini-cart.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-price-filter-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/price-filter.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-button-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-button.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-categories-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-categories.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-collection-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-collection.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-gallery-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-gallery.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-image-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-image.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-image-gallery-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-image-gallery.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-template-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-template.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-query-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-query.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-rating-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-rating.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-rating-stars-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-rating-stars.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-results-count-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-results-count.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-reviews-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-reviews.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-sale-badge-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-sale-badge.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-search-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-search.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-sku-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-sku.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-stock-indicator-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-stock-indicator.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-summary-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-summary.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-title-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-title.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-rating-filter-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/rating-filter.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-reviews-by-category-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/reviews-by-category.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-reviews-by-product-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/reviews-by-product.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-product-details-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/product-details.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-single-product-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/single-product.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-stock-filter-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/stock-filter.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-order-confirmation-status-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/order-confirmation-status.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-order-confirmation-summary-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/order-confirmation-summary.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-order-confirmation-totals-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/order-confirmation-totals.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-order-confirmation-downloads-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/order-confirmation-downloads.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-order-confirmation-billing-address-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/order-confirmation-billing-address.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-order-confirmation-shipping-address-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/order-confirmation-shipping-address.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-order-confirmation-additional-information-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/order-confirmation-additional-information.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-order-confirmation-additional-fields-wrapper-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/order-confirmation-additional-fields-wrapper.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-order-confirmation-additional-fields-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/order-confirmation-additional-fields.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-cart-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/cart.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-checkout-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/checkout.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='wc-blocks-style-mini-cart-contents-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/mini-cart-contents.css?ver=wc-9.1.0' media='all' />
<link rel='stylesheet' id='woocommerce-layout-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/css/woocommerce-layout.css?ver=9.1.0' media='all' />
<link rel='stylesheet' id='woocommerce-smallscreen-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/css/woocommerce-smallscreen.css?ver=9.1.0' media='only screen and (max-width: 768px)' />
<link rel='stylesheet' id='woocommerce-general-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/css/woocommerce.css?ver=9.1.0' media='all' />
<link rel='stylesheet' id='woocommerce-blocktheme-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/css/woocommerce-blocktheme.css?ver=9.1.0' media='all' />
<link rel='stylesheet' id='twentytwentyfour-button-style-outline-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/themes/twentytwentyfour/assets/css/button-outline.css?ver=1.1' media='all' />
<link rel='stylesheet' id='wc-blocks-editor-style-css' href='https://playground.wordpress.net/scope:0.9707661022834402/wp-content/plugins/woocommerce/assets/client/blocks/wc-blocks-editor-style.css?ver=wc-9.1.0' media='all' />

    </head>
    <body>
        <script>document.currentScript.parentElement.remove()</script>
    </body>
</html>

I was able to obtain this by hacking woocommerce/assets/client/admin/chunks/customize-store.js to no longer provide a disposal function that calls URL.revokeObjectURL(). Once the URLs were no longer being revoked, it was possible to view their source by clicking on them in the initiator column of the devtools Network tab.

It seems like the Service Worker may not be given an opportunity to intercept requests for the resources referenced by the HTML blob.

Will continue digging.

brandonpayton commented 1 month ago

This appears to be where the blob URL is created: https://github.com/woocommerce/woocommerce/blob/f0478c3f60b9d333d862cd0af149849a541b976b/plugins/woocommerce-admin/client/customize-store/assembler-hub/iframe.jsx#L79

brandonpayton commented 1 month ago

Some ServiceWorker spec discussion from 2015: https://github.com/w3c/ServiceWorker/issues/712

And an unresolved Chromium bug: "Script created documents in iframes aren't being associated with the correct SW registration" https://issues.chromium.org/issues/40397133

cc @adamziel @bgrgicak for visibility

brandonpayton commented 1 month ago

This issue also exists in Firefox v126.0.1.

Probably a good next step is to create an isolated test case without Playground or Woo to see whether it is possible to:

adamziel commented 3 weeks ago

It sounds like there'a an uncontrolled iframe somewhere – it's why we patch Gutenberg in the service worker:

/*
 * Pair the site editor's nested iframe to the Service Worker.
 *
 * Without the patch below, the site editor initiates network requests that
 * aren't routed through the service worker. That's a known browser issue:
 *
 * * https://bugs.chromium.org/p/chromium/issues/detail?id=880768
 * * https://bugzilla.mozilla.org/show_bug.cgi?id=1293277
 * * https://github.com/w3c/ServiceWorker/issues/765
 *
 * The problem with iframes using srcDoc and src="about:blank" as they
 * fail to inherit the root site's service worker.
 *
 * Gutenberg loads the site editor using <iframe srcDoc="<!doctype html">
 * to force the standards mode and not the quirks mode:
 *
 * https://github.com/WordPress/gutenberg/pull/38855
 *
 * This commit patches the site editor to achieve the same result via
 * <iframe src="/doctype.html"> and a doctype.html file containing just
 * `<!doctype html>`. This allows the iframe to inherit the service worker
 * and correctly load all the css, js, fonts, images, and other assets.
 *
 * Ideally this issue would be fixed directly in Gutenberg and the patch
 * below would be removed.
 *
 * See https://github.com/WordPress/wordpress-playground/issues/42 for more details
 *
 * ## Why does this code live in the service worker?
 *
 * There's many ways to install the Gutenberg plugin:
 *
 * * Install plugin step
 * * Import a site
 * * Install Gutenberg from the plugin directory
 * * Upload a Gutenberg zip
 *
 * It's too difficult to patch Gutenberg in all these cases, so we blanket-patch
 * all the scripts requested over the network whose names seem to indicate they're
 * related to the Gutenberg plugin.
 */
adamziel commented 3 weeks ago

I don't know any solution other than using <iframe src="/file-loaded-from-the-server.html"></iframe> instead of blobs, srcdocs, and all other ways of populating the iframe. Perhaps we could have a MutationObserver in each iframe that would enforce loading an empty.html file and would then populate the contentDocument with the content of the actual blob, srcDoc etc.

brandonpayton commented 3 weeks ago

@adamziel, since MutationObserver only gives async feedback, I wonder if it would be better to override document.createElement within the wp iframe to do the following after creating iframe elements:

Usually, it is better to avoid doing this kind of thing, but since Playground is serving as a platform, maybe it makes sense for Playground to be more opinionated in the service of that platform.

brandonpayton commented 3 weeks ago

During my investigation to debug this issue, I noticed that the culprit is https://github.com/WordPress/wordpress-playground/issues/646#issuecomment-1752615148. For this project, we are using an high customized version of Gutenberg editor, so I think that we have to wait thathttps://github.com/WordPress/gutenberg/pull/55152 will be merged in trunk.

@gigitux already pointed to the issue related to blobs, iframes and Service Worker in this issue description, but I assumed all relevant details were communicated via a separate Slack thread and skipped reading this issue description closely.

Then I found the same with independent investigation. Thanks for pointing us in the right direction anyway @gigitux. :)

brandonpayton commented 3 weeks ago

I'm planning to experiment with overriding DOM APIs to fix the general case here. We don't really want to force software not to use a web platform feature just to run adequately within WordPress Playground.

brandonpayton commented 3 weeks ago

@gigitux the original blueprint you linked under this issue no longer works for repro because the GitHub build artifact has expired by now, but I was able to load your changes using a more recent CI build from this PR: https://github.com/woocommerce/woocommerce/pull/48129

Specifically, I grabbed an updated plugins-#### from this line: https://github.com/woocommerce/woocommerce/actions/runs/9500080818/job/26182334827#step:7:28

Unfortunately (or fortunately?), when I do that, I am no longer able to reproduce this issue in WordPress Playground. Check out this nice looking Customize Your Store screen. Screenshot 2024-06-14 at 3 59 59 PM

Are you still able to reproduce this issue in Playground?

gigitux commented 3 weeks ago

@brandonpayton The Gutenberg iframe is loaded after that, you click on "Start designing": you should start to get errors after clicking on the button.

brandonpayton commented 2 weeks ago

@brandonpayton The Gutenberg iframe is loaded after that, you click on "Start designing": you should start to get errors after clicking on the button.

Ah, right. Thanks. Also in your repro instructions. 🤦‍♂️

I'm planning to experiment with overriding DOM APIs to fix the general case here. We don't really want to force software not to use a web platform feature just to run adequately within WordPress Playground.

I roughed out the following platform patches for the iframe-with-blog URL issue, and with initial, incomplete testing, it resolves the issue:

function readBlobAsText(blobUrl) {
    try {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', blobUrl, false);
        xhr.overrideMimeType('text/plain;charset=utf-8');
        xhr.send();
        return xhr.responseText;
    } catch(e) {
        return '';
    } finally {
        // TODO: Should we really revoke someone else's blob URL?
        //URL.revokeObjectURL(url);
    }
};

function looksLikeBlobUrl(url) {
    return url.startsWith('blob:');
}

function maybeOverrideIframeSrc(srcValue) {
    if (looksLikeBlobUrl(srcValue)) {
        const blobText = readBlobAsText(srcValue);
        return '/blob-relay.html#' + encodeURIComponent(blobText);
    } else {
        return srcValue;
    }
}

export default applyBlobUrlWorkaround;

export function applyBlobUrlWorkaround() {
    const originalIframeSetAttribute = HTMLIFrameElement.prototype.setAttribute;
    HTMLIFrameElement.prototype.setAttribute = function setAttribute_maybeBlobUrlWorkaround(name, value) {
        if (name === 'src') {
            value = maybeOverrideIframeSrc(value);
        }
        originalIframeSetAttribute.call(this, name, value);
    };
    // TODO: Consider overriding getter to show blob: URL

    const originalIframeSrcDescriptor = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'src');
    if (!originalIframeSrcDescriptor.configurable) {
        // TODO: Improve warning
        console.warn('iframe.src is not configurable');
    }
    const srcDescriptor = {
        ...originalIframeSrcDescriptor,
        set: function setSrcProperty_maybeBlobUrlWorkaround(value) {
            value = maybeOverrideIframeSrc(value);
            originalIframeSrcDescriptor.set.call(this, value);
        }
    }
    Object.defineProperty(HTMLIFrameElement.prototype, 'src', srcDescriptor);

    const originElementInnerHtmlDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerHTML');
    // TODO: Warn if not configurable
    const innerHtmlDescriptor = {
        ...originElementInnerHtmlDescriptor,
        set: function setInnerHtmlProperty_maybeBlobUrlWorkaround(value) {
            const template = document.createElement('template');
            template.innerHTML = value;
            template.content.querySelectorAll('iframe').forEach(iframe => {
                if (iframe.src && looksLikeBlobUrl(iframe.src)) {
                    iframe.src = maybeOverrideIframeSrc(iframe.src);
                }
            });
            originElementInnerHtmlDescriptor.set.call(this, template.innerHTML);
        },
    };
    Object.defineProperty(HTMLElement.prototype, 'innerHTML', innerHtmlDescriptor);

    const originElementOuterHtmlDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'outerHTML');
    // TODO: Warn if not configurable
    const outerHtmlDescriptor = {
        ...originElementOuterHtmlDescriptor,
        set: function setOuterHtmlProperty_maybeBlobUrlWorkaround(value) {
            const template = document.createElement('template');
            template.outerHTML = value;
            template.content.querySelectorAll('iframe').forEach(iframe => {
                if (iframe.src && looksLikeBlobUrl(iframe.src)) {
                    iframe.src = maybeOverrideIframeSrc(iframe.src);
                }
            });
            originElementOuterHtmlDescriptor.set.call(this, template.innerHTML);
        },
    };
    Object.defineProperty(HTMLElement.prototype, 'outerHTML', outerHtmlDescriptor);

    document.addEventListener('DOMContentLoaded', () => {
        document.querySelectorAll('iframe').forEach(iframe => {
            if (iframe.src && looksLikeBlobUrl(iframe.src)) {
                iframe.src = maybeOverrideIframeSrc(iframe.src);
            }
        });
    });

    // TODO: Return a function to undo the workaround
}

blob-relay.html is based on empty.html from the existing Playground workaround for the block editor:

<!doctype html><script>const hash = window.location.hash.substring(1); if ( hash ) document.write(decodeURIComponent(hash))</script>

I don't love patching the DOM APIs like this, but since Playground is a platform, I think it may make sense to apply a workaround like this to pages loaded in a WordPress context within Playground.

Next: Planning to make a blueprint to demonstrate this workaround.

brandonpayton commented 2 weeks ago

As an aside: In my testing, it looks like Safari may not have this problem with blob URLs, but Firefox and Chrome do.