From e2ecf81268cf85ba9c527a8d9b09468cd5d0710a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tommy=20Kihlstr=C3=B8m?= Date: Fri, 6 Mar 2026 17:04:28 +0100 Subject: [PATCH 1/2] Implement "Keep original tab open on container switch" feature with UI checkbox and storage handling --- background.ts | 21 +++++++++++++++++++-- manifest.json | 2 +- ui.html | 5 +++++ ui.ts | 8 ++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/background.ts b/background.ts index 718b2a49..ba5131be 100644 --- a/background.ts +++ b/background.ts @@ -2,6 +2,15 @@ import Sval from "sval"; import * as acorn from "acorn"; let program: acorn.Program | undefined; +let keepTab = false; + +async function onSettingsChanged() { + const result = await browser.storage.local.get("keepTab"); + keepTab = result.keepTab === true; +} + +onSettingsChanged(); +browser.storage.local.onChanged.addListener(onSettingsChanged); async function onScriptChanged() { program = undefined; @@ -36,6 +45,10 @@ function toContainerInfo(value: any): ContainerInfo | undefined { const DEFAULT_COOKIE_STORE_ID = "firefox-default"; +function isNewTab(tab: browser.tabs.Tab): boolean { + return !tab.url || tab.url === "about:newtab" || tab.url === "about:blank" || tab.url === "about:home"; +} + async function onBeforeRequest( request: browser.webRequest._OnBeforeRequestDetails, ): Promise { @@ -69,7 +82,9 @@ async function onBeforeRequest( cookieStoreId: DEFAULT_COOKIE_STORE_ID, }); - await browser.tabs.remove(request.tabId); + if (!keepTab || isNewTab(tab)) { + await browser.tabs.remove(request.tabId); + } return { cancel: true }; } @@ -97,7 +112,9 @@ async function onBeforeRequest( }); // Close the old tab - await browser.tabs.remove(request.tabId); + if (!keepTab || isNewTab(tab)) { + await browser.tabs.remove(request.tabId); + } return { cancel: true }; } diff --git a/manifest.json b/manifest.json index 7e241f7a..03c78c0b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "ContainerScript", - "version": "1.2", + "version": "1.3", "description": "User defined scripts for assining URLs to containers", "permissions": [ "", diff --git a/ui.html b/ui.html index 3e089f53..a2445a6c 100644 --- a/ui.html +++ b/ui.html @@ -95,6 +95,11 @@

Container Script

Programmatically assign URLs to containers.

+
+ +
How to Use ContainerScript diff --git a/ui.ts b/ui.ts index 92e1b2ed..b48d4912 100644 --- a/ui.ts +++ b/ui.ts @@ -22,6 +22,14 @@ async function main() { monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, libUri); monaco.editor.createModel(libSource, "typescript", monaco.Uri.parse(libUri)); + // setup keepTab checkbox + const keepTabCheckbox = document.getElementById("keepTab") as HTMLInputElement; + const { keepTab } = await browser.storage.local.get("keepTab"); + keepTabCheckbox.checked = keepTab === true; + keepTabCheckbox.addEventListener("change", () => { + browser.storage.local.set({ keepTab: keepTabCheckbox.checked }); + }); + const { script } = await browser.storage.local.get("script"); // create the editor From 8f4dc5c07338c6211a199e405fb36c044a66ac86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tommy=20Kihlstr=C3=B8m?= Date: Sat, 7 Mar 2026 00:32:35 +0100 Subject: [PATCH 2/2] Introduce replace, and remove keepTab --- background.ts | 25 +++++++------------------ ui.html | 40 +++++++++++++++++++++++++++++++--------- ui.ts | 16 ++++++---------- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/background.ts b/background.ts index ba5131be..df8d1b2c 100644 --- a/background.ts +++ b/background.ts @@ -2,15 +2,6 @@ import Sval from "sval"; import * as acorn from "acorn"; let program: acorn.Program | undefined; -let keepTab = false; - -async function onSettingsChanged() { - const result = await browser.storage.local.get("keepTab"); - keepTab = result.keepTab === true; -} - -onSettingsChanged(); -browser.storage.local.onChanged.addListener(onSettingsChanged); async function onScriptChanged() { program = undefined; @@ -31,6 +22,7 @@ interface ContainerInfo { name: string | null; icon?: string; color?: string; + replace: boolean; } function toContainerInfo(value: any): ContainerInfo | undefined { @@ -45,10 +37,6 @@ function toContainerInfo(value: any): ContainerInfo | undefined { const DEFAULT_COOKIE_STORE_ID = "firefox-default"; -function isNewTab(tab: browser.tabs.Tab): boolean { - return !tab.url || tab.url === "about:newtab" || tab.url === "about:blank" || tab.url === "about:home"; -} - async function onBeforeRequest( request: browser.webRequest._OnBeforeRequestDetails, ): Promise { @@ -56,12 +44,15 @@ async function onBeforeRequest( return {}; } + const tab = await browser.tabs.get(request.tabId); + const sourceUrl = tab.url ? new URL(tab.url) : undefined; + const interpreter = new Sval({ ecmaVer: "latest", sourceType: "script", sandBox: true, }); - interpreter.import({ url: new URL(request.url) }); + interpreter.import({ url: new URL(request.url), sourceUrl }); interpreter.run(program); const info = toContainerInfo(interpreter.exports.end); @@ -69,8 +60,6 @@ async function onBeforeRequest( return {}; } - const tab = await browser.tabs.get(request.tabId); - // Open in the default container (no container) if name is null. if (info.name === null) { if (!tab.cookieStoreId || tab.cookieStoreId === DEFAULT_COOKIE_STORE_ID) { @@ -82,7 +71,7 @@ async function onBeforeRequest( cookieStoreId: DEFAULT_COOKIE_STORE_ID, }); - if (!keepTab || isNewTab(tab)) { + if (info.replace != false) { await browser.tabs.remove(request.tabId); } @@ -112,7 +101,7 @@ async function onBeforeRequest( }); // Close the old tab - if (!keepTab || isNewTab(tab)) { + if (info.replace !== false) { await browser.tabs.remove(request.tabId); } diff --git a/ui.html b/ui.html index a2445a6c..31862682 100644 --- a/ui.html +++ b/ui.html @@ -95,28 +95,50 @@

