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

Rewrite bookmarklet execution to use userScripts in Manifest V3. #85

Merged
merged 25 commits into from
May 28, 2024

Conversation

anthonyec
Copy link
Owner

@anthonyec anthonyec commented May 13, 2024

In Manifest V3 (MV3), it's no longer possible to execute arbitrary code in a webpage. This is how Powerlet worked, it took a bookmarklet's code and injected it into the page.

The only way to execute arbitrary code in MV3 is by registering code using the userScripts API. This API is designed around running code when a page loads. You tell Chrome what code file or string you want to run and on what site.

You have no control with this new API to execute scripts "on demand". They always run on page load. This of course is an issue for Powerlet which is all about running code on demand.

To get around, code is generated from a user's bookmarklets. This generated code wraps the bookmarklet within a function. For example, the following bookmarklet:

javascript: alert("hello");

Will become registered code that looks a bit like this:

// The bookmark ID `420` is used to name the function.
function _powerlet_bookmarklet_420() {
  javascript: alert("hello");
}

These registered user scripts are executed in the "main" world on page load. This means they are located on the global window object!

When you tell Powerlet to execute a script, it essentially calls the function on the page. The equivalent of doing:

window._powerlet_bookmarklet_420(); // Shows an alert "hello"

Of course, it's not that easy. There is a lot of bridging of events (see content_isolated.js) so that an action in the extension popup can invoke a function on the global window object.

Also, since scripts are registered once and only available after a page reload, when you change a bookmarklet's contents, it needs to reload the page before it can execute. This is done by comparing a hash of the current bookmarklet to the previous. Hash functions are also stored on the page like so:

function _powerlet_get_hash_420() {
  return "my_cool_hash_123";
}

There are downsides to this approach. One of which is Chrome's popup blocker will stop any calls to window.open. This is because scripts are executed in the "main" world, they don't appear any different to Chrome than naughty script trying to open windows without the user's intent.

I tried a few ways to proxy the window object and invoke my own open function in an isolated world. However, this caused problems when bookmarklets try to use the opened window in their script. For example, this would not work:

const popup = window.open("")
popup.document.write("hello")

Some bookmarklets do this to show UI in a popup window. I didn't use the proxy solution, and instead let Chrome notify the user a window has been blocked. The user will have to unblock the window for the current site they are viewing and rerun the bookmarklet.

It's not ideal, but I'll see if there's a better way. Like maybe an option to run bookmarklets in an isolated world? Or an option enable the experimental window proxy?

The big benefit to scripts running in the "main" world is that now they have access to global variables. This fixes issues where people wanted to the existing jQuery on a page (#63 #61)

Also if the website allows it, scripts can now make network requests (#81)!

Anyway, I'm gonna follow up with other PRs that fix a few things. But I wanted to get this out before the June 1st deadline!

@anthonyec anthonyec changed the title Manifest v3 Manifest V3 May 13, 2024
@anthonyec anthonyec force-pushed the manifest-v3 branch 2 times, most recently from 996c724 to 17b356c Compare May 24, 2024 18:57
@anthonyec anthonyec force-pushed the manifest-v3 branch 2 times, most recently from adf3781 to 1cd491d Compare May 26, 2024 22:33
@anthonyec anthonyec changed the title Manifest V3 Rewrite bookmarklet execution to use userScripts in Manifest V3. May 28, 2024
@anthonyec anthonyec marked this pull request as ready for review May 28, 2024 20:05
@anthonyec anthonyec merged commit 2657365 into main May 28, 2024
@anthonyec anthonyec deleted the manifest-v3 branch May 28, 2024 22:25
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