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 });