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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 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,12 +20,15 @@ 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,
Expand All @@ -45,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
3 changes: 3 additions & 0 deletions test/fixtures/wpt/eventsource/WEB_FEATURES.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
features:
- name: server-sent-events
files: "**"
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// list of bad ports according to
// https://fetch.spec.whatwg.org/#port-blocking
var BLOCKED_PORTS_LIST = [
0,
1, // tcpmux
7, // echo
9, // discard
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/wpt/fetch/fetch-later/META.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
spec: https://whatpr.org/fetch/1647/094ea69...152d725.html#fetch-later-method
spec: https://fetch.spec.whatwg.org/#dom-window-fetchlater
suggested_reviewers:
- mingyc
4 changes: 2 additions & 2 deletions test/fixtures/wpt/fetch/fetch-later/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# FetchLater Tests
# `fetchLater()` Tests

These tests cover [FetchLater method](https://whatpr.org/fetch/1647.html#dom-window-fetchlater) related behaviors.
These tests cover [`fetchLater()` method](https://fetch.spec.whatwg.org/#dom-window-fetchlater) related behaviors.
124 changes: 124 additions & 0 deletions test/fixtures/wpt/fetch/fetch-later/basic.https.window.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
'use strict';

test(() => {
assert_throws_js(TypeError, () => fetchLater());
}, `fetchLater() cannot be called without request.`);

test(() => {
const result = fetchLater('/');
assert_false(result.activated, `result.activated should be false for '/'`);
}, `fetchLater() with same-origin (https) URL does not throw.`);

test(() => {
const url = 'http://localhost';
const result = fetchLater(url);
assert_false(result.activated, `result.activated should be false for ${url}`);
}, `fetchLater() with http://localhost URL does not throw.`);

test(() => {
const url = 'https://localhost';
const result = fetchLater(url);
assert_false(result.activated, `result.activated should be false for ${url}`);
}, `fetchLater() with https://localhost URL does not throw.`);

test(() => {
const url = 'http://127.0.0.1';
const result = fetchLater(url);
assert_false(result.activated, `result.activated should be false for ${url}`);
}, `fetchLater() with http://127.0.0.1 URL does not throw.`);

test(() => {
const url = 'https://127.0.0.1';
const result = fetchLater(url);
assert_false(result.activated, `result.activated should be false for ${url}`);
}, `fetchLater() with https://127.0.0.1 URL does not throw.`);

test(() => {
const url = 'http://[::1]';
const result = fetchLater(url);
assert_false(result.activated, `result.activated should be false for ${url}`);
}, `fetchLater() with http://[::1] URL does not throw.`);

test(() => {
const url = 'https://[::1]';
const result = fetchLater(url);
assert_false(result.activated, `result.activated should be false for ${url}`);
}, `fetchLater() with https://[::1] URL does not throw.`);

test(() => {
const url = 'https://example.com';
const result = fetchLater(url);
assert_false(result.activated, `result.activated should be false for ${url}`);
}, `fetchLater() with https://example.com URL does not throw.`);

test(() => {
const httpUrl = 'http://example.com';
assert_throws_dom(
'SecurityError', () => fetchLater(httpUrl),
`should throw SecurityError for insecure http url ${httpUrl}`);
}, `fetchLater() throws SecurityError on non-trustworthy http URL.`);

test(() => {
assert_throws_js(TypeError, () => fetchLater('file://tmp'));
}, `fetchLater() throws TypeError on file:// scheme.`);

test(() => {
assert_throws_js(TypeError, () => fetchLater('ftp://example.com'));
}, `fetchLater() throws TypeError on ftp:// scheme.`);

test(() => {
assert_throws_js(TypeError, () => fetchLater('ssh://example.com'));
}, `fetchLater() throws TypeError on ssh:// scheme.`);

test(() => {
assert_throws_js(TypeError, () => fetchLater('wss://example.com'));
}, `fetchLater() throws TypeError on wss:// scheme.`);

test(() => {
assert_throws_js(TypeError, () => fetchLater('about:blank'));
}, `fetchLater() throws TypeError on about: scheme.`);

test(() => {
assert_throws_js(TypeError, () => fetchLater(`javascript:alert('');`));
}, `fetchLater() throws TypeError on javascript: scheme.`);

test(() => {
assert_throws_js(TypeError, () => fetchLater('data:text/plain,Hello'));
}, `fetchLater() throws TypeError on data: scheme.`);

test(() => {
assert_throws_js(
TypeError, () => fetchLater('blob:https://example.com/some-uuid'));
}, `fetchLater() throws TypeError on blob: scheme.`);

test(() => {
assert_throws_js(
RangeError,
() => fetchLater('https://www.google.com', {activateAfter: -1}));
}, `fetchLater() throws RangeError on negative activateAfter.`);

test(() => {
const result = fetchLater('/');
assert_false(result.activated);
}, `fetchLater()'s return tells the deferred request is not yet sent.`);

test(() => {
const result = fetchLater('/');
assert_throws_js(TypeError, () => result.activated = true);
}, `fetchLater() throws TypeError when mutating its returned state.`);

test(() => {
const controller = new AbortController();
// Immediately aborts the controller.
controller.abort();
assert_throws_dom(
'AbortError', () => fetchLater('/', {signal: controller.signal}));
}, `fetchLater() throws AbortError when its initial abort signal is aborted.`);

test(() => {
const controller = new AbortController();
const result = fetchLater('/', {signal: controller.signal});
assert_false(result.activated);
controller.abort();
assert_false(result.activated);
}, `fetchLater() does not throw error when it is aborted before sending.`);

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ test(

// Makes the 2nd call (POST) to the same reporting origin that sends
// max bytes, which should be rejected.
assert_throws_dom('QuotaExceededError', () => {
assert_throws_quotaexceedederror(() => {
fetchLater(requestUrl, {
method: 'POST',
signal: controller.signal,
body: makeBeaconData(generatePayload(quota), dataType),
// Required, as the size of referrer also take up quota.
referrer: '',
});
});
}, null, null);

// Makes the 3rd call (GET) to the same reporting origin, where its
// request size is len(requestUrl) + headers, which should be accepted.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/fetch/fetch-later/resources/fetch-later-helper.js
// META: script=/fetch/fetch-later/quota/resources/helper.js
'use strict';

const {HTTPS_ORIGIN, HTTPS_NOTSAMESITE_ORIGIN} = get_host_info();

// Skips FormData & URLSearchParams, as browser adds extra bytes to them
// in addition to the user-provided content. It is difficult to test a
// request right at the quota limit.
// Skips File & Blob as it's difficult to estimate what additional data are
// added into them.
const dataType = BeaconDataType.String;

// Request headers are counted into total request size.
const headers = new Headers({'Content-Type': 'text/plain;charset=UTF-8'});

const requestUrl = `${HTTPS_ORIGIN}/`;
const quota = getRemainingQuota(QUOTA_PER_ORIGIN, requestUrl, headers);
const SMALL_REQUEST_BODY_SIZE = 4 * 1024; // 4KB, well within minimal quota.

// This test validates the correct behavior for a sandboxed iframe without the
// 'allow-same-origin' token.
//
// Such an iframe should be treated as cross-origin, even if its `src` attribute
// points to a same-origin URL. Therefore, it should be allocated its own
// separate "minimal quota" (8KB) for fetchLater() requests and should NOT share
// the parent document's primary quota pool.
//
// The test works by first completely exhausting the parent document's quota.
// Then, it creates the sandboxed iframe and attempts to send a small request
// from it.
//
// The expected result is that the iframe's request SUCCEEDS, because it should
// have its own independent 8KB quota to use.
//
// NOTE: This test will FAIL until the underlying bug is fixed. The bug causes
// the sandboxed iframe to be incorrectly treated as same-origin, making it try
// to use the parent's already-exhausted quota, which leads to a premature
// QuotaExceededError.
promise_test(async test => {
const controller = new AbortController();
test.add_cleanup(() => controller.abort());

// Step 1: Exhaust the parent frame's entire fetchLater() quota.
fetchLater(requestUrl, {
method: 'POST',
signal: controller.signal,
body: makeBeaconData(generatePayload(quota), dataType),
referrer: '', // Referrer is part of the quota, so we control it.
});

// Step 2: Create a sandboxed iframe and attempt a small fetchLater()
// call from it. This should succeed as it should have its own 8KB quota.
await loadFetchLaterIframe(
HTTPS_ORIGIN, // The iframe's src is same-origin.
{
targetUrl: requestUrl,
activateAfter: 0,
method: 'POST',
bodyType: dataType,
bodySize: SMALL_REQUEST_BODY_SIZE,
referrer: '',
sandbox: 'allow-scripts', // Sandboxed, but NOT allow-same-origin.
});
}, `A sandboxed iframe (without allow-same-origin) should be treated as cross-origin and have its own minimal quota.`);
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ promise_test(async _ => {
test(_ => {
const uuid = token();
const requestUrl = generateSetBeaconURL(uuid, {host: HTTPS_ORIGIN});
assert_throws_dom(
'QuotaExceededError',
() => fetchLater(requestUrl, {
activateAfter: 0,
method: 'POST',
body: generatePayload(
getRemainingQuota(QUOTA_PER_ORIGIN, requestUrl, headers) + 1,
dataType),
}));

assert_throws_quotaexceedederror(() => {
fetchLater(requestUrl, {
activateAfter: 0,
method: 'POST',
body: generatePayload(
getRemainingQuota(QUOTA_PER_ORIGIN, requestUrl, headers) + 1,
dataType),
});
}, null, null);
}, `fetchLater() rejects max+1 payload in a POST request body of ${dataType}.`);
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ const OVERSIZED_REQUEST_BODY_SIZE = QUOTA_PER_ORIGIN + 1;
for (const dataType in BeaconDataType) {
// Test making a POST request with oversized payload, which should be rejected
// by fetchLater API.
test(
() => assert_throws_dom(
'QuotaExceededError',
() => fetchLater('/', {
activateAfter: 0,
method: 'POST',
body: makeBeaconData(
generatePayload(OVERSIZED_REQUEST_BODY_SIZE), dataType),
})),
`fetchLater() does not accept payload[size=${
test(() => {
assert_throws_quotaexceedederror(() => {
fetchLater('/', {
activateAfter: 0,
method: 'POST',
body: makeBeaconData(
generatePayload(OVERSIZED_REQUEST_BODY_SIZE), dataType),
});
}, null, null);
}, `fetchLater() does not accept payload[size=${
OVERSIZED_REQUEST_BODY_SIZE}] exceeding per-origin quota in a POST request body of ${
dataType}.`);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ promise_test(async _ => {
// Queues a max bytes request in the root document, which should also be
// rejected, because the target URL's origin `HTTPS_ORIGIN` already has 64kb
// pending from 1st request.
assert_throws_dom(
'QuotaExceededError',
assert_throws_quotaexceedederror(
() => fetchLater(requestUrl, {
method: 'POST',
body: generatePayload(
getRemainingQuota(QUOTA_PER_ORIGIN, requestUrl, headers), dataType),
// Required, as the size of referrer also take up quota.
referrer: '',
}));
}),
null, null);

// Release quota taken by the pending requests for subsequent tests.
for (const element of document.querySelectorAll('iframe')) {
Expand Down
Loading