link-multiple-load-events.html (4941B)
1 <!DOCTYPE html> 2 <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org"> 3 <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element"> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="/common/utils.js"></script> 7 <script> 8 function getTimeoutPromise(t, msg) { 9 return new Promise((resolve, reject) => { 10 t.step_timeout(() => reject(`Timeout reached: ${msg}`), 2000); 11 }); 12 } 13 14 // The tests generated from this loop are important to ensure that a link's load 15 // or error event handler never incurs infinite recursion by virtue of 16 // re-setting a link element's attribute to the value it already has. This is 17 // what thwarted the first attempt at fixing https://crbug.com/41436016 in 18 // Chromium. 19 const attributes_to_test = ['rel', 'href', 'type', 'media']; 20 for (const attribute of attributes_to_test) { 21 promise_test(async t => { 22 const id = token(); 23 const link = document.createElement('link'); 24 let count = 0; 25 let response = null; 26 27 link.onerror = t.unreached_func('Sheet should load successfully'); 28 const firstLoadPromise = new Promise(resolve => { 29 link.onload = resolve; 30 }); 31 32 // Set all attributes to a sensible default, so when we re-assign one of 33 // them (`attribute`) idempotently later, the value doesn't change. 34 link.rel = 'stylesheet'; 35 link.media = 'all'; 36 link.type = 'text/css'; 37 link.href = new URL(`stylesheet.py?id=${id}`, location.href); 38 39 document.head.append(link); 40 t.add_cleanup(() => { 41 link.remove(); 42 }); 43 44 await Promise.race([firstLoadPromise, getTimeoutPromise(t, 'first resource')]); 45 response = await fetch(`stylesheet.py?id=${id}&count=foo`); 46 count = await response.text(); 47 assert_equals(count, '1', "server sees first style sheet request"); 48 49 const secondLoadPromise = new Promise((resolve, reject) => { 50 link.onload = () => reject('second load event unexpectedly fired'); 51 }); 52 const expectedTimeoutPromise = new Promise(resolve => { 53 t.step_timeout(resolve, 2000); 54 }); 55 56 link[attribute] = link[attribute]; 57 await Promise.race([expectedTimeoutPromise, secondLoadPromise]); 58 response = await fetch(`stylesheet.py?id=${id}&count=foo`); 59 count = await response.text(); 60 assert_equals(count, '0', 61 "server does not see second request to the same style sheet"); 62 }, `<link> cannot request the same resource twice by touching the ` + 63 `'${attribute}' attribute, if its value never changes`); 64 } 65 66 67 promise_test(async t => { 68 const id = token(); 69 const link = document.createElement('link'); 70 link.rel = 'preload'; 71 link.as = 'style'; 72 document.head.append(link); 73 t.add_cleanup(() => { 74 link.remove(); 75 }); 76 77 let count = 0; 78 let response = null; 79 80 link.onerror = t.unreached_func('Sheet should load successfully'); 81 const firstLoadPromise = new Promise(resolve => { 82 link.onload = resolve; 83 }); 84 85 link.href = `stylesheet.py?id=${id}`; 86 await Promise.race([firstLoadPromise, getTimeoutPromise(t, 'first resource')]); 87 response = await fetch(`stylesheet.py?id=${id}&count=foo`); 88 count = await response.text(); 89 assert_equals(count, '1', "server sees preload request"); 90 91 const secondLoadPromise = new Promise(resolve => { 92 link.onload = resolve; 93 }); 94 95 // Switching from `rel=preload` => `rel=stylesheet` triggers the stylesheet 96 // processing model. The resource loads from the preload cache and never 97 // touches the network, but the load event does fire again. 98 link.rel = 'stylesheet'; 99 await Promise.race([secondLoadPromise, 100 getTimeoutPromise(t, 'rel=stylesheet using preload cache')]); 101 response = await fetch(`stylesheet.py?id=${id}&count=foo`); 102 count = await response.text(); 103 assert_equals(count, '0', "server does not see second request to the same style sheet"); 104 }, "<link> load event can fire twice for the same href resource, based on " + 105 "'rel' attribute mutations"); 106 107 promise_test(async t => { 108 const link = document.createElement('link'); 109 link.rel = 'stylesheet'; 110 document.head.append(link); 111 t.add_cleanup(() => { 112 link.remove(); 113 }); 114 115 link.onerror = t.unreached_func('Sheet should load successfully'); 116 const firstLoadPromise = new Promise(resolve => { 117 link.onload = resolve; 118 }); 119 120 link.href = 'style.css?first'; 121 await Promise.race([firstLoadPromise, getTimeoutPromise(t, 'first resource')]); 122 123 const secondLoadPromise = new Promise(resolve => { 124 link.onload = resolve; 125 }); 126 127 link.href = 'style.css?second'; 128 await Promise.race([secondLoadPromise, getTimeoutPromise(t, 'second resource')]); 129 130 const thirdLoadPromise = new Promise(resolve => { 131 link.onload = resolve; 132 }); 133 134 link.href = 'style.css?third'; 135 await Promise.race([thirdLoadPromise, getTimeoutPromise(t, 'third resource')]); 136 }, "<link> load event fires for each DIFFERENT stylesheet it loads"); 137 </script> 138 </head> 139 </html>