LibWeb: Only expose performance.{timing,navigation} on Window

They are only valid in a Window context, and the spec only exposes them
in Window contexts.

Fixes workers crashing on:
- https://web.whatsapp.com/
- https://pro.kraken.com/app/trade/btc-usd
This commit is contained in:
Luke Wilde 2025-07-22 17:03:34 +01:00 committed by Andreas Kling
commit da5fca15ee
Notes: github-actions[bot] 2025-07-25 09:48:11 +00:00
5 changed files with 75 additions and 8 deletions

View file

@ -1,5 +1,6 @@
#import <DOM/EventTarget.idl>
#import <HighResolutionTime/DOMHighResTimeStamp.idl>
#import <NavigationTiming/PerformanceExtensions.idl>
#import <NavigationTiming/PerformanceNavigation.idl>
#import <NavigationTiming/PerformanceTiming.idl>
#import <PerformanceTimeline/PerformanceEntry.idl>
@ -34,11 +35,4 @@ interface Performance : EventTarget {
PerformanceEntryList getEntries();
PerformanceEntryList getEntriesByType(DOMString type);
PerformanceEntryList getEntriesByName(DOMString name, optional DOMString type);
// https://w3c.github.io/navigation-timing/#extensions-to-the-performance-interface
// Obsolete "Navigation Timing" extensions to the Performance interface
[SameObject]
readonly attribute PerformanceTiming timing;
[SameObject]
readonly attribute PerformanceNavigation navigation;
};

View file

@ -0,0 +1,8 @@
// https://w3c.github.io/navigation-timing/#extensions-to-the-performance-interface
[Exposed=Window]
partial interface Performance {
[SameObject]
readonly attribute PerformanceTiming timing;
[SameObject]
readonly attribute PerformanceNavigation navigation;
};

View file

@ -3136,6 +3136,19 @@ static void collect_attribute_values_of_an_inheritance_stack(SourceGenerator& fu
// FIXME: Check if the attributes are exposed.
// NOTE: Add more specified exposed global interface groups when needed.
StringBuilder window_exposed_only_members_builder;
SourceGenerator window_exposed_only_members_generator { window_exposed_only_members_builder, function_generator.clone_mapping() };
auto generator_for_member = [&](auto const& name, auto& extended_attributes) -> SourceGenerator {
if (auto maybe_exposed = extended_attributes.get("Exposed"); maybe_exposed.has_value()) {
auto exposed_to = MUST(IDL::parse_exposure_set(name, *maybe_exposed));
if (exposed_to == IDL::ExposedTo::Window) {
return window_exposed_only_members_generator.fork();
}
}
return function_generator.fork();
};
// 1. Let id be the identifier of attr.
// 2. Let value be the result of running the getter steps of attr with object as this.
@ -3154,7 +3167,7 @@ static void collect_attribute_values_of_an_inheritance_stack(SourceGenerator& fu
if (!attribute.type->is_json(interface_in_chain))
continue;
auto attribute_generator = function_generator.fork();
auto attribute_generator = generator_for_member(attribute.name, attribute.extended_attributes);
auto return_value_name = ByteString::formatted("{}_retval", attribute.name.to_snakecase());
attribute_generator.set("attribute.name", attribute.name);
@ -3211,6 +3224,16 @@ static void collect_attribute_values_of_an_inheritance_stack(SourceGenerator& fu
constant_generator.append(R"~~~(
MUST(result->create_data_property("@constant.name@"_fly_string, constant_@constant.name@_value));
)~~~");
}
if (!window_exposed_only_members_generator.as_string_view().is_empty()) {
auto window_only_property_declarations = function_generator.fork();
window_only_property_declarations.set("defines", window_exposed_only_members_generator.as_string_view());
window_only_property_declarations.append(R"~~~(
if (is<HTML::Window>(realm.global_object())) {
@defines@
}
)~~~");
}
}

View file

@ -0,0 +1,6 @@
(window) navigation: object
(window) timing: object
(window) performance JSON: {"timeOrigin":[removed-potentially-flaky-time-value],"timing":{"navigationStart":[removed-potentially-flaky-time-value],"unloadEventStart":[removed-potentially-flaky-time-value],"unloadEventEnd":[removed-potentially-flaky-time-value],"redirectStart":[removed-potentially-flaky-time-value],"redirectEnd":[removed-potentially-flaky-time-value],"fetchStart":[removed-potentially-flaky-time-value],"domainLookupStart":[removed-potentially-flaky-time-value],"domainLookupEnd":[removed-potentially-flaky-time-value],"connectStart":[removed-potentially-flaky-time-value],"connectEnd":[removed-potentially-flaky-time-value],"secureConnectionStart":[removed-potentially-flaky-time-value],"requestStart":[removed-potentially-flaky-time-value],"responseStart":[removed-potentially-flaky-time-value],"responseEnd":[removed-potentially-flaky-time-value],"domLoading":[removed-potentially-flaky-time-value],"domInteractive":[removed-potentially-flaky-time-value],"domContentLoadedEventStart":[removed-potentially-flaky-time-value],"domContentLoadedEventEnd":[removed-potentially-flaky-time-value],"domComplete":[removed-potentially-flaky-time-value],"loadEventStart":[removed-potentially-flaky-time-value],"loadEventEnd":[removed-potentially-flaky-time-value]},"navigation":{"type":[removed-potentially-flaky-time-value],"redirectCount":[removed-potentially-flaky-time-value],"TYPE_NAVIGATE":0,"TYPE_RELOAD":1,"TYPE_BACK_FORWARD":2,"TYPE_RESERVED":255}}
(worker) navigation: undefined
(worker) timing: undefined
(worker) performance JSON: {"timeOrigin":[removed-potentially-flaky-time-value]}

View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<script type="worker">
globalThis.onmessage = () => {
const result = [];
result.push("(worker) navigation: " + typeof performance.navigation);
result.push("(worker) timing: " + typeof performance.timing);
result.push("(worker) performance JSON: " + JSON.stringify(performance));
postMessage(result);
}
</script>
<script src="include.js"></script>
<script>
asyncTest((done) => {
const replacePotentiallyFlakyTimes = (string) => {
return string.replace(/("[a-zA-Z]+":)(\d+(\.\d+)?)/g, "$1[removed-potentially-flaky-time-value]");
};
println("(window) navigation: " + typeof performance.navigation);
println("(window) timing: " + typeof performance.timing);
println("(window) performance JSON: " + replacePotentiallyFlakyTimes(JSON.stringify(performance)));
const workerSource = document.querySelector('[type=worker]').innerText;
const workerBlob = new Blob([workerSource], { type: "text/javascript" });
const workerBlobURL = URL.createObjectURL(workerBlob);
const worker = new Worker(workerBlobURL);
worker.onmessage = ({ data }) => {
for (const string of data) {
println(replacePotentiallyFlakyTimes(string));
}
done();
};
worker.postMessage("start");
});
</script>