1// `integrityValue` indicates the 'integrity' attribute value at the time of 2// #prepare-a-script. 3// 4// `integrityValueAfterPrepare` indicates how the 'integrity' attribute value 5// is modified after #prepare-a-script: 6// - `undefined` => not modified. 7// - `null` => 'integrity' attribute is removed. 8// - others => 'integrity' attribute value is set to that value. 9// 10// TODO: Make the arguments a dictionary for readability in the test files. 11var SRIScriptTest = function(pass, name, src, integrityValue, crossoriginValue, nonce, integrityValueAfterPrepare) { 12 this.pass = pass; 13 this.name = "Script: " + name; 14 this.src = src; 15 this.integrityValue = integrityValue; 16 this.crossoriginValue = crossoriginValue; 17 this.nonce = nonce; 18 this.integrityValueAfterPrepare = integrityValueAfterPrepare; 19} 20 21SRIScriptTest.prototype.execute = function() { 22 var test = async_test(this.name); 23 var e = document.createElement("script"); 24 e.src = this.src; 25 if (this.integrityValue) { 26 e.setAttribute("integrity", this.integrityValue); 27 } 28 if(this.crossoriginValue) { 29 e.setAttribute("crossorigin", this.crossoriginValue); 30 } 31 if(this.nonce) { 32 e.setAttribute("nonce", this.nonce); 33 } 34 if(this.pass) { 35 e.addEventListener("load", function() {test.done()}); 36 e.addEventListener("error", function() { 37 test.step(function(){ assert_unreached("Good load fired error handler.") }) 38 }); 39 } else { 40 e.addEventListener("load", function() { 41 test.step(function() { assert_unreached("Bad load succeeded.") }) 42 }); 43 e.addEventListener("error", function() {test.done()}); 44 } 45 document.body.appendChild(e); 46 47 if (this.integrityValueAfterPrepare === null) { 48 e.removeAttribute("integrity"); 49 } else if (this.integrityValueAfterPrepare !== undefined) { 50 e.setAttribute("integrity", this.integrityValueAfterPrepare); 51 } 52}; 53 54function set_extra_attributes(element, attrs) { 55 // Apply the rest of the attributes, if any. 56 for (const [attr_name, attr_val] of Object.entries(attrs)) { 57 element[attr_name] = attr_val; 58 } 59} 60 61function buildElementFromDestination(resource_url, destination, attrs) { 62 // Assert: |destination| is a valid destination. 63 let element; 64 65 // The below switch is responsible for: 66 // 1. Creating the correct subresource element 67 // 2. Setting said element's href, src, or fetch-instigating property 68 // appropriately. 69 switch (destination) { 70 case "script": 71 element = document.createElement(destination); 72 set_extra_attributes(element, attrs); 73 element.src = resource_url; 74 break; 75 case "style": 76 element = document.createElement('link'); 77 set_extra_attributes(element, attrs); 78 element.rel = 'stylesheet'; 79 element.href = resource_url; 80 break; 81 case "image": 82 element = document.createElement('img'); 83 set_extra_attributes(element, attrs); 84 element.src = resource_url; 85 break; 86 default: 87 assert_unreached("INVALID DESTINATION"); 88 } 89 90 return element; 91} 92 93// When using SRIPreloadTest, also include /preload/resources/preload_helper.js 94// |number_of_requests| is used to ensure that preload requests are actually 95// reused as expected. 96const SRIPreloadTest = (preload_sri_success, subresource_sri_success, name, 97 number_of_requests, destination, resource_url, 98 link_attrs, subresource_attrs) => { 99 const test = async_test(name); 100 const link = document.createElement('link'); 101 102 // Early-fail in UAs that do not support `preload` links. 103 test.step_func(() => { 104 assert_true(link.relList.supports('preload'), 105 "This test is automatically failing because the browser does not" + 106 "support `preload` links."); 107 })(); 108 109 // Build up the link. 110 link.rel = 'preload'; 111 link.as = destination; 112 link.href = resource_url; 113 for (const [attr_name, attr_val] of Object.entries(link_attrs)) { 114 link[attr_name] = attr_val; // This may override `rel` to modulepreload. 115 } 116 117 // Preload + subresource success and failure loading functions. 118 const valid_preload_failed = test.step_func(() => 119 { assert_unreached("Valid preload fired error handler.") }); 120 const invalid_preload_succeeded = test.step_func(() => 121 { assert_unreached("Invalid preload load succeeded.") }); 122 const valid_subresource_failed = test.step_func(() => 123 { assert_unreached("Valid subresource fired error handler.") }); 124 const invalid_subresource_succeeded = test.step_func(() => 125 { assert_unreached("Invalid subresource load succeeded.") }); 126 const subresource_pass = test.step_func(() => { 127 verifyNumberOfResourceTimingEntries(resource_url, number_of_requests); 128 test.done(); 129 }); 130 const preload_pass = test.step_func(() => { 131 const subresource_element = buildElementFromDestination( 132 resource_url, 133 destination, 134 subresource_attrs 135 ); 136 137 if (subresource_sri_success) { 138 subresource_element.onload = subresource_pass; 139 subresource_element.onerror = valid_subresource_failed; 140 } else { 141 subresource_element.onload = invalid_subresource_succeeded; 142 subresource_element.onerror = subresource_pass; 143 } 144 145 document.body.append(subresource_element); 146 }); 147 148 if (preload_sri_success) { 149 link.onload = preload_pass; 150 link.onerror = valid_preload_failed; 151 } else { 152 link.onload = invalid_preload_succeeded; 153 link.onerror = preload_pass; 154 } 155 156 document.head.append(link); 157} 158 159// <link> tests 160// Style tests must be done synchronously because they rely on the presence 161// and absence of global style, which can affect later tests. Thus, instead 162// of executing them one at a time, the style tests are implemented as a 163// queue that builds up a list of tests, and then executes them one at a 164// time. 165var SRIStyleTest = function(queue, pass, name, attrs, customCallback, altPassValue) { 166 this.pass = pass; 167 this.name = "Style: " + name; 168 this.customCallback = customCallback || function () {}; 169 this.attrs = attrs || {}; 170 this.passValue = altPassValue || "rgb(255, 255, 0)"; 171 172 this.test = async_test(this.name); 173 174 this.queue = queue; 175 this.queue.push(this); 176} 177 178SRIStyleTest.prototype.execute = function() { 179 var that = this; 180 var container = document.getElementById("container"); 181 while (container.hasChildNodes()) { 182 container.removeChild(container.firstChild); 183 } 184 185 var test = this.test; 186 187 var div = document.createElement("div"); 188 div.className = "testdiv"; 189 var e = document.createElement("link"); 190 191 // The link relation is guaranteed to not be "preload" or "modulepreload". 192 this.attrs.rel = this.attrs.rel || "stylesheet"; 193 for (var key in this.attrs) { 194 if (this.attrs.hasOwnProperty(key)) { 195 e.setAttribute(key, this.attrs[key]); 196 } 197 } 198 199 if(this.pass) { 200 e.addEventListener("load", function() { 201 test.step(function() { 202 var background = window.getComputedStyle(div, null).getPropertyValue("background-color"); 203 assert_equals(background, that.passValue); 204 test.done(); 205 }); 206 }); 207 e.addEventListener("error", function() { 208 test.step(function(){ assert_unreached("Good load fired error handler.") }) 209 }); 210 } else { 211 e.addEventListener("load", function() { 212 test.step(function() { assert_unreached("Bad load succeeded.") }) 213 }); 214 e.addEventListener("error", function() { 215 test.step(function() { 216 var background = window.getComputedStyle(div, null).getPropertyValue("background-color"); 217 assert_not_equals(background, that.passValue); 218 test.done(); 219 }); 220 }); 221 } 222 container.appendChild(div); 223 container.appendChild(e); 224 this.customCallback(e, container); 225}; 226 227