Rewrite the background scripts.
This commit is contained in:
parent
974d8f22fd
commit
20f399bda8
|
@ -1,43 +0,0 @@
|
||||||
import browser from 'webextension-polyfill';
|
|
||||||
|
|
||||||
import {Settings} from '../settings/settings.js';
|
|
||||||
import {updateBadge} from '../utilities/badge.js';
|
|
||||||
|
|
||||||
// Chromium action handler in service worker.
|
|
||||||
export async function actionClicked(): Promise<void> {
|
|
||||||
await nextItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeoutId: number | undefined;
|
|
||||||
|
|
||||||
// Firefox browser action handler in background script.
|
|
||||||
export async function browserActionClicked(): Promise<void> {
|
|
||||||
// When the extension icon is initially clicked, create a timeout for 500ms
|
|
||||||
// that will open the next queue item when it expires.
|
|
||||||
if (timeoutId === undefined) {
|
|
||||||
timeoutId = window.setTimeout(async () => {
|
|
||||||
timeoutId = undefined;
|
|
||||||
await nextItem();
|
|
||||||
}, 500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the icon is clicked again in those 500ms, open the options page instead.
|
|
||||||
window.clearTimeout(timeoutId);
|
|
||||||
timeoutId = undefined;
|
|
||||||
await browser.runtime.openOptionsPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function nextItem(): Promise<void> {
|
|
||||||
const settings = await Settings.fromSyncStorage();
|
|
||||||
const nextItem = settings.nextQueueItem();
|
|
||||||
|
|
||||||
if (nextItem === undefined) {
|
|
||||||
await browser.runtime.openOptionsPage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await browser.tabs.update({url: nextItem.url});
|
|
||||||
await settings.removeQueueItem(nextItem.id);
|
|
||||||
await updateBadge(settings);
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
import browser from 'webextension-polyfill';
|
|
||||||
|
|
||||||
import {Settings} from '../settings/settings.js';
|
|
||||||
import {updateBadge} from '../utilities/badge.js';
|
|
||||||
|
|
||||||
export function getContextMenus(): browser.Menus.CreateCreatePropertiesType[] {
|
|
||||||
const actionContext =
|
|
||||||
import.meta.env.VITE_BROWSER === 'chromium' ? 'action' : 'browser_action';
|
|
||||||
|
|
||||||
const contextMenus: browser.Menus.CreateCreatePropertiesType[] = [
|
|
||||||
{
|
|
||||||
id: 'queue-add-new-link',
|
|
||||||
title: 'Add to Queue',
|
|
||||||
contexts: ['link'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'queue-open-next-link-in-new-tab',
|
|
||||||
title: 'Open next link in new tab',
|
|
||||||
contexts: [actionContext],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'queue-open-options-page',
|
|
||||||
title: 'Open the extension page',
|
|
||||||
contexts: [actionContext],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (import.meta.env.VITE_BROWSER === 'firefox') {
|
|
||||||
contextMenus.push({
|
|
||||||
id: 'queue-add-new-link-tab',
|
|
||||||
title: 'Add to Queue',
|
|
||||||
contexts: ['tab'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return contextMenus;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initializeContextMenus(): Promise<void> {
|
|
||||||
const contextMenus = getContextMenus();
|
|
||||||
|
|
||||||
await browser.contextMenus.removeAll();
|
|
||||||
|
|
||||||
for (const contextMenu of contextMenus) {
|
|
||||||
browser.contextMenus.create(contextMenu, contextCreated);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function contextCreated(): void {
|
|
||||||
const error = browser.runtime.lastError;
|
|
||||||
|
|
||||||
if (error !== null && error !== undefined) {
|
|
||||||
console.error('Queue', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function contextClicked(
|
|
||||||
contextMenuIds: Set<string>,
|
|
||||||
info: browser.Menus.OnClickData,
|
|
||||||
tab?: browser.Tabs.Tab,
|
|
||||||
): Promise<void> {
|
|
||||||
const id = info.menuItemId.toString();
|
|
||||||
if (!contextMenuIds.has(id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = await Settings.fromSyncStorage();
|
|
||||||
|
|
||||||
if (id.startsWith('queue-add-new-link')) {
|
|
||||||
let text: string | undefined;
|
|
||||||
let url: string | undefined;
|
|
||||||
|
|
||||||
switch (id) {
|
|
||||||
case 'queue-add-new-link': {
|
|
||||||
text = info.linkText;
|
|
||||||
url = info.linkUrl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'queue-add-new-link-tab': {
|
|
||||||
text = tab?.title;
|
|
||||||
url = info.pageUrl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
console.warn(`Encountered unknown context menu ID: ${id}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url === undefined) {
|
|
||||||
console.warn('Cannot add a new item without a URL.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await settings.insertQueueItem(text ?? url, url);
|
|
||||||
await updateBadge(settings);
|
|
||||||
} else if (id === 'queue-open-next-link-in-new-tab') {
|
|
||||||
const nextItem = settings.nextQueueItem();
|
|
||||||
if (nextItem === undefined) {
|
|
||||||
await browser.runtime.openOptionsPage();
|
|
||||||
} else {
|
|
||||||
await browser.tabs.create({active: true, url: nextItem.url});
|
|
||||||
await settings.removeQueueItem(nextItem.id);
|
|
||||||
await updateBadge(settings);
|
|
||||||
}
|
|
||||||
} else if (id === 'queue-open-options-page') {
|
|
||||||
await browser.runtime.openOptionsPage();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
import browser from 'webextension-polyfill';
|
|
||||||
|
|
||||||
import {Settings} from '../settings/settings.js';
|
|
||||||
import {updateBadge} from '../utilities/badge.js';
|
|
||||||
import {History} from '../utilities/history.js';
|
|
||||||
import {actionClicked, browserActionClicked} from './browser-action.js';
|
|
||||||
import {
|
|
||||||
contextClicked,
|
|
||||||
getContextMenus,
|
|
||||||
initializeContextMenus,
|
|
||||||
} from './context-menus.js';
|
|
||||||
|
|
||||||
browser.runtime.onStartup.addListener(async () => {
|
|
||||||
console.debug('Clearing history.');
|
|
||||||
await History.clear();
|
|
||||||
await updateBadge(await Settings.fromSyncStorage());
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.runtime.onInstalled.addListener(async () => {
|
|
||||||
await initializeContextMenus();
|
|
||||||
await updateBadge(await Settings.fromSyncStorage());
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.contextMenus.onClicked.addListener(async (info, tab) => {
|
|
||||||
const contextMenus = getContextMenus();
|
|
||||||
const contextMenuIds = new Set<string>(
|
|
||||||
contextMenus.map(({id}) => id ?? 'queue-unknown'),
|
|
||||||
);
|
|
||||||
|
|
||||||
await contextClicked(contextMenuIds, info, tab);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
void browser.runtime.openOptionsPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.env.VITE_BROWSER === 'chromium') {
|
|
||||||
browser.action.onClicked.addListener(actionClicked);
|
|
||||||
} else {
|
|
||||||
browser.browserAction.onClicked.addListener(browserActionClicked);
|
|
||||||
void initializeContextMenus();
|
|
||||||
}
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Code for the WebExtension icon (AKA the "browser action").
|
||||||
|
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
import {createValue} from "@holllo/webextension-storage";
|
||||||
|
|
||||||
|
import {
|
||||||
|
nextItem,
|
||||||
|
setBadgeText,
|
||||||
|
openNextItemOrOptionsPage,
|
||||||
|
} from "../item/item.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle single and double clicks for Firefox.
|
||||||
|
* - For single click: open the next queued item or the options page if none are
|
||||||
|
* in the queue.
|
||||||
|
* - For double click: open the options page.
|
||||||
|
*
|
||||||
|
* The reason this can't be done in Chromium is due to Manifest V3 running
|
||||||
|
* background scripts in service workers where `setTimeout` doesn't work
|
||||||
|
* reliably. The solution is to use `browser.alarms` instead, however, alarms
|
||||||
|
* also don't work reliably for this use case because they can only run every
|
||||||
|
* minute and we need milliseconds for this. And so, Chromium doesn't get double
|
||||||
|
* click functionality.
|
||||||
|
*/
|
||||||
|
export async function firefoxActionClick(): Promise<void> {
|
||||||
|
const timeoutId = await createValue<number | undefined>({
|
||||||
|
deserialize: Number,
|
||||||
|
key: "actionClickTimeoutId",
|
||||||
|
value: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no ID is in storage, this is the first click so start a timeout and
|
||||||
|
// save its ID.
|
||||||
|
if (timeoutId.value === undefined) {
|
||||||
|
timeoutId.value = window.setTimeout(async () => {
|
||||||
|
// When no second click happens, open the next item or the options page.
|
||||||
|
await openNextItemOrOptionsPage();
|
||||||
|
await timeoutId.remove();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
await timeoutId.save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an ID is present in storage, this is the second click and we want to
|
||||||
|
// open the options page instead.
|
||||||
|
window.clearTimeout(timeoutId.value);
|
||||||
|
await browser.runtime.openOptionsPage();
|
||||||
|
await timeoutId.remove();
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
import {
|
||||||
|
createItem,
|
||||||
|
setBadgeText,
|
||||||
|
openNextItemOrOptionsPage,
|
||||||
|
} from "../item/item.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get properties for all the context menu entries.
|
||||||
|
*
|
||||||
|
* @returns The context menu entries.
|
||||||
|
*/
|
||||||
|
export function getContextMenus(): browser.Menus.CreateCreatePropertiesType[] {
|
||||||
|
// In Manifest V2 the WebExtension icon is referred to as the
|
||||||
|
// "browser action", in MV3 it's just "action".
|
||||||
|
const actionContext: browser.Menus.ContextType =
|
||||||
|
$browser === "firefox" ? "browser_action" : "action";
|
||||||
|
|
||||||
|
const contextMenus: ReturnType<typeof getContextMenus> = [
|
||||||
|
{
|
||||||
|
id: "queue-add-new-link",
|
||||||
|
title: "Add to Queue",
|
||||||
|
contexts: ["link"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "queue-open-next-link-in-new-tab",
|
||||||
|
title: "Open next link in new tab",
|
||||||
|
contexts: [actionContext],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "queue-open-options-page",
|
||||||
|
title: "Open the extension page",
|
||||||
|
contexts: [actionContext],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Only Firefox supports context menu entries for tabs.
|
||||||
|
if ($browser === "firefox") {
|
||||||
|
contextMenus.push({
|
||||||
|
id: "queue-add-new-link-tab",
|
||||||
|
title: "Add to Queue",
|
||||||
|
contexts: ["tab"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return contextMenus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize all the context menu entries.
|
||||||
|
*/
|
||||||
|
export async function initializeContextMenus(): Promise<void> {
|
||||||
|
const contextMenus = getContextMenus();
|
||||||
|
await browser.contextMenus.removeAll();
|
||||||
|
for (const contextMenu of contextMenus) {
|
||||||
|
browser.contextMenus.create(contextMenu, contextCreatedHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for context menu creation.
|
||||||
|
*/
|
||||||
|
function contextCreatedHandler(): void {
|
||||||
|
const error = browser.runtime.lastError;
|
||||||
|
if (error !== null && error !== undefined) {
|
||||||
|
console.error("Queue", error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for context menu clicks.
|
||||||
|
*
|
||||||
|
* @param contextMenuIds A set of all our context menu IDs.
|
||||||
|
* @param info The context menu click data.
|
||||||
|
* @param tab The browser tab, if available.
|
||||||
|
*/
|
||||||
|
export async function contextClicked(
|
||||||
|
contextMenuIds: Set<string>,
|
||||||
|
info: browser.Menus.OnClickData,
|
||||||
|
tab?: browser.Tabs.Tab,
|
||||||
|
): Promise<void> {
|
||||||
|
// Only handle context menus that we know the ID of.
|
||||||
|
const id = info.menuItemId.toString();
|
||||||
|
if (!contextMenuIds.has(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith("queue-add-new-link")) {
|
||||||
|
let text: string | undefined;
|
||||||
|
let url: string | undefined;
|
||||||
|
|
||||||
|
if (id === "queue-add-new-link") {
|
||||||
|
text = info.linkText;
|
||||||
|
url = info.linkUrl;
|
||||||
|
} else if (id === "queue-add-new-link-tab") {
|
||||||
|
text = tab?.title;
|
||||||
|
url = info.pageUrl;
|
||||||
|
} else {
|
||||||
|
console.warn(`Encountered unknown context menu ID: ${id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url === undefined) {
|
||||||
|
console.warn("Cannot add a new item without a URL.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await createItem(text, url);
|
||||||
|
await item.save();
|
||||||
|
await setBadgeText();
|
||||||
|
} else if (id === "queue-open-next-link-in-new-tab") {
|
||||||
|
await openNextItemOrOptionsPage(true);
|
||||||
|
} else if (id === "queue-open-options-page") {
|
||||||
|
await browser.runtime.openOptionsPage();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// The main entry point for the background script. Note that in Manifest V3 this
|
||||||
|
// is run in a service worker.
|
||||||
|
// https://developer.chrome.com/docs/extensions/migrating/to-service-workers/
|
||||||
|
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearHistory,
|
||||||
|
openNextItemOrOptionsPage,
|
||||||
|
setBadgeText,
|
||||||
|
} from "../item/item.js";
|
||||||
|
import {firefoxActionClick} from "./action.js";
|
||||||
|
import {
|
||||||
|
contextClicked,
|
||||||
|
getContextMenus,
|
||||||
|
initializeContextMenus,
|
||||||
|
} from "./context-menu.js";
|
||||||
|
|
||||||
|
if ($browser === "firefox") {
|
||||||
|
browser.browserAction.onClicked.addListener(firefoxActionClick);
|
||||||
|
} else {
|
||||||
|
browser.action.onClicked.addListener(async () => {
|
||||||
|
await openNextItemOrOptionsPage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.runtime.onStartup.addListener(async () => {
|
||||||
|
await clearHistory();
|
||||||
|
await setBadgeText();
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.runtime.onInstalled.addListener(async () => {
|
||||||
|
await initializeContextMenus();
|
||||||
|
await setBadgeText();
|
||||||
|
|
||||||
|
if ($dev) {
|
||||||
|
await browser.runtime.openOptionsPage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.contextMenus.onClicked.addListener(async (info, tab) => {
|
||||||
|
const contextMenus = getContextMenus();
|
||||||
|
const contextMenuIds = new Set<string>(contextMenus.map(({id}) => id!));
|
||||||
|
await contextClicked(contextMenuIds, info, tab);
|
||||||
|
});
|
Loading…
Reference in New Issue