dangling-markup-mitigation-data-url.tentative.sub.html (8781B)
1 <!DOCTYPE html> 2 <script src="/resources/testharness.js"></script> 3 <script src="/resources/testharnessreport.js"></script> 4 <body> 5 <script> 6 function readableURL(url) { 7 return url.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"); 8 } 9 10 // For each of the following tests, we'll inject a frame containing the HTML we'd like to poke at 11 // as a `srcdoc` attribute. Because we're injecting markup via `srcdoc`, we need to entity-escape 12 // the content we'd like to treat as "raw" (e.g. `\n` => ` `, `<` => `<`), and 13 // double-escape the "escaped" content. 14 var rawBrace = "<"; 15 var escapedBrace = "&lt;"; 16 var doubleEscapedBrace = "&amp;lt;"; 17 var rawNewline = " "; 18 var escapedNewline = "&#10;"; 19 // doubleEscapedNewline is used inside a data URI, and so must have its '#' escaped. 20 var doubleEscapedNewline = "&amp;%2310;"; 21 22 function appendFrameAndGetElement(test, frame) { 23 return new Promise((resolve, reject) => { 24 frame.onload = test.step_func(_ => { 25 frame.onload = null; 26 resolve(frame.contentDocument.querySelector('#dangling')); 27 }); 28 document.body.appendChild(frame); 29 }); 30 } 31 32 function assert_img_loaded(test, frame) { 33 appendFrameAndGetElement(test, frame) 34 .then(test.step_func_done(img => { 35 assert_equals(img.naturalHeight, 1, "Height"); 36 frame.remove(); 37 })); 38 } 39 40 function assert_img_not_loaded(test, frame) { 41 appendFrameAndGetElement(test, frame) 42 .then(test.step_func_done(img => { 43 assert_equals(img.naturalHeight, 0, "Height"); 44 assert_equals(img.naturalWidth, 0, "Width"); 45 })); 46 } 47 48 function assert_nested_img_not_loaded(test, frame) { 49 window.addEventListener('message', test.step_func(e => { 50 if (e.source != frame.contentWindow) 51 return; 52 53 assert_equals(e.data, 'error'); 54 test.done(); 55 })); 56 appendFrameAndGetElement(test, frame); 57 } 58 59 function assert_nested_img_loaded(test, frame) { 60 window.addEventListener('message', test.step_func(e => { 61 if (e.source != frame.contentWindow) 62 return; 63 64 assert_equals(e.data, 'loaded'); 65 test.done(); 66 })); 67 appendFrameAndGetElement(test, frame); 68 } 69 70 function createFrame(markup) { 71 var i = document.createElement('iframe'); 72 i.srcdoc = `${markup}sekrit`; 73 return i; 74 } 75 76 // Subresource requests: 77 [ 78 // Data URLs don't themselves trigger blocking: 79 `<img id="dangling" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`, 80 `<img id="dangling" src="data:image/png;base64,${rawNewline}iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`, 81 `<img id="dangling" src="data:image/png;base64,i${rawNewline}VBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`, 82 83 // Data URLs with visual structure don't trigger blocking 84 `<img id="dangling" src="data:image/svg+xml;utf8, 85 <svg width='1' height='1' xmlns='http://www.w3.org/2000/svg'> 86 <rect width='100%' height='100%' fill='rebeccapurple'/> 87 <rect x='10%' y='10%' width='80%' height='80%' fill='lightgreen'/> 88 </svg>">` 89 ].forEach(markup => { 90 async_test(t => { 91 var i = createFrame(`${markup} <element attr="" another=''>`); 92 assert_img_loaded(t, i); 93 }, readableURL(markup)); 94 }); 95 96 // Nested subresource requests: 97 // 98 // The following tests load a given HTML string into `<iframe srcdoc="...">`, so we'll 99 // end up with a frame with an ID of `dangling` inside the srcdoc frame. That frame's 100 // `src` is a `data:` URL that resolves to an HTML document containing an `<img>`. The 101 // error/load handlers on that image are piped back up to the top-level document to 102 // determine whether the tests' expectations were met. *phew* 103 104 // Allowed: 105 [ 106 // Just a newline: 107 `<iframe id="dangling" 108 src="data:text/html, 109 <img 110 onload='window.parent.postMessage("loaded", "*");' 111 onerror='window.parent.postMessage("error", "*");' 112 src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png'> 113 "> 114 </iframe>`, 115 116 // Just a brace: 117 `<iframe id="dangling" 118 src="data:text/html, 119 <img 120 onload='window.parent.postMessage("loaded", "*");' 121 onerror='window.parent.postMessage("error", "*");' 122 src='http://{{host}}:{{ports[http][0]}}/images/green-256x256.png?${rawBrace}'> 123 "> 124 </iframe>`, 125 126 // Newline and escaped brace. 127 `<iframe id="dangling" 128 src="data:text/html, 129 <img 130 onload='window.parent.postMessage("loaded", "*");' 131 onerror='window.parent.postMessage("error", "*");' 132 src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${doubleEscapedBrace}'> 133 "> 134 </iframe>`, 135 136 // Brace and escaped newline: 137 `<iframe id="dangling" 138 src="data:text/html, 139 <img 140 onload='window.parent.postMessage("loaded", "*");' 141 onerror='window.parent.postMessage("error", "*");' 142 src='http://{{host}}:{{ports[http][0]}}/images/green-256x256.png?${doubleEscapedNewline}${rawBrace}'> 143 "> 144 </iframe>`, 145 ].forEach(markup => { 146 async_test(t => { 147 var i = createFrame(` 148 <script> 149 // Repeat the message so that the parent can track this frame as the source. 150 window.onmessage = e => window.parent.postMessage(e.data, '*'); 151 </scr`+`ipt> 152 ${markup} 153 `); 154 assert_nested_img_loaded(t, i); 155 }, readableURL(markup)); 156 }); 157 158 // Nested requests that should fail: 159 [ 160 // Newline and brace: 161 `<iframe id="dangling" 162 src="data:text/html, 163 <img 164 onload='window.parent.postMessage("loaded", "*");' 165 onerror='window.parent.postMessage("error", "*");' 166 src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> 167 "> 168 </iframe>`, 169 170 // Leading whitespace: 171 `<iframe id="dangling" 172 src=" data:text/html, 173 <img 174 onload='window.parent.postMessage("loaded", "*");' 175 onerror='window.parent.postMessage("error", "*");' 176 src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> 177 "> 178 </iframe>`, 179 180 // Leading newline: 181 `<iframe id="dangling" 182 src="\ndata:text/html, 183 <img 184 onload='window.parent.postMessage("loaded", "*");' 185 onerror='window.parent.postMessage("error", "*");' 186 src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> 187 "> 188 </iframe>`, 189 `<iframe id="dangling" 190 src="${rawNewline}data:text/html, 191 <img 192 onload='window.parent.postMessage("loaded", "*");' 193 onerror='window.parent.postMessage("error", "*");' 194 src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> 195 "> 196 </iframe>`, 197 198 // Leading tab: 199 `<iframe id="dangling" 200 src="\tdata:text/html, 201 <img 202 onload='window.parent.postMessage("loaded", "*");' 203 onerror='window.parent.postMessage("error", "*");' 204 src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> 205 "> 206 </iframe>`, 207 208 // Leading carrige return: 209 `<iframe id="dangling" 210 src="\rdata:text/html, 211 <img 212 onload='window.parent.postMessage("loaded", "*");' 213 onerror='window.parent.postMessage("error", "*");' 214 src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> 215 "> 216 </iframe>`, 217 ].forEach(markup => { 218 async_test(t => { 219 var i = createFrame(` 220 <script> 221 // Repeat the message so that the parent can track this frame as the source. 222 window.onmessage = e => window.parent.postMessage(e.data, '*'); 223 </scr`+`ipt> 224 ${markup} 225 `); 226 assert_nested_img_not_loaded(t, i); 227 }, readableURL(markup)); 228 }); 229 </script>