fix(extension): request Firefox host permission during popup click
Preload popup settings before enabling the clip button so the permission request remains in the user action handler. Add a regression test for the request timing.
This commit is contained in:
1 parent
4242dedd24
commit
dce5e2bc40
4 files changed
+77
-4
No files matched your search
@@ -9,7 +9,7 @@
|
||||
"dev": "vite --config vite.config.ts",
|
||||
"lint:firefox": "web-ext lint --source-dir dist-firefox --output text",
|
||||
"package:firefox": "bun run build:firefox && bun run lint:firefox && web-ext build --source-dir dist-firefox --artifacts-dir web-ext-artifacts --overwrite-dest",
|
||||
"test": "bun test manifest.test.mjs context-menus.test.mjs image-clip.test.mjs tweet-clip.test.mjs selection-clip.test.mjs github-repo.test.mjs xhs-clip.test.mjs zhihu-clip.test.mjs reddit-clip.test.mjs"
|
||||
"test": "bun test manifest.test.mjs context-menus.test.mjs popup.test.mjs image-clip.test.mjs tweet-clip.test.mjs selection-clip.test.mjs github-repo.test.mjs xhs-clip.test.mjs zhihu-clip.test.mjs reddit-clip.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mixmark-io/domino": "2.2.0",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<span class="platform-tag">GitHub</span>
|
||||
</div>
|
||||
</div>
|
||||
<button id="save" class="save-button" type="button">
|
||||
<button id="save" class="save-button" type="button" disabled>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" width="18" height="18">
|
||||
<path d="M5 4.75A1.75 1.75 0 0 1 6.75 3h8.836c.464 0 .91.184 1.238.513l1.663 1.663c.329.328.513.774.513 1.238v12.836A1.75 1.75 0 0 1 17.25 21H6.75A1.75 1.75 0 0 1 5 19.25V4.75Z" />
|
||||
<path d="M8 3v5h7V3M8 21v-7h8v7" />
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
test("requests instance permission during the popup click user action", async () => {
|
||||
const previousChrome = globalThis.chrome;
|
||||
const previousDocument = globalThis.document;
|
||||
const listeners = new Map();
|
||||
const saveButton = {
|
||||
disabled: false,
|
||||
addEventListener: (event, listener) => listeners.set(`save:${event}`, listener),
|
||||
};
|
||||
const settingsButton = { addEventListener: () => {} };
|
||||
const status = { textContent: "", dataset: {} };
|
||||
let finishLoadingSettings;
|
||||
let insideClick = false;
|
||||
let permissionRequests = 0;
|
||||
let captures = 0;
|
||||
|
||||
globalThis.document = {
|
||||
documentElement: { lang: "", dir: "" },
|
||||
querySelectorAll: () => [],
|
||||
querySelector: (selector) => ({ "#save": saveButton, "#settings": settingsButton, "#status": status })[selector],
|
||||
};
|
||||
globalThis.chrome = {
|
||||
i18n: { getMessage: (key) => key, getUILanguage: () => "en" },
|
||||
storage: { local: { get: () => new Promise((resolve) => { finishLoadingSettings = resolve; }) } },
|
||||
permissions: {
|
||||
request: ({ origins }) => {
|
||||
expect(insideClick).toBe(true);
|
||||
expect(origins).toEqual(["https://example.com/*"]);
|
||||
permissionRequests += 1;
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
},
|
||||
runtime: {
|
||||
sendMessage: async (message) => {
|
||||
expect(message).toEqual({ type: "captureCurrentPage" });
|
||||
captures += 1;
|
||||
return { ok: true };
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await import("./src/popup.ts");
|
||||
expect(saveButton.disabled).toBe(true);
|
||||
|
||||
finishLoadingSettings({ instanceUrl: "https://example.com", token: "token", notebookId: "" });
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
expect(saveButton.disabled).toBe(false);
|
||||
|
||||
insideClick = true;
|
||||
const click = listeners.get("save:click")();
|
||||
insideClick = false;
|
||||
await click;
|
||||
|
||||
expect(permissionRequests).toBe(1);
|
||||
expect(captures).toBe(1);
|
||||
expect(status.textContent).toBe("savedToEdgeEver");
|
||||
expect(saveButton.disabled).toBe(false);
|
||||
} finally {
|
||||
globalThis.chrome = previousChrome;
|
||||
globalThis.document = previousDocument;
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import "./styles.css";
|
||||
import { getSettings, requestInstancePermission } from "./extension";
|
||||
import { getSettings, requestInstancePermission, type ExtensionSettings } from "./extension";
|
||||
import { localizeDocument, t } from "./i18n";
|
||||
|
||||
localizeDocument();
|
||||
@@ -7,6 +7,7 @@ localizeDocument();
|
||||
const saveButton = document.querySelector<HTMLButtonElement>("#save");
|
||||
const settingsButton = document.querySelector<HTMLButtonElement>("#settings");
|
||||
const status = document.querySelector<HTMLParagraphElement>("#status");
|
||||
let settings: ExtensionSettings | null = null;
|
||||
|
||||
const setStatus = (message: string, kind: "normal" | "error" | "success" = "normal") => {
|
||||
if (status) {
|
||||
@@ -15,12 +16,20 @@ const setStatus = (message: string, kind: "normal" | "error" | "success" = "norm
|
||||
}
|
||||
};
|
||||
|
||||
if (saveButton) saveButton.disabled = true;
|
||||
void getSettings().then((loadedSettings) => {
|
||||
settings = loadedSettings;
|
||||
if (saveButton) saveButton.disabled = false;
|
||||
}).catch((error: unknown) => {
|
||||
setStatus(error instanceof Error ? error.message : t("saveFailed"), "error");
|
||||
});
|
||||
|
||||
saveButton?.addEventListener("click", async () => {
|
||||
if (!settings) return;
|
||||
saveButton.disabled = true;
|
||||
setStatus(t("readingAndSaving"));
|
||||
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
if (!settings.instanceUrl || !settings.token) {
|
||||
throw new Error(t("completePluginConfiguration"));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user