Skip to content

Commit da74b30

Browse files
committed
this commit fixes the issue with firefox's dynamic importation problem: Support Firefox #3.
- add `content_script_extension_adapter.ts`, which now takes the role of being the actual loaded `content_script`, and it dynamically imports `content_script.ts` through the use of `browser.runtime.getURL`, which is the only acceptable way of importing in firefox, as firefox does not allow the use of relative url constant string import. the imported url has to be absolute. - make `content_script.ts` import statically, since dynamic import is no longer required by it, as it is no longer the direct entry point of the actual `content_script`. - this also reduces our code-splitting-chunk output files from 3 to just 1 - in `manifest.json`: - reduce the `web_accessible_resources` matches url to just github, since we do not want other websites to be able to fetch `GET` and sniff our `".js"` files from untrusted domains, should they somehow be able to figure out our extension's UUID. - add `browser_specific_settings`, which is the only way to get storage access permission in firefox when it is loaded as a development extension. I don't know if this key is necessary for publication though.
1 parent 5d58ec8 commit da74b30

File tree

3 files changed

+67
-47
lines changed

3 files changed

+67
-47
lines changed

src/js/content_script.ts

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
11
/// <reference types="https://esm.sh/@types/dom-navigation" />
22

3-
/** `content_script`s cannot import through esm module import syntax.
4-
* thus, we have to resort to dynamic `import()` function, which must be awaited.
5-
* so, for that, we have to create an async iife to execute the script.
6-
* furthermore, the imported script MUST have external-resource security clearance to be imported here.
7-
* for that, we have to specify in "manifest.json": `web_accessible_resources = [{resources: ["*.js"], matches: "<all_urls>"}]`.
8-
* which is basically saying: javascript within "<all_urls>" in this extension can load the resource url_pattern "*.js" (all javascript files).
9-
*/
10-
11-
// dynamic imports of the following are done in `runMain()`:
12-
// import { getCurrentURL, parseRepoEntryPath } from "../lib/typedefs.ts"
13-
// import { injectDiskspaceButton, injectDownloadButton, injectSizeButton } from "../lib/modify_ui.ts"
14-
// { dom_setTimeout, storage } = await import("../lib/deps.ts")
3+
import { dom_setTimeout, storage } from "../lib/deps.ts"
4+
import { injectDiskspaceButton, injectDownloadButton, injectSizeButton } from "../lib/modify_ui.ts"
5+
import { getCurrentURL, parseRepoEntryPath } from "../lib/typedefs.ts"
156

