2019-07-30 20:07:40 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
2020-10-08 06:25:39 +03:00
|
|
|
/* globals browser, module */
|
2019-07-30 20:07:40 +03:00
|
|
|
|
|
|
|
class Injections {
|
2019-09-24 16:08:42 +03:00
|
|
|
constructor(availableInjections, customFunctions) {
|
2019-07-30 20:07:40 +03:00
|
|
|
this.INJECTION_PREF = "perform_injections";
|
|
|
|
|
|
|
|
this._injectionsEnabled = true;
|
|
|
|
|
|
|
|
this._availableInjections = availableInjections;
|
2023-09-19 00:12:05 +03:00
|
|
|
this._activeInjections = new Set();
|
|
|
|
// Only used if this.shouldUseScriptingAPI is false and we are falling back
|
|
|
|
// to use the contentScripts API.
|
|
|
|
this._activeInjectionHandles = new Map();
|
2019-09-24 16:08:42 +03:00
|
|
|
this._customFunctions = customFunctions;
|
2023-09-19 00:12:05 +03:00
|
|
|
|
|
|
|
this.shouldUseScriptingAPI =
|
|
|
|
browser.aboutConfigPrefs.getBoolPrefSync("useScriptingAPI");
|
|
|
|
// Debug log emit only on nightly (similarly to the debug
|
|
|
|
// helper used in shims.js for similar purpose).
|
|
|
|
browser.appConstants.getReleaseBranch().then(releaseBranch => {
|
|
|
|
if (releaseBranch !== "release_or_beta") {
|
|
|
|
console.debug(
|
|
|
|
`WebCompat Injections will be injected using ${
|
|
|
|
this.shouldUseScriptingAPI ? "scripting" : "contentScripts"
|
|
|
|
} API`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
2019-07-30 20:07:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
bindAboutCompatBroker(broker) {
|
|
|
|
this._aboutCompatBroker = broker;
|
|
|
|
}
|
|
|
|
|
|
|
|
bootup() {
|
|
|
|
browser.aboutConfigPrefs.onPrefChange.addListener(() => {
|
|
|
|
this.checkInjectionPref();
|
|
|
|
}, this.INJECTION_PREF);
|
|
|
|
this.checkInjectionPref();
|
|
|
|
}
|
|
|
|
|
|
|
|
checkInjectionPref() {
|
|
|
|
browser.aboutConfigPrefs.getPref(this.INJECTION_PREF).then(value => {
|
|
|
|
if (value === undefined) {
|
|
|
|
browser.aboutConfigPrefs.setPref(this.INJECTION_PREF, true);
|
|
|
|
} else if (value === false) {
|
|
|
|
this.unregisterContentScripts();
|
|
|
|
} else {
|
|
|
|
this.registerContentScripts();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getAvailableInjections() {
|
|
|
|
return this._availableInjections;
|
|
|
|
}
|
|
|
|
|
|
|
|
isEnabled() {
|
|
|
|
return this._injectionsEnabled;
|
|
|
|
}
|
|
|
|
|
2023-09-19 00:12:05 +03:00
|
|
|
async getPromiseRegisteredScriptIds(scriptIds) {
|
|
|
|
let registeredScriptIds = [];
|
|
|
|
|
|
|
|
// Try to avoid re-registering scripts already registered
|
|
|
|
// (e.g. if the webcompat background page is restarted
|
|
|
|
// after an extension process crash, after having registered
|
|
|
|
// the content scripts already once), but do not prevent
|
|
|
|
// to try registering them again if the getRegisteredContentScripts
|
|
|
|
// method returns an unexpected rejection.
|
|
|
|
try {
|
|
|
|
const registeredScripts =
|
|
|
|
await browser.scripting.getRegisteredContentScripts({
|
|
|
|
// By default only look for script ids that belongs to Injections
|
|
|
|
// (and ignore the ones that may belong to Shims).
|
|
|
|
ids: scriptIds ?? this._availableInjections.map(inj => inj.id),
|
|
|
|
});
|
|
|
|
registeredScriptIds = registeredScripts.map(script => script.id);
|
|
|
|
} catch (ex) {
|
|
|
|
console.error(
|
|
|
|
"Retrieve WebCompat GoFaster registered content scripts failed: ",
|
|
|
|
ex
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return registeredScriptIds;
|
|
|
|
}
|
|
|
|
|
2019-07-30 20:07:40 +03:00
|
|
|
async registerContentScripts() {
|
2022-11-11 01:22:03 +03:00
|
|
|
const platformInfo = await browser.runtime.getPlatformInfo();
|
|
|
|
const platformMatches = [
|
|
|
|
"all",
|
|
|
|
platformInfo.os,
|
|
|
|
platformInfo.os == "android" ? "android" : "desktop",
|
|
|
|
];
|
2023-09-19 00:12:05 +03:00
|
|
|
|
|
|
|
let registeredScriptIds = this.shouldUseScriptingAPI
|
|
|
|
? await this.getPromiseRegisteredScriptIds()
|
|
|
|
: [];
|
|
|
|
|
2019-07-30 20:07:40 +03:00
|
|
|
for (const injection of this._availableInjections) {
|
|
|
|
if (platformMatches.includes(injection.platform)) {
|
|
|
|
injection.availableOnPlatform = true;
|
2023-09-19 00:12:05 +03:00
|
|
|
await this.enableInjection(injection, registeredScriptIds);
|
2019-07-30 20:07:40 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._injectionsEnabled = true;
|
|
|
|
this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
|
|
|
|
interventionsChanged: this._aboutCompatBroker.filterOverrides(
|
|
|
|
this._availableInjections
|
|
|
|
),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-09-19 00:12:05 +03:00
|
|
|
buildContentScriptRegistrations(contentScripts) {
|
2019-09-24 16:08:42 +03:00
|
|
|
let finalConfig = Object.assign({}, contentScripts);
|
|
|
|
|
|
|
|
if (!finalConfig.runAt) {
|
|
|
|
finalConfig.runAt = "document_start";
|
|
|
|
}
|
|
|
|
|
2023-09-19 00:12:05 +03:00
|
|
|
if (this.shouldUseScriptingAPI) {
|
|
|
|
// Don't persist the content scripts across browser restarts
|
|
|
|
// (at least not yet, we would need to apply some more changes
|
|
|
|
// to adjust webcompat for accounting for the scripts to be
|
|
|
|
// already registered).
|
|
|
|
//
|
|
|
|
// NOTE: scripting API has been introduced in Gecko 102,
|
|
|
|
// prior to Gecko 105 persistAcrossSessions option was required
|
|
|
|
// and only accepted false persistAcrossSessions, after Gecko 105
|
|
|
|
// is optional and defaults to true.
|
|
|
|
|
|
|
|
finalConfig.persistAcrossSessions = false;
|
|
|
|
|
|
|
|
// Convert js/css from contentScripts.register API method
|
|
|
|
// format to scripting.registerContentScripts API method
|
|
|
|
// format.
|
|
|
|
if (Array.isArray(finalConfig.js)) {
|
|
|
|
finalConfig.js = finalConfig.js.map(e => e.file);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(finalConfig.css)) {
|
|
|
|
finalConfig.css = finalConfig.css.map(e => e.file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-24 16:08:42 +03:00
|
|
|
return finalConfig;
|
2019-07-30 20:07:40 +03:00
|
|
|
}
|
|
|
|
|
2023-09-19 00:12:05 +03:00
|
|
|
async enableInjection(injection, registeredScriptIds) {
|
2019-07-30 20:07:40 +03:00
|
|
|
if (injection.active) {
|
2019-09-24 16:08:42 +03:00
|
|
|
return undefined;
|
2019-07-30 20:07:40 +03:00
|
|
|
}
|
|
|
|
|
2019-09-24 16:08:42 +03:00
|
|
|
if (injection.customFunc) {
|
|
|
|
return this.enableCustomInjection(injection);
|
|
|
|
}
|
|
|
|
|
2023-09-19 00:12:05 +03:00
|
|
|
return this.enableContentScripts(injection, registeredScriptIds);
|
2019-09-24 16:08:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
enableCustomInjection(injection) {
|
|
|
|
if (injection.customFunc in this._customFunctions) {
|
|
|
|
this._customFunctions[injection.customFunc](injection);
|
2019-07-30 20:07:40 +03:00
|
|
|
injection.active = true;
|
2019-09-24 16:08:42 +03:00
|
|
|
} else {
|
|
|
|
console.error(
|
2020-03-09 00:45:16 +03:00
|
|
|
`Provided function ${injection.customFunc} wasn't found in functions list`
|
2019-09-24 16:08:42 +03:00
|
|
|
);
|
2019-07-30 20:07:40 +03:00
|
|
|
}
|
2019-09-24 16:08:42 +03:00
|
|
|
}
|
2019-07-30 20:07:40 +03:00
|
|
|
|
2023-09-19 00:12:05 +03:00
|
|
|
async enableContentScripts(injection, registeredScriptIds) {
|
|
|
|
let injectProps;
|
2019-07-30 20:07:40 +03:00
|
|
|
try {
|
2023-09-19 00:12:05 +03:00
|
|
|
const { id } = injection;
|
|
|
|
if (this.shouldUseScriptingAPI) {
|
|
|
|
// enableContentScripts receives a registeredScriptIds already
|
|
|
|
// pre-computed once from registerContentScripts to register all
|
|
|
|
// the injection, whereas it does not expect to receive one when
|
|
|
|
// it is called from the AboutCompatBroker to re-enable one specific
|
|
|
|
// injection.
|
|
|
|
let activeScriptIds = Array.isArray(registeredScriptIds)
|
|
|
|
? registeredScriptIds
|
|
|
|
: await this.getPromiseRegisteredScriptIds([id]);
|
|
|
|
injectProps = this.buildContentScriptRegistrations(
|
|
|
|
injection.contentScripts
|
|
|
|
);
|
|
|
|
injectProps.id = id;
|
|
|
|
if (!activeScriptIds.includes(id)) {
|
|
|
|
await browser.scripting.registerContentScripts([injectProps]);
|
|
|
|
}
|
|
|
|
this._activeInjections.add(id);
|
|
|
|
} else {
|
|
|
|
const handle = await browser.contentScripts.register(
|
|
|
|
this.buildContentScriptRegistrations(injection.contentScripts)
|
|
|
|
);
|
|
|
|
this._activeInjections.add(id);
|
|
|
|
this._activeInjectionHandles.set(id, handle);
|
|
|
|
}
|
|
|
|
|
2019-07-30 20:07:40 +03:00
|
|
|
injection.active = true;
|
|
|
|
} catch (ex) {
|
|
|
|
console.error(
|
|
|
|
"Registering WebCompat GoFaster content scripts failed: ",
|
2023-09-19 00:12:05 +03:00
|
|
|
{ injection, injectProps },
|
2019-07-30 20:07:40 +03:00
|
|
|
ex
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unregisterContentScripts() {
|
|
|
|
for (const injection of this._availableInjections) {
|
|
|
|
this.disableInjection(injection);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._injectionsEnabled = false;
|
|
|
|
this._aboutCompatBroker.portsToAboutCompatTabs.broadcast({
|
|
|
|
interventionsChanged: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async disableInjection(injection) {
|
|
|
|
if (!injection.active) {
|
2019-09-24 16:08:42 +03:00
|
|
|
return undefined;
|
2019-07-30 20:07:40 +03:00
|
|
|
}
|
|
|
|
|
2019-09-24 16:08:42 +03:00
|
|
|
if (injection.customFunc) {
|
|
|
|
return this.disableCustomInjections(injection);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.disableContentScripts(injection);
|
|
|
|
}
|
|
|
|
|
|
|
|
disableCustomInjections(injection) {
|
|
|
|
const disableFunc = injection.customFunc + "Disable";
|
|
|
|
|
|
|
|
if (disableFunc in this._customFunctions) {
|
|
|
|
this._customFunctions[disableFunc](injection);
|
2019-07-30 20:07:40 +03:00
|
|
|
injection.active = false;
|
2019-09-24 16:08:42 +03:00
|
|
|
} else {
|
|
|
|
console.error(
|
|
|
|
`Provided function ${disableFunc} for disabling injection wasn't found in functions list`
|
|
|
|
);
|
2019-07-30 20:07:40 +03:00
|
|
|
}
|
2019-09-24 16:08:42 +03:00
|
|
|
}
|
2019-07-30 20:07:40 +03:00
|
|
|
|
2019-09-24 16:08:42 +03:00
|
|
|
async disableContentScripts(injection) {
|
2023-09-19 00:12:05 +03:00
|
|
|
if (this._activeInjections.has(injection.id)) {
|
|
|
|
if (this.shouldUseScriptingAPI) {
|
|
|
|
await browser.scripting.unregisterContentScripts({
|
|
|
|
ids: [injection.id],
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const handle = this._activeInjectionHandles.get(injection.id);
|
|
|
|
await handle.unregister();
|
|
|
|
this._activeInjectionHandles.delete(injection.id);
|
|
|
|
}
|
|
|
|
this._activeInjections.delete(injection);
|
|
|
|
}
|
2019-07-30 20:07:40 +03:00
|
|
|
injection.active = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Injections;
|