From dbd6f96102b91286a8adc32d90b312a92b9fc301 Mon Sep 17 00:00:00 2001 From: kunoka <163348146+kunokareal@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:22:34 +0300 Subject: [PATCH 1/3] chore: manifest v3 --- src/bg/background.ts | 2 +- src/manifest.json | 8 +++++--- src/services/menu.actions.ts | 16 ++++++++++++---- src/services/search.actions.ts | 6 +++--- src/styles/themes/proton/sidebar/sidebar.styl | 1 + src/types/web-ext.d.ts | 12 ++++++++++-- 6 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/bg/background.ts b/src/bg/background.ts index c8900095..276135d8 100644 --- a/src/bg/background.ts +++ b/src/bg/background.ts @@ -129,7 +129,7 @@ void (async function main() { function initToolbarButton(): void { Menu.createSettingsMenu() - browser.browserAction.onClicked.addListener((_, info): void => { + browser.action.onClicked.addListener((_, info): void => { if (info && info.button === 1) browser.runtime.openOptionsPage() else browser.sidebarAction.toggle() }) diff --git a/src/manifest.json b/src/manifest.json index 86e2b944..b962698c 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,5 +1,5 @@ { - "manifest_version": 2, + "manifest_version": 3, "browser_specific_settings": { "gecko": { "id": "{3c078156-979c-498b-8990-85f7987dd929}", @@ -34,7 +34,6 @@ "theme" ], "optional_permissions": [ - "", "proxy", "webRequest", "webRequestBlocking", @@ -44,6 +43,9 @@ "history", "downloads" ], + "host_permissions": [ + "" + ], "sidebar_action": { "default_icon": "./assets/logo-native.svg", "default_title": "Sidebery", @@ -424,7 +426,7 @@ "description": "__MSG_KbSortPanelTabsByTimeDes__" } }, - "browser_action": { + "action": { "default_icon": "./assets/logo-native.svg", "default_title": "__MSG_ActionTitle__", "default_area": "navbar", diff --git a/src/services/menu.actions.ts b/src/services/menu.actions.ts index c3855864..db06618a 100644 --- a/src/services/menu.actions.ts +++ b/src/services/menu.actions.ts @@ -107,15 +107,23 @@ export function createSettingsMenu(): void { id: 'open_settings', title: translate('menu.browserAction.open_settings'), icons: { '16': 'assets/logo-native.svg' }, - onclick: () => browser.runtime.openOptionsPage(), - contexts: ['browser_action'], + contexts: ['action'], }) browser.menus.create({ id: 'create_snapshot', title: translate('menu.browserAction.create_snapshot'), icons: { '16': 'assets/snapshot-native.svg' }, - onclick: () => Snapshots.createSnapshot(), - contexts: ['browser_action'], + contexts: ['action'], + }) + browser.menus.onClicked.addListener((info, tab) => { + switch (info.menuItemId) { + case 'open_settings': + browser.runtime.openOptionsPage() + break + case 'create_snapshot': + Snapshots.createSnapshot() + break + } }) } diff --git a/src/services/search.actions.ts b/src/services/search.actions.ts index bec1205f..462f5dfa 100644 --- a/src/services/search.actions.ts +++ b/src/services/search.actions.ts @@ -429,12 +429,12 @@ export function start(): void { const hasFocus = document.hasFocus() if (!hasFocus) { - browser.browserAction.setPopup({ popup: SEARCH_URL }) - browser.browserAction.openPopup() + browser.action.setPopup({ popup: SEARCH_URL }) + browser.action.openPopup() Search.reactive.barIsActive = true // Reset browser action - setTimeout(() => browser.browserAction.setPopup({ popup: null }), 500) + setTimeout(() => browser.action.setPopup({ popup: null }), 500) } showBar() diff --git a/src/styles/themes/proton/sidebar/sidebar.styl b/src/styles/themes/proton/sidebar/sidebar.styl index 08e8f981..949060b8 100644 --- a/src/styles/themes/proton/sidebar/sidebar.styl +++ b/src/styles/themes/proton/sidebar/sidebar.styl @@ -48,6 +48,7 @@ body height: 100% -webkit-font-smoothing: antialiased -moz-osx-font-smoothing: grayscale + font-family: sans-serif padding: 0 margin: 0 diff --git a/src/types/web-ext.d.ts b/src/types/web-ext.d.ts index c76c0fd2..5e133fb7 100644 --- a/src/types/web-ext.d.ts +++ b/src/types/web-ext.d.ts @@ -497,7 +497,7 @@ declare namespace browser { /** * Browser action button */ - namespace browserAction { + namespace action { type BrowserActionButton = 0 | 1 interface PopupDetails { @@ -562,7 +562,7 @@ declare namespace browser { */ namespace menus { // prettier-ignore - type ContextType = 'all' | 'audio' | 'bookmark' | 'browser_action' + type ContextType = 'all' | 'audio' | 'bookmark' | 'action' | 'editable' | 'frame' | 'image' | 'link' | 'page' | 'page_action' | 'password' | 'selection' | 'tab' | 'tools_menu' | 'video' @@ -606,6 +606,14 @@ declare namespace browser { function overrideContext(contextOptions: ContextOptions): void const onHidden: EventTarget + + interface OnClickData { + button?: number + menuItemId: string + } + type ClickListener = (info: OnClickData, tab: tabs.Tab) => void + + const onClicked: EventTarget } /** From 57883f6974a61eefbedd816a97d8fb8e76ad1811 Mon Sep 17 00:00:00 2001 From: kunoka <163348146+kunokareal@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:51:43 +0300 Subject: [PATCH 2/3] chore: use scripting api for executeScript --- src/manifest.json | 1 + src/services/tabs.bg.actions.ts | 48 +++++++------ src/services/tabs.fg.actions.ts | 24 ++++--- src/services/tabs.fg.media.ts | 116 +++++++++++++++++++++----------- src/services/tabs.preview.ts | 32 +++++---- src/types/web-ext.d.ts | 37 +++++++--- 6 files changed, 169 insertions(+), 89 deletions(-) diff --git a/src/manifest.json b/src/manifest.json index b962698c..87483f63 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -28,6 +28,7 @@ "storage", "unlimitedStorage", "sessions", + "scripting", "menus", "menus.overrideContext", "search", diff --git a/src/services/tabs.bg.actions.ts b/src/services/tabs.bg.actions.ts index 951c2a8f..51a83018 100644 --- a/src/services/tabs.bg.actions.ts +++ b/src/services/tabs.bg.actions.ts @@ -514,22 +514,26 @@ export async function initInternalPageScripts(tabs: Tab[]) { export function injectUrlPageScript(winId: ID, tabId: ID) { try { - browser.tabs - .executeScript(tabId, { - file: '/injections/url.js', - runAt: 'document_start', - matchAboutBlank: true, + browser.scripting + .executeScript({ + files: ['/injections/url.js'], + target: { tabId }, + injectImmediately: true, }) .catch(err => { Logs.warn('Tabs.injectUrlPageScript: Cannot inject script, tabId:', tabId, err) }) const initData = getUrlPageInitData(winId, tabId) const initDataJson = JSON.stringify(initData) - browser.tabs - .executeScript(tabId, { - code: `window.sideberyInitData=${initDataJson};window.onSideberyInitDataReady?.()`, - runAt: 'document_start', - matchAboutBlank: true, + browser.scripting + .executeScript<[string], void>({ + args: [initDataJson], + func: (initDataJson: string) => { + window.sideberyInitData = JSON.parse(initDataJson); + window.onSideberyInitDataReady?.() + }, + target: { tabId }, + injectImmediately: true, }) .catch(() => { Logs.warn('Tabs.injectUrlPageScript: Cannot inject init data, reloading tab (if active)...') @@ -576,22 +580,26 @@ export async function injectGroupPageScript(winId: ID, tabId: ID): Promise injectingGroup.add(tabId) try { - browser.tabs - .executeScript(tabId, { - file: '/injections/group.js', - runAt: 'document_start', - matchAboutBlank: true, + browser.scripting + .executeScript({ + files: ['/injections/group.js'], + target: { tabId }, + injectImmediately: true, }) .catch(err => { Logs.warn('Tabs.injectGroupPageScript: Cannot inject script, tabId:', tabId, err) }) const initData = await getGroupPageInitData(winId, tabId) const initDataJson = JSON.stringify(initData) - browser.tabs - .executeScript(tabId, { - code: `window.sideberyInitData=${initDataJson};window.onSideberyInitDataReady?.()`, - runAt: 'document_start', - matchAboutBlank: true, + browser.scripting + .executeScript<[string], void>({ + args: [initDataJson], + func: (initDataJson: string) => { + window.sideberyInitData = JSON.parse(initDataJson); + window.onSideberyInitDataReady?.() + }, + target: { tabId }, + injectImmediately: true, }) .catch(() => { Logs.warn('Tabs.injectGroupPageScript: Cannot inject init data, reloading tab') diff --git a/src/services/tabs.fg.actions.ts b/src/services/tabs.fg.actions.ts index cebb30d0..4d5d3a85 100644 --- a/src/services/tabs.fg.actions.ts +++ b/src/services/tabs.fg.actions.ts @@ -1156,15 +1156,23 @@ export async function discardTabs(tabIds: ID[] = []): Promise { // Try to reset closing prevention and discard such tabs if (Settings.state.forceDiscard && Permissions.allUrls && secondTryIds.length) { - const forceDiscardInjection = - 'window.onbeforeunload=null;window.addEventListener("beforeunload", e => {e.returnValue=""})' await Promise.allSettled( - secondTryIds.map(id => { - return browser.tabs.executeScript(id, { - code: forceDiscardInjection, - runAt: 'document_start', - allFrames: true, - }) + secondTryIds.map(async (id) => { + return browser.scripting.executeScript({ + func: () => { + window.onbeforeunload = null; + window.addEventListener("beforeunload", e => { + e.stopPropagation() + e.returnValue = '' + }, { capture: true }) + }, + injectImmediately: true, + target: { + tabId: id, + allFrames: true + }, + world: 'MAIN' + }).then(console.log).catch(console.error) }) ) diff --git a/src/services/tabs.fg.media.ts b/src/services/tabs.fg.media.ts index 73ff0d2a..1be7bc27 100644 --- a/src/services/tabs.fg.media.ts +++ b/src/services/tabs.fg.media.ts @@ -98,14 +98,17 @@ export async function pauseTabMedia(id?: ID): Promise { tab.reactive.mediaPaused = tab.mediaPaused = true Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) - browser.tabs - .executeScript(tab.id, { - file: '../injections/pauseMedia.js', - runAt: 'document_start', - allFrames: true, + browser.scripting + .executeScript({ + files: ['../injections/pauseMedia.js'], + injectImmediately: true, + target: { + tabId: tab.id, + allFrames: true + } }) .then(results => { - if (results.every(result => result === false)) { + if (results.every(({ result }) => result === false)) { tab.reactive.mediaPaused = tab.mediaPaused = false Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) } @@ -131,11 +134,14 @@ export async function playTabMedia(id?: ID): Promise { tab.reactive.mediaPaused = tab.mediaPaused = false Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) - browser.tabs - .executeScript(tab.id, { - file: '../injections/playMedia.js', - runAt: 'document_start', - allFrames: true, + browser.scripting + .executeScript({ + files: ['../injections/playMedia.js'], + injectImmediately: true, + target: { + tabId: tab.id, + allFrames: true + } }) .catch(err => { Logs.err('Tabs.playTabMedia: Cannot exec script:', err) @@ -163,10 +169,9 @@ export async function pauseTabsMediaOfPanel(panelId: ID): Promise { const panel = Sidebar.panelsById[panelId] if (!Utils.isTabsPanel(panel)) return - const injectionConfig: browser.tabs.ExecuteOpts = { - file: '../injections/pauseMedia.js', - runAt: 'document_start', - allFrames: true, + const injectionConfig: Omit = { + files: ['../injections/pauseMedia.js'], + injectImmediately: true, } if (Settings.state.pinnedTabsPosition === 'panel') { @@ -176,10 +181,16 @@ export async function pauseTabsMediaOfPanel(panelId: ID): Promise { if ((tab.audible || tab.mutedInfo?.muted) && tab.panelId === panel.id) { tab.reactive.mediaPaused = tab.mediaPaused = true Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) - browser.tabs - .executeScript(tab.id, injectionConfig) + browser.scripting + .executeScript({ + ...injectionConfig, + target: { + tabId: tab.id, + allFrames: true + }, + }) .then(results => { - if (results.every(result => result === false)) { + if (results.every(({ result }) => result === false)) { tab.reactive.mediaPaused = tab.mediaPaused = false Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) } @@ -196,10 +207,16 @@ export async function pauseTabsMediaOfPanel(panelId: ID): Promise { if (tab.audible || tab.mutedInfo?.muted) { tab.reactive.mediaPaused = tab.mediaPaused = true Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) - browser.tabs - .executeScript(tab.id, injectionConfig) + browser.scripting + .executeScript({ + ...injectionConfig, + target: { + tabId: tab.id, + allFrames: true + }, + }) .then(results => { - if (results.every(result => result === false)) { + if (results.every(({ result }) => result === false)) { tab.reactive.mediaPaused = tab.mediaPaused = false Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) } @@ -222,10 +239,9 @@ export async function playTabsMediaOfPanel(panelId: ID): Promise { const panel = Sidebar.panelsById[panelId] if (!Utils.isTabsPanel(panel)) return - const injectionConfig: browser.tabs.ExecuteOpts = { - file: '../injections/playMedia.js', - runAt: 'document_start', - allFrames: true, + const injectionConfig: Omit = { + files: ['../injections/playMedia.js'], + injectImmediately: true, } if (Settings.state.pinnedTabsPosition === 'panel') { @@ -234,7 +250,13 @@ export async function playTabsMediaOfPanel(panelId: ID): Promise { if (tab.mediaPaused && tab.panelId === panel.id) { tab.reactive.mediaPaused = tab.mediaPaused = false Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) - browser.tabs.executeScript(tab.id, injectionConfig).catch(err => { + browser.scripting.executeScript({ + ...injectionConfig, + target: { + tabId: tab.id, + allFrames: true + }, + }).catch(err => { Logs.err('Tabs.playTabsMediaOfPanel: Cannot exec script (pinned):', err) }) } @@ -245,7 +267,13 @@ export async function playTabsMediaOfPanel(panelId: ID): Promise { if (tab.mediaPaused) { tab.reactive.mediaPaused = tab.mediaPaused = false Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) - browser.tabs.executeScript(tab.id, injectionConfig).catch(err => { + browser.scripting.executeScript({ + ...injectionConfig, + target: { + tabId: tab.id, + allFrames: true + }, + }).catch(err => { Logs.err('Tabs.playTabsMediaOfPanel: Cannot exec script:', err) }) } @@ -271,20 +299,25 @@ export async function pauseAllAudibleTabsMedia(): Promise { if (!result) return } - const injectionConfig: browser.tabs.ExecuteOpts = { - file: '../injections/pauseMedia.js', - runAt: 'document_start', - allFrames: true, + const injectionConfig: Omit = { + files: ['../injections/pauseMedia.js'], + injectImmediately: true, } for (const tab of Tabs.list) { if (tab.audible) { tab.reactive.mediaPaused = tab.mediaPaused = true Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) - browser.tabs - .executeScript(tab.id, injectionConfig) + browser.scripting + .executeScript({ + ...injectionConfig, + target: { + tabId: tab.id, + allFrames: true + }, + }) .then(results => { - if (results.every(result => result === false)) { + if (results.every(({ result }) => result === false)) { tab.reactive.mediaPaused = tab.mediaPaused = false Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) } @@ -304,17 +337,22 @@ export async function playAllPausedTabsMedia(): Promise { if (!result) return } - const injectionConfig: browser.tabs.ExecuteOpts = { - file: '../injections/playMedia.js', - runAt: 'document_start', - allFrames: true, + const injectionConfig: Omit = { + files: ['../injections/playMedia.js'], + injectImmediately: true, } for (const tab of Tabs.list) { if (tab.mediaPaused) { tab.reactive.mediaPaused = tab.mediaPaused = false Sidebar.updateMediaStateOfPanelDebounced(100, tab.panelId, tab) - browser.tabs.executeScript(tab.id, injectionConfig).catch(err => { + browser.scripting.executeScript({ + ...injectionConfig, + target: { + tabId: tab.id, + allFrames: true + }, + }).catch(err => { Logs.err('Tabs.playAllPausedTabsMedia: Cannot exec script:', err) }) } diff --git a/src/services/tabs.preview.ts b/src/services/tabs.preview.ts index c3553918..ed7de3ad 100644 --- a/src/services/tabs.preview.ts +++ b/src/services/tabs.preview.ts @@ -149,22 +149,30 @@ async function injectTabPreview(tabId: ID, y?: number) { const initData = getTabPreviewInitData(tabId, y) const initDataJson = JSON.stringify(initData) - const injectingData = browser.tabs - .executeScript(activeTab.id, { - code: `window.sideberyInitData=${initDataJson};window.onSideberyInitDataReady?.();true;`, - runAt: 'document_start', - allFrames: false, - matchAboutBlank: true, + const injectingData = browser.scripting + .executeScript<[string], void>({ + args: [initDataJson], + func: (initDataJson: string) => { + window.sideberyInitData = JSON.parse(initDataJson); + window.onSideberyInitDataReady?.();true; + }, + injectImmediately: true, + target: { + tabId: activeTab.id, + allFrames: false, + }, }) .catch(() => { // Cannot inject init data }) - const injectingScript = browser.tabs - .executeScript(activeTab.id, { - file: '../injections/tab-preview.js', - runAt: 'document_start', - allFrames: false, - matchAboutBlank: true, + const injectingScript = browser.scripting + .executeScript({ + files: ['../injections/tab-preview.js'], + injectImmediately: true, + target: { + tabId: activeTab.id, + allFrames: false, + } }) .catch(() => { // Cannot exec script diff --git a/src/types/web-ext.d.ts b/src/types/web-ext.d.ts index 5e133fb7..f1b10a88 100644 --- a/src/types/web-ext.d.ts +++ b/src/types/web-ext.d.ts @@ -312,15 +312,6 @@ declare namespace browser { active?: boolean } - interface ExecuteOpts { - allFrames?: boolean - code?: string - file?: string - frameId?: number - matchAboutBlank?: boolean - runAt?: 'document_start' | 'document_end' | 'document_idle' - } - function create(createProperties: CreateProperties): Promise function query(options: TabsQueryOptions): Promise function remove(tabIds: ID | ID[]): Promise @@ -340,7 +331,6 @@ declare namespace browser { function getCurrent(): Promise function saveAsPDF(pageSettings: PageSettings): Promise function duplicate(tabId: ID, opts?: DuplOpts): Promise - function executeScript(tabId: ID, opts: ExecuteOpts): Promise function warmup(tabId: ID): Promise interface RemoveInfo { @@ -1414,4 +1404,31 @@ declare namespace browser { const onUpdated: EventTarget } + + namespace scripting { + interface InjectionTarget { + allFrames?: boolean + frameIds?: number[] + tabId: ID + } + + type ExecutionWorld = 'ISOLATED' | 'MAIN' + + interface InjectDetails { + args?: Args + files?: string[] + func?: (...args: Args) => Result + injectImmediately?: boolean + target: InjectionTarget + world?: ExecutionWorld + } + + interface InjectionResult { + frameId: number + result?: T + error?: unknown // unsupported by chrome + } + + function executeScript(details: InjectDetails): Promise[]> + } } From 8af5e85b74b61ceac9dd0688b20f4443d6bd9dd5 Mon Sep 17 00:00:00 2001 From: kunoka <163348146+kunokareal@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:51:50 +0300 Subject: [PATCH 3/3] fix: sidebar styles --- src/styles/themes/proton/sidebar/sidebar.styl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/themes/proton/sidebar/sidebar.styl b/src/styles/themes/proton/sidebar/sidebar.styl index 949060b8..ed73acf8 100644 --- a/src/styles/themes/proton/sidebar/sidebar.styl +++ b/src/styles/themes/proton/sidebar/sidebar.styl @@ -42,6 +42,9 @@ html position: relative height: 100% +* + box-sizing: border-box + body position: relative width: 100% @@ -51,6 +54,7 @@ body font-family: sans-serif padding: 0 margin: 0 + cursor: default a text-decoration: none