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

Support for Manifest V3 in Browser Extension Development #902

Closed
groundedsage opened this issue Jun 15, 2021 · 10 comments
Closed

Support for Manifest V3 in Browser Extension Development #902

groundedsage opened this issue Jun 15, 2021 · 10 comments

Comments

@groundedsage
Copy link

Although not immediately pressing. I would like to discuss what is required to update shadow-cljs so that it supports V3 of the extension manifest https://developer.chrome.com/docs/extensions/mv3/intro/

V2 will eventually be phased out and V3 made mandatory so starting to work on support for building V3 extensions will be helpful for extension developers opting to use ClojureScript.

@thheller
Copy link
Owner

Can you be more precice in what shadow needs to do here? Can't you just set "manifest_version": 3 in the JSON file? shadow really doesn't care whats in your manifest.

Someone actually working on a Chrome Extension should probably take this on.

@yqrashawn
Copy link

yqrashawn commented Jan 26, 2022

One big change in manifest v3 is user can't use unsafe-eval in development build and remove it in production build any more. (user can still use unsafe-eval csp in sandboxed pages)

https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/#content-security-policy

So is it possible to still has a working repl and don't use eval in development build?

@thheller
Copy link
Owner

Well, it is read-eval-print-loop. So no, you can't have a REPL without eval.

@yqrashawn
Copy link

That's sad. The quickest solution might be build a chromium without the extension csp check and use it in dev env.

The related code might be in this file https://github.com/chromium/chromium/blob/main/extensions/common/csp_validator.cc

@dvingo
Copy link

dvingo commented May 11, 2022

I am about to start dev'ing a chrome extension and thought I'd try figuring this out.

I started with this tutorial extension:
https://developer.chrome.com/docs/extensions/mv3/getstarted
and attempted to load it with this addition to the manifest.json file:

"content_security_policy": {
  "extension_pages": "default-src 'self'; script-src 'self' 'unsafe-eval' http://localhost:9630; connect-src * data: blob: filesystem:; style-src 'self' data: chrome-extension-resource: 'unsafe-inline'; img-src 'self' data: chrome-extension-resource:; frame-src 'self' data: chrome-extension-resource:; font-src 'self' data: chrome-extension-resource:; media-src * data: blob: filesystem:;"
}

I also needed to add the directive: "host_permissions": ["<all_urls>"], (separate from our issue of desiring to evaluate code - yep, their own tutorial is broken).

And indeed there is a validation error and the extension cannot be loaded.

Shout out to @yqrashawn for the idea to augment the source (and a starting point to look at).

I managed to build chromium fairly easily (took about an hour to figure out and 40 mins to compile) having never done this before.

Then another hour or two tinkering and re-building - but it's working now! The extension loads successfully.

The source changes I made in csp_validator.cc were to add this on line 47:

const char kExtensionUnsafeEvalSource[] = "'unsafe-eval'";

and make use of it in the implementation of DoesCSPDisallowRemoteCode
the block starting on line 706 becomes:

auto directive_values = mapping.directive->directive_values;
auto it = std::find_if_not(
    directive_values.begin(), directive_values.end(),
    [](base::StringPiece source) {
      std::string source_lower = base::ToLowerASCII(source);

      return source_lower == kSelfSource || source_lower == kNoneSource ||
             IsLocalHostSource(source_lower) ||
             source_lower == kExtensionUnsafeEvalSource || // <-- added
             source_lower == kWasmUnsafeEvalSource;
    });

I haven't actually attempted loading any code though - the chrome extension written in cljs I'm trying this with outputs background scripts which are not supported in v3 (uses service workers instead). I'm hoping there won't be more checks in the chromium codebase but, at least the first hurdle is passed.

@thheller
Copy link
Owner

thheller commented Oct 3, 2022

I created an example v3 extension. See #1051

I made no attempt at eval but :target :chrome-extension should be considered obsolete by now and :target :esm seems to be the way forward.

@thheller thheller closed this as completed Oct 3, 2022
@blake-ctrl
Copy link

@dvingo @yqrashawn
Hi! I know this is a closed issue, but I wanted to say thanks for the inspiration and starting point within the Chromium source. I'm porting an MV2 Clojurescript extension to MV3 at this moment and as of yesterday I have hot reloading working within a modified Chromium build.

If anybody else is building an MV3 extension with Shadow CLJS, hopefully this might be helpful.
https://gist.github.com/blake-ctrl/778db8715556d1bc1af00338a8d755b9

@ketansrivastav
Copy link

@dvingo @yqrashawn Hi! I know this is a closed issue, but I wanted to say thanks for the inspiration and starting point within the Chromium source. I'm porting an MV2 Clojurescript extension to MV3 at this moment and as of yesterday I have hot reloading working within a modified Chromium build.

If anybody else is building an MV3 extension with Shadow CLJS, hopefully this might be helpful. https://gist.github.com/blake-ctrl/778db8715556d1bc1af00338a8d755b9

Thanks! I am in the same boat: migrating a mv2 extension to mv3. However, I can't seem to get the REPL to work on the patched Chromium build.

I am trying to run thheller's mv3 example: https://github.com/thheller/chrome-ext-v3

After I changed the ":runtime" to "browser" i could see a bunch of websocket errors that indicate outputted JS is unable to connect to shadow-cljs

I am curious what changes you had to make to the shadow-cljs config to make it work.

@blake-ctrl
Copy link

My shadow-cljs.edn looks something like this:

...
 :dev-http {8080 {:root "public"
                  :host "127.0.0.1"}}
 :builds
 {:extension
  {:target :esm
   :compiler-options {:source-map true}
   :output-dir "extension/js"
   :runtime :browser ;:custom

   :devtools {:preloads [devtools.preload]
              :use-document-host false}
   :modules {:shared {:entries []}
             :serviceworker
             {:init-fn xyz.serviceworker/init
              :depends-on #{:shared}}
             :popup
             {:init-fn xyz.popup/init
              :depends-on #{:shared}}
...

I don't know if it makes a difference but my manifest explicitly sets the CSP policy:

  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self';"
  },

It doesn't include unsafe-eval, but the patch to Chromium should make it always act as if unsafe-eval is allowed.

For me, before patching, the websockets connected without issue but the js-eval from a reload or repl evaluation would throw an error visible from the console in Chromium Devtools.

@ketansrivastav
Copy link

ketansrivastav commented Oct 29, 2023

thank you @blake-ctrl

Posting this comment for posterity for anyone else who might be facing similar issues:

in shadow-cljs.edn
This fixed the WebSockets issues:

   :devtools {
              :use-document-host false}

in manifest.json
This fixed the eval issue:

  "content_security_policy": {
      "extension_pages": "script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval';"

  }

So after applying your patch and building Chromium i had to make the 2 mentioned changes in my extension and voila!

Thanks you for your reply and for the Chromium fix 🙂

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

6 participants