Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ In addition:
* `storage` for settings
* `notifications` for showing notification
* `activeTab` for requesting page info
* `scripting` for getting text selection on the page

# Building & developing
The most up-to-date instructions should be in [CI config](./.circleci/config.yml).
Expand Down
5 changes: 5 additions & 0 deletions extension/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
- manifest version: both chrome and firefox store versions are on manifest v3 now

# permissions used
See main `README.md`.

# `package.json` comments
## browserslist
Not used by the extension (since `rollup-plugin-typescript` is looking at `tsconfig.json`), but it's used by babel/jest?
Expand Down
40 changes: 21 additions & 19 deletions extension/build
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env python3
import argparse
import json
import os
from subprocess import check_call
from pathlib import Path
import subprocess
import sys
from pathlib import Path
from subprocess import check_call

# right, specifying id in manifest doesn't seem to work
# AMO responds with: Server response: Duplicate add-on ID found. (status: 400)
Expand Down Expand Up @@ -71,9 +72,8 @@ def main() -> None:
'--output=json',
'--no-config-discovery', # prevent it from printing "Applying config file" to stdout
silent=True, # otherwise craps in stdout
method=lambda cmd: subprocess.run(cmd, stdout=subprocess.PIPE, text=True),
method=lambda cmd: subprocess.run(cmd, check=False, stdout=subprocess.PIPE, text=True),
)
import json
j = json.loads(res.stdout)
errors = j['errors']
if len(errors) == 0:
Expand All @@ -97,7 +97,7 @@ def main() -> None:
}

if args.watch:
check_call([npm, 'run', 'watch'], env=env, cwd=str(THISDIR)) # TODO exec instead?
check_call([npm, 'run', 'watch'], env=env, cwd=str(THISDIR)) # todo exec instead?
return

check_call([npm, 'run', 'build'], env=env, cwd=str(THISDIR))
Expand All @@ -108,32 +108,34 @@ def main() -> None:
webext_lint()

# TODO move somewhere more appropriate..
webext('build', '-o') # -o overwrites existing artifact
webext('build', '-o') # -o overwrites existing artifact

if args.release:
assert args.lint # TODO not sure..

def firefox_publish_args():
from firefox_dev_secrets import API_KEY, API_SECRET
return [
'--artifacts-dir', str(artifacts_dir),
'--api-key' , API_KEY,
'--api-secret' , API_SECRET,
# seems like webext sign requires addon id to be in manifest now
]
assert args.lint # TODO not sure..

if args.publish not in {None, 'skip'}:
# 'skip' mode is useful to build exactly same build as for the store, but without actually uploading
assert args.lint
assert args.release

check_call(['git', 'diff-index', 'HEAD']) # ensure repo is clean
source_zip = base_ext_dir / f'extension-source-{target}.zip'
check_call(['git', 'archive', 'HEAD', '--output', source_zip], cwd=THISDIR) # cwd so we only package the extension

if 'firefox' in target:
from firefox_dev_secrets import API_KEY, API_SECRET
check_call([
npm, 'run', 'web-ext',
'--',
'sign', '--use-submission-api',
'sign',
'--channel', args.publish,
'--source-dir', str(ext_dir),
*firefox_publish_args(),
'--artifacts-dir', str(artifacts_dir),
'--api-key' , API_KEY,
'--api-secret' , API_SECRET,
# seems like webext sign requires addon id to be in manifest now
'--amo-metadata' , THISDIR / 'amo-metadata.json',
'--upload-source-code', source_zip,
])
elif target == 'chrome':
assert args.publish == 'listed' # no notion of unlisted on chrome store?
Expand All @@ -151,7 +153,7 @@ def main() -> None:
# TODO trusted testers?
])
else:
raise RuntimeError("{target} is not supported for publishing yet".format(target=target))
raise RuntimeError(f"{target} is not supported for publishing yet")

