test_sharedMap.js (8942B)
1 "use strict"; 2 3 const { AppConstants } = ChromeUtils.importESModule( 4 "resource://gre/modules/AppConstants.sys.mjs" 5 ); 6 const { XPCShellContentUtils } = ChromeUtils.importESModule( 7 "resource://testing-common/XPCShellContentUtils.sys.mjs" 8 ); 9 10 const PROCESS_COUNT_PREF = "dom.ipc.processCount"; 11 12 const remote = AppConstants.platform !== "android"; 13 14 XPCShellContentUtils.init(this); 15 16 let contentPage; 17 18 async function readBlob(key, sharedData = Services.cpmm.sharedData) { 19 const { ExtensionUtils } = ChromeUtils.importESModule( 20 "resource://gre/modules/ExtensionUtils.sys.mjs" 21 ); 22 23 let reader = new FileReader(); 24 reader.readAsText(sharedData.get(key)); 25 await ExtensionUtils.promiseEvent(reader, "loadend"); 26 return reader.result; 27 } 28 29 function getKey(key, sharedData = Services.cpmm.sharedData) { 30 return sharedData.get(key); 31 } 32 33 function hasKey(key, sharedData = Services.cpmm.sharedData) { 34 return sharedData.has(key); 35 } 36 37 function getContents(sharedMap = Services.cpmm.sharedData) { 38 return { 39 keys: Array.from(sharedMap.keys()), 40 values: Array.from(sharedMap.values()), 41 entries: Array.from(sharedMap.entries()), 42 getValues: Array.from(sharedMap.keys(), key => sharedMap.get(key)), 43 }; 44 } 45 46 function checkMap(contents, expected) { 47 expected = Array.from(expected); 48 49 equal(contents.keys.length, expected.length, "Got correct number of keys"); 50 equal( 51 contents.values.length, 52 expected.length, 53 "Got correct number of values" 54 ); 55 equal( 56 contents.entries.length, 57 expected.length, 58 "Got correct number of entries" 59 ); 60 61 for (let [i, [key, val]] of contents.entries.entries()) { 62 equal(key, contents.keys[i], `keys()[${i}] matches entries()[${i}]`); 63 deepEqual( 64 val, 65 contents.values[i], 66 `values()[${i}] matches entries()[${i}]` 67 ); 68 } 69 70 expected.sort(([a], [b]) => a.localeCompare(b)); 71 contents.entries.sort(([a], [b]) => a.localeCompare(b)); 72 73 for (let [i, [key, val]] of contents.entries.entries()) { 74 equal( 75 key, 76 expected[i][0], 77 `expected[${i}].key matches entries()[${i}].key` 78 ); 79 deepEqual( 80 val, 81 expected[i][1], 82 `expected[${i}].value matches entries()[${i}].value` 83 ); 84 } 85 } 86 87 function checkParentMap(expected) { 88 info("Checking parent map"); 89 checkMap(getContents(Services.ppmm.sharedData), expected); 90 } 91 92 async function checkContentMaps(expected, parentOnly = false) { 93 info("Checking in-process content map"); 94 checkMap(getContents(Services.cpmm.sharedData), expected); 95 96 if (!parentOnly) { 97 info("Checking out-of-process content map"); 98 let contents = await contentPage.spawn([], getContents); 99 checkMap(contents, expected); 100 } 101 } 102 103 async function loadContentPage() { 104 let page = await XPCShellContentUtils.loadContentPage("data:text/html,", { 105 remote, 106 }); 107 registerCleanupFunction(() => page.close()); 108 return page; 109 } 110 111 add_setup(async function () { 112 // Start with one content process so that we can increase the number 113 // later and test the behavior of a fresh content process. 114 Services.prefs.setIntPref(PROCESS_COUNT_PREF, 1); 115 116 contentPage = await loadContentPage(); 117 }); 118 119 add_task(async function test_sharedMap() { 120 let { sharedData } = Services.ppmm; 121 122 info("Check that parent and child maps are both initially empty"); 123 124 checkParentMap([]); 125 await checkContentMaps([]); 126 127 let expected = [ 128 ["foo-a", { foo: "a" }], 129 ["foo-b", { foo: "b" }], 130 ["bar-c", null], 131 ["bar-d", 42], 132 ]; 133 134 function setKey(key, val) { 135 sharedData.set(key, val); 136 expected = expected.filter(([k]) => k != key); 137 expected.push([key, val]); 138 } 139 function deleteKey(key) { 140 sharedData.delete(key); 141 expected = expected.filter(([k]) => k != key); 142 } 143 144 for (let [key, val] of expected) { 145 sharedData.set(key, val); 146 } 147 148 info( 149 "Add some entries, test that they are initially only available in the parent" 150 ); 151 152 checkParentMap(expected); 153 await checkContentMaps([]); 154 155 info("Flush. Check that changes are visible in both parent and children"); 156 157 sharedData.flush(); 158 159 checkParentMap(expected); 160 await checkContentMaps(expected); 161 162 info( 163 "Add another entry. Check that it is initially only available in the parent" 164 ); 165 166 let oldExpected = Array.from(expected); 167 168 setKey("baz-a", { meh: "meh" }); 169 170 // When we do several checks in a row, we can't check the values in 171 // the content process, since the async checks may allow the idle 172 // flush task to run, and update it before we're ready. 173 174 checkParentMap(expected); 175 checkContentMaps(oldExpected, true); 176 177 info( 178 "Add another entry. Check that both new entries are only available in the parent" 179 ); 180 181 setKey("baz-a", { meh: 12 }); 182 183 checkParentMap(expected); 184 checkContentMaps(oldExpected, true); 185 186 info( 187 "Delete an entry. Check that all changes are only visible in the parent" 188 ); 189 190 deleteKey("foo-b"); 191 192 checkParentMap(expected); 193 checkContentMaps(oldExpected, true); 194 195 info( 196 "Flush. Check that all entries are available in both parent and children" 197 ); 198 199 sharedData.flush(); 200 201 checkParentMap(expected); 202 await checkContentMaps(expected); 203 204 info("Test that entries are automatically flushed on idle:"); 205 206 info( 207 "Add a new entry. Check that it is initially only available in the parent" 208 ); 209 210 // Test the idle flush task. 211 oldExpected = Array.from(expected); 212 213 setKey("thing", "stuff"); 214 215 checkParentMap(expected); 216 checkContentMaps(oldExpected, true); 217 218 info( 219 "Wait for an idle timeout. Check that changes are now visible in all children" 220 ); 221 222 await new Promise(resolve => ChromeUtils.idleDispatch(resolve)); 223 224 checkParentMap(expected); 225 await checkContentMaps(expected); 226 227 // Test that has() rebuilds map after a flush. 228 sharedData.set("grick", true); 229 sharedData.flush(); 230 equal( 231 await contentPage.spawn(["grick"], hasKey), 232 true, 233 "has() should see key after flush" 234 ); 235 236 sharedData.set("grack", true); 237 sharedData.flush(); 238 equal( 239 await contentPage.spawn(["gruck"], hasKey), 240 false, 241 "has() should return false for nonexistent key" 242 ); 243 }); 244 245 add_task(async function test_blobs() { 246 let { sharedData } = Services.ppmm; 247 248 let text = [ 249 "The quick brown fox jumps over the lazy dog", 250 "Lorem ipsum dolor sit amet, consectetur adipiscing elit", 251 "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 252 ]; 253 let blobs = text.map(str => new Blob([str])); 254 255 let data = { foo: { bar: "baz" } }; 256 257 sharedData.set("blob0", blobs[0]); 258 sharedData.set("blob1", blobs[1]); 259 sharedData.set("data", data); 260 261 equal( 262 await readBlob("blob0", sharedData), 263 text[0], 264 "Expected text for blob0 in parent ppmm" 265 ); 266 267 sharedData.flush(); 268 269 equal( 270 await readBlob("blob0", sharedData), 271 text[0], 272 "Expected text for blob0 in parent ppmm" 273 ); 274 equal( 275 await readBlob("blob1", sharedData), 276 text[1], 277 "Expected text for blob1 in parent ppmm" 278 ); 279 280 equal( 281 await readBlob("blob0"), 282 text[0], 283 "Expected text for blob0 in parent cpmm" 284 ); 285 equal( 286 await readBlob("blob1"), 287 text[1], 288 "Expected text for blob1 in parent cpmm" 289 ); 290 291 equal( 292 await contentPage.spawn(["blob0"], readBlob), 293 text[0], 294 "Expected text for blob0 in child 1 cpmm" 295 ); 296 equal( 297 await contentPage.spawn(["blob1"], readBlob), 298 text[1], 299 "Expected text for blob1 in child 1 cpmm" 300 ); 301 302 // Start a second child process 303 Services.prefs.setIntPref(PROCESS_COUNT_PREF, 2); 304 305 let page2 = await loadContentPage(); 306 307 equal( 308 await page2.spawn(["blob0"], readBlob), 309 text[0], 310 "Expected text for blob0 in child 2 cpmm" 311 ); 312 equal( 313 await page2.spawn(["blob1"], readBlob), 314 text[1], 315 "Expected text for blob1 in child 2 cpmm" 316 ); 317 318 sharedData.set("blob0", blobs[2]); 319 320 equal( 321 await readBlob("blob0", sharedData), 322 text[2], 323 "Expected text for blob0 in parent ppmm" 324 ); 325 326 sharedData.flush(); 327 328 equal( 329 await readBlob("blob0", sharedData), 330 text[2], 331 "Expected text for blob0 in parent ppmm" 332 ); 333 equal( 334 await readBlob("blob1", sharedData), 335 text[1], 336 "Expected text for blob1 in parent ppmm" 337 ); 338 339 equal( 340 await readBlob("blob0"), 341 text[2], 342 "Expected text for blob0 in parent cpmm" 343 ); 344 equal( 345 await readBlob("blob1"), 346 text[1], 347 "Expected text for blob1 in parent cpmm" 348 ); 349 350 equal( 351 await contentPage.spawn(["blob0"], readBlob), 352 text[2], 353 "Expected text for blob0 in child 1 cpmm" 354 ); 355 equal( 356 await contentPage.spawn(["blob1"], readBlob), 357 text[1], 358 "Expected text for blob1 in child 1 cpmm" 359 ); 360 361 equal( 362 await page2.spawn(["blob0"], readBlob), 363 text[2], 364 "Expected text for blob0 in child 2 cpmm" 365 ); 366 equal( 367 await page2.spawn(["blob1"], readBlob), 368 text[1], 369 "Expected text for blob1 in child 2 cpmm" 370 ); 371 372 deepEqual( 373 await page2.spawn(["data"], getKey), 374 data, 375 "Expected data for data key in child 2 cpmm" 376 ); 377 });