diff --git a/manifest.json b/manifest.json index 3b4cceb6..900d106f 100644 --- a/manifest.json +++ b/manifest.json @@ -32,18 +32,7 @@ }, "default_title": "__MSG_manifest_action_default_title__" }, - "content_scripts": [ - { - "matches": [ - "" - ], - "js": [ - "content.js" - ], - "all_frames": true, - "run_at": "document_start" - } - ], + "commands": { "renew-useragent": { "description": "__MSG_manifest_command_renew_useragent__", @@ -59,6 +48,7 @@ "tabs", "alarms", "storage", + "scripting", "declarativeNetRequest" ], "$what_the_specified_permissions_are_for": { diff --git a/src/entrypoints/background/hooks/index.ts b/src/entrypoints/background/hooks/index.ts index c390f172..ca1bd4ce 100644 --- a/src/entrypoints/background/hooks/index.ts +++ b/src/entrypoints/background/hooks/index.ts @@ -1,2 +1,3 @@ export { setRequestHeaders, unsetRequestHeaders } from './http-requests' export { setBridgeData, unsetBridgeData } from './content-script-bridge' +export { registerContentScripts } from './scripting' diff --git a/src/entrypoints/background/hooks/scripting.ts b/src/entrypoints/background/hooks/scripting.ts new file mode 100644 index 00000000..be2b8c92 --- /dev/null +++ b/src/entrypoints/background/hooks/scripting.ts @@ -0,0 +1,41 @@ +import RegisteredContentScript = chrome.scripting.RegisteredContentScript + +// the common properties for the content scripts +const common: Omit = { + matches: [''], + allFrames: true, + runAt: 'document_start', +} + +// properties for the content script that will be executed in the isolated world (as a content script) +const content: RegisteredContentScript = { ...common, id: 'content', js: ['content.js'] } + +// properties for the content script that will be executed in the main world (as an injected script) +const inject: RegisteredContentScript = { ...common, id: 'inject', js: [__UNIQUE_INJECT_FILENAME__] } + +/** Register the content scripts */ +export async function registerContentScripts() { + // first, unregister (probably) previously registered content scripts + await chrome.scripting.unregisterContentScripts() + + try { + await chrome.scripting.registerContentScripts([ + { ...content, world: 'ISOLATED' }, + { ...inject, world: 'MAIN' }, + ]) + } catch (err) { + if ( + err instanceof Error && + err.message.toLowerCase().includes('unexpected property') && + err.message.includes('world') + ) { + // if so, it means that the "world" property is not supported by the current browser (FireFox at this moment) + // so we need to register the content scripts without the "world" property + // + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/RegisteredContentScript#browser_compatibility + return await chrome.scripting.registerContentScripts([content, inject]) + } + + throw err + } +} diff --git a/src/entrypoints/background/index.ts b/src/entrypoints/background/index.ts index 81fa495a..0d37ebda 100644 --- a/src/entrypoints/background/index.ts +++ b/src/entrypoints/background/index.ts @@ -2,7 +2,7 @@ import { checkPermissions, detectBrowser, watchPermissionsChange } from '~/share import { type HandlersMap, listen as listenRuntime } from '~/shared/messaging' import { newErrorEvent, newExtensionLoadedEvent } from '~/shared/stats' import { isApplicableForDomain, reloadRequestHeadersAndBridge, renewUserAgent, updateRemoteUserAgentList } from './api' -import { unsetBridgeData, unsetRequestHeaders } from './hooks' +import { registerContentScripts, unsetBridgeData, unsetRequestHeaders } from './hooks' import { registerHotkeys } from './hotkeys' import { CurrentUserAgent, RemoteUserAgentList, Settings, StorageArea, UserID } from './persistent' import { type Collector as StatsCollector, GaCollector } from './stats' @@ -17,6 +17,9 @@ let stats: StatsCollector | undefined = undefined // run the background script ;(async () => { + // register the content scripts + await registerContentScripts() + // at least FireFox does not allow the extension to work with all URLs by default, and the user must grant the // necessary permissions. in addition, the user can revoke the permissions at any time, so we need to monitor the // changes in the permissions and open the onboarding page if the necessary permissions are missing diff --git a/src/entrypoints/content/content.ts b/src/entrypoints/content/content.ts index f3d87fd3..0a36be1d 100644 --- a/src/entrypoints/content/content.ts +++ b/src/entrypoints/content/content.ts @@ -47,6 +47,7 @@ const isApplicableToDomain = (currentDomain: string, p: ContentScriptPayload): b try { const key = __UNIQUE_PAYLOAD_KEY_NAME__ + // get the payload from the storage chrome.storage.local.get([key], (items) => { if (chrome.runtime.lastError) { throw new Error(chrome.runtime.lastError.message) @@ -59,12 +60,31 @@ try { const payload = items[key] as ContentScriptPayload const currentDomain = window.location.hostname - console.log('currentDomain', currentDomain, isApplicableToDomain(currentDomain, payload), payload) - if (!isApplicableToDomain(currentDomain, payload)) { return // the script is not applicable to the current domain } + // Important Note: + // + // Chromium-based browsers (like Chrome, Edge, Opera, etc.) support the `world` property in the + // `chrome.scripting.registerContentScripts` API. This property allows content scripts to run in the "isolated" + // world, which is necessary for accessing the payload from the storage using the `chrome.store` API. Additionally, + // they can run in the "main" world as an "inject" script, which modifies the user agent. + // + // However, FireFox does not yet support the `world` property. Therefore, I need to ensure that the "inject" script + // code is executed in both environments. + // + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/RegisteredContentScript + // + // For Chromium-based browsers, the "inject" script is registered as a content script in the + // `registerContentScripts` function, resulting in faster execution without adding a