167
declare global {
178
const navigation: Navigation
@@ -29,43 +20,38 @@ const reserved_fullpaths = new Set([
2920
])
3021

3122
const runMain = async () => {
32-
const
33-
{ parseRepoEntryPath, getCurrentURL } = await import("../lib/typedefs.ts"),
34-
{ injectDiskspaceButton, injectDownloadButton, injectSizeButton } = await import("../lib/modify_ui.ts"),
35-
{ dom_setTimeout, storage } = await import("../lib/deps.ts")
36-
37-
const onPageReload = async () => {
38-
const repo_path = parseRepoEntryPath(getCurrentURL())
39-
if (
40-
repo_path &&
41-
!reserved_owners.has(repo_path.owner) &&
42-
!reserved_repos.has(repo_path.repo) &&
43-
!reserved_fullpaths.has(repo_path.fullpath?.split("/")[0])
44-
) {
45-
const layout = await storage.get("layout")
46-
layout.forEach((layout_row, row_number) => {
47-
layout_row?.forEach((layout_cell, column_number) => {
48-
const { feature, span } = layout_cell ?? {}
49-
switch (feature) {
50-
case "size": {
51-
injectSizeButton(row_number, span)
52-
break
53-
} case "download": {
54-
injectDownloadButton(row_number, span)
55-
break
56-
} case "diskspace": {
57-
injectDiskspaceButton(row_number, span)
58-
break
59-
}
60-
default: { break }
23+
const repo_path = parseRepoEntryPath(getCurrentURL())
24+
if (
25+
repo_path &&
26+
!reserved_owners.has(repo_path.owner) &&
27+
!reserved_repos.has(repo_path.repo) &&
28+
!reserved_fullpaths.has(repo_path.fullpath?.split("/")[0])
29+
) {
30+
const layout = await storage.get("layout")
31+
layout.forEach((layout_row, row_number) => {
32+
layout_row?.forEach((layout_cell, column_number) => {
33+
const { feature, span } = layout_cell ?? {}
34+
switch (feature) {
35+
case "size": {
36+
injectSizeButton(row_number, span)
37+
break
38+
} case "download": {
39+
injectDownloadButton(row_number, span)
40+
break
41+
} case "diskspace": {
42+
injectDiskspaceButton(row_number, span)
43+
break
6144
}
62-
})
45+
default: { break }
46+
}
6347
})
64-
}
48+
})
6549
}
50+
}
6651

67-
navigation.addEventListener("navigatesuccess ", () => dom_setTimeout(onPageReload, 300))
68-
await onPageReload()
52+
// BUG: firefox currently does not support `Navigation API`
53+
if (typeof navigation !== "undefined") {
54+
navigation.addEventListener("navigatesuccess ", () => dom_setTimeout(runMain, 300))
6955
}
7056

7157
// all content_scripts are loaded after the `DOMContentLoaded` event. therefore, we should run the script at top level
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// <reference types="npm:web-ext-types" />
2+
3+
/** `content_script`s cannot import through esm module import syntax.
4+
* thus, we have to resort to dynamic `import()` in an async context, in addition to awaiting for it.
5+
*
6+
* furthermore, the imported script MUST have external-resource security clearance to be imported here.
7+
* for that, we have to specify in "manifest.json": `web_accessible_resources = [{resources: ["*.js"], matches: "<all_urls>"}]`.
8+
* which is basically saying: javascript within "<all_urls>" in this extension can load the resource url_pattern "*.js" (all javascript files).
9+
*
10+
* finally, firefox cannot dynamically import with a relative url (relative to this script's location in the extension's root path).
11+
* moreover, you cannot use `import.meta.url` to figure out this script's url, because `content_script`s are not loaded as esm modules, but rather as classical scripts.
12+
* so you have to use `browser.runtime.getURL("/your/module/to_load.js")` with the absolute path of the module (relative to the extension's root url) to get its url.
13+
* in chromium, this isn't an issue, and you can do relative imports and the browser will understand that it's relative to this script's location in the extension.
14+
* to summarize here's what's valid:
15+
* - in chromium: `import("./content_script.ts")`
16+
* - esbuild understands dynamic imports when they are a constant string expression, so `./content_script.ts` will also get bundled and transformed automatically
17+
* - in firefox and chromium: `import(chrome.runtime.getURL("/js/content_script.js"))`
18+
* - esbuild cannot evaluate this kind of dynamic string expression, so it will ignore it and leave it as is.
19+
* that's why we'll have to reference it through its compiled name instead of the source file name.
20+
*/
21+
22+
declare const chrome: typeof browser
23+
const getBrowser = () => {
24+
return typeof browser !== "undefined" ? browser : chrome
25+
}
26+
27+
(async () => {
28+
await import(getBrowser().runtime.getURL("/js/content_script.js"))
29+
})()

src/manifest.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"*://github.com/*"
99
],
1010
"js": [
11-
"./js/content_script.js"
11+
"./js/content_script_extension_adapter.js"
1212
]
1313
}
1414
],
@@ -18,10 +18,15 @@
1818
"*.js"
1919
],
2020
"matches": [
21-
"<all_urls>"
21+
"*://github.com/*"
2222
]
2323
}
2424
],
25+
"browser_specific_settings": {
26+
"gecko": {
27+
"id": "github_aid@temp.com"
28+
}
29+
},
2530
"icons": {
2631
"16": "./icon/eldercat_16.png",
2732
"32": "./icon/eldercat_32.png",

0 commit comments

Comments
 (0)