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

Scoped WordPress instances to support multiple browser tabs #31

Merged
merged 3 commits into from
Oct 13, 2022

Conversation

adamziel
Copy link
Collaborator

@adamziel adamziel commented Oct 13, 2022

What problem does this PR solve?

Adds support for running WASM WordPress in multiple browser tabs and solves #9.

All WordPress requests are routed through a single service worker shared between all browser tabs. The request lifecycle looks as follows:

  1. A request originates in a specific tab
  2. A service worker intercepts it and requests the same tab to pass it to its WASM WordPress instance
  3. The tab renders it and sends the response to the service worker
  4. The service worker responds to the intercepted HTTP request using the WordPress-generated response
  5. The original tab receives the WordPress-generated response and displays it to the user

It's a back-and-forth conversation between a specific browser tab and the service worker.

Unfortunately, Service workers communicate with tabs using a BroadcastChannel – it's a messaging strategy that routes every message to every listener. As a result, each WordPress request was rendered in every tab, often causing unexpected behaviors.

How does this PR propose to solve it?

This PR introduces a concept of WordPress scope and enables the service worker to post BroadcastChannel messages scoped to specific listeners.

Scoping a WordPress instance means installing it at a unique pathname starting with /scope:<unique number>. For example:

  • In an unscoped WordPress instance, /wp-login.php would be available at http://localhost:8778/wp-login.php
  • In a scoped WordPress instance, /wp-login.php would be available at http://localhost:8778/scope:96253/wp-login.php

The scope number is a random and unique number generated by a specific browser tab. The service worker is aware of this concept and will use any /scope: found in the request URL to tag all the related BroadcastChannel communication. The WASM workers running in specific browser tabs will then ignore all the BroadcastChannel communication with an unfamiliar scope attached.

Alternatives considered

  • Using the scope feature of ServiceWorker – it led to multiple worker registrations and was hard to reason about.
  • Loading Workers from a tab-specific unique URL, e.g. sw.js?tab_id=432 or sw-1.js – it led to the same problems as relying on the scope feature.
  • Match the request with its originating tab in the ServiceWorker – There's not enough information available. The worker can't figure out the top-level client ID from a request originating in an iframe, and the top-level client wouldn't be able to tell whether the request originated in its browsing context.
  • Scoping WordPress instance by a domain, e.g. w87953.localhost – it would require setting up a catch-all DNS domain to even use this project. That's a steep barrier of entry.
  • Displaying an error in other browser tabs – it would be a large and unnecessary limitation.

How to test?

Run npm run dev and open the WASM WordPress page in a few different browser tabs. Log in, create pages, play with it, and confirm that each tab behaves as expected. Specifically:

  • No tab should unexpectedly log you in or out
  • No pages and changes should leak between the browser tabs

Follow-up work

  • If a use-case arises, a tab could use sessionStorage to preserve the scope across page reloads.

…he `scope` feature of service workers.

Scoping service workers only leads to registering dozens of workers and a hard to untangle mess.
@adamziel adamziel changed the title Scoped WordPress Scoped WordPress instances Oct 13, 2022
@adamziel adamziel changed the title Scoped WordPress instances Scoped WordPress instances to support multiple browser tabs Oct 13, 2022
@adamziel adamziel merged commit 5990946 into trunk Oct 13, 2022
@adamziel adamziel deleted the scoped-instances branch October 13, 2022 23:15
adamziel added a commit that referenced this pull request Oct 14, 2022
Related to #42

This commit implements the URL rewrite with:

* A liveServer middleware for local dev
* A .htaccess file for apache production setups

Documenting this behavior in a visible place will be critical for mass
adoption of this project.

Nested iframes with src="about:srcdoc" are not controlled by the
parent's frame ServiceWorker. This is a bug in Chrome and potentially
other Chromium-based browser.

This is problematic because WordPress site editor is rendered in one
such iframe and it loads a bunch of stylesheets using a link tag like:

    <link rel="stylesheet" href="/scope:<scope>/<URL>" />

The /scope:<scope>/ part of the URL does not actually exist on the
server – it's a WordPress instance scope introduced to make WASM
WordPress usable in multiple browser tabs. Learn more at #31

The point is – these stylesheet requests bypass the ServiceWorker and
are actually sent to the server where they get a 404 response. This
effectively breaks the site editor.

We cannot re-route these requests in a ServiceWorker, but we can do it
on the server. That's what this commit is.

Unfortunately this commit won't solve the problem in setups based on nginx
and other server software – let's follow it up with a documentation
update.
adamziel added a commit that referenced this pull request Oct 14, 2022
Related to #42

This commit implements the URL rewrite with:

* A liveServer middleware for local dev
* A .htaccess file for apache production setups

Documenting this behavior in a visible place will be critical for mass
adoption of this project.

Nested iframes with src="about:srcdoc" are not controlled by the
parent's frame ServiceWorker. This is a bug in Chrome and potentially
other Chromium-based browser.

This is problematic because WordPress site editor is rendered in one
such iframe and it loads a bunch of stylesheets using a link tag like:

    <link rel="stylesheet" href="/scope:<scope>/<URL>" />

The /scope:<scope>/ part of the URL does not actually exist on the
server – it's a WordPress instance scope introduced to make WASM
WordPress usable in multiple browser tabs. Learn more at #31

The point is – these stylesheet requests bypass the ServiceWorker and
are actually sent to the server where they get a 404 response. This
effectively breaks the site editor.

We cannot re-route these requests in a ServiceWorker, but we can do it
on the server. That's what this commit is.

Unfortunately this commit won't solve the problem in setups based on nginx
and other server software – let's follow it up with a documentation
update.
Pookie717 added a commit to Pookie717/wordpress-playground that referenced this pull request Oct 1, 2023
Related to WordPress/wordpress-playground#42

This commit implements the URL rewrite with:

* A liveServer middleware for local dev
* A .htaccess file for apache production setups

Documenting this behavior in a visible place will be critical for mass
adoption of this project.

Nested iframes with src="about:srcdoc" are not controlled by the
parent's frame ServiceWorker. This is a bug in Chrome and potentially
other Chromium-based browser.

This is problematic because WordPress site editor is rendered in one
such iframe and it loads a bunch of stylesheets using a link tag like:

    <link rel="stylesheet" href="/scope:<scope>/<URL>" />

The /scope:<scope>/ part of the URL does not actually exist on the
server – it's a WordPress instance scope introduced to make WASM
WordPress usable in multiple browser tabs. Learn more at WordPress/wordpress-playground#31

The point is – these stylesheet requests bypass the ServiceWorker and
are actually sent to the server where they get a 404 response. This
effectively breaks the site editor.

We cannot re-route these requests in a ServiceWorker, but we can do it
on the server. That's what this commit is.

Unfortunately this commit won't solve the problem in setups based on nginx
and other server software – let's follow it up with a documentation
update.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant