Update web-platform-tests to revision b'0132c620b681ab085bffb45ad74040e61090e1ba'

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
Servo WPT Sync
2026-03-29 14:34:56 +02:00
committed by sagudev
parent 242cec5770
commit 8530b8b02a
607 changed files with 15389 additions and 12341 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
[scope-import-implicit.tentative.html]
[Scope-imported rule applies within implicit scope]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[scope-import-inner-scope.tentative.html]
[@scope within scope-imported stylesheet matches]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[scope-import-multiple.tentative.html]
[A stylesheet may be imported multiple times, and scoped differently]
expected: FAIL

View File

@@ -1,6 +0,0 @@
[scope-import-parent-pseudo.tentative.html]
[The & selector matches the scoping root]
expected: FAIL
[The & selector behaves like :scope]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[scope-import-scope-end.tentative.html]
[Scope-imported rule applies within scope, above limit]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[scope-import-scope-pseudo.tentative.html]
[The :scope pseudo-class works in imported stylesheets]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[scope-import-scope-start.tentative.html]
[Scope-imported rule applies within scope]
expected: FAIL

View File

@@ -1,2 +0,0 @@
[grid-lanes-subgrid.html]
expected: FAIL

View File

@@ -1,2 +0,0 @@
[marker-content-007.tentative.html]
expected: FAIL

View File

@@ -1,2 +0,0 @@
[marker-content-009.tentative.html]
expected: FAIL

View File

@@ -1,2 +0,0 @@
[marker-content-011.tentative.html]
expected: FAIL

View File

@@ -1,24 +0,0 @@
[marker-attribute.html]
[Access to .marker returns an empty DOMTokenList.]
expected: FAIL
[Multiple names give a DOMTokenList with multiple entries.]
expected: FAIL
[DOMTokenList created by access is persisted.]
expected: FAIL
[Changes in DOMTokenList are refected in attribute.]
expected: FAIL
[Access to .marker returns an empty string]
expected: FAIL
[Multiple names stay a single string]
expected: FAIL
[Setting .marker updates the attribute.]
expected: FAIL
[marker retains whitespace]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[template-for-prepend-invalid-ref.html]
[Prepending works even when element after the range is removed]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[template-for-replace-invalid-ref.html]
[<template for> replace]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[audio-loading-attr-default.tentative.html]
[Audio loading attribute default should be eager when not set]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[audio-loading-attr-lazy.tentative.html]
[Audio loading attribute should reflect the loading content attribute]
expected: FAIL

View File

@@ -1,30 +0,0 @@
[audio-loading-attr-reflect.tentative.html]
[Audio loading attribute default value is 'eager']
expected: FAIL
[Audio loading attribute reflects 'lazy']
expected: FAIL
[Audio loading attribute reflects 'eager']
expected: FAIL
[Audio loading attribute with invalid value reflects as 'eager']
expected: FAIL
[Audio loading attribute with empty string reflects as 'eager']
expected: FAIL
[Audio loading attribute is case-insensitive for 'lazy']
expected: FAIL
[Audio loading attribute is case-insensitive for 'eager']
expected: FAIL
[Setting audio.loading IDL attribute to 'lazy']
expected: FAIL
[Setting audio.loading IDL attribute to 'eager']
expected: FAIL
[Setting audio.loading IDL attribute to invalid value]
expected: FAIL

View File

@@ -1,4 +0,0 @@
[audio-loading-autoplay-deferred.tentative.html]
expected: ERROR
[Audio with loading=lazy and autoplay that is not visible in viewport does not load audio data or autoplay]
expected: FAIL

View File

@@ -1,4 +0,0 @@
[audio-loading-lazy-autoplay-when-visible.tentative.html]
expected: ERROR
[Audio with loading=lazy and autoplay does not play while not visible, then plays once scrolled into view]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[audio-loading-lazy-disconnected.tentative.html]
[Audio with loading=lazy does not load when not connected to document]
expected: FAIL

View File

@@ -1,4 +0,0 @@
[audio-loading-lazy-in-viewport.tentative.html]
expected: ERROR
[In-viewport audio with loading='lazy' fires window load before loadstart]
expected: TIMEOUT

View File

@@ -1,3 +0,0 @@
[audio-loading-lazy-not-rendered.tentative.html]
[In-viewport audio with loading=lazy and no controls attribute, or display:none or hidden attribute should never load]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[audio-loading-lazy-to-eager.tentative.html]
[Below-viewport audio with loading='lazy' loads when changed to loading='eager' or when loading attribute is removed]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[audio-loading-lazy-window-onload.tentative.html]
[Audio with loading=lazy does not delay window onload event]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[audio-loading-load-deferred.tentative.html]
[Audio with loading=lazy that is not visible in viewport does not load audio data]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[audio-loading-load-preload-auto-deferred.tentative.html]
[Audio with loading=lazy and preload=auto that is not visible in viewport does not load audio data]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[audio-loading-load-preload-metadata-deferred.tentative.html]
[Audio with loading=lazy and preload=metadata that is not visible in viewport does not load audio data]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-attr-lazy.tentative.html]
[Video loading attribute should reflect the loading content attribute]
expected: FAIL

View File

@@ -1,30 +0,0 @@
[video-loading-attr-reflect.tentative.html]
[Video loading attribute default value is 'eager']
expected: FAIL
[Video loading attribute reflects 'lazy']
expected: FAIL
[Video loading attribute reflects 'eager']
expected: FAIL
[Video loading attribute with invalid value reflects as 'eager']
expected: FAIL
[Video loading attribute with empty string reflects as 'eager']
expected: FAIL
[Video loading attribute is case-insensitive for 'lazy']
expected: FAIL
[Video loading attribute is case-insensitive for 'eager']
expected: FAIL
[Setting video.loading IDL attribute to 'lazy']
expected: FAIL
[Setting video.loading IDL attribute to 'eager']
expected: FAIL
[Setting video.loading IDL attribute to invalid value]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-attr-default.tentative.html]
[Video loading attribute default should be eager when not set]
expected: FAIL

View File

@@ -1,4 +0,0 @@
[video-loading-autoplay-deferred.tentative.html]
expected: ERROR
[Video with loading=lazy and autoplay that is not visible in viewport does not load video data or autoplay]
expected: FAIL

View File

@@ -1,4 +0,0 @@
[video-loading-lazy-autoplay-when-visible.tentative.html]
expected: ERROR
[Video with loading=lazy and autoplay does not play while not visible, then plays once scrolled into view]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-lazy-disconnected.tentative.html]
[Video with loading=lazy does not load when not connected to document]
expected: FAIL

View File

@@ -1,4 +0,0 @@
[video-loading-lazy-in-viewport.tentative.html]
expected: ERROR
[In-viewport video with loading='lazy' fires window load before loadstart]
expected: TIMEOUT

View File

@@ -1,3 +0,0 @@
[video-loading-lazy-load-when-visible.tentative.html]
[Video with loading=lazy does not fetch video while not visible, then fetches video once scrolled into view]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-lazy-not-rendered.tentative.html]
[In-viewport video with loading=lazy and no controls attribute, or display:none or hidden attribute should never load]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-lazy-poster-when-visible.tentative.html]
[Video with loading=lazy does not fetch poster while not visible, then fetches poster once scrolled into view]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-lazy-to-eager.tentative.html]
[Below-viewport video with loading='lazy' loads when changed to loading='eager' or when loading attribute is removed]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-lazy-window-onload.tentative.html]
[Video with loading=lazy does not delay window onload event]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-load-deferred.tentative.html]
[Video with loading=lazy that is not visible in viewport does not load video data]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-load-preload-auto-deferred.tentative.html]
[Video with loading=lazy and preload=auto that is not visible in viewport does not load video data]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-load-preload-metadata-deferred.tentative.html]
[Video with loading=lazy and preload=metadata that is not visible in viewport does not load video data]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[video-loading-poster-deferred.tentative.html]
[Video with loading=lazy that is not visible in viewport does not load poster image]
expected: FAIL

View File

