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

[Browser] Support multiple browser tabs #9

Closed
adamziel opened this issue Sep 22, 2022 · 7 comments
Closed

[Browser] Support multiple browser tabs #9

adamziel opened this issue Sep 22, 2022 · 7 comments

Comments

@adamziel
Copy link
Collaborator

adamziel commented Sep 22, 2022

What problem is this issue looking to solve?

In the browser, all the requests to WordPress are routed to a web worker by a service worker. The service worker handles the traffic for the entire domain, which means that all the open tabs are routing their WordPress requests via the same service worker.

Unfortunately, it passes them to all the running web workers instead of targeting specifically the one from the tab where the request originated. When five tabs are opened, five web workers handle all the requests, which causes all sorts of problems.

How does this issue proposes to solve it?

Find a way to route each request from service worker to only the specific web worker that initiated it. Somewhere around here:

https://github.com/adamziel/wordpress-wasm/blob/9d04a113a97f53a1303fefba642f65f1b5a5a5f6/src/web/service-worker.js#L31-L45

If there was a way to brand all requests from a specific tab somehow, that would be perfect. Perhaps each iframe could pretend to have a unique subdomain? Or each tab could attach a unique cookie or other http header to every request?

@adamziel
Copy link
Collaborator Author

sessionStorage could be helpful here:

Data stored using sessionStorage do not persist across browser tabs, even if two tabs both contain webpages from the same domain origin

@adamziel
Copy link
Collaborator Author

adamziel commented Sep 26, 2022

Another way of supporting multiple browser tabs would be running the same app instance across multiple tabs – kudos to @gziolo for the idea!

Edit: Let's track this one separately in #32

@adamziel
Copy link
Collaborator Author

Surfacing this relevant comment here: #24

This StackOverflow answer offers some inspiration:
https://stackoverflow.com/questions/63848494/how-to-differ-regular-and-iframe-requests-through-a-service-worker
The service worker has access to self.clients and each client has a unique id and a frameType. There should be a way to only route requests coming from the client id associated with a specific iframe – this would also solve the multitab scenario.

@adamziel
Copy link
Collaborator Author

Another way of supporting multiple browser tabs would be running the same app instance across multiple tabs – kudos to @gziolo for the idea!

This could be handled via a SharedWorker. Ideally WASM itself would run there, but it's currently blocked by a bug in Chrome – see #1. Another option would be to use SharedWorker as a traffic controller that elects one of the active tabs as a "leader" and uses it to resolve all the requests. I'm a bit worried how similar this is to distributed data synchronization problems.

@adamziel
Copy link
Collaborator Author

adamziel commented Oct 13, 2022

A few ideas:

  • Require a catch-all domain like *.mysite.com where all subdomains point to the same directory on the same server and then load WordPress from a random subdomain in each browser tab. The downside is that such a setup is a huge barrier of entry.
  • Load a unique worker file in each browser tab, e.g. worker.js?tab_id=90871hjv189 or worker1.js and then interact only with that worker. The next opened tab would take the next available worker. The build process could even ship about 10 workers to cover the 90% use-case. User would have to be warned if the limit is exceeded. This won't work – you can only register one service worker per scope.
  • Initialize each WordPress on a random path, e.g. /tab-897/index.php, and either render just the requests related to the current tab's path, or register a service worker restricted to the /tab-897 scope.
  • Display an error in the second, third, etc tab.

@adamziel
Copy link
Collaborator Author

adamziel commented Oct 13, 2022

Here are some problems I ran across:

  • There are no browser tab-specific cookies to uniquely tag all requests originating in each tab.
  • From the service worker's perspective, the clientId behind each request belongs to an iframe and not to the browser tab that runs the WordPress worker. [Feature request] Expose parent client ID for iframe navigations w3c/ServiceWorker#1556 could help, but it's not implemented today.
  • The service worker cannot ask a client "is this request coming from you?" because the client doesn't know answer.

adamziel added a commit that referenced this issue 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.
@adamziel
Copy link
Collaborator Author

Solved by #31

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant