diff --git a/docs/validation/owner-document-url-reflection-904.md b/docs/validation/owner-document-url-reflection-904.md new file mode 100644 index 000000000..1d7812e9e --- /dev/null +++ b/docs/validation/owner-document-url-reflection-904.md @@ -0,0 +1,40 @@ +# Owner-document URL reflection — WebScene #904 + +On the merged WebScene `5b4739c8` installed Code OSS build, the nested +Markdown prelude belongs to a virtual HTTPS document and authors +`./fake.html?id=...`. Reading its iframe `src` from the main realm returned a +loopback `/fake.html` URL, while `ownerDocument.baseURI` remained virtual +HTTPS. This is a URL-property reflection defect; actual request navigation +uses a separate owner-root base path and was not shown to be wrong. + +`get_element_url` now resolves URL-valued properties through the element's +owning cascade/document root instead of the caller's current document. Its +existing fallback remains for unavailable bases. The change shares the same +owner-root/base cache as resource navigation. It leaves the authored content +attribute and cross-origin access policy unchanged. + +## Executed focused gates + +- Production-style V8 native engine and source-test targets built in Release + with pinned V8 pointer compression and shared cage. +- `WEBSCENE_NATIVE_ENGINE_TEST_FILTER=iframe-navigation-lifecycle` passed. + The added regression reads connected `a.href` and `input.src` from a + same-origin parent realm before and after a nested `` change and + checks that `getAttribute` stays authored. The existing gate also passed + 100 iframe create/load/write/replace/remove cycles, 1 MiB update p95 + 3.73 ms (250 ms limit), flat heap before/after at 1,849,528 bytes, + cross-origin/opaque access denial, and history ordering. +- The checked-in `tests/WebPlatformSubset/chrome/owner-document-url-reflection-reference.html` + returned `PASS` under installed Chrome 152 with `--headless=new + --virtual-time-budget=3000 --dump-dom`. It checks the same nested `href`, + `src`, base mutation, and authored-attribute expectations. +- `git diff --check` passed. + +The existing standalone cross-document-history WPT profile cannot currently +serve its nested fixture through this native runner, and a new `srcdoc` or +initial `about:blank` contract timed out. Those attempts were removed from +the PR; no WPT pass is claimed. The direct native source gate and checked-in +Chrome reference carry this focused regression. The broader WebScene #267 +WPT/browser matrix and unchanged Markdown rendering remain open. The Chrome +CDP runner also failed to expose a debuggable page target on this host, so +the standalone headless DOM check was used for the browser oracle. diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_dom_properties.inc b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_dom_properties.inc index 0fc9dd098..e662ae78f 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_dom_properties.inc +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_dom_properties.inc @@ -5125,7 +5125,10 @@ || node->tag == "video")); if (iterator != node->attributes.end() && url_reflected) { auto* self = current(info.GetIsolate()); - auto base = self->current_base_address(); + // URL reflection follows the element's owning document, even when + // a same-origin caller reads it from another browsing context. + auto base = self->effective_document_base( + self->css_cascade_root_for_node(*node)); if (base.empty()) { auto local_context = info.GetIsolate()->GetCurrentContext(); v8::Local location; diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_resource_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_resource_tests.inc index 338d4382e..172f4969d 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_resource_tests.inc +++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_resource_tests.inc @@ -768,6 +768,61 @@ void test_same_origin_iframe_navigation_document_replacement() "nested cross-document traversal admitted an unexpected resource request: before=" + std::to_string(requests_before_cross_document_history) + " after=" + std::to_string(server.requests.load(std::memory_order_relaxed))); + execute(engine, R"JS( + globalThis.__ownerDocumentUrlReflection = { + complete:false, first:false, changed:false, srcFirst:false, + srcChanged:false, authored:false, error:null + }; + (() => { + const frame = document.createElement('iframe'); + frame.setAttribute('sandbox', 'allow-same-origin allow-scripts'); + frame.src = './fake.html?id=markdown-preview'; + document.body.appendChild(frame); + frame.contentWindow.addEventListener('DOMContentLoaded', event => { + try { + const child = event.target; + const link = child.createElement('a'); + link.setAttribute('href', './relative.txt'); + child.body.appendChild(link); + const input = child.createElement('input'); + input.setAttribute('src', './relative.png'); + child.body.appendChild(input); + __ownerDocumentUrlReflection.first = + link.href === new URL('./relative.txt', child.baseURI).href; + __ownerDocumentUrlReflection.srcFirst = + input.src === new URL('./relative.png', child.baseURI).href; + const base = child.createElement('base'); + base.setAttribute('href', '/nested-owner/'); + child.head.appendChild(base); + __ownerDocumentUrlReflection.changed = + link.href === new URL('./relative.txt', child.baseURI).href + && link.href.endsWith('/nested-owner/relative.txt'); + __ownerDocumentUrlReflection.srcChanged = + input.src === new URL('./relative.png', child.baseURI).href + && input.src.endsWith('/nested-owner/relative.png'); + __ownerDocumentUrlReflection.authored = + link.getAttribute('href') === './relative.txt' + && input.getAttribute('src') === './relative.png'; + } catch (error) { + __ownerDocumentUrlReflection.error = String(error && error.stack || error); + } finally { + frame.remove(); + __ownerDocumentUrlReflection.complete = true; + } + }); + })(); + )JS", "iframe-owner-document-url-reflection.js"); + require(evaluate_until_equals(engine, "__ownerDocumentUrlReflection.complete", + "iframe-owner-document-url-reflection-complete.js", "true", 4'000) == "true", + "nested owner-document URL reflection timed out"); + require(evaluate(engine, R"JS((() => { + const s = __ownerDocumentUrlReflection; + return s.error === null && s.first && s.changed && s.srcFirst + && s.srcChanged && s.authored; + })())JS", "iframe-owner-document-url-reflection-result.js") == "true", + "nested owner-document URL reflection diverged: " + + evaluate(engine, "JSON.stringify(__ownerDocumentUrlReflection)", + "iframe-owner-document-url-reflection-detail.js")); execute(engine, R"JS( globalThis.__iframeLifecycleGate = { complete:false, error:null, cycles:0, maximumLiveFrames:0, diff --git a/tests/WebPlatformSubset/chrome/owner-document-url-reflection-reference.html b/tests/WebPlatformSubset/chrome/owner-document-url-reflection-reference.html new file mode 100644 index 000000000..31c17a738 --- /dev/null +++ b/tests/WebPlatformSubset/chrome/owner-document-url-reflection-reference.html @@ -0,0 +1,24 @@ + + +Owner document URL reflection reference +PENDING + +