@@ -1647,11 +1647,14 @@ webtransport/back-forward-cache-with-open-webtransport-connection-ccns.https.ten
webtransport/back-forward-cache-with-open-webtransport-connection.https.window.js @web-platform-tests/interop
webtransport/close.https.any.js @web-platform-tests/interop
webtransport/connect.https.any.js @web-platform-tests/interop
webtransport/constructor.https.sub.any.js @web-platform-tests/interop
webtransport/csp-fail.https.window.js @web-platform-tests/interop
webtransport/csp-pass.https.window.js @web-platform-tests/interop
webtransport/datagram-bad-chunk.https.any.js @web-platform-tests/interop
webtransport/datagram-cancel-crash.https.sub.window.js @web-platform-tests/interop
webtransport/datagrams.https.any.js @web-platform-tests/interop
webtransport/echo-large-bidirectional-streams.https.any.js @web-platform-tests/interop
webtransport/idlharness.https.sub.any.js @web-platform-tests/interop
webtransport/in-removed-iframe.https.html @web-platform-tests/interop
webtransport/sendorder.https.any.js @web-platform-tests/interop
webtransport/sendstream-bad-chunk.https.any.js @web-platform-tests/interop

View File

@@ -0,0 +1,72 @@
// META: title=WebCryptoAPI: raw-secret and raw-public importKey() format aliases
// META: timeout=long
// META: script=../util/helpers.js
// For all existing symmetric algorithms in WebCrypto, "raw-secret" acts as an
// alias of "raw". For all existing asymmetric algorithms in WebCrypto,
// "raw-public" acts as an alias of "raw".
"use strict";
const rawKeyData16 = crypto.getRandomValues(new Uint8Array(16));
const rawKeyData32 = crypto.getRandomValues(new Uint8Array(32));
const wrapAlgorithm = { name: "AES-GCM", iv: new Uint8Array(12) };
const symmetricAlgorithms = [
{ algorithm: { name: "AES-CTR", length: 128 }, keyData: rawKeyData16, usages: ["encrypt", "decrypt"] },
{ algorithm: { name: "AES-CBC", length: 128 }, keyData: rawKeyData16, usages: ["encrypt", "decrypt"] },
{ algorithm: { name: "AES-GCM", length: 128 }, keyData: rawKeyData16, usages: ["encrypt", "decrypt"] },
{ algorithm: { name: "AES-KW", length: 128 }, keyData: rawKeyData16, usages: ["wrapKey", "unwrapKey"] },
{ algorithm: { name: "HMAC", hash: "SHA-256", length: 256 }, keyData: rawKeyData32, usages: ["sign", "verify"] },
{ algorithm: { name: "HKDF" }, keyData: rawKeyData32, usages: ["deriveBits", "deriveKey"], extractable: false },
{ algorithm: { name: "PBKDF2" }, keyData: rawKeyData32, usages: ["deriveBits", "deriveKey"], extractable: false },
];
for (const { algorithm, keyData, usages, extractable = true } of symmetricAlgorithms) {
promise_test(async () => {
const key = await crypto.subtle.importKey("raw-secret", keyData, algorithm, extractable, usages);
assert_goodCryptoKey(key, algorithm, extractable, usages, "secret");
if (extractable) {
await crypto.subtle.exportKey("raw-secret", key);
}
}, `importKey/exportKey with raw-secret: ${algorithm.name}`);
if (extractable) {
promise_test(async () => {
const wrappingKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["wrapKey", "unwrapKey"]);
const key = await crypto.subtle.importKey("raw-secret", keyData, algorithm, true, usages);
const wrapped = await crypto.subtle.wrapKey("raw-secret", key, wrappingKey, wrapAlgorithm);
const unwrapped = await crypto.subtle.unwrapKey("raw-secret", wrapped, wrappingKey, wrapAlgorithm, algorithm, true, usages);
assert_goodCryptoKey(unwrapped, algorithm, true, usages, "secret");
}, `wrapKey/unwrapKey with raw-secret: ${algorithm.name}`);
}
}
const asymmetricAlgorithms = [
{ algorithm: { name: "ECDSA", namedCurve: "P-256" }, usages: ["verify"] },
{ algorithm: { name: "ECDH", namedCurve: "P-256" }, usages: [] },
{ algorithm: { name: "Ed25519" }, usages: ["verify"] },
{ algorithm: { name: "X25519" }, usages: [] },
];
for (const { algorithm, usages } of asymmetricAlgorithms) {
const generateKeyUsages = usages.length ? usages.concat("sign") : ["deriveBits"];
promise_test(async () => {
const keyPair = await crypto.subtle.generateKey(algorithm, true, generateKeyUsages);
const keyData = await crypto.subtle.exportKey("raw-public", keyPair.publicKey);
const key = await crypto.subtle.importKey("raw-public", keyData, algorithm, true, usages);
assert_goodCryptoKey(key, algorithm, true, usages, "public");
await crypto.subtle.exportKey("raw-public", key);
}, `importKey/exportKey with raw-public: ${algorithm.name}`);
promise_test(async () => {
const keyPair = await crypto.subtle.generateKey(algorithm, true, generateKeyUsages);
const wrappingKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["wrapKey", "unwrapKey"]);
const wrapped = await crypto.subtle.wrapKey("raw-public", keyPair.publicKey, wrappingKey, wrapAlgorithm);
const unwrapped = await crypto.subtle.unwrapKey("raw-public", wrapped, wrappingKey, wrapAlgorithm, algorithm, true, usages);
assert_goodCryptoKey(unwrapped, algorithm, true, usages, "public");
}, `wrapKey/unwrapKey with raw-public: ${algorithm.name}`);
}

View File

