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

Multiple worker backends on the web (to fix the Chrome crash) #28

Merged
merged 11 commits into from
Oct 11, 2022
145 changes: 98 additions & 47 deletions dist-web/app.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,117 @@
(() => {
// src/shared/messaging.mjs
function postMessageFactory(target) {
let lastRequestId = 0;
return function postMessage(data, timeout = 5e4) {
return new Promise((resolve, reject) => {
const requestId = ++lastRequestId;
const responseHandler = (event) => {
if (event.data.type === "response" && event.data.requestId === requestId) {
target.removeEventListener("message", responseHandler);
clearTimeout(failOntimeout);
resolve(event.data.result);
}
};
const failOntimeout = setTimeout(() => {
reject("Request timed out");
target.removeEventListener("message", responseHandler);
}, timeout);
target.addEventListener("message", responseHandler);
target.postMessage({
...data,
requestId
});
});
};
var DEFAULT_REPLY_TIMEOUT = 25e3;
var lastMessageId = 0;
function postMessageExpectReply(messageTarget, message, ...postMessageArgs) {
const messageId = ++lastMessageId;
messageTarget.postMessage(
{
...message,
messageId
},
...postMessageArgs
);
return messageId;
}
async function awaitReply(messageTarget, messageId, timeout = DEFAULT_REPLY_TIMEOUT) {
return new Promise((resolve, reject) => {
const responseHandler = (event) => {
if (event.data.type === "response" && event.data.messageId === messageId) {
messageTarget.removeEventListener("message", responseHandler);
clearTimeout(failOntimeout);
resolve(event.data.result);
}
};
const failOntimeout = setTimeout(() => {
reject(new Error("Request timed out"));
messageTarget.removeEventListener("message", responseHandler);
}, timeout);
messageTarget.addEventListener("message", responseHandler);
});
}
function replyTo(event, result, target) {
target.postMessage({
function responseTo(messageId, result) {
return {
type: "response",
requestId: event.data.requestId,
messageId,
result
}, event.origin);
};
}

// src/web/app.mjs
if (!navigator.serviceWorker) {
alert("Service workers are not supported by your browser");
}
var serviceWorkerReady = navigator.serviceWorker.register(`/service-worker.js`);
var myWebWorker = new Worker("web-worker.js");
var webWorkerReady = new Promise((resolve) => {
const callback = (event) => {
if (event.data.type === "ready") {
resolve();
myWebWorker.removeEventListener("message", callback);
var serviceWorkerChannel = new BroadcastChannel("wordpress-service-worker");
serviceWorkerChannel.addEventListener("message", async function onMessage(event) {
console.debug(`[Main] "${event.data.type}" message received from a service worker`);
let result;
if (event.data.type === "is_ready") {
result = isReady;
} else if (event.data.type === "request" || event.data.type === "httpRequest") {
const worker = await wasmWorker;
result = await worker.HTTPRequest(event.data.request);
}
if (event.data.messageId) {
serviceWorkerChannel.postMessage(
responseTo(
event.data.messageId,
result
)
);
}
console.debug(`[Main] "${event.data.type}" message processed`, { result });
});
var wasmWorker = createWordPressWorker(
{
backend: iframeWorkerBackend("http://127.0.0.1:8778/iframe-worker.html"),
wordPressSiteURL: location.href
}
);
async function createWordPressWorker({ backend, wordPressSiteURL }) {
while (true) {
try {
await backend.sendMessage({ type: "is_alive" }, 50);
break;
} catch (e) {
}
await new Promise((resolve) => setTimeout(resolve, 50));
}
await backend.sendMessage({
type: "initialize_wordpress",
siteURL: wordPressSiteURL
});
return {
async HTTPRequest(request) {
return await backend.sendMessage({
type: "request",
request
});
}
};
myWebWorker.addEventListener("message", callback);
});
}
function iframeWorkerBackend(workerDocumentURL) {
const iframe = document.createElement("iframe");
iframe.src = workerDocumentURL;
iframe.style.display = "none";
document.body.appendChild(iframe);
return {
sendMessage: async function(message, timeout = DEFAULT_REPLY_TIMEOUT) {
const messageId = postMessageExpectReply(iframe.contentWindow, message, "*");
const response = await awaitReply(window, messageId, timeout);
return response;
}
};
}
var isReady = false;
async function init() {
console.log("[Main] Initializing the worker");
await wasmWorker;
await serviceWorkerReady;
await webWorkerReady;
const postMessage = postMessageFactory(myWebWorker);
window.addEventListener("message", async (event) => {
if (event.data.type === "goto") {
document.querySelector("iframe").src = event.data.path;
}
console.log("[APP.js] Got a message", event);
const response = await postMessage(event.data);
console.log("[APP.js] Got a response", response);
replyTo(event, response, parent);
});
document.querySelector("iframe").src = "/wp-login.php";
isReady = true;
console.log("[Main] Iframe is ready");
const WPIframe = document.querySelector("#wp");
WPIframe.src = "/wp-login.php";
}
init();
})();
9 changes: 9 additions & 0 deletions dist-web/iframe-worker.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body style="padding: 0; margin: 0">
<script src="wasm-worker.js"></script>
</body>
</html>

2 changes: 1 addition & 1 deletion dist-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<title>WordPress code embed!</title>
</head>
<body style="padding: 0; margin: 0">
<iframe style="width: 100vw; height: 100vh; border: 0; margin: 0; padding: 0;"></iframe>
<iframe id="wp" style="width: 100vw; height: 100vh; border: 0; margin: 0; padding: 0;"></iframe>
<script src="app.js"></script>
</body>
</html>
Expand Down
22 changes: 22 additions & 0 deletions dist-web/php-web.js

Large diffs are not rendered by default.

Binary file not shown.
22 changes: 22 additions & 0 deletions dist-web/php-webworker.js

Large diffs are not rendered by default.

66 changes: 37 additions & 29 deletions dist-web/service-worker.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
(() => {
// src/shared/messaging.mjs
function postMessageFactory(target) {
let lastRequestId = 0;
return function postMessage(data, timeout = 5e4) {
return new Promise((resolve, reject) => {
const requestId = ++lastRequestId;
const responseHandler = (event) => {
if (event.data.type === "response" && event.data.requestId === requestId) {
target.removeEventListener("message", responseHandler);
clearTimeout(failOntimeout);
resolve(event.data.result);
}
};
const failOntimeout = setTimeout(() => {
reject("Request timed out");
target.removeEventListener("message", responseHandler);
}, timeout);
target.addEventListener("message", responseHandler);
target.postMessage({
...data,
requestId
});
});
};
var DEFAULT_REPLY_TIMEOUT = 25e3;
var lastMessageId = 0;
function postMessageExpectReply(messageTarget, message, ...postMessageArgs) {
const messageId = ++lastMessageId;
messageTarget.postMessage(
{
...message,
messageId
},
...postMessageArgs
);
return messageId;
}
async function awaitReply(messageTarget, messageId, timeout = DEFAULT_REPLY_TIMEOUT) {
return new Promise((resolve, reject) => {
const responseHandler = (event) => {
if (event.data.type === "response" && event.data.messageId === messageId) {
messageTarget.removeEventListener("message", responseHandler);
clearTimeout(failOntimeout);
resolve(event.data.result);
}
};
const failOntimeout = setTimeout(() => {
reject(new Error("Request timed out"));
messageTarget.removeEventListener("message", responseHandler);
}, timeout);
messageTarget.addEventListener("message", responseHandler);
});
}

// src/web/service-worker.js
var workerChannel = new BroadcastChannel("wordpress-service-worker");
var postWebWorkerMessage = postMessageFactory(workerChannel);
var broadcastChannel = new BroadcastChannel("wordpress-service-worker");
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
const isWpOrgRequest = url.hostname.includes("api.wordpress.org");
const isPHPRequest = url.pathname.endsWith("/") || url.pathname.endsWith(".php");
const isPHPRequest = url.pathname.endsWith("/") && url.pathname !== "/" || url.pathname.endsWith(".php");
if (isWpOrgRequest || !isPHPRequest) {
console.log(`[ServiceWorker] Ignoring request: ${url.pathname}`);
return;
Expand All @@ -40,23 +44,27 @@
return event.respondWith(
new Promise(async (accept) => {
console.log(`[ServiceWorker] Serving request: ${url.pathname}?${url.search}`);
console.log({ isWpOrgRequest, isPHPRequest });
const post = await parsePost(event.request);
const requestHeaders = {};
for (const pair of event.request.headers.entries()) {
requestHeaders[pair[0]] = pair[1];
}
let wpResponse;
try {
wpResponse = await postWebWorkerMessage({
const message = {
type: "httpRequest",
request: {
path: url.pathname + url.search,
method: event.request.method,
_POST: post,
headers: requestHeaders
}
});
console.log({ wpResponse });
};
console.log("[ServiceWorker] Forwarding a request to the main app", { message });
const messageId = postMessageExpectReply(broadcastChannel, message);
wpResponse = await awaitReply(broadcastChannel, messageId);
console.log("[ServiceWorker] Response received from the main app", { wpResponse });
} catch (e) {
console.error(e);
throw e;
Expand Down
Loading