- Userscript
- {#if scriptChecking}
- {showInstallPrompt}
- {:else}
- {scriptInstalled ? "Installed" : "Detected"}:
-
- {/if}
-
+
+ Userscript
+ {#if scriptChecking}
+ {showInstallPrompt}
+ {:else}
+ {scriptInstalled ? "Installed" : "Detected"}:
+
+ {/if}
+
{/if}
{#if errorNotification}
-
- {#if loading}
-
- {:else}
- {#if inactive}
-
Popup inactive on extension page
- {:else if firstGuide}
-
-
Welcome, first use please:
-
-
to complete the initialization
-
- {:else if initError}
-
- Something went wrong:
-
-
- {:else if items.length < 1}
-
No matched userscripts
- {:else}
-
- {#each list as item, i (item.filename)}
-
toggleItem(item)}
- />
- {/each}
-
- {/if}
- {/if}
+
+ {#if loading}
+
+ {:else if inactive}
+
Popup inactive on extension page
+ {:else if firstGuide}
+
+
Welcome, first use please:
+
+
to complete the initialization
+
+ {:else if initError}
+
+ Something went wrong:
+
+
+ {:else if items.length < 1}
+
No matched userscripts
+ {:else}
+
+ {#each list as item, i (item.filename)}
+
toggleItem(item)}
+ />
+ {/each}
+
+ {/if}
{#if !inactive && platform === "macos"}
-
+
{/if}
{#if showUpdates}
-
showUpdates = false}
- showLoaderOnDisabled={true}
- abortClick={abortUpdates}
- abort={showUpdates}
- >
-
-
+
(showUpdates = false)}
+ showLoaderOnDisabled={true}
+ abortClick={abortUpdates}
+ abort={showUpdates}
+ >
+
+
{:else if showInstall}
-
showInstall = false}
- showLoaderOnDisabled={true}
- >
- showInstall = false}
- installConfirmClick={installConfirm}
- />
-
+
(showInstall = false)}
+ showLoaderOnDisabled={true}
+ >
+ (showInstall = false)}
+ installConfirmClick={installConfirm}
+ />
+
{:else if showAll}
-
{
- showAll = false;
- refreshView();
- }}
- showLoaderOnDisabled={false}
- >
-
-
+
{
+ showAll = false;
+ refreshView();
+ }}
+ showLoaderOnDisabled={false}
+ >
+
+
{/if}
diff --git a/src/ext/action-popup/Components/PopupItem.svelte b/src/ext/action-popup/Components/PopupItem.svelte
index 01e091de..f83fee4b 100644
--- a/src/ext/action-popup/Components/PopupItem.svelte
+++ b/src/ext/action-popup/Components/PopupItem.svelte
@@ -1,97 +1,97 @@
-
-
{name}
- {#if subframe}
SUB
{/if}
-
+
+
{name}
+ {#if subframe}
SUB
{/if}
+
diff --git a/src/ext/action-popup/Components/View.svelte b/src/ext/action-popup/Components/View.svelte
index 71115f7b..00b6d61f 100644
--- a/src/ext/action-popup/Components/View.svelte
+++ b/src/ext/action-popup/Components/View.svelte
@@ -1,80 +1,76 @@
-
-
- {#if loading && showLoaderOnDisabled}
-
- {:else}
-
Slot content is required...
- {/if}
-
+
+
+ {#if loading && showLoaderOnDisabled}
+
+ {:else}
+
Slot content is required...
+ {/if}
+
diff --git a/src/ext/action-popup/Components/Views/AllItemsView.svelte b/src/ext/action-popup/Components/Views/AllItemsView.svelte
index a5a59553..b5457123 100644
--- a/src/ext/action-popup/Components/Views/AllItemsView.svelte
+++ b/src/ext/action-popup/Components/Views/AllItemsView.svelte
@@ -1,58 +1,58 @@
{#if allItems.length}
-
- {#each list as item (item.filename)}
-
allItemsToggleItem(item)}
- />
- {/each}
-
+
+ {#each list as item (item.filename)}
+
allItemsToggleItem(item)}
+ />
+ {/each}
+
{:else}
-
No valid files found in directory
+
No valid files found in directory
{/if}
diff --git a/src/ext/action-popup/Components/Views/InstallView.svelte b/src/ext/action-popup/Components/Views/InstallView.svelte
index 9d9cd3fe..fe3a3152 100644
--- a/src/ext/action-popup/Components/Views/InstallView.svelte
+++ b/src/ext/action-popup/Components/Views/InstallView.svelte
@@ -1,180 +1,181 @@
- {#if installError}
-
- {@html iconError}
-
Couldn't install userscript
-
{installError}
-
- {:else if userscript}
-
- - {userscript.name}
- {#if userscript.description}
- - {userscript.description}
- {/if}
- {#if userscript.match}
- -
-
@match
- {#each userscript.match as match}
- {match}
- {/each}
-
- {/if}
- {#if userscript.include}
- -
-
@include
- {#each userscript.include as include}
- {include}
- {/each}
-
- {/if}
- {#if userscript.require}
- -
-
@require
- {#each userscript.require as require}
- {require}
- {/each}
-
- {/if}
- {#if userscript.grant}
- -
-
@grant
- {#each userscript.grant as grant}
- {grant}
- {/each}
-
- {/if}
-
-
-
{@html iconWarn}
-
Be sure you trust the author before installing. Nefarious code can exploit your security and privacy.
-
-
-
-
-
- {/if}
+ {#if installError}
+
+
+ {@html iconError}
+
Couldn't install userscript
+
{installError}
+
+ {:else if userscript}
+
+ - {userscript.name}
+ {#if userscript.description}
+ - {userscript.description}
+ {/if}
+ {#if userscript.match}
+ -
+
@match
+ {#each userscript.match as match}
+ {match}
+ {/each}
+
+ {/if}
+ {#if userscript.include}
+ -
+
@include
+ {#each userscript.include as include}
+ {include}
+ {/each}
+
+ {/if}
+ {#if userscript.require}
+ -
+
@require
+ {#each userscript.require as require}
+ {require}
+ {/each}
+
+ {/if}
+ {#if userscript.grant}
+ -
+
@grant
+ {#each userscript.grant as grant}
+ {grant}
+ {/each}
+
+ {/if}
+
+
+
+
{@html iconWarn}
+
+ Be sure you trust the author before installing. Nefarious code can
+ exploit your security and privacy.
+
+
+
+
+
+
+ {/if}
diff --git a/src/ext/action-popup/Components/Views/UpdateView.svelte b/src/ext/action-popup/Components/Views/UpdateView.svelte
index fd121c84..957925c1 100644
--- a/src/ext/action-popup/Components/Views/UpdateView.svelte
+++ b/src/ext/action-popup/Components/Views/UpdateView.svelte
@@ -1,88 +1,83 @@
{#if updates.length}
- {#each updates as item (item.name)}
-
-
-
{item.name}
-
- Source
-
-
-
- {/each}
-
Be sure you trust the author before saving remote code to your device.
-
+ {#each updates as item (item.name)}
+
+
+
{item.name}
+
Source
+
+
+ {/each}
+
Be sure you trust the author before saving remote code to your device.
+
{:else}
-
- {@html iconUpdate}
-
- There are no file updates available
-
-
-
-
+
+
+ {@html iconUpdate}
+
+ There are no file updates available
+
+
+
+
{/if}
diff --git a/src/ext/action-popup/app.css b/src/ext/action-popup/app.css
index 8bbc54fe..2d2feca2 100644
--- a/src/ext/action-popup/app.css
+++ b/src/ext/action-popup/app.css
@@ -1,86 +1,86 @@
html {
- font-size: 100%;
- overflow: hidden;
+ font-size: 100%;
+ overflow: hidden;
}
body {
- background-color: var(--color-bg-secondary);
- color: var(--text-color-primary);
- font: var(--text-medium);
- letter-spacing: var(--letter-spacing-medium);
- position: relative;
- text-rendering: optimizelegibility;
- width: 18rem;
- -webkit-font-smoothing: antialiased;
+ background-color: var(--color-bg-secondary);
+ color: var(--text-color-primary);
+ font: var(--text-medium);
+ letter-spacing: var(--letter-spacing-medium);
+ position: relative;
+ text-rendering: optimizelegibility;
+ width: 18rem;
+ -webkit-font-smoothing: antialiased;
}
/* attempts to detect iOS */
@media (hover: none) {
- html {
- font-size: 125%;
- }
+ html {
+ font-size: 125%;
+ }
- body {
- width: 100vw;
- }
+ body {
+ width: 100vw;
+ }
}
@media screen and (width >= 481px) and (orientation: portrait) and (hover: none) {
- body {
- width: 18rem;
- }
+ body {
+ width: 18rem;
+ }
}
@media screen and (width >= 1024px) and (orientation: landscape) and (hover: none) {
- body {
- width: 18rem;
- }
+ body {
+ width: 18rem;
+ }
}
body.ipados {
- width: 18rem;
- height: 100vh;
+ width: 18rem;
+ height: 100vh;
}
body.ipados #app {
- height: 100%;
+ height: 100%;
}
body.ios {
- height: 100vh;
- width: 100vw;
+ height: 100vh;
+ width: 100vw;
}
body.ios #app {
- height: 100%;
+ height: 100%;
}
noscript {
- display: block;
+ display: block;
}
button {
- border: none;
- cursor: pointer;
- padding: 0;
- user-select: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ user-select: none;
}
button:disabled {
- opacity: var(--opacity-disabled);
- pointer-events: none;
+ opacity: var(--opacity-disabled);
+ pointer-events: none;
}
a,
.link {
- background: none;
- color: var(--color-blue);
- cursor: pointer;
- text-decoration: underline;
+ background: none;
+ color: var(--color-blue);
+ cursor: pointer;
+ text-decoration: underline;
}
.truncate {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
diff --git a/src/ext/action-popup/main.js b/src/ext/action-popup/main.js
index ed71b808..48a7c630 100644
--- a/src/ext/action-popup/main.js
+++ b/src/ext/action-popup/main.js
@@ -3,37 +3,38 @@ import "../shared/variables.css";
import "./app.css";
import App from "./App.svelte";
-if (import.meta.env.MODE === "development") { // vite feat that only import in dev mode
- const modules = import.meta.glob("../shared/dev.js", {eager: true});
- window.browser = modules["../shared/dev.js"].browser;
- console.info("DEV-ENV", import.meta.env, modules, browser);
- // macos popup simulation
- const style = document.createElement("style");
- style.textContent = `
+// vite feat that only import in dev mode
+if (import.meta.env.MODE === "development") {
+ const modules = import.meta.glob("../shared/dev.js", { eager: true });
+ window.browser = modules["../shared/dev.js"].browser;
+ console.info("DEV-ENV", import.meta.env, modules, browser);
+ // macos popup simulation
+ const style = document.createElement("style");
+ style.textContent = `
body {
- top: 20px;
- left: 20px;
- box-sizing: content-box;
- border: 2px solid #0c0e0f;
- border-radius: 10px;
- box-shadow: 2px 2px 20px rgba(0, 0, 0, 0.2);
+ top: 20px;
+ left: 20px;
+ box-sizing: content-box;
+ border: 2px solid #0c0e0f;
+ border-radius: 10px;
+ box-shadow: 2px 2px 20px rgba(0, 0, 0, 0.2);
}
body:before {
- content: "";
- position: absolute;
- top: -1px;
- left: -1px;
- right: -1px;
- bottom: -1px;
- border: 1px solid #54575a;
- border-radius: 9px;
+ content: "";
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ right: -1px;
+ bottom: -1px;
+ border: 1px solid #54575a;
+ border-radius: 9px;
}
`;
- browser.platform === "macos" && document.head.append(style);
+ browser.platform === "macos" && document.head.append(style);
}
const app = new App({
- target: document.getElementById("app")
+ target: document.getElementById("app"),
});
export default app;
diff --git a/src/ext/background/main.js b/src/ext/background/main.js
index d8a57ee3..1df31003 100644
--- a/src/ext/background/main.js
+++ b/src/ext/background/main.js
@@ -1,119 +1,124 @@
-import {openExtensionPage} from "../shared/utils.js";
-// eslint-disable-next-line import/no-namespace
+import { openExtensionPage } from "../shared/utils.js";
import * as settingsStorage from "../shared/settings.js";
// first sorts files by run-at value, then by weight value
function userscriptSort(a, b) {
- // map the run-at values to numeric values
- const runAtValues = {
- "document-start": 1,
- "document-end": 2,
- "document-idle": 3
- };
- const runAtA = a.scriptObject["run-at"];
- const runAtB = b.scriptObject["run-at"];
- if (runAtA !== runAtB && runAtValues[runAtA] && runAtValues[runAtB]) {
- return runAtValues[runAtA] > runAtValues[runAtB];
- }
- return Number(a.scriptObject.weight) < Number(b.scriptObject.weight);
+ // map the run-at values to numeric values
+ const runAtValues = {
+ "document-start": 1,
+ "document-end": 2,
+ "document-idle": 3,
+ };
+ const runAtA = a.scriptObject["run-at"];
+ const runAtB = b.scriptObject["run-at"];
+ if (runAtA !== runAtB && runAtValues[runAtA] && runAtValues[runAtB]) {
+ return runAtValues[runAtA] > runAtValues[runAtB];
+ }
+ return Number(a.scriptObject.weight) < Number(b.scriptObject.weight);
}
async function readAsDataURL(blob) {
- return new Promise(resolve => {
- const reader = new FileReader();
- reader.readAsDataURL(blob);
- reader.onloadend = () => resolve(reader.result); // base64data
- });
+ return new Promise((resolve) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = () => resolve(reader.result); // base64data
+ });
}
async function getPlatform() {
- let platform = localStorage.getItem("platform");
- if (!platform) {
- const message = {name: "REQ_PLATFORM"};
- const response = await browser.runtime.sendNativeMessage(message);
- if (!response.platform) {
- console.error("Failed to get platform");
- return "";
- }
- platform = response.platform;
- localStorage.setItem("platform", platform);
- }
- return platform;
+ let platform = localStorage.getItem("platform");
+ if (!platform) {
+ const message = { name: "REQ_PLATFORM" };
+ const response = await browser.runtime.sendNativeMessage(message);
+ if (!response.platform) {
+ console.error("Failed to get platform");
+ return "";
+ }
+ platform = response.platform;
+ localStorage.setItem("platform", platform);
+ }
+ return platform;
}
function setClipboard(data, type = "text/plain") {
- // future enhancement?
- // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write
- // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText
- const onCopy = e => {
- e.stopImmediatePropagation();
- e.preventDefault();
- e.clipboardData.setData(type, data);
- document.removeEventListener("copy", onCopy, true);
- };
+ // future enhancement?
+ // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write
+ // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText
+ const onCopy = (e) => {
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ e.clipboardData.setData(type, data);
+ document.removeEventListener("copy", onCopy, true);
+ };
- const textarea = document.createElement("textarea");
- textarea.textContent = "
";
- document.body.appendChild(textarea);
- textarea.select();
- document.addEventListener("copy", onCopy, true);
- try {
- return document.execCommand("copy");
- } catch (error) {
- console.warn("setClipboard failed", error);
- document.removeEventListener("copy", onCopy, true);
- return false;
- } finally {
- document.body.removeChild(textarea);
- }
+ const textarea = document.createElement("textarea");
+ textarea.textContent = "";
+ document.body.appendChild(textarea);
+ textarea.select();
+ document.addEventListener("copy", onCopy, true);
+ try {
+ return document.execCommand("copy");
+ } catch (error) {
+ console.warn("setClipboard failed", error);
+ document.removeEventListener("copy", onCopy, true);
+ return false;
+ } finally {
+ document.body.removeChild(textarea);
+ }
}
async function setBadgeCount() {
- const clearBadge = () => browser.browserAction.setBadgeText({text: ""});
- // @todo until better introduce in ios, only set badge on macOS
- const platform = await getPlatform();
- if (platform !== "macos") return clearBadge();
- // @todo settingsStorage.get("global_exclude_match")
- const settings = await settingsStorage.get(["global_active", "toolbar_badge_count"]);
- if (settings.global_active === false) return clearBadge();
- if (settings.toolbar_badge_count === false) return clearBadge();
+ const clearBadge = () => browser.browserAction.setBadgeText({ text: "" });
+ // @todo until better introduce in ios, only set badge on macOS
+ const platform = await getPlatform();
+ if (platform !== "macos") return clearBadge();
+ // @todo settingsStorage.get("global_exclude_match")
+ const settings = await settingsStorage.get([
+ "global_active",
+ "toolbar_badge_count",
+ ]);
+ if (settings.global_active === false) return clearBadge();
+ if (settings.toolbar_badge_count === false) return clearBadge();
- const currentTab = await browser.tabs.getCurrent();
- // no active tabs exist (user closed all windows)
- if (!currentTab) return clearBadge();
- const url = currentTab.url;
- // if url doesn't exist, stop
- if (!url) return clearBadge();
- // only check for http/s pages
- if (!url.startsWith("http://") && !url.startsWith("https://")) return clearBadge();
- // @todo if url match in global exclude list, clear badge
- const frameUrls = new Set();
- const frames = await browser.webNavigation.getAllFrames({tabId: currentTab.id});
- for (let i = 0; i < frames.length; i++) {
- const frameUrl = frames[i].url;
- if (frameUrl !== url && frameUrl.startsWith("http")) {
- frameUrls.add(frameUrl);
- }
- }
- const message = {
- name: "POPUP_BADGE_COUNT",
- url,
- frameUrls: Array.from(frameUrls)
- };
- browser.runtime.sendNativeMessage(message, response => {
- if (response.error) return console.error(response.error);
- const count = response.count;
- if (count > 0) {
- browser.browserAction.setBadgeText({text: count.toString()});
- } else {
- const _url = new URL(url);
- if (_url.pathname.endsWith(".user.js")) {
- browser.browserAction.setBadgeText({text: "JS"});
- } else {
- clearBadge();
- }
- }
- });
+ const currentTab = await browser.tabs.getCurrent();
+ // no active tabs exist (user closed all windows)
+ if (!currentTab) return clearBadge();
+ const url = currentTab.url;
+ // if url doesn't exist, stop
+ if (!url) return clearBadge();
+ // only check for http/s pages
+ if (!url.startsWith("http://") && !url.startsWith("https://"))
+ return clearBadge();
+ // @todo if url match in global exclude list, clear badge
+ const frameUrls = new Set();
+ const frames = await browser.webNavigation.getAllFrames({
+ tabId: currentTab.id,
+ });
+ for (let i = 0; i < frames.length; i++) {
+ const frameUrl = frames[i].url;
+ if (frameUrl !== url && frameUrl.startsWith("http")) {
+ frameUrls.add(frameUrl);
+ }
+ }
+ const message = {
+ name: "POPUP_BADGE_COUNT",
+ url,
+ frameUrls: Array.from(frameUrls),
+ };
+ browser.runtime.sendNativeMessage(message, (response) => {
+ if (response.error) return console.error(response.error);
+ const count = response.count;
+ if (count > 0) {
+ browser.browserAction.setBadgeText({ text: count.toString() });
+ } else {
+ const _url = new URL(url);
+ if (_url.pathname.endsWith(".user.js")) {
+ browser.browserAction.setBadgeText({ text: "JS" });
+ } else {
+ clearBadge();
+ }
+ }
+ });
}
// on startup get declarativeNetRequests
@@ -123,380 +128,390 @@ async function setBadgeCount() {
// 2. dnr item toggle event in the page occurs
// 3. external editor changes script file content
async function setSessionRules() {
- // not supported below safari 15.4
- if (!browser.declarativeNetRequest.updateSessionRules) return;
- await clearAllSessionRules();
- const message = {name: "REQ_REQUESTS"};
- const response = await browser.runtime.sendNativeMessage(message);
- if (response.error) {
- console.error(response.error);
- return;
- }
- // there are no rules to apply
- if (!response.length) return;
- // loop through response, parse the rules, push to array and log
- const rules = [];
- for (let i = 0; i < response.length; i++) {
- const rule = response[i];
- const code = JSON.parse(rule.code);
- // check if an array or single rule
- if (Array.isArray(code)) {
- code.forEach(r => rules.push(r));
- console.info(`Setting session rule: ${rule.name} (${code.length})`);
- } else {
- rules.push(code);
- console.info(`Setting session rule: ${rule.name}`);
- }
- }
- // generate unique ids for all rules to ensure no repeats
- const ids = randomNumberSet(1000, rules.length);
- rules.map((rule, index) => rule.id = ids[index]);
- try {
- await browser.declarativeNetRequest.updateSessionRules({addRules: rules});
- } catch (error) {
- console.error(`Error setting session rules: ${error}`);
- return;
- }
- console.info(`Finished setting ${rules.length} session rules`);
+ // not supported below safari 15.4
+ if (!browser.declarativeNetRequest.updateSessionRules) return;
+ await clearAllSessionRules();
+ const message = { name: "REQ_REQUESTS" };
+ const response = await browser.runtime.sendNativeMessage(message);
+ if (response.error) {
+ console.error(response.error);
+ return;
+ }
+ // there are no rules to apply
+ if (!response.length) return;
+ // loop through response, parse the rules, push to array and log
+ const rules = [];
+ for (let i = 0; i < response.length; i++) {
+ const rule = response[i];
+ const code = JSON.parse(rule.code);
+ // check if an array or single rule
+ if (Array.isArray(code)) {
+ code.forEach((r) => rules.push(r));
+ console.info(`Setting session rule: ${rule.name} (${code.length})`);
+ } else {
+ rules.push(code);
+ console.info(`Setting session rule: ${rule.name}`);
+ }
+ }
+ // generate unique ids for all rules to ensure no repeats
+ const ids = randomNumberSet(1000, rules.length);
+ rules.map((rule, index) => (rule.id = ids[index]));
+ try {
+ await browser.declarativeNetRequest.updateSessionRules({ addRules: rules });
+ } catch (error) {
+ console.error(`Error setting session rules: ${error}`);
+ return;
+ }
+ console.info(`Finished setting ${rules.length} session rules`);
}
async function clearAllSessionRules() {
- const rules = await browser.declarativeNetRequest.getSessionRules();
- if (!rules.length) return;
- console.info(`Clearing ${rules.length} session rules`);
- const ruleIds = rules.map(a => a.id);
- await browser.declarativeNetRequest.updateSessionRules({
- removeRuleIds: ruleIds
- });
+ const rules = await browser.declarativeNetRequest.getSessionRules();
+ if (!rules.length) return;
+ console.info(`Clearing ${rules.length} session rules`);
+ const ruleIds = rules.map((a) => a.id);
+ await browser.declarativeNetRequest.updateSessionRules({
+ removeRuleIds: ruleIds,
+ });
}
function randomNumberSet(max, count) {
- // generates a set of random unique numbers
- // returns an array
- const numbers = new Set();
- while (numbers.size < count) {
- numbers.add((Math.floor(Math.random() * (max - 1 + 1)) + 1));
- }
- return [...numbers];
+ // generates a set of random unique numbers
+ // returns an array
+ const numbers = new Set();
+ while (numbers.size < count) {
+ numbers.add(Math.floor(Math.random() * (max - 1 + 1)) + 1);
+ }
+ return [...numbers];
}
// the current update logic is similar to setSessionRules()
// this feature needs a more detailed redesign in the future
// https://github.com/quoid/userscripts/issues/453
async function getContextMenuItems() {
- // macos exclusive feature
- const platform = await getPlatform();
- if (platform !== "macos") return;
- // since it's not possible to get a list of currently active menu items
- // on update, all context-menu items are cleared, then re-added
- // this is done to ensure fresh code changes appear
- await browser.menus.removeAll();
- // get the context-menu scripts
- const message = {name: "REQ_CONTEXT_MENU_SCRIPTS"};
- const response = await browser.runtime.sendNativeMessage(message);
- if (response.error) {
- console.error(response.error);
- return;
- }
- // add menus items
- const items = response.files?.menu || [];
- if (items.length) {
- console.info(`Setting ${items.length} context-menu userscripts`);
- }
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- // context-menu scripts require @match value
- // @include values are ignored
- if (!item.scriptObject.matches.length) continue;
- addContextMenuItem(item);
- }
+ // macos exclusive feature
+ const platform = await getPlatform();
+ if (platform !== "macos") return;
+ // since it's not possible to get a list of currently active menu items
+ // on update, all context-menu items are cleared, then re-added
+ // this is done to ensure fresh code changes appear
+ await browser.menus.removeAll();
+ // get the context-menu scripts
+ const message = { name: "REQ_CONTEXT_MENU_SCRIPTS" };
+ const response = await browser.runtime.sendNativeMessage(message);
+ if (response.error) {
+ console.error(response.error);
+ return;
+ }
+ // add menus items
+ const items = response.files?.menu || [];
+ if (items.length) {
+ console.info(`Setting ${items.length} context-menu userscripts`);
+ }
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ // context-menu scripts require @match value
+ // @include values are ignored
+ if (!item.scriptObject.matches.length) continue;
+ addContextMenuItem(item);
+ }
}
async function addContextMenuItem(userscript) {
- // context-menu items persist for a session
- // to avoid duplication, when created, save the filename to session storage
- const savedItems = sessionStorage.getItem("menu");
- // if the session storage key doesn't exist use empty array
- const activeItems = savedItems ? JSON.parse(savedItems) : [];
- if (activeItems.indexOf(userscript.scriptObject.filename) !== -1) {
- // if already saved, remove it, to get fresh code changes
- await browser.menus.remove(userscript.scriptObject.filename);
- }
- // potential bug? https://developer.apple.com/forums/thread/685273
- // https://stackoverflow.com/q/68431201
- // parse through match values and change pathnames to deal with bug
- const patterns = userscript.scriptObject.matches;
- patterns.forEach((pattern, index) => {
- try {
- const url = new URL(pattern);
- let pathname = url.pathname;
- if (pathname.length > 1 && pathname.endsWith("/")) {
- pathname = pathname.slice(0, -1);
- }
- patterns[index] = `${url.protocol}//${url.hostname}${pathname}`;
- } catch (error) {
- // prevent breaking when non-url pattern present
- }
- });
+ // context-menu items persist for a session
+ // to avoid duplication, when created, save the filename to session storage
+ const savedItems = sessionStorage.getItem("menu");
+ // if the session storage key doesn't exist use empty array
+ const activeItems = savedItems ? JSON.parse(savedItems) : [];
+ if (activeItems.indexOf(userscript.scriptObject.filename) !== -1) {
+ // if already saved, remove it, to get fresh code changes
+ await browser.menus.remove(userscript.scriptObject.filename);
+ }
+ // potential bug? https://developer.apple.com/forums/thread/685273
+ // https://stackoverflow.com/q/68431201
+ // parse through match values and change pathnames to deal with bug
+ const patterns = userscript.scriptObject.matches;
+ patterns.forEach((pattern, index) => {
+ try {
+ const url = new URL(pattern);
+ let pathname = url.pathname;
+ if (pathname.length > 1 && pathname.endsWith("/")) {
+ pathname = pathname.slice(0, -1);
+ }
+ patterns[index] = `${url.protocol}//${url.hostname}${pathname}`;
+ } catch (error) {
+ // prevent breaking when non-url pattern present
+ }
+ });
- browser.menus.create({
- contexts: ["all"],
- documentUrlPatterns: patterns,
- id: userscript.scriptObject.filename,
- title: userscript.scriptObject.name
- }, () => {
- // add event listener if needed
- if (!browser.menus.onClicked.hasListener(contextClick)) {
- browser.menus.onClicked.addListener(contextClick);
- }
- // save the context-menu item reference to sessionStorage
- const value = JSON.stringify([userscript.scriptObject.filename]);
- sessionStorage.setItem("menu", value);
- });
+ browser.menus.create(
+ {
+ contexts: ["all"],
+ documentUrlPatterns: patterns,
+ id: userscript.scriptObject.filename,
+ title: userscript.scriptObject.name,
+ },
+ () => {
+ // add event listener if needed
+ if (!browser.menus.onClicked.hasListener(contextClick)) {
+ browser.menus.onClicked.addListener(contextClick);
+ }
+ // save the context-menu item reference to sessionStorage
+ const value = JSON.stringify([userscript.scriptObject.filename]);
+ sessionStorage.setItem("menu", value);
+ },
+ );
}
function contextClick(info, tab) {
- // when any created context-menu item is clicked, send message to tab
- // the content script for that tag will have the context-menu code
- // which will get send back in the response if/when found
- const message = {name: "CONTEXT_RUN", menuItemId: info.menuItemId};
- browser.tabs.sendMessage(tab.id, message, response => {
- // if code is returned, execute on that tab
- if (!response.code) return;
- browser.tabs.executeScript(tab.id, {
- code: response.code
- });
- });
+ // when any created context-menu item is clicked, send message to tab
+ // the content script for that tag will have the context-menu code
+ // which will get send back in the response if/when found
+ const message = { name: "CONTEXT_RUN", menuItemId: info.menuItemId };
+ browser.tabs.sendMessage(tab.id, message, (response) => {
+ // if code is returned, execute on that tab
+ if (!response.code) return;
+ browser.tabs.executeScript(tab.id, {
+ code: response.code,
+ });
+ });
}
async function nativeChecks() {
- const response = await browser.runtime.sendNativeMessage({name: "NATIVE_CHECKS"});
- if (response.error) {
- settingsStorage.set({error_native: response});
- return false;
- }
- settingsStorage.reset("error_native");
- return true;
+ const response = await browser.runtime.sendNativeMessage({
+ name: "NATIVE_CHECKS",
+ });
+ if (response.error) {
+ settingsStorage.set({ error_native: response });
+ return false;
+ }
+ settingsStorage.reset("error_native");
+ return true;
}
// handles messages sent with browser.runtime.sendMessage
function handleMessage(request, sender, sendResponse) {
- switch (request.name) {
- case "REQ_USERSCRIPTS": {
- // get the page url from the content script that sent request
- const url = sender.url;
- // use frameId to determine if request came from top level window
- // if @noframes true, and isTop false, swift layer won't return code
- const isTop = sender.frameId === 0;
- // send request to swift layer to provide code for page url
- const message = {name: "REQ_USERSCRIPTS", url, isTop};
- browser.runtime.sendNativeMessage(message, response => {
- // if request failed, send error to content script for logging
- if (response.error) return sendResponse(response);
- // sort files
- response.files.js.sort(userscriptSort);
- response.files.css.sort((a, b) => {
- return Number(a.weight) < Number(b.weight);
- });
- // return sorted files for injection
- sendResponse(response);
- });
- return true;
- }
- case "API_CLOSE_TAB": {
- const tabId = request.tabId || sender.tab.id;
- browser.tabs.remove(tabId, () => sendResponse({success: 1}));
- return true;
- }
- case "API_OPEN_TAB": {
- const props = {
- active: request.active,
- index: sender.tab.index + 1,
- url: request.url
- };
- browser.tabs.create(props, response => sendResponse(response));
- return true;
- }
- case "API_ADD_STYLE": {
- const tabId = sender.tab.id;
- const details = {code: request.css, cssOrigin: "user"};
- browser.tabs.insertCSS(tabId, details, () => {
- sendResponse(request.css);
- });
- return true;
- }
- case "API_GET_TAB": {
- let tab = null;
- if (typeof sender.tab !== "undefined") {
- const tabData = sessionStorage.getItem(`tab-${sender.tab.id}`);
- try {
- // if tabData is null, can still parse it and return that
- tab = JSON.parse(tabData);
- } catch (error) {
- console.error("failed to parse tab data for getTab");
- }
- } else {
- console.error("unable to deliver tab due to empty tab id");
- }
- sendResponse(tab == null ? {} : tab);
- break;
- }
- case "API_SAVE_TAB": {
- if (sender.tab != null && sender.tab.id) {
- const key = `tab-${sender.tab.id}`;
- sessionStorage.setItem(key, JSON.stringify(request.tab));
- sendResponse({success: true});
- } else {
- console.error("unable to save tab, empty tab id");
- sendResponse({success: false});
- }
- break;
- }
- case "API_SET_CLIPBOARD": {
- const result = setClipboard(request.clipboardData, request.type);
- sendResponse(result);
- break;
- }
- case "API_XHR": {
- // parse details and set up for XMLHttpRequest
- const details = request.details;
- const method = details.method ? details.method : "GET";
- const user = details.user || null;
- const password = details.password || null;
- let body = details.data || null;
- if (body != null && details.binary != null) {
- const len = body.length;
- const arr = new Uint8Array(len);
- for (let i = 0; i < len; i++) {
- arr[i] = body.charCodeAt(i);
- }
- body = new Blob([arr], {type: "text/plain"});
- }
- // establish a long-lived port connection to content script
- const port = browser.tabs.connect(sender.tab.id, {
- name: request.xhrPortName
- });
- // set up XMLHttpRequest
- const xhr = new XMLHttpRequest();
- xhr.withCredentials = (details.user && details.password);
- xhr.timeout = details.timeout || 0;
- if (details.overrideMimeType) {
- xhr.overrideMimeType(details.overrideMimeType);
- }
- // add required listeners and send result back to the content script
- for (const e of request.events) {
- if (!details[e]) continue;
- xhr[e] = async event => {
- // can not send xhr through postMessage
- // construct new object to be sent as "response"
- const x = {
- readyState: xhr.readyState,
- response: xhr.response,
- responseHeaders: xhr.getAllResponseHeaders(),
- responseType: xhr.responseType,
- responseURL: xhr.responseURL,
- status: xhr.status,
- statusText: xhr.statusText,
- timeout: xhr.timeout,
- withCredentials: xhr.withCredentials
- };
- // only include responseText when needed
- if (["", "text"].indexOf(xhr.responseType) !== -1) {
- x.responseText = xhr.responseText;
- }
- // only process when xhr is complete and data exist
- if (xhr.readyState === 4 && xhr.response !== null) {
- // need to convert arraybuffer data to postMessage
- if (xhr.responseType === "arraybuffer") {
- const arr = Array.from(new Uint8Array(xhr.response));
- x.response = arr;
- }
- // need to convert blob data to postMessage
- if (xhr.responseType === "blob") {
- const base64data = await readAsDataURL(xhr.response);
- x.response = {
- data: base64data,
- type: xhr.responseType
- };
- }
- }
- port.postMessage({name: e, event, response: x});
- };
- }
- xhr.open(method, details.url, true, user, password);
- xhr.responseType = details.responseType || "";
- if (details.headers) {
- for (const key in details.headers) {
- if (!key.startsWith("Proxy-") && !key.startsWith("Sec-")) {
- const val = details.headers[key];
- xhr.setRequestHeader(key, val);
- }
- }
- }
- // receive messages from content script and process them
- port.onMessage.addListener(msg => {
- if (msg.name === "ABORT") xhr.abort();
- if (msg.name === "DISCONNECT") port.disconnect();
- });
- // handle port disconnect and clean tasks
- port.onDisconnect.addListener(p => {
- if (p?.error) {
- console.error(`port disconnected due to an error: ${p.error.message}`);
- }
- });
- xhr.send(body);
- // if onloadend not set in xhr details
- // onloadend event won't be passed to content script
- // if that happens port DISCONNECT message won't be posted
- // if details lacks onloadend attach listener
- if (!details.onloadend) {
- xhr.onloadend = event => {
- port.postMessage({name: "onloadend", event});
- };
- }
- break;
- }
- case "REFRESH_SESSION_RULES": {
- setSessionRules();
- break;
- }
- case "REFRESH_CONTEXT_MENU_SCRIPTS": {
- getContextMenuItems();
- break;
- }
- }
+ switch (request.name) {
+ case "REQ_USERSCRIPTS": {
+ // get the page url from the content script that sent request
+ const url = sender.url;
+ // use frameId to determine if request came from top level window
+ // if @noframes true, and isTop false, swift layer won't return code
+ const isTop = sender.frameId === 0;
+ // send request to swift layer to provide code for page url
+ const message = { name: "REQ_USERSCRIPTS", url, isTop };
+ browser.runtime.sendNativeMessage(message, (response) => {
+ // if request failed, send error to content script for logging
+ if (response.error) return sendResponse(response);
+ // sort files
+ response.files.js.sort(userscriptSort);
+ response.files.css.sort((a, b) => {
+ return Number(a.weight) < Number(b.weight);
+ });
+ // return sorted files for injection
+ sendResponse(response);
+ });
+ return true;
+ }
+ case "API_CLOSE_TAB": {
+ const tabId = request.tabId || sender.tab.id;
+ browser.tabs.remove(tabId, () => sendResponse({ success: 1 }));
+ return true;
+ }
+ case "API_OPEN_TAB": {
+ const props = {
+ active: request.active,
+ index: sender.tab.index + 1,
+ url: request.url,
+ };
+ browser.tabs.create(props, (response) => sendResponse(response));
+ return true;
+ }
+ case "API_ADD_STYLE": {
+ const tabId = sender.tab.id;
+ const details = { code: request.css, cssOrigin: "user" };
+ browser.tabs.insertCSS(tabId, details, () => {
+ sendResponse(request.css);
+ });
+ return true;
+ }
+ case "API_GET_TAB": {
+ let tab = null;
+ if (typeof sender.tab !== "undefined") {
+ const tabData = sessionStorage.getItem(`tab-${sender.tab.id}`);
+ try {
+ // if tabData is null, can still parse it and return that
+ tab = JSON.parse(tabData);
+ } catch (error) {
+ console.error("failed to parse tab data for getTab");
+ }
+ } else {
+ console.error("unable to deliver tab due to empty tab id");
+ }
+ sendResponse(tab == null ? {} : tab);
+ break;
+ }
+ case "API_SAVE_TAB": {
+ if (sender.tab != null && sender.tab.id) {
+ const key = `tab-${sender.tab.id}`;
+ sessionStorage.setItem(key, JSON.stringify(request.tab));
+ sendResponse({ success: true });
+ } else {
+ console.error("unable to save tab, empty tab id");
+ sendResponse({ success: false });
+ }
+ break;
+ }
+ case "API_SET_CLIPBOARD": {
+ const result = setClipboard(request.clipboardData, request.type);
+ sendResponse(result);
+ break;
+ }
+ case "API_XHR": {
+ // parse details and set up for XMLHttpRequest
+ const details = request.details;
+ const method = details.method ? details.method : "GET";
+ const user = details.user || null;
+ const password = details.password || null;
+ let body = details.data || null;
+ if (body != null && details.binary != null) {
+ const len = body.length;
+ const arr = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ arr[i] = body.charCodeAt(i);
+ }
+ body = new Blob([arr], { type: "text/plain" });
+ }
+ // establish a long-lived port connection to content script
+ const port = browser.tabs.connect(sender.tab.id, {
+ name: request.xhrPortName,
+ });
+ // set up XMLHttpRequest
+ const xhr = new XMLHttpRequest();
+ xhr.withCredentials = details.user && details.password;
+ xhr.timeout = details.timeout || 0;
+ if (details.overrideMimeType) {
+ xhr.overrideMimeType(details.overrideMimeType);
+ }
+ // add required listeners and send result back to the content script
+ for (const e of request.events) {
+ if (!details[e]) continue;
+ xhr[e] = async (event) => {
+ // can not send xhr through postMessage
+ // construct new object to be sent as "response"
+ const x = {
+ readyState: xhr.readyState,
+ response: xhr.response,
+ responseHeaders: xhr.getAllResponseHeaders(),
+ responseType: xhr.responseType,
+ responseURL: xhr.responseURL,
+ status: xhr.status,
+ statusText: xhr.statusText,
+ timeout: xhr.timeout,
+ withCredentials: xhr.withCredentials,
+ };
+ // only include responseText when needed
+ if (["", "text"].indexOf(xhr.responseType) !== -1) {
+ x.responseText = xhr.responseText;
+ }
+ // only process when xhr is complete and data exist
+ if (xhr.readyState === 4 && xhr.response !== null) {
+ // need to convert arraybuffer data to postMessage
+ if (xhr.responseType === "arraybuffer") {
+ const arr = Array.from(new Uint8Array(xhr.response));
+ x.response = arr;
+ }
+ // need to convert blob data to postMessage
+ if (xhr.responseType === "blob") {
+ const base64data = await readAsDataURL(xhr.response);
+ x.response = {
+ data: base64data,
+ type: xhr.responseType,
+ };
+ }
+ }
+ port.postMessage({ name: e, event, response: x });
+ };
+ }
+ xhr.open(method, details.url, true, user, password);
+ xhr.responseType = details.responseType || "";
+ if (details.headers) {
+ for (const key in details.headers) {
+ if (!key.startsWith("Proxy-") && !key.startsWith("Sec-")) {
+ const val = details.headers[key];
+ xhr.setRequestHeader(key, val);
+ }
+ }
+ }
+ // receive messages from content script and process them
+ port.onMessage.addListener((msg) => {
+ if (msg.name === "ABORT") xhr.abort();
+ if (msg.name === "DISCONNECT") port.disconnect();
+ });
+ // handle port disconnect and clean tasks
+ port.onDisconnect.addListener((p) => {
+ if (p?.error) {
+ console.error(
+ `port disconnected due to an error: ${p.error.message}`,
+ );
+ }
+ });
+ xhr.send(body);
+ // if onloadend not set in xhr details
+ // onloadend event won't be passed to content script
+ // if that happens port DISCONNECT message won't be posted
+ // if details lacks onloadend attach listener
+ if (!details.onloadend) {
+ xhr.onloadend = (event) => {
+ port.postMessage({ name: "onloadend", event });
+ };
+ }
+ break;
+ }
+ case "REFRESH_SESSION_RULES": {
+ setSessionRules();
+ break;
+ }
+ case "REFRESH_CONTEXT_MENU_SCRIPTS": {
+ getContextMenuItems();
+ break;
+ }
+ }
}
browser.runtime.onInstalled.addListener(async () => {
- nativeChecks();
+ nativeChecks();
});
browser.runtime.onStartup.addListener(async () => {
- setSessionRules();
- getContextMenuItems();
+ setSessionRules();
+ getContextMenuItems();
});
// listens for messages from content script, popup and page
browser.runtime.onMessage.addListener(handleMessage);
// set the badge count
browser.tabs.onActivated.addListener(setBadgeCount);
-browser.windows.onFocusChanged.addListener(async windowId => {
- if (windowId < 1) { // lose focus
- return;
- }
- nativeChecks();
- setBadgeCount();
- setSessionRules();
- getContextMenuItems();
+browser.windows.onFocusChanged.addListener(async (windowId) => {
+ if (windowId < 1) {
+ // lose focus
+ return;
+ }
+ nativeChecks();
+ setBadgeCount();
+ setSessionRules();
+ getContextMenuItems();
});
browser.webNavigation.onCompleted.addListener(setBadgeCount);
// handle native app messages
const port = browser.runtime.connectNative();
-port.onMessage.addListener(message => {
- // console.info(message); // DEBUG
- if (message.name === "SAVE_LOCATION_CHANGED") {
- openExtensionPage();
- if (message?.userInfo?.returnApp === true) browser.runtime.sendNativeMessage({name: "OPEN_APP"});
- }
- // if (message.name === "OPEN_EXTENSION_PAGE") {
- // openExtensionPage();
- // }
+port.onMessage.addListener((message) => {
+ // console.info(message); // DEBUG
+ if (message.name === "SAVE_LOCATION_CHANGED") {
+ openExtensionPage();
+ if (message?.userInfo?.returnApp === true) {
+ browser.runtime.sendNativeMessage({ name: "OPEN_APP" });
+ }
+ }
+ // if (message.name === "OPEN_EXTENSION_PAGE") {
+ // openExtensionPage();
+ // }
});
diff --git a/src/ext/content-scripts/api.js b/src/ext/content-scripts/api.js
index 3907cb98..9b9f6337 100644
--- a/src/ext/content-scripts/api.js
+++ b/src/ext/content-scripts/api.js
@@ -1,232 +1,232 @@
function setValue(key, value) {
- if (typeof key !== "string" || !key.length) {
- return console.error("setValue invalid key arg");
- }
- if (value == null) {
- return console.error("setValue invalid value arg");
- }
- return new Promise(resolve => {
- const item = {};
- item[`${this.US_filename}---${key}`] = value;
- browser.storage.local.set(item, () => resolve({success: 1}));
- });
+ if (typeof key !== "string" || !key.length) {
+ return console.error("setValue invalid key arg");
+ }
+ if (value == null) {
+ return console.error("setValue invalid value arg");
+ }
+ return new Promise((resolve) => {
+ const item = {};
+ item[`${this.US_filename}---${key}`] = value;
+ browser.storage.local.set(item, () => resolve({ success: 1 }));
+ });
}
function getValue(key, defaultValue) {
- if (typeof key !== "string" || !key.length) {
- return console.error("getValue invalid key arg");
- }
- const prefixedKey = `${this.US_filename}---${key}`;
- return new Promise(resolve => {
- browser.storage.local.get(prefixedKey, item => {
- if (Object.keys(item).length === 0) {
- if (defaultValue != null) {
- resolve(defaultValue);
- } else {
- resolve(undefined);
- }
- } else {
- resolve(Object.values(item)[0]);
- }
- });
- });
+ if (typeof key !== "string" || !key.length) {
+ return console.error("getValue invalid key arg");
+ }
+ const prefixedKey = `${this.US_filename}---${key}`;
+ return new Promise((resolve) => {
+ browser.storage.local.get(prefixedKey, (item) => {
+ if (Object.keys(item).length === 0) {
+ if (defaultValue != null) {
+ resolve(defaultValue);
+ } else {
+ resolve(undefined);
+ }
+ } else {
+ resolve(Object.values(item)[0]);
+ }
+ });
+ });
}
function deleteValue(key) {
- if (typeof key !== "string" || !key.length) {
- return console.error("deleteValue missing key arg");
- }
- return new Promise(resolve => {
- const prefixedKey = `${this.US_filename}---${key}`;
- browser.storage.local.remove(prefixedKey, () => {
- resolve({success: 1});
- });
- });
+ if (typeof key !== "string" || !key.length) {
+ return console.error("deleteValue missing key arg");
+ }
+ return new Promise((resolve) => {
+ const prefixedKey = `${this.US_filename}---${key}`;
+ browser.storage.local.remove(prefixedKey, () => {
+ resolve({ success: 1 });
+ });
+ });
}
function listValues() {
- return new Promise(resolve => {
- const prefix = `${this.US_filename}---`;
- const keys = [];
- browser.storage.local.get().then(items => {
- for (const key in items) {
- if (key.startsWith(prefix)) {
- const k = key.replace(prefix, "");
- keys.push(k);
- }
- }
- resolve(keys);
- });
- });
+ return new Promise((resolve) => {
+ const prefix = `${this.US_filename}---`;
+ const keys = [];
+ browser.storage.local.get().then((items) => {
+ for (const key in items) {
+ if (key.startsWith(prefix)) {
+ const k = key.replace(prefix, "");
+ keys.push(k);
+ }
+ }
+ resolve(keys);
+ });
+ });
}
function openInTab(url, openInBackground = false) {
- if (!url) return console.error("openInTab missing url arg");
- return new Promise(resolve => {
- const message = {
- name: "API_OPEN_TAB",
- url,
- active: !openInBackground
- };
- browser.runtime.sendMessage(message, response => resolve(response));
- });
+ if (!url) return console.error("openInTab missing url arg");
+ return new Promise((resolve) => {
+ const message = {
+ name: "API_OPEN_TAB",
+ url,
+ active: !openInBackground,
+ };
+ browser.runtime.sendMessage(message, (response) => resolve(response));
+ });
}
function getTab() {
- return new Promise(resolve => {
- const message = {name: "API_GET_TAB"};
- browser.runtime.sendMessage(message, response => {
- resolve(response);
- });
- });
+ return new Promise((resolve) => {
+ const message = { name: "API_GET_TAB" };
+ browser.runtime.sendMessage(message, (response) => {
+ resolve(response);
+ });
+ });
}
function saveTab(tab) {
- if (tab == null) return console.error("saveTab invalid arg");
- return new Promise(resolve => {
- const message = {
- name: "API_SAVE_TAB",
- tab
- };
- browser.runtime.sendMessage(message, response => {
- resolve(response);
- });
- });
+ if (tab == null) return console.error("saveTab invalid arg");
+ return new Promise((resolve) => {
+ const message = {
+ name: "API_SAVE_TAB",
+ tab,
+ };
+ browser.runtime.sendMessage(message, (response) => {
+ resolve(response);
+ });
+ });
}
function closeTab(tabId) {
- return new Promise(resolve => {
- const message = {
- name: "API_CLOSE_TAB",
- tabId
- };
- browser.runtime.sendMessage(message, response => resolve(response));
- });
+ return new Promise((resolve) => {
+ const message = {
+ name: "API_CLOSE_TAB",
+ tabId,
+ };
+ browser.runtime.sendMessage(message, (response) => resolve(response));
+ });
}
function addStyle(css) {
- if (typeof css !== "string") {
- return console.error("addStyle invalid css arg");
- }
- return new Promise(resolve => {
- const message = {
- name: "API_ADD_STYLE",
- css
- };
- browser.runtime.sendMessage(message, response => resolve(response));
- });
+ if (typeof css !== "string") {
+ return console.error("addStyle invalid css arg");
+ }
+ return new Promise((resolve) => {
+ const message = {
+ name: "API_ADD_STYLE",
+ css,
+ };
+ browser.runtime.sendMessage(message, (response) => resolve(response));
+ });
}
function setClipboard(clipboardData, type) {
- return new Promise(resolve => {
- const message = {
- name: "API_SET_CLIPBOARD",
- clipboardData,
- type
- };
- browser.runtime.sendMessage(message, response => {
- resolve(response);
- });
- });
+ return new Promise((resolve) => {
+ const message = {
+ name: "API_SET_CLIPBOARD",
+ clipboardData,
+ type,
+ };
+ browser.runtime.sendMessage(message, (response) => {
+ resolve(response);
+ });
+ });
}
function xhr(details) {
- if (details == null) return console.error("xhr invalid details arg");
- if (!details.url) return console.error("xhr details missing url key");
- // generate random port name for single xhr
- const xhrPortName = Math.random().toString(36).substring(1, 9);
- // strip out functions from details
- const detailsParsed = JSON.parse(JSON.stringify(details));
- // get all the "on" events from XMLHttpRequest object
- const events = [];
- for (const k in XMLHttpRequest.prototype) {
- if (k.slice(0, 2) === "on") events.push(k);
- }
- // check which functions are included in the original details object
- // add a bool to indicate if event listeners should be attached
- for (const e of events) {
- if (typeof details[e] === "function") detailsParsed[e] = true;
- }
- // define return method, will be populated after port is established
- const response = {
- abort: () => console.error("xhr has not yet been initialized")
- };
- // port listener, most of the messaging logic goes here
- const listener = port => {
- if (port.name !== xhrPortName) return;
- port.onMessage.addListener(async msg => {
- if (
- events.includes(msg.name)
- && typeof details[msg.name] === "function"
- ) {
- // process xhr response
- const r = msg.response;
- // only process when xhr is complete and data exist
- if (r.readyState === 4 && r.response !== null) {
- if (r.responseType === "arraybuffer") {
- // arraybuffer responses had their data converted in background
- // convert it back to arraybuffer
- try {
- const buffer = new Uint8Array(r.response).buffer;
- r.response = buffer;
- } catch (err) {
- console.error("error parsing xhr arraybuffer", err);
- }
- } else if (r.responseType === "blob" && r.response.data) {
- // blob responses had their data converted in background
- // convert it back to blob
- const resp = await fetch(r.response.data);
- const b = await resp.blob();
- r.response = b;
- }
- }
- // call userscript method
- details[msg.name](msg.response);
- }
- // all messages received
- // tell background it's safe to close port
- if (msg.name === "onloadend") {
- port.postMessage({name: "DISCONNECT"});
- }
- });
+ if (details == null) return console.error("xhr invalid details arg");
+ if (!details.url) return console.error("xhr details missing url key");
+ // generate random port name for single xhr
+ const xhrPortName = Math.random().toString(36).substring(1, 9);
+ // strip out functions from details
+ const detailsParsed = JSON.parse(JSON.stringify(details));
+ // get all the "on" events from XMLHttpRequest object
+ const events = [];
+ for (const k in XMLHttpRequest.prototype) {
+ if (k.slice(0, 2) === "on") events.push(k);
+ }
+ // check which functions are included in the original details object
+ // add a bool to indicate if event listeners should be attached
+ for (const e of events) {
+ if (typeof details[e] === "function") detailsParsed[e] = true;
+ }
+ // define return method, will be populated after port is established
+ const response = {
+ abort: () => console.error("xhr has not yet been initialized"),
+ };
+ // port listener, most of the messaging logic goes here
+ const listener = (port) => {
+ if (port.name !== xhrPortName) return;
+ port.onMessage.addListener(async (msg) => {
+ if (
+ events.includes(msg.name) &&
+ typeof details[msg.name] === "function"
+ ) {
+ // process xhr response
+ const r = msg.response;
+ // only process when xhr is complete and data exist
+ if (r.readyState === 4 && r.response !== null) {
+ if (r.responseType === "arraybuffer") {
+ // arraybuffer responses had their data converted in background
+ // convert it back to arraybuffer
+ try {
+ const buffer = new Uint8Array(r.response).buffer;
+ r.response = buffer;
+ } catch (err) {
+ console.error("error parsing xhr arraybuffer", err);
+ }
+ } else if (r.responseType === "blob" && r.response.data) {
+ // blob responses had their data converted in background
+ // convert it back to blob
+ const resp = await fetch(r.response.data);
+ const b = await resp.blob();
+ r.response = b;
+ }
+ }
+ // call userscript method
+ details[msg.name](msg.response);
+ }
+ // all messages received
+ // tell background it's safe to close port
+ if (msg.name === "onloadend") {
+ port.postMessage({ name: "DISCONNECT" });
+ }
+ });
- // handle port disconnect and clean tasks
- port.onDisconnect.addListener(p => {
- if (p?.error) {
- console.error(`port disconnected due to an error: ${p.error.message}`);
- }
- browser.runtime.onConnect.removeListener(listener);
- });
- // fill the method returned to the user script
- response.abort = () => port.postMessage({name: "ABORT"});
- };
- // wait for the background to establish a port connection
- browser.runtime.onConnect.addListener(listener);
- // pass the basic information to the background through a common message
- const message = {
- name: "API_XHR",
- details: detailsParsed,
- xhrPortName,
- events
- };
- browser.runtime.sendMessage(message);
- return response;
+ // handle port disconnect and clean tasks
+ port.onDisconnect.addListener((p) => {
+ if (p?.error) {
+ console.error(`port disconnected due to an error: ${p.error.message}`);
+ }
+ browser.runtime.onConnect.removeListener(listener);
+ });
+ // fill the method returned to the user script
+ response.abort = () => port.postMessage({ name: "ABORT" });
+ };
+ // wait for the background to establish a port connection
+ browser.runtime.onConnect.addListener(listener);
+ // pass the basic information to the background through a common message
+ const message = {
+ name: "API_XHR",
+ details: detailsParsed,
+ xhrPortName,
+ events,
+ };
+ browser.runtime.sendMessage(message);
+ return response;
}
export default {
- setValue,
- getValue,
- listValues,
- deleteValue,
- openInTab,
- getTab,
- saveTab,
- closeTab,
- addStyle,
- setClipboard,
- // notification,
- // registerMenuCommand,
- // getResourceUrl,
- xmlHttpRequest: xhr,
- GM_xmlhttpRequest: xhr
+ setValue,
+ getValue,
+ listValues,
+ deleteValue,
+ openInTab,
+ getTab,
+ saveTab,
+ closeTab,
+ addStyle,
+ setClipboard,
+ // notification,
+ // registerMenuCommand,
+ // getResourceUrl,
+ xmlHttpRequest: xhr,
+ GM_xmlhttpRequest: xhr,
};
diff --git a/src/ext/content-scripts/main.js b/src/ext/content-scripts/main.js
index 6d23a254..9c7dabef 100644
--- a/src/ext/content-scripts/main.js
+++ b/src/ext/content-scripts/main.js
@@ -11,223 +11,246 @@ const label = randomLabel();
const usTag = window.self === window.top ? "" : `(${label})`;
function randomLabel() {
- const a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", r = Math.random();
- return a[Math.floor(r * a.length)] + r.toString().slice(5, 6);
+ const a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ const r = Math.random();
+ return a[Math.floor(r * a.length)] + r.toString().slice(5, 6);
}
function triageJS(userscript) {
- const runAt = userscript.scriptObject["run-at"];
- if (runAt === "document-start") {
- injectJS(userscript);
- } else if (runAt === "document-end") {
- if (document.readyState !== "loading") {
- injectJS(userscript);
- } else {
- document.addEventListener("DOMContentLoaded", () => {
- injectJS(userscript);
- });
- }
- } else if (runAt === "document-idle") {
- if (document.readyState === "complete") {
- injectJS(userscript);
- } else {
- document.addEventListener("readystatechange", () => {
- if (document.readyState === "complete") {
- injectJS(userscript);
- }
- });
- }
- }
+ const runAt = userscript.scriptObject["run-at"];
+ if (runAt === "document-start") {
+ injectJS(userscript);
+ } else if (runAt === "document-end") {
+ if (document.readyState !== "loading") {
+ injectJS(userscript);
+ } else {
+ document.addEventListener("DOMContentLoaded", () => {
+ injectJS(userscript);
+ });
+ }
+ } else if (runAt === "document-idle") {
+ if (document.readyState === "complete") {
+ injectJS(userscript);
+ } else {
+ document.addEventListener("readystatechange", () => {
+ if (document.readyState === "complete") {
+ injectJS(userscript);
+ }
+ });
+ }
+ }
}
function injectJS(userscript) {
- const filename = userscript.scriptObject.filename;
- const name = userscript.scriptObject.name;
- const code = `${userscript.code} //# sourceURL=${filename.replace(/\s/g, "-") + usTag}`;
- let injectInto = userscript.scriptObject["inject-into"];
- // change scope to content since strict CSP event detected
- if (injectInto === "auto" && (userscript.fallback || cspFallbackAttempted)) {
- injectInto = "content";
- console.warn(`Attempting fallback injection for ${name}`);
- } else if (window.self === window.top) {
- console.info(`Injecting ${name} %c(js)`, "color: #fff600");
- } else {
- console.info(`Injecting ${name} %c(js)%c - %cframe(${label})(${window.location})`, "color: #fff600", "color: inherit", "color: #006fff");
- }
- if (injectInto !== "content") {
- const div = document.createElement("div");
- div.style.display = "none";
- const shadowRoot = div.attachShadow({mode: "closed"});
- document.body.append(div);
- const tag = document.createElement("script");
- tag.textContent = code;
- shadowRoot.append(tag);
- } else {
- try {
- // eslint-disable-next-line no-new-func
- return Function(`{${Object.keys(userscript.apis).join(",")}}`, code)(userscript.apis);
- } catch (error) {
- console.error(`"${filename}" error:`, error);
- }
- }
+ const filename = userscript.scriptObject.filename;
+ const name = userscript.scriptObject.name;
+ const code = `${userscript.code} //# sourceURL=${
+ filename.replace(/\s/g, "-") + usTag
+ }`;
+ let injectInto = userscript.scriptObject["inject-into"];
+ // change scope to content since strict CSP event detected
+ if (injectInto === "auto" && (userscript.fallback || cspFallbackAttempted)) {
+ injectInto = "content";
+ console.warn(`Attempting fallback injection for ${name}`);
+ } else if (window.self === window.top) {
+ console.info(`Injecting ${name} %c(js)`, "color: #fff600");
+ } else {
+ console.info(
+ `Injecting ${name} %c(js)%c - %cframe(${label})(${window.location})`,
+ "color: #fff600",
+ "color: inherit",
+ "color: #006fff",
+ );
+ }
+ if (injectInto !== "content") {
+ const div = document.createElement("div");
+ div.style.display = "none";
+ const shadowRoot = div.attachShadow({ mode: "closed" });
+ document.body.append(div);
+ const tag = document.createElement("script");
+ tag.textContent = code;
+ shadowRoot.append(tag);
+ } else {
+ try {
+ // eslint-disable-next-line no-new-func
+ return Function(
+ `{${Object.keys(userscript.apis).join(",")}}`,
+ code,
+ )(userscript.apis);
+ } catch (error) {
+ console.error(`"${filename}" error:`, error);
+ }
+ }
}
function injectCSS(name, code) {
- if (window.self === window.top) {
- console.info(`Injecting ${name} %c(css)`, "color: #60f36c");
- } else {
- console.info(`Injecting ${name} %c(css)%c - %cframe(${label})(${window.location})`, "color: #60f36c", "color: inherit", "color: #006fff");
- }
- // Safari lacks full support for tabs.insertCSS
- // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/insertCSS
- // specifically frameId and cssOrigin
- // if support for those details keys arrives, the method below can be used
- // NOTE: manifest V3 does support frameId, but not origin
- // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/insertCSS
+ if (window.self === window.top) {
+ console.info(`Injecting ${name} %c(css)`, "color: #60f36c");
+ } else {
+ console.info(
+ `Injecting ${name} %c(css)%c - %cframe(${label})(${window.location})`,
+ "color: #60f36c",
+ "color: inherit",
+ "color: #006fff",
+ );
+ }
+ // Safari lacks full support for tabs.insertCSS
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/insertCSS
+ // specifically frameId and cssOrigin
+ // if support for those details keys arrives, the method below can be used
+ // NOTE: manifest V3 does support frameId, but not origin
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/insertCSS
- // write the css code to head of the document
- const tag = document.createElement("style");
- tag.textContent = code;
- document.head.appendChild(tag);
+ // write the css code to head of the document
+ const tag = document.createElement("style");
+ tag.textContent = code;
+ document.head.appendChild(tag);
}
function cspFallback(e) {
- // if a security policy violation event has occurred
- // and the directive is script-src or script-src-elem
- // it's fair to assume that there is a strict CSP for javascript
- // and that injection was blocked for all userscripts
- // when any script-src violation is detected, re-attempt injection
- if (
- e.effectiveDirective === "script-src"
- || e.effectiveDirective === "script-src-elem"
- ) {
- // get all "auto" code
- // since other code can trigger a security policy violation event
- // make sure data var is not undefined before attempting fallback
- if (!data || cspFallbackAttempted) return;
- // update global that tracks security policy violations
- cspFallbackAttempted = 1;
- // for all userscripts with @inject-into: auto, attempt re-injection
- for (let i = 0; i < data.files.js.length; i++) {
- const userscript = data.files.js[i];
- if (userscript.scriptObject["inject-into"] !== "auto") continue;
- userscript.fallback = 1;
- triageJS(userscript);
- }
- }
+ // if a security policy violation event has occurred
+ // and the directive is script-src or script-src-elem
+ // it's fair to assume that there is a strict CSP for javascript
+ // and that injection was blocked for all userscripts
+ // when any script-src violation is detected, re-attempt injection
+ if (
+ e.effectiveDirective === "script-src" ||
+ e.effectiveDirective === "script-src-elem"
+ ) {
+ // get all "auto" code
+ // since other code can trigger a security policy violation event
+ // make sure data var is not undefined before attempting fallback
+ if (!data || cspFallbackAttempted) return;
+ // update global that tracks security policy violations
+ cspFallbackAttempted = 1;
+ // for all userscripts with @inject-into: auto, attempt re-injection
+ for (let i = 0; i < data.files.js.length; i++) {
+ const userscript = data.files.js[i];
+ if (userscript.scriptObject["inject-into"] !== "auto") continue;
+ userscript.fallback = 1;
+ triageJS(userscript);
+ }
+ }
}
function injection() {
- browser.runtime.sendMessage({name: "REQ_USERSCRIPTS"}, response => {
- // cancel injection if errors detected
- if (!response || response.error) {
- console.error(response?.error || "REQ_USERSCRIPTS returned undefined");
- return;
- }
- // save response locally in case CSP events occur
- data = response;
- // combine regular and context-menu scripts
- const scripts = [...data.files.js, ...data.files.menu];
- // loop through each userscript and prepare for processing
- for (let i = 0; i < scripts.length; i++) {
- const userscript = scripts[i];
- const filename = userscript.scriptObject.filename;
- const grants = userscript.scriptObject.grant;
- const injectInto = userscript.scriptObject["inject-into"];
- // create GM.info object, all userscripts get access to GM.info
- userscript.apis = {GM: {}};
- userscript.apis.GM.info = {
- script: userscript.scriptObject,
- scriptHandler: data.scriptHandler,
- scriptHandlerVersion: data.scriptHandlerVersion,
- scriptMetaStr: userscript.scriptMetaStr
- };
- // add GM_info
- userscript.apis.GM_info = userscript.apis.GM.info;
- // if @grant explicitly set to none, empty grants array
- if (grants.includes("none")) grants.length = 0;
- // @grant values exist for page scoped userscript
- if (grants.length && injectInto === "page") {
- // remove grants
- grants.length = 0;
- // log warning
- console.warn(`${filename} @grant values removed due to @inject-into value: ${injectInto} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`);
- }
- // @grant exist for auto scoped userscript
- if (grants.length && injectInto === "auto") {
- // change scope
- userscript.scriptObject["inject-into"] = "content";
- // log warning
- console.warn(`${filename} @inject-into value set to 'content' due to @grant values: ${grants} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`);
- }
- // loop through each userscript @grant value, add methods as needed
- for (let j = 0; j < grants.length; j++) {
- const grant = grants[j];
- const method = grant.split(".")[1] || grant.split(".")[0];
- // ensure API method exists in apis object
- if (!Object.keys(apis).includes(method)) continue;
- // add granted methods
- switch (method) {
- case "info":
- case "GM_info":
- continue;
- case "getValue":
- case "setValue":
- case "deleteValue":
- case "listValues":
- userscript.apis.GM[method] = apis[method].bind({US_filename: filename});
- break;
- case "GM_xmlhttpRequest":
- userscript.apis[method] = apis[method];
- break;
- default:
- userscript.apis.GM[method] = apis[method];
- }
- }
- // triage userjs item for injection
- triageJS(userscript);
- }
- // loop through each usercss and inject
- for (let i = 0; i < data.files.css.length; i++) {
- const userstyle = data.files.css[i];
- injectCSS(userstyle.name, userstyle.code);
- }
- });
+ browser.runtime.sendMessage({ name: "REQ_USERSCRIPTS" }, (response) => {
+ // cancel injection if errors detected
+ if (!response || response.error) {
+ console.error(response?.error || "REQ_USERSCRIPTS returned undefined");
+ return;
+ }
+ // save response locally in case CSP events occur
+ data = response;
+ // combine regular and context-menu scripts
+ const scripts = [...data.files.js, ...data.files.menu];
+ // loop through each userscript and prepare for processing
+ for (let i = 0; i < scripts.length; i++) {
+ const userscript = scripts[i];
+ const filename = userscript.scriptObject.filename;
+ const grants = userscript.scriptObject.grant;
+ const injectInto = userscript.scriptObject["inject-into"];
+ // create GM.info object, all userscripts get access to GM.info
+ userscript.apis = { GM: {} };
+ userscript.apis.GM.info = {
+ script: userscript.scriptObject,
+ scriptHandler: data.scriptHandler,
+ scriptHandlerVersion: data.scriptHandlerVersion,
+ scriptMetaStr: userscript.scriptMetaStr,
+ };
+ // add GM_info
+ userscript.apis.GM_info = userscript.apis.GM.info;
+ // if @grant explicitly set to none, empty grants array
+ if (grants.includes("none")) grants.length = 0;
+ // @grant values exist for page scoped userscript
+ if (grants.length && injectInto === "page") {
+ // remove grants
+ grants.length = 0;
+ // log warning
+ console.warn(
+ `${filename} @grant values removed due to @inject-into value: ${injectInto} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`,
+ );
+ }
+ // @grant exist for auto scoped userscript
+ if (grants.length && injectInto === "auto") {
+ // change scope
+ userscript.scriptObject["inject-into"] = "content";
+ // log warning
+ console.warn(
+ `${filename} @inject-into value set to 'content' due to @grant values: ${grants} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`,
+ );
+ }
+ // loop through each userscript @grant value, add methods as needed
+ for (let j = 0; j < grants.length; j++) {
+ const grant = grants[j];
+ const method = grant.split(".")[1] || grant.split(".")[0];
+ // ensure API method exists in apis object
+ if (!Object.keys(apis).includes(method)) continue;
+ // add granted methods
+ switch (method) {
+ case "info":
+ case "GM_info":
+ continue;
+ case "getValue":
+ case "setValue":
+ case "deleteValue":
+ case "listValues":
+ userscript.apis.GM[method] = apis[method].bind({
+ US_filename: filename,
+ });
+ break;
+ case "GM_xmlhttpRequest":
+ userscript.apis[method] = apis[method];
+ break;
+ default:
+ userscript.apis.GM[method] = apis[method];
+ }
+ }
+ // triage userjs item for injection
+ triageJS(userscript);
+ }
+ // loop through each usercss and inject
+ for (let i = 0; i < data.files.css.length; i++) {
+ const userstyle = data.files.css[i];
+ injectCSS(userstyle.name, userstyle.code);
+ }
+ });
}
function listeners() {
- // listens for messages from background, popup, etc...
- browser.runtime.onMessage.addListener(request => {
- const name = request.name;
- if (name === "CONTEXT_RUN") {
- // from bg script when context-menu item is clicked
- // double check to ensure context-menu scripts only run in top windows
- if (window !== window.top) return;
+ // listens for messages from background, popup, etc...
+ browser.runtime.onMessage.addListener((request) => {
+ const name = request.name;
+ if (name === "CONTEXT_RUN") {
+ // from bg script when context-menu item is clicked
+ // double check to ensure context-menu scripts only run in top windows
+ if (window !== window.top) return;
- // loop through context-menu scripts saved to data object and find match
- // if no match found, nothing will execute and error will log
- const filename = request.menuItemId;
- for (let i = 0; i < data.files.menu.length; i++) {
- const item = data.files.menu[i];
- if (item.scriptObject.filename === filename) {
- console.info(`Injecting ${filename} %c(js)`, "color: #fff600");
- injectJS(item);
- return;
- }
- }
- console.error(`Couldn't find ${filename} code!`);
- }
- });
- // listen for CSP violations
- document.addEventListener("securitypolicyviolation", cspFallback);
+ // loop through context-menu scripts saved to data object and find match
+ // if no match found, nothing will execute and error will log
+ const filename = request.menuItemId;
+ for (let i = 0; i < data.files.menu.length; i++) {
+ const item = data.files.menu[i];
+ if (item.scriptObject.filename === filename) {
+ console.info(`Injecting ${filename} %c(js)`, "color: #fff600");
+ injectJS(item);
+ return;
+ }
+ }
+ console.error(`Couldn't find ${filename} code!`);
+ }
+ });
+ // listen for CSP violations
+ document.addEventListener("securitypolicyviolation", cspFallback);
}
async function initialize() {
- const results = await browser.storage.local.get("US_GLOBAL_ACTIVE");
- if (results?.US_GLOBAL_ACTIVE === false) return console.info("Userscripts off");
- // start the injection process and add the listeners
- injection();
- listeners();
+ const results = await browser.storage.local.get("US_GLOBAL_ACTIVE");
+ if (results?.US_GLOBAL_ACTIVE === false)
+ return console.info("Userscripts off");
+ // start the injection process and add the listeners
+ injection();
+ listeners();
}
initialize();
diff --git a/src/ext/extension-page/App.svelte b/src/ext/extension-page/App.svelte
index 62e46a66..ac1b853a 100644
--- a/src/ext/extension-page/App.svelte
+++ b/src/ext/extension-page/App.svelte
@@ -1,133 +1,136 @@
-
+
{#if $state.includes("init")}
-
- {@html logo}
- {#if $state.includes("init-error")}
- Failed to initialize app, check the browser console
- {:else}
- Initializing app...
- {/if}
-
+
+
+ {@html logo}
+ {#if $state.includes("init-error")}
+ Failed to initialize app, check the browser console
+ {:else}
+ Initializing app...
+ {/if}
+
{/if}
-
-
+
+
- {#each $notifications as item (item.id)}
- notifications.remove(item.id)} {item}/>
- {/each}
+ {#each $notifications as item (item.id)}
+ notifications.remove(item.id)} {item} />
+ {/each}
-{#if $state.includes("settings")}{/if}
+{#if $state.includes("settings")}{/if}
diff --git a/src/ext/extension-page/Components/Editor/CodeMirror.svelte b/src/ext/extension-page/Components/Editor/CodeMirror.svelte
index 849662ef..e0c39081 100644
--- a/src/ext/extension-page/Components/Editor/CodeMirror.svelte
+++ b/src/ext/extension-page/Components/Editor/CodeMirror.svelte
@@ -1,390 +1,409 @@
+
+
{#if instance}
- searchActive = false}
- instance={instance}
- />
+ (searchActive = false)}
+ {instance}
+ />
{/if}
diff --git a/src/ext/extension-page/Components/Editor/Editor.svelte b/src/ext/extension-page/Components/Editor/Editor.svelte
index e8f1ed49..46163ec6 100644
--- a/src/ext/extension-page/Components/Editor/Editor.svelte
+++ b/src/ext/extension-page/Components/Editor/Editor.svelte
@@ -1,329 +1,346 @@
- {#if $state.includes("editor-loading") || $state.includes("fetching")}
-
- {/if}
- {#if !activeItem}
-
No Item Selected
- {/if}
-
-
-
-
-
+ {#if $state.includes("editor-loading") || $state.includes("fetching")}
+
+ {/if}
+ {#if !activeItem}
+
No Item Selected
+ {/if}
+
+
+
+
+
diff --git a/src/ext/extension-page/Components/Editor/EditorSearch.svelte b/src/ext/extension-page/Components/Editor/EditorSearch.svelte
index ff1833cb..cc1f70db 100644
--- a/src/ext/extension-page/Components/Editor/EditorSearch.svelte
+++ b/src/ext/extension-page/Components/Editor/EditorSearch.svelte
@@ -1,199 +1,207 @@
{#if active}
-
-
- {rangesIndex}/{query ? ranges.length : "?"}
- next()}/>
- previous()}/>
-
-
+
+
+ {rangesIndex}/{query ? ranges.length : "?"}
+ next()} />
+ previous()} />
+
+
{/if}
diff --git a/src/ext/extension-page/Components/Notification.svelte b/src/ext/extension-page/Components/Notification.svelte
index 9896909b..f79e5343 100644
--- a/src/ext/extension-page/Components/Notification.svelte
+++ b/src/ext/extension-page/Components/Notification.svelte
@@ -1,148 +1,154 @@
-
-
-
{@html icon}
-
{item.message}
-
-
+
+
+
+
{@html icon}
+
{item.message}
+
+
diff --git a/src/ext/extension-page/Components/Settings.svelte b/src/ext/extension-page/Components/Settings.svelte
index 40f7f7b3..fdbe1b7f 100644
--- a/src/ext/extension-page/Components/Settings.svelte
+++ b/src/ext/extension-page/Components/Settings.svelte
@@ -1,318 +1,351 @@
-
-
-
state.remove("settings")}>
-
-
-
-
Editor Settings
-
state.remove("settings")}/>
-
-
-
Auto Close Brackets
-
update("autoCloseBrackets", !$settings.autoCloseBrackets)}
- />
-
-
-
Auto Hint
-
update("autoHint", !$settings.autoHint)}
- />
-
-
-
Hide Descriptions
-
update("descriptions", !$settings.descriptions)}
- />
-
-
-
Javascript Linter
-
update("lint", !$settings.lint)}
- />
-
-
-
Show Invisibles
-
update("showInvisibles", !$settings.showInvisibles)}
- />
-
-
-
Tab Size
-
-
-
-
-
-
-
Enable Injection
-
update("active", !$settings.active)}
- />
-
-
-
Show Toolbar Count
-
update("showCount", !$settings.showCount)}
- />
-
-
-
Save Location
-
-
-
-
-
- Global exclude match patterns
- { #if blacklistSaving}{@html iconLoader}{/if}
-
-
-
-
-
-
Information
-
- Userscripts Safari Version {$settings.version} ({$settings.build})
-
- You can review the documentation, report bugs and get more information about this extension by visiting
-
-
- If you enjoy using this extension, please consider
-
- on the App Store or
-
-
-
-
+
+
+
state.remove("settings")}>
+
+
+
+
Editor Settings
+
state.remove("settings")}
+ />
+
+
+
Auto Close Brackets
+
+ update("autoCloseBrackets", !$settings.autoCloseBrackets)}
+ />
+
+
+
Auto Hint
+
update("autoHint", !$settings.autoHint)}
+ />
+
+
+
Hide Descriptions
+
update("descriptions", !$settings.descriptions)}
+ />
+
+
+
Javascript Linter
+
update("lint", !$settings.lint)}
+ />
+
+
+
Show Invisibles
+
update("showInvisibles", !$settings.showInvisibles)}
+ />
+
+
+
Tab Size
+
+
+
+
+
+
+
Enable Injection
+
update("active", !$settings.active)}
+ />
+
+
+
Show Toolbar Count
+
update("showCount", !$settings.showCount)}
+ />
+
+
+
Save Location
+
+
+
+
+
+ Global exclude match patterns
+
+ {#if blacklistSaving}{@html iconLoader}{/if}
+
+
+
+
+
+
Information
+
+ Userscripts Safari Version {$settings.version} ({$settings.build})
+
+ You can review the documentation, report bugs and get more information about
+ this extension by visiting
+
+
+ If you enjoy using this extension, please consider
+
+ on the App Store or
+
+
+
+
diff --git a/src/ext/extension-page/Components/Sidebar/Sidebar.svelte b/src/ext/extension-page/Components/Sidebar/Sidebar.svelte
index 4504b4a1..c554c4ea 100644
--- a/src/ext/extension-page/Components/Sidebar/Sidebar.svelte
+++ b/src/ext/extension-page/Components/Sidebar/Sidebar.svelte
@@ -1,260 +1,266 @@
-