@@ -23,17 +23,6 @@ promise_test(async () => {
assert_equals(session.contextUsage, promptUsage);
}, 'Check contextUsage increases from a simple LanguageModel.append() call');
promise_test(async (t) => {
await ensureLanguageModel();
const session = await createLanguageModel();
assert_equals(session.contextUsage, 0);
await session.append([]);
assert_equals(session.contextUsage, 0);
// Invalid input should be stringified.
await session.append({});
assert_greater_than(session.contextUsage, 0);
}, 'Check empty Object input for LanguageModel.append()');
promise_test(async t => {
await ensureLanguageModel();
const session = await createLanguageModel();
@@ -42,3 +31,38 @@ promise_test(async t => {
await promise_rejects_quotaexceedederror(
t, session.append(promptString), usage, session.contextWindow);
}, 'Test that append input exceeding the total context window rejects');
promise_test(async t => {
await ensureLanguageModel();
const session = await createLanguageModel();
const result1 = session.append([
{role: 'user', content: 'foo'},
{role: 'system', content: 'bar'},
]);
await promise_rejects_js(t, TypeError, result1);
const result2 = session.append([
{role: 'system', content: 'foo'},
{role: 'system', content: 'bar'},
]);
await promise_rejects_js(t, TypeError, result2);
const result3 = session.append({role: 'system', content: 'foo'});
await promise_rejects_js(
t, TypeError, session.append([{role: 'system', content: 'bar'}]));
await result3;
}, 'append() should reject system role messages after other messages');
promise_test(async (t) => {
await ensureLanguageModel();
const model = await createLanguageModel();
// null, undefined, and objects are coerced to strings.
await model.append(null);
await model.append(undefined);
await model.append({});
await model.append('');
await model.append([]);
await model.append([{ role: 'user', content: [] }]);
await model.append([{role: 'user', content: [{type: 'text', value: ''}]}]);
}, 'LanguageModel.append() allows empty and coerced inputs');

View File

@@ -70,13 +70,19 @@ promise_test(async t => {
}, 'Create with initialPrompts without system role');
promise_test(async t => {
let result = createLanguageModel({
let result1 = createLanguageModel({
initialPrompts: [
{role: 'user', content: 'hello'}, {role: 'assistant', content: 'hello'},
{role: 'system', content: 'you are a robot'}
]
});
await promise_rejects_js(t, TypeError, result);
await promise_rejects_js(t, TypeError, result1);
let result2 = createLanguageModel({
initialPrompts:
[{role: 'system', content: 'foo'}, {role: 'system', content: 'bar'}]
});
await promise_rejects_js(t, TypeError, result2);
}, 'Create with system role not ordered first should fail');
promise_test(async t => {

View File

@@ -8,14 +8,21 @@
promise_test(async t => {
await ensureLanguageModel();
// Start a new session.
const session = await createLanguageModel();
const result = await session.measureContextUsage('This is a prompt.');
assert_equals(typeof result, 'number');
assert_greater_than(result, 0);
}, 'measureContextUsage returns a number greater than zero for text');
// Test the measureContextUsage() API.
let result = await session.measureContextUsage('This is a prompt.');
assert_true(
typeof result === "number" && result > 0,
"The counting result should be a positive number."
);
});
promise_test(async t => {
const prompts = [
{role: 'system', content: 'foo'},
{role: 'user', content: 'bar'},
{role: 'assistant', content: 'baz'},
];
await ensureLanguageModel();
const session = await createLanguageModel({initialPrompts: prompts});
const result = await session.measureContextUsage(prompts);
assert_equals(typeof result, 'number');
assert_greater_than(result, 0);
}, 'measure message sequences of various roles, even after adding prompts');

View File

@@ -1,53 +0,0 @@
// META: title=Language Model Prompt Empty Input
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver-vendor.js
// META: script=../resources/util.js
// META: timeout=long
'use strict';
promise_test(async (t) => {
await ensureLanguageModel();
const model = await createLanguageModel();
// null and undefined are coerced to the strings "null" and "undefined" by the
// IDL bindings.
assert_regexp_match(await model.prompt(null), /null/);
assert_regexp_match(await model.prompt(undefined), /undefined/);
// Empty string is allowed even when context is empty.
assert_equals(typeof await model.prompt(""), "string");
// Empty sequence [] is allowed even when context is empty.
assert_equals(typeof await model.prompt([]), "string");
// Nested empty content sequence is allowed.
assert_equals(typeof await model.prompt([{ role: 'user', content: [] }]), "string");
// Nested structured message with empty text is allowed.
assert_equals(typeof await model.prompt([{ role: 'user', content: [{ type: 'text', value: '' }] }]), "string");
}, "LanguageModel.prompt() allows empty or coerced inputs");
promise_test(async (t) => {
await ensureLanguageModel();
const model = await createLanguageModel();
assert_equals(await model.append(null), undefined);
assert_equals(await model.append(undefined), undefined);
assert_equals(await model.append(""), undefined);
assert_equals(await model.append([]), undefined);
assert_equals(await model.append([{ role: 'user', content: [] }]), undefined);
assert_equals(await model.append([{ role: 'user', content: [{ type: 'text', value: '' }] }]), undefined);
}, "LanguageModel.append() allows empty or coerced inputs");
promise_test(async (t) => {
await ensureLanguageModel();
const model = await createLanguageModel();
assert_true(model.promptStreaming(null) instanceof ReadableStream);
assert_true(model.promptStreaming(undefined) instanceof ReadableStream);
assert_true(model.promptStreaming("") instanceof ReadableStream);
assert_true(model.promptStreaming([]) instanceof ReadableStream);
assert_true(model.promptStreaming([{ role: 'user', content: [] }]) instanceof ReadableStream);
assert_true(model.promptStreaming([{ role: 'user', content: [{ type: 'text', value: '' }] }]) instanceof ReadableStream);
}, "LanguageModel.promptStreaming() allows empty or coerced inputs");

View File

@@ -16,10 +16,7 @@ promise_test(async t => {
session.promptStreaming(kTestPrompt);
// Run GC.
gc();
assert_equals(
Object.prototype.toString.call(streamingResponse),
"[object ReadableStream]"
);
assert_true(streamingResponse instanceof ReadableStream);
let result = "";
for await (const value of streamingResponse) {
result += value;

View File

@@ -8,26 +8,23 @@
promise_test(async t => {
await ensureLanguageModel();
// Start a new session.
const session = await createLanguageModel();
// Test the streaming prompt API.
const streamingResponse =
session.promptStreaming(kTestPrompt);
assert_equals(
Object.prototype.toString.call(streamingResponse),
"[object ReadableStream]"
);
const reader = streamingResponse.getReader();
let result = "";
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
if (value) {
result += value;
}
}
assert_greater_than(result.length, 0, "The result should not be empty.");
const streamingResponse = session.promptStreaming(kTestPrompt);
assert_true(streamingResponse instanceof ReadableStream);
const result = (await Array.fromAsync(streamingResponse)).join('');
assert_greater_than(result.length, 0, 'The result should not be empty.');
});
promise_test(async (t) => {
await ensureLanguageModel();
const model = await createLanguageModel();
// null, undefined, and objects are coerced to strings.
for await (const _ of model.promptStreaming(null)) { }
for await (const _ of model.promptStreaming(undefined)) { }
for await (const _ of model.promptStreaming({})) { }
for await (const _ of model.promptStreaming('')) { }
for await (const _ of model.promptStreaming([])) { }
for await (const _ of model.promptStreaming([{ role: 'user', content: [] }])) { }
for await (const _ of model.promptStreaming([{ role: 'user', content: [{ type: 'text', value: '' }] }])) { }
}, 'LanguageModel.promptStreaming() allows empty and coerced inputs');

View File

@@ -33,6 +33,24 @@ promise_test(async (t) => {
assert_regexp_match(await session.prompt({}), /\[object Object\]/);
}, 'Check empty Object input');
promise_test(async (t) => {
await ensureLanguageModel();
const model = await createLanguageModel();
// null, undefined, and objects are coerced to strings.
assert_regexp_match(await model.prompt(null), /null/);
assert_regexp_match(await model.prompt(undefined), /undefined/);
assert_equals(typeof await model.prompt({}), 'string');
assert_equals(typeof await model.prompt(''), 'string');
assert_equals(typeof await model.prompt([]), 'string');
assert_equals(
typeof await model.prompt([{role: 'user', content: []}]), 'string');
assert_equals(
typeof await model.prompt(
[{role: 'user', content: [{type: 'text', value: ''}]}]),
'string');
}, 'LanguageModel.prompt() allows empty and coerced inputs');
promise_test(async (t) => {
await ensureLanguageModel();
const session = await createLanguageModel();
@@ -42,15 +60,15 @@ promise_test(async (t) => {
promise_test(async () => {
const options = {
initialPrompts:
[{role: 'system', content: [{type: 'text', value: 'The word of the day is regurgitation.'}]}]
[{role: 'system', content: 'The word of the day is regurgitation.'}]
};
await ensureLanguageModel(options);
const session = await LanguageModel.create(options);
const usage = await session.measureContextUsage(options.initialPrompts);
assert_greater_than(usage, 0);
assert_equals(session.contextUsage, usage);
assert_regexp_match(await session.prompt('What is the word of the day?'),
/regurgitation/i);
assert_regexp_match(
await session.prompt('What is the word of the day?'), /regurgitation/i);
}, 'Test that initialPrompt counts towards session contextUsage');
promise_test(async () => {
@@ -80,3 +98,24 @@ promise_test(async t => {
await promise_rejects_quotaexceedederror(
t, session.prompt(promptString), usage, session.contextWindow);
}, 'Test that prompt input exceeding the total context window rejects');
promise_test(async t => {
await ensureLanguageModel();
const session = await createLanguageModel();
const result1 = session.prompt([
{role: 'user', content: 'foo'},
{role: 'system', content: 'bar'},
]);
await promise_rejects_js(t, TypeError, result1);
const result2 = session.prompt([
{role: 'system', content: 'foo'},
{role: 'system', content: 'bar'},
]);
await promise_rejects_js(t, TypeError, result2);
const result3 = session.prompt({role: 'system', content: 'foo'});
await promise_rejects_js(
t, TypeError, session.prompt([{role: 'system', content: 'bar'}]));
await result3;
}, 'prompt() should reject system role messages after other messages');

View File

@@ -0,0 +1,3 @@
features:
- name: topics
files: "**"

View File

@@ -0,0 +1,34 @@
// META: script=/common/get-host-info.sub.js
// META: script=resources/worker_redirect_test.js
//
// The following tests assume the policy `Connection-Allowlist:
// (response-origin);redirects=allow` has been set.
// 1. Same-origin worker script redirect:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[][]}} (also allowed)
// This should SUCCEED because redirects are unconditionally enabled via
// redirects=allow.
worker_script_redirect_test(
get_host_info().HTTP_ORIGIN, get_host_info().HTTP_ORIGIN, SUCCESS,
'Same-origin dedicated worker main script fetch with redirect succeeds due to redirects=allow.');
// 2. Same-origin subresource fetch from same-origin worker with same-origin
// redirect:
// worker: local scheme (data: URL inherits policy)
// fetch origin: same-origin
// fetch target: same-origin
// This should SUCCEED.
worker_subresource_redirect_test(
get_host_info().HTTP_ORIGIN, get_host_info().HTTP_ORIGIN, SUCCESS,
'Same-origin subresource fetch from dedicated worker with same-origin redirect succeeds due to redirects=allow.');
// 3. Same-origin subresource fetch from same-origin worker with cross-origin
// redirect:
// worker: local scheme (data: URL inherits policy)
// fetch origin: same-origin
// fetch target: cross-origin
// This should SUCCEED.
worker_subresource_redirect_test(
get_host_info().HTTP_ORIGIN, get_host_info().HTTP_REMOTE_ORIGIN, SUCCESS,
'Same-origin subresource fetch from dedicated worker with cross-origin redirect succeeds due to redirects=allow.');

View File

@@ -0,0 +1 @@
Connection-Allowlist: (response-origin);redirects=allow

View File

@@ -0,0 +1,33 @@
// META: script=/common/get-host-info.sub.js
// META: script=resources/worker_redirect_test.js
//
// The following tests assume the policy `Connection-Allowlist:
// (response-origin);redirects=block` has been set.
// 1. Same-origin worker script redirect:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[][]}} (also allowed)
// This should FAIL because redirects are explicitly blocked.
worker_script_redirect_test(
get_host_info().HTTP_ORIGIN, get_host_info().HTTP_ORIGIN, FAILURE,
'Same-origin dedicated worker main script fetch with redirect fails due to redirects=block.');
// 2. Same-origin subresource fetch from same-origin worker with same-origin
// redirect:
// worker: local scheme (data: URL inherits policy)
// fetch origin: same-origin
// fetch target: same-origin
// This should FAIL.
worker_subresource_redirect_test(
get_host_info().HTTP_ORIGIN, get_host_info().HTTP_ORIGIN, FAILURE,
'Same-origin subresource fetch from dedicated worker with same-origin redirect fails due to redirects=block.');
// 3. Same-origin subresource fetch from same-origin worker with cross-origin
// redirect:
// worker: local scheme (data: URL inherits policy)
// fetch origin: same-origin
// fetch target: cross-origin
// This should FAIL.
worker_subresource_redirect_test(
get_host_info().HTTP_ORIGIN, get_host_info().HTTP_REMOTE_ORIGIN, FAILURE,
'Same-origin subresource fetch from dedicated worker with cross-origin redirect fails due to redirects=block.');

View File

@@ -0,0 +1 @@
Connection-Allowlist: (response-origin);redirects=block

View File

@@ -0,0 +1,34 @@
// META: script=/common/get-host-info.sub.js
// META: script=resources/worker_redirect_test.js
//
// The following tests assume the policy `Connection-Allowlist:
// (response-origin)` has been set.
// 1. Same-origin worker script redirect:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[][]}} (also allowed)
// This should FAIL because redirects are default-blocked for allowlisted
// fetches.
worker_script_redirect_test(
get_host_info().HTTP_ORIGIN, get_host_info().HTTP_ORIGIN, FAILURE,
'Same-origin dedicated worker main script fetch with redirect fails by default.');
// 2. Same-origin subresource fetch from same-origin worker with same-origin
// redirect:
// worker: local scheme (data: URL inherits policy)
// fetch origin: same-origin
// fetch target: same-origin
// This should FAIL.
worker_subresource_redirect_test(
get_host_info().HTTP_ORIGIN, get_host_info().HTTP_ORIGIN, FAILURE,
'Same-origin subresource fetch from dedicated worker with same-origin redirect fails by default.');
// 3. Same-origin subresource fetch from same-origin worker with cross-origin
// redirect:
// worker: local scheme (data: URL inherits policy)
// fetch origin: same-origin
// fetch target: cross-origin
// This should FAIL.
worker_subresource_redirect_test(
get_host_info().HTTP_ORIGIN, get_host_info().HTTP_REMOTE_ORIGIN, FAILURE,
'Same-origin subresource fetch from dedicated worker with cross-origin redirect fails by default.');

View File

@@ -1,112 +0,0 @@
// META: script=/common/get-host-info.sub.js
//
// The following tests assume the policy `Connection-Allowlist: (response-origin)` has been set.
const port = get_host_info().HTTP_PORT_ELIDED;
const SUCCESS = true;
const FAILURE = false;
function worker_script_redirect_test(origin, target_origin, expectation, description) {
promise_test(async t => {
const target_url = target_origin + "/connection-allowlist/tentative/resources/worker-fetch-script.js";
const url = origin + "/common/redirect.py?status=302&location=" + encodeURIComponent(target_url);
let worker;
try {
worker = new Worker(url);
} catch (e) {
// Some implementations might throw synchronously for some origins, but usually it's an error event.
assert_equals(expectation, FAILURE, "Worker constructor threw unexpectedly");
return;
}
const promise = new Promise((resolve, reject) => {
worker.onmessage = () => resolve(SUCCESS);
worker.onerror = (e) => {
e.preventDefault();
reject(new Error("Worker Load Error"));
};
// Send a message to the worker. If it loaded successfully, it will
// respond and onmessage will fire. If it failed to load, onerror
// should fire.
worker.postMessage(`${get_host_info().HTTP_ORIGIN}/common/blank-with-cors.html`);
});
if (expectation === SUCCESS) {
const result = await promise;
assert_equals(result, expectation, description);
} else {
await promise_rejects_js(t, Error, promise, description);
}
}, description);
}
// 1. Same-origin worker script redirect:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[][]}} (also allowed)
// This should FAIL because redirects are default-blocked for allowlisted fetches.
worker_script_redirect_test(
get_host_info().HTTP_ORIGIN,
get_host_info().HTTP_ORIGIN,
FAILURE,
"Same-origin dedicated worker main script fetch with redirect fails."
);
const worker_content = `
onmessage = async (e) => {
const url = e.data;
try {
const r = await fetch(url, { mode: 'cors', credentials: 'omit' });
postMessage({ url: url, success: r.ok });
} catch (err) {
postMessage({ url: url, success: false, error: err.name });
}
};
`;
const dataUrl = "data:text/javascript," + encodeURIComponent(worker_content);
function worker_subresource_redirect_test(fetch_origin, fetch_target_origin, expectation, description) {
promise_test(async t => {
const worker = new Worker(dataUrl, { type: 'module' });
const target_url = fetch_target_origin + "/common/blank-with-cors.html";
const fetch_url = fetch_origin + "/common/redirect.py?status=302&location=" + encodeURIComponent(target_url);
worker.postMessage(fetch_url);
const msgEvent = await new Promise((resolve, reject) => {
worker.onmessage = resolve;
worker.onerror = (e) => reject(new Error("Worker Error"));
});
if (expectation === SUCCESS) {
assert_true(msgEvent.data.success, `Fetch to ${fetch_url} should succeed.`);
} else {
assert_false(msgEvent.data.success, `Fetch to ${fetch_url} should be blocked.`);
}
}, description);
}
// 2. Same-origin subresource fetch from same-origin worker with same-origin redirect:
// worker: same-origin (data: URL inherits policy)
// fetch origin: same-origin
// fetch target: same-origin
// This should FAIL.
worker_subresource_redirect_test(
get_host_info().HTTP_ORIGIN,
get_host_info().HTTP_ORIGIN,
FAILURE,
"Same-origin subresource fetch from dedicated worker with same-origin redirect fails."
);
// 3. Same-origin subresource fetch from same-origin worker with cross-origin redirect:
// worker: same-origin (data: URL inherits policy)
// fetch origin: same-origin
// fetch target: cross-origin
// This should FAIL.
worker_subresource_redirect_test(
get_host_info().HTTP_ORIGIN,
get_host_info().HTTP_REMOTE_ORIGIN,
FAILURE,
"Same-origin subresource fetch from dedicated worker with cross-origin redirect fails."
);

View File

@@ -0,0 +1,31 @@
// META: script=/common/get-host-info.sub.js
// META: script=resources/fetch_redirect_test.js
//
// The following tests assume the policy `Connection-Allowlist:
// (response-origin);redirects=allow` has been set. Redirects for fetches
// allowed through connection allowlists should be allowed.
// We're loading this page from `http://{{hosts[][]}}`.
// The connection allowlist header is `Connection-Allowlist:
// (response-origin);redirects=allow`. Thus, only `http://{{hosts[][]}}` is
// allowlisted for fetches.
// Same-origin redirect:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[][]}} (also allowed)
// This should SUCCEED, because of the "allow" header param.
fetch_redirect_test(
'http://{{hosts[][]}}' + port, 'http://{{hosts[][]}}' + port, SUCCESS);
// Redirect from an allowlisted origin to a different origin:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[alt][]}} (not allowed)
// This should SUCCEED, because of the "allow" header param.
fetch_redirect_test(
'http://{{hosts[][]}}' + port, 'http://{{hosts[alt][]}}' + port, SUCCESS);
// Fetch to a non-allowlisted origin:
// origin: http://{{hosts[alt][]}} (not allowed)
// This is blocked before the redirect even happens.
fetch_redirect_test(
'http://{{hosts[alt][]}}' + port, 'http://{{hosts[][]}}' + port, FAILURE);

View File

@@ -0,0 +1 @@
Connection-Allowlist: (response-origin);redirects=allow

View File

@@ -0,0 +1,31 @@
// META: script=/common/get-host-info.sub.js
// META: script=resources/fetch_redirect_test.js
//
// The following tests assume the policy `Connection-Allowlist:
// (response-origin);redirects=block` has been set. Redirects for fetches
// allowed through connection allowlists should be blocked.
// We're loading this page from `http://{{hosts[][]}}`.
// The connection allowlist header is `Connection-Allowlist:
// (response-origin);redirects=block`. Thus, only `http://{{hosts[][]}}` is
// allowlisted for fetches.
// Same-origin redirect:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[][]}} (also allowed)
// This should FAIL.
fetch_redirect_test(
'http://{{hosts[][]}}' + port, 'http://{{hosts[][]}}' + port, FAILURE);
// Redirect from an allowlisted origin to a different origin:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[alt][]}} (not allowed)
// This should FAIL.
fetch_redirect_test(
'http://{{hosts[][]}}' + port, 'http://{{hosts[alt][]}}' + port, FAILURE);
// Fetch to a non-allowlisted origin:
// origin: http://{{hosts[alt][]}} (not allowed)
// This is blocked before the redirect even happens.
fetch_redirect_test(
'http://{{hosts[alt][]}}' + port, 'http://{{hosts[][]}}' + port, FAILURE);

View File

@@ -0,0 +1 @@
Connection-Allowlist: (response-origin);redirects=block

View File

@@ -0,0 +1,31 @@
// META: script=/common/get-host-info.sub.js
// META: script=resources/fetch_redirect_test.js
//
// The following tests assume the policy `Connection-Allowlist:
// (response-origin)` has been set. Redirects for fetches allowed through
// connection allowlists should be blocked by default.
// We're loading this page from `http://{{hosts[][]}}`.
// The connection allowlist header is `Connection-Allowlist: (response-origin)`.
// Thus, only `http://{{hosts[][]}}` is allowlisted for fetches.
// Same-origin redirect:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[][]}} (also allowed)
// This should FAIL because redirects are default-blocked for allowlisted
// fetches.
fetch_redirect_test(
'http://{{hosts[][]}}' + port, 'http://{{hosts[][]}}' + port, FAILURE);
// Redirect from an allowlisted origin to a different origin:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[alt][]}} (not allowed)
// This should FAIL.
fetch_redirect_test(
'http://{{hosts[][]}}' + port, 'http://{{hosts[alt][]}}' + port, FAILURE);
// Fetch to a non-allowlisted origin:
// origin: http://{{hosts[alt][]}} (not allowed)
// This is blocked before the redirect even happens.
fetch_redirect_test(
'http://{{hosts[alt][]}}' + port, 'http://{{hosts[][]}}' + port, FAILURE);

View File

@@ -1,46 +0,0 @@
// META: script=/common/get-host-info.sub.js
//
// The following tests assume the policy `Connection-Allowlist: (response-origin)` has been set.
// Redirects for fetches allowed through connection allowlists should be blocked by default.
const port = get_host_info().HTTP_PORT_ELIDED;
const SUCCESS = true;
const FAILURE = false;
function fetch_redirect_test(origin, target_origin, expectation) {
const target_url = target_origin + "/common/blank-with-cors.html";
const url = origin + "/common/redirect.py?status=302&location=" + encodeURIComponent(target_url);
if (expectation === FAILURE) {
return promise_test(async t => {
const fetcher = fetch(url, { mode: "cors", credentials: "omit" });
return promise_rejects_js(t, TypeError, fetcher);
}, `Fetch redirect from ${origin} to ${target_origin} fails.`);
}
promise_test(async t => {
const r = await fetch(url, { mode: "cors", credentials: "omit" });
assert_equals(r.status, 200);
}, `Fetch redirect from ${origin} to ${target_origin} succeeds.`);
}
// We're loading this page from `http://{{hosts[][]}}`.
// The connection allowlist header is `Connection-Allowlist: (response-origin)`.
// Thus, only `http://{{hosts[][]}}` is allowlisted for fetches.
// Same-origin redirect:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[][]}} (also allowed)
// This should FAIL because redirects are default-blocked for allowlisted fetches.
fetch_redirect_test("http://{{hosts[][]}}" + port, "http://{{hosts[][]}}" + port, FAILURE);
// Redirect from an allowlisted origin to a different origin:
// origin: http://{{hosts[][]}} (allowed by allowlist)
// target: http://{{hosts[alt][]}} (not allowed)
// This should FAIL.
fetch_redirect_test("http://{{hosts[][]}}" + port, "http://{{hosts[alt][]}}" + port, FAILURE);
// Fetch to a non-allowlisted origin:
// origin: http://{{hosts[alt][]}} (not allowed)
// This is blocked before the redirect even happens.
fetch_redirect_test("http://{{hosts[alt][]}}" + port, "http://{{hosts[][]}}" + port, FAILURE);

View File

@@ -0,0 +1,23 @@
// META: script=/common/get-host-info.sub.js
const port = get_host_info().HTTP_PORT_ELIDED;
const SUCCESS = true;
const FAILURE = false;
function fetch_redirect_test(origin, target_origin, expectation) {
const target_url = target_origin + '/common/blank-with-cors.html';
const url = origin + '/common/redirect.py?status=302&location=' +
encodeURIComponent(target_url);
if (expectation === FAILURE) {
return promise_test(async t => {
const fetcher = fetch(url, {mode: 'cors', credentials: 'omit'});
return promise_rejects_js(t, TypeError, fetcher);
}, `Fetch redirect from ${origin} to ${target_origin} fails.`);
}
promise_test(async t => {
const r = await fetch(url, {mode: 'cors', credentials: 'omit'});
assert_equals(r.status, 200);
}, `Fetch redirect from ${origin} to ${target_origin} succeeds.`);
}

View File

@@ -0,0 +1,88 @@
// META: script=/common/get-host-info.sub.js
const port = get_host_info().HTTP_PORT_ELIDED;
const SUCCESS = true;
const FAILURE = false;
function worker_script_redirect_test(
origin, target_origin, expectation, description) {
promise_test(async t => {
const target_url = target_origin +
'/connection-allowlist/tentative/resources/worker-fetch-script.js';
const url = origin + '/common/redirect.py?status=302&location=' +
encodeURIComponent(target_url);
let worker;
try {
worker = new Worker(url);
} catch (e) {
// Some implementations might throw synchronously for some origins, but
// usually it's an error event.
assert_equals(
expectation, FAILURE, 'Worker constructor threw unexpectedly');
return;
}
const promise = new Promise((resolve, reject) => {
worker.onmessage = () => resolve(SUCCESS);
worker.onerror = (e) => {
e.preventDefault();
reject(new Error('Worker Load Error'));
};
// Send a message to the worker. If it loaded successfully, it will
// respond and onmessage will fire. If it failed to load, onerror
// should fire.
worker.postMessage(
`${get_host_info().HTTP_ORIGIN}/common/blank-with-cors.html`);
});
if (expectation === SUCCESS) {
const result = await promise;
assert_equals(result, expectation, description);
} else {
await promise_rejects_js(t, Error, promise, description);
}
}, description);
}
const worker_content = `
onmessage = async (e) => {
const url = e.data;
try {
const r = await fetch(url, { mode: 'cors', credentials: 'omit' });
postMessage({ url: url, success: r.ok });
} catch (err) {
postMessage({ url: url, success: false, error: err.name });
}
};
`;
const dataUrl = 'data:text/javascript,' + encodeURIComponent(worker_content);
function worker_subresource_redirect_test(
fetch_origin, fetch_target_origin, expectation, description) {
promise_test(async t => {
const worker = new Worker(dataUrl, {type: 'module'});
const target_url = fetch_target_origin + '/common/blank-with-cors.html';
// We need to pass `enable-cors` because the worker request origin will be
// `null`.
const fetch_url = fetch_origin +
'/common/redirect.py?status=302&enable-cors&location=' +
encodeURIComponent(target_url);
worker.postMessage(fetch_url);
const msgEvent = await new Promise((resolve, reject) => {
worker.onmessage = resolve;
worker.onerror = (e) => reject(new Error('Worker Error'));
});
if (expectation === SUCCESS) {
assert_true(
msgEvent.data.success, `Fetch to ${fetch_url} should succeed.`);
} else {
assert_false(
msgEvent.data.success, `Fetch to ${fetch_url} should be blocked.`);
}
}, description);
}

View File

@@ -0,0 +1,56 @@
// META: script=/common/get-host-info.sub.js
//
// The following tests assume the policy
// `Connection-Allowlist: (response-origin "http://{{host}}:{{ports[ws][0]}}")`
// has been set. The WPT WebSocket server runs on a different port than HTTP,
// so the allowlist explicitly includes the same-host WebSocket origin.
const ws_port = "{{ports[ws][0]}}";
function websocket_test(host, expectation, description) {
promise_test(async t => {
const url = `ws://${host}:${ws_port}/echo`;
const ws = new WebSocket(url);
const result = await new Promise(resolve => {
ws.onopen = () => { ws.close(); resolve('open'); };
ws.onerror = () => resolve('error');
});
assert_equals(result, expectation,
`WebSocket to ${host} should ${expectation === 'open' ? 'connect' : 'be blocked'}.`);
}, description);
}
// Same-origin WebSocket should succeed (allowlisted via explicit pattern).
websocket_test(
"{{hosts[][]}}",
"open",
"Same-origin WebSocket succeeds."
);
// Same-site but cross-origin subdomains should fail.
websocket_test(
"{{hosts[][www]}}",
"error",
"Cross-origin same-site WebSocket (www) is blocked."
);
websocket_test(
"{{hosts[][www1]}}",
"error",
"Cross-origin same-site WebSocket (www1) is blocked."
);
// Cross-site origins should fail.
websocket_test(
"{{hosts[alt][]}}",
"error",
"Cross-site WebSocket is blocked."
);
websocket_test(
"{{hosts[alt][www]}}",
"error",
"Cross-site WebSocket (www subdomain) is blocked."
);

View File

@@ -0,0 +1 @@
Connection-Allowlist: (response-origin "http://{{host}}:{{ports[ws][0]}}")

View File

@@ -78,12 +78,11 @@ promise_test(t => {
}, "Same-origin => cross-origin 'fetch()'.");
let websocket_url = "wss://{{host}}:{{ports[wss][0]}}/echo";
let expected_websocket_csp_url = websocket_url.replace('wss://', 'https://');
// The WebSocket URL is not the same as 'self'
promise_test(t => {
return Promise.all([
waitUntilCSPEventForURL(t, expected_websocket_csp_url),
waitUntilCSPEventForURL(t, websocket_url),
new Promise(resolve => {
let ws = new WebSocket(websocket_url);
ws.onopen = resolve;
@@ -92,8 +91,8 @@ promise_test(t => {
}, "WebSocket.");
let expected_blocked_urls = self.XMLHttpRequest
? [ fetch_cross_origin_url, xhr_cross_origin_url, redirect_url, expected_websocket_csp_url ]
: [ fetch_cross_origin_url, redirect_url, expected_websocket_csp_url ];
? [ fetch_cross_origin_url, xhr_cross_origin_url, redirect_url, websocket_url ]
: [ fetch_cross_origin_url, redirect_url, websocket_url ];
promise_test(async t => {
let report_url = `{{location[server]}}/reporting/resources/report.py?` +

View File

@@ -90,12 +90,11 @@ promise_test(t => {
let websocket_url = "wss://{{host}}:{{ports[wss][0]}}/echo";
let expected_websocket_csp_url = websocket_url.replace('wss://', 'https://');
// The WebSocket URL is not the same as 'self'
promise_test(t => {
return Promise.all([
waitUntilCSPEventForURL(t, expected_websocket_csp_url),
waitUntilCSPEventForURL(t, websocket_url),
new Promise((resolve, reject) => {
// Firefox throws in the constructor, Chrome triggers the error event.
try {
@@ -110,8 +109,8 @@ promise_test(t => {
}, "WebSocket in " + self.location.protocol + " with {{GET[test-name]}}");
let expected_blocked_urls = self.XMLHttpRequest
? [ fetch_cross_origin_url, xhr_cross_origin_url, redirect_url, expected_websocket_csp_url ]
: [ fetch_cross_origin_url, redirect_url, expected_websocket_csp_url ];
? [ fetch_cross_origin_url, xhr_cross_origin_url, redirect_url, websocket_url ]
: [ fetch_cross_origin_url, redirect_url, websocket_url ];
promise_test(async t => {
let report_url = `{{location[server]}}/reporting/resources/report.py` +

View File

@@ -26,28 +26,28 @@ promise_test(async test => {
const url = get_host_info().HTTP_ORIGIN.replace("http", "ws") + "/path";
const violation = nextCSPViolation(test);
try { new WebSocket(url); } catch (e) {}
assert_equals((await violation).blockedURI, url.replace('ws://', 'http://'));
assert_equals((await violation).blockedURI, url);
}, "ws");
promise_test(async test => {
const url = get_host_info().HTTP_ORIGIN.replace("http", "wss") + "/path";
const violation = nextCSPViolation(test);
try { new WebSocket(url); } catch (e) {}
assert_equals((await violation).blockedURI, url.replace('wss://', 'https://'));
assert_equals((await violation).blockedURI, url);
}, "wss");
promise_test(async test => {
const url = get_host_info().HTTP_REMOTE_ORIGIN.replace("http", "wss") + "/path";
const violation = nextCSPViolation(test);
try { new WebSocket(url); } catch (e) {}
assert_equals((await violation).blockedURI, url.replace('wss://', 'https://'));
assert_equals((await violation).blockedURI, url);
}, "cross-origin");
promise_test(async test => {
const url = get_host_info().HTTP_ORIGIN.replace("http", "wss") + "/path";
const violation = nextCSPViolation(test);
try {new WebSocket(redirector + "?location=" + url); } catch (e) {}
assert_equals((await violation).blockedURI, url.replace('wss://', 'https://'));
assert_equals((await violation).blockedURI, url);
}, "redirect");
</script>

View File

@@ -55,6 +55,42 @@
);
}, "Calling navigator.credentials.get() without a valid matching interface.");
promise_test(async (t) => {
const invalidCombinations = [
{ password: true, publicKey: { challenge: new Uint8Array() } },
{ password: true, otp: { transport: ["sms"] } },
{ password: true, identity: { providers: [] } },
{ federated: { providers: ['https://idp.example.com'] }, publicKey: { challenge: new Uint8Array() } },
{ federated: { providers: ['https://idp.example.com'] }, otp: { transport: ["sms"] } },
{ federated: { providers: ['https://idp.example.com'] }, identity: { providers: [] } },
{ publicKey: { challenge: new Uint8Array() }, otp: { transport: ["sms"] } },
{ publicKey: { challenge: new Uint8Array() }, identity: { providers: [] } },
{ otp: { transport: ["sms"] }, identity: { providers: [] } },
];
for (const options of invalidCombinations) {
await promise_rejects_dom(
t,
"NotSupportedError",
navigator.credentials.get(options)
);
}
}, "Calling navigator.credentials.get() with invalid combinations of credential types.");
promise_test(async (t) => {
// Valid combination (password + federated) is allowed by the spec. It should reach the
// fetching phase, where it will fail with NotAllowedError due to lack of user activation,
// but crucially NOT NotSupportedError.
await promise_rejects_dom(
t,
"NotAllowedError",
navigator.credentials.get({
password: true,
federated: { providers: ['https://idp.example.com'] }
})
);
}, "Calling navigator.credentials.get() with valid combination (password + federated).");
promise_test(function(t) {
const controller = new AbortController();
controller.abort("custom reason");

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<link rel="help" href="https://crbug.com/495571483">
<style>
.float::backdrop { float: left; }
</style>
<dialog id="dialog"></dialog>
<script>
dialog.showModal();
dialog.classList.add('float');
</script>

View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<title>Tests that chain of elements that anchor to each other using anchor() works.</title>
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-pos">
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#determining">
<link rel="author" title="Kiet Ho" href="mailto:kiet.ho@apple.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/check-layout-th.js"></script>
<script src="support/test-common.js"></script>
<style>
.containing-block {
border: 1px solid black;
position: relative;
width: 500px;
height: 200px;
}
.box {
position: absolute;
left: calc(anchor(--box right) + 10px);
anchor-name: --box;
width: 50px;
height: 50px;
background-color: green;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
</style>
<body onload="checkLayoutForAnchorPos('.target')">
<p>You should see a row of five boxes in order.</p>
<div class="containing-block">
<div class="box target" data-offset-x="0" data-offset-y="0">1</div>
<div class="box target" data-offset-x="60" data-offset-y="0">2</div>
<div class="box target" data-offset-x="120" data-offset-y="0">3</div>
<div class="box target" data-offset-x="180" data-offset-y="0">4</div>
<div class="box target" data-offset-x="240" data-offset-y="0">5</div>
</div>
</body>

View File

@@ -83,4 +83,8 @@ test_valid_value('top', 'calc((anchor(--foo top) + anchor(--bar bottom)) / 2)',
test_valid_value('top', 'calc(0.5 * (anchor(--foo top) + anchor(--bar bottom)))');
test_valid_value('top', 'anchor(--foo top, calc(0.5 * anchor(--bar bottom)))');
test_valid_value('top', 'min(100px, 10%, anchor(--foo top), anchor(--bar bottom))');
// Should accept unitless zero
test_valid_value('top', "anchor(--foo left, 0)", "anchor(--foo left, 0px)");
test_valid_value('top', "calc(anchor(--foo left, 0))", "anchor(--foo left, 0px)");
</script>

View File

@@ -20,7 +20,11 @@ div {
}
#scroller1,#scroller2 {
overflow: scroll;
/* Using visible scrollbars here tests scrollbar painting order as well, which
isn't related to anchor positioning. See:
https://github.com/web-platform-tests/wpt/pull/55022#issuecomment-3499478504 */
overflow: scroll;
scrollbar-width: none;
}
#anchor {

View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<title>Tests that chain of elements that anchor to each other using anchor-size() works.</title>
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-size-fn">
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#determining">
<link rel="author" title="Kiet Ho" href="mailto:kiet.ho@apple.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/check-layout-th.js"></script>
<script src="support/test-common.js"></script>
<style>
.containing-block {
border: 1px solid black;
position: relative;
width: 500px;
height: 200px;
}
.box {
/* Can only use anchor-size() with position: absolute */
position: absolute;
background-color: green;
anchor-name: --box;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
#box1 {
width: 50px;
height: 60px;
}
#box2, #box3, #box4, #box5 {
width: calc(anchor-size(--box width) + 10px);
height: calc(anchor-size(--box height) + 20px);
}
#box2 { left: 60px; }
#box3 { left: 130px; }
#box4 { left: 210px; }
#box5 { left: 300px; }
</style>
<body onload="checkLayoutForAnchorPos('.target')">
<p>You should see a row of five rectangles growing in size in order.</p>
<div class="containing-block">
<!--
The increasing size is to check that the boxes are anchoring to each other
in the correct order i.e box2 chains to box1, box3 chains to box2, ...
-->
<div class="box target" id="box1" data-expected-width="50" data-expected-height="60">1</div>
<div class="box target" id="box2" data-expected-width="60" data-expected-height="80">2</div>
<div class="box target" id="box3" data-expected-width="70" data-expected-height="100">3</div>
<div class="box target" id="box4" data-expected-width="80" data-expected-height="120">4</div>
<div class="box target" id="box5" data-expected-width="90" data-expected-height="140">5</div>
</div>
</body>

View File

@@ -105,4 +105,8 @@ for (const prop of ['width', 'max-width', 'margin-left']) {
test_valid_value(prop, 'anchor-size(--foo width, calc(0.5 * anchor-size(--bar height)))');
test_valid_value(prop, 'min(100px, 10%, anchor-size(--foo width), anchor-size(--bar height))');
}
// Should accept unitless zero
test_valid_value('width', "anchor-size(--foo width, 0)", "anchor-size(--foo width, 0px)");
test_valid_value('width', "calc(anchor-size(--foo width, 0))", "anchor-size(--foo width, 0px)");
</script>

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<title>Tests that chain of elements that anchor to each other using position-area works.</title>
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-area">
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#determining">
<link rel="author" title="Kiet Ho" href="mailto:kiet.ho@apple.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/check-layout-th.js"></script>
<script src="support/test-common.js"></script>
<style>
.containing-block {
border: 1px solid black;
position: relative;
width: 500px;
height: 200px;
}
.box {
position: absolute;
anchor-name: --box;
position-area: center right;
position-anchor: --box;
width: 50px;
height: 50px;
border-right: 10px white solid;
background-color: green;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
</style>
<body onload="checkLayoutForAnchorPos('.target')">
<p>You should see a row of five boxes in order.</p>
<div class="containing-block">
<div class="box target" data-offset-x="0" data-offset-y="0">1</div>
<div class="box target" data-offset-x="60" data-offset-y="0">2</div>
<div class="box target" data-offset-x="120" data-offset-y="0">3</div>
<div class="box target" data-offset-x="180" data-offset-y="0">4</div>
<div class="box target" data-offset-x="240" data-offset-y="0">5</div>
</div>
</body>

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<link rel="help" href="https://crbug.com/388575663">
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#typedef-position-area">
<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
<title>If the box does not have a default anchor box, or is not an absolutely positioned box, position-area has no effect.</title>
<style>
.abspos {
position: absolute;
width: 100px;
height: 100px;
}
</style>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div style="position: relative; width: 100px; height: 200px;">
<div class="abspos" style="background: red;"></div>
<div class="abspos" style="position-area: left; background: green;"></div>
</div>

View File

@@ -17,6 +17,7 @@ div {
#scroller1,#scroller2 {
overflow: scroll;
scrollbar-width: none;
}
#anchor {

View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Animations: fill-forwards with viewport units responds to viewport resize</title>
<link rel="help" href="https://drafts.csswg.org/css-animations-1/#animation-fill-mode">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../support/testcommon.js"></script>
<style>
iframe {
width: 200px;
height: 200px;
border: none;
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
function createIframe(test) {
const iframe = document.createElement('iframe');
iframe.srcdoc = `
<style>
@keyframes grow {
from { width: 10vw; }
to { width: 50vw; }
}
#target {
width: 0px;
height: 100px;
background: green;
animation: grow 100s forwards linear;
}
</style>
<div id="target"></div>
`;
document.body.appendChild(iframe);
test.add_cleanup(() => iframe.remove());
return new Promise(resolve => iframe.addEventListener('load', () => resolve(iframe)));
}
promise_test(async t => {
const iframe = await createIframe(t);
const doc = iframe.contentDocument;
const win = iframe.contentWindow;
const target = doc.getElementById('target');
const anim = target.getAnimations()[0];
// Jump to end so fill-mode: forwards takes effect.
anim.currentTime = 100 * 1000;
await anim.finished;
await waitForAnimationFrames(2);
// iframe is 200px wide, so 50vw = 100px.
assert_approx_equals(
parseFloat(win.getComputedStyle(target).width), 100, 1,
'Filled width should be 50vw of 200px viewport'
);
// Resize iframe to 400px wide.
iframe.style.width = '400px';
target.offsetHeight;
await waitForAnimationFrames(3);
// 50vw of 400px = 200px.
assert_approx_equals(
parseFloat(win.getComputedStyle(target).width), 200, 1,
'Filled width should update to 50vw of 400px viewport after resize'
);
// Resize iframe to 600px wide.
iframe.style.width = '600px';
target.offsetHeight;
await waitForAnimationFrames(3);
// 50vw of 600px = 300px.
assert_approx_equals(
parseFloat(win.getComputedStyle(target).width), 300, 1,
'Filled width should update to 50vw of 600px viewport after second resize'
);
}, 'fill: forwards with viewport units updates on viewport resize');
</script>
</body>

View File

@@ -1,41 +1,29 @@
<!DOCTYPE html>
<style>
body {
margin: 0;
}
svg {
position: absolute;
top: 0;
left: 0;
width: 230px;
height: 240px;
overflow: visible;
}
.outer-shadow {
fill: blue;
stroke: blue;
stroke-width: 20px;
transform: translate(10px, 20px);
}
.border {
fill: green;
stroke: black;
stroke-width: 10px;
}
.inset-shadow {
fill: none;
stroke: purple;
stroke-width: 10px;
}
body {
margin: 0;
}
svg {
display: block;
width: 240px;
height: 250px;
}
</style>
<svg viewBox="0 0 230 240" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 240 250" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="inner-clip">
<rect x="10" y="10" width="190" height="190" />
</clipPath>
</defs>
<rect x="0" y="0" width="210" height="210" class="outer-shadow" />
<rect x="5" y="5" width="200" height="200" class="border" />
<rect x="10" y="10" width="190" height="190" class="inset-shadow" clip-path="url(#inner-clip)" />
<!-- Outer shadow: 230x230 at (0,10) -->
<rect x="0" y="10" width="230" height="230" fill="blue" />
<!-- Background: 190x190 at (10,10) -->
<rect x="10" y="10" width="190" height="190" fill="green" />
<!-- Inset shadow: 10px stroke inside the 190x190 rect. Center at (15,15), size 180x180 -->
<rect x="10" y="10" width="190" height="190" fill="none" stroke="purple" stroke-width="20" clip-path="url(#inner-clip)" />
<!-- Border: 10px stroke centered on base path (5,5) 200x200 rect -->
<rect x="5" y="5" width="200" height="200" fill="none" stroke="black" stroke-width="10" />
</svg>

View File

@@ -1,36 +1,27 @@
<!DOCTYPE html>
<style>
body {
margin: 0;
}
svg {
width: 200px;
height: 200px;
overflow: visible;
border: 5px solid transparent;
}
.background {
fill: green;
}
.inset-shadow {
fill: none;
stroke: purple;
stroke-width: 20px;
}
.border {
fill: none;
stroke: black;
stroke-width: 10px;
}
body {
margin: 0;
}
svg {
display: block;
width: 210px;
height: 210px;
overflow: visible;
}
</style>
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 210 210" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="inner-clip">
<polygon points="100,0 200,100, 100,200, 0,100" />
<polygon points="105,0 210,105 105,210 0,105" />
</clipPath>
</defs>
<polygon points="100,0 200,100, 100,200, 0,100" class="background" />
<polygon points="100,0 200,100, 100,200, 0,100" class="inset-shadow" clip-path="url(#inner-clip)" />
<polygon points="100,0 200,100, 100,200, 0,100" class="border" />
<!-- Background: clipped to the inner edge -->
<polygon points="105,10 200,105 105,200 10,105" fill="green" />
<!-- Inset shadow -->
<polygon points="105,5 205,105 105,205 5,105" fill="none" stroke="purple" stroke-width="30" clip-path="url(#inner-clip)" />
<!-- Border -->
<polygon points="105,5 205,105 105,205 5,105" fill="none" stroke="black" stroke-width="10" />
</svg>

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
<link rel="match" href="border-shape-inset-shadow-ref.html">
<meta name="fuzzy" content="maxDifference=0-255;totalPixels=0-2000">
<style>
body {
margin: 0;

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<title>CSS Borders: border-shape with box-shadow reference</title>
<style>
body {
margin: 0;
padding: 50px;
}
#target {
width: 100px;
height: 100px;
background: green;
/* Use border-radius as the canonical circle reference */
border-radius: 50px;
box-shadow: 0 0 10px 0 black;
}
</style>
<body>
<div id="target"></div>
</body>
</html>

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<title>CSS Borders: border-shape with box-shadow (blur > 0)</title>
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
<link rel="match" href="border-shape-shadow-blur-ref.html">
<meta name="fuzzy" content="maxDifference=0-32;totalPixels=0-500">
<meta name="assert" content="box-shadow blur on a circle border-shape should exactly match the blur on a border-radius circle.">
<style>
body {
margin: 0;
padding: 50px;
}
#target {
width: 100px;
height: 100px;
background: green;
border-shape: circle(50px at 50% 50%);
box-shadow: 0 0 10px 0 black;
}
</style>
<body>
<div id="target"></div>
</body>
</html>

View File

@@ -0,0 +1,13 @@
<!doctype html>
<style>
#ref1, #ref2 {
width: 100px;
height: 100px;
border-radius: 50px;
margin: 100px;
box-shadow: 0 0 10px 0 black;
}
</style>
<p>The following two should look the same:</p>
<div id="ref1"></div>
<div id="ref2"></div>

View File

@@ -0,0 +1,22 @@
<!doctype html>
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
<link rel="match" href="border-shape-shadow-circle-ref.html">
<style>
#target {
width: 100px;
height: 100px;
border-shape: circle(50px at 50% 50%);
}
#ref {
width: 100px;
height: 100px;
border-radius: 50px;
}
#target, #ref {
margin: 100px;
box-shadow: 0 0 10px 0 black;
}
</style>
<p>The following two should look the same:</p>
<div id="target"></div>
<div id="ref"></div>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<title>CSS Borders: border-shape with inset box-shadow reference</title>
<style>
body {
margin: 0;
padding: 50px;
}
#target {
width: 100px;
height: 100px;
background: green;
/* Use border-radius as the canonical circle reference */
border-radius: 50px;
box-shadow: inset 0 0 10px 0 black;
}
</style>
<body>
<div id="target"></div>
</body>
</html>

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<title>CSS Borders: border-shape with inset box-shadow (blur > 0)</title>
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
<link rel="match" href="border-shape-shadow-inset-blur-ref.html">
<meta name="fuzzy" content="maxDifference=0-2;totalPixels=0-150">
<meta name="assert" content="inset box-shadow blur on a circle border-shape should exactly match the blur on a border-radius circle.">
<style>
body {
margin: 0;
padding: 50px;
}
#target {
width: 100px;
height: 100px;
background: green;
border-shape: circle(50px at 50% 50%);
box-shadow: inset 0 0 10px 0 black;
}
</style>
<body>
<div id="target"></div>
</body>
</html>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<title>CSS Borders: border-shape with transparent background ref</title>
<style>
body { padding: 50px; }
#target {
width: 100px; height: 100px;
background: transparent;
border-radius: 50px;
box-shadow: 0 0 0 20px black;
}
</style>
<body>
<div id="target"></div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<title>CSS Borders: border-shape with transparent background</title>
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
<link rel="match" href="border-shape-shadow-transparent-ref.html">
<meta name="fuzzy" content="maxDifference=0-72;totalPixels=0-500">
<style>
body { padding: 50px; }
#target {
width: 100px; height: 100px;
background: transparent;
border-shape: circle(50px at 50% 50%);
box-shadow: 0 0 0 20px black;
}
</style>
<body>
<div id="target"></div>
</body>
</html>

View File

@@ -1,43 +1,29 @@
<!DOCTYPE html>
<style>
body {
margin: 0;
}
svg {
position: absolute;
top: 0;
left: 0;
width: 220px;
height: 220px;
overflow: visible;
}
.outer-shadow {
fill: blue;
stroke: blue;
stroke-width: 10px;
transform: translate(10px, 10px);
}
.outer-border {
fill: yellow;
}
.inner-fill {
fill: green;
}
.inset-shadow {
fill: none;
stroke: purple;
stroke-width: 10px;
}
body {
margin: 0;
}
svg {
display: block;
width: 220px;
height: 220px;
}
</style>
<svg viewBox="0 0 220 220" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="inner-clip">
<circle cx="100" cy="100" r="50" />
</clipPath>
</defs>
<circle cx="100" cy="100" r="100" class="outer-shadow" />
<circle cx="100" cy="100" r="100" class="outer-border" />
<circle cx="100" cy="100" r="50" class="inner-fill" />
<circle cx="100" cy="100" r="50" class="inset-shadow" clip-path="url(#inner-clip)" />
<!-- Outer shadow: r=105 at (110,110) -->
<circle cx="110" cy="110" r="105" fill="blue" />
<!-- Outer border shape (filled with border color) -->
<circle cx="100" cy="100" r="100" fill="yellow" />
<!-- Background (filled inside inner shape) -->
<circle cx="100" cy="100" r="50" fill="green" />
<!-- Inset shadow: 10px stroke on r=50 circle, clipped to inside -->
<circle cx="100" cy="100" r="50" fill="none" stroke="purple" stroke-width="10" clip-path="url(#inner-clip)" />
</svg>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape">
<link rel="match" href="border-shape-two-shapes-shadow-ref.html">
<meta name="fuzzy" content="maxDifference=0-110;totalPixels=0-1400">
<meta name="fuzzy" content="maxDifference=0-91;totalPixels=0-2000">
<style>
body {
margin: 0;

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<title>The revert-rule keyword: custom properties</title>
<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-rule-keyword">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
#test1 {
--a: red;
--b: green;
}
#test1 {
--a: green;
--b: revert-rule;
}
#test1 {
--a: revert-rule;
--b: revert-rule;
}
</style>
<div id=test1></div>
<script>
test(() => {
assert_true(CSS.supports('color:revert-rule'));
assert_equals(getComputedStyle(test1).getPropertyValue("--a"), 'green')
assert_equals(getComputedStyle(test1).getPropertyValue("--b"), 'green')
}, 'revert-rule in a custom property');
</script>

Some files were not shown because too many files have changed in this diff Show More