// `integrityValue` indicates the 'integrity' attribute value at the time of // #prepare-a-script. // // `integrityValueAfterPrepare` indicates how the 'integrity' attribute value // is modified after #prepare-a-script: // - `undefined` => not modified. // - `null` => 'integrity' attribute is removed. // - others => 'integrity' attribute value is set to that value. // // TODO: Make the arguments a dictionary for readability in the test files. var SRIScriptTest = function(pass, name, src, integrityValue, crossoriginValue, nonce, integrityValueAfterPrepare) { this.pass = pass; this.name = "Script: " + name; this.src = src; this.integrityValue = integrityValue; this.crossoriginValue = crossoriginValue; this.nonce = nonce; this.integrityValueAfterPrepare = integrityValueAfterPrepare; } SRIScriptTest.prototype.execute = function() { var test = async_test(this.name); var e = document.createElement("script"); e.src = this.src; if (this.integrityValue) { e.setAttribute("integrity", this.integrityValue); } if(this.crossoriginValue) { e.setAttribute("crossorigin", this.crossoriginValue); } if(this.nonce) { e.setAttribute("nonce", this.nonce); } if(this.pass) { e.addEventListener("load", function() {test.done()}); e.addEventListener("error", function() { test.step(function(){ assert_unreached("Good load fired error handler.") }) }); } else { e.addEventListener("load", function() { test.step(function() { assert_unreached("Bad load succeeded.") }) }); e.addEventListener("error", function() {test.done()}); } document.body.appendChild(e); if (this.integrityValueAfterPrepare === null) { e.removeAttribute("integrity"); } else if (this.integrityValueAfterPrepare !== undefined) { e.setAttribute("integrity", this.integrityValueAfterPrepare); } }; function set_extra_attributes(element, attrs) { // Apply the rest of the attributes, if any. for (const [attr_name, attr_val] of Object.entries(attrs)) { element[attr_name] = attr_val; } } function buildElementFromDestination(resource_url, destination, attrs) { // Assert: |destination| is a valid destination. let element; // The below switch is responsible for: // 1. Creating the correct subresource element // 2. Setting said element's href, src, or fetch-instigating property // appropriately. switch (destination) { case "script": element = document.createElement(destination); set_extra_attributes(element, attrs); element.src = resource_url; break; case "style": element = document.createElement('link'); set_extra_attributes(element, attrs); element.rel = 'stylesheet'; element.href = resource_url; break; case "image": element = document.createElement('img'); set_extra_attributes(element, attrs); element.src = resource_url; break; default: assert_unreached("INVALID DESTINATION"); } return element; } // When using SRIPreloadTest, also include /preload/resources/preload_helper.js // |number_of_requests| is used to ensure that preload requests are actually // reused as expected. const SRIPreloadTest = (preload_sri_success, subresource_sri_success, name, number_of_requests, destination, resource_url, link_attrs, subresource_attrs) => { const test = async_test(name); const link = document.createElement('link'); // Early-fail in UAs that do not support `preload` links. test.step_func(() => { assert_true(link.relList.supports('preload'), "This test is automatically failing because the browser does not" + "support `preload` links."); })(); // Build up the link. link.rel = 'preload'; link.as = destination; link.href = resource_url; for (const [attr_name, attr_val] of Object.entries(link_attrs)) { link[attr_name] = attr_val; // This may override `rel` to modulepreload. } // Preload + subresource success and failure loading functions. const valid_preload_failed = test.step_func(() => { assert_unreached("Valid preload fired error handler.") }); const invalid_preload_succeeded = test.step_func(() => { assert_unreached("Invalid preload load succeeded.") }); const valid_subresource_failed = test.step_func(() => { assert_unreached("Valid subresource fired error handler.") }); const invalid_subresource_succeeded = test.step_func(() => { assert_unreached("Invalid subresource load succeeded.") }); const subresource_pass = test.step_func(() => { verifyNumberOfResourceTimingEntries(resource_url, number_of_requests); test.done(); }); const preload_pass = test.step_func(() => { const subresource_element = buildElementFromDestination( resource_url, destination, subresource_attrs ); if (subresource_sri_success) { subresource_element.onload = subresource_pass; subresource_element.onerror = valid_subresource_failed; } else { subresource_element.onload = invalid_subresource_succeeded; subresource_element.onerror = subresource_pass; } document.body.append(subresource_element); }); if (preload_sri_success) { link.onload = preload_pass; link.onerror = valid_preload_failed; } else { link.onload = invalid_preload_succeeded; link.onerror = preload_pass; } document.head.append(link); } // tests // Style tests must be done synchronously because they rely on the presence // and absence of global style, which can affect later tests. Thus, instead // of executing them one at a time, the style tests are implemented as a // queue that builds up a list of tests, and then executes them one at a // time. var SRIStyleTest = function(queue, pass, name, attrs, customCallback, altPassValue) { this.pass = pass; this.name = "Style: " + name; this.customCallback = customCallback || function () {}; this.attrs = attrs || {}; this.passValue = altPassValue || "rgb(255, 255, 0)"; this.test = async_test(this.name); this.queue = queue; this.queue.push(this); } SRIStyleTest.prototype.execute = function() { var that = this; var container = document.getElementById("container"); while (container.hasChildNodes()) { container.removeChild(container.firstChild); } var test = this.test; var div = document.createElement("div"); div.className = "testdiv"; var e = document.createElement("link"); // The link relation is guaranteed to not be "preload" or "modulepreload". this.attrs.rel = this.attrs.rel || "stylesheet"; for (var key in this.attrs) { if (this.attrs.hasOwnProperty(key)) { e.setAttribute(key, this.attrs[key]); } } if(this.pass) { e.addEventListener("load", function() { test.step(function() { var background = window.getComputedStyle(div, null).getPropertyValue("background-color"); assert_equals(background, that.passValue); test.done(); }); }); e.addEventListener("error", function() { test.step(function(){ assert_unreached("Good load fired error handler.") }) }); } else { e.addEventListener("load", function() { test.step(function() { assert_unreached("Bad load succeeded.") }) }); e.addEventListener("error", function() { test.step(function() { var background = window.getComputedStyle(div, null).getPropertyValue("background-color"); assert_not_equals(background, that.passValue); test.done(); }); }); } container.appendChild(div); container.appendChild(e); this.customCallback(e, container); };