Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WP: Apply the "controlled iframe" patch in JavaScript, not in Dockerfile #703

Merged
merged 2 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/playground/blueprints/public/blueprint-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@
},
"disableWpNewBlogNotification": {
"type": "boolean"
},
"makeEditorFrameControlled": {
"type": "boolean"
}
},
"required": ["step"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface ApplyWordPressPatchesStep {
patchSecrets?: boolean;
disableSiteHealth?: boolean;
disableWpNewBlogNotification?: boolean;
makeEditorFrameControlled?: boolean;
}

export const applyWordPressPatches: StepHandler<
Expand All @@ -40,6 +41,12 @@ export const applyWordPressPatches: StepHandler<
if (options.disableWpNewBlogNotification === true) {
await patch.disableWpNewBlogNotification();
}
if (options.makeEditorFrameControlled === true) {
await makeEditorFrameControlled(php, patch.wordpressPath, [
`${patch.wordpressPath}/wp-includes/js/dist/block-editor.js`,
`${patch.wordpressPath}/wp-includes/js/dist/block-editor.min.js`,
]);
}
};

class WordPressPatcher {
Expand Down Expand Up @@ -123,3 +130,105 @@ function randomString(length: number) {
result += chars[Math.floor(Math.random() * chars.length)];
return result;
}

/**
*
* 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
*/
export async function makeEditorFrameControlled(
php: UniversalPHP,
wordpressPath: string,
blockEditorScripts: string[]
) {
const controlledIframe = `
/**
* A synchronous function to read a blob URL as text.
*
* @param {string} url
* @returns {string}
*/
const __playground_readBlobAsText = function (url) {
try {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.overrideMimeType('text/plain;charset=utf-8');
xhr.send();
return xhr.responseText;
} catch(e) {
return '';
} finally {
URL.revokeObjectURL(url);
}
}

window.__playground_ControlledIframe = window.wp.element.forwardRef(function (props, ref) {
const source = window.wp.element.useMemo(function () {
if (props.srcDoc) {
// WordPress <= 6.2 uses a srcDoc that only contains a doctype.
return '/wp-includes/empty.html';
} else if (props.src && props.src.startsWith('blob:')) {
// WordPress 6.3 uses a blob URL with doctype and a list of static assets.
// Let's pass the document content to empty.html and render it there.
return '/wp-includes/empty.html#' + encodeURIComponent(__playground_readBlobAsText(props.src));
} else {
// WordPress >= 6.4 uses a plain HTTPS URL that needs no correction.
return props.src;
}
}, [props.src]);
return (
window.wp.element.createElement('iframe', {
...props,
ref: ref,
src: source,
// Make sure there's no srcDoc, as it would interfere with the src.
srcDoc: undefined
})
)
});`;

for (const filePath of blockEditorScripts) {
if (!(await php.fileExists(filePath))) {
continue;
}

await updateFile(
php,
filePath,
// The original version of this function crashes WASM PHP, let's define an empty one instead.
(contents) =>
`${controlledIframe} ${contents.replace(
/\(\s*"iframe",/,
'(__playground_ControlledIframe,'
)}`
);
}
await php.writeFile(
`${wordpressPath}/wp-includes/empty.html`,
'<!doctype html><script>const hash = window.location.hash.substring(1); if ( hash ) document.write(decodeURIComponent(hash))</script>'
);
}
81 changes: 3 additions & 78 deletions packages/playground/blueprints/src/lib/steps/install-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { StepHandler } from '.';
import { zipNameToHumanName } from './common';
import { installAsset } from './install-asset';
import { activatePlugin } from './activate-plugin';
import { makeEditorFrameControlled } from './apply-wordpress-patches';

/**
* @inheritDoc installPlugin
Expand Down Expand Up @@ -100,92 +101,16 @@ async function applyGutenbergPatchOnce(playground: UniversalPHP) {
* data URL, blob URL, or a srcDoc like it does by default.
*
* @see https://github.com/WordPress/wordpress-playground/pull/668
*
* The code below repeated in the WordPress bundler in
* compile-wordpress/build-assets/controlled-iframe.js.
*/

if (
(await playground.isDir('/wordpress/wp-content/plugins/gutenberg')) &&
!(await playground.fileExists('/wordpress/.gutenberg-patched'))
) {
const controlledIframe = `
/**
* A synchronous function to read a blob URL as text.
*
* @param {string} url
* @returns {string}
*/
const __playground_readBlobAsText = function (url) {
try {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.overrideMimeType('text/plain;charset=utf-8');
xhr.send();
return xhr.responseText;
} catch(e) {
return '';
} finally {
URL.revokeObjectURL(url);
}
}

window.__playground_ControlledIframe = window.wp.element.forwardRef(function (props, ref) {
const source = window.wp.element.useMemo(function () {
if (props.srcDoc) {
// WordPress <= 6.2 uses a srcDoc that only contains a doctype.
return '/wp-includes/empty.html';
} else if (props.src && props.src.startsWith('blob:')) {
// WordPress 6.3 uses a blob URL with doctype and a list of static assets.
// Let's pass the document content to empty.html and render it there.
return '/wp-includes/empty.html#' + encodeURIComponent(__playground_readBlobAsText(props.src));
} else {
// WordPress >= 6.4 uses a plain HTTPS URL that needs no correction.
return props.src;
}
}, [props.src]);
return (
window.wp.element.createElement('iframe', {
...props,
ref: ref,
src: source,
// Make sure there's no srcDoc, as it would interfere with the src.
srcDoc: undefined
})
)
});`;

await playground.writeFile('/wordpress/.gutenberg-patched', '1');
await updateFile(
playground,
await makeEditorFrameControlled(playground, '/wordpress', [
`/wordpress/wp-content/plugins/gutenberg/build/block-editor/index.js`,
(contents) =>
controlledIframe +
contents.replace(
/\(\s*"iframe",/g,
'(window.__playground_ControlledIframe,'
)
);
await updateFile(
playground,
`/wordpress/wp-content/plugins/gutenberg/build/block-editor/index.min.js`,
(contents) =>
controlledIframe +
contents.replace(
/\(\s*"iframe",/g,
'(window.__playground_ControlledIframe,'
)
);
]);
}
}

async function updateFile(
playground: UniversalPHP,
path: string,
callback: (contents: string) => string
) {
return await playground.writeFile(
path,
callback(await playground.readFileAsText(path))
);
}
40 changes: 3 additions & 37 deletions packages/playground/compile-wordpress/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,43 +43,6 @@ RUN cp -r wordpress wordpress-static && \
# Remove all empty directories
find . -type d -empty -delete

# 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
COPY ./build-assets/controlled-iframe.js /root/

RUN echo '<!doctype html><script>const hash = window.location.hash.substring(1); if ( hash ) document.write(decodeURIComponent(hash))</script>' > wordpress-static/wp-includes/empty.html
# Replace a vanilla HTML iframe with Playground's ControlledIframe component in the block editor.
RUN sed -E 's#\(\s*"iframe",#(__playground_ControlledIframe,#g' -i wordpress-static/wp-includes/js/dist/block-editor.js && \
cat /root/controlled-iframe.js wordpress-static/wp-includes/js/dist/block-editor.js >> /root/block-editor.js && \
mv /root/block-editor.js wordpress-static/wp-includes/js/dist/ && \
sed -E 's#\(\s*"iframe",#(__playground_ControlledIframe,#g' -i wordpress-static/wp-includes/js/dist/block-editor.min.js && \
cat /root/controlled-iframe.js wordpress-static/wp-includes/js/dist/block-editor.min.js >> /root/block-editor.min.js && \
mv /root/block-editor.min.js wordpress-static/wp-includes/js/dist/;

# Move the static files to the final output directory
RUN mkdir /root/output/$OUT_FILENAME
RUN mv wordpress-static/* /root/output/$OUT_FILENAME/
Expand Down Expand Up @@ -111,6 +74,9 @@ RUN cd wordpress && \
find ./ -type f -name '*.js' \
-not -path '*/wp-includes/blocks/*/*.min.js' \
-not -name 'wp-emoji-loader.min.js' \
# This file is patched in JavaScript and needs to
# be served from VFS. See #703
-not -path '*/wp-includes/js/dist/block-editor*.js' \
-delete

RUN cd wordpress && \
Expand Down

This file was deleted.

Loading