Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / test / browser_use_counters.js
blob8b850e7963a1276a9839381f7b5b08416c5ed6fa
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({
9     set: [
10       ["layout.css.use-counters.enabled", true],
11       ["layout.css.use-counters-unimplemented.enabled", true],
12     ],
13   });
14 });
16 async function grabCounters(counters, before) {
17   let result = { sentinel: await ensureData(before?.sentinel) };
18   await Services.fog.testFlushAllChildren();
19   result.gleanPage = Object.fromEntries(
20     counters.map(c => [
21       c.name,
22       Glean[`useCounter${c.glean[0]}Page`][c.glean[1]].testGetValue() ?? 0,
23     ])
24   );
25   result.gleanDoc = Object.fromEntries(
26     counters.map(c => [
27       c.name,
28       Glean[`useCounter${c.glean[0]}Doc`][c.glean[1]].testGetValue() ?? 0,
29     ])
30   );
31   result.glean_docs_destroyed =
32     Glean.useCounter.contentDocumentsDestroyed.testGetValue();
33   result.glean_toplevel_destroyed =
34     Glean.useCounter.topLevelContentDocumentsDestroyed.testGetValue();
35   return result;
38 function assertRange(before, after, key, range) {
39   before = before[key];
40   after = after[key];
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);
46   } else {
47     Assert.equal(after, before + range, desc);
48   }
51 async function test_once(
52   { counters, toplevel_docs, docs, ignore_sentinel },
53   callback
54 ) {
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);
59   await callback();
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;
67     if (!counter.xfail) {
68       is(
69         after.gleanPage[name],
70         before.gleanPage[name] + value,
71         `Glean page counts for ${name} are correct`
72       );
73       is(
74         after.gleanDoc[name],
75         before.gleanDoc[name] + value,
76         `Glean document counts for ${name} are correct`
77       );
78     }
79   }
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() {
86   const TESTS = [
87     // Check that use counters are incremented by SVGs loaded directly in iframes.
88     {
89       type: "iframe",
90       filename: "file_use_counter_svg_getElementById.svg",
91       counters: [
92         {
93           name: "SVGSVGELEMENT_GETELEMENTBYID",
94           glean: ["", "svgsvgelementGetelementbyid"],
95         },
96       ],
97     },
98     {
99       type: "iframe",
100       filename: "file_use_counter_svg_currentScale.svg",
101       counters: [
102         {
103           name: "SVGSVGELEMENT_CURRENTSCALE_getter",
104           glean: ["", "svgsvgelementCurrentscaleGetter"],
105         },
106         {
107           name: "SVGSVGELEMENT_CURRENTSCALE_setter",
108           glean: ["", "svgsvgelementCurrentscaleSetter"],
109         },
110       ],
111     },
113     {
114       type: "iframe",
115       filename: "file_use_counter_style.html",
116       counters: [
117         // Check for longhands.
118         {
119           name: "CSS_PROPERTY_BackgroundImage",
120           glean: ["Css", "cssBackgroundImage"],
121         },
122         // Check for shorthands.
123         { name: "CSS_PROPERTY_Padding", glean: ["Css", "cssPadding"] },
124         // Check for aliases.
125         {
126           name: "CSS_PROPERTY_MozAppearance",
127           glean: ["Css", "cssMozAppearance"],
128         },
129         // Check for counted unknown properties.
130         {
131           name: "CSS_PROPERTY_WebkitPaddingStart",
132           glean: ["Css", "webkitPaddingStart"],
133         },
134       ],
135     },
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.
141     {
142       type: "iframe",
143       filename: "file_use_counter_svg_getElementById.svg",
144       counters: [
145         {
146           name: "SVGSVGELEMENT_GETELEMENTBYID",
147           glean: ["", "svgsvgelementGetelementbyid"],
148         },
149       ],
150     },
151     {
152       type: "iframe",
153       filename: "file_use_counter_svg_currentScale.svg",
154       counters: [
155         {
156           name: "SVGSVGELEMENT_CURRENTSCALE_getter",
157           glean: ["", "svgsvgelementCurrentscaleGetter"],
158         },
159         {
160           name: "SVGSVGELEMENT_CURRENTSCALE_setter",
161           glean: ["", "svgsvgelementCurrentscaleSetter"],
162         },
163       ],
164     },
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.
169     {
170       type: "img",
171       filename: "file_use_counter_svg_getElementById.svg",
172       counters: [{ name: "CSS_PROPERTY_Fill", glean: ["Css", "cssFill"] }],
173     },
174     {
175       type: "img",
176       filename: "file_use_counter_svg_currentScale.svg",
177       counters: [{ name: "CSS_PROPERTY_Fill", glean: ["Css", "cssFill"] }],
178     },
180     // Check that use counters are incremented by directly loading SVGs
181     // that reference patterns defined in another SVG file.
182     {
183       type: "direct",
184       filename: "file_use_counter_svg_fill_pattern.svg",
185       counters: [
186         {
187           name: "CSS_PROPERTY_FillOpacity",
188           glean: ["Css", "cssFillOpacity"],
189           xfail: true,
190         },
191       ],
192     },
194     // Check that use counters are incremented by directly loading SVGs
195     // that reference patterns defined in the same file or in data: URLs.
196     {
197       type: "direct",
198       filename: "file_use_counter_svg_fill_pattern_internal.svg",
199       counters: [
200         { name: "CSS_PROPERTY_FillOpacity", glean: ["Css", "cssFillOpacity"] },
201       ],
202     },
204     // Check that use counters are incremented in a display:none iframe.
205     {
206       type: "undisplayed-iframe",
207       filename: "file_use_counter_svg_currentScale.svg",
208       counters: [
209         {
210           name: "SVGSVGELEMENT_CURRENTSCALE_getter",
211           glean: ["", "svgsvgelementCurrentscaleGetter"],
212         },
213       ],
214     },
216     // Check that a document that comes out of the bfcache reports any new use
217     // counters recorded on it.
218     {
219       type: "direct",
220       filename: "file_use_counter_bfcache.html",
221       waitForExplicitFinish: true,
222       counters: [
223         {
224           name: "SVGSVGELEMENT_GETELEMENTBYID",
225           glean: ["", "svgsvgelementGetelementbyid"],
226         },
227       ],
228     },
230     // // data: URLs don't correctly propagate to their referring document yet.
231     // {
232     //   type: "direct",
233     //   filename: "file_use_counter_svg_fill_pattern_data.svg",
234     //   counters: [
235     //     { name: "PROPERTY_FILL_OPACITY" },
236     //   ],
237     // },
238   ];
240   for (let test of TESTS) {
241     let file = test.filename;
242     info(`checking ${file} (${test.type})`);
244     let options = {
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
249       // with +/- 1.
250       toplevel_docs: file == "file_use_counter_bfcache.html" ? [5, 6] : 1,
251       docs: [test.type == "img" ? 2 : 1, Infinity],
252     };
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;
258       switch (test.type) {
259         case "iframe":
260           url = gHttpTestRoot + "file_use_counter_outer.html";
261           targetElement = "content";
262           break;
263         case "undisplayed-iframe":
264           url = gHttpTestRoot + "file_use_counter_outer_display_none.html";
265           targetElement = "content";
266           break;
267         case "img":
268           url = gHttpTestRoot + "file_use_counter_outer.html";
269           targetElement = "display";
270           break;
271         case "direct":
272           url = gHttpTestRoot + file;
273           targetElement = null;
274           break;
275         default:
276           throw `unexpected type ${test.type}`;
277       }
279       let waitForFinish = null;
280       if (test.waitForExplicitFinish) {
281         is(
282           test.type,
283           "direct",
284           `cannot use waitForExplicitFinish with test type ${test.type}`
285         );
286         // Wait until the tab changes its hash to indicate it has finished.
287         waitForFinish = BrowserTestUtils.waitForLocationChange(
288           gBrowser,
289           url + "#finished"
290         );
291       }
293       let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
294       if (waitForFinish) {
295         await waitForFinish;
296       }
298       if (targetElement) {
299         // Inject our desired file into the target element of the newly-loaded page.
300         await SpecialPowers.spawn(
301           gBrowser.selectedBrowser,
302           [{ file, targetElement }],
303           function (opts) {
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);
310                 resolve();
311               };
312               target.addEventListener("load", listener, true);
313             });
314           }
315         );
316       }
318       // Tear down the page.
319       await BrowserTestUtils.removeTab(newTab);
320     });
321   }
324 add_task(async function test_extension_counters() {
325   let options = {
326     counters: [],
327     docs: 0,
328     toplevel_docs: 0,
329     ignore_sentinel: true,
330   };
331   await test_once(options, async function () {
332     let extension = ExtensionTestUtils.loadExtension({
333       manifest: {
334         page_action: {
335           default_popup: "page.html",
336           browser_style: false,
337         },
338       },
339       async background() {
340         let [tab] = await browser.tabs.query({
341           active: true,
342           currentWindow: true,
343         });
344         await browser.pageAction.show(tab.id);
345         browser.test.sendMessage("ready");
346       },
347       files: {
348         "page.html": `<!DOCTYPE html>
349           <meta charset="utf-8">
350           <!-- Enough to trigger the use counter -->
351           <style>:root { opacity: .5 }</style>
352         `,
353       },
354     });
356     await extension.startup();
357     info("Extension started up");
359     await extension.awaitMessage("ready");
361     await extension.unload();
362     info("Extension unloaded");
363   });
366 async function ensureData(prevSentinelValue = null) {
367   ok(
368     !prevSentinelValue ||
369       ("page" in prevSentinelValue && "doc" in prevSentinelValue),
370     `Sentinel's valid: ${JSON.stringify(prevSentinelValue)}`
371   );
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(
378     async () => {
379       await Services.fog.testFlushAllChildren();
380       return (
381         !prevSentinelValue ||
382         (prevSentinelValue?.page !=
383           Glean.useCounterCssPage.cssMarkerMid.testGetValue() &&
384           prevSentinelValue?.doc !=
385             Glean.useCounterCssDoc.cssMarkerMid.testGetValue())
386       );
387     },
388     "ensureData",
389     100,
390     Infinity
391   ).then(
392     () => ({
393       doc: Glean.useCounterCssPage.cssMarkerMid.testGetValue(),
394       page: Glean.useCounterCssDoc.cssMarkerMid.testGetValue(),
395     }),
396     msg => {
397       throw msg;
398     }
399   );