/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */ requestLongerTimeout(2); const gHttpTestRoot = "https://example.com/browser/dom/base/test/"; /** * Enable local telemetry recording for the duration of the tests. */ var gOldContentCanRecord = false; var gOldParentCanRecord = false; add_task(async function test_initialize() { let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService( Ci.nsITelemetry ); gOldParentCanRecord = Telemetry.canRecordExtended; Telemetry.canRecordExtended = true; await SpecialPowers.pushPrefEnv({ set: [ // Because canRecordExtended is a per-process variable, we need to make sure // that all of the pages load in the same content process. Limit the number // of content processes to at most 1 (or 0 if e10s is off entirely). ["dom.ipc.processCount", 1], ["layout.css.use-counters.enabled", true], ["layout.css.use-counters-unimplemented.enabled", true], ], }); gOldContentCanRecord = await SpecialPowers.spawn( gBrowser.selectedBrowser, [], function () { let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService( Ci.nsITelemetry ); let old = telemetry.canRecordExtended; telemetry.canRecordExtended = true; return old; } ); info("canRecord for content: " + gOldContentCanRecord); }); add_task(async function () { const TESTS = [ // Check that use counters are incremented by SVGs loaded directly in iframes. { type: "iframe", filename: "file_use_counter_svg_getElementById.svg", counters: [{ name: "SVGSVGELEMENT_GETELEMENTBYID" }], }, { type: "iframe", filename: "file_use_counter_svg_currentScale.svg", counters: [ { name: "SVGSVGELEMENT_CURRENTSCALE_getter" }, { name: "SVGSVGELEMENT_CURRENTSCALE_setter" }, ], }, { type: "iframe", filename: "file_use_counter_style.html", counters: [ // Check for longhands. { name: "CSS_PROPERTY_BackgroundImage" }, // Check for shorthands. { name: "CSS_PROPERTY_Padding" }, // Check for aliases. { name: "CSS_PROPERTY_MozTransform" }, // Check for counted unknown properties. { name: "CSS_PROPERTY_WebkitPaddingStart" }, ], }, // Check that even loads from the imglib cache update use counters. The // images should still be there, because we just loaded them in the last // set of tests. But we won't get updated counts for the document // counters, because we won't be re-parsing the SVG documents. { type: "iframe", filename: "file_use_counter_svg_getElementById.svg", counters: [{ name: "SVGSVGELEMENT_GETELEMENTBYID" }], check_documents: false, }, { type: "iframe", filename: "file_use_counter_svg_currentScale.svg", counters: [ { name: "SVGSVGELEMENT_CURRENTSCALE_getter" }, { name: "SVGSVGELEMENT_CURRENTSCALE_setter" }, ], check_documents: false, }, // Check that use counters are incremented by SVGs loaded as images. // Note that SVG images are not permitted to execute script, so we can only // check for properties here. { type: "img", filename: "file_use_counter_svg_getElementById.svg", counters: [{ name: "CSS_PROPERTY_Fill" }], }, { type: "img", filename: "file_use_counter_svg_currentScale.svg", counters: [{ name: "CSS_PROPERTY_Fill" }], }, // Check that use counters are incremented by directly loading SVGs // that reference patterns defined in another SVG file. { type: "direct", filename: "file_use_counter_svg_fill_pattern.svg", counters: [{ name: "CSS_PROPERTY_FillOpacity", xfail: true }], }, // Check that use counters are incremented by directly loading SVGs // that reference patterns defined in the same file or in data: URLs. { type: "direct", filename: "file_use_counter_svg_fill_pattern_internal.svg", counters: [{ name: "CSS_PROPERTY_FillOpacity" }], }, // Check that use counters are incremented in a display:none iframe. { type: "undisplayed-iframe", filename: "file_use_counter_svg_currentScale.svg", counters: [{ name: "SVGSVGELEMENT_CURRENTSCALE_getter" }], }, // Check that a document that comes out of the bfcache reports any new use // counters recorded on it. { type: "direct", filename: "file_use_counter_bfcache.html", waitForExplicitFinish: true, counters: [{ name: "SVGSVGELEMENT_GETELEMENTBYID" }], }, // // data: URLs don't correctly propagate to their referring document yet. // { // type: "direct", // filename: "file_use_counter_svg_fill_pattern_data.svg", // counters: [ // { name: "PROPERTY_FILL_OPACITY" }, // ], // }, ]; for (let test of TESTS) { let file = test.filename; info(`checking ${file} (${test.type})`); let newTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); gBrowser.selectedTab = newTab; newTab.linkedBrowser.stop(); // Hold on to the current values of the telemetry histograms we're // interested in. let before = await grabHistogramsFromContent( test.counters.map(c => c.name) ); // Load the test file in the new tab, either directly or via // file_use_counter_outer{,_display_none}.html, depending on the test type. let url, targetElement; switch (test.type) { case "iframe": url = gHttpTestRoot + "file_use_counter_outer.html"; targetElement = "content"; break; case "undisplayed-iframe": url = gHttpTestRoot + "file_use_counter_outer_display_none.html"; targetElement = "content"; break; case "img": url = gHttpTestRoot + "file_use_counter_outer.html"; targetElement = "display"; break; case "direct": url = gHttpTestRoot + file; targetElement = null; break; default: throw `unexpected type ${test.type}`; } BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, url); await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); if (test.waitForExplicitFinish) { if (test.type != "direct") { throw new Error( `cannot use waitForExplicitFinish with test type ${test.type}` ); } // Wait until the tab changes its hash to indicate it has finished. await BrowserTestUtils.waitForLocationChange(gBrowser, url + "#finished"); } if (targetElement) { // Inject our desired file into the target element of the newly-loaded page. await SpecialPowers.spawn( gBrowser.selectedBrowser, [{ file, targetElement }], function (opts) { let target = content.document.getElementById(opts.targetElement); target.src = opts.file; return new Promise(resolve => { let listener = event => { event.target.removeEventListener("load", listener, true); resolve(); }; target.addEventListener("load", listener, true); }); } ); } // Tear down the page. let tabClosed = BrowserTestUtils.waitForTabClosing(newTab); gBrowser.removeTab(newTab); await tabClosed; // Grab histograms again. let after = await grabHistogramsFromContent( test.counters.map(c => c.name), before.sentinel ); // Compare before and after. for (let counter of test.counters) { let name = counter.name; let value = counter.value ?? 1; if (!counter.xfail) { is( after.page[name], before.page[name] + value, `page counts for ${name} after are correct` ); is( after.document[name], before.document[name] + value, `document counts for ${name} after are correct` ); } } if (test.check_documents ?? true) { ok( after.toplevel_docs >= before.toplevel_docs + 1, "top level destroyed document counts are correct" ); // 2 documents for "img" tests: one for the outer html page containing the // element, and one for the SVG image itself. ok( after.docs >= before.docs + (test.type == "img" ? 2 : 1), "destroyed document counts are correct" ); } } }); add_task(async function () { let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService( Ci.nsITelemetry ); Telemetry.canRecordExtended = gOldParentCanRecord; await SpecialPowers.spawn( gBrowser.selectedBrowser, [{ oldCanRecord: gOldContentCanRecord }], async function (arg) { await new Promise(resolve => { let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService( Ci.nsITelemetry ); telemetry.canRecordExtended = arg.oldCanRecord; resolve(); }); } ); }); async function grabHistogramsFromContent(names, prev_sentinel = null) { // We don't have any way to get a notification when telemetry from the // document that was just closed has been reported. So instead, we // repeatedly poll for telemetry until we see that a specific use counter // histogram (CSS_PROPERTY_MarkerMid, the "sentinel") that likely is not // used by any other document that's open has been incremented. let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService( Ci.nsITelemetry ); let gatheredHistograms; return BrowserTestUtils.waitForCondition( function () { // Document use counters are reported in the content process (when e10s // is enabled), and page use counters are reported in the parent process. let snapshots = telemetry.getSnapshotForHistograms("main", false); let checkGet = probe => { // When e10s is disabled, all histograms are reported in the parent // process. Otherwise, all page use counters are reported in the parent // and document use counters are reported in the content process. let process = !Services.appinfo.browserTabsRemoteAutostart || probe.endsWith("_PAGE") || probe == "TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED" ? "parent" : "content"; return snapshots[process][probe] ? snapshots[process][probe].sum : 0; }; let page = Object.fromEntries( names.map(name => [name, checkGet(`USE_COUNTER2_${name}_PAGE`)]) ); let document = Object.fromEntries( names.map(name => [name, checkGet(`USE_COUNTER2_${name}_DOCUMENT`)]) ); gatheredHistograms = { page, document, docs: checkGet("CONTENT_DOCUMENTS_DESTROYED"), toplevel_docs: checkGet("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED"), sentinel: { doc: checkGet("USE_COUNTER2_CSS_PROPERTY_MarkerMid_DOCUMENT"), page: checkGet("USE_COUNTER2_CSS_PROPERTY_MarkerMid_PAGE"), }, }; let sentinelChanged = !prev_sentinel || (prev_sentinel.doc != gatheredHistograms.sentinel.doc && prev_sentinel.page != gatheredHistograms.sentinel.page); return sentinelChanged; }, "grabHistogramsFromContent", 100, Infinity ).then( () => gatheredHistograms, function (msg) { throw msg; } ); }