tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_use_counters.js (11986B)


      1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 
      3 requestLongerTimeout(2);
      4 
      5 const gHttpTestRoot = "https://example.com/browser/dom/base/test/";
      6 
      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 });
     15 
     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;
     36 }
     37 
     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  }
     49 }
     50 
     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);
     58 
     59  await callback();
     60 
     61  let after = await grabCounters(counters, ignore_sentinel ? null : before);
     62 
     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  }
     80 
     81  assertRange(before, after, "glean_toplevel_destroyed", toplevel_docs);
     82  assertRange(before, after, "glean_docs_destroyed", docs);
     83 }
     84 
     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    },
    112 
    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    },
    136 
    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    },
    165 
    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    },
    179 
    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    },
    193 
    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    },
    203 
    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    },
    215 
    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    },
    229 
    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  ];
    239 
    240  for (let test of TESTS) {
    241    let file = test.filename;
    242    info(`checking ${file} (${test.type})`);
    243 
    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    };
    253 
    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      }
    278 
    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      }
    292 
    293      let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
    294      if (waitForFinish) {
    295        await waitForFinish;
    296      }
    297 
    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;
    306 
    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      }
    317 
    318      // Tear down the page.
    319      await BrowserTestUtils.removeTab(newTab);
    320    });
    321  }
    322 });
    323 
    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    });
    355 
    356    await extension.startup();
    357    info("Extension started up");
    358 
    359    await extension.awaitMessage("ready");
    360 
    361    await extension.unload();
    362    info("Extension unloaded");
    363  });
    364 });
    365 
    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  );
    400 }