tor-browser

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

browser_jsonview_array_buckets.js (6533B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 /**
      7 * Tests that large arrays in JSON view are bucketed into ranges
      8 * to prevent browser freezing when expanding them.
      9 */
     10 
     11 /**
     12 * Load JSON data in the JSON viewer
     13 */
     14 async function loadJsonData(data) {
     15  const json = JSON.stringify(data);
     16  return addJsonViewTab("data:application/json," + json);
     17 }
     18 
     19 /**
     20 * Get all bucket labels matching the pattern [n…m]
     21 */
     22 async function getBucketLabels() {
     23  return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
     24    const labels = Array.from(
     25      content.document.querySelectorAll(".treeLabelCell")
     26    );
     27    return labels
     28      .filter(label => /\[\d+…\d+\]/.test(label.textContent))
     29      .map(label => label.textContent.trim());
     30  });
     31 }
     32 
     33 /**
     34 * Check if any bucket labels exist
     35 */
     36 async function hasBuckets() {
     37  return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
     38    const labels = Array.from(
     39      content.document.querySelectorAll(".treeLabelCell")
     40    );
     41    return labels.some(label => /\[\d+…\d+\]/.test(label.textContent));
     42  });
     43 }
     44 
     45 add_task(async function test_small_array_no_buckets() {
     46  const smallArray = Array(100).fill("item");
     47  await loadJsonData({ data: smallArray });
     48 
     49  // Small JSON auto-expands, so check the already-expanded state
     50  // Count the rows - should have root, "data" label, and 100 array elements
     51  const rowCount = await getElementCount(".treeRow");
     52  is(rowCount, 101, "Small array shows all 100 elements without bucketing");
     53 
     54  // Verify no bucket nodes (no labels like "[0…99]")
     55  is(await hasBuckets(), false, "No bucket nodes in small array");
     56 });
     57 
     58 add_task(async function test_medium_array_has_buckets() {
     59  const mediumArray = Array(250)
     60    .fill(null)
     61    .map((_, i) => `item${i}`);
     62  await loadJsonData({ data: mediumArray });
     63 
     64  // Array auto-expands, showing buckets instead of individual elements
     65  // For 250 elements, bucket size = 100, so we expect 3 buckets:
     66  // [0…99], [100…199], [200…249]
     67  const bucketLabels = await getBucketLabels();
     68  is(bucketLabels.length, 3, "Medium array (250 elements) creates 3 buckets");
     69 
     70  // Verify bucket names
     71  Assert.deepEqual(
     72    bucketLabels,
     73    ["[0…99]", "[100…199]", "[200…249]"],
     74    "Bucket names are correct"
     75  );
     76 });
     77 
     78 add_task(async function test_expand_bucket() {
     79  const array = Array(150)
     80    .fill(null)
     81    .map((_, i) => `value${i}`);
     82  await loadJsonData(array);
     83 
     84  // Root array auto-expands and shows 2 buckets: [0…99] and [100…149]
     85  const bucketLabels = await getBucketLabels();
     86  is(bucketLabels.length, 2, "Root array shows 2 buckets");
     87 
     88  // Find and expand the first bucket [0…99]
     89  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
     90    const labels = Array.from(
     91      content.document.querySelectorAll(".treeLabelCell")
     92    );
     93    const firstBucket = labels.find(label =>
     94      label.textContent.includes("[0…99]")
     95    );
     96    if (firstBucket) {
     97      firstBucket.click();
     98    }
     99  });
    100 
    101  // Verify that elements 0-99 are now visible
    102  const hasElements = await SpecialPowers.spawn(
    103    gBrowser.selectedBrowser,
    104    [],
    105    () => {
    106      const labels = Array.from(
    107        content.document.querySelectorAll(".treeLabelCell")
    108      );
    109      const hasZero = labels.some(label => label.textContent.trim() === "0");
    110      const has99 = labels.some(label => label.textContent.trim() === "99");
    111      return hasZero && has99;
    112    }
    113  );
    114  is(hasElements, true, "Expanding bucket shows individual elements");
    115 });
    116 
    117 add_task(async function test_large_array_bucket_size() {
    118  // For 10,000 elements, bucket size should be 100 (10^2)
    119  // This creates 100 buckets
    120  const largeArray = Array(10000).fill("x");
    121  await loadJsonData(largeArray);
    122 
    123  // Root array auto-expands showing buckets
    124  const bucketLabels = await getBucketLabels();
    125 
    126  is(bucketLabels.length, 100, "10,000 elements create 100 buckets");
    127  is(bucketLabels[0], "[0…99]", "First bucket starts at 0");
    128  is(bucketLabels[99], "[9900…9999]", "Last bucket ends at 9999");
    129 });
    130 
    131 add_task(async function test_very_large_array_bucket_size() {
    132  // For 100,000 elements, bucket size should be 1000 (10^3)
    133  // This creates 100 buckets
    134  const veryLargeArray = Array(100000).fill(1);
    135  await loadJsonData(veryLargeArray);
    136 
    137  // Root array auto-expands showing buckets
    138  const bucketLabels = await getBucketLabels();
    139 
    140  is(bucketLabels.length, 100, "100,000 elements create 100 buckets");
    141  is(bucketLabels[0], "[0…999]", "First bucket is [0…999]");
    142  is(bucketLabels[1], "[1000…1999]", "Second bucket is [1000…1999]");
    143 });
    144 
    145 add_task(async function test_nested_buckets() {
    146  // Create an array with 100,000 elements
    147  // Initial bucket size will be 1000, so first bucket is [0…999]
    148  // That bucket has 1000 elements, which needs 10 sub-buckets of 100 each
    149  const veryLargeArray = Array(100000)
    150    .fill(null)
    151    .map((_, i) => i);
    152  await loadJsonData(veryLargeArray);
    153 
    154  // Root array auto-expands showing top-level buckets
    155  // Find and expand the first bucket [0…999]
    156  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    157    const labels = Array.from(
    158      content.document.querySelectorAll(".treeLabelCell")
    159    );
    160    const firstBucket = labels.find(label =>
    161      label.textContent.includes("[0…999]")
    162    );
    163    if (firstBucket) {
    164      firstBucket.click();
    165    }
    166  });
    167 
    168  // The [0…999] bucket should now show nested buckets, not individual elements
    169  // The 1000 elements need 10 sub-buckets of 100 each: [0…99], [100…199], ..., [900…999]
    170  const nestedBuckets = await SpecialPowers.spawn(
    171    gBrowser.selectedBrowser,
    172    [],
    173    () => {
    174      const labels = Array.from(
    175        content.document.querySelectorAll(".treeLabelCell")
    176      );
    177      return labels
    178        .map(label => label.textContent)
    179        .filter(text => {
    180          // Look for nested buckets (exclude the parent [0…999] bucket)
    181          const match = text.match(/\[(\d+)…(\d+)\]/);
    182          if (!match) {
    183            return false;
    184          }
    185          const start = parseInt(match[1], 10);
    186          const end = parseInt(match[2], 10);
    187          // Nested buckets have size 100, parent has size 1000
    188          return end - start === 99;
    189        });
    190    }
    191  );
    192 
    193  is(nestedBuckets.length, 10, "1000-element bucket creates 10 nested buckets");
    194  is(nestedBuckets[0], "[0…99]", "First nested bucket is [0…99]");
    195  is(nestedBuckets[9], "[900…999]", "Last nested bucket is [900…999]");
    196 });