Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Empty file.
Empty file.
5 changes: 5 additions & 0 deletions test/fixtures/wpt/common/get-host-info.sub.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,20 @@ function get_host_info() {
var REMOTE_HOST = (ORIGINAL_HOST === 'localhost') ? '127.0.0.1' : ('www1.' + ORIGINAL_HOST);
var OTHER_HOST = '{{domains[www2]}}';
var NOTSAMESITE_HOST = (ORIGINAL_HOST === 'localhost') ? '127.0.0.1' : ('{{hosts[alt][]}}');
var OTHER_NOTSAMESITE_HOST = '{{hosts[alt][www2]}}';

return {
HTTP_PORT: HTTP_PORT,
HTTP_PORT2: HTTP_PORT2,
HTTPS_PORT: HTTPS_PORT,
HTTPS_PORT2: HTTPS_PORT2,
HTTP_PORT_ELIDED: HTTP_PORT_ELIDED,
HTTPS_PORT_ELIDED: HTTPS_PORT_ELIDED,
PORT: PORT,
PORT2: PORT2,
ORIGINAL_HOST: ORIGINAL_HOST,
REMOTE_HOST: REMOTE_HOST,
NOTSAMESITE_HOST,

ORIGIN: PROTOCOL + "//" + ORIGINAL_HOST + PORT_ELIDED,
HTTP_ORIGIN: 'http://' + ORIGINAL_HOST + HTTP_PORT_ELIDED,
Expand All @@ -44,6 +48,7 @@ function get_host_info() {
HTTPS_REMOTE_ORIGIN: 'https://' + REMOTE_HOST + HTTPS_PORT_ELIDED,
HTTPS_REMOTE_ORIGIN_WITH_CREDS: 'https://foo:bar@' + REMOTE_HOST + HTTPS_PORT_ELIDED,
HTTPS_NOTSAMESITE_ORIGIN: 'https://' + NOTSAMESITE_HOST + HTTPS_PORT_ELIDED,
HTTPS_OTHER_NOTSAMESITE_ORIGIN: 'https://' + OTHER_NOTSAMESITE_HOST + HTTPS_PORT_ELIDED,
UNAUTHENTICATED_ORIGIN: 'http://' + OTHER_HOST + HTTP_PORT_ELIDED,
AUTHENTICATED_ORIGIN: 'https://' + OTHER_HOST + HTTPS_PORT_ELIDED
};
Expand Down
139 changes: 139 additions & 0 deletions test/fixtures/wpt/common/reftest-wait.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function takeScreenshotDelayed(timeout) {
* Ensure that a precondition is met before waiting for a screenshot.
* @param {bool} condition - Fail the test if this evaluates to false
* @param {string} msg - Error message to write to the screenshot
* @returns {bool} True if the condition passed, false if it failed
*/
function failIfNot(condition, msg) {
const fail = () => {
Expand All @@ -35,5 +36,143 @@ function failIfNot(condition, msg) {
} else {
document.addEventListener("DOMContentLoaded", fail, false);
}
return false;
}
return true;
}

/**
* Display the failure reason and stop waiting for a screenshot.
*
* Does nothing for a mismatch-only reftest (no `match` reference is also
* present), since an error page necessarily differs from the reference and
* would spuriously pass instead of correctly timing out.
* @param {Error|ErrorEvent|PromiseRejectionEvent|*} error - The error that caused the failure.
*/
function failOnError(error) {
// Only <link>s explicitly in the XHTML namespace count as match/mismatch
// links, matching the manifest's own namespace-qualified lookup.
const links = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "link");
let hasMatch = false;
let hasMismatch = false;
for (const link of links) {
const rel = link.getAttribute("rel");
if (rel === "match") {
hasMatch = true;
} else if (rel === "mismatch") {
hasMismatch = true;
}
}
if (hasMismatch && !hasMatch) {
return;
}

let message;
if (typeof PromiseRejectionEvent !== "undefined" && error instanceof PromiseRejectionEvent) {
if (error.reason && error.reason.message) {
message = "Unhandled rejection: " + error.reason.message;
} else {
message = "Unhandled rejection";
}
} else {
if (error.message) {
message = "Uncaught exception: " + error.message;
} else {
message = "Uncaught exception";
}
}

const node = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
node.textContent = message;

if (document.body) {
document.body.insertBefore(node, document.body.firstChild);
} else {
const root = document.documentElement;
const is_html = (root &&
root.namespaceURI === "http://www.w3.org/1999/xhtml" &&
root.localName === "html");
const is_svg = ("SVGSVGElement" in self && root instanceof SVGSVGElement);
if (is_svg) {
const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
foreignObject.setAttribute("width", "100%");
foreignObject.setAttribute("height", "100%");
root.insertBefore(foreignObject, root.firstChild);
foreignObject.appendChild(node);
} else if (is_html) {
root.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "body"))
.appendChild(node);
} else {
root.insertBefore(node, root.firstChild);
}
}

takeScreenshot();
}

