LibWeb/HTML: Correctly set base elements frozen base url
Some checks are pending
CI / macOS, arm64, Sanitizer_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, Clang (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

This commit implements the fallback to the documents fallback base url
if the href of the first base element is a data or javascript url.

Additionally the frozen base url is set, if a base element becomes the
first base element with an href content attribute because the previous
one got removed.
This commit is contained in:
Glenn Skrzypczak 2025-05-02 23:25:38 +02:00 committed by Shannon Booth
commit 6b84cd8d11
Notes: github-actions[bot] 2025-06-23 06:57:47 +00:00
7 changed files with 103 additions and 12 deletions

View file

@ -1135,10 +1135,10 @@ Vector<CSS::BackgroundLayerData> const* Document::background_layers() const
void Document::update_base_element(Badge<HTML::HTMLBaseElement>) void Document::update_base_element(Badge<HTML::HTMLBaseElement>)
{ {
GC::Ptr<HTML::HTMLBaseElement const> base_element_with_href = nullptr; GC::Ptr<HTML::HTMLBaseElement> base_element_with_href = nullptr;
GC::Ptr<HTML::HTMLBaseElement const> base_element_with_target = nullptr; GC::Ptr<HTML::HTMLBaseElement> base_element_with_target = nullptr;
for_each_in_subtree_of_type<HTML::HTMLBaseElement>([&base_element_with_href, &base_element_with_target](HTML::HTMLBaseElement const& base_element_in_tree) { for_each_in_subtree_of_type<HTML::HTMLBaseElement>([&base_element_with_href, &base_element_with_target](HTML::HTMLBaseElement& base_element_in_tree) {
if (!base_element_with_href && base_element_in_tree.has_attribute(HTML::AttributeNames::href)) { if (!base_element_with_href && base_element_in_tree.has_attribute(HTML::AttributeNames::href)) {
base_element_with_href = &base_element_in_tree; base_element_with_href = &base_element_in_tree;
if (base_element_with_target) if (base_element_with_target)
@ -1157,12 +1157,12 @@ void Document::update_base_element(Badge<HTML::HTMLBaseElement>)
m_first_base_element_with_target_in_tree_order = base_element_with_target; m_first_base_element_with_target_in_tree_order = base_element_with_target;
} }
GC::Ptr<HTML::HTMLBaseElement const> Document::first_base_element_with_href_in_tree_order() const GC::Ptr<HTML::HTMLBaseElement> Document::first_base_element_with_href_in_tree_order() const
{ {
return m_first_base_element_with_href_in_tree_order; return m_first_base_element_with_href_in_tree_order;
} }
GC::Ptr<HTML::HTMLBaseElement const> Document::first_base_element_with_target_in_tree_order() const GC::Ptr<HTML::HTMLBaseElement> Document::first_base_element_with_target_in_tree_order() const
{ {
return m_first_base_element_with_target_in_tree_order; return m_first_base_element_with_target_in_tree_order;
} }

View file

@ -223,8 +223,8 @@ public:
URL::URL base_url() const; URL::URL base_url() const;
void update_base_element(Badge<HTML::HTMLBaseElement>); void update_base_element(Badge<HTML::HTMLBaseElement>);
GC::Ptr<HTML::HTMLBaseElement const> first_base_element_with_href_in_tree_order() const; GC::Ptr<HTML::HTMLBaseElement> first_base_element_with_href_in_tree_order() const;
GC::Ptr<HTML::HTMLBaseElement const> first_base_element_with_target_in_tree_order() const; GC::Ptr<HTML::HTMLBaseElement> first_base_element_with_target_in_tree_order() const;
String url_string() const { return m_url.to_string(); } String url_string() const { return m_url.to_string(); }
String document_uri() const { return url_string(); } String document_uri() const { return url_string(); }
@ -1135,8 +1135,8 @@ private:
GC::Ptr<Selection::Selection> m_selection; GC::Ptr<Selection::Selection> m_selection;
// NOTE: This is a cache to make finding the first <base href> or <base target> element O(1). // NOTE: This is a cache to make finding the first <base href> or <base target> element O(1).
GC::Ptr<HTML::HTMLBaseElement const> m_first_base_element_with_href_in_tree_order; GC::Ptr<HTML::HTMLBaseElement> m_first_base_element_with_href_in_tree_order;
GC::Ptr<HTML::HTMLBaseElement const> m_first_base_element_with_target_in_tree_order; GC::Ptr<HTML::HTMLBaseElement> m_first_base_element_with_target_in_tree_order;
// https://html.spec.whatwg.org/multipage/images.html#list-of-available-images // https://html.spec.whatwg.org/multipage/images.html#list-of-available-images
GC::Ptr<HTML::ListOfAvailableImages> m_list_of_available_images; GC::Ptr<HTML::ListOfAvailableImages> m_list_of_available_images;

View file

@ -43,7 +43,14 @@ void HTMLBaseElement::inserted()
void HTMLBaseElement::removed_from(Node* old_parent, Node& old_root) void HTMLBaseElement::removed_from(Node* old_parent, Node& old_root)
{ {
HTMLElement::removed_from(old_parent, old_root); HTMLElement::removed_from(old_parent, old_root);
auto old_first_base_element_with_href_in_tree_order = document().first_base_element_with_href_in_tree_order();
document().update_base_element({}); document().update_base_element({});
// The frozen base URL must be immediately set for an element whenever any of the following situations occur:
// - The base element becomes the first base element in tree order with an href content attribute in its Document.
auto first_base_element_with_href_in_document = document().first_base_element_with_href_in_tree_order();
if (first_base_element_with_href_in_document != old_first_base_element_with_href_in_tree_order)
first_base_element_with_href_in_document->set_the_frozen_base_url();
} }
void HTMLBaseElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) void HTMLBaseElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
@ -72,13 +79,17 @@ void HTMLBaseElement::set_the_frozen_base_url()
auto href = get_attribute_value(AttributeNames::href); auto href = get_attribute_value(AttributeNames::href);
auto url_record = document.fallback_base_url().complete_url(href); auto url_record = document.fallback_base_url().complete_url(href);
// 3. Set element's frozen base URL to document's fallback base URL, if urlRecord is failure or running Is base allowed for Document? on the resulting URL record and document returns "Blocked", and to urlRecord otherwise. // 3. If any of the following are true:
// FIXME: Apply "Is base allowed for Document?" CSP // - urlRecord is failure;
if (!url_record.has_value()) { // - urlRecord's scheme is "data" or "javascript"; or
// FIXME: - running Is base allowed for Document? on urlRecord and document returns "Blocked",
// then set element's frozen base URL to document's fallback base URL and return.
if (!url_record.has_value() || url_record->scheme() == "data" || url_record->scheme() == "javascript") {
m_frozen_base_url = document.fallback_base_url(); m_frozen_base_url = document.fallback_base_url();
return; return;
} }
// 4. Set element's frozen base URL to urlRecord.
m_frozen_base_url = url_record.release_value(); m_frozen_base_url = url_record.release_value();
} }

View file

@ -0,0 +1,8 @@
Harness status: OK
Found 3 tests
3 Pass
Pass First <base> has a data: URL so fallback is used
Pass First <base> is removed so second is used
Pass Dynamically inserted first <base> has a data: URL so fallback is used

View file

@ -0,0 +1,8 @@
Harness status: OK
Found 3 tests
3 Pass
Pass First <base> has a javascript: URL so fallback is used
Pass First <base> is removed so second is used
Pass Dynamically inserted first <base> has a javascript: URL so fallback is used

View file

@ -0,0 +1,32 @@
<!-- Please update base-javascript.html together with this -->
<!DOCTYPE html>
<meta charset="utf-8">
<title>&lt;base> and data: URLs</title>
<script src=../../../../resources/testharness.js></script>
<script src=../../../../resources/testharnessreport.js></script>
<base href="data:/,test">
<base href="https://example.com/">
<div id=log></div>
<script>
test(() => {
const link = document.createElement("a");
link.href = "blah";
assert_equals(link.href, new URL("blah", document.URL).href);
}, "First <base> has a data: URL so fallback is used");
test(() => {
document.querySelector("base").remove();
const link = document.createElement("a");
link.href = "blah";
assert_equals(link.href, new URL("blah", "https://example.com/").href);
}, "First <base> is removed so second is used");
test(() => {
const base = document.createElement("base");
base.href = "data:/,more-test";
document.head.prepend(base);
const link = document.createElement("a");
link.href = "blah";
assert_equals(link.href, new URL("blah", document.URL).href);
}, "Dynamically inserted first <base> has a data: URL so fallback is used");
</script>

View file

@ -0,0 +1,32 @@
<!-- Please update base-data.html together with this -->
<!DOCTYPE html>
<meta charset="utf-8">
<title>&lt;base> and javascript: URLs</title>
<script src=../../../../resources/testharness.js></script>
<script src=../../../../resources/testharnessreport.js></script>
<base href="javascript:/,test">
<base href="https://example.com/">
<div id=log></div>
<script>
test(() => {
const link = document.createElement("a");
link.href = "blah";
assert_equals(link.href, new URL("blah", document.URL).href);
}, "First <base> has a javascript: URL so fallback is used");
test(() => {
document.querySelector("base").remove();
const link = document.createElement("a");
link.href = "blah";
assert_equals(link.href, new URL("blah", "https://example.com/").href);
}, "First <base> is removed so second is used");
test(() => {
const base = document.createElement("base");
base.href = "javascript:/,more-test";
document.head.prepend(base);
const link = document.createElement("a");
link.href = "blah";
assert_equals(link.href, new URL("blah", document.URL).href);
}, "Dynamically inserted first <base> has a javascript: URL so fallback is used");
</script>