feat: add support for long-animation-frame script attribution (#49771)

* feat: add support for `long-animation-frame` script attribution

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* docs: document `AlwaysLogLOAFURL`

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* chore: add test

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* docs: adjust docs as per PR comment

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* fix: test failures

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* chore: simplify test

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* fix: tests on Windows and Linux

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* chore: fixup patches

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
This commit is contained in:
trop[bot]
2026-02-18 16:19:21 -05:00
committed by GitHub
parent 2ffb9e1e05
commit 0bcaabff0c
5 changed files with 112 additions and 0 deletions

View File

@@ -361,6 +361,13 @@ Keep in mind that standalone switches can sometimes be split into individual fea
Finally, you'll need to ensure that the version of Chromium in Electron matches the version of the browser you're using to cross-reference the switches.
### Chromium features relevant to Electron apps
* `AlwaysLogLOAFURL`: enables script attribution for
[`long-animation-frame`](https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Long_animation_frame_timing)
`PerformanceObserver` events for non-http(s), non-data, non-blob URLs (such as `file:` or custom
protocol URLs).
[app]: app.md
[append-switch]: command-line.md#commandlineappendswitchswitch-value
[debugging-main-process]: ../tutorial/debugging-main-process.md

View File

@@ -150,3 +150,4 @@ viz_fix_visual_artifacts_while_resizing_window_with_dcomp.patch
graphite_handle_out_of_order_recording_errors.patch
ozone_wayland_treat_dnd_drop_performed_with_none_action_as_a.patch
cherry-pick-e045399a1ecb.patch
loaf_add_feature_to_enable_sourceurl_for_all_protocols.patch

View File

@@ -0,0 +1,52 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Niklas Wenzel <dev@nikwen.de>
Date: Wed, 4 Feb 2026 06:02:40 -0800
Subject: LoAF: Add feature to enable sourceURL for all protocols
Backports https://crrev.com/c/7510894 (minus the test changes).
Can be removed when that CL is included via a Chromium roll.
Change-Id: Id5e58a151b13cc0ac054f4ec237b038255d683fd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7510894
Commit-Queue: Noam Rosenthal <nrosenthal@google.com>
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Reviewed-by: Noam Rosenthal <nrosenthal@google.com>
Cr-Commit-Position: refs/heads/main@{#1579397}
diff --git a/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.cc b/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.cc
index 6e118aeed3b13e4d36064f2e114aba9edf4bb6dc..5e0304eda6548e7065527e03c0635bd1387138bd 100644
--- a/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.cc
+++ b/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.cc
@@ -519,8 +519,15 @@ void AnimationFrameTimingMonitor::Trace(Visitor* visitor) const {
visitor->Trace(frame_handling_input_);
}
+BASE_FEATURE(kAlwaysLogLOAFURL, base::FEATURE_DISABLED_BY_DEFAULT);
+
namespace {
+
bool ShouldAllowScriptURL(const String& url) {
+ if (base::FeatureList::IsEnabled(kAlwaysLogLOAFURL)) {
+ return true;
+ }
+
KURL kurl(url);
return kurl.ProtocolIsData() || kurl.ProtocolIsInHTTPFamily() ||
kurl.ProtocolIs("blob") || kurl.IsEmpty();
diff --git a/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.h b/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.h
index f2fe39be2db525d89fcd9787c2ae9285babab26d..c395cf39d404f6c4f6f6e23c9fb8dfe92151a7d2 100644
--- a/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.h
+++ b/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.h
@@ -22,6 +22,11 @@ class TimeTicks;
namespace blink {
+// When enabled, long-animation-frame events will always include the sourceURL,
+// regardless of protocol. This is useful during development when using `file:`
+// URLs or custom protocols defined by embedders.
+CORE_EXPORT BASE_DECLARE_FEATURE(kAlwaysLogLOAFURL);
+
class LocalFrame;
// Monitors long-animation-frame timing (LoAF).

View File

@@ -3203,6 +3203,29 @@ describe('chromium features', () => {
expect(rgb).to.equal('');
});
});
describe('long-animation-frame', () => {
it('should include script attribution on custom protocols if AlwaysLogLOAFURL is enabled', async () => {
const rc = await startRemoteControlApp(['--enable-features=AlwaysLogLOAFURL']);
const hasAttribution = await rc.remotely(async (fixture: string) => {
const { BrowserWindow, protocol, net } = require('electron/main');
const { pathToFileURL } = require('node:url');
protocol.handle('custom', () => net.fetch(pathToFileURL(fixture).toString()));
// `show: true` is necessary on Windows and Linux due to https://github.com/electron/electron/issues/32001
const w = new BrowserWindow({ show: true });
await w.loadURL('custom://my-url');
const hasAttribution = await w.webContents.executeJavaScript('hasAttributionPromise');
global.setTimeout(() => require('electron').app.quit());
return hasAttribution;
}, path.join(fixturesPath, 'chromium', 'long-animation-frame.html'));
expect(hasAttribution).to.be.true();
});
});
});
describe('font fallback', () => {

View File

@@ -0,0 +1,29 @@
<!doctype html>
<html>
<body>
<div id="update"></div>
<script>
var hasAttributionPromise = new Promise((resolve) => {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const has_attribution =
entries.length > 0 &&
entries.every(
({ scripts }) =>
scripts.length > 0 &&
scripts.every(({ sourceURL }) => sourceURL === location.href),
);
resolve(has_attribution);
}).observe({ entryTypes: ["long-animation-frame"] });
// Produce a long animation frame
requestAnimationFrame(() => {
const before = performance.now();
document.querySelector("#update").innerHTML = "Update";
while (performance.now() < before + 60) {}
requestAnimationFrame(() => {});
});
});
</script>
</body>
</html>