Container Script

Programmatically assign URLs to containers.

-
- -
How to Use ContainerScript

Write JavaScript code to define container rules. - The code runs each time you open a URL, with the url variable (an instance of the URL class) available in scope. + The code runs each time you navigate to a URL. Two variables are available: +

+
    +
  • url — the URL being navigated to.
  • +
  • sourceUrl — the URL of the page that initiated the navigation (may be undefined for new tabs).
  • +
+

The code is executed using Sval due to CSP restrictions.

Basic Usage

-

Return a string to open the URL in that container:

+

Return a string to open the URL in that container (replaces the current tab):

if (url.hostname === "www.amazon.com") return "shopping";

Return null to open the URL in the default container (no container):

if (url.hostname === "github.com") return null;

Advanced Configuration

-

Return an object for more control over the container:

-

if (url.toString().includes("bank.com")) return { name: "finance", icon: "fingerprint", color: "blue" };

+

Return an object for more control. Use name (or profile) for the container name, and replace to control whether the original tab is closed:

+

if (url.toString().includes("bank.com")) return { name: "finance", icon: "fingerprint", color: "blue" };

+

Keep the original tab open by setting replace to false:

+

if (url.hostname === "example.com") return { name: "work", replace: false };

+

Use sourceUrl to keep the original tab open when navigating from a specific site (e.g. Google):

+

+let replace = true;
+switch (sourceUrl?.hostname) {
+    case "www.google.com":
+    case "duckduckgo.com":
+    case "www.bing.com":
+        replace = false;
+}
+
+switch (url.hostname) {
+    case "www.youtube.com":
+    case "chatgpt.com":
+    case "www.reddit.com":
+        return { name: "Personal", replace };
+}
+
+return null;
+
diff --git a/ui.ts b/ui.ts index b48d4912..de742a49 100644 --- a/ui.ts +++ b/ui.ts @@ -11,25 +11,21 @@ async function main() { }, }; - // setup the url parameter for auto-complete + // setup auto-complete for available variables const libSource = [ "/**", - " * The URL we're finding a container for", + " * The URL being navigated to", " */", "declare const url: URL", + "/**", + " * The URL of the page that initiated the navigation (may be undefined for new tabs)", + " */", + "declare const sourceUrl: URL | undefined", ].join("\n"); const libUri = "ts:filename/ContainerScript.d.ts"; monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, libUri); monaco.editor.createModel(libSource, "typescript", monaco.Uri.parse(libUri)); - // setup keepTab checkbox - const keepTabCheckbox = document.getElementById("keepTab") as HTMLInputElement; - const { keepTab } = await browser.storage.local.get("keepTab"); - keepTabCheckbox.checked = keepTab === true; - keepTabCheckbox.addEventListener("change", () => { - browser.storage.local.set({ keepTab: keepTabCheckbox.checked }); - }); - const { script } = await browser.storage.local.get("script"); // create the editor