if __name__ == '__main__':
main()
11 changes: 6 additions & 5 deletions extension/generate_manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function generateManifest({


// TODO make permissions literate
// keep in sync with readme
const permissions = [
// for keeping extension settings
"storage",
Expand All @@ -90,19 +91,16 @@ export function generateManifest({
if (v3) {
if (target === T.CHROME) {
// webext lint will warn about this since it's not supported in firefox yet
// see https://github.com/mozilla/web-ext/issues/2916
background['service_worker'] = 'background.js'

// this isn't supported in chrome manifest v3 (chrome warns about unsupported field)
// but without it webext lint fails
background['scripts'] = ['background.js']
} else {
background['scripts'] = ['background.js']
}
} else {
background['scripts'] = ['background.js']
background['persistent'] = false
}
background['type'] = 'module' // hmm seems like it works in firefox v2 too now??
background['type'] = 'module'

const content_scripts = []

Expand Down Expand Up @@ -169,6 +167,9 @@ export function generateManifest({
manifest['browser_specific_settings'] = {
'gecko': {
'id': gecko_id,
'data_collection_permissions': { // required for new firefox addons
'required': ['none']
},
},
}
}
Expand Down
6 changes: 3 additions & 3 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@
"@types/webextension-polyfill": "^0.12.4",
"chrome-webstore-upload-cli": "^3.1.0",
"eslint": "^9.39.2",
"globals": "^16.5.0",
"globals": "^17.0.0",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"jest-fetch-mock": "^3.0.3",
"rollup": "^4.18.0",
"rollup": "^4.53.5",
"rollup-plugin-copy": "^3.5.0",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.0",
"typescript-eslint": "^8.51.0",
"web-ext": "^9.2.0",
"webextension-polyfill": "^0.12.0"
},
Expand Down
3 changes: 2 additions & 1 deletion extension/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import assert from 'assert'
import fs from 'fs'
const { globSync } = import('node:fs')
import path from 'path'
import { fileURLToPath } from 'url'

Expand Down Expand Up @@ -78,6 +77,8 @@ const compile = inputs => { return {
// format: 'esm', // default??
// format: 'iife', // inlines? e.g. could use for bg page if we disable splitting..

chunkFileNames: '[name].js', // instead of '[name]-[hash].js' -- otherwise the emitted filenames change every time, very annoying for tracking in git

// huh! so if I build all files in one go, it figures out the shared files properly it seems
// however it still inlines webextension stuff into one of the files? e.g. common
manualChunks: id => { // ugh, seems a bit shit?
Expand Down
2 changes: 1 addition & 1 deletion tests/test_end2end.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def test_capture_custom_endpoint(*, addon: Addon, driver: Driver, backend: Backe
# due to broad permissions given by content script to detect dark mode and set icon accordingly.
# See comment about detect_dark_mode.js in generate_manifest.js
wait_for_permissions = browser.name != 'firefox'
if wait_for_permissions:
if wait_for_permissions and browser.headless:
pytest.skip("This test requires GUI to confirm permission prompts")
addon.options_page.change_endpoint(
endpoint=f'http://{hostname}:{backend.port}/capture',
Expand Down
6 changes: 5 additions & 1 deletion tests/webdriver_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ def get_webdriver(
# regular --headless doesn't support extensions for some reason
cr_options.add_argument('--headless=new')

# not sure this does anything??
cr_options.set_capability('goog:loggingPrefs', {'browser': 'ALL'})

# ugh. this is necessary for chrome to consider extension pages as part of normal tabs
# https://github.com/SeleniumHQ/selenium/issues/15685
# https://issues.chromium.org/issues/416666972
Expand All @@ -229,7 +232,8 @@ def get_webdriver(
# generally 'selenium manager' downloads the correct driver version itself
chromedriver_bin: str | None = None # default

service = webdriver.ChromeService(executable_path=chromedriver_bin)
# 2 means stderr (seems like otherwise it's not logging at all)
service = webdriver.ChromeService(executable_path=chromedriver_bin, log_output=2)
driver = webdriver.Chrome(service=service, options=cr_options)

version_data['chromedriverVersion'] = driver.capabilities['chrome']['chromedriverVersion']
Expand Down
Loading