Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f87de84ce | ||
|
|
b58ecec1f9 |
@@ -1505,11 +1505,12 @@ wrote last, the record says which build's shape it is in.
|
|||||||
Each write reads the record, changes it and writes it back, holding a lock from
|
Each write reads the record, changes it and writes it back, holding a lock from
|
||||||
the read to the write: the browser's own (`navigator.locks`), taken through
|
the read to the write: the browser's own (`navigator.locks`), taken through
|
||||||
`withLock()` in `src/shared/browserApi.js`. Every page of the extension and its
|
`withLock()` in `src/shared/browserApi.js`. Every page of the extension and its
|
||||||
background share it, except a page in a Firefox private window, which Firefox
|
background share it, except a page in a Firefox private window, which has locks
|
||||||
gives locks of its own (see https://git.eeqj.de/sneak/AutistMask/issues/646).
|
of its own. `manifest/firefox.json` sets `"incognito": "not_allowed"`, so such a
|
||||||
The popup's `saveState()`, the background's `updateState()` and the
|
page cannot read or write extension storage. In Chrome, a page opened from an
|
||||||
StateRecovery reset all take it, so among the pages and the background that
|
incognito window still runs outside incognito and shares the lock. The popup's
|
||||||
share it, no write lands between another write's read and its write, where it
|
`saveState()`, the background's `updateState()` and the StateRecovery reset all
|
||||||
|
take it, so no write lands between another write's read and its write, where it
|
||||||
would be lost. Chrome has it from version 69, older than any Chrome that runs a
|
would be lost. Chrome has it from version 69, older than any Chrome that runs a
|
||||||
Manifest V3 extension, in pages and in the service worker; Firefox from version
|
Manifest V3 extension, in pages and in the service worker; Firefox from version
|
||||||
96, older than the 115 `manifest/firefox.json` declares. Where it is missing, a
|
96, older than the 115 `manifest/firefox.json` declares. Where it is missing, a
|
||||||
@@ -2654,6 +2655,8 @@ view would leave a wallet one click from deletion.
|
|||||||
Data Model) → full-sentence error on the error line saying why, nothing
|
Data Model) → full-sentence error on the error line saying why, nothing
|
||||||
signed (no screen change)
|
signed (no screen change)
|
||||||
- "Reject" → closes popup (returns rejection to background)
|
- "Reject" → closes popup (returns rejection to background)
|
||||||
|
- "Reject" after "Confirm", while the transaction is being sent → the
|
||||||
|
background's message on the error line, no screen change
|
||||||
- Popup window closed without answering → the request stays pending, and the
|
- Popup window closed without answering → the request stays pending, and the
|
||||||
site gets no answer. The browser can close this window when the user
|
site gets no answer. The browser can close this window when the user
|
||||||
clicks away from it, for example to copy the password from a password
|
clicks away from it, for example to copy the password from a password
|
||||||
@@ -2748,6 +2751,8 @@ view would leave a wallet one click from deletion.
|
|||||||
- "Sign" (wrong password, or a signing failure) → error line, no screen
|
- "Sign" (wrong password, or a signing failure) → error line, no screen
|
||||||
change
|
change
|
||||||
- "Reject" → closes popup (returns rejection to background)
|
- "Reject" → closes popup (returns rejection to background)
|
||||||
|
- "Reject" after "Sign", while the signature is being checked → the
|
||||||
|
background's message on the error line, no screen change
|
||||||
- Popup window closed without answering → the request stays pending, as on
|
- Popup window closed without answering → the request stays pending, as on
|
||||||
TxApproval. Until the request is answered, the toolbar button opens this
|
TxApproval. Until the request is answered, the toolbar button opens this
|
||||||
screen instead of the wallet.
|
screen instead of the wallet.
|
||||||
|
|||||||
@@ -208,6 +208,9 @@ password.
|
|||||||
3. Click "Load Temporary Add-on".
|
3. Click "Load Temporary Add-on".
|
||||||
4. Select `dist/firefox/manifest.json`.
|
4. Select `dist/firefox/manifest.json`.
|
||||||
|
|
||||||
|
AutistMask does not work in Firefox private windows. If Firefox is set to "Never
|
||||||
|
remember history", every window is private, so AutistMask does not work at all.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Creating a New Wallet
|
### Creating a New Wallet
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Minimal Ethereum wallet for Firefox",
|
"description": "Minimal Ethereum wallet for Firefox",
|
||||||
"permissions": ["storage", "activeTab", "alarms", "<all_urls>"],
|
"permissions": ["storage", "activeTab", "alarms", "<all_urls>"],
|
||||||
|
"incognito": "not_allowed",
|
||||||
"content_security_policy": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' https: http:; frame-src 'none'; form-action 'none'; base-uri 'none'",
|
"content_security_policy": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' https: http:; frame-src 'none'; form-action 'none'; base-uri 'none'",
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "icons/icon16.png",
|
"16": "icons/icon16.png",
|
||||||
|
|||||||
@@ -390,10 +390,11 @@ let toolbarPopupApprovalId = null;
|
|||||||
// Point the toolbar popup at the oldest pending approval whose window closed,
|
// Point the toolbar popup at the oldest pending approval whose window closed,
|
||||||
// or at the wallet when there is none. The user can then answer that approval
|
// or at the wallet when there is none. The user can then answer that approval
|
||||||
// from the toolbar popup. pendingApprovals lists approvals in the order they
|
// from the toolbar popup. pendingApprovals lists approvals in the order they
|
||||||
// were added, so the first one found is the oldest.
|
// were added, so the first one found is the oldest. An approval held by a
|
||||||
|
// running attempt is skipped until that attempt ends.
|
||||||
function updateToolbarPopup() {
|
function updateToolbarPopup() {
|
||||||
const closed = Object.values(pendingApprovals).find(
|
const closed = Object.values(pendingApprovals).find(
|
||||||
(approval) => approval.windowClosed,
|
(approval) => approval.windowClosed && !approval.attemptInFlight,
|
||||||
);
|
);
|
||||||
toolbarPopupApprovalId = closed ? closed.id : null;
|
toolbarPopupApprovalId = closed ? closed.id : null;
|
||||||
if (actionNs && typeof actionNs.setPopup === "function") {
|
if (actionNs && typeof actionNs.setPopup === "function") {
|
||||||
@@ -478,6 +479,7 @@ const APPROVAL_WINDOW_FAILED_MESSAGE =
|
|||||||
function claimApproval(approval) {
|
function claimApproval(approval) {
|
||||||
if (approval.attemptInFlight) return false;
|
if (approval.attemptInFlight) return false;
|
||||||
approval.attemptInFlight = true;
|
approval.attemptInFlight = true;
|
||||||
|
updateToolbarPopup();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,6 +488,7 @@ function claimApproval(approval) {
|
|||||||
// its window while the attempt ran, the toolbar popup opens it for that retry.
|
// its window while the attempt ran, the toolbar popup opens it for that retry.
|
||||||
function releaseApproval(approval) {
|
function releaseApproval(approval) {
|
||||||
approval.attemptInFlight = false;
|
approval.attemptInFlight = false;
|
||||||
|
updateToolbarPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open approval in a separate popup window, unless it is no longer pending.
|
// Open approval in a separate popup window, unless it is no longer pending.
|
||||||
@@ -1488,9 +1491,9 @@ updateToolbarPopup();
|
|||||||
// A transaction or signature approval stays pending when its window closes.
|
// A transaction or signature approval stays pending when its window closes.
|
||||||
// The browser can close that window when the user clicks away from it, for
|
// The browser can close that window when the user clicks away from it, for
|
||||||
// example to copy the password from a password manager. The approval is marked
|
// example to copy the password from a password manager. The approval is marked
|
||||||
// as closed, and from then on the toolbar popup opens it (see
|
// as closed, so the toolbar popup can open it (see updateToolbarPopup()). An
|
||||||
// updateToolbarPopup()). An attempt already running when the window closes
|
// attempt already running when the window closes goes on and reports its
|
||||||
// goes on and reports its outcome to the page.
|
// outcome to the page.
|
||||||
//
|
//
|
||||||
// This listener rejects a site-connection or network-switch approval whose
|
// This listener rejects a site-connection or network-switch approval whose
|
||||||
// window closes before its popup connected its port. Once the port is
|
// window closes before its popup connected its port. Once the port is
|
||||||
|
|||||||
@@ -18,11 +18,10 @@
|
|||||||
// updateState(fn) read fresh, apply fn to that fresh record, write it
|
// updateState(fn) read fresh, apply fn to that fresh record, write it
|
||||||
// back — all holding the lock every write of the stored
|
// back — all holding the lock every write of the stored
|
||||||
// record takes, so no other write lands in between, by
|
// record takes, so no other write lands in between, by
|
||||||
// the background or by any page except a page in a
|
// the background or by any page (README, "Stored state
|
||||||
// Firefox private window (README, "Stored state and its
|
// and its version"). The read is one storage round trip
|
||||||
// version"). The read is one storage round trip ahead of
|
// ahead of the write rather than a page lifetime ahead of
|
||||||
// the write rather than a page lifetime ahead of it
|
// it (which is what made the popup's saveState() need a
|
||||||
// (which is what made the popup's saveState() need a
|
|
||||||
// per-field merge against a baseline at all).
|
// per-field merge against a baseline at all).
|
||||||
//
|
//
|
||||||
// A handler that must both read and write therefore does its network work
|
// A handler that must both read and write therefore does its network work
|
||||||
@@ -73,8 +72,7 @@ async function updateStateOnce(mutate) {
|
|||||||
// src/shared/browserApi.js) is held from the read to the write: two
|
// src/shared/browserApi.js) is held from the read to the write: two
|
||||||
// background turns, or a turn and a page's save, never interleave
|
// background turns, or a turn and a page's save, never interleave
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/641). Every page's save waits
|
// (https://git.eeqj.de/sneak/AutistMask/issues/641). Every page's save waits
|
||||||
// while it is held, except a page in a Firefox private window, which Firefox
|
// while it is held (README, "Stored state and its version").
|
||||||
// gives locks of its own (README, "Stored state and its version").
|
|
||||||
//
|
//
|
||||||
// `mutate` must also not call updateState() itself, directly or through
|
// `mutate` must also not call updateState() itself, directly or through
|
||||||
// anything it awaits: the inner turn waits for the lock the outer one holds,
|
// anything it awaits: the inner turn waits for the lock the outer one holds,
|
||||||
|
|||||||
@@ -1144,13 +1144,19 @@ function init(_ctx) {
|
|||||||
|
|
||||||
// The window closes only once the background has the rejection. A message
|
// The window closes only once the background has the rejection. A message
|
||||||
// sent as the window closes can be lost, and a closed window does not
|
// sent as the window closes can be lost, and a closed window does not
|
||||||
// reject the request.
|
// reject the request. After Confirm, the background refuses a rejection
|
||||||
|
// while the transaction is being sent. The window then stays open and
|
||||||
|
// shows the background's message.
|
||||||
$("btn-reject-tx").addEventListener("click", async () => {
|
$("btn-reject-tx").addEventListener("click", async () => {
|
||||||
await sendMessage({
|
const response = await sendMessage({
|
||||||
type: "AUTISTMASK_TX_RESPONSE",
|
type: "AUTISTMASK_TX_RESPONSE",
|
||||||
id: approvalId,
|
id: approvalId,
|
||||||
approved: false,
|
approved: false,
|
||||||
}).catch(() => {});
|
}).catch(() => null);
|
||||||
|
if (response && response.error) {
|
||||||
|
showError("approve-tx-error", response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1273,13 +1279,18 @@ function init(_ctx) {
|
|||||||
if (outcome.retryable) setSignButtonBusy(false);
|
if (outcome.retryable) setSignButtonBusy(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// As for the transaction's Reject above.
|
// As for the transaction's Reject above. After Sign, the background
|
||||||
|
// refuses a rejection while the signature is being checked.
|
||||||
$("btn-reject-sign").addEventListener("click", async () => {
|
$("btn-reject-sign").addEventListener("click", async () => {
|
||||||
await sendMessage({
|
const response = await sendMessage({
|
||||||
type: "AUTISTMASK_SIGN_RESPONSE",
|
type: "AUTISTMASK_SIGN_RESPONSE",
|
||||||
id: approvalId,
|
id: approvalId,
|
||||||
approved: false,
|
approved: false,
|
||||||
}).catch(() => {});
|
}).catch(() => null);
|
||||||
|
if (response && response.error) {
|
||||||
|
showError("approve-sign-error", response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,8 +143,6 @@ async function resetProfile() {
|
|||||||
try {
|
try {
|
||||||
// Under the lock every write of the stored record takes, so the erase
|
// Under the lock every write of the stored record takes, so the erase
|
||||||
// cannot land between another write's read and its write and be undone.
|
// cannot land between another write's read and its write and be undone.
|
||||||
// A page in a Firefox private window is the exception: Firefox gives
|
|
||||||
// it locks of its own (README, "Stored state and its version").
|
|
||||||
await withLock("autistmask", () => storageRemove("autistmask"));
|
await withLock("autistmask", () => storageRemove("autistmask"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.errorf("state recovery: reset failed:", e);
|
log.errorf("state recovery: reset failed:", e);
|
||||||
|
|||||||
+1
-3
@@ -581,9 +581,7 @@ function reportSaveFailure(err) {
|
|||||||
// Each save runs start to finish holding the lock every write of the stored
|
// Each save runs start to finish holding the lock every write of the stored
|
||||||
// record takes (withLock() in src/shared/browserApi.js), so no write by
|
// record takes (withLock() in src/shared/browserApi.js), so no write by
|
||||||
// another page or the background lands between its read and its write and is
|
// another page or the background lands between its read and its write and is
|
||||||
// lost (https://git.eeqj.de/sneak/AutistMask/issues/641). A page in a Firefox
|
// lost (https://git.eeqj.de/sneak/AutistMask/issues/641).
|
||||||
// private window is the exception: Firefox gives it locks of its own (README,
|
|
||||||
// "Stored state and its version").
|
|
||||||
//
|
//
|
||||||
// The same lock orders this page's own saves. showView() calls saveState() on
|
// The same lock orders this page's own saves. showView() calls saveState() on
|
||||||
// every navigation without awaiting it, so two saves from one page can be
|
// every navigation without awaiting it, so two saves from one page can be
|
||||||
|
|||||||
@@ -176,17 +176,17 @@ test("a connection prompt in the toolbar popup leaves the browser window alone",
|
|||||||
expect(env.pageErrors).toEqual([]);
|
expect(env.pageErrors).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// A closed transaction or signature window leaves its request pending
|
// Each row holds a transaction or signature approval as the background
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/673). So Reject closes the
|
// describes it, plus the element ids of the screen that shows it. `refusal` is
|
||||||
// window only once the background has the rejection. A message sent as the
|
// the message the background refuses a Reject with.
|
||||||
// window closes can be lost, and the request would stay pending although the
|
const TX_AND_SIGN_APPROVALS = [
|
||||||
// user rejected it.
|
|
||||||
test.each([
|
|
||||||
{
|
{
|
||||||
kind: "transaction",
|
kind: "transaction",
|
||||||
view: "approve-tx",
|
view: "approve-tx",
|
||||||
reject: "btn-reject-tx",
|
reject: "btn-reject-tx",
|
||||||
|
error: "approve-tx-error",
|
||||||
type: "AUTISTMASK_TX_RESPONSE",
|
type: "AUTISTMASK_TX_RESPONSE",
|
||||||
|
refusal: "This transaction is already being sent.",
|
||||||
approval: {
|
approval: {
|
||||||
type: "tx",
|
type: "tx",
|
||||||
approvedFrom: ADDRESS,
|
approvedFrom: ADDRESS,
|
||||||
@@ -206,7 +206,9 @@ test.each([
|
|||||||
kind: "signature",
|
kind: "signature",
|
||||||
view: "approve-sign",
|
view: "approve-sign",
|
||||||
reject: "btn-reject-sign",
|
reject: "btn-reject-sign",
|
||||||
|
error: "approve-sign-error",
|
||||||
type: "AUTISTMASK_SIGN_RESPONSE",
|
type: "AUTISTMASK_SIGN_RESPONSE",
|
||||||
|
refusal: "This request is already being signed.",
|
||||||
approval: {
|
approval: {
|
||||||
type: "sign",
|
type: "sign",
|
||||||
approvedFrom: ADDRESS,
|
approvedFrom: ADDRESS,
|
||||||
@@ -217,7 +219,14 @@ test.each([
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])(
|
];
|
||||||
|
|
||||||
|
// A closed transaction or signature window leaves its request pending
|
||||||
|
// (https://git.eeqj.de/sneak/AutistMask/issues/673). So Reject closes the
|
||||||
|
// window only once the background has the rejection. A message sent as the
|
||||||
|
// window closes can be lost, and the request would stay pending although the
|
||||||
|
// user rejected it.
|
||||||
|
test.each(TX_AND_SIGN_APPROVALS)(
|
||||||
"Reject on a $kind approval closes the window only once the background has answered",
|
"Reject on a $kind approval closes the window only once the background has answered",
|
||||||
async ({ view, reject, type, approval }) => {
|
async ({ view, reject, type, approval }) => {
|
||||||
let describeApproval = null;
|
let describeApproval = null;
|
||||||
@@ -259,3 +268,43 @@ test.each([
|
|||||||
expect(env.pageErrors).toEqual([]);
|
expect(env.pageErrors).toEqual([]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// After Confirm or Sign, the background refuses a Reject while the transaction
|
||||||
|
// is being sent or the signature checked. The request is not rejected then, so
|
||||||
|
// the window stays open and shows the background's message.
|
||||||
|
test.each(TX_AND_SIGN_APPROVALS)(
|
||||||
|
"Reject on a $kind approval that the background refuses shows the background's message and keeps the window open",
|
||||||
|
async ({ view, reject, error, type, refusal, approval }) => {
|
||||||
|
let describeApproval = null;
|
||||||
|
let answerReject = null;
|
||||||
|
const env = await bootPopup(unversionedValidProfile(), {
|
||||||
|
search: "?approval=approval-1",
|
||||||
|
runtime: {
|
||||||
|
connect: () => ({ postMessage: () => {} }),
|
||||||
|
sendMessage: (msg, reply) => {
|
||||||
|
if (msg.type === "AUTISTMASK_GET_APPROVAL") {
|
||||||
|
describeApproval = reply;
|
||||||
|
}
|
||||||
|
if (msg.type === type) answerReject = reply;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
describeApproval({
|
||||||
|
...approval,
|
||||||
|
origin: "https://dapp.example",
|
||||||
|
isPhishingDomain: false,
|
||||||
|
});
|
||||||
|
await settle();
|
||||||
|
const close = jest.fn();
|
||||||
|
globalThis.window.close = close;
|
||||||
|
|
||||||
|
const clicked = env.click(reject);
|
||||||
|
await settle();
|
||||||
|
answerReject({ error: refusal, retryable: false });
|
||||||
|
await clicked;
|
||||||
|
expect(close).not.toHaveBeenCalled();
|
||||||
|
expect(env.visibleViews()).toEqual([view]);
|
||||||
|
expect(env.text(error)).toBe(refusal);
|
||||||
|
expect(env.pageErrors).toEqual([]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -1989,6 +1989,90 @@ describe("a closed approval window leaves the request pending", () => {
|
|||||||
expect(toolbarPopup(bg)).toBe("src/popup/index.html");
|
expect(toolbarPopup(bg)).toBe("src/popup/index.html");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The user can close the window while the attempt after Confirm runs. That
|
||||||
|
// attempt answers the request, so meanwhile the toolbar popup opens the
|
||||||
|
// wallet. Opened from the toolbar, the approval would look unanswered.
|
||||||
|
test("when the window closes during an attempt that sends, the toolbar popup keeps opening the wallet", async () => {
|
||||||
|
const bg = loadBackground();
|
||||||
|
const pending = bg.requestTx();
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
const inFlight = deferred();
|
||||||
|
bg.broadcastTransaction.mockReturnValue(inFlight.promise);
|
||||||
|
bg.send(
|
||||||
|
{
|
||||||
|
type: "AUTISTMASK_TX_RESPONSE",
|
||||||
|
id: pending.id(),
|
||||||
|
approved: true,
|
||||||
|
rawSignedTx: await signedAtNonce(NONCE),
|
||||||
|
},
|
||||||
|
{ url: bg.fromPopup.url },
|
||||||
|
);
|
||||||
|
await settle();
|
||||||
|
bg.closeWindow(1);
|
||||||
|
await settle();
|
||||||
|
expect(toolbarPopup(bg)).toBe("src/popup/index.html");
|
||||||
|
|
||||||
|
inFlight.resolve({ hash: "0xfeed" });
|
||||||
|
await settle();
|
||||||
|
expect(pending.result()).toEqual({ result: "0xfeed" });
|
||||||
|
expect(toolbarPopup(bg)).toBe("src/popup/index.html");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("when the window closes during an attempt that fails retryably, the toolbar popup opens the wallet until the attempt ends, then the approval", async () => {
|
||||||
|
const stalled = deferred();
|
||||||
|
const bg = loadBackground();
|
||||||
|
const pending = bg.requestTx();
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
bg.setStateReadHook(async () => {
|
||||||
|
await stalled.promise;
|
||||||
|
throw new Error("The wallet data could not be read.");
|
||||||
|
});
|
||||||
|
bg.send(
|
||||||
|
{
|
||||||
|
type: "AUTISTMASK_TX_RESPONSE",
|
||||||
|
id: pending.id(),
|
||||||
|
approved: true,
|
||||||
|
rawSignedTx: await signedAtNonce(NONCE),
|
||||||
|
},
|
||||||
|
{ url: bg.fromPopup.url },
|
||||||
|
);
|
||||||
|
await settle();
|
||||||
|
bg.closeWindow(1);
|
||||||
|
await settle();
|
||||||
|
expect(toolbarPopup(bg)).toBe("src/popup/index.html");
|
||||||
|
|
||||||
|
stalled.resolve();
|
||||||
|
await settle();
|
||||||
|
expect(pending.result()).toBeNull();
|
||||||
|
expect(toolbarPopup(bg)).toBe(
|
||||||
|
"src/popup/index.html?approval=" + pending.id(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// A retry from the toolbar popup holds the approval again, so the
|
||||||
|
// toolbar popup opens the wallet while that retry runs.
|
||||||
|
bg.setStateReadHook(null);
|
||||||
|
const inFlight = deferred();
|
||||||
|
bg.broadcastTransaction.mockReturnValue(inFlight.promise);
|
||||||
|
bg.send(
|
||||||
|
{
|
||||||
|
type: "AUTISTMASK_TX_RESPONSE",
|
||||||
|
id: pending.id(),
|
||||||
|
approved: true,
|
||||||
|
rawSignedTx: await signedAtNonce(NONCE),
|
||||||
|
},
|
||||||
|
{ url: bg.fromPopup.url },
|
||||||
|
);
|
||||||
|
await settle();
|
||||||
|
expect(toolbarPopup(bg)).toBe("src/popup/index.html");
|
||||||
|
|
||||||
|
inFlight.resolve({ hash: "0xfeed" });
|
||||||
|
await settle();
|
||||||
|
expect(pending.result()).toEqual({ result: "0xfeed" });
|
||||||
|
expect(toolbarPopup(bg)).toBe("src/popup/index.html");
|
||||||
|
});
|
||||||
|
|
||||||
test("an address switch still rejects a closed approval", async () => {
|
test("an address switch still rejects a closed approval", async () => {
|
||||||
const bg = loadBackground();
|
const bg = loadBackground();
|
||||||
const pending = bg.requestTx();
|
const pending = bg.requestTx();
|
||||||
|
|||||||
@@ -165,6 +165,15 @@ describe("declared icons", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Firefox gives a page in a private window locks of its own, so that page's
|
||||||
|
// save could land between the background's read and its write (README,
|
||||||
|
// "Stored state and its version").
|
||||||
|
describe("private windows", () => {
|
||||||
|
test("a page in a firefox private window cannot read or write extension storage", () => {
|
||||||
|
expect(readManifest("firefox").incognito).toBe("not_allowed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("shipped Content Security Policy", () => {
|
describe("shipped Content Security Policy", () => {
|
||||||
// MV3 takes an object and applies extension_pages to the popup and the
|
// MV3 takes an object and applies extension_pages to the popup and the
|
||||||
// background service worker, which is where libsodium runs.
|
// background service worker, which is where libsodium runs.
|
||||||
|
|||||||
@@ -6,9 +6,8 @@
|
|||||||
// now holds a lock from its read to its write.
|
// now holds a lock from its read to its write.
|
||||||
//
|
//
|
||||||
// The popup and the background run here as the real modules over one shared
|
// The popup and the background run here as the real modules over one shared
|
||||||
// storage stub, and share the lock the way the background and a page outside
|
// storage stub, and share the lock the way the background and its pages share
|
||||||
// a Firefox private window share the browser's
|
// the browser's (tests/support/navigatorLocks.js).
|
||||||
// (tests/support/navigatorLocks.js).
|
|
||||||
|
|
||||||
const { makeStorageStub } = require("./support/storageStub");
|
const { makeStorageStub } = require("./support/storageStub");
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,8 @@
|
|||||||
// the callback settles, whether it resolved or rejected. That is what the
|
// the callback settles, whether it resolved or rejected. That is what the
|
||||||
// browser's own does for the one call the extension makes,
|
// browser's own does for the one call the extension makes,
|
||||||
// `navigator.locks.request(name, callback)`. Every module a test file loads
|
// `navigator.locks.request(name, callback)`. Every module a test file loads
|
||||||
// shares it, as the extension's background and its pages share the browser's,
|
// shares it, as the extension's background and its pages share the browser's
|
||||||
// except a page in a Firefox private window (README, "Stored state and its
|
// (README, "Stored state and its version").
|
||||||
// version").
|
|
||||||
//
|
//
|
||||||
// Run before each test file by `setupFiles` in package.json.
|
// Run before each test file by `setupFiles` in package.json.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user