mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-31 09:46:05 -04:00
Merge pull request #2913 from lightpanda-io/attribute-lookup-fix
fix: broken attribute lookup cross frame
This commit is contained in:
52
src/browser/tests/frames/cross_realm_attributes.html
Normal file
52
src/browser/tests/frames/cross_realm_attributes.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<!--
|
||||
Attribute lookups must work across realms. Names not in the static intern
|
||||
table (data-*, custom attributes) are canonicalized per-frame, so a parent
|
||||
reading an iframe element's attributes (or vice versa) can't rely on
|
||||
pointer identity — it must fall back to comparing bytes. Regression: parent
|
||||
reads of iframe data-* returned null (WPT encoding/*).
|
||||
-->
|
||||
<iframe id="idf" src="support/cross_realm_attributes.html"></iframe>
|
||||
|
||||
<script id="cross_realm_reads">
|
||||
testing.onload(() => {
|
||||
const el = document.getElementById('idf').contentDocument.getElementById('s1');
|
||||
|
||||
testing.expectEqual('5F', el.getAttribute('data-cp'));
|
||||
testing.expectEqual('5F', el.dataset.cp);
|
||||
testing.expectEqual('A1B2', el.dataset.bytes);
|
||||
testing.expectEqual('ns1', el.getAttribute('nonstandardattr'));
|
||||
testing.expectTrue(el.hasAttribute('nonstandardattr'));
|
||||
testing.expectFalse(el.hasAttribute('data-nope'));
|
||||
testing.expectEqual(null, el.getAttribute('data-nope'));
|
||||
});
|
||||
</script>
|
||||
|
||||
<script id="cross_realm_writes">
|
||||
testing.onload(() => {
|
||||
const el = document.getElementById('idf').contentDocument.getElementById('s1');
|
||||
const count = el.attributes.length;
|
||||
|
||||
// Updating an existing attribute from the parent realm must find the
|
||||
// entry (not append a duplicate).
|
||||
el.setAttribute('data-cp', '60');
|
||||
testing.expectEqual('60', el.getAttribute('data-cp'));
|
||||
testing.expectEqual(count, el.attributes.length);
|
||||
|
||||
// A new name canonicalized by the parent realm must be readable from
|
||||
// the iframe's own realm (and back).
|
||||
el.setAttribute('data-parentset', 'pv');
|
||||
const iwin = document.getElementById('idf').contentWindow;
|
||||
testing.expectEqual('pv', iwin.readAttr('data-parentset'));
|
||||
testing.expectEqual('60', iwin.readAttr('data-cp'));
|
||||
|
||||
el.removeAttribute('data-parentset');
|
||||
testing.expectFalse(el.hasAttribute('data-parentset'));
|
||||
testing.expectEqual(null, iwin.readAttr('data-parentset'));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
10
src/browser/tests/frames/support/cross_realm_attributes.html
Normal file
10
src/browser/tests/frames/support/cross_realm_attributes.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<body>
|
||||
<span id="s1" data-cp="5F" data-bytes="A1B2" nonstandardattr="ns1">x</span>
|
||||
<script>
|
||||
// Called by the parent test: reads execute in this frame's realm.
|
||||
function readAttr(name) {
|
||||
return document.getElementById('s1').getAttribute(name);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
@@ -385,29 +385,12 @@ pub const List = struct {
|
||||
const normalized =
|
||||
if (self.normalize) try normalizeNameForLookup(name, frame) else name;
|
||||
|
||||
// A name that was never canonicalized can't be in any list.
|
||||
const canonical = lookupCanonicalName(normalized.str(), frame) orelse {
|
||||
return .{ .normalized = normalized, .entry = null };
|
||||
};
|
||||
return .{
|
||||
.normalized = normalized,
|
||||
.entry = self.getEntryWithCanonicalName(canonical.ptr),
|
||||
.entry = self.getEntryWithNormalizedName(normalized),
|
||||
};
|
||||
}
|
||||
|
||||
// This is one of the wins of the canonical names...we can compare strings
|
||||
// as a single pointer comparison. By applying the same canonicalization to
|
||||
// the lists attributes name and to an input, we get the same pointer for
|
||||
// a given string.
|
||||
fn getEntryWithCanonicalName(self: *const List, name_ptr: [*]const u8) ?*Entry {
|
||||
for (self._entries[0..self._len]) |*e| {
|
||||
if (e._name_ptr == name_ptr) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn getEntryWithNormalizedName(self: *const List, name: String) ?*Entry {
|
||||
const name_str = name.str();
|
||||
for (self._entries[0..self._len]) |*e| {
|
||||
@@ -514,10 +497,10 @@ pub fn validateAttributeName(name: String) !void {
|
||||
}
|
||||
|
||||
// Every stored entry name either comes from the static String.intern or from
|
||||
// the frame._attribute_names. This doesn't just avoid extra dupes/allocations,
|
||||
// it gives us comparable pointer.
|
||||
// For the lifetime of a frame:
|
||||
// canonicalizeName("attrx").ptr == canonicalizeName("attrx").ptr
|
||||
// the frame._attribute_names. Beyond avoiding extra dupes/allocations, this
|
||||
// gives a stable pointer for the frame's lifetime, which List.LookupKey
|
||||
// relies on for identity. The pointer is NOT comparable across frames (each
|
||||
// frame has its own pool), which is why lookups byte-compare.
|
||||
fn canonicalizeName(name: []const u8, frame: *Frame) ![]const u8 {
|
||||
if (String.intern(name)) |static| {
|
||||
return static;
|
||||
@@ -529,19 +512,6 @@ fn canonicalizeName(name: []const u8, frame: *Frame) ![]const u8 {
|
||||
return gop.key_ptr.*;
|
||||
}
|
||||
|
||||
// Let's say you want to find an attribute name "attrx". We could iterate an
|
||||
// attribute list to do the string comparison. OR, we could run "attrx" through
|
||||
// the same canonicalization as we applied to the attribute's names. If we don't
|
||||
// find a canonical value, then it's then this attribute was never canonicalized
|
||||
// before (and thus, we can shortcircuit a search). If we DO canonicalize it
|
||||
// the we get do a pointer comparison rather than a full string comparison.
|
||||
fn lookupCanonicalName(name: []const u8, frame: *const Frame) ?[]const u8 {
|
||||
if (String.intern(name)) |static| {
|
||||
return static;
|
||||
}
|
||||
return frame._attribute_names.getKey(name);
|
||||
}
|
||||
|
||||
fn normalizeNameForLookup(name: String, frame: *Frame) !String {
|
||||
if (!needsLowerCasing(name.str())) {
|
||||
return name;
|
||||
|
||||
Reference in New Issue
Block a user