/**
* Wrap `func` so a synchronous exception it throws is reported via
* failOnError() instead of propagating as an uncaught error. For use
* wrapping a callback, e.g. an event listener.
* @param {Function} func - Callback to wrap.
* @returns {Function} The wrapped callback.
*/
function reftestStep(func) {
return function(...args) {
try {
return func.apply(this, args);
} catch (e) {
failOnError(e);
}
};
}

/**
* Call `func` immediately, reporting via failOnError() either a
* synchronous exception it throws or an asynchronous rejection of the
* promise it returns.
* @param {Function} func - Function to call, typically async.
*/
function reftestPromise(func) {
const result = reftestStep(func)();
Promise.resolve(result).catch(failOnError);
}

/**
* Once a text track cue becomes active, pause the video, wait
* for layout to update, then call takeScreenshot().
*/
function waitForActiveCueAndTakeScreenshot() {
var videoElement = document.querySelector("video");
var trackElement = document.querySelector("track");

if (!failIfNot(videoElement, "Video element not found"))
return;

if (!failIfNot(trackElement, "Track element not found"))
return;

var textTrack = trackElement.track;

function pauseVideoAndTakeScreenshot() {
if (videoElement.paused)
requestAnimationFrame(() => takeScreenshot());
else {
videoElement.addEventListener("pause", function() {
requestAnimationFrame(() => takeScreenshot());
});
videoElement.pause();
}
}

textTrack.oncuechange = function() {
if (textTrack.activeCues && textTrack.activeCues.length) {
textTrack.oncuechange = null;
pauseVideoAndTakeScreenshot();
}
};

if (textTrack.activeCues && textTrack.activeCues.length)
pauseVideoAndTakeScreenshot();
}

91 changes: 79 additions & 12 deletions test/fixtures/wpt/common/security-features/resources/common.sub.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ function setAttributes(el, attrs) {
attrs = attrs || {}
for (var attr in attrs) {
if (attr !== 'src')
el.setAttribute(attr, attrs[attr]);
el.setAttribute(attr.toLowerCase(), attrs[attr]);
}
// Workaround for Chromium: set <img>'s src attribute after all other
// attributes to ensure the policy is applied.
Expand Down Expand Up @@ -826,6 +826,54 @@ function requestViaWebSocket(url) {
});
}

/**
* Creates a svg anchor element and the corresponding svg setup, appends the
* setup to {@code document.body} and performs the navigation.
* @param {string} url The URL to navigate to.
* @return {Promise} The promise for success/error events.
*/
function requestViaSVGAnchor(url, additionalAttributes) {
const name = guid();

const iframe =
createElement("iframe", { "name": name, "id": name }, document.body, false);

// Create SVG container
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");

// Create SVG anchor element
const svgAnchor = document.createElementNS("http://www.w3.org/2000/svg", "a");
const link_attributes = Object.assign({ "href": url, "target": name }, additionalAttributes);
setAttributes(svgAnchor, link_attributes);

// Add some text content for the anchor
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("y", "50");
text.textContent = "SVG Link to resource";

svgAnchor.appendChild(text);
svg.appendChild(svgAnchor);
document.body.appendChild(svg);

const promise =
bindEvents2(window, "message", iframe, "error", window, "error")
.then(event => {
if (event.source !== iframe.contentWindow)
return Promise.reject(new Error('Unexpected event.source'));
return event.data;
});

// Simulate a click event on the SVG anchor
const event = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
svgAnchor.dispatchEvent(event);

return promise;
}

