forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			348 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | |
| 
 | |
| requestLongerTimeout(2);
 | |
| 
 | |
| const gHttpTestRoot = "http://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.loadURI(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
 | |
|       // <img> 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;
 | |
|     }
 | |
|   );
 | |
| }
 | 
