1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
3 requestLongerTimeout(2);
5 const gHttpTestRoot = "https://example.com/browser/dom/base/test/";
7 add_setup(async function test_initialize() {
8 await SpecialPowers.pushPrefEnv({
10 ["layout.css.use-counters.enabled", true],
11 ["layout.css.use-counters-unimplemented.enabled", true],
16 async function grabCounters(counters, before) {
17 let result = { sentinel: await ensureData(before?.sentinel) };
18 await Services.fog.testFlushAllChildren();
19 result.gleanPage = Object.fromEntries(
22 Glean[`useCounter${c.glean[0]}Page`][c.glean[1]].testGetValue() ?? 0,
25 result.gleanDoc = Object.fromEntries(
28 Glean[`useCounter${c.glean[0]}Doc`][c.glean[1]].testGetValue() ?? 0,
31 result.glean_docs_destroyed =
32 Glean.useCounter.contentDocumentsDestroyed.testGetValue();
33 result.glean_toplevel_destroyed =
34 Glean.useCounter.topLevelContentDocumentsDestroyed.testGetValue();
38 function assertRange(before, after, key, range) {
41 let desc = key + " are correct";
42 if (Array.isArray(range)) {
43 let [min, max] = range;
44 Assert.greaterOrEqual(after, before + min, desc);
45 Assert.lessOrEqual(after, before + max, desc);
47 Assert.equal(after, before + range, desc);
51 async function test_once(
52 { counters, toplevel_docs, docs, ignore_sentinel },
55 // Hold on to the current values of the data we're interested in.
56 // Opening an about:blank tab shouldn't change those.
57 let before = await grabCounters(counters);
61 let after = await grabCounters(counters, ignore_sentinel ? null : before);
63 // Compare before and after.
64 for (let counter of counters) {
65 let name = counter.name;
66 let value = counter.value ?? 1;
69 after.gleanPage[name],
70 before.gleanPage[name] + value,
71 `Glean page counts for ${name} are correct`
75 before.gleanDoc[name] + value,
76 `Glean document counts for ${name} are correct`
81 assertRange(before, after, "glean_toplevel_destroyed", toplevel_docs);
82 assertRange(before, after, "glean_docs_destroyed", docs);
85 add_task(async function test_page_counters() {
87 // Check that use counters are incremented by SVGs loaded directly in iframes.
90 filename: "file_use_counter_svg_getElementById.svg",
93 name: "SVGSVGELEMENT_GETELEMENTBYID",
94 glean: ["", "svgsvgelementGetelementbyid"],
100 filename: "file_use_counter_svg_currentScale.svg",
103 name: "SVGSVGELEMENT_CURRENTSCALE_getter",
104 glean: ["", "svgsvgelementCurrentscaleGetter"],
107 name: "SVGSVGELEMENT_CURRENTSCALE_setter",
108 glean: ["", "svgsvgelementCurrentscaleSetter"],
115 filename: "file_use_counter_style.html",
117 // Check for longhands.
119 name: "CSS_PROPERTY_BackgroundImage",
120 glean: ["Css", "cssBackgroundImage"],
122 // Check for shorthands.
123 { name: "CSS_PROPERTY_Padding", glean: ["Css", "cssPadding"] },
124 // Check for aliases.
126 name: "CSS_PROPERTY_MozAppearance",
127 glean: ["Css", "cssMozAppearance"],
129 // Check for counted unknown properties.
131 name: "CSS_PROPERTY_WebkitPaddingStart",
132 glean: ["Css", "webkitPaddingStart"],
137 // Check that even loads from the imglib cache update use counters. The
138 // images should still be there, because we just loaded them in the last
139 // set of tests. But we won't get updated counts for the document
140 // counters, because we won't be re-parsing the SVG documents.
143 filename: "file_use_counter_svg_getElementById.svg",
146 name: "SVGSVGELEMENT_GETELEMENTBYID",
147 glean: ["", "svgsvgelementGetelementbyid"],
153 filename: "file_use_counter_svg_currentScale.svg",
156 name: "SVGSVGELEMENT_CURRENTSCALE_getter",
157 glean: ["", "svgsvgelementCurrentscaleGetter"],
160 name: "SVGSVGELEMENT_CURRENTSCALE_setter",
161 glean: ["", "svgsvgelementCurrentscaleSetter"],
166 // Check that use counters are incremented by SVGs loaded as images.
167 // Note that SVG images are not permitted to execute script, so we can only
168 // check for properties here.
171 filename: "file_use_counter_svg_getElementById.svg",
172 counters: [{ name: "CSS_PROPERTY_Fill", glean: ["Css", "cssFill"] }],
176 filename: "file_use_counter_svg_currentScale.svg",
177 counters: [{ name: "CSS_PROPERTY_Fill", glean: ["Css", "cssFill"] }],
180 // Check that use counters are incremented by directly loading SVGs
181 // that reference patterns defined in another SVG file.
184 filename: "file_use_counter_svg_fill_pattern.svg",
187 name: "CSS_PROPERTY_FillOpacity",
188 glean: ["Css", "cssFillOpacity"],
194 // Check that use counters are incremented by directly loading SVGs
195 // that reference patterns defined in the same file or in data: URLs.
198 filename: "file_use_counter_svg_fill_pattern_internal.svg",
200 { name: "CSS_PROPERTY_FillOpacity", glean: ["Css", "cssFillOpacity"] },
204 // Check that use counters are incremented in a display:none iframe.
206 type: "undisplayed-iframe",
207 filename: "file_use_counter_svg_currentScale.svg",
210 name: "SVGSVGELEMENT_CURRENTSCALE_getter",
211 glean: ["", "svgsvgelementCurrentscaleGetter"],
216 // Check that a document that comes out of the bfcache reports any new use
217 // counters recorded on it.
220 filename: "file_use_counter_bfcache.html",
221 waitForExplicitFinish: true,
224 name: "SVGSVGELEMENT_GETELEMENTBYID",
225 glean: ["", "svgsvgelementGetelementbyid"],
230 // // data: URLs don't correctly propagate to their referring document yet.
233 // filename: "file_use_counter_svg_fill_pattern_data.svg",
235 // { name: "PROPERTY_FILL_OPACITY" },
240 for (let test of TESTS) {
241 let file = test.filename;
242 info(`checking ${file} (${test.type})`);
245 counters: test.counters,
246 // bfcache test navigates a bunch of times and thus creates multiple top
247 // level document entries, as expected. Whether the last document is
248 // destroyed is a bit racy, see bug 1842800, so for now we allow it
250 toplevel_docs: file == "file_use_counter_bfcache.html" ? [5, 6] : 1,
251 docs: [test.type == "img" ? 2 : 1, Infinity],
254 await test_once(options, async function () {
255 // Load the test file in the new tab, either directly or via
256 // file_use_counter_outer{,_display_none}.html, depending on the test type.
257 let url, targetElement;
260 url = gHttpTestRoot + "file_use_counter_outer.html";
261 targetElement = "content";
263 case "undisplayed-iframe":
264 url = gHttpTestRoot + "file_use_counter_outer_display_none.html";
265 targetElement = "content";
268 url = gHttpTestRoot + "file_use_counter_outer.html";
269 targetElement = "display";
272 url = gHttpTestRoot + file;
273 targetElement = null;
276 throw `unexpected type ${test.type}`;
279 let waitForFinish = null;
280 if (test.waitForExplicitFinish) {
284 `cannot use waitForExplicitFinish with test type ${test.type}`
286 // Wait until the tab changes its hash to indicate it has finished.
287 waitForFinish = BrowserTestUtils.waitForLocationChange(
293 let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
299 // Inject our desired file into the target element of the newly-loaded page.
300 await SpecialPowers.spawn(
301 gBrowser.selectedBrowser,
302 [{ file, targetElement }],
304 let target = content.document.getElementById(opts.targetElement);
305 target.src = opts.file;
307 return new Promise(resolve => {
308 let listener = event => {
309 event.target.removeEventListener("load", listener, true);
312 target.addEventListener("load", listener, true);
318 // Tear down the page.
319 await BrowserTestUtils.removeTab(newTab);
324 add_task(async function test_extension_counters() {
329 ignore_sentinel: true,
331 await test_once(options, async function () {
332 let extension = ExtensionTestUtils.loadExtension({
335 default_popup: "page.html",
336 browser_style: false,
340 let [tab] = await browser.tabs.query({
344 await browser.pageAction.show(tab.id);
345 browser.test.sendMessage("ready");
348 "page.html": `<!DOCTYPE html>
349 <meta charset="utf-8">
350 <!-- Enough to trigger the use counter -->
351 <style>:root { opacity: .5 }</style>
356 await extension.startup();
357 info("Extension started up");
359 await extension.awaitMessage("ready");
361 await extension.unload();
362 info("Extension unloaded");
366 async function ensureData(prevSentinelValue = null) {
368 !prevSentinelValue ||
369 ("page" in prevSentinelValue && "doc" in prevSentinelValue),
370 `Sentinel's valid: ${JSON.stringify(prevSentinelValue)}`
372 // Unfortunately, document destruction (when use counter reporting happens)
373 // happens at some time later than the removal of the tab.
374 // To wait for the use counters to be reported, we repeatedly flush IPC and
375 // check for a change in the "sentinel" use counters
376 // `use.counter.css.{page|doc}.css_marker_mid`.
377 return BrowserTestUtils.waitForCondition(
379 await Services.fog.testFlushAllChildren();
381 !prevSentinelValue ||
382 (prevSentinelValue?.page !=
383 Glean.useCounterCssPage.cssMarkerMid.testGetValue() &&
384 prevSentinelValue?.doc !=
385 Glean.useCounterCssDoc.cssMarkerMid.testGetValue())
393 doc: Glean.useCounterCssPage.cssMarkerMid.testGetValue(),
394 page: Glean.useCounterCssDoc.cssMarkerMid.testGetValue(),