mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-24 15:07:17 +00:00
Elide a long host from the left, as its own rule intends
The identity line is the only thing on the permission dialog naming the requester, so a host too long for the dialog has to lose its front, not its tail: the registrable domain is the part that says who is asking. `direction: rtl` was there for that, but paired with `unicode-bidi: plaintext` it does nothing — plaintext takes the base direction from the content's own first strong character, which for any Latin host is LTR, so the ellipsis went back on the right. Measured in the real dialog, `account-security.paypal.com.verify-login.example` rendered as `account-security.paypal.com.verify-l…`, reading as PayPal. Isolating instead keeps the box anchored to the end of the text, and still stops a bidi control character in the host from reordering anything around it. The test that covers this asserted the computed `direction` — the property, not the outcome — so it passed throughout. It now measures which characters are actually on screen, and that the host still reads in source order.
This commit is contained in:
@@ -6286,13 +6286,21 @@ html.dark-mode .usage-table-show-less:hover {
|
||||
/* A hostname must never lose its tail to the ellipsis: the registrable domain
|
||||
is the part that identifies who is asking. `direction: rtl` anchors the text
|
||||
to its end, so a too-long host is elided from the left instead — what
|
||||
browsers do in their own origin chips. Latin hosts still read left-to-right
|
||||
because the content's own direction is resolved from its first strong
|
||||
character. */
|
||||
browsers do in their own origin chips. Latin hosts still read
|
||||
left-to-right: their characters are strongly LTR, so the bidi algorithm
|
||||
keeps them in order inside the right-anchored box.
|
||||
|
||||
`isolate`, not `plaintext`: plaintext takes the base direction from the
|
||||
content's own first strong character, which for any Latin host overrides the
|
||||
`direction` above and puts the ellipsis back on the right — hiding exactly
|
||||
the part that identifies the requester, so
|
||||
`accounts.google.com.attacker.example` reads as `accounts.google.com…`.
|
||||
Isolating still keeps a bidi control character in the host from reordering
|
||||
anything outside it. */
|
||||
.perm-dialog-entity-host,
|
||||
.perm-dialog-entity-origin {
|
||||
direction: rtl;
|
||||
unicode-bidi: plaintext;
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
.perm-dialog-entity-origin {
|
||||
|
||||
@@ -1004,17 +1004,46 @@ test.describe('request-permission action hardening', () => {
|
||||
const name = page.locator('dialog.perm-dialog .perm-dialog-entity-name');
|
||||
await expect(name).toBeVisible({ timeout: 60_000 });
|
||||
|
||||
const info = await name.evaluate((el) => ({
|
||||
text: el.textContent,
|
||||
overflowing: el.scrollWidth > el.clientWidth,
|
||||
direction: getComputedStyle(el).direction,
|
||||
}));
|
||||
// Measured from what is actually on screen, not from the declared
|
||||
// `direction`: `unicode-bidi: plaintext` leaves `direction: rtl`
|
||||
// computing as `rtl` while taking the real base direction from the
|
||||
// content's first strong character, which for any Latin host put the
|
||||
// ellipsis back on the right. Asserting the property passed while the
|
||||
// rendering did the opposite of what it claims.
|
||||
const info = await name.evaluate((el) => {
|
||||
const node = el.firstChild;
|
||||
const box = el.getBoundingClientRect();
|
||||
const rectOf = (i) => {
|
||||
const r = document.createRange();
|
||||
r.setStart(node, i);
|
||||
r.setEnd(node, i + 1);
|
||||
return r.getBoundingClientRect();
|
||||
};
|
||||
const text = node.textContent;
|
||||
const visible = [];
|
||||
for ( let i = 0; i < text.length; i++ ) {
|
||||
const r = rectOf(i);
|
||||
if ( r.left >= box.left - 1 && r.right <= box.right + 1 ) {
|
||||
visible.push({ ch: text[i], i, left: r.left });
|
||||
}
|
||||
}
|
||||
return {
|
||||
text,
|
||||
overflowing: el.scrollWidth > el.clientWidth,
|
||||
visibleText: visible.map((c) => c.ch).join(''),
|
||||
// Still reads left-to-right, in source order: anchoring the box
|
||||
// to its end must not reorder the host itself.
|
||||
readsInOrder: visible.every((c, k) => k === 0
|
||||
|| (c.i > visible[k - 1].i && c.left >= visible[k - 1].left)),
|
||||
};
|
||||
});
|
||||
// The host is genuinely too long for the dialog, so the elision this
|
||||
// asserts about is actually happening.
|
||||
expect(info.text).toBe(host);
|
||||
expect(info.overflowing).toBe(true);
|
||||
// `direction: rtl` anchors the text to its end, so the ellipsis lands on
|
||||
// the left and the registrable domain stays on screen.
|
||||
expect(info.direction).toBe('rtl');
|
||||
// What survives the ellipsis is the end of the host — the part that says
|
||||
// who is really asking — not the trusted-looking prefix.
|
||||
expect(info.visibleText.endsWith('attacker-run-domain.example')).toBe(true);
|
||||
expect(info.readsInOrder).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user