Skip to content

Commit 1f50634

Browse files
authored
Update docs (#74)
* Add note on the architecture * Update docs
1 parent ab5b297 commit 1f50634

File tree

9 files changed

+130
-150
lines changed

9 files changed

+130
-150
lines changed

README.md

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,18 @@
11

22
![Witchcraft](docs/src/assets/title.png)
33

4-
Think Greasemonkey (or Tampermonkey, Violentmonkey) for developers.
5-
6-
Witchcraft is a Google Chrome extension for loading custom Javascript and CSS directly from a folder in your file system, injecting them into pages that match their files names.
4+
Witchcraft is a Google Chrome extension for loading custom JavaScript and CSS directly from a folder on your local machine, injecting them into web pages that match specified URL patterns.
75

86
It works by matching every page domain against script file names available in the scripts folder. For instance, if one navigates to `google.com`, Witchcraft will try to load and run `google.com.js` and `google.com.css`.
97

108
For more information on how to install and use it, head to Witchcraft's [home page](//luciopaiva.com/witchcraft).
119

12-
# Serving local files
13-
14-
Chrome extensions cannot access local files directly for security reasons. To work around this, Witchcraft needs a local HTTP server to serve the scripts folder.
15-
16-
There are many ways to accomplish this. For example, if you have Python installed, you can run the following command in the scripts folder:
17-
18-
python3 -m http.server 5743
19-
20-
Alternatively, if you have Node.js installed, you can run:
21-
22-
npx http-server -p 5743
23-
2410
# Development
2511

2612
See [here](./development.md).
2713

28-
# Technical notes
29-
30-
Read it [here](./technical-notes.md).
31-
3214
# Credits
3315

34-
Witchcraft is my rendition of [defunkt](//github.com/defunkt)'s original extension, [dotjs](//github.com/defunkt/dotjs). Although I never got to actually use dotjs (it only worked on macOS and the installation process was not easy), I really wanted something like that. Thanks, defunkt, for having such a cool idea.
16+
Witchcraft is my rendition of [defunkt](//github.com/defunkt)'s original extension, [dotjs](//github.com/defunkt/dotjs).
3517

3618
Images in the logo were provided by [Freepik](//www.flaticon.com/authors/freepik).

docs/astro.config.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,18 @@ export default defineConfig({
7979
items: [
8080
{ label: 'Introduction', slug: 'introduction' },
8181
{ label: 'How to install', slug: 'how-to-install' },
82-
{ label: 'How to use', slug: 'user-guide' },
82+
{ label: 'How to use', slug: 'how-to-use' },
8383
{ label: 'New in version 3', slug: 'new-in-v3' },
8484
{ label: 'FAQ', slug: 'faq' },
8585
{ label: 'Credits', slug: 'credits' },
8686
]
8787
},
88+
{
89+
label: 'Technical notes',
90+
items: [
91+
{ label: 'Architecture', slug: 'architecture' },
92+
]
93+
}
8894
// {
8995
// label: 'Cookbook',
9096
// autogenerate: { directory: 'cookbook' },
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
title: Architecture
3+
---
4+
5+
This technical documents talks a bit about the different architectures Witchcraft has had over time, the problems faced and how they were solved.
6+
7+
## Version 2
8+
9+
Up until Witchcraft v2, the architecture was composed of a background script, a content script and the popup window. These are the relevant parts of the manifest file:
10+
11+
```json title="manifest.json (excerpt)"
12+
"content_scripts": [{
13+
"all_frames": true,
14+
"run_at": "document_start",
15+
"matches": ["http://*/*", "https://*/*"],
16+
"js": ["content-script.js"]
17+
}],
18+
"background": {
19+
"page": "background.html",
20+
"persistent": false
21+
},
22+
"content_security_policy": "script-src 'self'; object-src 'self'"
23+
```
24+
25+
This is how it worked:
26+
27+
1. The content script was injected into every frame of every tab (the `content_scripts.matches` property)
28+
29+
2. at `document_start`, the content script sent a message to the background script passing `window.location` as argument:
30+
31+
```js title="content-script.js (excerpt)"
32+
chrome.runtime.sendMessage(location);
33+
```
34+
35+
3. the background would listen for these messages (via `chrome.runtime.onMessage.addListener()`), receive the message and fetch all the scripts that could be applied to that URL
36+
37+
4. the background script would then send each loaded script as text to the content script at, using `chrome.tabs.sendMessage()`, targeting the specific tab and frame
38+
39+
5. the content script would then run the script like so:
40+
41+
```js title="content-script.js (excerpt, simplified version)"
42+
chrome.runtime.onMessage.addListener(scriptContents => {
43+
Function(scriptContents)();
44+
});
45+
```
46+
47+
*(CSS worked similarly, but instead of using `Function()`, it would create a `<style>` element and append it to the document's `<head>`)*
48+
49+
The background script would keep an in-memory cache of what scripts were loaded for which tab/frame, mainly used to show the counter in the extension icon and the list of scripts in the popup window.
50+
51+
### Problems with v2
52+
53+
The first problem appeared when Chrome changed behavior and started unloading extensions at its own will. Extensions that relied on caching data in memory would lose information and stop working - and that affected Witchcraft v2, which started showing erratic behavior. See more about this [here](https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers?utm_source=chatgpt.com#persist-states).
54+
55+
The second (and most important) problem was that Google [announced](https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline) that Manifest v2 was being deprecated and that all extensions should migrate to Manifest v3. Migrating to Manifest v3 was not a simple task, because it introduced a few breaking changes that directly affected Witchcraft.
56+
57+
## Version 3
58+
59+
This was a major rewrite of the extension to comply with Chrome's manifest v3. The extension now has a service worker instead of a background page, and uses the new `chrome.scripting` API to inject scripts into pages - not the [ideal solution](https://github.com/Tampermonkey/tampermonkey/issues/644#issuecomment-1140110430), but works for sites that don't have strict Content Security Policies (CSPs).
60+
61+
The content script was dropped and the service worker now handles the loading and injecting of scripts directly, listening for `chrome.webNavigation.onCommitted` events to know when to inject scripts.
62+
63+
```js title="background.js (excerpt, simplified)"
64+
chrome.webNavigation.onCommitted(async details => {
65+
const { url, tabId, frameId } = details;
66+
await loader.loadScripts(url, tabId, frameId);
67+
});
68+
```
69+
70+
And then eventually:
71+
72+
```js title="inject-js.js (excerpt, simplified)"
73+
chrome.scripting.executeScript({
74+
injectImmediately: true,
75+
target: { tabId: tabId, frameIds: [frameId] },
76+
func: (contents) => Function(contents)(),
77+
args: [contents],
78+
world: "MAIN"
79+
});
80+
```
81+
82+
A breaking change is that now scripts are injected into the "main world" of the page, which means that they have direct access to the page's JavaScript context. This wasn't like that in v2, where scripts were injected into an "isolated world", which meant that they didn't have direct access to the page's JavaScript context and had to manually inject script tags into the page to run code in the page's context.
83+
84+
## Version 3.2
85+
86+
One interesting problem is that `chrome.scripting.executeScript()` respects the Content Security Policy (CSP) of the page, which means that if a page has a CSP that disallows `eval()` (or similar), scripts that use `Function()` or `eval()` will fail to run. The same is also true for pages that disallow the injection of `<script>` tags via the CSP rule `script-src 'self'`.
87+
88+
To solve this, in v3.2 the architecture was changed to use the very recent API `chrome.userScripts.execute()`, which allows injecting scripts into pages without being affected by the page's CSP. The only gotcha is that now the user has to explicitly enable the "Allow user scripts" permission in the extension's settings.
89+
90+
Since few pages are affected by CSP rules (most sites nowadays doesn't seem to enforce strict CSPs), the change was made that if the user hasn't enabled the "Allow user scripts" permission, the extension will fall back to using `chrome.scripting.executeScript()`:
91+
92+
```js title="inject-js.js (excerpt, simplified)"
93+
if (isUserScriptsEnabled()) {
94+
chrome.userScripts.execute({
95+
injectImmediately: true,
96+
target: { tabId: tabId, frameIds: [frameId] },
97+
js: [{
98+
code: contents,
99+
}],
100+
world: "MAIN"
101+
});
102+
} else {
103+
chrome.scripting.executeScript({
104+
injectImmediately: true,
105+
target: { tabId: tabId, frameIds: [frameId] },
106+
func: (contents) => Function(contents)(),
107+
args: [contents],
108+
world: "MAIN"
109+
});
110+
}
111+
```
112+
113+
## Version 3.3
114+
115+
Another outsanding problem with v3 is that scripts are being injected too late when compared to v2. In v2, scripts were injected at `document_start` - before `DOMContentLoaded` - which is the earliest possible moment to run scripts in a page. In v3, `DOMContentLoaded` has already fired by the time the service worker receives the `onCommitted` event.
116+
117+
To fix that, v3.3 brings back the content script that used to exist in v2. That way, we can now be notified at `document_start` and have the chance to load scripts earlier. The difference is that back in v2, the content script was responsible for loading and injecting scripts, while now it just notifies the service worker that a new frame has loaded and the service worker is the one that loads and injects scripts.

docs/src/content/docs/faq.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Witchcraft performs GET requests to your local web server to check if a script e
2525

2626
## How to add third-party libraries to my scripts?
2727

28-
Use the `@include` directive as described [here](/user-guide#including-other-scripts). You can either download the library and include it locally or reference it via a CDN URL. Example:
28+
Use the `@include` directive as described [here](/how-to-use#including-other-scripts). You can either download the library and include it locally or reference it via a CDN URL. Example:
2929

3030
<Code lang="js" code="// @include https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.13/dayjs.min.js" title="include-3rd-party.js" />
3131

docs/src/content/docs/internals/differences-from-v2-to-v3.mdx

Lines changed: 0 additions & 49 deletions
This file was deleted.

docs/src/content/docs/introduction.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ This is what makes Witchcraft different than other similar extensions:
5555

5656
Witchcraft is open source and available on [GitHub](https://github.com/luciopaiva/witchcraft). That means you can load it directly into Chrome as an unpacked extension, if you prefer (which is not the case for other popular extensions like Tampermonkey, which was turned closed source).
5757

58-
For a deeper dive into Witchcraft's features, check out the [user guide](/user-guide).
58+
For a deeper dive into Witchcraft's features, check out the [user guide](/how-to-use).

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

technical-notes.md

Lines changed: 0 additions & 76 deletions
This file was deleted.

0 commit comments

Comments
 (0)