Major rewrite to support manifest v3 #10
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR is a major rewrite, refactoring most of the original code to better use modern javascript tools like modules, rollup, and the component pattern. The result is a version of AuRo that supports manifest v3, is easy to debug, and by changing how data is passed around gets to benefit from a better UX when something goes wrong. Here's a rundown of everything that changed and the reasoning behind every major decisions.
See #4
RollupJs
Modularity
Something I noticed in the original code was that it attempted to be modular, but fell short due to the complex structure and limitations that comes with Chrome extensions. Any attempt to share code is basically impossible "out-of-the-box". While the service worker, popup, and content script supports modules, the injected scripts requires everything to be available in the page directly. The initial code simply duplicated the code, but this can and did lead to subtle differences between the implementations.
This PR solves this problem by introducing Rollupjs to the project. Rollup is a simple nodejs based toolchain that transforms source code into a deployable bundle. It handles things like minification and concatenation, but mostly, through the use of plugins, also supports very complex bundling scenarios. In this case, we use the rollup-plugin-chrome-extension which handles most of the grunt work for us. It read the extension manifest.json and produces the appropriate content, popup, and service worker bundles for us.
This change, combined with a custom supplemental bundle (see
rollup.config.js:24
), means that we can now share code between the extension and the code injected in the page.To produce the production bundle, run
npm run build
.Manual Testing
A big pain point of working in chrome extensions is the constant reloading that have to be done in the extension management page of the browser every time you want to test a change. This can become very tedious in a project like this where every change happen one small refactoring/redesign at a time and have to be tested constantly to ensure that the original behavior is still there.
Thanks to rollup, we can instead spin-up a tiny web server that monitor file changes and communicate via web-socket with the extension to force it to reload every time a file updates. This is done for us via the simpleReloader module of the chrome extension plugin.
To produce the auto-updating bundle, run
npm run watch
.Build Configurations
With the benefit of reusable code, and a configurable build system, this PR introduce a set of logging functions that automatically handles the
AuRo ::
prefix as well as the frame id prefix. These functions are declared inlib/logging.js
. You will notice that they make use of special variables named__log_namespace__
and__log_verbose__
. Those variables are placeholders that gets replaced automatically during the build process by values configured in therollup.config.js
file. Some of those are also dynamic, depending of command line arguments.By default, the
build
script is not verbose, so any log underwarn
will be suppressed. Thewatch
script, however is a verbose build, keeping all of the logs active.Eventing
Centralized Events
Being a Chrome extension, the main communication point between its various components are doing to be messages. While the original extension used this extensively, there wasn't any centralized way of managing events. There were custom reply mechanisms in place, different event sources, and inconsistent event listener structures.
Making use of the now modular design of the project, this PR adds a centralized event "hub" in
lib/events.js
. Events are declared globally withmakeEvent
, a small function that returns a callable message emitter, with an additionalon
property that can be used to listen to the event with a callback.Chrome messages supports replies out of the box, so there is no need to use custom reply events to deal with this scenario. You can simply use the return value of the emitter instead.
Chrome messages also supports async events, but they have some quirks and require that you inform the runtime that your event might reply in an async way. The event emitter automatically handles this scenario.
Chrome messages have to be dispatched at the correct "location". Events can be sent to the background runtime, or the content script of a specific tab, including specific frames, or the popup. This event library bind those different targets when the event is declared, removing this concern from the logic of the application.
Storage
Local vs Session
Storage was previously handled in a very risky way that introduced race conditions. This PR fixes those by drastically simplifying how storage is accessed and by using different storages for different needs.
Since manifest v3 makes uses a service worker, the background script will not run for the entire time of the browser session. It can be shutdown and restarted as needed. Instead of clearing tab keys when the script starts, with a risk of race condition as the operation was not awaited, the extension now stores those keys in
session
storage. This preserves them across service worker restarts, and ensure they are cleared before the extension load without any risk of race conditions.Repository Pattern
With this new need for session storage for tabs, and local storage for hosts, this PR introduce a repository pattern that exposes only what is needed to manipulate storage.
Promisification
The original code spends a lot of effort converting the various Chrome callback APIs into Promises, which is a good thing. This is however supported out of the box in all versions of Chrome that also supports the Audio API the extension is using. This PR now uses the native implementation when possible.
Injected Content
Sandbox & Scripting API
The original extension depended on having full control in the content script to inject its content. This script is now sandboxed in manifest v3, requiring a different approached based on the scripting API. This API is only available from the service worker, meaning that a large portion of the code had to be restructured to move this process out of the content script.
API Change
The first prototype used window events to attempt to communicate between the extension and the injected code. This PR uses the original solution: script injection.
Popup
Components
With the many changes brought by the new modular structure, and new initialization process, we now have the opportunity of handling errors in a more user friendly way than before. The original design for the popup did not made this simple and was instead replaced by a custom, react-inspired, component architecture. It is very simple, and far from optimized, but it should be easy to replace with react if the needs ever requires something more substantial.
The core of this custom library is in
popup/renderer.js
and offers three methods:createElement
which works like its react equivalent.render
which takes a component (a function that returns elements), and applies it to the DOM.requestRender
which triggers an update when the state of the components changes.Components are declared in
popup/components
in the form of a setup function that returns a render function.New Feature - Error Handling
With this new structure, the extension can now display different error messages to handle multiple scenarios, like distinguishing between a missing authorization, or a missing content script. This also comes with a simplified and more accessible look.
New Feature - Dark Theme
Just a little thing that I included to protect my eyes and complement your already existing white icon 😃
That's it! I hope you appreciate the work that I did here as much as I appreciated your efforts in producing the initial version.