harden: drop 'unsafe-inline' from style-src (closes #328)
The popup's markup no longer carries style attributes. The 42 in index.html and in the HTML the view helpers build are now Tailwind classes, each computing to the value it replaced, so style-src is 'self' in both manifests, pinned in tests/manifest.test.js. The address dot's 16 colours are written out as whole classes, because Tailwind builds only the classes it finds in the source. The Settings debug well is shown and hidden with the hidden class, since clearing an inline display no longer uncovers it. Two tests that found the colour dot by its inline style now find it by its class. Script that sets element.style is unaffected. Model: opus-5-5
This commit is contained in:
@@ -806,13 +806,15 @@ discoverable.
|
|||||||
on critical screens and when space is available to allow users to disambiguate
|
on critical screens and when space is available to allow users to disambiguate
|
||||||
addresses visually, as a security feature.
|
addresses visually, as a security feature.
|
||||||
- **Tailwind CSS**: Utility-first CSS via Tailwind. No custom CSS classes for
|
- **Tailwind CSS**: Utility-first CSS via Tailwind. No custom CSS classes for
|
||||||
styling. Tailwind is configured with a minimal monochrome palette. This keeps
|
styling, and no `style="..."` attributes, which the
|
||||||
the styling co-located with the markup and eliminates CSS file management. The
|
[Content Security Policy](#content-security-policy) refuses. Tailwind is
|
||||||
handful of classes in `styles/main.css` are not styling: `.copy-flash-*`
|
configured with a minimal monochrome palette. This keeps the styling
|
||||||
carries the copy feedback animation, and `.am-address` carries the rule that
|
co-located with the markup and eliminates CSS file management. The handful of
|
||||||
an address never wraps. Both are invariants that hold in every place they
|
classes in `styles/main.css` are not styling: `.copy-flash-*` carries the copy
|
||||||
appear, and spelling either out as repeated utilities is how one of those
|
feedback animation, and `.am-address` carries the rule that an address never
|
||||||
places drifts away from the rest.
|
wraps. Both are invariants that hold in every place they appear, and spelling
|
||||||
|
either out as repeated utilities is how one of those places drifts away from
|
||||||
|
the rest.
|
||||||
- **Vanilla JS**: No framework (React, Vue, Svelte, etc.). The popup UI is small
|
- **Vanilla JS**: No framework (React, Vue, Svelte, etc.). The popup UI is small
|
||||||
enough that vanilla JS with simple view switching is sufficient. A framework
|
enough that vanilla JS with simple view switching is sufficient. A framework
|
||||||
would add bundle size, build complexity, and attack surface for no benefit at
|
would add bundle size, build complexity, and attack surface for no benefit at
|
||||||
@@ -2205,7 +2207,7 @@ a bare string in `manifest/firefox.json` (MV2):
|
|||||||
|
|
||||||
```
|
```
|
||||||
default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; object-src 'self';
|
default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; object-src 'self';
|
||||||
style-src 'self' 'unsafe-inline'; img-src 'self' data:;
|
style-src 'self'; img-src 'self' data:;
|
||||||
connect-src 'self' https: http:; frame-src 'none'; form-action 'none';
|
connect-src 'self' https: http:; frame-src 'none'; form-action 'none';
|
||||||
base-uri 'none'
|
base-uri 'none'
|
||||||
```
|
```
|
||||||
@@ -2217,15 +2219,17 @@ wallet's own UI. Escaping is the primary fix for that (see
|
|||||||
`src/shared/html.js`); this is the second line, so an escape that does slip
|
`src/shared/html.js`); this is the second line, so an escape that does slip
|
||||||
cannot reach the network.
|
cannot reach the network.
|
||||||
|
|
||||||
Four directives are looser than `'self'`, each for a reason that does not
|
`style-src 'self'` admits the stylesheet and nothing inline: both browsers
|
||||||
|
refuse a `style="..."` attribute and a `<style>` block. So the popup's markup,
|
||||||
|
in `src/popup/index.html` and in the HTML the view helpers build, carries
|
||||||
|
Tailwind classes and never a `style` attribute. Script that sets `element.style`
|
||||||
|
is not affected; that is how the views show and hide their error lines. An
|
||||||
|
inline style that slips in anyway is refused with a console error, which fails
|
||||||
|
both end-to-end suites.
|
||||||
|
|
||||||
|
These directives differ from a plain `'self'`, each for a reason that does not
|
||||||
generalise:
|
generalise:
|
||||||
|
|
||||||
- `style-src 'unsafe-inline'` — `src/popup/index.html` and the view helpers set
|
|
||||||
presentation through `style="..."` attributes, which CSP blocks without this.
|
|
||||||
Chrome enforces `style-src` on attributes, not only on `<style>` blocks, and
|
|
||||||
Firefox has never implemented `style-src-attr`, so there is no narrower
|
|
||||||
spelling that works on both targets. It permits inline **style**; script stays
|
|
||||||
under `script-src`, which does not allow `'unsafe-inline'`.
|
|
||||||
- `img-src data:` — identicons are generated in the popup by
|
- `img-src data:` — identicons are generated in the popup by
|
||||||
`ethereum-blockies-base64` and assigned to `img.src` as `data:` PNGs.
|
`ethereum-blockies-base64` and assigned to `img.src` as `data:` PNGs.
|
||||||
- `connect-src https: http:` — the RPC endpoint is user-configurable and a local
|
- `connect-src https: http:` — the RPC endpoint is user-configurable and a local
|
||||||
|
|||||||
@@ -45,6 +45,17 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-05: The popup's Content Security Policy no longer allows inline style
|
||||||
|
([#328](https://git.eeqj.de/sneak/AutistMask/issues/328)): `style-src` is
|
||||||
|
`'self'` in both manifests, pinned in `tests/manifest.test.js`. The 42
|
||||||
|
`style="..."` attributes in `src/popup/index.html` and in the markup the view
|
||||||
|
helpers build are now Tailwind classes, each computing to the value it
|
||||||
|
replaced. The 16 address dot colours are written out as whole classes, because
|
||||||
|
Tailwind builds only the classes it finds in the source. The Settings debug
|
||||||
|
well is shown and hidden with the `hidden` class, since clearing an inline
|
||||||
|
`display` no longer uncovers it. Script that sets `element.style` is
|
||||||
|
unaffected.
|
||||||
|
|
||||||
- 2026-10-05: The Chrome end-to-end suite covers the last of the
|
- 2026-10-05: The Chrome end-to-end suite covers the last of the
|
||||||
[#150](https://git.eeqj.de/sneak/AutistMask/issues/150) and
|
[#150](https://git.eeqj.de/sneak/AutistMask/issues/150) and
|
||||||
[#151](https://git.eeqj.de/sneak/AutistMask/issues/151) items
|
[#151](https://git.eeqj.de/sneak/AutistMask/issues/151) items
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"permissions": ["storage", "activeTab", "alarms"],
|
"permissions": ["storage", "activeTab", "alarms"],
|
||||||
"host_permissions": ["<all_urls>"],
|
"host_permissions": ["<all_urls>"],
|
||||||
"content_security_policy": {
|
"content_security_policy": {
|
||||||
"extension_pages": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https: http:; frame-src 'none'; form-action 'none'; base-uri 'none'"
|
"extension_pages": "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",
|
||||||
|
|||||||
@@ -4,7 +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>"],
|
||||||
"content_security_policy": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; style-src 'self' 'unsafe-inline'; 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",
|
||||||
"32": "icons/icon32.png",
|
"32": "icons/icon32.png",
|
||||||
|
|||||||
+32
-83
@@ -110,8 +110,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="add-wallet-phrase-warning"
|
id="add-wallet-phrase-warning"
|
||||||
class="text-xs mb-2 border border-border border-dashed p-2"
|
class="text-xs mb-2 border border-border border-dashed p-2 invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
>
|
>
|
||||||
Write these words down and keep them safe. Anyone with
|
Write these words down and keep them safe. Anyone with
|
||||||
them can take your funds; if you lose them, your wallet
|
them can take your funds; if you lose them, your wallet
|
||||||
@@ -262,10 +261,7 @@
|
|||||||
|
|
||||||
<!-- recent transactions across all addresses -->
|
<!-- recent transactions across all addresses -->
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div class="font-bold bg-section py-1 px-2 -mx-2">
|
||||||
class="font-bold bg-section py-1 px-2"
|
|
||||||
style="margin-left: -0.5rem; margin-right: -0.5rem"
|
|
||||||
>
|
|
||||||
Recent Transactions
|
Recent Transactions
|
||||||
</div>
|
</div>
|
||||||
<div id="home-tx-list">
|
<div id="home-tx-list">
|
||||||
@@ -273,7 +269,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-1" style="margin: 0 -0.5rem"> </div>
|
<div class="py-1 -mx-2"> </div>
|
||||||
|
|
||||||
<div class="text-xs text-muted">
|
<div class="text-xs text-muted">
|
||||||
<span
|
<span
|
||||||
@@ -409,8 +405,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
id="export-privkey-flash"
|
id="export-privkey-flash"
|
||||||
class="text-xs mb-2 min-h-[1.25rem]"
|
class="text-xs mb-2 min-h-[1.25rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<div id="export-privkey-password-section" class="mb-2">
|
<div id="export-privkey-password-section" class="mb-2">
|
||||||
<label class="block mb-1">Password</label>
|
<label class="block mb-1">Password</label>
|
||||||
@@ -542,8 +537,7 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
id="send-to-error"
|
id="send-to-error"
|
||||||
class="text-xs"
|
class="text-xs min-h-[1.25rem] text-[#cc0000]"
|
||||||
style="min-height: 1.25rem; color: #cc0000"
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
@@ -619,7 +613,7 @@
|
|||||||
<div class="text-xs text-muted mb-1">Your balance</div>
|
<div class="text-xs text-muted mb-1">Your balance</div>
|
||||||
<div id="confirm-balance" class="text-xs"></div>
|
<div id="confirm-balance" class="text-xs"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="confirm-fee" class="mb-3" style="visibility: hidden">
|
<div id="confirm-fee" class="mb-3 invisible">
|
||||||
<div class="text-xs text-muted mb-1">Network fee</div>
|
<div class="text-xs text-muted mb-1">Network fee</div>
|
||||||
<div id="confirm-fee-amount" class="text-xs"></div>
|
<div id="confirm-fee-amount" class="text-xs"></div>
|
||||||
<!-- Holds its one line of space from the first paint, so
|
<!-- Holds its one line of space from the first paint, so
|
||||||
@@ -627,22 +621,13 @@
|
|||||||
nothing. The placeholder is never seen. -->
|
nothing. The placeholder is never seen. -->
|
||||||
<div
|
<div
|
||||||
id="confirm-fee-reserve"
|
id="confirm-fee-reserve"
|
||||||
class="text-xs text-muted"
|
class="text-xs text-muted invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
>
|
>
|
||||||
reserve pending
|
reserve pending
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div id="confirm-warnings" class="mb-2 invisible"></div>
|
||||||
id="confirm-warnings"
|
<div id="confirm-recipient-warning" class="mb-2 invisible">
|
||||||
class="mb-2"
|
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
id="confirm-recipient-warning"
|
|
||||||
class="mb-2"
|
|
||||||
style="visibility: hidden"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
|
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
|
||||||
>
|
>
|
||||||
@@ -655,14 +640,9 @@
|
|||||||
in confirmTx.js sets it. -->
|
in confirmTx.js sets it. -->
|
||||||
<div
|
<div
|
||||||
id="confirm-contract-warning"
|
id="confirm-contract-warning"
|
||||||
class="mb-2 border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
|
class="mb-2 border border-red-500 border-dashed p-2 text-xs font-bold text-red-500 invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div id="confirm-burn-warning" class="mb-2 invisible">
|
||||||
id="confirm-burn-warning"
|
|
||||||
class="mb-2"
|
|
||||||
style="visibility: hidden"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
|
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
|
||||||
>
|
>
|
||||||
@@ -670,11 +650,7 @@
|
|||||||
here are permanently destroyed and cannot be recovered.
|
here are permanently destroyed and cannot be recovered.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div id="confirm-etherscan-warning" class="mb-2 invisible">
|
||||||
id="confirm-etherscan-warning"
|
|
||||||
class="mb-2"
|
|
||||||
style="visibility: hidden"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
|
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
|
||||||
>
|
>
|
||||||
@@ -684,13 +660,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="confirm-errors"
|
id="confirm-errors"
|
||||||
class="mb-2 border border-border border-dashed p-2"
|
class="mb-2 border border-border border-dashed p-2 invisible min-h-[1.25rem]"
|
||||||
style="visibility: hidden; min-height: 1.25rem"
|
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
id="confirm-amount-fee-error"
|
id="confirm-amount-fee-error"
|
||||||
class="mb-2 border border-border border-dashed p-2 text-xs"
|
class="mb-2 border border-border border-dashed p-2 text-xs invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
>
|
>
|
||||||
Your balance does not cover this amount plus the network
|
Your balance does not cover this amount plus the network
|
||||||
fee. Please go back and send a smaller amount.
|
fee. Please go back and send a smaller amount.
|
||||||
@@ -699,15 +673,13 @@
|
|||||||
in confirmTx.js sets it. -->
|
in confirmTx.js sets it. -->
|
||||||
<div
|
<div
|
||||||
id="confirm-gas-error"
|
id="confirm-gas-error"
|
||||||
class="mb-2 border border-border border-dashed p-2 text-xs"
|
class="mb-2 border border-border border-dashed p-2 text-xs invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<!-- Its sentence names why the fee could not be estimated,
|
<!-- Its sentence names why the fee could not be estimated,
|
||||||
so show() in confirmTx.js sets it. -->
|
so show() in confirmTx.js sets it. -->
|
||||||
<div
|
<div
|
||||||
id="confirm-fee-unknown-error"
|
id="confirm-fee-unknown-error"
|
||||||
class="mb-2 border border-border border-dashed p-2 text-xs"
|
class="mb-2 border border-border border-dashed p-2 text-xs invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="block mb-1 text-xs">Password</label>
|
<label class="block mb-1 text-xs">Password</label>
|
||||||
@@ -719,8 +691,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="confirm-tx-password-error"
|
id="confirm-tx-password-error"
|
||||||
class="text-xs mb-2 min-h-[1.25rem]"
|
class="text-xs mb-2 min-h-[1.25rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<button
|
<button
|
||||||
id="btn-confirm-send"
|
id="btn-confirm-send"
|
||||||
@@ -835,8 +806,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
id="receive-erc20-warning"
|
id="receive-erc20-warning"
|
||||||
class="text-xs border border-border border-dashed p-2 mt-3"
|
class="text-xs border border-border border-dashed p-2 mt-3 invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -864,8 +834,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="add-token-info"
|
id="add-token-info"
|
||||||
class="text-xs text-muted mb-2 min-h-[1.25rem]"
|
class="text-xs text-muted mb-2 min-h-[1.25rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="block mb-1 text-xs text-muted"
|
<label class="block mb-1 text-xs text-muted"
|
||||||
@@ -1051,8 +1020,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
id="settings-dust-threshold"
|
id="settings-dust-threshold"
|
||||||
class="border border-border p-1 text-xs bg-bg text-fg"
|
class="border border-border p-1 text-xs bg-bg text-fg w-[10ch]"
|
||||||
style="width: 10ch"
|
|
||||||
/>
|
/>
|
||||||
<span class="text-xs text-muted">gwei</span>
|
<span class="text-xs text-muted">gwei</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1129,8 +1097,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
id="settings-debug-well"
|
id="settings-debug-well"
|
||||||
class="bg-well p-3 mx-1 mb-3"
|
class="bg-well p-3 mx-1 mb-3 hidden"
|
||||||
style="display: none"
|
|
||||||
>
|
>
|
||||||
<h3 class="font-bold mb-1">Debug</h3>
|
<h3 class="font-bold mb-1">Debug</h3>
|
||||||
<label
|
<label
|
||||||
@@ -1158,8 +1125,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
id="delete-wallet-flash"
|
id="delete-wallet-flash"
|
||||||
class="text-xs text-red-500 mb-2 min-h-[1.25rem]"
|
class="text-xs text-red-500 mb-2 min-h-[1.25rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="block mb-1">Password</label>
|
<label class="block mb-1">Password</label>
|
||||||
@@ -1232,8 +1198,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="delete-wallet-lost-flash"
|
id="delete-wallet-lost-flash"
|
||||||
class="text-xs text-red-500 mb-2 min-h-[1.25rem]"
|
class="text-xs text-red-500 mb-2 min-h-[1.25rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<button
|
<button
|
||||||
id="btn-delete-wallet-lost-confirm"
|
id="btn-delete-wallet-lost-confirm"
|
||||||
@@ -1288,8 +1253,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
id="delete-address-flash"
|
id="delete-address-flash"
|
||||||
class="text-xs text-red-500 mb-2 min-h-[1.25rem]"
|
class="text-xs text-red-500 mb-2 min-h-[1.25rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<button
|
<button
|
||||||
id="btn-delete-address-confirm"
|
id="btn-delete-address-confirm"
|
||||||
@@ -1318,8 +1282,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="show-phrase-flash"
|
id="show-phrase-flash"
|
||||||
class="text-xs text-red-500 mb-2 min-h-[1.25rem]"
|
class="text-xs text-red-500 mb-2 min-h-[1.25rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<div id="show-phrase-password-section" class="mb-2">
|
<div id="show-phrase-password-section" class="mb-2">
|
||||||
<label class="block mb-1">Password</label>
|
<label class="block mb-1">Password</label>
|
||||||
@@ -1401,8 +1364,7 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
id="settings-addtoken-info"
|
id="settings-addtoken-info"
|
||||||
class="text-xs text-muted mt-1 min-h-[1.25rem]"
|
class="text-xs text-muted mt-1 min-h-[1.25rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<button
|
<button
|
||||||
id="btn-settings-addtoken-manual"
|
id="btn-settings-addtoken-manual"
|
||||||
@@ -1635,8 +1597,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="approve-tx-error"
|
id="approve-tx-error"
|
||||||
class="text-xs mb-2 border border-border border-dashed p-1 min-h-[1.875rem]"
|
class="text-xs mb-2 border border-border border-dashed p-1 min-h-[1.875rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<button
|
<button
|
||||||
@@ -1672,15 +1633,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
id="approve-sign-danger-warning"
|
id="approve-sign-danger-warning"
|
||||||
class="mb-3 p-2 text-xs font-bold"
|
class="mb-3 p-2 text-xs font-bold invisible min-h-[1.25rem] bg-[#fee2e2] text-[#991b1b] border-2 border-[#dc2626] rounded-[6px]"
|
||||||
style="
|
|
||||||
visibility: hidden;
|
|
||||||
min-height: 1.25rem;
|
|
||||||
background: #fee2e2;
|
|
||||||
color: #991b1b;
|
|
||||||
border: 2px solid #dc2626;
|
|
||||||
border-radius: 6px;
|
|
||||||
"
|
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -1697,8 +1650,7 @@
|
|||||||
<div class="text-xs text-muted mb-1">Message</div>
|
<div class="text-xs text-muted mb-1">Message</div>
|
||||||
<div
|
<div
|
||||||
id="approve-sign-message"
|
id="approve-sign-message"
|
||||||
class="text-xs break-all"
|
class="text-xs break-all max-h-48 overflow-y-auto"
|
||||||
style="max-height: 12rem; overflow-y: auto"
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1706,8 +1658,7 @@
|
|||||||
<div class="text-xs text-muted mb-1">Raw data</div>
|
<div class="text-xs text-muted mb-1">Raw data</div>
|
||||||
<div
|
<div
|
||||||
id="approve-sign-hex"
|
id="approve-sign-hex"
|
||||||
class="text-xs break-all"
|
class="text-xs break-all max-h-24 overflow-y-auto"
|
||||||
style="max-height: 6rem; overflow-y: auto"
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1721,8 +1672,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="approve-sign-error"
|
id="approve-sign-error"
|
||||||
class="text-xs mb-2 border border-border border-dashed p-1 min-h-[1.875rem]"
|
class="text-xs mb-2 border border-border border-dashed p-1 min-h-[1.875rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<button
|
<button
|
||||||
@@ -1846,8 +1796,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="state-recovery-flash"
|
id="state-recovery-flash"
|
||||||
class="text-xs text-red-500 mb-2 min-h-[1.25rem]"
|
class="text-xs text-red-500 mb-2 min-h-[1.25rem] invisible"
|
||||||
style="visibility: hidden"
|
|
||||||
></div>
|
></div>
|
||||||
<button
|
<button
|
||||||
id="btn-state-recovery-reset"
|
id="btn-state-recovery-reset"
|
||||||
|
|||||||
@@ -181,10 +181,10 @@ function renderTransactions(txs) {
|
|||||||
// it on the line above rather than replacing it.
|
// it on the line above rather than replacing it.
|
||||||
const nameStr = escapeHtml(title || ensName || "");
|
const nameStr = escapeHtml(title || ensName || "");
|
||||||
const err = tx.isError ? " (failed)" : "";
|
const err = tx.isError ? " (failed)" : "";
|
||||||
const opacity = tx.isError ? " opacity:0.5;" : "";
|
const opacity = tx.isError ? " opacity-50" : "";
|
||||||
const ago = escapeHtml(timeAgo(tx.timestamp));
|
const ago = escapeHtml(timeAgo(tx.timestamp));
|
||||||
const iso = escapeHtml(isoDate(tx.timestamp));
|
const iso = escapeHtml(isoDate(tx.timestamp));
|
||||||
html += `<div class="tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover" data-tx="${i}" style="${opacity}">`;
|
html += `<div class="tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover${opacity}" data-tx="${i}">`;
|
||||||
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
|
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
|
||||||
html += txCounterpartyHtml(counterparty, nameStr, amountStr);
|
html += txCounterpartyHtml(counterparty, nameStr, amountStr);
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
|
|||||||
@@ -258,10 +258,10 @@ function renderTransactions(txs) {
|
|||||||
// it on the line above rather than replacing it.
|
// it on the line above rather than replacing it.
|
||||||
const nameStr = escapeHtml(title || ensName || "");
|
const nameStr = escapeHtml(title || ensName || "");
|
||||||
const err = tx.isError ? " (failed)" : "";
|
const err = tx.isError ? " (failed)" : "";
|
||||||
const opacity = tx.isError ? " opacity:0.5;" : "";
|
const opacity = tx.isError ? " opacity-50" : "";
|
||||||
const ago = escapeHtml(timeAgo(tx.timestamp));
|
const ago = escapeHtml(timeAgo(tx.timestamp));
|
||||||
const iso = escapeHtml(isoDate(tx.timestamp));
|
const iso = escapeHtml(isoDate(tx.timestamp));
|
||||||
html += `<div class="tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover" data-tx="${i}" style="${opacity}">`;
|
html += `<div class="tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover${opacity}" data-tx="${i}">`;
|
||||||
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
|
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
|
||||||
html += txCounterpartyHtml(counterparty, nameStr, amountStr);
|
html += txCounterpartyHtml(counterparty, nameStr, amountStr);
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
|
|||||||
+23
-20
@@ -335,7 +335,7 @@ function balanceLine(symbol, amount, price, tokenId) {
|
|||||||
: "";
|
: "";
|
||||||
return (
|
return (
|
||||||
`<div class="flex text-xs${clickClass}"${tokenAttr}>` +
|
`<div class="flex text-xs${clickClass}"${tokenAttr}>` +
|
||||||
`<span class="flex justify-between" style="width:42ch;max-width:100%">` +
|
`<span class="flex justify-between w-[42ch] max-w-full">` +
|
||||||
`<span>${escapeHtml(displaySymbol(symbol))}</span>` +
|
`<span>${escapeHtml(displaySymbol(symbol))}</span>` +
|
||||||
`<span>${qty}</span>` +
|
`<span>${qty}</span>` +
|
||||||
`</span>` +
|
`</span>` +
|
||||||
@@ -430,23 +430,26 @@ function truncateMiddle(str, maxLen) {
|
|||||||
|
|
||||||
// 16 colors evenly spaced around the hue wheel (22.5° apart),
|
// 16 colors evenly spaced around the hue wheel (22.5° apart),
|
||||||
// all at HSL saturation 70%, lightness 50% for uniform vibrancy.
|
// all at HSL saturation 70%, lightness 50% for uniform vibrancy.
|
||||||
|
// Each is a whole Tailwind class: Tailwind builds only the classes it finds
|
||||||
|
// written out in the source, so the class name cannot be put together at
|
||||||
|
// runtime.
|
||||||
const ADDRESS_COLORS = [
|
const ADDRESS_COLORS = [
|
||||||
"#d92626",
|
"bg-[#d92626]",
|
||||||
"#d96926",
|
"bg-[#d96926]",
|
||||||
"#d9ac26",
|
"bg-[#d9ac26]",
|
||||||
"#c2d926",
|
"bg-[#c2d926]",
|
||||||
"#80d926",
|
"bg-[#80d926]",
|
||||||
"#3dd926",
|
"bg-[#3dd926]",
|
||||||
"#26d953",
|
"bg-[#26d953]",
|
||||||
"#26d996",
|
"bg-[#26d996]",
|
||||||
"#26d9d9",
|
"bg-[#26d9d9]",
|
||||||
"#2696d9",
|
"bg-[#2696d9]",
|
||||||
"#2653d9",
|
"bg-[#2653d9]",
|
||||||
"#3d26d9",
|
"bg-[#3d26d9]",
|
||||||
"#8026d9",
|
"bg-[#8026d9]",
|
||||||
"#c226d9",
|
"bg-[#c226d9]",
|
||||||
"#d926ac",
|
"bg-[#d926ac]",
|
||||||
"#d92669",
|
"bg-[#d92669]",
|
||||||
];
|
];
|
||||||
|
|
||||||
function addressColor(address) {
|
function addressColor(address) {
|
||||||
@@ -456,12 +459,12 @@ function addressColor(address) {
|
|||||||
|
|
||||||
function addressDotHtml(address) {
|
function addressDotHtml(address) {
|
||||||
const color = addressColor(address);
|
const color = addressColor(address);
|
||||||
return `<span style="width:8px;height:8px;border-radius:50%;display:inline-block;background:${color};margin-right:4px;vertical-align:middle;flex-shrink:0;"></span>`;
|
return `<span class="inline-block w-[8px] h-[8px] rounded-[50%] ${color} mr-[4px] align-middle shrink-0"></span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function blockieHtml(address) {
|
function blockieHtml(address) {
|
||||||
const src = makeBlockie(address);
|
const src = makeBlockie(address);
|
||||||
return `<img src="${escapeHtml(src)}" width="48" height="48" style="image-rendering:pixelated;border-radius:50%;display:inline-block">`;
|
return `<img src="${escapeHtml(src)}" width="48" height="48" class="inline-block rounded-[50%] [image-rendering:pixelated]">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up an address across all wallets and return its title
|
// Look up an address across all wallets and return its title
|
||||||
@@ -571,7 +574,7 @@ function timeAgo(timestamp) {
|
|||||||
|
|
||||||
// Shared external-link icon SVG used across all views.
|
// Shared external-link icon SVG used across all views.
|
||||||
const EXT_ICON =
|
const EXT_ICON =
|
||||||
`<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` +
|
`<span class="inline-block w-[10px] h-[10px] ml-[4px] align-middle">` +
|
||||||
`<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">` +
|
`<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">` +
|
||||||
`<path d="M4.5 1.5H2a.5.5 0 00-.5.5v8a.5.5 0 00.5.5h8a.5.5 0 00.5-.5V7.5"/>` +
|
`<path d="M4.5 1.5H2a.5.5 0 00-.5.5v8a.5.5 0 00.5.5h8a.5.5 0 00.5-.5V7.5"/>` +
|
||||||
`<path d="M7 1.5h3.5V5M7 5.5L10.5 1.5"/>` +
|
`<path d="M7 1.5h3.5V5M7 5.5L10.5 1.5"/>` +
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ function renderHomeTxList(ctx) {
|
|||||||
const title = addressTitle(counterparty, state.wallets);
|
const title = addressTitle(counterparty, state.wallets);
|
||||||
const titleStr = title ? escapeHtml(title) : "";
|
const titleStr = title ? escapeHtml(title) : "";
|
||||||
const err = tx.isError ? " (failed)" : "";
|
const err = tx.isError ? " (failed)" : "";
|
||||||
const opacity = tx.isError ? " opacity:0.5;" : "";
|
const opacity = tx.isError ? " opacity-50" : "";
|
||||||
const ago = escapeHtml(timeAgo(tx.timestamp));
|
const ago = escapeHtml(timeAgo(tx.timestamp));
|
||||||
const iso = escapeHtml(isoDate(tx.timestamp));
|
const iso = escapeHtml(isoDate(tx.timestamp));
|
||||||
html += `<div class="home-tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover" data-tx="${i}" style="${opacity}">`;
|
html += `<div class="home-tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover${opacity}" data-tx="${i}">`;
|
||||||
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
|
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
|
||||||
html += txCounterpartyHtml(counterparty, titleStr, amountStr);
|
html += txCounterpartyHtml(counterparty, titleStr, amountStr);
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
@@ -241,7 +241,7 @@ function walletListHtml() {
|
|||||||
state.wallets.forEach((wallet, wi) => {
|
state.wallets.forEach((wallet, wi) => {
|
||||||
const defect = walletDefect(wallet);
|
const defect = walletDefect(wallet);
|
||||||
html += `<div>`;
|
html += `<div>`;
|
||||||
html += `<div class="flex justify-between items-center bg-section py-1 px-2" style="margin:0 -0.5rem">`;
|
html += `<div class="flex justify-between items-center bg-section py-1 px-2 -mx-2">`;
|
||||||
html += `<span class="font-bold cursor-pointer wallet-name underline decoration-dashed" data-wallet="${wi}">${escapeHtml(wallet.name)}</span>`;
|
html += `<span class="font-bold cursor-pointer wallet-name underline decoration-dashed" data-wallet="${wi}">${escapeHtml(wallet.name)}</span>`;
|
||||||
// No "+" on a defective wallet: deriving another address from that
|
// No "+" on a defective wallet: deriving another address from that
|
||||||
// xpub would only add one more address the key does not produce
|
// xpub would only add one more address the key does not produce
|
||||||
@@ -255,12 +255,12 @@ function walletListHtml() {
|
|||||||
wallet.addresses.forEach((addr, ai) => {
|
wallet.addresses.forEach((addr, ai) => {
|
||||||
html += `<div class="address-row py-1 border-b border-border-light cursor-pointer hover:bg-hover" data-wallet="${wi}" data-address="${ai}">`;
|
html += `<div class="address-row py-1 border-b border-border-light cursor-pointer hover:bg-hover" data-wallet="${wi}" data-address="${ai}">`;
|
||||||
const isActive = state.activeAddress === addr.address;
|
const isActive = state.activeAddress === addr.address;
|
||||||
const infoBtn = `<span class="btn-addr-info text-xs cursor-pointer border border-border hover:bg-fg hover:text-bg" style="padding:0" data-wallet="${wi}" data-address="${ai}">[info]</span>`;
|
const infoBtn = `<span class="btn-addr-info text-xs cursor-pointer border border-border hover:bg-fg hover:text-bg p-0" data-wallet="${wi}" data-address="${ai}">[info]</span>`;
|
||||||
// Only where a wallet can spare the address: a wallet holding a
|
// Only where a wallet can spare the address: a wallet holding a
|
||||||
// single address has no remove control, because its last address
|
// single address has no remove control, because its last address
|
||||||
// is never removable.
|
// is never removable.
|
||||||
const removeBtn = canRemoveAddress(wallet)
|
const removeBtn = canRemoveAddress(wallet)
|
||||||
? `<span class="btn-remove-address text-xs cursor-pointer border border-border hover:bg-fg hover:text-bg ml-1" style="padding:0" data-wallet="${wi}" data-address="${ai}" title="Remove this address from the wallet">[x]</span>`
|
? `<span class="btn-remove-address text-xs cursor-pointer border border-border hover:bg-fg hover:text-bg ml-1 p-0" data-wallet="${wi}" data-address="${ai}" title="Remove this address from the wallet">[x]</span>`
|
||||||
: "";
|
: "";
|
||||||
const dot = addressDotHtml(addr.address);
|
const dot = addressDotHtml(addr.address);
|
||||||
const titleBold = isActive ? "font-bold" : "";
|
const titleBold = isActive ? "font-bold" : "";
|
||||||
|
|||||||
@@ -213,12 +213,7 @@ function show() {
|
|||||||
versionClickCount = 0;
|
versionClickCount = 0;
|
||||||
|
|
||||||
// Show debug well if debug mode is already enabled
|
// Show debug well if debug mode is already enabled
|
||||||
const debugWell = $("settings-debug-well");
|
$("settings-debug-well").classList.toggle("hidden", !state.debugMode);
|
||||||
if (state.debugMode) {
|
|
||||||
debugWell.style.display = "";
|
|
||||||
} else {
|
|
||||||
debugWell.style.display = "none";
|
|
||||||
}
|
|
||||||
$("settings-debug-mode").checked = state.debugMode;
|
$("settings-debug-mode").checked = state.debugMode;
|
||||||
|
|
||||||
showView("settings");
|
showView("settings");
|
||||||
@@ -434,7 +429,7 @@ function init(ctx) {
|
|||||||
if (versionClickCount >= 10) {
|
if (versionClickCount >= 10) {
|
||||||
versionClickCount = 0;
|
versionClickCount = 0;
|
||||||
clearTimeout(versionClickTimer);
|
clearTimeout(versionClickTimer);
|
||||||
$("settings-debug-well").style.display = "";
|
$("settings-debug-well").classList.remove("hidden");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ describe.each([
|
|||||||
test("a contract creation's row says so, with no colour dot and no address line", async () => {
|
test("a contract creation's row says so, with no colour dot and no address line", async () => {
|
||||||
const html = await rowsFor(historyTx(""));
|
const html = await rowsFor(historyTx(""));
|
||||||
expect(html).toContain(SENTENCE);
|
expect(html).toContain(SENTENCE);
|
||||||
expect(html).not.toContain("background:");
|
expect(html).not.toContain("bg-[#");
|
||||||
expect(html).not.toContain("am-address");
|
expect(html).not.toContain("am-address");
|
||||||
expect(html).not.toContain("undefined");
|
expect(html).not.toContain("undefined");
|
||||||
});
|
});
|
||||||
@@ -309,7 +309,7 @@ describe.each([
|
|||||||
test("a transaction with a recipient shows its colour dot and address", async () => {
|
test("a transaction with a recipient shows its colour dot and address", async () => {
|
||||||
const html = await rowsFor(historyTx(RECIPIENT));
|
const html = await rowsFor(historyTx(RECIPIENT));
|
||||||
expectAddressLine(html);
|
expectAddressLine(html);
|
||||||
expect(html).toContain("background:#");
|
expect(html).toContain("bg-[#");
|
||||||
expect(html).toContain(`<div class="am-address">${RECIPIENT}</div>`);
|
expect(html).toContain(`<div class="am-address">${RECIPIENT}</div>`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -311,7 +311,7 @@ test("transaction detail renders an ERC-20 transfer (#151)", async (env) => {
|
|||||||
"token contract row missing the contract address, got: " +
|
"token contract row missing the contract address, got: " +
|
||||||
JSON.stringify(contractText),
|
JSON.stringify(contractText),
|
||||||
);
|
);
|
||||||
const dots = await contract.locator('span[style*="border-radius"]').count();
|
const dots = await contract.locator('span[class*="rounded-[50%]"]').count();
|
||||||
assert(dots > 0, "token contract row rendered without its colour dot");
|
assert(dots > 0, "token contract row rendered without its colour dot");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,15 +21,14 @@
|
|||||||
// escaping in src/shared/html.js is the primary fix; default-src is what
|
// escaping in src/shared/html.js is the primary fix; default-src is what
|
||||||
// stops the next escape that slips from reaching the network.
|
// stops the next escape that slips from reaching the network.
|
||||||
//
|
//
|
||||||
// Every directive below is pinned exactly, because each of the four
|
// And for #328: style-src is 'self' alone, so the browser refuses every
|
||||||
|
// style="..." attribute in the popup's markup, including one an escape lets
|
||||||
|
// through. The popup styles with classes; script setting element.style is
|
||||||
|
// not affected.
|
||||||
|
//
|
||||||
|
// Every directive below is pinned exactly, because each of the three
|
||||||
// loosenings is load-bearing and none of them may grow:
|
// loosenings is load-bearing and none of them may grow:
|
||||||
//
|
//
|
||||||
// style-src 'unsafe-inline' src/popup/index.html and the view helpers
|
|
||||||
// use style="..." attributes throughout, which
|
|
||||||
// CSP blocks without it. Chrome enforces this
|
|
||||||
// on attributes, not just <style> blocks, and
|
|
||||||
// Firefox has never implemented style-src-attr,
|
|
||||||
// so there is no narrower spelling available.
|
|
||||||
// img-src data: blockies are data: PNGs assigned to img.src.
|
// img-src data: blockies are data: PNGs assigned to img.src.
|
||||||
// connect-src https: http: the RPC endpoint is user-configurable, and a
|
// connect-src https: http: the RPC endpoint is user-configurable, and a
|
||||||
// local node over http://127.0.0.1 is a
|
// local node over http://127.0.0.1 is a
|
||||||
@@ -58,7 +57,7 @@ const EXPECTED_DIRECTIVES = {
|
|||||||
"default-src": ["'self'"],
|
"default-src": ["'self'"],
|
||||||
"script-src": ["'self'", "'wasm-unsafe-eval'"],
|
"script-src": ["'self'", "'wasm-unsafe-eval'"],
|
||||||
"object-src": ["'self'"],
|
"object-src": ["'self'"],
|
||||||
"style-src": ["'self'", "'unsafe-inline'"],
|
"style-src": ["'self'"],
|
||||||
"img-src": ["'self'", "data:"],
|
"img-src": ["'self'", "data:"],
|
||||||
"connect-src": ["'self'", "http:", "https:"],
|
"connect-src": ["'self'", "http:", "https:"],
|
||||||
"frame-src": ["'none'"],
|
"frame-src": ["'none'"],
|
||||||
|
|||||||
Reference in New Issue
Block a user