From e94a034da1a62882cf7f0cd3d212538e4c75cdee Mon Sep 17 00:00:00 2001 From: Robert Knight Date: Thu, 30 Nov 2023 13:52:35 +0000 Subject: [PATCH] Make Manifest V3 the default and only build option Remove support for building the extension using Manifest V2. We've been shipping our extension as Manifest V3 since April 2023 and have not encountered any issues. Removing Manifest V2 support will simplify future development. Fixes https://github.com/hypothesis/browser-extension/issues/1210 --- settings/chrome-dev.json | 1 - settings/chrome-prod.json | 1 - settings/chrome-qa.json | 1 - src/background/chrome-api.ts | 69 ++++++------------- src/background/settings.ts | 1 - src/manifest.json.mustache | 54 --------------- tests/background/chrome-api-test.js | 101 +--------------------------- 7 files changed, 20 insertions(+), 208 deletions(-) diff --git a/settings/chrome-dev.json b/settings/chrome-dev.json index b4baf8cf..3d64a49a 100644 --- a/settings/chrome-dev.json +++ b/settings/chrome-dev.json @@ -1,6 +1,5 @@ { "buildType": "dev", - "manifestV3": true, "apiUrl": "http://localhost:5000/api/", "authDomain": "localhost", diff --git a/settings/chrome-prod.json b/settings/chrome-prod.json index 59b022cb..2cdc5104 100644 --- a/settings/chrome-prod.json +++ b/settings/chrome-prod.json @@ -1,6 +1,5 @@ { "buildType": "production", - "manifestV3": true, "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq7XsXE/uakq4aKMG5Smz2nc8VSaandriziGorxX08py3mTkab79GpWYu7j/hA3Yf7fkCLQnX8QoZGj7WdaMX6+b+eHxF7vYpOhEW/Bam7TOlb+5AVmL1KReG9PPTLz4dp+xA4WfK2dqFM+XN40FTbm2G/SNk3GRP3gQOxgy3ZKwIDAQAB", diff --git a/settings/chrome-qa.json b/settings/chrome-qa.json index 3d0e525a..04cf756b 100644 --- a/settings/chrome-qa.json +++ b/settings/chrome-qa.json @@ -1,6 +1,5 @@ { "buildType": "qa", - "manifestV3": true, "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqjbEOhG+ZCl2Bl17m2ltNC+3uw0Fqv3Dzuja5vLnH1MLBRQG7L77pXtKCZgVgFJ2K+Kn0L0OqnMDcKEi5pUpNTi39b8twp1imDsoLO+L5XgpKYBtUgfR+T8OO2INjEgz0LDth0l26WmHNS377KZjSTsfPWNnLozXHHkETgug1lt9VzgcvSboiyZuwk23xHmiqnVpZtuqVAv4HdqFofHiNQn2fF7awsQxEYYNfuSk0Jp33XJkkadyrJ/dQ7vVFi0F0O//Oyaw3s4TD58frABxznusmKkjHZorJUrm2OaYbn/7TSUcG5fReQC08fXiMsFGUKxK01HfAwdmVUAmASL+NwIDAQAB", "apiUrl": "https://qa.hypothes.is/api/", diff --git a/src/background/chrome-api.ts b/src/background/chrome-api.ts index 25b1de9d..195593e6 100644 --- a/src/background/chrome-api.ts +++ b/src/background/chrome-api.ts @@ -8,8 +8,6 @@ * - Provide a seam that can be easily mocked in tests */ -import settings from './settings'; - type Callback = (r: Result) => void; /** @@ -122,12 +120,8 @@ export function getChromeAPI(chrome = globalThis.chrome) { onUpdated: chrome.tabs.onUpdated, query: promisifyAlt(chrome.tabs.query), update: promisify(chrome.tabs.update), - - // Manifest V2 only. - executeScript: promisifyAlt(chrome.tabs.executeScript), }, - // Manifest V3 only. scripting: { executeScript: chrome.scripting?.executeScript, }, @@ -164,16 +158,8 @@ export function getChromeAPI(chrome = globalThis.chrome) { export const chromeAPI = getChromeAPI(); // The functions below are wrappers around the extension APIs for scripting -// which abstract over differences between browsers (eg. Manifest V2 vs Manifest V3) -// and provide a simpler and more strongly typed interface. - -/** - * Generate a string of code which can be eval-ed to produce the same result - * as invoking `func` with `args`. - */ -function codeStringForFunctionCall(func: () => void, args: unknown[]) { - return `(${func})(${args.map(arg => JSON.stringify(arg)).join(',')})`; -} +// which abstract over differences between browsers and provide a simpler and +// more strongly typed interface. export type ExecuteScriptOptions = { tabId: number; @@ -188,23 +174,15 @@ export async function executeScript( { tabId, frameId, file }: ExecuteScriptOptions, chromeAPI_ = chromeAPI, ): Promise { - if (settings.manifestV3) { - const target: chrome.scripting.InjectionTarget = { tabId }; - if (frameId) { - target.frameIds = [frameId]; - } - const results = await chromeAPI_.scripting.executeScript({ - target, - files: [file], - }); - return results[0].result; + const target: chrome.scripting.InjectionTarget = { tabId }; + if (frameId) { + target.frameIds = [frameId]; } - - const result = (await chromeAPI_.tabs.executeScript(tabId, { - frameId, - file, - })) as unknown[]; - return result[0]; + const results = await chromeAPI_.scripting.executeScript({ + target, + files: [file], + }); + return results[0].result; } export type ExecuteFunctionOptions = { @@ -228,25 +206,16 @@ export async function executeFunction( { tabId, frameId, func, args }: ExecuteFunctionOptions, chromeAPI_ = chromeAPI, ): Promise { - if (settings.manifestV3) { - const target: chrome.scripting.InjectionTarget = { tabId }; - if (frameId) { - target.frameIds = [frameId]; - } - const results = await chromeAPI_.scripting.executeScript({ - target, - func, - args, - }); - return results[0].result as Result; + const target: chrome.scripting.InjectionTarget = { tabId }; + if (frameId) { + target.frameIds = [frameId]; } - - const code = codeStringForFunctionCall(func, args); - const result = (await chromeAPI_.tabs.executeScript(tabId, { - frameId, - code, - })) as Result[]; - return result[0]; + const results = await chromeAPI_.scripting.executeScript({ + target, + func, + args, + }); + return results[0].result as Result; } export function getExtensionId(chromeAPI_ = chromeAPI) { diff --git a/src/background/settings.ts b/src/background/settings.ts index 6a1d98f8..d3f8eb11 100644 --- a/src/background/settings.ts +++ b/src/background/settings.ts @@ -8,7 +8,6 @@ export type Settings = { apiUrl: string; buildType: string; serviceUrl: string; - manifestV3?: boolean; }; // nb. This will error if the build has not been run yet. diff --git a/src/manifest.json.mustache b/src/manifest.json.mustache index 46de0464..2a33dc66 100644 --- a/src/manifest.json.mustache +++ b/src/manifest.json.mustache @@ -5,13 +5,7 @@ {{#browserIsChrome}} "version_name": "{{ version }} ({{ versionName }})", {{/browserIsChrome}} - - {{#manifestV3}} "manifest_version": 3, - {{/manifestV3}} - {{^manifestV3}} - "manifest_version": 2, - {{/manifestV3}} {{#browserIsChrome}} "minimum_chrome_version": "88", @@ -54,65 +48,28 @@ {{/browserIsChrome}} "permissions": [ - {{#manifestV3}} "scripting", - {{/manifestV3}} - - {{^manifestV3}} - "", - {{/manifestV3}} - "storage", "tabs" ], - {{#manifestV3}} "host_permissions": [""], - {{/manifestV3}} "optional_permissions": [ {{! Used to enumerate frames on certain websites. }} "webNavigation" ], - {{^manifestV3}} - "content_security_policy": "script-src 'self'; object-src 'self'", - {{/manifestV3}} - - {{#manifestV3}} "background": { "service_worker": "extension.bundle.js" }, - {{/manifestV3}} - {{^manifestV3}} - "background": { - {{#browserIsChrome}} - "persistent": true, - {{/browserIsChrome}} - "scripts": [ - "extension.bundle.js" - ] - }, - {{/manifestV3}} - - {{#manifestV3}} "action": { "default_icon": { "19": "images/browser-icon-inactive.png", "38": "images/browser-icon-inactive@2x.png" } }, - {{/manifestV3}} - - {{^manifestV3}} - "browser_action": { - "default_icon": { - "19": "images/browser-icon-inactive.png", - "38": "images/browser-icon-inactive@2x.png" - } - }, - {{/manifestV3}} {{#browserIsChrome}} "externally_connectable": { @@ -120,7 +77,6 @@ }, {{/browserIsChrome}} - {{#manifestV3}} "web_accessible_resources": [ { "resources": [ @@ -132,14 +88,4 @@ "matches": [""] } ] - {{/manifestV3}} - - {{^manifestV3}} - "web_accessible_resources": [ - "client/*", - "help/*", - "pdfjs/*", - "pdfjs/web/viewer.html" - ] - {{/manifestV3}} } diff --git a/tests/background/chrome-api-test.js b/tests/background/chrome-api-test.js index fea0c877..30b646a1 100644 --- a/tests/background/chrome-api-test.js +++ b/tests/background/chrome-api-test.js @@ -1,14 +1,4 @@ -import { - getChromeAPI, - executeFunction, - executeScript, - getExtensionId, -} from '../../src/background/chrome-api'; - -// Helper defined at top level to simplify its stringified representation. -function testFunc(a, b) { - return a + b; -} +import { getChromeAPI, getExtensionId } from '../../src/background/chrome-api'; describe('chrome-api', () => { describe('getChromeAPI', () => { @@ -49,7 +39,6 @@ describe('chrome-api', () => { tabs: { create: sinon.stub(), get: sinon.stub(), - executeScript: sinon.stub(), onCreated: fakeListener(), onReplaced: fakeListener(), onRemoved: fakeListener(), @@ -140,94 +129,6 @@ describe('chrome-api', () => { }); }); - describe('executeFunction', () => { - let fakeChromeAPI; - - beforeEach(() => { - fakeChromeAPI = { - tabs: { - executeScript: sinon.stub().resolves(['result']), - }, - }; - }); - - it('calls `chrome.tabs.executeScript` with stringified source', async () => { - const result = await executeFunction( - { - tabId: 1, - func: testFunc, - args: [1, 2], - }, - fakeChromeAPI, - ); - assert.calledWith(fakeChromeAPI.tabs.executeScript, 1, { - frameId: undefined, - code: '(function testFunc(a, b) {\n return a + b;\n})(1,2)', - }); - assert.equal(result, 'result'); - }); - - it('sets frame ID if provided', async () => { - const result = await executeFunction( - { - tabId: 1, - frameId: 2, - func: testFunc, - args: [1, 2], - }, - fakeChromeAPI, - ); - assert.calledWith(fakeChromeAPI.tabs.executeScript, 1, { - frameId: 2, - code: '(function testFunc(a, b) {\n return a + b;\n})(1,2)', - }); - assert.equal(result, 'result'); - }); - }); - - describe('executeScript', () => { - let fakeChromeAPI; - - beforeEach(() => { - fakeChromeAPI = { - tabs: { - executeScript: sinon.stub().resolves(['result']), - }, - }; - }); - - it('calls `chrome.tabs.executeScript` with files', async () => { - const result = await executeScript( - { - tabId: 1, - file: 'foo.js', - }, - fakeChromeAPI, - ); - assert.calledWith(fakeChromeAPI.tabs.executeScript, 1, { - frameId: undefined, - file: 'foo.js', - }); - assert.equal(result, 'result'); - }); - - it('sets frame ID if provided', async () => { - const result = await executeScript( - { - tabId: 1, - frameId: 2, - file: 'foo.js', - }, - fakeChromeAPI, - ); - assert.calledWith(fakeChromeAPI.tabs.executeScript, 1, { - frameId: 2, - file: 'foo.js', - }); - assert.deepEqual(result, 'result'); - }); - }); - describe('getExtensionId', () => { let fakeChromeAPI; const id = 'hypothesisId';