tor-browser

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

browser_jsonview_profile_size.js (6250B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const TEST_JSON_URL = URL_ROOT + "simple_json.json";
      7 const PROFILER_URL_PREF = "devtools.performance.recording.ui-base-url";
      8 const TEST_PROFILER_URL = "http://127.0.0.1:8888";
      9 
     10 add_setup(async function () {
     11  info("Setting profiler URL to localhost for tests");
     12  await SpecialPowers.pushPrefEnv({
     13    set: [
     14      [PROFILER_URL_PREF, TEST_PROFILER_URL],
     15      ["devtools.jsonview.size-profiler.enabled", true],
     16    ],
     17  });
     18 });
     19 
     20 add_task(async function testProfileSizeButtonExists() {
     21  info("Test that the Profile Size button exists in the tab bar");
     22 
     23  await addJsonViewTab(TEST_JSON_URL);
     24 
     25  const buttonExists = await SpecialPowers.spawn(
     26    gBrowser.selectedBrowser,
     27    [],
     28    () => {
     29      const button = content.document.querySelector(".profiler-icon-button");
     30      return !!button;
     31    }
     32  );
     33 
     34  ok(buttonExists, "Profile Size button should exist in the tab bar");
     35 });
     36 
     37 add_task(async function testProfileSizePostMessage() {
     38  info("Test that profile is sent via postMessage with correct handshake");
     39 
     40  await addJsonViewTab(TEST_JSON_URL);
     41 
     42  const browser = gBrowser.selectedBrowser;
     43 
     44  // Set up the mock for window.open before clicking
     45  await SpecialPowers.spawn(browser, [TEST_PROFILER_URL], expectedUrl => {
     46    const win = Cu.waiveXrays(content);
     47 
     48    // Create test results object
     49    win.testResults = {
     50      windowUrl: null,
     51      profile: null,
     52      receivedReadyRequest: false,
     53      messageOrigin: null,
     54      resolved: false,
     55    };
     56 
     57    // Mock window.open
     58    win.open = Cu.exportFunction(function (url) {
     59      win.testResults.windowUrl = url;
     60 
     61      // Create mock window object with postMessage
     62      const mockWindow = {
     63        postMessage(message, origin) {
     64          if (message.name === "ready:request") {
     65            win.testResults.receivedReadyRequest = true;
     66            win.testResults.messageOrigin = origin;
     67            // Simulate profiler responding with ready:response
     68            const event = new win.MessageEvent(
     69              "message",
     70              Cu.cloneInto(
     71                {
     72                  origin: expectedUrl,
     73                  data: { name: "ready:response" },
     74                },
     75                win
     76              )
     77            );
     78            win.dispatchEvent(event);
     79          } else if (message.name === "inject-profile") {
     80            win.testResults.profile = message.profile;
     81            win.testResults.resolved = true;
     82          }
     83        },
     84        close() {},
     85      };
     86 
     87      return Cu.cloneInto(mockWindow, win, { cloneFunctions: true });
     88    }, win);
     89  });
     90 
     91  // Click the button from within the content process
     92  await SpecialPowers.spawn(browser, [], () => {
     93    const button = content.document.querySelector(".profiler-icon-button");
     94    button.click();
     95  });
     96 
     97  // Wait for the test to complete
     98  await TestUtils.waitForCondition(
     99    async () => {
    100      return SpecialPowers.spawn(browser, [], () => {
    101        return Cu.waiveXrays(content).testResults?.resolved;
    102      });
    103    },
    104    "Waiting for profile to be sent",
    105    100,
    106    100
    107  );
    108 
    109  // Get the results
    110  const result = await SpecialPowers.spawn(browser, [], () => {
    111    return Cu.waiveXrays(content).testResults;
    112  });
    113 
    114  ok(result.windowUrl, "window.open should have been called");
    115  ok(
    116    result.windowUrl.includes("/from-post-message/"),
    117    `URL should contain /from-post-message/, got: ${result.windowUrl}`
    118  );
    119  ok(
    120    result.windowUrl.includes(TEST_PROFILER_URL),
    121    `URL should use preference URL ${TEST_PROFILER_URL}, got: ${result.windowUrl}`
    122  );
    123  ok(result.receivedReadyRequest, "Should send ready:request");
    124  is(
    125    result.messageOrigin,
    126    TEST_PROFILER_URL,
    127    "postMessage should use correct origin"
    128  );
    129  ok(result.profile, "Should capture profile");
    130  ok(result.profile.meta, "Profile should have meta");
    131  ok(result.profile.threads, "Profile should have threads");
    132 });
    133 
    134 add_task(async function testProfileCreation() {
    135  info("Test that a valid profile is created");
    136 
    137  const { createSizeProfile } = ChromeUtils.importESModule(
    138    "resource://devtools/client/jsonview/json-size-profiler.mjs"
    139  );
    140 
    141  const testJson = '{"name": "test", "value": 123}';
    142  const profile = createSizeProfile(testJson);
    143 
    144  ok(profile.meta, "Profile should have meta object");
    145  ok(Array.isArray(profile.threads), "Profile should have threads array");
    146  ok(
    147    Array.isArray(profile.meta.markerSchema),
    148    "Profile meta should have markerSchema array"
    149  );
    150  ok(
    151    Array.isArray(profile.meta.categories),
    152    "Profile meta should have categories array"
    153  );
    154  Assert.greater(
    155    profile.threads[0].samples.length,
    156    0,
    157    "Profile should have samples"
    158  );
    159 
    160  // Validate total size of samples
    161  const samples = profile.threads[0].samples;
    162  const totalSize = samples.weight.reduce((sum, weight) => sum + weight, 0);
    163  is(
    164    totalSize,
    165    testJson.length,
    166    "Total sample size should match JSON string length"
    167  );
    168 });
    169 
    170 add_task(async function testProfileCreationWithUtf8() {
    171  info("Test that profile correctly handles UTF-8 multi-byte characters");
    172 
    173  const { createSizeProfile } = ChromeUtils.importESModule(
    174    "resource://devtools/client/jsonview/json-size-profiler.mjs"
    175  );
    176 
    177  // Test with various UTF-8 characters
    178  // "café" - é is 2 bytes in UTF-8
    179  // "中文" - each character is 3 bytes in UTF-8
    180  // "🔥" - emoji is 4 bytes in UTF-8
    181  const testJson = '{"name": "café", "lang": "中文", "emoji": "🔥"}';
    182  const profile = createSizeProfile(testJson);
    183 
    184  // Calculate expected byte length (UTF-8 encoded)
    185  const utf8Encoder = new TextEncoder();
    186  const expectedByteLength = utf8Encoder.encode(testJson).length;
    187 
    188  const samples = profile.threads[0].samples;
    189  const totalSize = samples.weight.reduce((sum, weight) => sum + weight, 0);
    190 
    191  is(
    192    totalSize,
    193    expectedByteLength,
    194    `Total sample size should match UTF-8 byte length (${expectedByteLength} bytes, not ${testJson.length} characters)`
    195  );
    196  Assert.greater(
    197    expectedByteLength,
    198    testJson.length,
    199    "UTF-8 byte length should be greater than character count for this test string"
    200  );
    201 
    202  info(`Sample count: ${samples.length} for ${expectedByteLength} bytes`);
    203 });