/**
@typedef SubresourceType
@type {string}
Expand Down Expand Up @@ -892,6 +940,10 @@ const subresourceMap = {
path: "/common/security-features/subresource/script.py",
invoker: requestViaDynamicImport,
},
"svg-a-tag": {
path: "/common/security-features/subresource/document.py",
invoker: requestViaSVGAnchor,
},
"video-tag": {
path: "/common/security-features/subresource/video.py",
invoker: requestViaVideo,
Expand Down Expand Up @@ -1009,13 +1061,12 @@ function getSubresourceOrigin(originType) {
"cross-wss": wssProtocol + "://" + crossOriginHost + wssPort,
"cross-ws": wsProtocol + "://" + crossOriginHost + wsPort,

// The following origin types are used for upgrade-insecure-requests tests:
// These rely on some unintuitive cleverness due to WPT's test setup:
// 'Upgrade-Insecure-Requests' does not upgrade the port number,
// so we use URLs in the form `http://[domain]:[https-port]`,
// which will be upgraded to `https://[domain]:[https-port]`.
// If the upgrade fails, the load will fail, as we don't serve HTTP over
// the secure port.
// The following origin types are used for upgrade-insecure-requests and
// mixed content tests tests. These rely on some unintuitive cleverness due
// to WPT's test setup: 'Upgrade-Insecure-Requests' does not upgrade the
// port number, so we use URLs in the form `http://[domain]:[https-port]`,
// which will be upgraded to `https://[domain]:[https-port]`. If the upgrade
// fails, the load will fail, as we don't serve HTTP over the secure port.
"same-http-downgrade":
httpProtocol + "://" + sameOriginHost + ":" + httpsRawPort,
"cross-http-downgrade":
Expand All @@ -1035,6 +1086,7 @@ function getSubresourceOrigin(originType) {
@param {SubresourceType} subresourceType
@param {OriginType} originType
@param {RedirectionType} redirectionType
@param {boolean} checkScheme Optional
@returns {object} with following properties:
{string} testUrl
The subresource request URL.
Expand All @@ -1045,10 +1097,12 @@ function getSubresourceOrigin(originType) {
1. Fetch `announceUrl` first,
2. then possibly fetch `testUrl`, and
3. finally fetch `assertUrl`.
The fetch result of `assertUrl` should indicate whether
`testUrl` is actually sent to the server or not.
The fetch result of `assertUrl` should indicate whether `testUrl` is
actually sent to the server or not, or if `checkScheme` is specified
and this is a insecure origin type, whether the request was upgraded or
not.
*/
function getRequestURLs(subresourceType, originType, redirectionType) {
function getRequestURLs(subresourceType, originType, redirectionType, checkScheme = false) {
const key = guid();
const value = guid();

Expand All @@ -1062,7 +1116,8 @@ function getRequestURLs(subresourceType, originType, redirectionType) {
getSubresourceOrigin(originType) +
subresourceMap[subresourceType].path +
"?redirection=" + encodeURIComponent(redirectionType) +
"&action=purge&key=" + key +
"&action=" + (checkScheme && !["https", "wss"].includes(originType.split("-").pop()) ? "check-scheme" : "purge") +
"&key=" + key +
"&path=" + stashPath,
announceUrl: stashEndpoint + "&action=put&value=" + value,
assertUrl: stashEndpoint + "&action=take",
Expand Down Expand Up @@ -1139,6 +1194,9 @@ function invokeRequest(subresource, sourceContextList) {
"iframe-blank": { // <iframe></iframe>
invoker: invokeFromIframe,
},
"iframe-data": {
invoker: invokeFromIframe,
},
"worker-classic": {
// Classic dedicated worker loaded from same-origin.
invoker: invokeFromWorker.bind(undefined, "worker", false, {}),
Expand Down Expand Up @@ -1284,6 +1342,15 @@ function invokeFromIframe(subresource, sourceContextList) {
iframe.contentDocument.close();
return iframe.eventPromise;
});
} else if (currentSourceContext.sourceContextType === 'iframe-data') {
promise = fetch(frameUrl)
.then(r => r.text())
.then(content => {
let dataSrc = "data:text/html;base64," + btoa(content)
iframe = createElement(
"iframe", {src: dataSrc}, document.body, true);
return iframe.eventPromise;
});
}

return promise
Expand Down
3 changes: 2 additions & 1 deletion test/fixtures/wpt/common/security-features/scope/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def main(request, response):

handler = lambda: util.get_template(u"document.html.template") % ({
u"meta": meta,
u"error": error
u"error": error,
u"scriptOrigin": request.url_parts.scheme + u'://' + request.url_parts.hostname + u':' + str(request.url_parts.port)
})
util.respond(
request,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
<html>
<head>
%(meta)s
<script src="/common/security-features/resources/common.sub.js"></script>
<script src="%(scriptOrigin)s/common/security-features/resources/common.sub.js"></script>
<script src="%(scriptOrigin)s/resources/testharness.js"></script>
<script src="%(scriptOrigin)s/resources/testharnessreport.js"></script>
<script>
// Receive a message from the parent and start the test.
function onMessageFromParent(event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ def create_url(request,
if swap_scheme:
scheme = u"http" if parsed.scheme == u"https" else u"https"
hostname = parsed.netloc.split(u':')[0]
port = request.server.config[u"ports"][scheme][0]
# Always use the HTTPS port to allow testing upgrades correctly, similar
# to the downgrade case below.
port = request.server.config[u"ports"]["https"][0]
destination_netloc = u":".join([hostname, str(port)])

if downgrade:
Expand Down Expand Up @@ -132,9 +134,17 @@ def preprocess_stash_action(request, response):
elif action == b"purge":
value = stash.take(key=key, path=path)
return False
elif action == b"check-scheme":
scheme = urlsplit(request.url).scheme
if scheme in [u"https", u"wss"]:
stash.take(key=key, path=path)
stash.put(key=key, value=u"upgraded", path=path)
return False
elif action == b"take":
value = stash.take(key=key, path=path)
if value is None:
if value == u"upgraded":
status = u"upgraded"
elif value is None:
status = u"allowed"
else:
status = u"blocked"
Expand Down
Loading