Skip to content

Commit

Permalink
Make Manifest V3 the default and only build option
Browse files Browse the repository at this point in the history
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 #1210
  • Loading branch information
robertknight committed Dec 1, 2023
1 parent aebc165 commit e94a034
Show file tree
Hide file tree
Showing 7 changed files with 20 additions and 208 deletions.
1 change: 0 additions & 1 deletion settings/chrome-dev.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"buildType": "dev",
"manifestV3": true,

"apiUrl": "http://localhost:5000/api/",
"authDomain": "localhost",
Expand Down
1 change: 0 additions & 1 deletion settings/chrome-prod.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"buildType": "production",
"manifestV3": true,
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq7XsXE/uakq4aKMG5Smz2nc8VSaandriziGorxX08py3mTkab79GpWYu7j/hA3Yf7fkCLQnX8QoZGj7WdaMX6+b+eHxF7vYpOhEW/Bam7TOlb+5AVmL1KReG9PPTLz4dp+xA4WfK2dqFM+XN40FTbm2G/SNk3GRP3gQOxgy3ZKwIDAQAB",


Expand Down
1 change: 0 additions & 1 deletion settings/chrome-qa.json
Original file line number Diff line number Diff line change
@@ -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/",
Expand Down
69 changes: 19 additions & 50 deletions src/background/chrome-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
* - Provide a seam that can be easily mocked in tests
*/

import settings from './settings';

type Callback<Result> = (r: Result) => void;

/**
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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;
Expand All @@ -188,23 +174,15 @@ export async function executeScript(
{ tabId, frameId, file }: ExecuteScriptOptions,
chromeAPI_ = chromeAPI,
): Promise<unknown> {
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<Args extends unknown[], Result> = {
Expand All @@ -228,25 +206,16 @@ export async function executeFunction<Args extends unknown[], Result>(
{ tabId, frameId, func, args }: ExecuteFunctionOptions<Args, Result>,
chromeAPI_ = chromeAPI,
): Promise<Result> {
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) {
Expand Down
1 change: 0 additions & 1 deletion src/background/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
54 changes: 0 additions & 54 deletions src/manifest.json.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -54,73 +48,35 @@
{{/browserIsChrome}}

"permissions": [
{{#manifestV3}}
"scripting",
{{/manifestV3}}

{{^manifestV3}}
"<all_urls>",
{{/manifestV3}}

"storage",
"tabs"
],

{{#manifestV3}}
"host_permissions": ["<all_urls>"],
{{/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/[email protected]"
}
},
{{/manifestV3}}

{{^manifestV3}}
"browser_action": {
"default_icon": {
"19": "images/browser-icon-inactive.png",
"38": "images/[email protected]"
}
},
{{/manifestV3}}

{{#browserIsChrome}}
"externally_connectable": {
{{#bouncerUrl}}"matches": ["{{&bouncerUrl}}*"]{{/bouncerUrl}}
},
{{/browserIsChrome}}

{{#manifestV3}}
"web_accessible_resources": [
{
"resources": [
Expand All @@ -132,14 +88,4 @@
"matches": ["<all_urls>"]
}
]
{{/manifestV3}}

{{^manifestV3}}
"web_accessible_resources": [
"client/*",
"help/*",
"pdfjs/*",
"pdfjs/web/viewer.html"
]
{{/manifestV3}}
}
101 changes: 1 addition & 100 deletions tests/background/chrome-api-test.js
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -49,7 +39,6 @@ describe('chrome-api', () => {
tabs: {
create: sinon.stub(),
get: sinon.stub(),
executeScript: sinon.stub(),
onCreated: fakeListener(),
onReplaced: fakeListener(),
onRemoved: fakeListener(),
Expand Down Expand Up @@ -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';
Expand Down

0 comments on commit e94a034

Please sign in to comment.