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