diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 30f0942b6a3b..01dc211934d6 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -5776,6 +5776,15 @@ var TabsProgressListener = { stopwatchRunning /* we won't see STATE_START events for pre-rendered tabs */ ) { if (recordLoadTelemetry) { + if (aBrowser.browsingContext?.topWindowContext?.hadLazyLoadImage) { + let timeElapsed = TelemetryStopwatch.timeElapsed( + histogram, + aBrowser + ); + Services.telemetry + .getHistogramById("FX_LAZYLOAD_IMAGE_PAGE_LOAD_MS") + .add(timeElapsed); + } TelemetryStopwatch.finish(histogram, aBrowser); BrowserTelemetryUtils.recordSiteOriginTelemetry(browserWindows()); } diff --git a/docshell/base/WindowContext.cpp b/docshell/base/WindowContext.cpp index 4d5b1e178152..bb06f991685f 100644 --- a/docshell/base/WindowContext.cpp +++ b/docshell/base/WindowContext.cpp @@ -260,6 +260,11 @@ bool WindowContext::CanSet(FieldIndex, const bool& aValue, return CheckOnlyOwningProcessCanSet(aSource); } +bool WindowContext::CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return IsTop() && CheckOnlyOwningProcessCanSet(aSource); +} + void WindowContext::DidSet(FieldIndex, bool aOldValue) { MOZ_ASSERT( diff --git a/docshell/base/WindowContext.h b/docshell/base/WindowContext.h index f0ee15ee43a2..3fa2751bc344 100644 --- a/docshell/base/WindowContext.h +++ b/docshell/base/WindowContext.h @@ -82,7 +82,10 @@ class BrowsingContextGroup; FIELD(HasReportedShadowDOMUsage, bool) \ /* Whether the principal of this window is for a local \ * IP address */ \ - FIELD(IsLocalIP, bool) + FIELD(IsLocalIP, bool) \ + /* Whether the corresponding document has `loading='lazy'` \ + * images; It won't become false if the image becomes non-lazy */ \ + FIELD(HadLazyLoadImage, bool) class WindowContext : public nsISupports, public nsWrapperCache { MOZ_DECL_SYNCED_CONTEXT(WindowContext, MOZ_EACH_WC_FIELD) @@ -175,6 +178,8 @@ class WindowContext : public nsISupports, public nsWrapperCache { bool CanShowPopup(); + bool HadLazyLoadImage() const { return GetHadLazyLoadImage(); } + protected: WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, uint64_t aOuterWindowId, bool aInProcess, @@ -262,6 +267,9 @@ class WindowContext : public nsISupports, public nsWrapperCache { bool CanSet(FieldIndex, const bool& aValue, ContentParent* aSource); + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + void DidSet(FieldIndex, bool aOldValue); void DidSet(FieldIndex, bool aOldValue); diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 9d04e3483381..e76dddf7f5fd 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -15572,6 +15572,17 @@ bool Document::ConsumeTransientUserGestureActivation() { return wc && wc->ConsumeTransientUserGestureActivation(); } +void Document::IncLazyLoadImageCount() { + if (!mLazyLoadImageCount) { + if (WindowContext* wc = GetTopLevelWindowContext()) { + if (!wc->HadLazyLoadImage()) { + Unused << wc->SetHadLazyLoadImage(true); + } + } + } + ++mLazyLoadImageCount; +} + void Document::SetDocTreeHadMedia() { RefPtr topWc = GetTopLevelWindowContext(); if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) { diff --git a/dom/base/Document.h b/dom/base/Document.h index cafcc97b5ccf..b0ac1f1ca7d1 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3739,7 +3739,7 @@ class Document : public nsINode, } DOMIntersectionObserver& EnsureLazyLoadImageObserver(); DOMIntersectionObserver& EnsureLazyLoadImageObserverViewport(); - void IncLazyLoadImageCount() { ++mLazyLoadImageCount; } + void IncLazyLoadImageCount(); void DecLazyLoadImageCount() { MOZ_DIAGNOSTIC_ASSERT(mLazyLoadImageCount > 0); --mLazyLoadImageCount; diff --git a/dom/chrome-webidl/WindowGlobalActors.webidl b/dom/chrome-webidl/WindowGlobalActors.webidl index 1ecc9614cbf0..d1688aaaf32f 100644 --- a/dom/chrome-webidl/WindowGlobalActors.webidl +++ b/dom/chrome-webidl/WindowGlobalActors.webidl @@ -25,6 +25,10 @@ interface WindowContext { // True if the principal of this window is for a local ip address. readonly attribute boolean isLocalIP; + + // True if the corresponding document has `loading='lazy'` images; + // It won't become false if the image becomes non-lazy. + readonly attribute boolean hadLazyLoadImage; }; // Keep this in sync with nsIContentViewer::PermitUnloadAction. diff --git a/layout/base/tests/browser.ini b/layout/base/tests/browser.ini index 41f79638cf54..0f4793e2e5b2 100644 --- a/layout/base/tests/browser.ini +++ b/layout/base/tests/browser.ini @@ -26,3 +26,7 @@ support-files = support-files = file_lazyload_telemetry.html image_rgrg-256x256.png +[browser_lazyload_page_load_telemetry_iframe.js] +support-files = + file_lazyload_telemetry.html + image_rgrg-256x256.png diff --git a/layout/base/tests/browser_lazyload_page_load_telemetry_iframe.js b/layout/base/tests/browser_lazyload_page_load_telemetry_iframe.js new file mode 100644 index 000000000000..b700a229d157 --- /dev/null +++ b/layout/base/tests/browser_lazyload_page_load_telemetry_iframe.js @@ -0,0 +1,48 @@ +const baseURL = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); + +const testFileURL = `${baseURL}file_lazyload_telemetry.html`; + +const { TelemetryTestUtils } = ChromeUtils.import( + "resource://testing-common/TelemetryTestUtils.jsm" +); + +function OtherLazyLoadDataIsReported() { + const snapshot = Services.telemetry.getSnapshotForHistograms("main", false) + .content; + return snapshot.LAZYLOAD_IMAGE_TOTAL; +} + +function pageLoadIsReported() { + const snapshot = Services.telemetry.getSnapshotForHistograms("main", false) + .parent; + return snapshot.FX_LAZYLOAD_IMAGE_PAGE_LOAD_MS; +} + +add_task(async function testTelemetryCollection() { + Services.telemetry.getHistogramById("FX_LAZYLOAD_IMAGE_PAGE_LOAD_MS").clear(); + + const testTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,", + true + ); + + await BrowserTestUtils.waitForCondition(pageLoadIsReported); + + gBrowser.removeTab(testTab); + + // Running this test also causes some other LAZYLOAD related data + // to be collected. Wait for them to be collected to avoid firing + // them at an unexpected time. + await BrowserTestUtils.waitForCondition(OtherLazyLoadDataIsReported); + + const snapshot = Services.telemetry.getSnapshotForHistograms("main", false); + + ok( + snapshot.parent.FX_LAZYLOAD_IMAGE_PAGE_LOAD_MS.sum > 0, + "lazyload image page load telemetry" + ); +}); diff --git a/layout/base/tests/browser_lazyload_telemetry.js b/layout/base/tests/browser_lazyload_telemetry.js index 5bebd72bdb22..745e2b513bd1 100644 --- a/layout/base/tests/browser_lazyload_telemetry.js +++ b/layout/base/tests/browser_lazyload_telemetry.js @@ -9,6 +9,12 @@ const { TelemetryTestUtils } = ChromeUtils.import( "resource://testing-common/TelemetryTestUtils.jsm" ); +function pageLoadIsReported() { + const snapshot = Services.telemetry.getSnapshotForHistograms("main", false) + .parent; + return snapshot.FX_LAZYLOAD_IMAGE_PAGE_LOAD_MS; +} + function dataIsReported() { const snapshot = Services.telemetry.getSnapshotForHistograms("main", false) .content; @@ -25,6 +31,7 @@ add_task(async function testTelemetryCollection() { .getHistogramById("LAZYLOAD_IMAGE_VIEWPORT_LOADING") .clear(); Services.telemetry.getHistogramById("LAZYLOAD_IMAGE_VIEWPORT_LOADED").clear(); + Services.telemetry.getHistogramById("FX_LAZYLOAD_IMAGE_PAGE_LOAD_MS").clear(); const testTab = await BrowserTestUtils.openNewForegroundTab( gBrowser, @@ -47,20 +54,21 @@ add_task(async function testTelemetryCollection() { } ); + await BrowserTestUtils.waitForCondition(pageLoadIsReported); + gBrowser.removeTab(testTab); await BrowserTestUtils.waitForCondition(dataIsReported); - const snapshot = Services.telemetry.getSnapshotForHistograms("main", false) - .content; + const snapshot = Services.telemetry.getSnapshotForHistograms("main", false); // Ensures we have 4 lazyload images. - is(snapshot.LAZYLOAD_IMAGE_TOTAL.values[4], 1, "total images"); + is(snapshot.content.LAZYLOAD_IMAGE_TOTAL.values[4], 1, "total images"); // All 4 images should be lazy-loaded. - is(snapshot.LAZYLOAD_IMAGE_STARTED.values[4], 1, "started to load"); + is(snapshot.content.LAZYLOAD_IMAGE_STARTED.values[4], 1, "started to load"); // The last image didn't reach to the viewport. is( - snapshot.LAZYLOAD_IMAGE_NOT_VIEWPORT.values[1], + snapshot.content.LAZYLOAD_IMAGE_NOT_VIEWPORT.values[1], 1, "images didn't reach viewport" ); @@ -68,9 +76,13 @@ add_task(async function testTelemetryCollection() { // should be three. This includes all images except // the last one. is( - snapshot.LAZYLOAD_IMAGE_VIEWPORT_LOADING.sum + - snapshot.LAZYLOAD_IMAGE_VIEWPORT_LOADED.sum, + snapshot.content.LAZYLOAD_IMAGE_VIEWPORT_LOADING.sum + + snapshot.content.LAZYLOAD_IMAGE_VIEWPORT_LOADED.sum, 3, "images reached viewport" ); + ok( + snapshot.parent.FX_LAZYLOAD_IMAGE_PAGE_LOAD_MS.sum > 0, + "lazyload image page load telemetry" + ); }); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 9dac9f6fcc4c..636b190e6002 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -7708,6 +7708,18 @@ "alert_emails": ["tdsmith@mozilla.com", "perf-telemetry-alerts@mozilla.com", "product-metrics-telemetry-alerts@mozilla.com"], "releaseChannelCollection": "opt-out" }, + "FX_LAZYLOAD_IMAGE_PAGE_LOAD_MS": { + "record_in_processes": ["main"], + "products": ["firefox"], + "expires_in_version": "never", + "kind": "linear", + "high": 10000, + "n_buckets": 100, + "description": "Firefox: Time taken to load a page that has at least one `loading='lazy'` image. (This metric includes both loads and reloads)", + "bug_numbers": [1692350], + "alert_emails": ["sefeng@mozilla.com", "perf-telemetry-alerts@mozilla.com"], + "releaseChannelCollection": "opt-out" + }, "FX_PAGE_RELOAD_NORMAL_MS": { "record_in_processes": ["main"], "products": ["firefox", "fennec"],