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

Sample: Using WASM in Manifest V3 #775

Closed
oliverdunk opened this issue Nov 12, 2022 · 26 comments · Fixed by #841
Closed

Sample: Using WASM in Manifest V3 #775

oliverdunk opened this issue Nov 12, 2022 · 26 comments · Fixed by #841

Comments

@oliverdunk
Copy link
Member

I'd like to try working on a PR to demonstrate using WASM in MV3. Before doing so, I wanted to propose it here. I've tried my best to follow the format which @dotproto used in other issues (e.g #766).

Overview

A sample showing how to load WASM in Manifest V3. This requires the new wasm-unsafe-eval CSP directive (see https://developer.chrome.com/docs/extensions/mv3/mv3-migration/#content-security-policy).

Proposed Implementation

I think a nice demo would be a simple extension which prints "Hello World!" from Rust.

  • Manifest with wasm-unsafe-eval
  • lib.rs file that prints "Hello World" in the start function
  • Service worker script which loads the WASM, using importScripts to include the generated JS

Additional Thoughts

Deciding where to put this is a little hard. Looking at existing discussions (here), I suspect cookbook may be the best place as this feels beyond an API demo but is not a fully functioning extension. We may want to commit the compiled WASM to avoid the need for a build step, or alternatively that could be documented in the README.

I also considered demonstrating how to access something like self.clients since this is not covered well in general WASM tutorials, which seem to focus heavily on webpages. However, in the interests of keeping this demo simple I suspect that may be something to revisit in the future.

@guest271314

This comment was marked as off-topic.

@dotproto
Copy link
Contributor

I think the cookbook directory is a good match. My use of the term "fully functioning" may have set the wrong expectations. What I was trying to convey is that the sample extension is not just a snippet of code, it's an extension that can be loaded in a browser and inspected with the browser's debug tools.

I think it's entirely appropriate to commit the compiled Wasm; IMO the main intent of the sample isn't to show how to create Wasm, but rather to show how one can use Wasm they already have in an extension. Guidance on authoring Wasm modules should probably be limited to some notes and links in a README.md file.

@daidr
Copy link
Contributor

daidr commented Mar 3, 2023

I tried to write a simple example (#841) of using wasm in manifest v3, but instead of using importScripts I used the static import syntax, I'm not sure if this is right.

@octopols
Copy link

octopols commented Mar 24, 2023

I am currently attempting to create the extension, but I am encountering some issues.

whenever I'm trying to load the extension I'm getting this error "
`Failed to load extension

File
C:\Users\SONYv\OneDrive\Desktop\extention\hello-world\hello-world-extension

Error:Invalid value for 'content_security_policy'.`

Here is my manifest file

{
    "manifest_version": 3,
    "name": "Hello World Extension",
    "version": "1.0.0",
    "background": {
      "service_worker": "sw.js"
    },
    "content_security_policy": "worker-src 'self'; script-src 'self' 'unsafe-eval'",
    "permissions": [],
    "host_permissions": [],
    "action": {}
  }

@patrickkettner
Copy link
Collaborator

@octopols in manifest v3, content_security_policy takes an object rather than just a string. Note this is an optional value, that applies to pages. You do not need to include it (chrome applies a default value, as noted in the above docs)

@jpmedley
Copy link
Contributor

@octopols
Copy link

octopols commented Mar 27, 2023

@patrickkettner @jpmedley thanks a lot I was able to fi the security thing but I still get many errors, I'm still getting the hang of Rust and I think there's something seriously wrong with my extension. I'll check out some documentation to see if I can figure out how to fix it and give it another shot.

@patrickkettner
Copy link
Collaborator

@octopols feel free to post in progress stuff!

@octopols
Copy link

octopols commented Mar 27, 2023

I'll make a pull request or a separate repository for my progress. Based on my understanding of the problem statement, I need to create an extension that uses manifest version 3 and prints a message in the console. My objective is to make the extension print a message in the console when clicked.

To achieve this, I began by creating a base Rust project. Then, I used the Rust repository, wasm-bindgen, which has an example of printing in the console using Rust and WebAssembly (wasm).

However, I am currently struggling to link the files generated by running wasm-pack build --target web to the extension, and I have not been successful in getting it to work yet.

@octopols
Copy link

here is my current progress https://github.com/octopols/rust_wasl_extension

@patrickkettner
Copy link
Collaborator

great job so far!

what is the issue you are hitting? I see a handful (forgive me if anything is obivous)

  1. running npm run build && npm run serve produces a working wasm page for me, so the build itself is good.
  2. The background script in your manifets has a dynamic import, which isn't allowed without also setting "type": "module" in your manifest

e.g.

  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  1. the reference file (i.e. console_log_bg.wasm.js) doesn't exist. changing that to something like
import wasm_bindgen from './pkg/index_bg.js';

note that it also should be a relative import as I changed above

  1. your rust output does not export a default. You would either need to declare one or just import everything
import * as wasm from "./pkg/index_bg.js";
  1. I believe (I know very little about wasm rust) you need to declare some of your functions as public within your lib.rs
pub fn bare_bones() {
     log("Hello from Rust!");
     log_u32(42);
     log_many("Logging", "many values!");
}

From there, running the npm run build, I am able to load the unpacked extension. Hope that helps a bit!

@octopols
Copy link

octopols commented Mar 28, 2023

Thanks a lot for testing and suggesting fixes, I'll try to implement them after college today. I have one question though, what's the need/use to run npn run build? I was loading the extension after creating the manifest file manually and linking the wasm files.
I'm a complete beginner to wasm and chrome extensions.

@patrickkettner
Copy link
Collaborator

great question!

npm run build runs the build option of your scripts in your package.json. In this case, it is just calling webpack.

in turn, webpack is running your webpack config. The reason it is relevant here is because of the inclusion of wasm-pack.

wasm-pack is what is compiling the rust code into wasm modules. It outputs the compiled version of your rust code to the dist directory. Once that code has been compiled, the content can be made available using npm run serve, which starts a DevServer (if you wanted to check things in the regular browser environment).

@octopols
Copy link

great job so far!

what is the issue you are hitting? I see a handful (forgive me if anything is obivous)

  1. running npm run build && npm run serve produces a working wasm page for me, so the build itself is good.
  2. The background script in your manifets has a dynamic import, which isn't allowed without also setting "type": "module" in your manifest

e.g.

  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  1. the reference file (i.e. console_log_bg.wasm.js) doesn't exist. changing that to something like
import wasm_bindgen from './pkg/index_bg.js';

note that it also should be a relative import as I changed above

  1. your rust output does not export a default. You would either need to declare one or just import everything
import * as wasm from "./pkg/index_bg.js";
  1. I believe (I know very little about wasm rust) you need to declare some of your functions as public within your lib.rs
pub fn bare_bones() {
     log("Hello from Rust!");
     log_u32(42);
     log_many("Logging", "many values!");
}

From there, running the npm run build, I am able to load the unpacked extension. Hope that helps a bit!

I have a console_log_bg.wasm file generated when I run wasm-pack build --target web there is no index_bg.js in the pkg folder? I don't know if I'm doing something really dumb here

@patrickkettner
Copy link
Collaborator

patrickkettner commented Mar 29, 2023 via email

@octopols
Copy link

I'll give it try, thanks for the reply

@octopols
Copy link

THIS WORKS! but I'm just loading the webpage rn, how do I make it work as an extension. I'm thinking of making the extension clickable, so whenever I click on the extension it logs "hello world!" to the console.

@patrickkettner
Copy link
Collaborator

congratulations!!!

You should be able to load the unpacked version of the extension in chrome by going to chrome://extensions > Load unpacked (if you don't see Load unpacked, make sure the Developer mode" toggle is enabled in the chrome://extension page).

You would want to load the dist directory, which is the compiled output of your build process (the input, or "entry" in webpack's parlance, is your index file). You can start to add the javascript that powers your extension in that file.

If you want to be able to have a clickable action, I would suggest checking out browserAction.

@octopols
Copy link

I've started working on the extension part (updated my repo). I looked around various online videos talking about wasm and realised that the wasm files only run on a live server. Extensions are locally loaded (as far as I know) so getting the wasm file to console log while being used in an extension is not working for me. :/

@patrickkettner
Copy link
Collaborator

The code you already had running is a wasm file, running in the browser. You are already pretty much there. Did you load the unpacked extension?

@octopols
Copy link

I loaded the dist folder, created a manifest file. In the popup I added a button that loads the index.js. when I was running it directly not as an extension on a live server it was working fine and worked only when I clicked the button. As an extension I noticed it was not printing to console and I looked online and realised I have to console log on the background html's console so the current state is a bit messy.
One more thing about the wasm code, I could only make it work on a live server. When I run the html directly from the dist folder it throws a bunch of wasm errors.

@patrickkettner
Copy link
Collaborator

no worries, lets step back and reevaluate.

We know WASM runs in the browser. It has been the case for a very long time.

I am not entirely sure what you mean by "a live server", but in essence WebAssembly is a compilation target for other programing languages. That means we write the code in something other than javascript - in this case, rust, which is not able to run directly in the browser. We then use tooling to convert that code into something that can run directly in the browser - in this case, WASM code.

If that is all true, we want to write code in rust that is compiled into something that logs a string in our console, in an extension. You have already create something that is written in rust, and that logs code to the console. We just need to get that into an extension. Lets go over the architecture in your repo to see how to make that happen.

You have your rust code, that should be the core functionality of your extension. It needs to be compiled into wasm code. The compilation is done via wasm-pack, which your repo has implemented using webpack. Webpack knows what to compile based on the entry point. Your entry point is pointing to index.js. That file is just importing the pkg directory. The pkg directory doesn't exist in your repo. It is the default output directory of wasm-pack. So the index.js file would just be the compiled output of the wasm code. There is no logic in the compiled output in your repo beyond that right now. (which is fine, for now).

Putting that all aside, lets look at your extension. The entry of the extension is the manifest file. I think part of the problem you are having is that you have multiple manifest files. Your original one, and the one you just added. I believe this happened because you manually added a file to the dist folder - which is a mistake. The dist folder should just be the output of your build process. You can enforce this by setting the clean option in the output section of. your webpack config. This will delete everything in the dist folder before every build.
Rather than manually save a new manifest file into this directory, you should have webpack copy the manifest you already have into the dist directory.

Your original manifest just loads a single javascript file - background.js. If we wanted to use this manifest file, then you should also update your webpack config to copy the background.js to the dist folder as a part of the build. The background.js file is also pretty short

import * as wasm from "./pkg/index_bg.js";

chrome.browserAction.onClicked.addListener(async (tab) => {
  await wasm_bindgen.default();
});

It was importing the wasm code being generated, and then calling it on browserAction.
But since your new manifest file does not mention background.js, and it is not in your dist folder, none of this code matters. Since it exists, I am assuming youd rather use the newer manifest file. So we can look at that to see if there are any problems with it.

You said you wanted to have an extension that worked based on the extension's popup. Looking at your manifest you have a content_scripts section. Content scripts are scripts that are injected into any URL that matches the matches section (which is all urls, in. your code). This is not needed to do what you said you wanted to do, so this whole section can be deleted. You also have an action section, which does give you the popup you said you wanted. In your code, it is creating a popup when you click the icon, that will load in index.html.

If you check out your repo, the index.html looks like this

<!DOCTYPE html>
<html>
  <head>
    <title>My Chrome Extension</title>
    <script src="popup.js"></script>
    <meta charset='UTF-8' />
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="container">
      <h1>Using WebAssembly with 🦀Rust in chrome extension using Manifest V3.</h1>
      <button id="myButton">Click me to log to console!</button>
    </div>
  </body>
</html>

but if you check the same file after running the build step (i.e. npm run build), it will look like this

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="index.js"></script></head>
  <body>
  </body>
</html>

It is way different, because you are using HtmlWebpackPlugin, which is overwriting your html code with the html webpack is generating to load your compiled code. This is a reason why setting the clean option in your output is a good practice, it prevents this sort of misunderstanding.

That being said, since you made an html page already, you don't need HtmlWebpackPlugin at all. You can remove that from your config, and instead have webpack copy the index file you made into the dist folder.

Looking at your index.html page, you are loading in popup.js. Looking at that file (which also should not be in dist by default - it should be copied there by your build step), you are grabbing the background page, and then trying to [inject a script tag] (https://github.com/octopols/rust_wasl_extension/blob/4d0afe17b041eb5eb968f8e9aba05c318d8d2e29/dist/popup.js#L5-L7) every time someone clicks on the popup. Rather than do that, you can just use a script tag in the html that loads a file structured more like the background.js file. You would want to import the wasm code within the javascript, create a listener (in background.js, you are currently listening for when the icon for the extension is clicked on, rather than a button within the popup.html), and then call the wasm code.

Let me know if anything is unclear!

@octopols
Copy link

Omg thank you so much!!! I will go through this and try to make this work. I really appreciate you for giving your time 💖.

@patrickkettner
Copy link
Collaborator

awesome! please feel free to reach out if you hit any problems :D

@ayushgupta9906
Copy link

import * as wasm from "./pkg/index_bg.js";

chrome.browserAction.onClicked.addListener(async (tab) => {
  await wasm_bindgen.default();
});

If you check out your repo, the [index.html]

<!DOCTYPE html>
<html>
  <head>
    <title>My Chrome Extension</title>
    <script src="popup.js"></script>
    <meta charset='UTF-8' />
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="container">
      <h1>Using WebAssembly with 🦀Rust in chrome extension using Manifest V3.</h1>
      <button id="myButton">Click me to log to console!</button>
    </div>
  </body>
</html>

but if you check the same file after running the build step (i.e. npm run build), it will look like this

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="index.js"></script></head>
  <body>
  </body>
</html>

@ayushgupta9906
Copy link

import * as wasm from "./pkg/index_bg.js";

chrome.browserAction.onClicked.addListener(async (tab) => {
  await wasm_bindgen.default();
});

If you check out your repo, the [index.html]

<!DOCTYPE html>
<html>
  <head>
    <title>My Chrome Extension</title>
    <script src="popup.js"></script>
    <meta charset='UTF-8' />
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="container">
      <h1>Using WebAssembly with 🦀Rust in chrome extension using Manifest V3.</h1>
      <button id="myButton">Click me to log to console!</button>
    </div>
  </body>
</html>

but if you check the same file after running the build step (i.e. npm run build), it will look like this

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="index.js"></script></head>
  <body>
  </body>
</html>

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

Successfully merging a pull request may close this issue.

9 participants