diff --git a/lib/cache/cache.js b/lib/cache/cache.js new file mode 100644 index 00000000000..8838e31c5f3 --- /dev/null +++ b/lib/cache/cache.js @@ -0,0 +1,834 @@ +'use strict' + +const { kConstruct } = require('./symbols') +const { urlEquals, fieldValues: getFieldValues } = require('./util') +const { kEnumerableProperty, isDisturbed } = require('../core/util') +const { kHeadersList } = require('../core/symbols') +const { webidl } = require('../fetch/webidl') +const { Response, cloneResponse } = require('../fetch/response') +const { Request } = require('../fetch/request') +const { kState, kHeaders, kGuard, kRealm } = require('../fetch/symbols') +const { fetching } = require('../fetch/index') +const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = require('../fetch/util') +const assert = require('assert') +const { getGlobalDispatcher } = require('../global') + +/** + * @see https://w3c.github.io/ServiceWorker/#dfn-cache-batch-operation + * @typedef {Object} CacheBatchOperation + * @property {'delete' | 'put'} type + * @property {any} request + * @property {any} response + * @property {import('../../types/cache').CacheQueryOptions} options + */ + +/** + * @see https://w3c.github.io/ServiceWorker/#dfn-request-response-list + * @typedef {[any, any][]} requestResponseList + */ + +class Cache { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-request-response-list + * @type {requestResponseList} + */ + #relevantRequestResponseList = [] + + constructor () { + if (arguments[0] !== kConstruct) { + webidl.illegalConstructor() + } + + this.#relevantRequestResponseList = arguments[1] + } + + async match (request, options = {}) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.match' }) + + request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + const p = await this.matchAll(request, options) + + if (p.length === 0) { + return + } + + return p[0] + } + + async matchAll (request = undefined, options = {}) { + webidl.brandCheck(this, Cache) + + if (request !== undefined) request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + // 1. + let r = null + + // 2. + if (request !== undefined) { + if (request instanceof Request) { + // 2.1.1 + r = request[kState] + + // 2.1.2 + if (r.method !== 'GET' && !options.ignoreMethod) { + return [] + } + } else if (typeof request === 'string') { + // 2.2.1 + r = new Request(request)[kState] + } + } + + // 5. + // 5.1 + const responses = [] + + // 5.2 + if (request === undefined) { + // 5.2.1 + for (const requestResponse of this.#relevantRequestResponseList) { + responses.push(requestResponse[1]) + } + } else { // 5.3 + // 5.3.1 + const requestResponses = this.#queryCache(r, options) + + // 5.3.2 + for (const requestResponse of requestResponses) { + responses.push(requestResponse[1]) + } + } + + // 5.4 + // We don't implement CORs so we don't need to loop over the responses, yay! + + // 5.5.1 + const responseList = [] + + // 5.5.2 + for (const response of responses) { + // 5.5.2.1 + const responseObject = new Response(response.body?.source ?? null) + const body = responseObject[kState].body + responseObject[kState] = response + responseObject[kState].body = body + responseObject[kHeaders][kHeadersList] = response.headersList + responseObject[kHeaders][kGuard] = 'immutable' + + responseList.push(responseObject) + } + + // 6. + return Object.freeze(responseList) + } + + async add (request) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.add' }) + + request = webidl.converters.RequestInfo(request) + + // 1. + const requests = [request] + + // 2. + const responseArrayPromise = this.addAll(requests) + + // 3. + return await responseArrayPromise + } + + async addAll (requests) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.addAll' }) + + requests = webidl.converters['sequence'](requests) + + // 1. + const responsePromises = [] + + // 2. + const requestList = [] + + // 3. + for (const request of requests) { + if (typeof request === 'string') { + continue + } + + // 3.1 + const r = request[kState] + + // 3.2 + if (!urlIsHttpHttpsScheme(r.url) || r.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Expected http/s scheme when method is not GET.' + }) + } + } + + // 4. + /** @type {ReturnType[]} */ + const fetchControllers = [] + + // 5. + for (const request of requests) { + // 5.1 + const r = new Request(request)[kState] + + // 5.2 + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Expected http/s scheme.' + }) + } + + // 5.4 + r.initiator = 'fetch' + r.destination = 'subresource' + + // 5.5 + requestList.push(r) + + // 5.6 + const responsePromise = createDeferredPromise() + + // 5.7 + fetchControllers.push(fetching({ + request: r, + dispatcher: getGlobalDispatcher(), + processResponse (response) { + // 1. + if (response.type === 'error' || response.status === 206 || response.status < 200 || response.status > 299) { + responsePromise.reject(webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Received an invalid status code or the request failed.' + })) + } else if (response.headersList.contains('vary')) { // 2. + // 2.1 + const fieldValues = getFieldValues(response.headersList.get('vary')) + + // 2.2 + for (const fieldValue of fieldValues) { + // 2.2.1 + if (fieldValue === '*') { + responsePromise.reject(webidl.errors.exception({ + header: 'Cache.addAll', + message: 'invalid vary field value' + })) + + for (const controller of fetchControllers) { + controller.abort() + } + + return + } + } + } + }, + processResponseEndOfBody (response) { + // 1. + if (response.aborted) { + responsePromise.reject(new DOMException('aborted', 'AbortError')) + return + } + + // 2. + responsePromise.resolve(response) + } + })) + + // 5.8 + responsePromises.push(responsePromise.promise) + } + + // 6. + const p = Promise.all(responsePromises) + + // 7. + const responses = await p + + // 7.1 + const operations = [] + + // 7.2 + let index = 0 + + // 7.3 + for (const response of responses) { + // 7.3.1 + /** @type {CacheBatchOperation} */ + const operation = { + type: 'put', // 7.3.2 + request: requestList[index], // 7.3.3 + response // 7.3.4 + } + + operations.push(operation) // 7.3.5 + + index++ // 7.3.6 + } + + // 7.5 + const cacheJobPromise = createDeferredPromise() + + // 7.6.1 + let errorData = null + + // 7.6.2 + try { + this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + // 7.6.3 + queueMicrotask(() => { + // 7.6.3.1 + if (errorData === null) { + cacheJobPromise.resolve(undefined) + } else { + // 7.6.3.2 + cacheJobPromise.reject(errorData) + } + }) + + // 7.7 + return cacheJobPromise.promise + } + + async put (request, response) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 2, { header: 'Cache.put' }) + + request = webidl.converters.RequestInfo(request) + response = webidl.converters.Response(response) + + // 1. + let innerRequest = null + + // 2. + if (typeof request !== 'string') { + innerRequest = request[kState] + } else { // 3. + innerRequest = new Request(request)[kState] + } + + // 4. + if (!urlIsHttpHttpsScheme(innerRequest.url) || innerRequest.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Expected an http/s scheme when method is not GET' + }) + } + + // 5. + const innerResponse = response[kState] + + // 6. + if (innerResponse.status === 206) { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Got 206 status' + }) + } + + // 7. + if (innerResponse.headersList.contains('vary')) { + // 7.1. + const fieldValues = getFieldValues(innerResponse.headersList.get('vary')) + + // 7.2. + for (const fieldValue of fieldValues) { + // 7.2.1 + if (fieldValue === '*') { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Got * vary field value' + }) + } + } + } + + // 8. + if (innerResponse.body && (isDisturbed(innerResponse.body.stream) || innerResponse.body.stream.locked)) { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Response body is locked or disturbed' + }) + } + + // 9. + const clonedResponse = cloneResponse(innerResponse) + + // 10. + const bodyReadPromise = createDeferredPromise() + + // 11. + if (innerResponse.body != null) { + // 11.1 + const stream = innerResponse.body.stream + + // 11.2 + const reader = stream.getReader() + + // 11.3 + readAllBytes( + reader, + (bytes) => bodyReadPromise.resolve(bytes), + (error) => bodyReadPromise.reject(error) + ) + } else { + bodyReadPromise.resolve(undefined) + } + + // 12. + /** @type {CacheBatchOperation[]} */ + const operations = [] + + // 13. + /** @type {CacheBatchOperation} */ + const operation = { + type: 'put', // 14. + request: innerRequest, // 15. + response: clonedResponse // 16. + } + + // 17. + operations.push(operation) + + // 19. + const bytes = await bodyReadPromise.promise + + if (clonedResponse.body != null) { + clonedResponse.body.source = bytes + } + + // 19.1 + const cacheJobPromise = createDeferredPromise() + + // 19.2.1 + let errorData = null + + // 19.2.2 + try { + this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + // 19.2.3 + queueMicrotask(() => { + // 19.2.3.1 + if (errorData === null) { + cacheJobPromise.resolve() + } else { // 19.2.3.2 + cacheJobPromise.reject(errorData) + } + }) + + return cacheJobPromise.promise + } + + async delete (request, options = {}) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.delete' }) + + request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + /** + * @type {Request} + */ + let r = null + + if (request instanceof Request) { + r = request[kState] + + if (r.method !== 'GET' && !options.ignoreMethod) { + return false + } + } else { + assert(typeof request === 'string') + + r = new Request(r)[kState] + } + + /** @type {CacheBatchOperation[]} */ + const operations = [] + + /** @type {CacheBatchOperation} */ + const operation = { + type: 'delete', + request: r, + options + } + + operations.push(operation) + + const cacheJobPromise = createDeferredPromise() + + let errorData = null + let requestResponses + + try { + requestResponses = this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + queueMicrotask(() => { + if (errorData === null) { + cacheJobPromise.resolve(!!requestResponses?.length) + } else { + cacheJobPromise.reject(errorData) + } + }) + + return cacheJobPromise.promise + } + + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cache-keys + * @param {any} request + * @param {import('../../types/cache').CacheQueryOptions} options + * @returns {readonly Request[]} + */ + async keys (request = undefined, options = {}) { + webidl.brandCheck(this, Cache) + + if (request !== undefined) request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + // 1. + let r = null + + // 2. + if (request !== undefined) { + // 2.1 + if (request instanceof Request) { + // 2.1.1 + r = request[kState] + + // 2.1.2 + if (r.method !== 'GET' && !options.ignoreMethod) { + return [] + } + } else if (typeof request === 'string') { // 2.2 + r = new Request(request)[kState] + } + } + + // 4. + const promise = createDeferredPromise() + + // 5. + // 5.1 + const requests = [] + + // 5.2 + if (request === undefined) { + // 5.2.1 + for (const requestResponse of this.#relevantRequestResponseList) { + // 5.2.1.1 + requests.push(requestResponse[0]) + } + } else { // 5.3 + // 5.3.1 + const requestResponses = this.#queryCache(r, options) + + // 5.3.2 + for (const requestResponse of requestResponses) { + // 5.3.2.1 + requests.push(requestResponse[0]) + } + } + + // 5.4 + queueMicrotask(() => { + // 5.4.1 + const requestList = [] + + // 5.4.2 + for (const request of requests) { + const requestObject = new Request('https://a') + requestObject[kState] = request + requestObject[kHeaders][kHeadersList] = request.headersList + requestObject[kHeaders][kGuard] = 'immutable' + requestObject[kRealm] = request.client + + // 5.4.2.1 + requestList.push(requestObject) + } + + // 5.4.3 + promise.resolve(Object.freeze(requestList)) + }) + + return promise.promise + } + + /** + * @see https://w3c.github.io/ServiceWorker/#batch-cache-operations + * @param {CacheBatchOperation[]} operations + * @returns {requestResponseList} + */ + #batchCacheOperations (operations) { + // 1. + const cache = this.#relevantRequestResponseList + + // 2. + const backupCache = [...cache] + + // 3. + const addedItems = [] + + // 4.1 + const resultList = [] + + try { + // 4.2 + for (const operation of operations) { + // 4.2.1 + if (operation.type !== 'delete' && operation.type !== 'put') { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'operation type does not match "delete" or "put"' + }) + } + + // 4.2.2 + if (operation.type === 'delete' && operation.response != null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'delete operation should not have an associated response' + }) + } + + // 4.2.3 + if (this.#queryCache(operation.request, operation.options, addedItems).length) { + throw new DOMException('???', 'InvalidStateError') + } + + // 4.2.4 + let requestResponses + + // 4.2.5 + if (operation.type === 'delete') { + // 4.2.5.1 + requestResponses = this.#queryCache(operation.request, operation.options) + + // TODO: the spec is wrong, this is needed to pass WPTs + if (requestResponses.length === 0) { + return [] + } + + // 4.2.5.2 + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse) + assert(idx !== -1) + + // 4.2.5.2.1 + cache.splice(idx, 1) + } + } else if (operation.type === 'put') { // 4.2.6 + // 4.2.6.1 + if (operation.response == null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'put operation should have an associated response' + }) + } + + // 4.2.6.2 + const r = operation.request + + // 4.2.6.3 + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'expected http or https scheme' + }) + } + + // 4.2.6.4 + if (r.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'not get method' + }) + } + + // 4.2.6.5 + if (operation.options != null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'options must not be defined' + }) + } + + // 4.2.6.6 + requestResponses = this.#queryCache(operation.request) + + // 4.2.6.7 + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse) + assert(idx !== -1) + + // 4.2.6.7.1 + cache.splice(idx, 1) + } + + // 4.2.6.8 + cache.push([operation.request, operation.response]) + + // 4.2.6.10 + addedItems.push([operation.request, operation.response]) + } + + // 4.2.7 + resultList.push([operation.request, operation.response]) + } + + // 4.3 + return resultList + } catch (e) { // 5. + // 5.1 + this.#relevantRequestResponseList.length = 0 + + // 5.2 + this.#relevantRequestResponseList = backupCache + + // 5.3 + throw e + } + } + + /** + * @see https://w3c.github.io/ServiceWorker/#query-cache + * @param {any} requestQuery + * @param {import('../../types/cache').CacheQueryOptions} options + * @param {requestResponseList} targetStorage + * @returns {requestResponseList} + */ + #queryCache (requestQuery, options, targetStorage) { + /** @type {requestResponseList} */ + const resultList = [] + + const storage = targetStorage ?? this.#relevantRequestResponseList + + for (const requestResponse of storage) { + const [cachedRequest, cachedResponse] = requestResponse + if (this.#requestMatchesCachedItem(requestQuery, cachedRequest, cachedResponse, options)) { + resultList.push(requestResponse) + } + } + + return resultList + } + + /** + * @see https://w3c.github.io/ServiceWorker/#request-matches-cached-item-algorithm + * @param {any} requestQuery + * @param {any} request + * @param {any | null} response + * @param {import('../../types/cache').CacheQueryOptions | undefined} options + * @returns {boolean} + */ + #requestMatchesCachedItem (requestQuery, request, response = null, options) { + // if (options?.ignoreMethod === false && request.method === 'GET') { + // return false + // } + + /** @type {URL} */ + const queryURL = requestQuery.url + + /** @type {URL} */ + const cachedURL = request.url + + if (options?.ignoreSearch) { + cachedURL.search = '' + + queryURL.search = '' + } + + if (!urlEquals(queryURL, cachedURL, true)) { + return false + } + + if ( + response == null || + options?.ignoreVary || + !response.headersList.contains('vary') + ) { + return true + } + + const fieldValues = getFieldValues(response.headersList.get('vary')) + + for (const fieldValue of fieldValues) { + if (fieldValue === '*') { + return false + } + + const requestValue = request.headersList.get(fieldValue) + const queryValue = requestQuery.headersList.get(fieldValue) + + // If one has the header and the other doesn't, or one has + // a different value than the other, return false + if (requestValue !== queryValue) { + return false + } + } + + return true + } +} + +Object.defineProperties(Cache.prototype, { + [Symbol.toStringTag]: { + value: 'Cache', + configurable: true + }, + match: kEnumerableProperty, + matchAll: kEnumerableProperty, + add: kEnumerableProperty, + addAll: kEnumerableProperty, + put: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty +}) + +webidl.converters.CacheQueryOptions = webidl.dictionaryConverter([ + { + key: 'ignoreSearch', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'ignoreMethod', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'ignoreVary', + converter: webidl.converters.boolean, + defaultValue: false + } +]) + +webidl.converters.Response = webidl.interfaceConverter(Response) + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.RequestInfo +) + +module.exports = { + Cache +} diff --git a/lib/cache/cachestorage.js b/lib/cache/cachestorage.js new file mode 100644 index 00000000000..4a6795932b7 --- /dev/null +++ b/lib/cache/cachestorage.js @@ -0,0 +1,133 @@ +'use strict' + +const { kConstruct } = require('./symbols') +const { Cache } = require('./cache') +const { webidl } = require('../fetch/webidl') +const { kEnumerableProperty } = require('../core/util') + +class CacheStorage { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-name-to-cache-map + * @type {Map} + */ + async has (cacheName) { + webidl.brandCheck(this, CacheStorage) + webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.has' }) + + cacheName = webidl.converters.DOMString(cacheName) + + // 2.1.1 + // 2.2 + return this.#caches.has(cacheName) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cachestorage-open + * @param {string} cacheName + * @returns {Promise} + */ + async open (cacheName) { + webidl.brandCheck(this, CacheStorage) + webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.open' }) + + cacheName = webidl.converters.DOMString(cacheName) + + // 2.1 + if (this.#caches.has(cacheName)) { + // await caches.open('v1') !== await caches.open('v1') + + // 2.1.1 + const cache = this.#caches.get(cacheName) + + // 2.1.1.1 + return new Cache(kConstruct, cache) + } + + // 2.2 + const cache = [] + + // 2.3 + this.#caches.set(cacheName, cache) + + // 2.4 + return new Cache(kConstruct, cache) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-delete + * @param {string} cacheName + * @returns {Promise} + */ + async delete (cacheName) { + webidl.brandCheck(this, CacheStorage) + webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.delete' }) + + cacheName = webidl.converters.DOMString(cacheName) + + // 1. + // 2. + const cacheExists = this.#caches.has(cacheName) + + // 2.1 + if (!cacheExists) { + return false + } + + // 2.3.1 + this.#caches.delete(cacheName) + + // 2.3.2 + return true + } + + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-keys + * @returns {string[]} + */ + async keys () { + webidl.brandCheck(this, CacheStorage) + + // 2.1 + const keys = this.#caches.keys() + + // 2.2 + return [...keys] + } +} + +Object.defineProperties(CacheStorage.prototype, { + [Symbol.toStringTag]: { + value: 'CacheStorage', + configurable: true + }, + match: kEnumerableProperty, + has: kEnumerableProperty, + open: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty +}) + +module.exports = { + CacheStorage +} diff --git a/lib/cache/symbols.js b/lib/cache/symbols.js new file mode 100644 index 00000000000..f9b19740af8 --- /dev/null +++ b/lib/cache/symbols.js @@ -0,0 +1,5 @@ +'use strict' + +module.exports = { + kConstruct: Symbol('constructable') +} diff --git a/lib/cache/util.js b/lib/cache/util.js new file mode 100644 index 00000000000..44d52b789ed --- /dev/null +++ b/lib/cache/util.js @@ -0,0 +1,49 @@ +'use strict' + +const assert = require('assert') +const { URLSerializer } = require('../fetch/dataURL') +const { isValidHeaderName } = require('../fetch/util') + +/** + * @see https://url.spec.whatwg.org/#concept-url-equals + * @param {URL} A + * @param {URL} B + * @param {boolean | undefined} excludeFragment + * @returns {boolean} + */ +function urlEquals (A, B, excludeFragment = false) { + const serializedA = URLSerializer(A, excludeFragment) + + const serializedB = URLSerializer(B, excludeFragment) + + return serializedA === serializedB +} + +/** + * @see https://github.com/chromium/chromium/blob/694d20d134cb553d8d89e5500b9148012b1ba299/content/browser/cache_storage/cache_storage_cache.cc#L260-L262 + * @param {string} header + */ +function fieldValues (header) { + assert(header !== null) + + const values = [] + + for (let value of header.split(',')) { + value = value.trim() + + if (!value.length) { + continue + } else if (!isValidHeaderName(value)) { + continue + } + + values.push(value) + } + + return values +} + +module.exports = { + urlEquals, + fieldValues +} diff --git a/lib/fetch/response.js b/lib/fetch/response.js index ff06bfb47d0..96cacbce157 100644 --- a/lib/fetch/response.js +++ b/lib/fetch/response.js @@ -569,5 +569,6 @@ module.exports = { makeResponse, makeAppropriateNetworkError, filterResponse, - Response + Response, + cloneResponse } diff --git a/lib/fetch/util.js b/lib/fetch/util.js index 23023262d14..400687ba2e7 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -1028,5 +1028,6 @@ module.exports = { isomorphicDecode, urlIsLocal, urlHasHttpsScheme, - urlIsHttpHttpsScheme + urlIsHttpHttpsScheme, + readAllBytes } diff --git a/lib/fetch/webidl.js b/lib/fetch/webidl.js index e55de139505..38a05e65759 100644 --- a/lib/fetch/webidl.js +++ b/lib/fetch/webidl.js @@ -51,6 +51,13 @@ webidl.argumentLengthCheck = function ({ length }, min, ctx) { } } +webidl.illegalConstructor = function () { + throw webidl.errors.exception({ + header: 'TypeError', + message: 'Illegal constructor' + }) +} + // https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values webidl.util.Type = function (V) { switch (typeof V) { diff --git a/test/wpt/runner/worker.mjs b/test/wpt/runner/worker.mjs index 0b323b4c972..638f53d7a57 100644 --- a/test/wpt/runner/worker.mjs +++ b/test/wpt/runner/worker.mjs @@ -9,6 +9,9 @@ import { } from '../../../index.js' import { CloseEvent } from '../../../lib/websocket/events.js' import { WebSocket } from '../../../lib/websocket/websocket.js' +import { Cache } from '../../../lib/cache/cache.js' +import { CacheStorage } from '../../../lib/cache/cachestorage.js' +import { kConstruct } from '../../../lib/cache/symbols.js' const { initScripts, meta, test, url, path } = workerData @@ -74,6 +77,18 @@ Object.defineProperties(globalThis, { ...globalPropertyDescriptors, // See https://github.com/nodejs/node/pull/45659 value: buffer.Blob + }, + caches: { + ...globalPropertyDescriptors, + value: new CacheStorage(kConstruct) + }, + Cache: { + ...globalPropertyDescriptors, + value: Cache + }, + CacheStorage: { + ...globalPropertyDescriptors, + value: CacheStorage } }) diff --git a/test/wpt/server/server.mjs b/test/wpt/server/server.mjs index b06f86d13d5..75039acb916 100644 --- a/test/wpt/server/server.mjs +++ b/test/wpt/server/server.mjs @@ -32,6 +32,10 @@ const server = createServer(async (req, res) => { const fullUrl = new URL(req.url, `http://localhost:${server.address().port}`) switch (fullUrl.pathname) { + case '/service-workers/cache-storage/resources/blank.html': { + res.setHeader('content-type', 'text/html') + // fall through + } case '/fetch/content-encoding/resources/foo.octetstream.gz': case '/fetch/content-encoding/resources/foo.text.gz': case '/fetch/api/resources/cors-top.txt': @@ -357,9 +361,20 @@ const server = createServer(async (req, res) => { res.end('') return } + case '/resources/simple.txt': { + res.end(readFileSync(join(tests, 'service-workers/service-worker', fullUrl.pathname), 'utf-8')) + return + } + case '/resources/fetch-status.py': { + const status = Number(fullUrl.searchParams.get('status')) + + res.statusCode = status + res.end() + return + } default: { res.statusCode = 200 - res.end('body') + res.end(fullUrl.toString()) } } }).listen(0) diff --git a/test/wpt/start-cacheStorage.mjs b/test/wpt/start-cacheStorage.mjs new file mode 100644 index 00000000000..a630e052285 --- /dev/null +++ b/test/wpt/start-cacheStorage.mjs @@ -0,0 +1,26 @@ +import { WPTRunner } from './runner/runner.mjs' +import { join } from 'path' +import { fileURLToPath } from 'url' +import { fork } from 'child_process' +import { on } from 'events' + +const serverPath = fileURLToPath(join(import.meta.url, '../server/server.mjs')) + +const child = fork(serverPath, [], { + stdio: ['pipe', 'pipe', 'pipe', 'ipc'] +}) + +child.on('exit', (code) => process.exit(code)) + +for await (const [message] of on(child, 'message')) { + if (message.server) { + const runner = new WPTRunner('service-workers/cache-storage', message.server) + runner.run() + + runner.once('completion', () => { + if (child.connected) { + child.send('shutdown') + } + }) + } +} diff --git a/test/wpt/status/service-workers/cache-storage.status.json b/test/wpt/status/service-workers/cache-storage.status.json new file mode 100644 index 00000000000..ed84c140eca --- /dev/null +++ b/test/wpt/status/service-workers/cache-storage.status.json @@ -0,0 +1,54 @@ +{ + "cache-storage": { + "cache-abort.https.any.js": { + "skip": true + }, + "cache-storage-buckets.https.any.js": { + "skip": true, + "note": "navigator is not defined" + }, + "cache-storage-match.https.any.js": { + "skip": true, + "note": "CacheStorage.prototype.match isnt implemented yet" + }, + "cache-put.https.any.js": { + "note": "probably can be fixed", + "fail": [ + "Cache.put with a VARY:* opaque response should not reject", + "Cache.put with opaque-filtered HTTP 206 response", + "Cache.put with a relative URL" + ] + }, + "cache-match.https.any.js": { + "note": "requires https server", + "fail": [ + "cors-exposed header should be stored correctly.", + "Cache.match ignores vary headers on opaque response." + ] + }, + "cache-delete.https.any.js": { + "note": "spec bug? - https://github.com/w3c/ServiceWorker/issues/1677 (first fail)", + "fail": [ + "Cache.delete called with a string URL", + "Cache.delete with ignoreSearch option (when it is specified as false)" + ] + }, + "cache-keys.https.any.js": { + "note": "probably can be fixed", + "fail": [ + "Cache.keys with ignoreSearch option (request with search parameters)", + "Cache.keys without parameters", + "Cache.keys with explicitly undefined request" + ] + }, + "cache-matchAll.https.any.js": { + "note": "probably can be fixed", + "fail": [ + "Cache.matchAll with ignoreSearch option (request with search parameters)", + "Cache.matchAll without parameters", + "Cache.matchAll with explicitly undefined request", + "Cache.matchAll with explicitly undefined request and empty options" + ] + } + } +} diff --git a/test/wpt/tests/service-workers/META.yml b/test/wpt/tests/service-workers/META.yml new file mode 100644 index 00000000000..03a0dd0fe16 --- /dev/null +++ b/test/wpt/tests/service-workers/META.yml @@ -0,0 +1,6 @@ +spec: https://w3c.github.io/ServiceWorker/ +suggested_reviewers: + - asutherland + - mkruisselbrink + - mattto + - wanderview diff --git a/test/wpt/tests/service-workers/cache-storage/META.yml b/test/wpt/tests/service-workers/cache-storage/META.yml new file mode 100644 index 00000000000..bf34474f74a --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/META.yml @@ -0,0 +1,3 @@ +suggested_reviewers: + - inexorabletash + - wanderview diff --git a/test/wpt/tests/service-workers/cache-storage/cache-abort.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-abort.https.any.js new file mode 100644 index 00000000000..960d1bb1bff --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-abort.https.any.js @@ -0,0 +1,81 @@ +// META: title=Cache Storage: Abort +// META: global=window,worker +// META: script=./resources/test-helpers.js +// META: script=/common/utils.js +// META: timeout=long + +// We perform the same tests on put, add, addAll. Parameterise the tests to +// reduce repetition. +const methodsToTest = { + put: async (cache, request) => { + const response = await fetch(request); + return cache.put(request, response); + }, + add: async (cache, request) => cache.add(request), + addAll: async (cache, request) => cache.addAll([request]), +}; + +for (const method in methodsToTest) { + const perform = methodsToTest[method]; + + cache_test(async (cache, test) => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + const request = new Request('../resources/simple.txt', { signal }); + return promise_rejects_dom(test, 'AbortError', perform(cache, request), + `${method} should reject`); + }, `${method}() on an already-aborted request should reject with AbortError`); + + cache_test(async (cache, test) => { + const controller = new AbortController(); + const signal = controller.signal; + const request = new Request('../resources/simple.txt', { signal }); + const promise = perform(cache, request); + controller.abort(); + return promise_rejects_dom(test, 'AbortError', promise, + `${method} should reject`); + }, `${method}() synchronously followed by abort should reject with ` + + `AbortError`); + + cache_test(async (cache, test) => { + const controller = new AbortController(); + const signal = controller.signal; + const stateKey = token(); + const abortKey = token(); + const request = new Request( + `../../../fetch/api/resources/infinite-slow-response.py?stateKey=${stateKey}&abortKey=${abortKey}`, + { signal }); + + const promise = perform(cache, request); + + // Wait for the server to start sending the response body. + let opened = false; + do { + // Normally only one fetch to 'stash-take' is needed, but the fetches + // will be served in reverse order sometimes + // (i.e., 'stash-take' gets served before 'infinite-slow-response'). + + const response = + await fetch(`../../../fetch/api/resources/stash-take.py?key=${stateKey}`); + const body = await response.json(); + if (body === 'open') opened = true; + } while (!opened); + + // Sadly the above loop cannot guarantee that the browser has started + // processing the response body. This delay is needed to make the test + // failures non-flaky in Chrome version 66. My deepest apologies. + await new Promise(resolve => setTimeout(resolve, 250)); + + controller.abort(); + + await promise_rejects_dom(test, 'AbortError', promise, + `${method} should reject`); + + // infinite-slow-response.py doesn't know when to stop. + return fetch(`../../../fetch/api/resources/stash-put.py?key=${abortKey}`); + }, `${method}() followed by abort after headers received should reject ` + + `with AbortError`); +} + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-add.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-add.https.any.js new file mode 100644 index 00000000000..eca516abd5f --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-add.https.any.js @@ -0,0 +1,368 @@ +// META: title=Cache.add and Cache.addAll +// META: global=window,worker +// META: script=/common/get-host-info.sub.js +// META: script=./resources/test-helpers.js +// META: timeout=long + +const { REMOTE_HOST } = get_host_info(); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.add(), + 'Cache.add should throw a TypeError when no arguments are given.'); + }, 'Cache.add called with no arguments'); + +cache_test(function(cache) { + return cache.add('./resources/simple.txt') + .then(function(result) { + assert_equals(result, undefined, + 'Cache.add should resolve with undefined on success.'); + return cache.match('./resources/simple.txt'); + }) + .then(function(response) { + assert_class_string(response, 'Response', + 'Cache.add should put a resource in the cache.'); + return response.text(); + }) + .then(function(body) { + assert_equals(body, 'a simple text file\n', + 'Cache.add should retrieve the correct body.'); + }); + }, 'Cache.add called with relative URL specified as a string'); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.add('javascript://this-is-not-http-mmkay'), + 'Cache.add should throw a TypeError for non-HTTP/HTTPS URLs.'); + }, 'Cache.add called with non-HTTP/HTTPS URL'); + +cache_test(function(cache) { + var request = new Request('./resources/simple.txt'); + return cache.add(request) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.add should resolve with undefined on success.'); + }); + }, 'Cache.add called with Request object'); + +cache_test(function(cache, test) { + var request = new Request('./resources/simple.txt', + {method: 'POST', body: 'This is a body.'}); + return promise_rejects_js( + test, + TypeError, + cache.add(request), + 'Cache.add should throw a TypeError for non-GET requests.'); + }, 'Cache.add called with POST request'); + +cache_test(function(cache) { + var request = new Request('./resources/simple.txt'); + return cache.add(request) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.add should resolve with undefined on success.'); + }) + .then(function() { + return cache.add(request); + }) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.add should resolve with undefined on success.'); + }); + }, 'Cache.add called twice with the same Request object'); + +cache_test(function(cache) { + var request = new Request('./resources/simple.txt'); + return request.text() + .then(function() { + assert_false(request.bodyUsed); + }) + .then(function() { + return cache.add(request); + }); + }, 'Cache.add with request with null body (not consumed)'); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.add('./resources/fetch-status.py?status=206'), + 'Cache.add should reject on partial response'); + }, 'Cache.add with 206 response'); + +cache_test(function(cache, test) { + var urls = ['./resources/fetch-status.py?status=206', + './resources/fetch-status.py?status=200']; + var requests = urls.map(function(url) { + return new Request(url); + }); + return promise_rejects_js( + test, + TypeError, + cache.addAll(requests), + 'Cache.addAll should reject with TypeError if any request fails'); + }, 'Cache.addAll with 206 response'); + +cache_test(function(cache, test) { + var urls = ['./resources/fetch-status.py?status=206', + './resources/fetch-status.py?status=200']; + var requests = urls.map(function(url) { + var cross_origin_url = new URL(url, location.href); + cross_origin_url.hostname = REMOTE_HOST; + return new Request(cross_origin_url.href, { mode: 'no-cors' }); + }); + return promise_rejects_js( + test, + TypeError, + cache.addAll(requests), + 'Cache.addAll should reject with TypeError if any request fails'); + }, 'Cache.addAll with opaque-filtered 206 response'); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.add('this-does-not-exist-please-dont-create-it'), + 'Cache.add should reject if response is !ok'); + }, 'Cache.add with request that results in a status of 404'); + + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.add('./resources/fetch-status.py?status=500'), + 'Cache.add should reject if response is !ok'); + }, 'Cache.add with request that results in a status of 500'); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.addAll(), + 'Cache.addAll with no arguments should throw TypeError.'); + }, 'Cache.addAll with no arguments'); + +cache_test(function(cache, test) { + // Assumes the existence of ../resources/simple.txt and ../resources/blank.html + var urls = ['./resources/simple.txt', undefined, './resources/blank.html']; + return promise_rejects_js( + test, + TypeError, + cache.addAll(urls), + 'Cache.addAll should throw TypeError for an undefined argument.'); + }, 'Cache.addAll with a mix of valid and undefined arguments'); + +cache_test(function(cache) { + return cache.addAll([]) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.addAll should resolve with undefined on ' + + 'success.'); + return cache.keys(); + }) + .then(function(result) { + assert_equals(result.length, 0, + 'There should be no entry in the cache.'); + }); + }, 'Cache.addAll with an empty array'); + +cache_test(function(cache) { + // Assumes the existence of ../resources/simple.txt and + // ../resources/blank.html + var urls = ['./resources/simple.txt', + self.location.href, + './resources/blank.html']; + return cache.addAll(urls) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.addAll should resolve with undefined on ' + + 'success.'); + return Promise.all( + urls.map(function(url) { return cache.match(url); })); + }) + .then(function(responses) { + assert_class_string( + responses[0], 'Response', + 'Cache.addAll should put a resource in the cache.'); + assert_class_string( + responses[1], 'Response', + 'Cache.addAll should put a resource in the cache.'); + assert_class_string( + responses[2], 'Response', + 'Cache.addAll should put a resource in the cache.'); + return Promise.all( + responses.map(function(response) { return response.text(); })); + }) + .then(function(bodies) { + assert_equals( + bodies[0], 'a simple text file\n', + 'Cache.add should retrieve the correct body.'); + assert_equals( + bodies[2], '\nEmpty doc\n', + 'Cache.add should retrieve the correct body.'); + }); + }, 'Cache.addAll with string URL arguments'); + +cache_test(function(cache) { + // Assumes the existence of ../resources/simple.txt and + // ../resources/blank.html + var urls = ['./resources/simple.txt', + self.location.href, + './resources/blank.html']; + var requests = urls.map(function(url) { + return new Request(url); + }); + return cache.addAll(requests) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.addAll should resolve with undefined on ' + + 'success.'); + return Promise.all( + urls.map(function(url) { return cache.match(url); })); + }) + .then(function(responses) { + assert_class_string( + responses[0], 'Response', + 'Cache.addAll should put a resource in the cache.'); + assert_class_string( + responses[1], 'Response', + 'Cache.addAll should put a resource in the cache.'); + assert_class_string( + responses[2], 'Response', + 'Cache.addAll should put a resource in the cache.'); + return Promise.all( + responses.map(function(response) { return response.text(); })); + }) + .then(function(bodies) { + assert_equals( + bodies[0], 'a simple text file\n', + 'Cache.add should retrieve the correct body.'); + assert_equals( + bodies[2], '\nEmpty doc\n', + 'Cache.add should retrieve the correct body.'); + }); + }, 'Cache.addAll with Request arguments'); + +cache_test(function(cache, test) { + // Assumes that ../resources/simple.txt and ../resources/blank.html exist. + // The second resource does not. + var urls = ['./resources/simple.txt', + 'this-resource-should-not-exist', + './resources/blank.html']; + var requests = urls.map(function(url) { + return new Request(url); + }); + return promise_rejects_js( + test, + TypeError, + cache.addAll(requests), + 'Cache.addAll should reject with TypeError if any request fails') + .then(function() { + return Promise.all(urls.map(function(url) { + return cache.match(url); + })); + }) + .then(function(matches) { + assert_array_equals( + matches, + [undefined, undefined, undefined], + 'If any response fails, no response should be added to cache'); + }); + }, 'Cache.addAll with a mix of succeeding and failing requests'); + +cache_test(function(cache, test) { + var request = new Request('../resources/simple.txt'); + return promise_rejects_dom( + test, + 'InvalidStateError', + cache.addAll([request, request]), + 'Cache.addAll should throw InvalidStateError if the same request is added ' + + 'twice.'); + }, 'Cache.addAll called with the same Request object specified twice'); + +cache_test(async function(cache, test) { + const url = './resources/vary.py?vary=x-shape'; + let requests = [ + new Request(url, { headers: { 'x-shape': 'circle' }}), + new Request(url, { headers: { 'x-shape': 'square' }}), + ]; + let result = await cache.addAll(requests); + assert_equals(result, undefined, 'Cache.addAll() should succeed'); + }, 'Cache.addAll should succeed when entries differ by vary header'); + +cache_test(async function(cache, test) { + const url = './resources/vary.py?vary=x-shape'; + let requests = [ + new Request(url, { headers: { 'x-shape': 'circle' }}), + new Request(url, { headers: { 'x-shape': 'circle' }}), + ]; + await promise_rejects_dom( + test, + 'InvalidStateError', + cache.addAll(requests), + 'Cache.addAll() should reject when entries are duplicate by vary header'); + }, 'Cache.addAll should reject when entries are duplicate by vary header'); + +// VARY header matching is asymmetric. Determining if two entries are duplicate +// depends on which entry's response is used in the comparison. The target +// response's VARY header determines what request headers are examined. This +// test verifies that Cache.addAll() duplicate checking handles this asymmetric +// behavior correctly. +cache_test(async function(cache, test) { + const base_url = './resources/vary.py'; + + // Define a request URL that sets a VARY header in the + // query string to be echoed back by the server. + const url = base_url + '?vary=x-size'; + + // Set a cookie to override the VARY header of the response + // when the request is made with credentials. This will + // take precedence over the query string vary param. This + // is a bit confusing, but it's necessary to construct a test + // where the URL is the same, but the VARY headers differ. + // + // Note, the test could also pass this information in additional + // request headers. If the cookie approach becomes too unwieldy + // this test could be rewritten to use that technique. + await fetch(base_url + '?set-vary-value-override-cookie=x-shape'); + test.add_cleanup(_ => fetch(base_url + '?clear-vary-value-override-cookie')); + + let requests = [ + // This request will result in a Response with a "Vary: x-shape" + // header. This *will not* result in a duplicate match with the + // other entry. + new Request(url, { headers: { 'x-shape': 'circle', + 'x-size': 'big' }, + credentials: 'same-origin' }), + + // This request will result in a Response with a "Vary: x-size" + // header. This *will* result in a duplicate match with the other + // entry. + new Request(url, { headers: { 'x-shape': 'square', + 'x-size': 'big' }, + credentials: 'omit' }), + ]; + await promise_rejects_dom( + test, + 'InvalidStateError', + cache.addAll(requests), + 'Cache.addAll() should reject when one entry has a vary header ' + + 'matching an earlier entry.'); + + // Test the reverse order now. + await promise_rejects_dom( + test, + 'InvalidStateError', + cache.addAll(requests.reverse()), + 'Cache.addAll() should reject when one entry has a vary header ' + + 'matching a later entry.'); + + }, 'Cache.addAll should reject when one entry has a vary header ' + + 'matching another entry'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-delete.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-delete.https.any.js new file mode 100644 index 00000000000..3eae2b6a08b --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-delete.https.any.js @@ -0,0 +1,164 @@ +// META: title=Cache.delete +// META: global=window,worker +// META: script=./resources/test-helpers.js +// META: timeout=long + +var test_url = 'https://example.com/foo'; + +// Construct a generic Request object. The URL is |test_url|. All other fields +// are defaults. +function new_test_request() { + return new Request(test_url); +} + +// Construct a generic Response object. +function new_test_response() { + return new Response('Hello world!', { status: 200 }); +} + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.delete(), + 'Cache.delete should reject with a TypeError when called with no ' + + 'arguments.'); + }, 'Cache.delete with no arguments'); + +cache_test(function(cache) { + return cache.put(new_test_request(), new_test_response()) + .then(function() { + return cache.delete(test_url); + }) + .then(function(result) { + assert_true(result, + 'Cache.delete should resolve with "true" if an entry ' + + 'was successfully deleted.'); + return cache.match(test_url); + }) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.delete should remove matching entries from cache.'); + }); + }, 'Cache.delete called with a string URL'); + +cache_test(function(cache) { + var request = new Request(test_url); + return cache.put(request, new_test_response()) + .then(function() { + return cache.delete(request); + }) + .then(function(result) { + assert_true(result, + 'Cache.delete should resolve with "true" if an entry ' + + 'was successfully deleted.'); + }); + }, 'Cache.delete called with a Request object'); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new_test_response(); + return cache.put(request, response) + .then(function() { + return cache.delete(new Request(test_url, {method: 'HEAD'})); + }) + .then(function(result) { + assert_false(result, + 'Cache.delete should not match a non-GET request ' + + 'unless ignoreMethod option is set.'); + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, response, + 'Cache.delete should leave non-matching response in the cache.'); + return cache.delete(new Request(test_url, {method: 'HEAD'}), + {ignoreMethod: true}); + }) + .then(function(result) { + assert_true(result, + 'Cache.delete should match a non-GET request ' + + ' if ignoreMethod is true.'); + }); + }, 'Cache.delete called with a HEAD request'); + +cache_test(function(cache) { + var vary_request = new Request('http://example.com/c', + {headers: {'Cookies': 'is-for-cookie'}}); + var vary_response = new Response('', {headers: {'Vary': 'Cookies'}}); + var mismatched_vary_request = new Request('http://example.com/c'); + + return cache.put(vary_request.clone(), vary_response.clone()) + .then(function() { + return cache.delete(mismatched_vary_request.clone()); + }) + .then(function(result) { + assert_false(result, + 'Cache.delete should not delete if vary does not ' + + 'match unless ignoreVary is true'); + return cache.delete(mismatched_vary_request.clone(), + {ignoreVary: true}); + }) + .then(function(result) { + assert_true(result, + 'Cache.delete should ignore vary if ignoreVary is true'); + }); + }, 'Cache.delete supports ignoreVary'); + +cache_test(function(cache) { + return cache.delete(test_url) + .then(function(result) { + assert_false(result, + 'Cache.delete should resolve with "false" if there ' + + 'are no matching entries.'); + }); + }, 'Cache.delete with a non-existent entry'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a_with_query.request, + { ignoreSearch: true }) + .then(function(result) { + assert_response_array_equals( + result, + [ + entries.a.response, + entries.a_with_query.response + ]); + return cache.delete(entries.a_with_query.request, + { ignoreSearch: true }); + }) + .then(function(result) { + return cache.matchAll(entries.a_with_query.request, + { ignoreSearch: true }); + }) + .then(function(result) { + assert_response_array_equals(result, []); + }); + }, + 'Cache.delete with ignoreSearch option (request with search parameters)'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a_with_query.request, + { ignoreSearch: true }) + .then(function(result) { + assert_response_array_equals( + result, + [ + entries.a.response, + entries.a_with_query.response + ]); + // cache.delete()'s behavior should be the same if ignoreSearch is + // not provided or if ignoreSearch is false. + return cache.delete(entries.a_with_query.request, + { ignoreSearch: false }); + }) + .then(function(result) { + return cache.matchAll(entries.a_with_query.request, + { ignoreSearch: true }); + }) + .then(function(result) { + assert_response_array_equals(result, [ entries.a.response ]); + }); + }, + 'Cache.delete with ignoreSearch option (when it is specified as false)'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-keys-attributes-for-service-worker.https.html b/test/wpt/tests/service-workers/cache-storage/cache-keys-attributes-for-service-worker.https.html new file mode 100644 index 00000000000..3c96348e0e0 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-keys-attributes-for-service-worker.https.html @@ -0,0 +1,75 @@ + +Cache.keys (checking request attributes that can be set only on service workers) + + + + + diff --git a/test/wpt/tests/service-workers/cache-storage/cache-keys.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-keys.https.any.js new file mode 100644 index 00000000000..232fb760d40 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-keys.https.any.js @@ -0,0 +1,212 @@ +// META: title=Cache.keys +// META: global=window,worker +// META: script=./resources/test-helpers.js +// META: timeout=long + +cache_test(cache => { + return cache.keys() + .then(requests => { + assert_equals( + requests.length, 0, + 'Cache.keys should resolve to an empty array for an empty cache'); + }); + }, 'Cache.keys() called on an empty cache'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys('not-present-in-the-cache') + .then(function(result) { + assert_request_array_equals( + result, [], + 'Cache.keys should resolve with an empty array on failure.'); + }); + }, 'Cache.keys with no matching entries'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys(entries.a.request.url) + .then(function(result) { + assert_request_array_equals(result, [entries.a.request], + 'Cache.keys should match by URL.'); + }); + }, 'Cache.keys with URL'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys(entries.a.request) + .then(function(result) { + assert_request_array_equals( + result, [entries.a.request], + 'Cache.keys should match by Request.'); + }); + }, 'Cache.keys with Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys(new Request(entries.a.request.url)) + .then(function(result) { + assert_request_array_equals( + result, [entries.a.request], + 'Cache.keys should match by Request.'); + }); + }, 'Cache.keys with new Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys(entries.a.request, {ignoreSearch: true}) + .then(function(result) { + assert_request_array_equals( + result, + [ + entries.a.request, + entries.a_with_query.request + ], + 'Cache.keys with ignoreSearch should ignore the ' + + 'search parameters of cached request.'); + }); + }, + 'Cache.keys with ignoreSearch option (request with no search ' + + 'parameters)'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys(entries.a_with_query.request, {ignoreSearch: true}) + .then(function(result) { + assert_request_array_equals( + result, + [ + entries.a.request, + entries.a_with_query.request + ], + 'Cache.keys with ignoreSearch should ignore the ' + + 'search parameters of request.'); + }); + }, + 'Cache.keys with ignoreSearch option (request with search parameters)'); + +cache_test(function(cache) { + var request = new Request('http://example.com/'); + var head_request = new Request('http://example.com/', {method: 'HEAD'}); + var response = new Response('foo'); + return cache.put(request.clone(), response.clone()) + .then(function() { + return cache.keys(head_request.clone()); + }) + .then(function(result) { + assert_request_array_equals( + result, [], + 'Cache.keys should resolve with an empty array with a ' + + 'mismatched method.'); + return cache.keys(head_request.clone(), + {ignoreMethod: true}); + }) + .then(function(result) { + assert_request_array_equals( + result, + [ + request, + ], + 'Cache.keys with ignoreMethod should ignore the ' + + 'method of request.'); + }); + }, 'Cache.keys supports ignoreMethod'); + +cache_test(function(cache) { + var vary_request = new Request('http://example.com/c', + {headers: {'Cookies': 'is-for-cookie'}}); + var vary_response = new Response('', {headers: {'Vary': 'Cookies'}}); + var mismatched_vary_request = new Request('http://example.com/c'); + + return cache.put(vary_request.clone(), vary_response.clone()) + .then(function() { + return cache.keys(mismatched_vary_request.clone()); + }) + .then(function(result) { + assert_request_array_equals( + result, [], + 'Cache.keys should resolve with an empty array with a ' + + 'mismatched vary.'); + return cache.keys(mismatched_vary_request.clone(), + {ignoreVary: true}); + }) + .then(function(result) { + assert_request_array_equals( + result, + [ + vary_request, + ], + 'Cache.keys with ignoreVary should ignore the ' + + 'vary of request.'); + }); + }, 'Cache.keys supports ignoreVary'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys(entries.cat.request.url + '#mouse') + .then(function(result) { + assert_request_array_equals( + result, + [ + entries.cat.request, + ], + 'Cache.keys should ignore URL fragment.'); + }); + }, 'Cache.keys with URL containing fragment'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys('http') + .then(function(result) { + assert_request_array_equals( + result, [], + 'Cache.keys should treat query as a URL and not ' + + 'just a string fragment.'); + }); + }, 'Cache.keys with string fragment "http" as query'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys() + .then(function(result) { + assert_request_array_equals( + result, + simple_entries.map(entry => entry.request), + 'Cache.keys without parameters should match all entries.'); + }); + }, 'Cache.keys without parameters'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys(undefined) + .then(function(result) { + assert_request_array_equals( + result, + simple_entries.map(entry => entry.request), + 'Cache.keys with undefined request should match all entries.'); + }); + }, 'Cache.keys with explicitly undefined request'); + +cache_test(cache => { + return cache.keys(undefined, {}) + .then(requests => { + assert_equals( + requests.length, 0, + 'Cache.keys should resolve to an empty array for an empty cache'); + }); + }, 'Cache.keys with explicitly undefined request and empty options'); + +prepopulated_cache_test(vary_entries, function(cache, entries) { + return cache.keys() + .then(function(result) { + assert_request_array_equals( + result, + [ + entries.vary_cookie_is_cookie.request, + entries.vary_cookie_is_good.request, + entries.vary_cookie_absent.request, + ], + 'Cache.keys without parameters should match all entries.'); + }); + }, 'Cache.keys without parameters and VARY entries'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.keys(new Request(entries.cat.request.url, {method: 'HEAD'})) + .then(function(result) { + assert_request_array_equals( + result, [], + 'Cache.keys should not match HEAD request unless ignoreMethod ' + + 'option is set.'); + }); + }, 'Cache.keys with a HEAD Request'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-match.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-match.https.any.js new file mode 100644 index 00000000000..9ca45903cbb --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-match.https.any.js @@ -0,0 +1,437 @@ +// META: title=Cache.match +// META: global=window,worker +// META: script=./resources/test-helpers.js +// META: script=/common/get-host-info.sub.js +// META: timeout=long + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match('not-present-in-the-cache') + .then(function(result) { + assert_equals(result, undefined, + 'Cache.match failures should resolve with undefined.'); + }); + }, 'Cache.match with no matching entries'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.a.request.url) + .then(function(result) { + assert_response_equals(result, entries.a.response, + 'Cache.match should match by URL.'); + }); + }, 'Cache.match with URL'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.a.request) + .then(function(result) { + assert_response_equals(result, entries.a.response, + 'Cache.match should match by Request.'); + }); + }, 'Cache.match with Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + var alt_response = new Response('', {status: 201}); + + return self.caches.open('second_matching_cache') + .then(function(cache) { + return cache.put(entries.a.request, alt_response.clone()); + }) + .then(function() { + return cache.match(entries.a.request); + }) + .then(function(result) { + assert_response_equals( + result, entries.a.response, + 'Cache.match should match the first cache.'); + }); + }, 'Cache.match with multiple cache hits'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(new Request(entries.a.request.url)) + .then(function(result) { + assert_response_equals(result, entries.a.response, + 'Cache.match should match by Request.'); + }); + }, 'Cache.match with new Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(new Request(entries.a.request.url, {method: 'HEAD'})) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.match should not match HEAD Request.'); + }); + }, 'Cache.match with HEAD'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.a.request, + {ignoreSearch: true}) + .then(function(result) { + assert_response_in_array( + result, + [ + entries.a.response, + entries.a_with_query.response + ], + 'Cache.match with ignoreSearch should ignore the ' + + 'search parameters of cached request.'); + }); + }, + 'Cache.match with ignoreSearch option (request with no search ' + + 'parameters)'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.a_with_query.request, + {ignoreSearch: true}) + .then(function(result) { + assert_response_in_array( + result, + [ + entries.a.response, + entries.a_with_query.response + ], + 'Cache.match with ignoreSearch should ignore the ' + + 'search parameters of request.'); + }); + }, + 'Cache.match with ignoreSearch option (request with search parameter)'); + +cache_test(function(cache) { + var request = new Request('http://example.com/'); + var head_request = new Request('http://example.com/', {method: 'HEAD'}); + var response = new Response('foo'); + return cache.put(request.clone(), response.clone()) + .then(function() { + return cache.match(head_request.clone()); + }) + .then(function(result) { + assert_equals( + result, undefined, + 'Cache.match should resolve as undefined with a ' + + 'mismatched method.'); + return cache.match(head_request.clone(), + {ignoreMethod: true}); + }) + .then(function(result) { + assert_response_equals( + result, response, + 'Cache.match with ignoreMethod should ignore the ' + + 'method of request.'); + }); + }, 'Cache.match supports ignoreMethod'); + +cache_test(function(cache) { + var vary_request = new Request('http://example.com/c', + {headers: {'Cookies': 'is-for-cookie'}}); + var vary_response = new Response('', {headers: {'Vary': 'Cookies'}}); + var mismatched_vary_request = new Request('http://example.com/c'); + + return cache.put(vary_request.clone(), vary_response.clone()) + .then(function() { + return cache.match(mismatched_vary_request.clone()); + }) + .then(function(result) { + assert_equals( + result, undefined, + 'Cache.match should resolve as undefined with a ' + + 'mismatched vary.'); + return cache.match(mismatched_vary_request.clone(), + {ignoreVary: true}); + }) + .then(function(result) { + assert_response_equals( + result, vary_response, + 'Cache.match with ignoreVary should ignore the ' + + 'vary of request.'); + }); + }, 'Cache.match supports ignoreVary'); + +cache_test(function(cache) { + let has_cache_name = false; + const opts = { + get cacheName() { + has_cache_name = true; + return undefined; + } + }; + return self.caches.open('foo') + .then(function() { + return cache.match('bar', opts); + }) + .then(function() { + assert_false(has_cache_name, + 'Cache.match does not support cacheName option ' + + 'which was removed in CacheQueryOptions.'); + }); + }, 'Cache.match does not support cacheName option'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match(entries.cat.request.url + '#mouse') + .then(function(result) { + assert_response_equals(result, entries.cat.response, + 'Cache.match should ignore URL fragment.'); + }); + }, 'Cache.match with URL containing fragment'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.match('http') + .then(function(result) { + assert_equals( + result, undefined, + 'Cache.match should treat query as a URL and not ' + + 'just a string fragment.'); + }); + }, 'Cache.match with string fragment "http" as query'); + +prepopulated_cache_test(vary_entries, function(cache, entries) { + return cache.match('http://example.com/c') + .then(function(result) { + assert_response_in_array( + result, + [ + entries.vary_cookie_absent.response + ], + 'Cache.match should honor "Vary" header.'); + }); + }, 'Cache.match with responses containing "Vary" header'); + +cache_test(function(cache) { + var request = new Request('http://example.com'); + var response; + var request_url = new URL('./resources/simple.txt', location.href).href; + return fetch(request_url) + .then(function(fetch_result) { + response = fetch_result; + assert_equals( + response.url, request_url, + '[https://fetch.spec.whatwg.org/#dom-response-url] ' + + 'Reponse.url should return the URL of the response.'); + return cache.put(request, response.clone()); + }) + .then(function() { + return cache.match(request.url); + }) + .then(function(result) { + assert_response_equals( + result, response, + 'Cache.match should return a Response object that has the same ' + + 'properties as the stored response.'); + return cache.match(response.url); + }) + .then(function(result) { + assert_equals( + result, undefined, + 'Cache.match should not match cache entry based on response URL.'); + }); + }, 'Cache.match with Request and Response objects with different URLs'); + +cache_test(function(cache) { + var request_url = new URL('./resources/simple.txt', location.href).href; + return fetch(request_url) + .then(function(fetch_result) { + return cache.put(new Request(request_url), fetch_result); + }) + .then(function() { + return cache.match(request_url); + }) + .then(function(result) { + return result.text(); + }) + .then(function(body_text) { + assert_equals(body_text, 'a simple text file\n', + 'Cache.match should return a Response object with a ' + + 'valid body.'); + }) + .then(function() { + return cache.match(request_url); + }) + .then(function(result) { + return result.text(); + }) + .then(function(body_text) { + assert_equals(body_text, 'a simple text file\n', + 'Cache.match should return a Response object with a ' + + 'valid body each time it is called.'); + }); + }, 'Cache.match invoked multiple times for the same Request/Response'); + +cache_test(function(cache) { + var request_url = new URL('./resources/simple.txt', location.href).href; + return fetch(request_url) + .then(function(fetch_result) { + return cache.put(new Request(request_url), fetch_result); + }) + .then(function() { + return cache.match(request_url); + }) + .then(function(result) { + return result.blob(); + }) + .then(function(blob) { + var sliced = blob.slice(2,8); + + return new Promise(function (resolve, reject) { + var reader = new FileReader(); + reader.onloadend = function(event) { + resolve(event.target.result); + }; + reader.readAsText(sliced); + }); + }) + .then(function(text) { + assert_equals(text, 'simple', + 'A Response blob returned by Cache.match should be ' + + 'sliceable.' ); + }); + }, 'Cache.match blob should be sliceable'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + var request = new Request(entries.a.request.clone(), {method: 'POST'}); + return cache.match(request) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.match should not find a match'); + }); + }, 'Cache.match with POST Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + var response = entries.non_2xx_response.response; + return cache.match(entries.non_2xx_response.request.url) + .then(function(result) { + assert_response_equals( + result, entries.non_2xx_response.response, + 'Cache.match should return a Response object that has the ' + + 'same properties as a stored non-2xx response.'); + }); + }, 'Cache.match with a non-2xx Response'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + var response = entries.error_response.response; + return cache.match(entries.error_response.request.url) + .then(function(result) { + assert_response_equals( + result, entries.error_response.response, + 'Cache.match should return a Response object that has the ' + + 'same properties as a stored network error response.'); + }); + }, 'Cache.match with a network error Response'); + +cache_test(function(cache) { + // This test validates that we can get a Response from the Cache API, + // clone it, and read just one side of the clone. This was previously + // bugged in FF for Responses with large bodies. + var data = []; + data.length = 80 * 1024; + data.fill('F'); + var response; + return cache.put('/', new Response(data.toString())) + .then(function(result) { + return cache.match('/'); + }) + .then(function(r) { + // Make sure the original response is not GC'd. + response = r; + // Return only the clone. We purposefully test that the other + // half of the clone does not need to be read here. + return response.clone().text(); + }) + .then(function(text) { + assert_equals(text, data.toString(), 'cloned body text can be read correctly'); + }); + }, 'Cache produces large Responses that can be cloned and read correctly.'); + +cache_test(async (cache) => { + const url = get_host_info().HTTPS_REMOTE_ORIGIN + + '/service-workers/cache-storage/resources/simple.txt?pipe=' + + 'header(access-control-allow-origin,*)|' + + 'header(access-control-expose-headers,*)|' + + 'header(foo,bar)|' + + 'header(set-cookie,X)'; + + const response = await fetch(url); + await cache.put(new Request(url), response); + const cached_response = await cache.match(url); + + const headers = cached_response.headers; + assert_equals(headers.get('access-control-expose-headers'), '*'); + assert_equals(headers.get('foo'), 'bar'); + assert_equals(headers.get('set-cookie'), null); + }, 'cors-exposed header should be stored correctly.'); + +cache_test(async (cache) => { + // A URL that should load a resource with a known mime type. + const url = '/service-workers/cache-storage/resources/blank.html'; + const expected_mime_type = 'text/html'; + + // Verify we get the expected mime type from the network. Note, + // we cannot use an exact match here since some browsers append + // character encoding information to the blob.type value. + const net_response = await fetch(url); + const net_mime_type = (await net_response.blob()).type; + assert_true(net_mime_type.includes(expected_mime_type), + 'network response should include the expected mime type'); + + // Verify we get the exact same mime type when reading the same + // URL resource back out of the cache. + await cache.add(url); + const cache_response = await cache.match(url); + const cache_mime_type = (await cache_response.blob()).type; + assert_equals(cache_mime_type, net_mime_type, + 'network and cache response mime types should match'); + }, 'MIME type should be set from content-header correctly.'); + +cache_test(async (cache) => { + const url = '/dummy'; + const original_type = 'text/html'; + const override_type = 'text/plain'; + const init_with_headers = { + headers: { + 'content-type': original_type + } + } + + // Verify constructing a synthetic response with a content-type header + // gets the correct mime type. + const response = new Response('hello world', init_with_headers); + const original_response_type = (await response.blob()).type; + assert_true(original_response_type.includes(original_type), + 'original response should include the expected mime type'); + + // Verify overwriting the content-type header changes the mime type. + const overwritten_response = new Response('hello world', init_with_headers); + overwritten_response.headers.set('content-type', override_type); + const overwritten_response_type = (await overwritten_response.blob()).type; + assert_equals(overwritten_response_type, override_type, + 'mime type can be overridden'); + + // Verify the Response read from Cache uses the original mime type + // computed when it was first constructed. + const tmp = new Response('hello world', init_with_headers); + tmp.headers.set('content-type', override_type); + await cache.put(url, tmp); + const cache_response = await cache.match(url); + const cache_mime_type = (await cache_response.blob()).type; + assert_equals(cache_mime_type, override_type, + 'overwritten and cached response mime types should match'); + }, 'MIME type should reflect Content-Type headers of response.'); + +cache_test(async (cache) => { + const url = new URL('./resources/vary.py?vary=foo', + get_host_info().HTTPS_REMOTE_ORIGIN + self.location.pathname); + const original_request = new Request(url, { mode: 'no-cors', + headers: { 'foo': 'bar' } }); + const fetch_response = await fetch(original_request); + assert_equals(fetch_response.type, 'opaque'); + + await cache.put(original_request, fetch_response); + + const match_response_1 = await cache.match(original_request); + assert_not_equals(match_response_1, undefined); + + // Verify that cache.match() finds the entry even if queried with a varied + // header that does not match the cache key. Vary headers should be ignored + // for opaque responses. + const different_request = new Request(url, { headers: { 'foo': 'CHANGED' } }); + const match_response_2 = await cache.match(different_request); + assert_not_equals(match_response_2, undefined); +}, 'Cache.match ignores vary headers on opaque response.'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-matchAll.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-matchAll.https.any.js new file mode 100644 index 00000000000..93c55178918 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-matchAll.https.any.js @@ -0,0 +1,244 @@ +// META: title=Cache.matchAll +// META: global=window,worker +// META: script=./resources/test-helpers.js +// META: timeout=long + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll('not-present-in-the-cache') + .then(function(result) { + assert_response_array_equals( + result, [], + 'Cache.matchAll should resolve with an empty array on failure.'); + }); + }, 'Cache.matchAll with no matching entries'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a.request.url) + .then(function(result) { + assert_response_array_equals(result, [entries.a.response], + 'Cache.matchAll should match by URL.'); + }); + }, 'Cache.matchAll with URL'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a.request) + .then(function(result) { + assert_response_array_equals( + result, [entries.a.response], + 'Cache.matchAll should match by Request.'); + }); + }, 'Cache.matchAll with Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(new Request(entries.a.request.url)) + .then(function(result) { + assert_response_array_equals( + result, [entries.a.response], + 'Cache.matchAll should match by Request.'); + }); + }, 'Cache.matchAll with new Request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(new Request(entries.a.request.url, {method: 'HEAD'}), + {ignoreSearch: true}) + .then(function(result) { + assert_response_array_equals( + result, [], + 'Cache.matchAll should not match HEAD Request.'); + }); + }, 'Cache.matchAll with HEAD'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a.request, + {ignoreSearch: true}) + .then(function(result) { + assert_response_array_equals( + result, + [ + entries.a.response, + entries.a_with_query.response + ], + 'Cache.matchAll with ignoreSearch should ignore the ' + + 'search parameters of cached request.'); + }); + }, + 'Cache.matchAll with ignoreSearch option (request with no search ' + + 'parameters)'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.a_with_query.request, + {ignoreSearch: true}) + .then(function(result) { + assert_response_array_equals( + result, + [ + entries.a.response, + entries.a_with_query.response + ], + 'Cache.matchAll with ignoreSearch should ignore the ' + + 'search parameters of request.'); + }); + }, + 'Cache.matchAll with ignoreSearch option (request with search parameters)'); + +cache_test(function(cache) { + var request = new Request('http://example.com/'); + var head_request = new Request('http://example.com/', {method: 'HEAD'}); + var response = new Response('foo'); + return cache.put(request.clone(), response.clone()) + .then(function() { + return cache.matchAll(head_request.clone()); + }) + .then(function(result) { + assert_response_array_equals( + result, [], + 'Cache.matchAll should resolve with empty array for a ' + + 'mismatched method.'); + return cache.matchAll(head_request.clone(), + {ignoreMethod: true}); + }) + .then(function(result) { + assert_response_array_equals( + result, [response], + 'Cache.matchAll with ignoreMethod should ignore the ' + + 'method of request.'); + }); + }, 'Cache.matchAll supports ignoreMethod'); + +cache_test(function(cache) { + var vary_request = new Request('http://example.com/c', + {headers: {'Cookies': 'is-for-cookie'}}); + var vary_response = new Response('', {headers: {'Vary': 'Cookies'}}); + var mismatched_vary_request = new Request('http://example.com/c'); + + return cache.put(vary_request.clone(), vary_response.clone()) + .then(function() { + return cache.matchAll(mismatched_vary_request.clone()); + }) + .then(function(result) { + assert_response_array_equals( + result, [], + 'Cache.matchAll should resolve as undefined with a ' + + 'mismatched vary.'); + return cache.matchAll(mismatched_vary_request.clone(), + {ignoreVary: true}); + }) + .then(function(result) { + assert_response_array_equals( + result, [vary_response], + 'Cache.matchAll with ignoreVary should ignore the ' + + 'vary of request.'); + }); + }, 'Cache.matchAll supports ignoreVary'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(entries.cat.request.url + '#mouse') + .then(function(result) { + assert_response_array_equals( + result, + [ + entries.cat.response, + ], + 'Cache.matchAll should ignore URL fragment.'); + }); + }, 'Cache.matchAll with URL containing fragment'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll('http') + .then(function(result) { + assert_response_array_equals( + result, [], + 'Cache.matchAll should treat query as a URL and not ' + + 'just a string fragment.'); + }); + }, 'Cache.matchAll with string fragment "http" as query'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll() + .then(function(result) { + assert_response_array_equals( + result, + simple_entries.map(entry => entry.response), + 'Cache.matchAll without parameters should match all entries.'); + }); + }, 'Cache.matchAll without parameters'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(undefined) + .then(result => { + assert_response_array_equals( + result, + simple_entries.map(entry => entry.response), + 'Cache.matchAll with undefined request should match all entries.'); + }); + }, 'Cache.matchAll with explicitly undefined request'); + +prepopulated_cache_test(simple_entries, function(cache, entries) { + return cache.matchAll(undefined, {}) + .then(result => { + assert_response_array_equals( + result, + simple_entries.map(entry => entry.response), + 'Cache.matchAll with undefined request should match all entries.'); + }); + }, 'Cache.matchAll with explicitly undefined request and empty options'); + +prepopulated_cache_test(vary_entries, function(cache, entries) { + return cache.matchAll('http://example.com/c') + .then(function(result) { + assert_response_array_equals( + result, + [ + entries.vary_cookie_absent.response + ], + 'Cache.matchAll should exclude matches if a vary header is ' + + 'missing in the query request, but is present in the cached ' + + 'request.'); + }) + + .then(function() { + return cache.matchAll( + new Request('http://example.com/c', + {headers: {'Cookies': 'none-of-the-above'}})); + }) + .then(function(result) { + assert_response_array_equals( + result, + [ + ], + 'Cache.matchAll should exclude matches if a vary header is ' + + 'missing in the cached request, but is present in the query ' + + 'request.'); + }) + + .then(function() { + return cache.matchAll( + new Request('http://example.com/c', + {headers: {'Cookies': 'is-for-cookie'}})); + }) + .then(function(result) { + assert_response_array_equals( + result, + [entries.vary_cookie_is_cookie.response], + 'Cache.matchAll should match the entire header if a vary header ' + + 'is present in both the query and cached requests.'); + }); + }, 'Cache.matchAll with responses containing "Vary" header'); + +prepopulated_cache_test(vary_entries, function(cache, entries) { + return cache.matchAll('http://example.com/c', + {ignoreVary: true}) + .then(function(result) { + assert_response_array_equals( + result, + [ + entries.vary_cookie_is_cookie.response, + entries.vary_cookie_is_good.response, + entries.vary_cookie_absent.response + ], + 'Cache.matchAll should support multiple vary request/response ' + + 'pairs.'); + }); + }, 'Cache.matchAll with multiple vary pairs'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-put.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-put.https.any.js new file mode 100644 index 00000000000..dbf2650a75a --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-put.https.any.js @@ -0,0 +1,411 @@ +// META: title=Cache.put +// META: global=window,worker +// META: script=/common/get-host-info.sub.js +// META: script=./resources/test-helpers.js +// META: timeout=long + +var test_url = 'https://example.com/foo'; +var test_body = 'Hello world!'; +const { REMOTE_HOST } = get_host_info(); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response(test_body); + return cache.put(request, response) + .then(function(result) { + assert_equals(result, undefined, + 'Cache.put should resolve with undefined on success.'); + }); + }, 'Cache.put called with simple Request and Response'); + +cache_test(function(cache) { + var test_url = new URL('./resources/simple.txt', location.href).href; + var request = new Request(test_url); + var response; + return fetch(test_url) + .then(function(fetch_result) { + response = fetch_result.clone(); + return cache.put(request, fetch_result); + }) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, response, + 'Cache.put should update the cache with ' + + 'new request and response.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, 'a simple text file\n', + 'Cache.put should store response body.'); + }); + }, 'Cache.put called with Request and Response from fetch()'); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response(test_body); + assert_false(request.bodyUsed, + '[https://fetch.spec.whatwg.org/#dom-body-bodyused] ' + + 'Request.bodyUsed should be initially false.'); + return cache.put(request, response) + .then(function() { + assert_false(request.bodyUsed, + 'Cache.put should not mark empty request\'s body used'); + }); + }, 'Cache.put with Request without a body'); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response(); + assert_false(response.bodyUsed, + '[https://fetch.spec.whatwg.org/#dom-body-bodyused] ' + + 'Response.bodyUsed should be initially false.'); + return cache.put(request, response) + .then(function() { + assert_false(response.bodyUsed, + 'Cache.put should not mark empty response\'s body used'); + }); + }, 'Cache.put with Response without a body'); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response(test_body); + return cache.put(request, response.clone()) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, response, + 'Cache.put should update the cache with ' + + 'new Request and Response.'); + }); + }, 'Cache.put with a Response containing an empty URL'); + +cache_test(function(cache) { + var request = new Request(test_url); + var response = new Response('', { + status: 200, + headers: [['Content-Type', 'text/plain']] + }); + return cache.put(request, response) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_equals(result.status, 200, 'Cache.put should store status.'); + assert_equals(result.headers.get('Content-Type'), 'text/plain', + 'Cache.put should store headers.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, '', + 'Cache.put should store response body.'); + }); + }, 'Cache.put with an empty response body'); + +cache_test(function(cache, test) { + var request = new Request(test_url); + var response = new Response('', { + status: 206, + headers: [['Content-Type', 'text/plain']] + }); + + return promise_rejects_js( + test, + TypeError, + cache.put(request, response), + 'Cache.put should reject 206 Responses with a TypeError.'); + }, 'Cache.put with synthetic 206 response'); + +cache_test(function(cache, test) { + var test_url = new URL('./resources/fetch-status.py?status=206', location.href).href; + var request = new Request(test_url); + var response; + return fetch(test_url) + .then(function(fetch_result) { + assert_equals(fetch_result.status, 206, + 'Test framework error: The status code should be 206.'); + response = fetch_result.clone(); + return promise_rejects_js(test, TypeError, cache.put(request, fetch_result)); + }); + }, 'Cache.put with HTTP 206 response'); + +cache_test(function(cache, test) { + // We need to jump through some hoops to allow the test to perform opaque + // response filtering, but bypass the ORB safelist check. This is + // done, by forcing the MIME type retrieval to fail and the + // validation of partial first response to succeed. + var pipe = "status(206)|header(Content-Type,)|header(Content-Range, bytes 0-1/41)|slice(null, 1)"; + var test_url = new URL(`./resources/blank.html?pipe=${pipe}`, location.href); + test_url.hostname = REMOTE_HOST; + var request = new Request(test_url.href, { mode: 'no-cors' }); + var response; + return fetch(request) + .then(function(fetch_result) { + assert_equals(fetch_result.type, 'opaque', + 'Test framework error: The response type should be opaque.'); + assert_equals(fetch_result.status, 0, + 'Test framework error: The status code should be 0 for an ' + + ' opaque-filtered response. This is actually HTTP 206.'); + response = fetch_result.clone(); + return cache.put(request, fetch_result); + }) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_not_equals(result, undefined, + 'Cache.put should store an entry for the opaque response'); + }); + }, 'Cache.put with opaque-filtered HTTP 206 response'); + +cache_test(function(cache) { + var test_url = new URL('./resources/fetch-status.py?status=500', location.href).href; + var request = new Request(test_url); + var response; + return fetch(test_url) + .then(function(fetch_result) { + assert_equals(fetch_result.status, 500, + 'Test framework error: The status code should be 500.'); + response = fetch_result.clone(); + return cache.put(request, fetch_result); + }) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, response, + 'Cache.put should update the cache with ' + + 'new request and response.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, '', + 'Cache.put should store response body.'); + }); + }, 'Cache.put with HTTP 500 response'); + +cache_test(function(cache) { + var alternate_response_body = 'New body'; + var alternate_response = new Response(alternate_response_body, + { statusText: 'New status' }); + return cache.put(new Request(test_url), + new Response('Old body', { statusText: 'Old status' })) + .then(function() { + return cache.put(new Request(test_url), alternate_response.clone()); + }) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, alternate_response, + 'Cache.put should replace existing ' + + 'response with new response.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, alternate_response_body, + 'Cache put should store new response body.'); + }); + }, 'Cache.put called twice with matching Requests and different Responses'); + +cache_test(function(cache) { + var first_url = test_url; + var second_url = first_url + '#(O_o)'; + var third_url = first_url + '#fragment'; + var alternate_response_body = 'New body'; + var alternate_response = new Response(alternate_response_body, + { statusText: 'New status' }); + return cache.put(new Request(first_url), + new Response('Old body', { statusText: 'Old status' })) + .then(function() { + return cache.put(new Request(second_url), alternate_response.clone()); + }) + .then(function() { + return cache.match(test_url); + }) + .then(function(result) { + assert_response_equals(result, alternate_response, + 'Cache.put should replace existing ' + + 'response with new response.'); + return result.text(); + }) + .then(function(body) { + assert_equals(body, alternate_response_body, + 'Cache put should store new response body.'); + }) + .then(function() { + return cache.put(new Request(third_url), alternate_response.clone()); + }) + .then(function() { + return cache.keys(); + }) + .then(function(results) { + // Should match urls (without fragments or with different ones) to the + // same cache key. However, result.url should be the latest url used. + assert_equals(results[0].url, third_url); + return; + }); +}, 'Cache.put called multiple times with request URLs that differ only by a fragment'); + +cache_test(function(cache) { + var url = 'http://example.com/foo'; + return cache.put(url, new Response('some body')) + .then(function() { return cache.match(url); }) + .then(function(response) { return response.text(); }) + .then(function(body) { + assert_equals(body, 'some body', + 'Cache.put should accept a string as request.'); + }); + }, 'Cache.put with a string request'); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.put(new Request(test_url), 'Hello world!'), + 'Cache.put should only accept a Response object as the response.'); + }, 'Cache.put with an invalid response'); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.put(new Request('file:///etc/passwd'), + new Response(test_body)), + 'Cache.put should reject non-HTTP/HTTPS requests with a TypeError.'); + }, 'Cache.put with a non-HTTP/HTTPS request'); + +cache_test(function(cache) { + var response = new Response(test_body); + return cache.put(new Request('relative-url'), response.clone()) + .then(function() { + return cache.match(new URL('relative-url', location.href).href); + }) + .then(function(result) { + assert_response_equals(result, response, + 'Cache.put should accept a relative URL ' + + 'as the request.'); + }); + }, 'Cache.put with a relative URL'); + +cache_test(function(cache, test) { + var request = new Request('http://example.com/foo', { method: 'HEAD' }); + return promise_rejects_js( + test, + TypeError, + cache.put(request, new Response(test_body)), + 'Cache.put should throw a TypeError for non-GET requests.'); + }, 'Cache.put with a non-GET request'); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.put(new Request(test_url), null), + 'Cache.put should throw a TypeError for a null response.'); + }, 'Cache.put with a null response'); + +cache_test(function(cache, test) { + var request = new Request(test_url, {method: 'POST', body: test_body}); + return promise_rejects_js( + test, + TypeError, + cache.put(request, new Response(test_body)), + 'Cache.put should throw a TypeError for a POST request.'); + }, 'Cache.put with a POST request'); + +cache_test(function(cache) { + var response = new Response(test_body); + assert_false(response.bodyUsed, + '[https://fetch.spec.whatwg.org/#dom-body-bodyused] ' + + 'Response.bodyUsed should be initially false.'); + return response.text().then(function() { + assert_true( + response.bodyUsed, + '[https://fetch.spec.whatwg.org/#concept-body-consume-body] ' + + 'The text() method should make the body disturbed.'); + var request = new Request(test_url); + return cache.put(request, response).then(() => { + assert_unreached('cache.put should be rejected'); + }, () => {}); + }); + }, 'Cache.put with a used response body'); + +cache_test(function(cache) { + var response = new Response(test_body); + return cache.put(new Request(test_url), response) + .then(function() { + assert_throws_js(TypeError, () => response.body.getReader()); + }); + }, 'getReader() after Cache.put'); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.put(new Request(test_url), + new Response(test_body, { headers: { VARY: '*' }})), + 'Cache.put should reject VARY:* Responses with a TypeError.'); + }, 'Cache.put with a VARY:* Response'); + +cache_test(function(cache, test) { + return promise_rejects_js( + test, + TypeError, + cache.put(new Request(test_url), + new Response(test_body, + { headers: { VARY: 'Accept-Language,*' }})), + 'Cache.put should reject Responses with an embedded VARY:* with a ' + + 'TypeError.'); + }, 'Cache.put with an embedded VARY:* Response'); + +cache_test(async function(cache, test) { + const url = new URL('./resources/vary.py?vary=*', + get_host_info().HTTPS_REMOTE_ORIGIN + self.location.pathname); + const request = new Request(url, { mode: 'no-cors' }); + const response = await fetch(request); + assert_equals(response.type, 'opaque'); + await cache.put(request, response); + }, 'Cache.put with a VARY:* opaque response should not reject'); + +cache_test(function(cache) { + var url = 'foo.html'; + var redirectURL = 'http://example.com/foo-bar.html'; + var redirectResponse = Response.redirect(redirectURL); + assert_equals(redirectResponse.headers.get('Location'), redirectURL, + 'Response.redirect() should set Location header.'); + return cache.put(url, redirectResponse.clone()) + .then(function() { + return cache.match(url); + }) + .then(function(response) { + assert_response_equals(response, redirectResponse, + 'Redirect response is reproduced by the Cache API'); + assert_equals(response.headers.get('Location'), redirectURL, + 'Location header is preserved by Cache API.'); + }); + }, 'Cache.put should store Response.redirect() correctly'); + +cache_test(async (cache) => { + var request = new Request(test_url); + var response = new Response(new Blob([test_body])); + await cache.put(request, response); + var cachedResponse = await cache.match(request); + assert_equals(await cachedResponse.text(), test_body); + }, 'Cache.put called with simple Request and blob Response'); + +cache_test(async (cache) => { + var formData = new FormData(); + formData.append("name", "value"); + + var request = new Request(test_url); + var response = new Response(formData); + await cache.put(request, response); + var cachedResponse = await cache.match(request); + var cachedResponseText = await cachedResponse.text(); + assert_true(cachedResponseText.indexOf("name=\"name\"\r\n\r\nvalue") !== -1); +}, 'Cache.put called with simple Request and form data Response'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-storage-buckets.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-storage-buckets.https.any.js new file mode 100644 index 00000000000..fd59ba464db --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-storage-buckets.https.any.js @@ -0,0 +1,64 @@ +// META: title=Cache.put +// META: global=window,worker +// META: script=/common/get-host-info.sub.js +// META: script=resources/test-helpers.js +// META: script=/storage/buckets/resources/util.js +// META: timeout=long + +var test_url = 'https://example.com/foo'; +var test_body = 'Hello world!'; +const { REMOTE_HOST } = get_host_info(); + +promise_test(async function(test) { + await prepareForBucketTest(test); + var inboxBucket = await navigator.storageBuckets.open('inbox'); + var draftsBucket = await navigator.storageBuckets.open('drafts'); + + const cacheName = 'attachments'; + const cacheKey = 'receipt1.txt'; + + var inboxCache = await inboxBucket.caches.open(cacheName); + var draftsCache = await draftsBucket.caches.open(cacheName); + + await inboxCache.put(cacheKey, new Response('bread x 2')) + await draftsCache.put(cacheKey, new Response('eggs x 1')); + + return inboxCache.match(cacheKey) + .then(function(result) { + return result.text(); + }) + .then(function(body) { + assert_equals(body, 'bread x 2', 'Wrong cache contents'); + return draftsCache.match(cacheKey); + }) + .then(function(result) { + return result.text(); + }) + .then(function(body) { + assert_equals(body, 'eggs x 1', 'Wrong cache contents'); + }); +}, 'caches from different buckets have different contents'); + +promise_test(async function(test) { + await prepareForBucketTest(test); + var inboxBucket = await navigator.storageBuckets.open('inbox'); + var draftBucket = await navigator.storageBuckets.open('drafts'); + + var caches = inboxBucket.caches; + var attachments = await caches.open('attachments'); + await attachments.put('receipt1.txt', new Response('bread x 2')); + var result = await attachments.match('receipt1.txt'); + assert_equals(await result.text(), 'bread x 2'); + + await navigator.storageBuckets.delete('inbox'); + + await promise_rejects_dom( + test, 'UnknownError', caches.open('attachments')); + + // Also test when `caches` is first accessed after the deletion. + await navigator.storageBuckets.delete('drafts'); + return promise_rejects_dom( + test, 'UnknownError', draftBucket.caches.open('attachments')); +}, 'cache.open promise is rejected when bucket is gone'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-storage-keys.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-storage-keys.https.any.js new file mode 100644 index 00000000000..f19522be1b4 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-storage-keys.https.any.js @@ -0,0 +1,35 @@ +// META: title=CacheStorage.keys +// META: global=window,worker +// META: script=./resources/test-helpers.js +// META: timeout=long + +var test_cache_list = + ['', 'example', 'Another cache name', 'A', 'a', 'ex ample']; + +promise_test(function(test) { + return self.caches.keys() + .then(function(keys) { + assert_true(Array.isArray(keys), + 'CacheStorage.keys should return an Array.'); + return Promise.all(keys.map(function(key) { + return self.caches.delete(key); + })); + }) + .then(function() { + return Promise.all(test_cache_list.map(function(key) { + return self.caches.open(key); + })); + }) + + .then(function() { return self.caches.keys(); }) + .then(function(keys) { + assert_true(Array.isArray(keys), + 'CacheStorage.keys should return an Array.'); + assert_array_equals(keys, + test_cache_list, + 'CacheStorage.keys should only return ' + + 'existing caches.'); + }); + }, 'CacheStorage keys'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-storage-match.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-storage-match.https.any.js new file mode 100644 index 00000000000..0c31b726294 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-storage-match.https.any.js @@ -0,0 +1,245 @@ +// META: title=CacheStorage.match +// META: global=window,worker +// META: script=./resources/test-helpers.js +// META: timeout=long + +(function() { + var next_index = 1; + + // Returns a transaction (request, response, and url) for a unique URL. + function create_unique_transaction(test) { + var uniquifier = String(next_index++); + var url = 'http://example.com/' + uniquifier; + + return { + request: new Request(url), + response: new Response('hello'), + url: url + }; + } + + self.create_unique_transaction = create_unique_transaction; +})(); + +cache_test(function(cache) { + var transaction = create_unique_transaction(); + + return cache.put(transaction.request.clone(), transaction.response.clone()) + .then(function() { + return self.caches.match(transaction.request); + }) + .then(function(response) { + assert_response_equals(response, transaction.response, + 'The response should not have changed.'); + }); +}, 'CacheStorageMatch with no cache name provided'); + +cache_test(function(cache) { + var transaction = create_unique_transaction(); + + var test_cache_list = ['a', 'b', 'c']; + return cache.put(transaction.request.clone(), transaction.response.clone()) + .then(function() { + return Promise.all(test_cache_list.map(function(key) { + return self.caches.open(key); + })); + }) + .then(function() { + return self.caches.match(transaction.request); + }) + .then(function(response) { + assert_response_equals(response, transaction.response, + 'The response should not have changed.'); + }); +}, 'CacheStorageMatch from one of many caches'); + +promise_test(function(test) { + var transaction = create_unique_transaction(); + + var test_cache_list = ['x', 'y', 'z']; + return Promise.all(test_cache_list.map(function(key) { + return self.caches.open(key); + })) + .then(function() { return self.caches.open('x'); }) + .then(function(cache) { + return cache.put(transaction.request.clone(), + transaction.response.clone()); + }) + .then(function() { + return self.caches.match(transaction.request, {cacheName: 'x'}); + }) + .then(function(response) { + assert_response_equals(response, transaction.response, + 'The response should not have changed.'); + }) + .then(function() { + return self.caches.match(transaction.request, {cacheName: 'y'}); + }) + .then(function(response) { + assert_equals(response, undefined, + 'Cache y should not have a response for the request.'); + }); +}, 'CacheStorageMatch from one of many caches by name'); + +cache_test(function(cache) { + var transaction = create_unique_transaction(); + return cache.put(transaction.url, transaction.response.clone()) + .then(function() { + return self.caches.match(transaction.request); + }) + .then(function(response) { + assert_response_equals(response, transaction.response, + 'The response should not have changed.'); + }); +}, 'CacheStorageMatch a string request'); + +cache_test(function(cache) { + var transaction = create_unique_transaction(); + return cache.put(transaction.request.clone(), transaction.response.clone()) + .then(function() { + return self.caches.match(new Request(transaction.request.url, + {method: 'HEAD'})); + }) + .then(function(response) { + assert_equals(response, undefined, + 'A HEAD request should not be matched'); + }); +}, 'CacheStorageMatch a HEAD request'); + +promise_test(function(test) { + var transaction = create_unique_transaction(); + return self.caches.match(transaction.request) + .then(function(response) { + assert_equals(response, undefined, + 'The response should not be found.'); + }); +}, 'CacheStorageMatch with no cached entry'); + +promise_test(function(test) { + var transaction = create_unique_transaction(); + return self.caches.delete('foo') + .then(function() { + return self.caches.has('foo'); + }) + .then(function(has_foo) { + assert_false(has_foo, "The cache should not exist."); + return self.caches.match(transaction.request, {cacheName: 'foo'}); + }) + .then(function(response) { + assert_equals(response, undefined, + 'The match with bad cache name should resolve to ' + + 'undefined.'); + return self.caches.has('foo'); + }) + .then(function(has_foo) { + assert_false(has_foo, "The cache should still not exist."); + }); +}, 'CacheStorageMatch with no caches available but name provided'); + +cache_test(function(cache) { + var transaction = create_unique_transaction(); + + return self.caches.delete('') + .then(function() { + return self.caches.has(''); + }) + .then(function(has_cache) { + assert_false(has_cache, "The cache should not exist."); + return cache.put(transaction.request, transaction.response.clone()); + }) + .then(function() { + return self.caches.match(transaction.request, {cacheName: ''}); + }) + .then(function(response) { + assert_equals(response, undefined, + 'The response should not be found.'); + return self.caches.open(''); + }) + .then(function(cache) { + return cache.put(transaction.request, transaction.response); + }) + .then(function() { + return self.caches.match(transaction.request, {cacheName: ''}); + }) + .then(function(response) { + assert_response_equals(response, transaction.response, + 'The response should be matched.'); + return self.caches.delete(''); + }); +}, 'CacheStorageMatch with empty cache name provided'); + +cache_test(function(cache) { + var request = new Request('http://example.com/?foo'); + var no_query_request = new Request('http://example.com/'); + var response = new Response('foo'); + return cache.put(request.clone(), response.clone()) + .then(function() { + return self.caches.match(no_query_request.clone()); + }) + .then(function(result) { + assert_equals( + result, undefined, + 'CacheStorageMatch should resolve as undefined with a ' + + 'mismatched query.'); + return self.caches.match(no_query_request.clone(), + {ignoreSearch: true}); + }) + .then(function(result) { + assert_response_equals( + result, response, + 'CacheStorageMatch with ignoreSearch should ignore the ' + + 'query of the request.'); + }); + }, 'CacheStorageMatch supports ignoreSearch'); + +cache_test(function(cache) { + var request = new Request('http://example.com/'); + var head_request = new Request('http://example.com/', {method: 'HEAD'}); + var response = new Response('foo'); + return cache.put(request.clone(), response.clone()) + .then(function() { + return self.caches.match(head_request.clone()); + }) + .then(function(result) { + assert_equals( + result, undefined, + 'CacheStorageMatch should resolve as undefined with a ' + + 'mismatched method.'); + return self.caches.match(head_request.clone(), + {ignoreMethod: true}); + }) + .then(function(result) { + assert_response_equals( + result, response, + 'CacheStorageMatch with ignoreMethod should ignore the ' + + 'method of request.'); + }); + }, 'Cache.match supports ignoreMethod'); + +cache_test(function(cache) { + var vary_request = new Request('http://example.com/c', + {headers: {'Cookies': 'is-for-cookie'}}); + var vary_response = new Response('', {headers: {'Vary': 'Cookies'}}); + var mismatched_vary_request = new Request('http://example.com/c'); + + return cache.put(vary_request.clone(), vary_response.clone()) + .then(function() { + return self.caches.match(mismatched_vary_request.clone()); + }) + .then(function(result) { + assert_equals( + result, undefined, + 'CacheStorageMatch should resolve as undefined with a ' + + ' mismatched vary.'); + return self.caches.match(mismatched_vary_request.clone(), + {ignoreVary: true}); + }) + .then(function(result) { + assert_response_equals( + result, vary_response, + 'CacheStorageMatch with ignoreVary should ignore the ' + + 'vary of request.'); + }); + }, 'CacheStorageMatch supports ignoreVary'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/cache-storage.https.any.js b/test/wpt/tests/service-workers/cache-storage/cache-storage.https.any.js new file mode 100644 index 00000000000..b7d5af7b532 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cache-storage.https.any.js @@ -0,0 +1,239 @@ +// META: title=CacheStorage +// META: global=window,worker +// META: script=./resources/test-helpers.js +// META: timeout=long + +promise_test(function(t) { + var cache_name = 'cache-storage/foo'; + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function(cache) { + assert_true(cache instanceof Cache, + 'CacheStorage.open should return a Cache.'); + }); + }, 'CacheStorage.open'); + +promise_test(function(t) { + var cache_name = 'cache-storage/bar'; + var first_cache = null; + var second_cache = null; + return self.caches.open(cache_name) + .then(function(cache) { + first_cache = cache; + return self.caches.delete(cache_name); + }) + .then(function() { + return first_cache.add('./resources/simple.txt'); + }) + .then(function() { + return self.caches.keys(); + }) + .then(function(cache_names) { + assert_equals(cache_names.indexOf(cache_name), -1); + return self.caches.open(cache_name); + }) + .then(function(cache) { + second_cache = cache; + return second_cache.keys(); + }) + .then(function(keys) { + assert_equals(keys.length, 0); + return first_cache.keys(); + }) + .then(function(keys) { + assert_equals(keys.length, 1); + // Clean up + return self.caches.delete(cache_name); + }); + }, 'CacheStorage.delete dooms, but does not delete immediately'); + +promise_test(function(t) { + // Note that this test may collide with other tests running in the same + // origin that also uses an empty cache name. + var cache_name = ''; + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function(cache) { + assert_true(cache instanceof Cache, + 'CacheStorage.open should accept an empty name.'); + }); + }, 'CacheStorage.open with an empty name'); + +promise_test(function(t) { + return promise_rejects_js( + t, + TypeError, + self.caches.open(), + 'CacheStorage.open should throw TypeError if called with no arguments.'); + }, 'CacheStorage.open with no arguments'); + +promise_test(function(t) { + var test_cases = [ + { + name: 'cache-storage/lowercase', + should_not_match: + [ + 'cache-storage/Lowercase', + ' cache-storage/lowercase', + 'cache-storage/lowercase ' + ] + }, + { + name: 'cache-storage/has a space', + should_not_match: + [ + 'cache-storage/has' + ] + }, + { + name: 'cache-storage/has\000_in_the_name', + should_not_match: + [ + 'cache-storage/has', + 'cache-storage/has_in_the_name' + ] + } + ]; + return Promise.all(test_cases.map(function(testcase) { + var cache_name = testcase.name; + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function() { + return self.caches.has(cache_name); + }) + .then(function(result) { + assert_true(result, + 'CacheStorage.has should return true for existing ' + + 'cache.'); + }) + .then(function() { + return Promise.all( + testcase.should_not_match.map(function(cache_name) { + return self.caches.has(cache_name) + .then(function(result) { + assert_false(result, + 'CacheStorage.has should only perform ' + + 'exact matches on cache names.'); + }); + })); + }) + .then(function() { + return self.caches.delete(cache_name); + }); + })); + }, 'CacheStorage.has with existing cache'); + +promise_test(function(t) { + return self.caches.has('cheezburger') + .then(function(result) { + assert_false(result, + 'CacheStorage.has should return false for ' + + 'nonexistent cache.'); + }); + }, 'CacheStorage.has with nonexistent cache'); + +promise_test(function(t) { + var cache_name = 'cache-storage/open'; + var cache; + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function(result) { + cache = result; + }) + .then(function() { + return cache.add('./resources/simple.txt'); + }) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function(result) { + assert_true(result instanceof Cache, + 'CacheStorage.open should return a Cache object'); + assert_not_equals(result, cache, + 'CacheStorage.open should return a new Cache ' + + 'object each time its called.'); + return Promise.all([cache.keys(), result.keys()]); + }) + .then(function(results) { + var expected_urls = results[0].map(function(r) { return r.url }); + var actual_urls = results[1].map(function(r) { return r.url }); + assert_array_equals(actual_urls, expected_urls, + 'CacheStorage.open should return a new Cache ' + + 'object for the same backing store.'); + }); + }, 'CacheStorage.open with existing cache'); + +promise_test(function(t) { + var cache_name = 'cache-storage/delete'; + + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function() { return self.caches.delete(cache_name); }) + .then(function(result) { + assert_true(result, + 'CacheStorage.delete should return true after ' + + 'deleting an existing cache.'); + }) + + .then(function() { return self.caches.has(cache_name); }) + .then(function(cache_exists) { + assert_false(cache_exists, + 'CacheStorage.has should return false after ' + + 'fulfillment of CacheStorage.delete promise.'); + }); + }, 'CacheStorage.delete with existing cache'); + +promise_test(function(t) { + return self.caches.delete('cheezburger') + .then(function(result) { + assert_false(result, + 'CacheStorage.delete should return false for a ' + + 'nonexistent cache.'); + }); + }, 'CacheStorage.delete with nonexistent cache'); + +promise_test(function(t) { + var unpaired_name = 'unpaired\uD800'; + var converted_name = 'unpaired\uFFFD'; + + // The test assumes that a cache with converted_name does not + // exist, but if the implementation fails the test then such + // a cache will be created. Start off in a fresh state by + // deleting all caches. + return delete_all_caches() + .then(function() { + return self.caches.has(converted_name); + }) + .then(function(cache_exists) { + assert_false(cache_exists, + 'Test setup failure: cache should not exist'); + }) + .then(function() { return self.caches.open(unpaired_name); }) + .then(function() { return self.caches.keys(); }) + .then(function(keys) { + assert_true(keys.indexOf(unpaired_name) !== -1, + 'keys should include cache with bad name'); + }) + .then(function() { return self.caches.has(unpaired_name); }) + .then(function(cache_exists) { + assert_true(cache_exists, + 'CacheStorage names should be not be converted.'); + }) + .then(function() { return self.caches.has(converted_name); }) + .then(function(cache_exists) { + assert_false(cache_exists, + 'CacheStorage names should be not be converted.'); + }); + }, 'CacheStorage names are DOMStrings not USVStrings'); + +done(); diff --git a/test/wpt/tests/service-workers/cache-storage/common.https.window.js b/test/wpt/tests/service-workers/cache-storage/common.https.window.js new file mode 100644 index 00000000000..eba312c273d --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/common.https.window.js @@ -0,0 +1,44 @@ +// META: title=Cache Storage: Verify that Window and Workers see same storage +// META: timeout=long + +function wait_for_message(worker) { + return new Promise(function(resolve) { + worker.addEventListener('message', function listener(e) { + resolve(e.data); + worker.removeEventListener('message', listener); + }); + }); +} + +promise_test(function(t) { + var cache_name = 'common-test'; + return self.caches.delete(cache_name) + .then(function() { + var worker = new Worker('resources/common-worker.js'); + worker.postMessage({name: cache_name}); + return wait_for_message(worker); + }) + .then(function(message) { + return self.caches.open(cache_name); + }) + .then(function(cache) { + return Promise.all([ + cache.match('https://example.com/a'), + cache.match('https://example.com/b'), + cache.match('https://example.com/c') + ]); + }) + .then(function(responses) { + return Promise.all(responses.map( + function(response) { return response.text(); } + )); + }) + .then(function(bodies) { + assert_equals(bodies[0], 'a', + 'Body should match response put by worker'); + assert_equals(bodies[1], 'b', + 'Body should match response put by worker'); + assert_equals(bodies[2], 'c', + 'Body should match response put by worker'); + }); +}, 'Window sees cache puts by Worker'); diff --git a/test/wpt/tests/service-workers/cache-storage/crashtests/cache-response-clone.https.html b/test/wpt/tests/service-workers/cache-storage/crashtests/cache-response-clone.https.html new file mode 100644 index 00000000000..ec930a87d93 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/crashtests/cache-response-clone.https.html @@ -0,0 +1,17 @@ + + + + diff --git a/test/wpt/tests/service-workers/cache-storage/credentials.https.html b/test/wpt/tests/service-workers/cache-storage/credentials.https.html new file mode 100644 index 00000000000..0fe4a0a0ac0 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/credentials.https.html @@ -0,0 +1,46 @@ + + +Cache Storage: Verify credentials are respected by Cache operations + + + + + + diff --git a/test/wpt/tests/service-workers/cache-storage/cross-partition.https.tentative.html b/test/wpt/tests/service-workers/cache-storage/cross-partition.https.tentative.html new file mode 100644 index 00000000000..1cfc2569088 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/cross-partition.https.tentative.html @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/cache-storage/resources/blank.html b/test/wpt/tests/service-workers/cache-storage/resources/blank.html new file mode 100644 index 00000000000..a3c3a4689a6 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/blank.html @@ -0,0 +1,2 @@ + +Empty doc diff --git a/test/wpt/tests/service-workers/cache-storage/resources/cache-keys-attributes-for-service-worker.js b/test/wpt/tests/service-workers/cache-storage/resources/cache-keys-attributes-for-service-worker.js new file mode 100644 index 00000000000..ee574d2cb77 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/cache-keys-attributes-for-service-worker.js @@ -0,0 +1,22 @@ +self.addEventListener('fetch', (event) => { + const params = new URL(event.request.url).searchParams; + if (params.has('ignore')) { + return; + } + if (!params.has('name')) { + event.respondWith(Promise.reject(TypeError('No name is provided.'))); + return; + } + + event.respondWith(Promise.resolve().then(async () => { + const name = params.get('name'); + await caches.delete('foo'); + const cache = await caches.open('foo'); + await cache.put(event.request, new Response('hello')); + const keys = await cache.keys(); + + const original = event.request[name]; + const stored = keys[0][name]; + return new Response(`original: ${original}, stored: ${stored}`); + })); + }); diff --git a/test/wpt/tests/service-workers/cache-storage/resources/common-worker.js b/test/wpt/tests/service-workers/cache-storage/resources/common-worker.js new file mode 100644 index 00000000000..d0e8544b56c --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/common-worker.js @@ -0,0 +1,15 @@ +self.onmessage = function(e) { + var cache_name = e.data.name; + + self.caches.open(cache_name) + .then(function(cache) { + return Promise.all([ + cache.put('https://example.com/a', new Response('a')), + cache.put('https://example.com/b', new Response('b')), + cache.put('https://example.com/c', new Response('c')) + ]); + }) + .then(function() { + self.postMessage('ok'); + }); +}; diff --git a/test/wpt/tests/service-workers/cache-storage/resources/credentials-iframe.html b/test/wpt/tests/service-workers/cache-storage/resources/credentials-iframe.html new file mode 100644 index 00000000000..00702df9e5b --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/credentials-iframe.html @@ -0,0 +1,38 @@ + + +Controlled frame for Cache API test with credentials + + +Hello? Yes, this is iframe. + diff --git a/test/wpt/tests/service-workers/cache-storage/resources/credentials-worker.js b/test/wpt/tests/service-workers/cache-storage/resources/credentials-worker.js new file mode 100644 index 00000000000..43965b5fe4c --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/credentials-worker.js @@ -0,0 +1,59 @@ +var cache_name = 'credentials'; + +function assert_equals(actual, expected, message) { + if (!Object.is(actual, expected)) + throw Error(message + ': expected: ' + expected + ', actual: ' + actual); +} + +self.onfetch = function(e) { + if (!/\.txt$/.test(e.request.url)) return; + var content = e.request.url; + var cache; + e.respondWith( + self.caches.open(cache_name) + .then(function(result) { + cache = result; + return cache.put(e.request, new Response(content)); + }) + + .then(function() { return cache.match(e.request); }) + .then(function(result) { return result.text(); }) + .then(function(text) { + assert_equals(text, content, 'Cache.match() body should match'); + }) + + .then(function() { return cache.matchAll(e.request); }) + .then(function(results) { + assert_equals(results.length, 1, 'Should have one response'); + return results[0].text(); + }) + .then(function(text) { + assert_equals(text, content, 'Cache.matchAll() body should match'); + }) + + .then(function() { return self.caches.match(e.request); }) + .then(function(result) { return result.text(); }) + .then(function(text) { + assert_equals(text, content, 'CacheStorage.match() body should match'); + }) + + .then(function() { + return new Response('dummy'); + }) + ); +}; + +self.onmessage = function(e) { + if (e.data === 'keys') { + self.caches.open(cache_name) + .then(function(cache) { return cache.keys(); }) + .then(function(requests) { + var urls = requests.map(function(request) { return request.url; }); + self.clients.matchAll().then(function(clients) { + clients.forEach(function(client) { + client.postMessage(urls); + }); + }); + }); + } +}; diff --git a/test/wpt/tests/service-workers/cache-storage/resources/fetch-status.py b/test/wpt/tests/service-workers/cache-storage/resources/fetch-status.py new file mode 100644 index 00000000000..b7109f4787f --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/fetch-status.py @@ -0,0 +1,2 @@ +def main(request, response): + return int(request.GET[b"status"]), [], b"" diff --git a/test/wpt/tests/service-workers/cache-storage/resources/iframe.html b/test/wpt/tests/service-workers/cache-storage/resources/iframe.html new file mode 100644 index 00000000000..a2f1e502bb3 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/iframe.html @@ -0,0 +1,18 @@ + +ok + diff --git a/test/wpt/tests/service-workers/cache-storage/resources/simple.txt b/test/wpt/tests/service-workers/cache-storage/resources/simple.txt new file mode 100644 index 00000000000..9e3cb91fb9b --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/simple.txt @@ -0,0 +1 @@ +a simple text file diff --git a/test/wpt/tests/service-workers/cache-storage/resources/test-helpers.js b/test/wpt/tests/service-workers/cache-storage/resources/test-helpers.js new file mode 100644 index 00000000000..050ac0b5424 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/test-helpers.js @@ -0,0 +1,272 @@ +(function() { + var next_cache_index = 1; + + // Returns a promise that resolves to a newly created Cache object. The + // returned Cache will be destroyed when |test| completes. + function create_temporary_cache(test) { + var uniquifier = String(++next_cache_index); + var cache_name = self.location.pathname + '/' + uniquifier; + + test.add_cleanup(function() { + self.caches.delete(cache_name); + }); + + return self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }); + } + + self.create_temporary_cache = create_temporary_cache; +})(); + +// Runs |test_function| with a temporary unique Cache passed in as the only +// argument. The function is run as a part of Promise chain owned by +// promise_test(). As such, it is expected to behave in a manner identical (with +// the exception of the argument) to a function passed into promise_test(). +// +// E.g.: +// cache_test(function(cache) { +// // Do something with |cache|, which is a Cache object. +// }, "Some Cache test"); +function cache_test(test_function, description) { + promise_test(function(test) { + return create_temporary_cache(test) + .then(function(cache) { return test_function(cache, test); }); + }, description); +} + +// A set of Request/Response pairs to be used with prepopulated_cache_test(). +var simple_entries = [ + { + name: 'a', + request: new Request('http://example.com/a'), + response: new Response('') + }, + + { + name: 'b', + request: new Request('http://example.com/b'), + response: new Response('') + }, + + { + name: 'a_with_query', + request: new Request('http://example.com/a?q=r'), + response: new Response('') + }, + + { + name: 'A', + request: new Request('http://example.com/A'), + response: new Response('') + }, + + { + name: 'a_https', + request: new Request('https://example.com/a'), + response: new Response('') + }, + + { + name: 'a_org', + request: new Request('http://example.org/a'), + response: new Response('') + }, + + { + name: 'cat', + request: new Request('http://example.com/cat'), + response: new Response('') + }, + + { + name: 'catmandu', + request: new Request('http://example.com/catmandu'), + response: new Response('') + }, + + { + name: 'cat_num_lives', + request: new Request('http://example.com/cat?lives=9'), + response: new Response('') + }, + + { + name: 'cat_in_the_hat', + request: new Request('http://example.com/cat/in/the/hat'), + response: new Response('') + }, + + { + name: 'non_2xx_response', + request: new Request('http://example.com/non2xx'), + response: new Response('', {status: 404, statusText: 'nope'}) + }, + + { + name: 'error_response', + request: new Request('http://example.com/error'), + response: Response.error() + }, +]; + +// A set of Request/Response pairs to be used with prepopulated_cache_test(). +// These contain a mix of test cases that use Vary headers. +var vary_entries = [ + { + name: 'vary_cookie_is_cookie', + request: new Request('http://example.com/c', + {headers: {'Cookies': 'is-for-cookie'}}), + response: new Response('', + {headers: {'Vary': 'Cookies'}}) + }, + + { + name: 'vary_cookie_is_good', + request: new Request('http://example.com/c', + {headers: {'Cookies': 'is-good-enough-for-me'}}), + response: new Response('', + {headers: {'Vary': 'Cookies'}}) + }, + + { + name: 'vary_cookie_absent', + request: new Request('http://example.com/c'), + response: new Response('', + {headers: {'Vary': 'Cookies'}}) + } +]; + +// Run |test_function| with a Cache object and a map of entries. Prior to the +// call, the Cache is populated by cache entries from |entries|. The latter is +// expected to be an Object mapping arbitrary keys to objects of the form +// {request: , response: }. Entries are +// serially added to the cache in the order specified. +// +// |test_function| should return a Promise that can be used with promise_test. +function prepopulated_cache_test(entries, test_function, description) { + cache_test(function(cache) { + var p = Promise.resolve(); + var hash = {}; + entries.forEach(function(entry) { + hash[entry.name] = entry; + p = p.then(function() { + return cache.put(entry.request.clone(), entry.response.clone()) + .catch(function(e) { + assert_unreached( + 'Test setup failed for entry ' + entry.name + ': ' + e + ); + }); + }); + }); + return p + .then(function() { + assert_equals(Object.keys(hash).length, entries.length); + }) + .then(function() { + return test_function(cache, hash); + }); + }, description); +} + +// Helper for testing with Headers objects. Compares Headers instances +// by serializing |expected| and |actual| to arrays and comparing. +function assert_header_equals(actual, expected, description) { + assert_class_string(actual, "Headers", description); + var header; + var actual_headers = []; + var expected_headers = []; + for (header of actual) + actual_headers.push(header[0] + ": " + header[1]); + for (header of expected) + expected_headers.push(header[0] + ": " + header[1]); + assert_array_equals(actual_headers, expected_headers, + description + " Headers differ."); +} + +// Helper for testing with Response objects. Compares simple +// attributes defined on the interfaces, as well as the headers. It +// does not compare the response bodies. +function assert_response_equals(actual, expected, description) { + assert_class_string(actual, "Response", description); + ["type", "url", "status", "ok", "statusText"].forEach(function(attribute) { + assert_equals(actual[attribute], expected[attribute], + description + " Attributes differ: " + attribute + "."); + }); + assert_header_equals(actual.headers, expected.headers, description); +} + +// Assert that the two arrays |actual| and |expected| contain the same +// set of Responses as determined by assert_response_equals. The order +// is not significant. +// +// |expected| is assumed to not contain any duplicates. +function assert_response_array_equivalent(actual, expected, description) { + assert_true(Array.isArray(actual), description); + assert_equals(actual.length, expected.length, description); + expected.forEach(function(expected_element) { + // assert_response_in_array treats the first argument as being + // 'actual', and the second as being 'expected array'. We are + // switching them around because we want to be resilient + // against the |actual| array containing duplicates. + assert_response_in_array(expected_element, actual, description); + }); +} + +// Asserts that two arrays |actual| and |expected| contain the same +// set of Responses as determined by assert_response_equals(). The +// corresponding elements must occupy corresponding indices in their +// respective arrays. +function assert_response_array_equals(actual, expected, description) { + assert_true(Array.isArray(actual), description); + assert_equals(actual.length, expected.length, description); + actual.forEach(function(value, index) { + assert_response_equals(value, expected[index], + description + " : object[" + index + "]"); + }); +} + +// Equivalent to assert_in_array, but uses assert_response_equals. +function assert_response_in_array(actual, expected_array, description) { + assert_true(expected_array.some(function(element) { + try { + assert_response_equals(actual, element); + return true; + } catch (e) { + return false; + } + }), description); +} + +// Helper for testing with Request objects. Compares simple +// attributes defined on the interfaces, as well as the headers. +function assert_request_equals(actual, expected, description) { + assert_class_string(actual, "Request", description); + ["url"].forEach(function(attribute) { + assert_equals(actual[attribute], expected[attribute], + description + " Attributes differ: " + attribute + "."); + }); + assert_header_equals(actual.headers, expected.headers, description); +} + +// Asserts that two arrays |actual| and |expected| contain the same +// set of Requests as determined by assert_request_equals(). The +// corresponding elements must occupy corresponding indices in their +// respective arrays. +function assert_request_array_equals(actual, expected, description) { + assert_true(Array.isArray(actual), description); + assert_equals(actual.length, expected.length, description); + actual.forEach(function(value, index) { + assert_request_equals(value, expected[index], + description + " : object[" + index + "]"); + }); +} + +// Deletes all caches, returning a promise indicating success. +function delete_all_caches() { + return self.caches.keys() + .then(function(keys) { + return Promise.all(keys.map(self.caches.delete.bind(self.caches))); + }); +} diff --git a/test/wpt/tests/service-workers/cache-storage/resources/vary.py b/test/wpt/tests/service-workers/cache-storage/resources/vary.py new file mode 100644 index 00000000000..7fde1b1094e --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/resources/vary.py @@ -0,0 +1,25 @@ +def main(request, response): + if b"clear-vary-value-override-cookie" in request.GET: + response.unset_cookie(b"vary-value-override") + return b"vary cookie cleared" + + set_cookie_vary = request.GET.first(b"set-vary-value-override-cookie", + default=b"") + if set_cookie_vary: + response.set_cookie(b"vary-value-override", set_cookie_vary) + return b"vary cookie set" + + # If there is a vary-value-override cookie set, then use its value + # for the VARY header no matter what the query string is set to. This + # override is necessary to test the case when two URLs are identical + # (including query), but differ by VARY header. + cookie_vary = request.cookies.get(b"vary-value-override") + if cookie_vary: + response.headers.set(b"vary", str(cookie_vary)) + else: + # If there is no cookie, then use the query string value, if present. + query_vary = request.GET.first(b"vary", default=b"") + if query_vary: + response.headers.set(b"vary", query_vary) + + return b"vary response" diff --git a/test/wpt/tests/service-workers/cache-storage/sandboxed-iframes.https.html b/test/wpt/tests/service-workers/cache-storage/sandboxed-iframes.https.html new file mode 100644 index 00000000000..098fa89daf4 --- /dev/null +++ b/test/wpt/tests/service-workers/cache-storage/sandboxed-iframes.https.html @@ -0,0 +1,66 @@ + +Cache Storage: Verify access in sandboxed iframes + + + + + diff --git a/test/wpt/tests/service-workers/idlharness.https.any.js b/test/wpt/tests/service-workers/idlharness.https.any.js new file mode 100644 index 00000000000..8db5d4d10ff --- /dev/null +++ b/test/wpt/tests/service-workers/idlharness.https.any.js @@ -0,0 +1,53 @@ +// META: global=window,worker +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: script=cache-storage/resources/test-helpers.js +// META: script=service-worker/resources/test-helpers.sub.js +// META: timeout=long + +// https://w3c.github.io/ServiceWorker + +idl_test( + ['service-workers'], + ['dom', 'html'], + async (idl_array, t) => { + self.cacheInstance = await create_temporary_cache(t); + + idl_array.add_objects({ + CacheStorage: ['caches'], + Cache: ['self.cacheInstance'], + ServiceWorkerContainer: ['navigator.serviceWorker'] + }); + + // TODO: Add ServiceWorker and ServiceWorkerRegistration instances for the + // other worker scopes. + if (self.GLOBAL.isWindow()) { + idl_array.add_objects({ + ServiceWorkerRegistration: ['registrationInstance'], + ServiceWorker: ['registrationInstance.installing'] + }); + + const scope = 'service-worker/resources/scope/idlharness'; + const registration = await service_worker_unregister_and_register( + t, 'service-worker/resources/empty-worker.js', scope); + t.add_cleanup(() => registration.unregister()); + + self.registrationInstance = registration; + } else if (self.ServiceWorkerGlobalScope) { + // self.ServiceWorkerGlobalScope should only be defined for the + // ServiceWorker scope, which allows us to detect and test the interfaces + // exposed only for ServiceWorker. + idl_array.add_objects({ + Clients: ['clients'], + ExtendableEvent: ['new ExtendableEvent("type")'], + FetchEvent: ['new FetchEvent("type", { request: new Request("") })'], + ServiceWorkerGlobalScope: ['self'], + ServiceWorkerRegistration: ['registration'], + ServiceWorker: ['serviceWorker'], + // TODO: Test instances of Client and WindowClient, e.g. + // Client: ['self.clientInstance'], + // WindowClient: ['self.windowClientInstance'] + }); + } + } +); diff --git a/test/wpt/tests/service-workers/service-worker/Service-Worker-Allowed-header.https.html b/test/wpt/tests/service-workers/service-worker/Service-Worker-Allowed-header.https.html new file mode 100644 index 00000000000..6f44bb17e7d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/Service-Worker-Allowed-header.https.html @@ -0,0 +1,88 @@ + +Service Worker: Service-Worker-Allowed header + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/close.https.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/close.https.html new file mode 100644 index 00000000000..3e3cc8b2b08 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/close.https.html @@ -0,0 +1,11 @@ + +ServiceWorkerGlobalScope: close operation + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/extendable-message-event-constructor.https.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/extendable-message-event-constructor.https.html new file mode 100644 index 00000000000..525245fe9ec --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/extendable-message-event-constructor.https.html @@ -0,0 +1,10 @@ + +ServiceWorkerGlobalScope: ExtendableMessageEvent Constructor + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/extendable-message-event.https.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/extendable-message-event.https.html new file mode 100644 index 00000000000..89efd7a4a61 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/extendable-message-event.https.html @@ -0,0 +1,226 @@ + +ServiceWorkerGlobalScope: ExtendableMessageEvent + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/fetch-on-the-right-interface.https.any.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/fetch-on-the-right-interface.https.any.js new file mode 100644 index 00000000000..5ca5f65680c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/fetch-on-the-right-interface.https.any.js @@ -0,0 +1,14 @@ +// META: title=fetch method on the right interface +// META: global=serviceworker + +test(function() { + assert_false(self.hasOwnProperty('fetch'), 'ServiceWorkerGlobalScope ' + + 'instance should not have "fetch" method as its property.'); + assert_inherits(self, 'fetch', 'ServiceWorkerGlobalScope should ' + + 'inherit "fetch" method.'); + assert_own_property(Object.getPrototypeOf(Object.getPrototypeOf(self)), 'fetch', + 'WorkerGlobalScope should have "fetch" propery in its prototype.'); + assert_equals(self.fetch, Object.getPrototypeOf(Object.getPrototypeOf(self)).fetch, + 'ServiceWorkerGlobalScope.fetch should be the same as ' + + 'WorkerGlobalScope.fetch.'); +}, 'Fetch method on the right interface'); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/isSecureContext.https.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/isSecureContext.https.html new file mode 100644 index 00000000000..399820dd2c3 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/isSecureContext.https.html @@ -0,0 +1,32 @@ + + + +Service Worker: isSecureContext + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/isSecureContext.serviceworker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/isSecureContext.serviceworker.js new file mode 100644 index 00000000000..5033594e34b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/isSecureContext.serviceworker.js @@ -0,0 +1,5 @@ +importScripts("/resources/testharness.js"); + +test(() => { + assert_true(self.isSecureContext, true); +}, "isSecureContext"); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/postmessage.https.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/postmessage.https.html new file mode 100644 index 00000000000..99dedebf2e5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/postmessage.https.html @@ -0,0 +1,83 @@ + +ServiceWorkerGlobalScope: postMessage + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html new file mode 100644 index 00000000000..aa3c74a13bb --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/registration-attribute.https.html @@ -0,0 +1,107 @@ + +ServiceWorkerGlobalScope: registration + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/close-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/close-worker.js new file mode 100644 index 00000000000..41a8bc069af --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/close-worker.js @@ -0,0 +1,5 @@ +importScripts('../../resources/worker-testharness.js'); + +test(function() { + assert_false('close' in self); +}, 'ServiceWorkerGlobalScope close operation'); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/error-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/error-worker.js new file mode 100644 index 00000000000..f6838ffb394 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/error-worker.js @@ -0,0 +1,12 @@ +var source; + +self.addEventListener('message', function(e) { + source = e.source; + throw 'testError'; +}); + +self.addEventListener('error', function(e) { + source.postMessage({ + error: e.error, filename: e.filename, message: e.message, lineno: e.lineno, + colno: e.colno}); +}); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-constructor-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-constructor-worker.js new file mode 100644 index 00000000000..42da5825c56 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-constructor-worker.js @@ -0,0 +1,197 @@ +importScripts('/resources/testharness.js'); + +const TEST_OBJECT = { wanwan: 123 }; +const CHANNEL1 = new MessageChannel(); +const CHANNEL2 = new MessageChannel(); +const PORTS = [CHANNEL1.port1, CHANNEL1.port2, CHANNEL2.port1]; +function createEvent(initializer) { + if (initializer === undefined) + return new ExtendableMessageEvent('type'); + return new ExtendableMessageEvent('type', initializer); +} + +// These test cases are mostly copied from the following file in the Chromium +// project (as of commit 848ad70823991e0f12b437d789943a4ab24d65bb): +// third_party/WebKit/LayoutTests/fast/events/constructors/message-event-constructor.html + +test(function() { + assert_false(createEvent().bubbles); + assert_false(createEvent().cancelable); + assert_equals(createEvent().data, null); + assert_equals(createEvent().origin, ''); + assert_equals(createEvent().lastEventId, ''); + assert_equals(createEvent().source, null); + assert_array_equals(createEvent().ports, []); +}, 'no initializer specified'); + +test(function() { + assert_false(createEvent({ bubbles: false }).bubbles); + assert_true(createEvent({ bubbles: true }).bubbles); +}, '`bubbles` is specified'); + +test(function() { + assert_false(createEvent({ cancelable: false }).cancelable); + assert_true(createEvent({ cancelable: true }).cancelable); +}, '`cancelable` is specified'); + +test(function() { + assert_equals(createEvent({ data: TEST_OBJECT }).data, TEST_OBJECT); + assert_equals(createEvent({ data: undefined }).data, null); + assert_equals(createEvent({ data: null }).data, null); + assert_equals(createEvent({ data: false }).data, false); + assert_equals(createEvent({ data: true }).data, true); + assert_equals(createEvent({ data: '' }).data, ''); + assert_equals(createEvent({ data: 'chocolate' }).data, 'chocolate'); + assert_equals(createEvent({ data: 12345 }).data, 12345); + assert_equals(createEvent({ data: 18446744073709551615 }).data, + 18446744073709552000); + assert_equals(createEvent({ data: NaN }).data, NaN); + // Note that valueOf() is not called, when the left hand side is + // evaluated. + assert_false( + createEvent({ data: { + valueOf: function() { return TEST_OBJECT; } } }).data == + TEST_OBJECT); + assert_equals(createEvent({ get data(){ return 123; } }).data, 123); + let thrown = { name: 'Error' }; + assert_throws_exactly(thrown, function() { + createEvent({ get data() { throw thrown; } }); }); +}, '`data` is specified'); + +test(function() { + assert_equals(createEvent({ origin: 'melancholy' }).origin, 'melancholy'); + assert_equals(createEvent({ origin: '' }).origin, ''); + assert_equals(createEvent({ origin: null }).origin, 'null'); + assert_equals(createEvent({ origin: false }).origin, 'false'); + assert_equals(createEvent({ origin: true }).origin, 'true'); + assert_equals(createEvent({ origin: 12345 }).origin, '12345'); + assert_equals( + createEvent({ origin: 18446744073709551615 }).origin, + '18446744073709552000'); + assert_equals(createEvent({ origin: NaN }).origin, 'NaN'); + assert_equals(createEvent({ origin: [] }).origin, ''); + assert_equals(createEvent({ origin: [1, 2, 3] }).origin, '1,2,3'); + assert_equals( + createEvent({ origin: { melancholy: 12345 } }).origin, + '[object Object]'); + // Note that valueOf() is not called, when the left hand side is + // evaluated. + assert_equals( + createEvent({ origin: { + valueOf: function() { return 'melancholy'; } } }).origin, + '[object Object]'); + assert_equals( + createEvent({ get origin() { return 123; } }).origin, '123'); + let thrown = { name: 'Error' }; + assert_throws_exactly(thrown, function() { + createEvent({ get origin() { throw thrown; } }); }); +}, '`origin` is specified'); + +test(function() { + assert_equals( + createEvent({ lastEventId: 'melancholy' }).lastEventId, 'melancholy'); + assert_equals(createEvent({ lastEventId: '' }).lastEventId, ''); + assert_equals(createEvent({ lastEventId: null }).lastEventId, 'null'); + assert_equals(createEvent({ lastEventId: false }).lastEventId, 'false'); + assert_equals(createEvent({ lastEventId: true }).lastEventId, 'true'); + assert_equals(createEvent({ lastEventId: 12345 }).lastEventId, '12345'); + assert_equals( + createEvent({ lastEventId: 18446744073709551615 }).lastEventId, + '18446744073709552000'); + assert_equals(createEvent({ lastEventId: NaN }).lastEventId, 'NaN'); + assert_equals(createEvent({ lastEventId: [] }).lastEventId, ''); + assert_equals( + createEvent({ lastEventId: [1, 2, 3] }).lastEventId, '1,2,3'); + assert_equals( + createEvent({ lastEventId: { melancholy: 12345 } }).lastEventId, + '[object Object]'); + // Note that valueOf() is not called, when the left hand side is + // evaluated. + assert_equals( + createEvent({ lastEventId: { + valueOf: function() { return 'melancholy'; } } }).lastEventId, + '[object Object]'); + assert_equals( + createEvent({ get lastEventId() { return 123; } }).lastEventId, + '123'); + let thrown = { name: 'Error' }; + assert_throws_exactly(thrown, function() { + createEvent({ get lastEventId() { throw thrown; } }); }); +}, '`lastEventId` is specified'); + +test(function() { + assert_equals(createEvent({ source: CHANNEL1.port1 }).source, CHANNEL1.port1); + assert_equals( + createEvent({ source: self.registration.active }).source, + self.registration.active); + assert_equals( + createEvent({ source: CHANNEL1.port1 }).source, CHANNEL1.port1); + assert_throws_js( + TypeError, function() { createEvent({ source: this }); }, + 'source should be Client or ServiceWorker or MessagePort'); +}, '`source` is specified'); + +test(function() { + // Valid message ports. + var passed_ports = createEvent({ ports: PORTS}).ports; + assert_equals(passed_ports[0], CHANNEL1.port1); + assert_equals(passed_ports[1], CHANNEL1.port2); + assert_equals(passed_ports[2], CHANNEL2.port1); + assert_array_equals(createEvent({ ports: [] }).ports, []); + assert_array_equals(createEvent({ ports: undefined }).ports, []); + + // Invalid message ports. + assert_throws_js(TypeError, + function() { createEvent({ ports: [1, 2, 3] }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: TEST_OBJECT }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: null }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: this }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: false }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: true }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: '' }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: 'chocolate' }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: 12345 }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: 18446744073709551615 }); }); + assert_throws_js(TypeError, + function() { createEvent({ ports: NaN }); }); + assert_throws_js(TypeError, + function() { createEvent({ get ports() { return 123; } }); }); + let thrown = { name: 'Error' }; + assert_throws_exactly(thrown, function() { + createEvent({ get ports() { throw thrown; } }); }); + // Note that valueOf() is not called, when the left hand side is + // evaluated. + var valueOf = function() { return PORTS; }; + assert_throws_js(TypeError, function() { + createEvent({ ports: { valueOf: valueOf } }); }); +}, '`ports` is specified'); + +test(function() { + var initializers = { + bubbles: true, + cancelable: true, + data: TEST_OBJECT, + origin: 'wonderful', + lastEventId: 'excellent', + source: CHANNEL1.port1, + ports: PORTS + }; + assert_equals(createEvent(initializers).bubbles, true); + assert_equals(createEvent(initializers).cancelable, true); + assert_equals(createEvent(initializers).data, TEST_OBJECT); + assert_equals(createEvent(initializers).origin, 'wonderful'); + assert_equals(createEvent(initializers).lastEventId, 'excellent'); + assert_equals(createEvent(initializers).source, CHANNEL1.port1); + assert_equals(createEvent(initializers).ports[0], PORTS[0]); + assert_equals(createEvent(initializers).ports[1], PORTS[1]); + assert_equals(createEvent(initializers).ports[2], PORTS[2]); +}, 'all initial values are specified'); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-loopback-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-loopback-worker.js new file mode 100644 index 00000000000..13cae88d800 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-loopback-worker.js @@ -0,0 +1,36 @@ +importScripts('./extendable-message-event-utils.js'); + +self.addEventListener('message', function(event) { + switch (event.data.type) { + case 'start': + self.registration.active.postMessage( + {type: '1st', client_id: event.source.id}); + break; + case '1st': + // 1st loopback message via ServiceWorkerRegistration.active. + var results = { + trial: 1, + event: ExtendableMessageEventUtils.serialize(event) + }; + var client_id = event.data.client_id; + event.source.postMessage({type: '2nd', client_id: client_id}); + event.waitUntil(clients.get(client_id) + .then(function(client) { + client.postMessage({type: 'record', results: results}); + })); + break; + case '2nd': + // 2nd loopback message via ExtendableMessageEvent.source. + var results = { + trial: 2, + event: ExtendableMessageEventUtils.serialize(event) + }; + var client_id = event.data.client_id; + event.waitUntil(clients.get(client_id) + .then(function(client) { + client.postMessage({type: 'record', results: results}); + client.postMessage({type: 'finish'}); + })); + break; + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-ping-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-ping-worker.js new file mode 100644 index 00000000000..d07b22959cc --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-ping-worker.js @@ -0,0 +1,23 @@ +importScripts('./extendable-message-event-utils.js'); + +self.addEventListener('message', function(event) { + switch (event.data.type) { + case 'start': + // Send a ping message to another service worker. + self.registration.waiting.postMessage( + {type: 'ping', client_id: event.source.id}); + break; + case 'pong': + var results = { + pingOrPong: 'pong', + event: ExtendableMessageEventUtils.serialize(event) + }; + var client_id = event.data.client_id; + event.waitUntil(clients.get(client_id) + .then(function(client) { + client.postMessage({type: 'record', results: results}); + client.postMessage({type: 'finish'}); + })); + break; + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-pong-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-pong-worker.js new file mode 100644 index 00000000000..5e9669e83c4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-pong-worker.js @@ -0,0 +1,18 @@ +importScripts('./extendable-message-event-utils.js'); + +self.addEventListener('message', function(event) { + switch (event.data.type) { + case 'ping': + var results = { + pingOrPong: 'ping', + event: ExtendableMessageEventUtils.serialize(event) + }; + var client_id = event.data.client_id; + event.waitUntil(clients.get(client_id) + .then(function(client) { + client.postMessage({type: 'record', results: results}); + event.source.postMessage({type: 'pong', client_id: client_id}); + })); + break; + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-utils.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-utils.js new file mode 100644 index 00000000000..d6a3b483f53 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-utils.js @@ -0,0 +1,78 @@ +var ExtendableMessageEventUtils = {}; + +// Create a representation of a given ExtendableMessageEvent that is suitable +// for transmission via the `postMessage` API. +ExtendableMessageEventUtils.serialize = function(event) { + var ports = event.ports.map(function(port) { + return { constructor: { name: port.constructor.name } }; + }); + return { + constructor: { + name: event.constructor.name + }, + origin: event.origin, + lastEventId: event.lastEventId, + source: { + constructor: { + name: event.source.constructor.name + }, + url: event.source.url, + frameType: event.source.frameType, + visibilityState: event.source.visibilityState, + focused: event.source.focused + }, + ports: ports + }; +}; + +// Compare the actual and expected values of an ExtendableMessageEvent that has +// been transformed using the `serialize` function defined in this file. +ExtendableMessageEventUtils.assert_equals = function(actual, expected) { + assert_equals( + actual.constructor.name, expected.constructor.name, 'event constructor' + ); + assert_equals(actual.origin, expected.origin, 'event `origin` property'); + assert_equals( + actual.lastEventId, + expected.lastEventId, + 'event `lastEventId` property' + ); + + assert_equals( + actual.source.constructor.name, + expected.source.constructor.name, + 'event `source` property constructor' + ); + assert_equals( + actual.source.url, expected.source.url, 'event `source` property `url`' + ); + assert_equals( + actual.source.frameType, + expected.source.frameType, + 'event `source` property `frameType`' + ); + assert_equals( + actual.source.visibilityState, + expected.source.visibilityState, + 'event `source` property `visibilityState`' + ); + assert_equals( + actual.source.focused, + expected.source.focused, + 'event `source` property `focused`' + ); + + assert_equals( + actual.ports.length, + expected.ports.length, + 'event `ports` property length' + ); + + for (var idx = 0; idx < expected.ports.length; ++idx) { + assert_equals( + actual.ports[idx].constructor.name, + expected.ports[idx].constructor.name, + 'MessagePort #' + idx + ' constructor' + ); + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-worker.js new file mode 100644 index 00000000000..f5e7647e3e7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/extendable-message-event-worker.js @@ -0,0 +1,5 @@ +importScripts('./extendable-message-event-utils.js'); + +self.addEventListener('message', function(event) { + event.source.postMessage(ExtendableMessageEventUtils.serialize(event)); + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-loopback-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-loopback-worker.js new file mode 100644 index 00000000000..083e9aa2a85 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-loopback-worker.js @@ -0,0 +1,15 @@ +self.addEventListener('message', function(event) { + if ('port' in event.data) { + var port = event.data.port; + + var channel = new MessageChannel(); + channel.port1.onmessage = function(event) { + if ('pong' in event.data) + port.postMessage(event.data.pong); + }; + self.registration.active.postMessage({ping: channel.port2}, + [channel.port2]); + } else if ('ping' in event.data) { + event.data.ping.postMessage({pong: 'OK'}); + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-ping-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-ping-worker.js new file mode 100644 index 00000000000..ebb1eccce2d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-ping-worker.js @@ -0,0 +1,15 @@ +self.addEventListener('message', function(event) { + if ('port' in event.data) { + var port = event.data.port; + + var channel = new MessageChannel(); + channel.port1.onmessage = function(event) { + if ('pong' in event.data) + port.postMessage(event.data.pong); + }; + + // Send a ping message to another service worker. + self.registration.waiting.postMessage({ping: channel.port2}, + [channel.port2]); + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-pong-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-pong-worker.js new file mode 100644 index 00000000000..4a0d90b6182 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/postmessage-pong-worker.js @@ -0,0 +1,4 @@ +self.addEventListener('message', function(event) { + if ('ping' in event.data) + event.data.ping.postMessage({pong: 'OK'}); + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-newer-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-newer-worker.js new file mode 100644 index 00000000000..44f3e2e8e9b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-newer-worker.js @@ -0,0 +1,33 @@ +// TODO(nhiroki): stop using global states because service workers can be killed +// at any point. Instead, we could post a message to the page on each event via +// Client object (http://crbug.com/558244). +var results = []; + +function stringify(worker) { + return worker ? worker.scriptURL : 'empty'; +} + +function record(event_name) { + results.push(event_name); + results.push(' installing: ' + stringify(self.registration.installing)); + results.push(' waiting: ' + stringify(self.registration.waiting)); + results.push(' active: ' + stringify(self.registration.active)); +} + +record('evaluate'); + +self.registration.addEventListener('updatefound', function() { + record('updatefound'); + var worker = self.registration.installing; + self.registration.installing.addEventListener('statechange', function() { + record('statechange(' + worker.state + ')'); + }); + }); + +self.addEventListener('install', function(e) { record('install'); }); + +self.addEventListener('activate', function(e) { record('activate'); }); + +self.addEventListener('message', function(e) { + e.data.port.postMessage(results); + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js new file mode 100644 index 00000000000..315f4375932 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/registration-attribute-worker.js @@ -0,0 +1,139 @@ +importScripts('../../resources/test-helpers.sub.js'); +importScripts('../../resources/worker-testharness.js'); + +// TODO(nhiroki): stop using global states because service workers can be killed +// at any point. Instead, we could post a message to the page on each event via +// Client object (http://crbug.com/558244). +var events_seen = []; + +// TODO(nhiroki): Move these assertions to registration-attribute.html because +// an assertion failure on the worker is not shown on the result page and +// handled as timeout. See registration-attribute-newer-worker.js for example. + +assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On worker script evaluation, registration attribute should be set'); +assert_equals( + self.registration.installing, + null, + 'On worker script evaluation, installing worker should be null'); +assert_equals( + self.registration.waiting, + null, + 'On worker script evaluation, waiting worker should be null'); +assert_equals( + self.registration.active, + null, + 'On worker script evaluation, active worker should be null'); + +self.registration.addEventListener('updatefound', function() { + events_seen.push('updatefound'); + + assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On updatefound event, registration attribute should be set'); + assert_equals( + self.registration.installing.scriptURL, + normalizeURL('registration-attribute-worker.js'), + 'On updatefound event, installing worker should be set'); + assert_equals( + self.registration.waiting, + null, + 'On updatefound event, waiting worker should be null'); + assert_equals( + self.registration.active, + null, + 'On updatefound event, active worker should be null'); + + assert_equals( + self.registration.installing.state, + 'installing', + 'On updatefound event, worker should be in the installing state'); + + var worker = self.registration.installing; + self.registration.installing.addEventListener('statechange', function() { + events_seen.push('statechange(' + worker.state + ')'); + }); + }); + +self.addEventListener('install', function(e) { + events_seen.push('install'); + + assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On install event, registration attribute should be set'); + assert_equals( + self.registration.installing.scriptURL, + normalizeURL('registration-attribute-worker.js'), + 'On install event, installing worker should be set'); + assert_equals( + self.registration.waiting, + null, + 'On install event, waiting worker should be null'); + assert_equals( + self.registration.active, + null, + 'On install event, active worker should be null'); + + assert_equals( + self.registration.installing.state, + 'installing', + 'On install event, worker should be in the installing state'); + }); + +self.addEventListener('activate', function(e) { + events_seen.push('activate'); + + assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On activate event, registration attribute should be set'); + assert_equals( + self.registration.installing, + null, + 'On activate event, installing worker should be null'); + assert_equals( + self.registration.waiting, + null, + 'On activate event, waiting worker should be null'); + assert_equals( + self.registration.active.scriptURL, + normalizeURL('registration-attribute-worker.js'), + 'On activate event, active worker should be set'); + + assert_equals( + self.registration.active.state, + 'activating', + 'On activate event, worker should be in the activating state'); + }); + +self.addEventListener('fetch', function(e) { + events_seen.push('fetch'); + + assert_equals( + self.registration.scope, + normalizeURL('scope/registration-attribute'), + 'On fetch event, registration attribute should be set'); + assert_equals( + self.registration.installing, + null, + 'On fetch event, installing worker should be null'); + assert_equals( + self.registration.waiting, + null, + 'On fetch event, waiting worker should be null'); + assert_equals( + self.registration.active.scriptURL, + normalizeURL('registration-attribute-worker.js'), + 'On fetch event, active worker should be set'); + + assert_equals( + self.registration.active.state, + 'activated', + 'On fetch event, worker should be in the activated state'); + + e.respondWith(new Response(events_seen)); + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-controlling-worker.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js new file mode 100644 index 00000000000..6da397dd152 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/unregister-worker.js @@ -0,0 +1,25 @@ +function matchQuery(query) { + return self.location.href.indexOf(query) != -1; +} + +if (matchQuery('?evaluation')) + self.registration.unregister(); + +self.addEventListener('install', function(e) { + if (matchQuery('?install')) { + // Don't do waitUntil(unregister()) as that would deadlock as specified. + self.registration.unregister(); + } + }); + +self.addEventListener('activate', function(e) { + if (matchQuery('?activate')) + e.waitUntil(self.registration.unregister()); + }); + +self.addEventListener('message', function(e) { + e.waitUntil(self.registration.unregister() + .then(function(result) { + e.data.port.postMessage({result: result}); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js new file mode 100644 index 00000000000..8be8a1ffebe --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js @@ -0,0 +1,22 @@ +var events_seen = []; + +self.registration.addEventListener('updatefound', function() { + events_seen.push('updatefound'); + }); + +self.addEventListener('activate', function(e) { + events_seen.push('activate'); + }); + +self.addEventListener('fetch', function(e) { + events_seen.push('fetch'); + e.respondWith(new Response(events_seen)); + }); + +self.addEventListener('message', function(e) { + events_seen.push('message'); + self.registration.update(); + }); + +// update() during the script evaluation should be ignored. +self.registration.update(); diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py new file mode 100644 index 00000000000..8a87e1baa4c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py @@ -0,0 +1,16 @@ +import os +import time + +from wptserve.utils import isomorphic_decode + +def main(request, response): + # update() does not bypass cache so set the max-age to 0 such that update() + # can find a new version in the network. + headers = [(b'Cache-Control', b'max-age: 0'), + (b'Content-Type', b'application/javascript')] + with open(os.path.join(os.path.dirname(isomorphic_decode(__file__)), + u'update-worker.js'), u'r') as file: + script = file.read() + # Return a different script for each access. + return headers, u'// %s\n%s' % (time.time(), script) + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/service-worker-error-event.https.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/service-worker-error-event.https.html new file mode 100644 index 00000000000..988f5466b9e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/service-worker-error-event.https.html @@ -0,0 +1,31 @@ + +ServiceWorkerGlobalScope: Error event error message + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html new file mode 100644 index 00000000000..1a124d72768 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/unregister.https.html @@ -0,0 +1,139 @@ + +ServiceWorkerGlobalScope: unregister + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html new file mode 100644 index 00000000000..a7dde223397 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html @@ -0,0 +1,48 @@ + +ServiceWorkerGlobalScope: update + + + + diff --git a/test/wpt/tests/service-workers/service-worker/about-blank-replacement.https.html b/test/wpt/tests/service-workers/service-worker/about-blank-replacement.https.html new file mode 100644 index 00000000000..b6efe3ec562 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/about-blank-replacement.https.html @@ -0,0 +1,181 @@ + +Service Worker: about:blank replacement handling + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html b/test/wpt/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html new file mode 100644 index 00000000000..016a52c13c8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/activate-event-after-install-state-change.https.html @@ -0,0 +1,33 @@ + +Service Worker: registration events + + + + diff --git a/test/wpt/tests/service-workers/service-worker/activation-after-registration.https.html b/test/wpt/tests/service-workers/service-worker/activation-after-registration.https.html new file mode 100644 index 00000000000..29f97e3e3f4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/activation-after-registration.https.html @@ -0,0 +1,28 @@ + +Service Worker: Activation occurs after registration + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/activation.https.html b/test/wpt/tests/service-workers/service-worker/activation.https.html new file mode 100644 index 00000000000..278454d3383 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/activation.https.html @@ -0,0 +1,168 @@ + + + +service worker: activation + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/active.https.html b/test/wpt/tests/service-workers/service-worker/active.https.html new file mode 100644 index 00000000000..350a34b802f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/active.https.html @@ -0,0 +1,50 @@ + +ServiceWorker: navigator.serviceWorker.active + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/claim-affect-other-registration.https.html b/test/wpt/tests/service-workers/service-worker/claim-affect-other-registration.https.html new file mode 100644 index 00000000000..52555ac271b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/claim-affect-other-registration.https.html @@ -0,0 +1,136 @@ + + +Service Worker: claim() should affect other registration + + + + diff --git a/test/wpt/tests/service-workers/service-worker/claim-fetch.https.html b/test/wpt/tests/service-workers/service-worker/claim-fetch.https.html new file mode 100644 index 00000000000..ae0082df06b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/claim-fetch.https.html @@ -0,0 +1,90 @@ + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/claim-not-using-registration.https.html b/test/wpt/tests/service-workers/service-worker/claim-not-using-registration.https.html new file mode 100644 index 00000000000..fd61d05ba4e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/claim-not-using-registration.https.html @@ -0,0 +1,131 @@ + +Service Worker: claim client not using registration + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/claim-shared-worker-fetch.https.html b/test/wpt/tests/service-workers/service-worker/claim-shared-worker-fetch.https.html new file mode 100644 index 00000000000..f5f44886baa --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/claim-shared-worker-fetch.https.html @@ -0,0 +1,71 @@ + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/claim-using-registration.https.html b/test/wpt/tests/service-workers/service-worker/claim-using-registration.https.html new file mode 100644 index 00000000000..8a2a6ff25c8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/claim-using-registration.https.html @@ -0,0 +1,103 @@ + +Service Worker: claim client using registration + + + + diff --git a/test/wpt/tests/service-workers/service-worker/claim-with-redirect.https.html b/test/wpt/tests/service-workers/service-worker/claim-with-redirect.https.html new file mode 100644 index 00000000000..fd89cb9b00a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/claim-with-redirect.https.html @@ -0,0 +1,59 @@ + +Service Worker: Claim() when update happens after redirect + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/claim-worker-fetch.https.html b/test/wpt/tests/service-workers/service-worker/claim-worker-fetch.https.html new file mode 100644 index 00000000000..7cb26c742b9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/claim-worker-fetch.https.html @@ -0,0 +1,83 @@ + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/client-id.https.html b/test/wpt/tests/service-workers/service-worker/client-id.https.html new file mode 100644 index 00000000000..b93b3418999 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/client-id.https.html @@ -0,0 +1,60 @@ + +Service Worker: Client.id + + + + diff --git a/test/wpt/tests/service-workers/service-worker/client-navigate.https.html b/test/wpt/tests/service-workers/service-worker/client-navigate.https.html new file mode 100644 index 00000000000..f40a08635cf --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/client-navigate.https.html @@ -0,0 +1,107 @@ + + + +Service Worker: WindowClient.navigate + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/client-url-of-blob-url-worker.https.html b/test/wpt/tests/service-workers/service-worker/client-url-of-blob-url-worker.https.html new file mode 100644 index 00000000000..97a2fcf98f2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/client-url-of-blob-url-worker.https.html @@ -0,0 +1,29 @@ + +Service Worker: client.url of a worker created from a blob URL + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-get-client-types.https.html b/test/wpt/tests/service-workers/service-worker/clients-get-client-types.https.html new file mode 100644 index 00000000000..63e3e51b320 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-get-client-types.https.html @@ -0,0 +1,108 @@ + +Service Worker: Clients.get with window and worker clients + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-get-cross-origin.https.html b/test/wpt/tests/service-workers/service-worker/clients-get-cross-origin.https.html new file mode 100644 index 00000000000..1e4acfb286c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-get-cross-origin.https.html @@ -0,0 +1,69 @@ + +Service Worker: Clients.get across origins + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-get-resultingClientId.https.html b/test/wpt/tests/service-workers/service-worker/clients-get-resultingClientId.https.html new file mode 100644 index 00000000000..3419cf14b52 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-get-resultingClientId.https.html @@ -0,0 +1,177 @@ + + +Test clients.get(resultingClientId) + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-get.https.html b/test/wpt/tests/service-workers/service-worker/clients-get.https.html new file mode 100644 index 00000000000..4cfbf595cad --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-get.https.html @@ -0,0 +1,154 @@ + +Service Worker: Clients.get + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-matchall-blob-url-worker.https.html b/test/wpt/tests/service-workers/service-worker/clients-matchall-blob-url-worker.https.html new file mode 100644 index 00000000000..c29bac8b894 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-matchall-blob-url-worker.https.html @@ -0,0 +1,85 @@ + +Service Worker: Clients.matchAll with a blob URL worker client + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-matchall-client-types.https.html b/test/wpt/tests/service-workers/service-worker/clients-matchall-client-types.https.html new file mode 100644 index 00000000000..54f182b6202 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-matchall-client-types.https.html @@ -0,0 +1,92 @@ + +Service Worker: Clients.matchAll with various clientTypes + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-matchall-exact-controller.https.html b/test/wpt/tests/service-workers/service-worker/clients-matchall-exact-controller.https.html new file mode 100644 index 00000000000..a61c8af7019 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-matchall-exact-controller.https.html @@ -0,0 +1,67 @@ + +Service Worker: Clients.matchAll with exact controller + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-matchall-frozen.https.html b/test/wpt/tests/service-workers/service-worker/clients-matchall-frozen.https.html new file mode 100644 index 00000000000..479c28a60f2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-matchall-frozen.https.html @@ -0,0 +1,64 @@ + +Service Worker: Clients.matchAll + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html b/test/wpt/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html new file mode 100644 index 00000000000..9f34e5709eb --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html @@ -0,0 +1,117 @@ + +Service Worker: Clients.matchAll with includeUncontrolled + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-matchall-on-evaluation.https.html b/test/wpt/tests/service-workers/service-worker/clients-matchall-on-evaluation.https.html new file mode 100644 index 00000000000..8705f85b568 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-matchall-on-evaluation.https.html @@ -0,0 +1,24 @@ + +Service Worker: Clients.matchAll on script evaluation + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-matchall-order.https.html b/test/wpt/tests/service-workers/service-worker/clients-matchall-order.https.html new file mode 100644 index 00000000000..ec650f2264d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-matchall-order.https.html @@ -0,0 +1,427 @@ + +Service Worker: Clients.matchAll ordering + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/clients-matchall.https.html b/test/wpt/tests/service-workers/service-worker/clients-matchall.https.html new file mode 100644 index 00000000000..ce44f1924d5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/clients-matchall.https.html @@ -0,0 +1,50 @@ + +Service Worker: Clients.matchAll + + + + diff --git a/test/wpt/tests/service-workers/service-worker/controller-on-disconnect.https.html b/test/wpt/tests/service-workers/service-worker/controller-on-disconnect.https.html new file mode 100644 index 00000000000..f23dfe71bac --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/controller-on-disconnect.https.html @@ -0,0 +1,40 @@ + +Service Worker: Controller on load + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/controller-on-load.https.html b/test/wpt/tests/service-workers/service-worker/controller-on-load.https.html new file mode 100644 index 00000000000..e4c5e5f81f8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/controller-on-load.https.html @@ -0,0 +1,46 @@ + +Service Worker: Controller on load + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/controller-on-reload.https.html b/test/wpt/tests/service-workers/service-worker/controller-on-reload.https.html new file mode 100644 index 00000000000..2e966d42578 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/controller-on-reload.https.html @@ -0,0 +1,58 @@ + +Service Worker: Controller on reload + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/controller-with-no-fetch-event-handler.https.html b/test/wpt/tests/service-workers/service-worker/controller-with-no-fetch-event-handler.https.html new file mode 100644 index 00000000000..d947139c9e2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/controller-with-no-fetch-event-handler.https.html @@ -0,0 +1,56 @@ + + +Service Worker: controller without a fetch event handler + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/credentials.https.html b/test/wpt/tests/service-workers/service-worker/credentials.https.html new file mode 100644 index 00000000000..0a90dc2897c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/credentials.https.html @@ -0,0 +1,100 @@ + + +Credentials for service worker scripts + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/data-iframe.html b/test/wpt/tests/service-workers/service-worker/data-iframe.html new file mode 100644 index 00000000000..d767d574345 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/data-iframe.html @@ -0,0 +1,25 @@ + +Service Workers in data iframes + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/data-transfer-files.https.html b/test/wpt/tests/service-workers/service-worker/data-transfer-files.https.html new file mode 100644 index 00000000000..c503a28f965 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/data-transfer-files.https.html @@ -0,0 +1,41 @@ + + +Post a file in a navigation controlled by a service worker + + + + + +
+ +
+ + diff --git a/test/wpt/tests/service-workers/service-worker/dedicated-worker-service-worker-interception.https.html b/test/wpt/tests/service-workers/service-worker/dedicated-worker-service-worker-interception.https.html new file mode 100644 index 00000000000..2144f482712 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/dedicated-worker-service-worker-interception.https.html @@ -0,0 +1,40 @@ + +DedicatedWorker: ServiceWorker interception + + + + diff --git a/test/wpt/tests/service-workers/service-worker/detached-context.https.html b/test/wpt/tests/service-workers/service-worker/detached-context.https.html new file mode 100644 index 00000000000..747a953f620 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/detached-context.https.html @@ -0,0 +1,141 @@ + + +Service WorkerRegistration from a removed iframe + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/embed-and-object-are-not-intercepted.https.html b/test/wpt/tests/service-workers/service-worker/embed-and-object-are-not-intercepted.https.html new file mode 100644 index 00000000000..581dbeca977 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/embed-and-object-are-not-intercepted.https.html @@ -0,0 +1,104 @@ + + +embed and object are not intercepted + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html b/test/wpt/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html new file mode 100644 index 00000000000..04e98266b4f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/extendable-event-async-waituntil.https.html @@ -0,0 +1,120 @@ + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/extendable-event-waituntil.https.html b/test/wpt/tests/service-workers/service-worker/extendable-event-waituntil.https.html new file mode 100644 index 00000000000..33b4eac5c18 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/extendable-event-waituntil.https.html @@ -0,0 +1,140 @@ + +ExtendableEvent: waitUntil + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-audio-tainting.https.html b/test/wpt/tests/service-workers/service-worker/fetch-audio-tainting.https.html new file mode 100644 index 00000000000..9821759bc7b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-audio-tainting.https.html @@ -0,0 +1,47 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-double-write.https.html b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-double-write.https.html new file mode 100644 index 00000000000..dab2153baa6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-double-write.https.html @@ -0,0 +1,57 @@ + + + + + + +canvas tainting when written twice + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-image-cache.https.html b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-image-cache.https.html new file mode 100644 index 00000000000..21323811225 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-image-cache.https.html @@ -0,0 +1,16 @@ + + +Service Worker: canvas tainting of the fetched image using cached responses + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-image.https.html b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-image.https.html new file mode 100644 index 00000000000..57dc7d98caa --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-image.https.html @@ -0,0 +1,16 @@ + + +Service Worker: canvas tainting of the fetched image + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html new file mode 100644 index 00000000000..c37e8e56244 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html @@ -0,0 +1,17 @@ + + +Service Worker: canvas tainting of the fetched video using cache responses + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-video-with-range-request.https.html b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-video-with-range-request.https.html new file mode 100644 index 00000000000..28c30718047 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-video-with-range-request.https.html @@ -0,0 +1,92 @@ + + +Canvas tainting due to video whose responses are fetched via a service worker including range requests + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-video.https.html b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-video.https.html new file mode 100644 index 00000000000..e8c23a2edd6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-canvas-tainting-video.https.html @@ -0,0 +1,17 @@ + + +Service Worker: canvas tainting of the fetched video + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-cors-exposed-header-names.https.html b/test/wpt/tests/service-workers/service-worker/fetch-cors-exposed-header-names.https.html new file mode 100644 index 00000000000..317b02175f2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-cors-exposed-header-names.https.html @@ -0,0 +1,30 @@ + +Service Worker: CORS-exposed header names should be transferred correctly + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-cors-xhr.https.html b/test/wpt/tests/service-workers/service-worker/fetch-cors-xhr.https.html new file mode 100644 index 00000000000..f8ff445673b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-cors-xhr.https.html @@ -0,0 +1,49 @@ + +Service Worker: CORS XHR of fetch() + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-csp.https.html b/test/wpt/tests/service-workers/service-worker/fetch-csp.https.html new file mode 100644 index 00000000000..9e7b242b69a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-csp.https.html @@ -0,0 +1,138 @@ + +Service Worker: CSP control of fetch() + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-error.https.html b/test/wpt/tests/service-workers/service-worker/fetch-error.https.html new file mode 100644 index 00000000000..ca2f884a9b3 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-error.https.html @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-add-async.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-add-async.https.html new file mode 100644 index 00000000000..ac13e4f4167 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-add-async.https.html @@ -0,0 +1,11 @@ + +Service Worker: Fetch event added asynchronously doesn't throw + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html new file mode 100644 index 00000000000..4812d8a9155 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-after-navigation-within-page.https.html @@ -0,0 +1,71 @@ + +ServiceWorker: navigator.serviceWorker.waiting + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html new file mode 100644 index 00000000000..d9147f85494 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-async-respond-with.https.html @@ -0,0 +1,73 @@ + + +respondWith cannot be called asynchronously + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-handled.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-handled.https.html new file mode 100644 index 00000000000..08b88ce3773 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-handled.https.html @@ -0,0 +1,86 @@ + + +Service Worker: FetchEvent.handled + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-is-history-backward-navigation-manual.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-is-history-backward-navigation-manual.https.html new file mode 100644 index 00000000000..3cf5922f396 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-is-history-backward-navigation-manual.https.html @@ -0,0 +1,8 @@ + + +

Click this link. + Once you see "method = GET,..." in the page, go to another page, and then go back to the page using the Backward button. + You should see "method = GET, isHistoryNavigation = true". +

+ + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-is-history-forward-navigation-manual.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-is-history-forward-navigation-manual.https.html new file mode 100644 index 00000000000..401939b3cb2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-is-history-forward-navigation-manual.https.html @@ -0,0 +1,8 @@ + + +

Click this link. + Once you see "method = GET,..." in the page, go back to this page using the Backward button, and then go to the second page using the Forward button. + You should see "method = GET, isHistoryNavigation = true". +

+ + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-is-reload-iframe-navigation-manual.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-is-reload-iframe-navigation-manual.https.html new file mode 100644 index 00000000000..cf1feccf6e4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-is-reload-iframe-navigation-manual.https.html @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-is-reload-navigation-manual.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-is-reload-navigation-manual.https.html new file mode 100644 index 00000000000..a349f07c36c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-is-reload-navigation-manual.https.html @@ -0,0 +1,8 @@ + + +

Click this link. + Once you see "method = GET,..." in the page, reload the page. + You will see "method = GET, isReloadNavigation = true". +

+ + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-network-error.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-network-error.https.html new file mode 100644 index 00000000000..fea2ad1e3c2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-network-error.https.html @@ -0,0 +1,44 @@ + +Service Worker: Fetch event network error + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-redirect.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-redirect.https.html new file mode 100644 index 00000000000..52292847570 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-redirect.https.html @@ -0,0 +1,1038 @@ + +Service Worker: Fetch Event Redirect Handling + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-referrer-policy.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-referrer-policy.https.html new file mode 100644 index 00000000000..af4b20a9a4a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-referrer-policy.https.html @@ -0,0 +1,274 @@ + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-argument.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-argument.https.html new file mode 100644 index 00000000000..05e22105243 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-argument.https.html @@ -0,0 +1,44 @@ + +Service Worker: FetchEvent.respondWith() argument type test. + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-body-loaded-in-chunk.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-body-loaded-in-chunk.https.html new file mode 100644 index 00000000000..932f9030c51 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-body-loaded-in-chunk.https.html @@ -0,0 +1,24 @@ + + +respondWith with a response whose body is being loaded from the network by chunks + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-custom-response.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-custom-response.https.html new file mode 100644 index 00000000000..645a29c9b4f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-custom-response.https.html @@ -0,0 +1,82 @@ + + +respondWith with a new Response + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-partial-stream.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-partial-stream.https.html new file mode 100644 index 00000000000..505cef29726 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-partial-stream.https.html @@ -0,0 +1,62 @@ + + +respondWith streams data to an intercepted fetch() + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-readable-stream-chunk.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-readable-stream-chunk.https.html new file mode 100644 index 00000000000..4544a9e08f5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-readable-stream-chunk.https.html @@ -0,0 +1,23 @@ + + +respondWith with a response built from a ReadableStream + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-readable-stream.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-readable-stream.https.html new file mode 100644 index 00000000000..4651258e6ab --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-readable-stream.https.html @@ -0,0 +1,88 @@ + + +respondWith with a response built from a ReadableStream + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-response-body-with-invalid-chunk.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-response-body-with-invalid-chunk.https.html new file mode 100644 index 00000000000..2a44811461a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-response-body-with-invalid-chunk.https.html @@ -0,0 +1,46 @@ + + +respondWith with response body having invalid chunks + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html new file mode 100644 index 00000000000..31fd616b6d6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html @@ -0,0 +1,37 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-throws-after-respond-with.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-throws-after-respond-with.https.html new file mode 100644 index 00000000000..d98fb22ff42 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-throws-after-respond-with.https.html @@ -0,0 +1,37 @@ + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-within-sw-manual.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-within-sw-manual.https.html new file mode 100644 index 00000000000..15a2e95bd39 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-within-sw-manual.https.html @@ -0,0 +1,122 @@ + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event-within-sw.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event-within-sw.https.html new file mode 100644 index 00000000000..0b52b18305f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event-within-sw.https.html @@ -0,0 +1,53 @@ + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event.https.h2.html b/test/wpt/tests/service-workers/service-worker/fetch-event.https.h2.html new file mode 100644 index 00000000000..5cd381ec98d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event.https.h2.html @@ -0,0 +1,112 @@ + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-event.https.html b/test/wpt/tests/service-workers/service-worker/fetch-event.https.html new file mode 100644 index 00000000000..ce53f3c9bff --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-event.https.html @@ -0,0 +1,1000 @@ + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-frame-resource.https.html b/test/wpt/tests/service-workers/service-worker/fetch-frame-resource.https.html new file mode 100644 index 00000000000..a33309f34f9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-frame-resource.https.html @@ -0,0 +1,236 @@ + +Service Worker: Fetch for the frame loading. + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-header-visibility.https.html b/test/wpt/tests/service-workers/service-worker/fetch-header-visibility.https.html new file mode 100644 index 00000000000..1f4813c4f8e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-header-visibility.https.html @@ -0,0 +1,54 @@ + +Service Worker: Visibility of headers during fetch. + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html b/test/wpt/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html new file mode 100644 index 00000000000..0e8fa93b32c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-mixed-content-to-inscope.https.html @@ -0,0 +1,21 @@ + +Service Worker: Mixed content of fetch() + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html b/test/wpt/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html new file mode 100644 index 00000000000..391dc5d2c19 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-mixed-content-to-outscope.https.html @@ -0,0 +1,21 @@ + +Service Worker: Mixed content of fetch() + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-css-base-url.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-css-base-url.https.html new file mode 100644 index 00000000000..467a66cee46 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-css-base-url.https.html @@ -0,0 +1,87 @@ + +Service Worker: CSS's base URL must be the response URL + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-css-cross-origin.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-css-cross-origin.https.html new file mode 100644 index 00000000000..d9c1c7f5df2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-css-cross-origin.https.html @@ -0,0 +1,81 @@ + +Service Worker: Cross-origin CSS files fetched via SW. + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-css-images.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-css-images.https.html new file mode 100644 index 00000000000..586dea26135 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-css-images.https.html @@ -0,0 +1,214 @@ + +Service Worker: FetchEvent for css image + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-fallback.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-fallback.https.html new file mode 100644 index 00000000000..a29f31d127b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-fallback.https.html @@ -0,0 +1,282 @@ + +Service Worker: the fallback behavior of FetchEvent + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html new file mode 100644 index 00000000000..03b7d357610 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-no-freshness-headers.https.html @@ -0,0 +1,55 @@ + +Service Worker: the headers of FetchEvent shouldn't contain freshness headers + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-redirect.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-redirect.https.html new file mode 100644 index 00000000000..5ce015b421f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-redirect.https.html @@ -0,0 +1,385 @@ + +Service Worker: FetchEvent for resources + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-resources.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-resources.https.html new file mode 100644 index 00000000000..b4680c3ccd5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-resources.https.html @@ -0,0 +1,302 @@ + +Service Worker: FetchEvent for resources + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-xhr-sync-error.https.window.js b/test/wpt/tests/service-workers/service-worker/fetch-request-xhr-sync-error.https.window.js new file mode 100644 index 00000000000..e6c02139286 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-xhr-sync-error.https.window.js @@ -0,0 +1,19 @@ +// META: script=resources/test-helpers.sub.js + +"use strict"; + +promise_test(async t => { + const url = "resources/fetch-request-xhr-sync-error-worker.js"; + const scope = "resources/fetch-request-xhr-sync-iframe.html"; + + const registration = await service_worker_unregister_and_register(t, url, scope); + t.add_cleanup(() => registration.unregister()); + + await wait_for_state(t, registration.installing, 'activated'); + const frame = await with_iframe(scope); + t.add_cleanup(() => frame.remove()); + + assert_throws_dom("NetworkError", frame.contentWindow.DOMException, () => frame.contentWindow.performSyncXHR("non-existent-stream-1.txt")); + assert_throws_dom("NetworkError", frame.contentWindow.DOMException, () => frame.contentWindow.performSyncXHR("non-existent-stream-2.txt")); + assert_throws_dom("NetworkError", frame.contentWindow.DOMException, () => frame.contentWindow.performSyncXHR("non-existent-stream-3.txt")); +}, "Verify synchronous XMLHttpRequest always throws a NetworkError for ReadableStream errors"); diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-xhr-sync-on-worker.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-xhr-sync-on-worker.https.html new file mode 100644 index 00000000000..9f18096aa29 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-xhr-sync-on-worker.https.html @@ -0,0 +1,41 @@ + +Service Worker: Synchronous XHR on Worker is intercepted + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-xhr-sync.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-xhr-sync.https.html new file mode 100644 index 00000000000..ec27fb89834 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-xhr-sync.https.html @@ -0,0 +1,53 @@ + +Service Worker: Synchronous XHR is intercepted + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-request-xhr.https.html b/test/wpt/tests/service-workers/service-worker/fetch-request-xhr.https.html new file mode 100644 index 00000000000..37a457393b0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-request-xhr.https.html @@ -0,0 +1,75 @@ + +Service Worker: the body of FetchEvent using XMLHttpRequest + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-response-taint.https.html b/test/wpt/tests/service-workers/service-worker/fetch-response-taint.https.html new file mode 100644 index 00000000000..8e190f48506 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-response-taint.https.html @@ -0,0 +1,223 @@ + +Service Worker: Tainting of responses fetched via SW. + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-response-xhr.https.html b/test/wpt/tests/service-workers/service-worker/fetch-response-xhr.https.html new file mode 100644 index 00000000000..891eb029422 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-response-xhr.https.html @@ -0,0 +1,50 @@ + +Service Worker: the response of FetchEvent using XMLHttpRequest + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/fetch-waits-for-activate.https.html b/test/wpt/tests/service-workers/service-worker/fetch-waits-for-activate.https.html new file mode 100644 index 00000000000..7c888450f0d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/fetch-waits-for-activate.https.html @@ -0,0 +1,128 @@ + +Service Worker: Fetch Event Waits for Activate Event + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/getregistration.https.html b/test/wpt/tests/service-workers/service-worker/getregistration.https.html new file mode 100644 index 00000000000..634c2efa124 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/getregistration.https.html @@ -0,0 +1,108 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/getregistrations.https.html b/test/wpt/tests/service-workers/service-worker/getregistrations.https.html new file mode 100644 index 00000000000..3a9b9a23317 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/getregistrations.https.html @@ -0,0 +1,134 @@ + +Service Worker: getRegistrations() + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/global-serviceworker.https.any.js b/test/wpt/tests/service-workers/service-worker/global-serviceworker.https.any.js new file mode 100644 index 00000000000..19d77847c42 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/global-serviceworker.https.any.js @@ -0,0 +1,53 @@ +// META: title=serviceWorker on service worker global +// META: global=serviceworker + +test(() => { + assert_equals(registration.installing, null, 'registration.installing'); + assert_equals(registration.waiting, null, 'registration.waiting'); + assert_equals(registration.active, null, 'registration.active'); + assert_true('serviceWorker' in self, 'self.serviceWorker exists'); + assert_equals(serviceWorker.state, 'parsed', 'serviceWorker.state'); + assert_readonly(self, 'serviceWorker', `self.serviceWorker is read only`); +}, 'First run'); + +// Cache this for later tests. +const initialServiceWorker = self.serviceWorker; + +async_test((t) => { + assert_true('serviceWorker' in self, 'self.serviceWorker exists'); + serviceWorker.postMessage({ messageTest: true }); + + // The rest of the test runs once this receives the above message. + addEventListener('message', t.step_func((event) => { + // Ignore unrelated messages. + if (!event.data.messageTest) return; + assert_equals(event.source, serviceWorker, 'event.source'); + t.done(); + })); +}, 'Can post message to self during startup'); + +// The test is registered now so there isn't a race condition when collecting tests, but the asserts +// don't happen until the 'install' event fires. +async_test((t) => { + addEventListener('install', t.step_func_done(() => { + assert_true('serviceWorker' in self, 'self.serviceWorker exists'); + assert_equals(serviceWorker, initialServiceWorker, `self.serviceWorker hasn't changed`); + assert_equals(registration.installing, serviceWorker, 'registration.installing'); + assert_equals(registration.waiting, null, 'registration.waiting'); + assert_equals(registration.active, null, 'registration.active'); + assert_equals(serviceWorker.state, 'installing', 'serviceWorker.state'); + })); +}, 'During install'); + +// The test is registered now so there isn't a race condition when collecting tests, but the asserts +// don't happen until the 'activate' event fires. +async_test((t) => { + addEventListener('activate', t.step_func_done(() => { + assert_true('serviceWorker' in self, 'self.serviceWorker exists'); + assert_equals(serviceWorker, initialServiceWorker, `self.serviceWorker hasn't changed`); + assert_equals(registration.installing, null, 'registration.installing'); + assert_equals(registration.waiting, null, 'registration.waiting'); + assert_equals(registration.active, serviceWorker, 'registration.active'); + assert_equals(serviceWorker.state, 'activating', 'serviceWorker.state'); + })); +}, 'During activate'); diff --git a/test/wpt/tests/service-workers/service-worker/historical.https.any.js b/test/wpt/tests/service-workers/service-worker/historical.https.any.js new file mode 100644 index 00000000000..20b3ddfbf7b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/historical.https.any.js @@ -0,0 +1,5 @@ +// META: global=serviceworker + +test((t) => { + assert_false('targetClientId' in FetchEvent.prototype) +}, 'targetClientId should not be on FetchEvent'); diff --git a/test/wpt/tests/service-workers/service-worker/http-to-https-redirect-and-register.https.html b/test/wpt/tests/service-workers/service-worker/http-to-https-redirect-and-register.https.html new file mode 100644 index 00000000000..5626237dccc --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/http-to-https-redirect-and-register.https.html @@ -0,0 +1,49 @@ + +register on a secure page after redirect from an non-secure url + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/immutable-prototype-serviceworker.https.html b/test/wpt/tests/service-workers/service-worker/immutable-prototype-serviceworker.https.html new file mode 100644 index 00000000000..e63f6b348a8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/immutable-prototype-serviceworker.https.html @@ -0,0 +1,23 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/import-scripts-cross-origin.https.html b/test/wpt/tests/service-workers/service-worker/import-scripts-cross-origin.https.html new file mode 100644 index 00000000000..773708a9fbc --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/import-scripts-cross-origin.https.html @@ -0,0 +1,18 @@ + + +Tests for importScripts: cross-origin + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/import-scripts-data-url.https.html b/test/wpt/tests/service-workers/service-worker/import-scripts-data-url.https.html new file mode 100644 index 00000000000..f0922193dd0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/import-scripts-data-url.https.html @@ -0,0 +1,18 @@ + + +Tests for importScripts: data: URL + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/import-scripts-mime-types.https.html b/test/wpt/tests/service-workers/service-worker/import-scripts-mime-types.https.html new file mode 100644 index 00000000000..1679831d0f2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/import-scripts-mime-types.https.html @@ -0,0 +1,30 @@ + + +Tests for importScripts: MIME types + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/import-scripts-redirect.https.html b/test/wpt/tests/service-workers/service-worker/import-scripts-redirect.https.html new file mode 100644 index 00000000000..07ea49439eb --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/import-scripts-redirect.https.html @@ -0,0 +1,55 @@ + + +Tests for importScripts: redirect + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/import-scripts-resource-map.https.html b/test/wpt/tests/service-workers/service-worker/import-scripts-resource-map.https.html new file mode 100644 index 00000000000..4742bd01268 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/import-scripts-resource-map.https.html @@ -0,0 +1,34 @@ + + +Tests for importScripts: script resource map + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/import-scripts-updated-flag.https.html b/test/wpt/tests/service-workers/service-worker/import-scripts-updated-flag.https.html new file mode 100644 index 00000000000..09b4496aa0e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/import-scripts-updated-flag.https.html @@ -0,0 +1,83 @@ + + +Tests for importScripts: import scripts updated flag + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/indexeddb.https.html b/test/wpt/tests/service-workers/service-worker/indexeddb.https.html new file mode 100644 index 00000000000..be9be4968f7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/indexeddb.https.html @@ -0,0 +1,78 @@ + +Service Worker: Indexed DB + + + + diff --git a/test/wpt/tests/service-workers/service-worker/install-event-type.https.html b/test/wpt/tests/service-workers/service-worker/install-event-type.https.html new file mode 100644 index 00000000000..7e74af85c3a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/install-event-type.https.html @@ -0,0 +1,30 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/installing.https.html b/test/wpt/tests/service-workers/service-worker/installing.https.html new file mode 100644 index 00000000000..0f257b6aba4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/installing.https.html @@ -0,0 +1,48 @@ + +ServiceWorker: navigator.serviceWorker.installing + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/interface-requirements-sw.https.html b/test/wpt/tests/service-workers/service-worker/interface-requirements-sw.https.html new file mode 100644 index 00000000000..eef868c889f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/interface-requirements-sw.https.html @@ -0,0 +1,16 @@ + +Service Worker Global Scope Interfaces + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/invalid-blobtype.https.html b/test/wpt/tests/service-workers/service-worker/invalid-blobtype.https.html new file mode 100644 index 00000000000..1c5920fb039 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/invalid-blobtype.https.html @@ -0,0 +1,40 @@ + +Service Worker: respondWith with header value containing a null byte + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/invalid-header.https.html b/test/wpt/tests/service-workers/service-worker/invalid-header.https.html new file mode 100644 index 00000000000..1bc97697904 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/invalid-header.https.html @@ -0,0 +1,39 @@ + +Service Worker: respondWith with header value containing a null byte + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/iso-latin1-header.https.html b/test/wpt/tests/service-workers/service-worker/iso-latin1-header.https.html new file mode 100644 index 00000000000..c27a5f48a5b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/iso-latin1-header.https.html @@ -0,0 +1,40 @@ + +Service Worker: respondWith with header value containing an ISO Latin 1 (ISO-8859-1 Character Set) string + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/local-url-inherit-controller.https.html b/test/wpt/tests/service-workers/service-worker/local-url-inherit-controller.https.html new file mode 100644 index 00000000000..6702abcadbb --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/local-url-inherit-controller.https.html @@ -0,0 +1,115 @@ + +Service Worker: local URL windows and workers inherit controller + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/mime-sniffing.https.html b/test/wpt/tests/service-workers/service-worker/mime-sniffing.https.html new file mode 100644 index 00000000000..8175bcdf877 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/mime-sniffing.https.html @@ -0,0 +1,24 @@ + +Service Worker: MIME sniffing + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/multi-globals/current/current.https.html b/test/wpt/tests/service-workers/service-worker/multi-globals/current/current.https.html new file mode 100644 index 00000000000..82a48d40990 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multi-globals/current/current.https.html @@ -0,0 +1,2 @@ + +Current page used as a test helper diff --git a/test/wpt/tests/service-workers/service-worker/multi-globals/current/test-sw.js b/test/wpt/tests/service-workers/service-worker/multi-globals/current/test-sw.js new file mode 100644 index 00000000000..e673292f2c0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multi-globals/current/test-sw.js @@ -0,0 +1 @@ +// Service worker for current/ \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/multi-globals/incumbent/incumbent.https.html b/test/wpt/tests/service-workers/service-worker/multi-globals/incumbent/incumbent.https.html new file mode 100644 index 00000000000..4585f15b0f4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multi-globals/incumbent/incumbent.https.html @@ -0,0 +1,20 @@ + +Incumbent page used as a test helper + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/multi-globals/incumbent/test-sw.js b/test/wpt/tests/service-workers/service-worker/multi-globals/incumbent/test-sw.js new file mode 100644 index 00000000000..e2a0e93b583 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multi-globals/incumbent/test-sw.js @@ -0,0 +1 @@ +// Service worker for incumbent/ \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/multi-globals/relevant/relevant.https.html b/test/wpt/tests/service-workers/service-worker/multi-globals/relevant/relevant.https.html new file mode 100644 index 00000000000..44f42eda493 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multi-globals/relevant/relevant.https.html @@ -0,0 +1,2 @@ + +Relevant page used as a test helper diff --git a/test/wpt/tests/service-workers/service-worker/multi-globals/relevant/test-sw.js b/test/wpt/tests/service-workers/service-worker/multi-globals/relevant/test-sw.js new file mode 100644 index 00000000000..ff44cdf0867 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multi-globals/relevant/test-sw.js @@ -0,0 +1 @@ +// Service worker for relevant/ \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/multi-globals/test-sw.js b/test/wpt/tests/service-workers/service-worker/multi-globals/test-sw.js new file mode 100644 index 00000000000..ce3c940eced --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multi-globals/test-sw.js @@ -0,0 +1 @@ +// Service worker for / \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/multi-globals/url-parsing.https.html b/test/wpt/tests/service-workers/service-worker/multi-globals/url-parsing.https.html new file mode 100644 index 00000000000..b9dfe363435 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multi-globals/url-parsing.https.html @@ -0,0 +1,73 @@ + +register()/getRegistration() URL parsing, with multiple globals in play + + + + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/multipart-image.https.html b/test/wpt/tests/service-workers/service-worker/multipart-image.https.html new file mode 100644 index 00000000000..00c20d25f90 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multipart-image.https.html @@ -0,0 +1,68 @@ + +Tests for cross-origin multipart image returned by service worker + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/multiple-register.https.html b/test/wpt/tests/service-workers/service-worker/multiple-register.https.html new file mode 100644 index 00000000000..752e132fc13 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multiple-register.https.html @@ -0,0 +1,117 @@ + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/multiple-update.https.html b/test/wpt/tests/service-workers/service-worker/multiple-update.https.html new file mode 100644 index 00000000000..6a83f73a054 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/multiple-update.https.html @@ -0,0 +1,94 @@ + + +Service Worker: Trigger multiple updates + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigate-window.https.html b/test/wpt/tests/service-workers/service-worker/navigate-window.https.html new file mode 100644 index 00000000000..46d32a48a0a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigate-window.https.html @@ -0,0 +1,151 @@ + +Service Worker: Navigate a Window + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-headers.https.html b/test/wpt/tests/service-workers/service-worker/navigation-headers.https.html new file mode 100644 index 00000000000..a4b52035e22 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-headers.https.html @@ -0,0 +1,819 @@ + + + +Service Worker: Navigation Post Request Origin Header + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html new file mode 100644 index 00000000000..ec74282ac33 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/broken-chunked-encoding.https.html @@ -0,0 +1,42 @@ + + +Navigation Preload with chunked encoding + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/chunked-encoding.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/chunked-encoding.https.html new file mode 100644 index 00000000000..830ce32cea5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/chunked-encoding.https.html @@ -0,0 +1,25 @@ + + +Navigation Preload with chunked encoding + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/empty-preload-response-body.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/empty-preload-response-body.https.html new file mode 100644 index 00000000000..7e8aacdd36a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/empty-preload-response-body.https.html @@ -0,0 +1,25 @@ + + +Navigation Preload empty response body + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/get-state.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/get-state.https.html new file mode 100644 index 00000000000..08e2f4976c5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/get-state.https.html @@ -0,0 +1,217 @@ + + +NavigationPreloadManager.getState + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/navigationPreload.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/navigationPreload.https.html new file mode 100644 index 00000000000..392e5c14dc8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/navigationPreload.https.html @@ -0,0 +1,20 @@ + + +ServiceWorker: navigator.serviceWorker.navigationPreload + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/redirect.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/redirect.https.html new file mode 100644 index 00000000000..5970f053e36 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/redirect.https.html @@ -0,0 +1,93 @@ + + +Navigation Preload redirect response + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/request-headers.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/request-headers.https.html new file mode 100644 index 00000000000..09642010218 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/request-headers.https.html @@ -0,0 +1,41 @@ + + +Navigation Preload request headers + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resource-timing.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/resource-timing.https.html new file mode 100644 index 00000000000..468a1f51e85 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resource-timing.https.html @@ -0,0 +1,92 @@ + + +Navigation Preload Resource Timing + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-scope.asis b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-scope.asis new file mode 100644 index 00000000000..2a719536fb8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-scope.asis @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-type: text/html; charset=UTF-8 +Transfer-encoding: chunked + +hello +world diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js new file mode 100644 index 00000000000..7a453e4055f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/broken-chunked-encoding-worker.js @@ -0,0 +1,11 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse + .then( + _ => new Response('PASS: preloadResponse resolved'), + _ => new Response('FAIL: preloadResponse rejected'))); + }); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py new file mode 100644 index 00000000000..659c4d8cdf2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-scope.py @@ -0,0 +1,19 @@ +import time + +def main(request, response): + use_broken_body = b'use_broken_body' in request.GET + + response.add_required_headers = False + response.writer.write_status(200) + response.writer.write_header(b"Content-type", b"text/html; charset=UTF-8") + response.writer.write_header(b"Transfer-encoding", b"chunked") + response.writer.end_headers() + + for idx in range(10): + if use_broken_body: + response.writer.write(u"%s\n%s\n" % (len(str(idx)), idx)) + else: + response.writer.write(u"%s\r\n%s\r\n" % (len(str(idx)), idx)) + time.sleep(0.001) + + response.writer.write(u"0\r\n\r\n") diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-worker.js new file mode 100644 index 00000000000..f30e5ed274d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/chunked-encoding-worker.js @@ -0,0 +1,8 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse); + }); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/cookie.py b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/cookie.py new file mode 100644 index 00000000000..30a1dd498a5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/cookie.py @@ -0,0 +1,20 @@ +def main(request, response): + """ + Returns a response with a Set-Cookie header based on the query params. + The body will be "1" if the cookie is present in the request and `drop` parameter is "0", + otherwise the body will be "0". + """ + same_site = request.GET.first(b"same-site") + cookie_name = request.GET.first(b"cookie-name") + drop = request.GET.first(b"drop") + cookie_in_request = b"0" + cookie = b"%s=1; Secure; SameSite=%s" % (cookie_name, same_site) + + if drop == b"1": + cookie += b"; Max-Age=0" + + if request.cookies.get(cookie_name): + cookie_in_request = request.cookies[cookie_name].value + + headers = [(b'Content-Type', b'text/html'), (b'Set-Cookie', cookie)] + return (200, headers, cookie_in_request) diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-scope.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-scope.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-worker.js new file mode 100644 index 00000000000..48c14b7916f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/empty-preload-response-body-worker.js @@ -0,0 +1,15 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + event.respondWith( + event.preloadResponse + .then(res => res.text()) + .then(text => { + return new Response( + '[' + text + ']', + {headers: [['content-type', 'text/html']]}); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/get-state-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/get-state-worker.js new file mode 100644 index 00000000000..a14ffb4faaa --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/get-state-worker.js @@ -0,0 +1,21 @@ +// This worker listens for commands from the page and messages back +// the result. + +function handle(message) { + const np = self.registration.navigationPreload; + switch (message) { + case 'getState': + return np.getState(); + case 'enable': + return np.enable(); + case 'disable': + return np.disable(); + case 'setHeaderValue': + return np.setHeaderValue('insightful'); + } + return Promise.reject('bad message'); +} + +self.addEventListener('message', e => { + e.waitUntil(handle(e.data).then(result => e.source.postMessage(result))); + }); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/helpers.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/helpers.js new file mode 100644 index 00000000000..86f0c0916ec --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/helpers.js @@ -0,0 +1,5 @@ +function expect_navigation_preload_state(state, enabled, header, desc) { + assert_equals(Object.keys(state).length, 2, desc + ': # of keys'); + assert_equals(state.enabled, enabled, desc + ': enabled'); + assert_equals(state.headerValue, header, desc + ': header'); +} diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/navigation-preload-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/navigation-preload-worker.js new file mode 100644 index 00000000000..6e1ab232907 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/navigation-preload-worker.js @@ -0,0 +1,3 @@ +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse); +}); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/redirect-redirected.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/redirect-redirected.html new file mode 100644 index 00000000000..f9bfce5e895 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/redirect-redirected.html @@ -0,0 +1,3 @@ + + +redirected diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/redirect-scope.py b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/redirect-scope.py new file mode 100644 index 00000000000..84a97e594b2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/redirect-scope.py @@ -0,0 +1,38 @@ +def main(request, response): + if b"base" in request.GET: + return [(b"Content-Type", b"text/html")], b"OK" + type = request.GET.first(b"type") + + if type == b"normal": + response.status = 302 + response.headers.append(b"Location", b"redirect-redirected.html") + response.headers.append(b"Custom-Header", b"hello") + return b"" + + if type == b"no-location": + response.status = 302 + response.headers.append(b"Content-Type", b"text/html") + response.headers.append(b"Custom-Header", b"hello") + return b"" + + if type == b"no-location-with-body": + response.status = 302 + response.headers.append(b"Content-Type", b"text/html") + response.headers.append(b"Custom-Header", b"hello") + return b"BODY" + + if type == b"redirect-to-scope": + response.status = 302 + response.headers.append(b"Location", + b"redirect-scope.py?type=redirect-to-scope2") + return b"" + if type == b"redirect-to-scope2": + response.status = 302 + response.headers.append(b"Location", + b"redirect-scope.py?type=redirect-to-scope3") + return b"" + if type == b"redirect-to-scope3": + response.status = 302 + response.headers.append(b"Location", b"redirect-redirected.html") + response.headers.append(b"Custom-Header", b"hello") + return b"" diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/redirect-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/redirect-worker.js new file mode 100644 index 00000000000..1b55f2ef0d9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/redirect-worker.js @@ -0,0 +1,35 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +function get_response_info(r) { + var info = { + type: r.type, + url: r.url, + status: r.status, + ok: r.ok, + statusText: r.statusText, + headers: [] + }; + r.headers.forEach((value, name) => { info.headers.push([value, name]); }); + return info; +} + +function post_to_page(data) { + return self.clients.matchAll() + .then(clients => clients.forEach(client => client.postMessage(data))); +} + +self.addEventListener('fetch', event => { + event.respondWith( + event.preloadResponse + .then( + res => { + if (res.url.includes("base")) { + return res; + } + return post_to_page(get_response_info(res)).then(_ => res); + }, + err => new Response(err.toString()))); + }); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/request-headers-scope.py b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/request-headers-scope.py new file mode 100644 index 00000000000..5bab5b01f36 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/request-headers-scope.py @@ -0,0 +1,14 @@ +import json + +from wptserve.utils import isomorphic_decode + +def main(request, response): + normalized = dict() + + for key, values in dict(request.headers).items(): + values = [isomorphic_decode(value) for value in values] + normalized[isomorphic_decode(key.upper())] = values + + response.headers.append(b"Content-Type", b"text/html") + + return json.dumps(normalized) diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/request-headers-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/request-headers-worker.js new file mode 100644 index 00000000000..1006cf27913 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/request-headers-worker.js @@ -0,0 +1,10 @@ +self.addEventListener('activate', event => { + event.waitUntil( + Promise.all[ + self.registration.navigationPreload.enable(), + self.registration.navigationPreload.setHeaderValue('hello')]); + }); + +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse); + }); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-scope.py b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-scope.py new file mode 100644 index 00000000000..856f9dbc2a4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-scope.py @@ -0,0 +1,19 @@ +import zlib + +def main(request, response): + type = request.GET.first(b"type") + + if type == "normal": + content = b"This is Navigation Preload Resource Timing test." + output = zlib.compress(content, 9) + headers = [(b"Content-type", b"text/plain"), + (b"Content-Encoding", b"deflate"), + (b"X-Decoded-Body-Size", len(content)), + (b"X-Encoded-Body-Size", len(output)), + (b"Content-Length", len(output))] + return headers, output + + if type == b"redirect": + response.status = 302 + response.headers.append(b"Location", b"redirect-redirected.html") + return b"" diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-worker.js new file mode 100644 index 00000000000..fac0d8de2a7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/resource-timing-worker.js @@ -0,0 +1,37 @@ +async function wait_for_performance_entries(url) { + let entries = performance.getEntriesByName(url); + if (entries.length > 0) { + return entries; + } + return new Promise((resolve) => { + new PerformanceObserver((list) => { + const entries = list.getEntriesByName(url); + if (entries.length > 0) { + resolve(entries); + } + }).observe({ entryTypes: ['resource'] }); + }); +} + +self.addEventListener('activate', event => { + event.waitUntil(self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + let headers; + event.respondWith( + event.preloadResponse + .then(response => { + headers = response.headers; + return response.text() + }) + .then(_ => wait_for_performance_entries(event.request.url)) + .then(entries => + new Response( + JSON.stringify({ + decodedBodySize: headers.get('X-Decoded-Body-Size'), + encodedBodySize: headers.get('X-Encoded-Body-Size'), + timingEntries: entries + }), + {headers: {'Content-Type': 'text/html'}}))); + }); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/samesite-iframe.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/samesite-iframe.html new file mode 100644 index 00000000000..a28b61261e8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/samesite-iframe.html @@ -0,0 +1,10 @@ + + +samesite + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/samesite-sw-helper.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/samesite-sw-helper.html new file mode 100644 index 00000000000..51fdc9ec74b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/samesite-sw-helper.html @@ -0,0 +1,34 @@ + + +Navigation Preload Same Site SW registrator + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/samesite-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/samesite-worker.js new file mode 100644 index 00000000000..f30e5ed274d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/samesite-worker.js @@ -0,0 +1,8 @@ +self.addEventListener('activate', event => { + event.waitUntil( + self.registration.navigationPreload.enable()); + }); + +self.addEventListener('fetch', event => { + event.respondWith(event.preloadResponse); + }); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/wait-for-activate-worker.js b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/wait-for-activate-worker.js new file mode 100644 index 00000000000..87791d2e487 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/resources/wait-for-activate-worker.js @@ -0,0 +1,40 @@ +// This worker remains in the installing phase so that the +// navigation preload API can be tested when there is no +// active worker. +importScripts('/resources/testharness.js'); +importScripts('helpers.js'); + +function expect_rejection(promise) { + return promise.then( + () => { return Promise.reject('unexpected fulfillment'); }, + err => { assert_equals('InvalidStateError', err.name); }); +} + +function test_before_activation() { + const np = self.registration.navigationPreload; + return expect_rejection(np.enable()) + .then(() => expect_rejection(np.disable())) + .then(() => expect_rejection(np.setHeaderValue('hi'))) + .then(() => np.getState()) + .then(state => expect_navigation_preload_state( + state, false, 'true', 'state should be the default')) + .then(() => 'PASS') + .catch(err => 'FAIL: ' + err); +} + +var resolve_done_promise; +var done_promise = new Promise(resolve => { resolve_done_promise = resolve; }); + +// Run the test once the page messages this worker. +self.addEventListener('message', e => { + e.waitUntil(test_before_activation() + .then(result => { + e.source.postMessage(result); + resolve_done_promise(); + })); + }); + +// Don't become the active worker until the test is done. +self.addEventListener('install', e => { + e.waitUntil(done_promise); + }); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/samesite-cookies.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/samesite-cookies.https.html new file mode 100644 index 00000000000..a860d954566 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/samesite-cookies.https.html @@ -0,0 +1,61 @@ + + + +Navigation Preload: SameSite cookies + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-preload/samesite-iframe.https.html b/test/wpt/tests/service-workers/service-worker/navigation-preload/samesite-iframe.https.html new file mode 100644 index 00000000000..633da9926a8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-preload/samesite-iframe.https.html @@ -0,0 +1,67 @@ + + +Navigation Preload for same site iframe + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-redirect-body.https.html b/test/wpt/tests/service-workers/service-worker/navigation-redirect-body.https.html new file mode 100644 index 00000000000..0441c610b17 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-redirect-body.https.html @@ -0,0 +1,53 @@ + +Service Worker: Navigation redirection must clear body + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-redirect-resolution.https.html b/test/wpt/tests/service-workers/service-worker/navigation-redirect-resolution.https.html new file mode 100644 index 00000000000..59e1cafec34 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-redirect-resolution.https.html @@ -0,0 +1,58 @@ + +Service Worker: Navigation Redirect Resolution + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-redirect-to-http.https.html b/test/wpt/tests/service-workers/service-worker/navigation-redirect-to-http.https.html new file mode 100644 index 00000000000..d4d2788c589 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-redirect-to-http.https.html @@ -0,0 +1,25 @@ + +Service Worker: Service Worker can receive HTTP opaqueredirect response. + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-redirect.https.html b/test/wpt/tests/service-workers/service-worker/navigation-redirect.https.html new file mode 100644 index 00000000000..d7d3d5259a4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-redirect.https.html @@ -0,0 +1,846 @@ + +Service Worker: Navigation redirection + + + + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-sets-cookie.https.html b/test/wpt/tests/service-workers/service-worker/navigation-sets-cookie.https.html new file mode 100644 index 00000000000..7f6c756f557 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-sets-cookie.https.html @@ -0,0 +1,133 @@ + + + +Service Worker: Navigation setting cookies + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-timing-extended.https.html b/test/wpt/tests/service-workers/service-worker/navigation-timing-extended.https.html new file mode 100644 index 00000000000..acb02c6fe1f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-timing-extended.https.html @@ -0,0 +1,55 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-timing.https.html b/test/wpt/tests/service-workers/service-worker/navigation-timing.https.html new file mode 100644 index 00000000000..6b51a5c2da2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/navigation-timing.https.html @@ -0,0 +1,76 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/nested-blob-url-workers.https.html b/test/wpt/tests/service-workers/service-worker/nested-blob-url-workers.https.html new file mode 100644 index 00000000000..7269cbb701f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/nested-blob-url-workers.https.html @@ -0,0 +1,42 @@ + + +Service Worker: nested blob URL worker clients + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/next-hop-protocol.https.html b/test/wpt/tests/service-workers/service-worker/next-hop-protocol.https.html new file mode 100644 index 00000000000..7a907438d5d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/next-hop-protocol.https.html @@ -0,0 +1,49 @@ + + +Service Worker: Verify nextHopProtocol is set correctly + + + + diff --git a/test/wpt/tests/service-workers/service-worker/no-dynamic-import-in-module.any.js b/test/wpt/tests/service-workers/service-worker/no-dynamic-import-in-module.any.js new file mode 100644 index 00000000000..f7c2ef37b8b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/no-dynamic-import-in-module.any.js @@ -0,0 +1,7 @@ +// META: global=serviceworker-module + +// This is imported to ensure import('./basic-module-2.js') fails even if +// it has been previously statically imported. +import './resources/basic-module-2.js'; + +import './resources/no-dynamic-import.js'; diff --git a/test/wpt/tests/service-workers/service-worker/no-dynamic-import.any.js b/test/wpt/tests/service-workers/service-worker/no-dynamic-import.any.js new file mode 100644 index 00000000000..25b370b7094 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/no-dynamic-import.any.js @@ -0,0 +1,3 @@ +// META: global=serviceworker + +importScripts('resources/no-dynamic-import.js'); diff --git a/test/wpt/tests/service-workers/service-worker/onactivate-script-error.https.html b/test/wpt/tests/service-workers/service-worker/onactivate-script-error.https.html new file mode 100644 index 00000000000..f5e80bb9a45 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/onactivate-script-error.https.html @@ -0,0 +1,74 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/oninstall-script-error.https.html b/test/wpt/tests/service-workers/service-worker/oninstall-script-error.https.html new file mode 100644 index 00000000000..fe7f6e90120 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/oninstall-script-error.https.html @@ -0,0 +1,72 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/opaque-response-preloaded.https.html b/test/wpt/tests/service-workers/service-worker/opaque-response-preloaded.https.html new file mode 100644 index 00000000000..417aa4ebec8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/opaque-response-preloaded.https.html @@ -0,0 +1,50 @@ + + +Opaque responses should not be reused for XHRs + + + + diff --git a/test/wpt/tests/service-workers/service-worker/opaque-script.https.html b/test/wpt/tests/service-workers/service-worker/opaque-script.https.html new file mode 100644 index 00000000000..7d2121855df --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/opaque-script.https.html @@ -0,0 +1,71 @@ + +Cache Storage: verify scripts loaded from cache_storage are marked opaque + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/partitioned-claim.tentative.https.html b/test/wpt/tests/service-workers/service-worker/partitioned-claim.tentative.https.html new file mode 100644 index 00000000000..1f42c528e0b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/partitioned-claim.tentative.https.html @@ -0,0 +1,74 @@ + + +Service Worker: Partitioned Service Workers + + + + + + + +This test creates a iframe in a first-party context and then registers a +service worker (such that the iframe client is unclaimed). +A third-party iframe is then created which has its SW call clients.claim() +and then the test checks that the 1p iframe was not claimed int he process. +Finally the test has its SW call clients.claim() and confirms the 1p iframe is +claimed. + + + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/partitioned-getRegistrations.tentative.https.html b/test/wpt/tests/service-workers/service-worker/partitioned-getRegistrations.tentative.https.html new file mode 100644 index 00000000000..7c4d4f1e028 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/partitioned-getRegistrations.tentative.https.html @@ -0,0 +1,99 @@ + + +Service Worker: Partitioned Service Workers + + + + + + + +This test loads a SW in a first-party context and gets the SW's (randomly) +generated ID. It does the same thing for the SW but in a third-party context +and then confirms that the IDs are different. + + + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/partitioned-matchAll.tentative.https.html b/test/wpt/tests/service-workers/service-worker/partitioned-matchAll.tentative.https.html new file mode 100644 index 00000000000..46beec819c8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/partitioned-matchAll.tentative.https.html @@ -0,0 +1,65 @@ + + +Service Worker: Partitioned Service Workers + + + + + + + +This test loads a SW in a first-party context and gets has the SW send +its list of clients from client.matchAll(). It does the same thing for the +SW in a third-party context as well and confirms that each SW see's the correct +clients and that they don't see eachother's clients. + + + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/partitioned.tentative.https.html b/test/wpt/tests/service-workers/service-worker/partitioned.tentative.https.html new file mode 100644 index 00000000000..17a375f9c73 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/partitioned.tentative.https.html @@ -0,0 +1,188 @@ + + +Service Worker: Partitioned Service Workers + + + + + + + + + The 3p iframe's postMessage: +

No message received

+ + The nested iframe's postMessage: +

No message received

+ + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/performance-timeline.https.html b/test/wpt/tests/service-workers/service-worker/performance-timeline.https.html new file mode 100644 index 00000000000..e56e6fe416f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/performance-timeline.https.html @@ -0,0 +1,49 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/postmessage-blob-url.https.html b/test/wpt/tests/service-workers/service-worker/postmessage-blob-url.https.html new file mode 100644 index 00000000000..16fddd57b83 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/postmessage-blob-url.https.html @@ -0,0 +1,33 @@ + +Service Worker: postMessage Blob URL + + + + diff --git a/test/wpt/tests/service-workers/service-worker/postmessage-from-waiting-serviceworker.https.html b/test/wpt/tests/service-workers/service-worker/postmessage-from-waiting-serviceworker.https.html new file mode 100644 index 00000000000..117def9eb2a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/postmessage-from-waiting-serviceworker.https.html @@ -0,0 +1,50 @@ + +Service Worker: postMessage from waiting serviceworker + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html b/test/wpt/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html new file mode 100644 index 00000000000..29c056080c7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/postmessage-msgport-to-client.https.html @@ -0,0 +1,43 @@ + +Service Worker: postMessage via MessagePort to Client + + + + diff --git a/test/wpt/tests/service-workers/service-worker/postmessage-to-client-message-queue.https.html b/test/wpt/tests/service-workers/service-worker/postmessage-to-client-message-queue.https.html new file mode 100644 index 00000000000..83e5f4540d1 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/postmessage-to-client-message-queue.https.html @@ -0,0 +1,212 @@ + +Service Worker: postMessage to Client (message queue) + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/postmessage-to-client.https.html b/test/wpt/tests/service-workers/service-worker/postmessage-to-client.https.html new file mode 100644 index 00000000000..f834a4bffe2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/postmessage-to-client.https.html @@ -0,0 +1,42 @@ + +Service Worker: postMessage to Client + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/postmessage.https.html b/test/wpt/tests/service-workers/service-worker/postmessage.https.html new file mode 100644 index 00000000000..7abb3022f91 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/postmessage.https.html @@ -0,0 +1,202 @@ + +Service Worker: postMessage + + + + diff --git a/test/wpt/tests/service-workers/service-worker/ready.https.window.js b/test/wpt/tests/service-workers/service-worker/ready.https.window.js new file mode 100644 index 00000000000..6c4e270682c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/ready.https.window.js @@ -0,0 +1,223 @@ +// META: title=Service Worker: navigator.serviceWorker.ready +// META: script=resources/test-helpers.sub.js + +test(() => { + assert_equals( + navigator.serviceWorker.ready, + navigator.serviceWorker.ready, + 'repeated access to ready without intervening registrations should return the same Promise object' + ); +}, 'ready returns the same Promise object'); + +promise_test(async t => { + const frame = await with_iframe('resources/blank.html?uncontrolled'); + t.add_cleanup(() => frame.remove()); + + const promise = frame.contentWindow.navigator.serviceWorker.ready; + + assert_equals( + Object.getPrototypeOf(promise), + frame.contentWindow.Promise.prototype, + 'the Promise should be in the context of the related document' + ); +}, 'ready returns a Promise object in the context of the related document'); + +promise_test(async t => { + const url = 'resources/empty-worker.js'; + const scope = 'resources/blank.html?ready-controlled'; + const expectedURL = normalizeURL(url); + const registration = await service_worker_unregister_and_register(t, url, scope); + t.add_cleanup(() => registration.unregister()); + + await wait_for_state(t, registration.installing, 'activated'); + + const frame = await with_iframe(scope); + t.add_cleanup(() => frame.remove()); + + const readyReg = await frame.contentWindow.navigator.serviceWorker.ready; + + assert_equals(readyReg.installing, null, 'installing should be null'); + assert_equals(readyReg.waiting, null, 'waiting should be null'); + assert_equals(readyReg.active.scriptURL, expectedURL, 'active after ready should not be null'); + assert_equals( + frame.contentWindow.navigator.serviceWorker.controller, + readyReg.active, + 'the controller should be the active worker' + ); + assert_in_array( + readyReg.active.state, + ['activating', 'activated'], + '.ready should be resolved when the registration has an active worker' + ); +}, 'ready on a controlled document'); + +promise_test(async t => { + const url = 'resources/empty-worker.js'; + const scope = 'resources/blank.html?ready-potential-controlled'; + const expected_url = normalizeURL(url); + const frame = await with_iframe(scope); + t.add_cleanup(() => frame.remove()); + + const registration = await navigator.serviceWorker.register(url, { scope }); + t.add_cleanup(() => registration.unregister()); + + const readyReg = await frame.contentWindow.navigator.serviceWorker.ready; + + assert_equals(readyReg.installing, null, 'installing should be null'); + assert_equals(readyReg.waiting, null, 'waiting should be null.') + assert_equals(readyReg.active.scriptURL, expected_url, 'active after ready should not be null'); + assert_in_array( + readyReg.active.state, + ['activating', 'activated'], + '.ready should be resolved when the registration has an active worker' + ); + assert_equals( + frame.contentWindow.navigator.serviceWorker.controller, + null, + 'uncontrolled document should not have a controller' + ); +}, 'ready on a potential controlled document'); + +promise_test(async t => { + const url = 'resources/empty-worker.js'; + const scope = 'resources/blank.html?ready-installing'; + + await service_worker_unregister(t, scope); + + const frame = await with_iframe(scope); + const promise = frame.contentWindow.navigator.serviceWorker.ready; + navigator.serviceWorker.register(url, { scope }); + const registration = await promise; + + t.add_cleanup(async () => { + await registration.unregister(); + frame.remove(); + }); + + assert_equals(registration.installing, null, 'installing should be null'); + assert_equals(registration.waiting, null, 'waiting should be null'); + assert_not_equals(registration.active, null, 'active after ready should not be null'); + assert_in_array( + registration.active.state, + ['activating', 'activated'], + '.ready should be resolved when the registration has an active worker' + ); +}, 'ready on an iframe whose parent registers a new service worker'); + +promise_test(async t => { + const scope = 'resources/register-iframe.html'; + const frame = await with_iframe(scope); + + const registration = await frame.contentWindow.navigator.serviceWorker.ready; + + t.add_cleanup(async () => { + await registration.unregister(); + frame.remove(); + }); + + assert_equals(registration.installing, null, 'installing should be null'); + assert_equals(registration.waiting, null, 'waiting should be null'); + assert_not_equals(registration.active, null, 'active after ready should not be null'); + assert_in_array( + registration.active.state, + ['activating', 'activated'], + '.ready should be resolved with "active worker"' + ); + }, 'ready on an iframe that installs a new service worker'); + +promise_test(async t => { + const url = 'resources/empty-worker.js'; + const matchedScope = 'resources/blank.html?ready-after-match'; + const longerMatchedScope = 'resources/blank.html?ready-after-match-longer'; + + await service_worker_unregister(t, matchedScope); + await service_worker_unregister(t, longerMatchedScope); + + const frame = await with_iframe(longerMatchedScope); + const registration = await navigator.serviceWorker.register(url, { scope: matchedScope }); + + t.add_cleanup(async () => { + await registration.unregister(); + frame.remove(); + }); + + await wait_for_state(t, registration.installing, 'activated'); + + const longerRegistration = await navigator.serviceWorker.register(url, { scope: longerMatchedScope }); + + t.add_cleanup(() => longerRegistration.unregister()); + + const readyReg = await frame.contentWindow.navigator.serviceWorker.ready; + + assert_equals( + readyReg.scope, + normalizeURL(longerMatchedScope), + 'longer matched registration should be returned' + ); + assert_equals( + frame.contentWindow.navigator.serviceWorker.controller, + null, + 'controller should be null' + ); +}, 'ready after a longer matched registration registered'); + +promise_test(async t => { + const url = 'resources/empty-worker.js'; + const matchedScope = 'resources/blank.html?ready-after-resolve'; + const longerMatchedScope = 'resources/blank.html?ready-after-resolve-longer'; + const registration = await service_worker_unregister_and_register(t, url, matchedScope); + t.add_cleanup(() => registration.unregister()); + + await wait_for_state(t, registration.installing, 'activated'); + + const frame = await with_iframe(longerMatchedScope); + t.add_cleanup(() => frame.remove()); + + const readyReg1 = await frame.contentWindow.navigator.serviceWorker.ready; + + assert_equals( + readyReg1.scope, + normalizeURL(matchedScope), + 'matched registration should be returned' + ); + + const longerReg = await navigator.serviceWorker.register(url, { scope: longerMatchedScope }); + t.add_cleanup(() => longerReg.unregister()); + + const readyReg2 = await frame.contentWindow.navigator.serviceWorker.ready; + + assert_equals( + readyReg2.scope, + normalizeURL(matchedScope), + 'ready should only be resolved once' + ); +}, 'access ready after it has been resolved'); + +promise_test(async t => { + const url1 = 'resources/empty-worker.js'; + const url2 = url1 + '?2'; + const matchedScope = 'resources/blank.html?ready-after-unregister'; + const reg1 = await service_worker_unregister_and_register(t, url1, matchedScope); + t.add_cleanup(() => reg1.unregister()); + + await wait_for_state(t, reg1.installing, 'activating'); + + const frame = await with_iframe(matchedScope); + t.add_cleanup(() => frame.remove()); + + await reg1.unregister(); + + // Ready promise should be pending, waiting for a new registration to arrive + const readyPromise = frame.contentWindow.navigator.serviceWorker.ready; + + const reg2 = await navigator.serviceWorker.register(url2, { scope: matchedScope }); + t.add_cleanup(() => reg2.unregister()); + + const readyReg = await readyPromise; + + // Wait for registration update, since it comes from another global, the states are racy. + await wait_for_state(t, reg2.installing || reg2.waiting || reg2.active, 'activated'); + + assert_equals(readyReg.active.scriptURL, reg2.active.scriptURL, 'Resolves with the second registration'); + assert_not_equals(reg1, reg2, 'Registrations should be different'); +}, 'resolve ready after unregistering'); diff --git a/test/wpt/tests/service-workers/service-worker/redirected-response.https.html b/test/wpt/tests/service-workers/service-worker/redirected-response.https.html new file mode 100644 index 00000000000..71b35d0e120 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/redirected-response.https.html @@ -0,0 +1,471 @@ + +Service Worker: Redirected response + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/referer.https.html b/test/wpt/tests/service-workers/service-worker/referer.https.html new file mode 100644 index 00000000000..0957e4c5330 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/referer.https.html @@ -0,0 +1,40 @@ + +Service Worker: check referer of fetch() + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/referrer-policy-header.https.html b/test/wpt/tests/service-workers/service-worker/referrer-policy-header.https.html new file mode 100644 index 00000000000..784343e6d8f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/referrer-policy-header.https.html @@ -0,0 +1,67 @@ + +Service Worker: check referer of fetch() with Referrer Policy + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/referrer-toplevel-script-fetch.https.html b/test/wpt/tests/service-workers/service-worker/referrer-toplevel-script-fetch.https.html new file mode 100644 index 00000000000..65c60a11db2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/referrer-toplevel-script-fetch.https.html @@ -0,0 +1,64 @@ + +Service Worker: check referrer of top-level script fetch + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/register-closed-window.https.html b/test/wpt/tests/service-workers/service-worker/register-closed-window.https.html new file mode 100644 index 00000000000..9c1b639bb75 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/register-closed-window.https.html @@ -0,0 +1,35 @@ + +Service Worker: Register() on Closed Window + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/register-default-scope.https.html b/test/wpt/tests/service-workers/service-worker/register-default-scope.https.html new file mode 100644 index 00000000000..1d86548eb5e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/register-default-scope.https.html @@ -0,0 +1,69 @@ + +register() and scope + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html b/test/wpt/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html new file mode 100644 index 00000000000..6eb00f3071a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/register-same-scope-different-script-url.https.html @@ -0,0 +1,233 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html b/test/wpt/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html new file mode 100644 index 00000000000..0920b5cb223 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/register-wait-forever-in-install-worker.https.html @@ -0,0 +1,57 @@ + +Service Worker: Register wait-forever-in-install-worker + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-basic.https.html b/test/wpt/tests/service-workers/service-worker/registration-basic.https.html new file mode 100644 index 00000000000..759b4244a26 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-basic.https.html @@ -0,0 +1,39 @@ + +Service Worker: Registration (basic) + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-end-to-end.https.html b/test/wpt/tests/service-workers/service-worker/registration-end-to-end.https.html new file mode 100644 index 00000000000..1af4582d387 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-end-to-end.https.html @@ -0,0 +1,88 @@ + +Service Worker: registration end-to-end + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-events.https.html b/test/wpt/tests/service-workers/service-worker/registration-events.https.html new file mode 100644 index 00000000000..5bcfd66846b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-events.https.html @@ -0,0 +1,42 @@ + +Service Worker: registration events + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-iframe.https.html b/test/wpt/tests/service-workers/service-worker/registration-iframe.https.html new file mode 100644 index 00000000000..ae39ddfea3e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-iframe.https.html @@ -0,0 +1,116 @@ + + +Service Worker: Registration for iframe + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-mime-types.https.html b/test/wpt/tests/service-workers/service-worker/registration-mime-types.https.html new file mode 100644 index 00000000000..3a21aac5c75 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-mime-types.https.html @@ -0,0 +1,10 @@ + +Service Worker: Registration (MIME types) + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-schedule-job.https.html b/test/wpt/tests/service-workers/service-worker/registration-schedule-job.https.html new file mode 100644 index 00000000000..25d758ee8f0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-schedule-job.https.html @@ -0,0 +1,107 @@ + + + +Service Worker: Schedule Job algorithm + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-scope-module-static-import.https.html b/test/wpt/tests/service-workers/service-worker/registration-scope-module-static-import.https.html new file mode 100644 index 00000000000..5c75295aed1 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-scope-module-static-import.https.html @@ -0,0 +1,41 @@ + +Service Worker: Static imports from module top-level scripts shouldn't be affected by the service worker script path restriction + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-scope.https.html b/test/wpt/tests/service-workers/service-worker/registration-scope.https.html new file mode 100644 index 00000000000..141875f5847 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-scope.https.html @@ -0,0 +1,9 @@ + +Service Worker: Registration (scope) + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-script-module.https.html b/test/wpt/tests/service-workers/service-worker/registration-script-module.https.html new file mode 100644 index 00000000000..9e39a1f75b2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-script-module.https.html @@ -0,0 +1,13 @@ + +Service Worker: Registration (module script) + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-script-url.https.html b/test/wpt/tests/service-workers/service-worker/registration-script-url.https.html new file mode 100644 index 00000000000..bda61adb002 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-script-url.https.html @@ -0,0 +1,9 @@ + +Service Worker: Registration (scriptURL) + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-script.https.html b/test/wpt/tests/service-workers/service-worker/registration-script.https.html new file mode 100644 index 00000000000..f1e51fd265e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-script.https.html @@ -0,0 +1,12 @@ + +Service Worker: Registration (script) + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-security-error.https.html b/test/wpt/tests/service-workers/service-worker/registration-security-error.https.html new file mode 100644 index 00000000000..860c2d22ea9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-security-error.https.html @@ -0,0 +1,9 @@ + +Service Worker: Registration (SecurityError) + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-service-worker-attributes.https.html b/test/wpt/tests/service-workers/service-worker/registration-service-worker-attributes.https.html new file mode 100644 index 00000000000..f7b52d5ddce --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-service-worker-attributes.https.html @@ -0,0 +1,72 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/registration-updateviacache.https.html b/test/wpt/tests/service-workers/service-worker/registration-updateviacache.https.html new file mode 100644 index 00000000000..b2f6bbc6f84 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/registration-updateviacache.https.html @@ -0,0 +1,204 @@ + +Service Worker: Registration-updateViaCache + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/rejections.https.html b/test/wpt/tests/service-workers/service-worker/rejections.https.html new file mode 100644 index 00000000000..8002ad9a81b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/rejections.https.html @@ -0,0 +1,21 @@ + +Service Worker: Rejection Types + + + diff --git a/test/wpt/tests/service-workers/service-worker/request-end-to-end.https.html b/test/wpt/tests/service-workers/service-worker/request-end-to-end.https.html new file mode 100644 index 00000000000..a39ceadd9f3 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/request-end-to-end.https.html @@ -0,0 +1,40 @@ + +Service Worker: FetchEvent.request passed to onfetch + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resource-timing-bodySize.https.html b/test/wpt/tests/service-workers/service-worker/resource-timing-bodySize.https.html new file mode 100644 index 00000000000..5c2b1eba8c8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resource-timing-bodySize.https.html @@ -0,0 +1,55 @@ + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resource-timing-cross-origin.https.html b/test/wpt/tests/service-workers/service-worker/resource-timing-cross-origin.https.html new file mode 100644 index 00000000000..2155d7ff6e4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resource-timing-cross-origin.https.html @@ -0,0 +1,46 @@ + + + + +This test validates Resource Timing for cross origin content fetched by Service Worker from an originally same-origin URL. + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resource-timing-fetch-variants.https.html b/test/wpt/tests/service-workers/service-worker/resource-timing-fetch-variants.https.html new file mode 100644 index 00000000000..8d4f0be01a8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resource-timing-fetch-variants.https.html @@ -0,0 +1,121 @@ + + + +Test various interactions between fetch, service-workers and resource timing + + + + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resource-timing.sub.https.html b/test/wpt/tests/service-workers/service-worker/resource-timing.sub.https.html new file mode 100644 index 00000000000..9808ae5ae1b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resource-timing.sub.https.html @@ -0,0 +1,150 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/404.py b/test/wpt/tests/service-workers/service-worker/resources/404.py new file mode 100644 index 00000000000..1ee4af169e7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/404.py @@ -0,0 +1,5 @@ +# iframe does not fire onload event if the response's content-type is not +# text/plain or text/html so this script exists if you want to test a 404 load +# in an iframe. +def main(req, res): + return 404, [(b'Content-Type', b'text/plain')], b"Page not found" diff --git a/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-blank-dynamic-nested-frame.html b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-blank-dynamic-nested-frame.html new file mode 100644 index 00000000000..1e0c6209bfc --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-blank-dynamic-nested-frame.html @@ -0,0 +1,21 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-blank-nested-frame.html b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-blank-nested-frame.html new file mode 100644 index 00000000000..16f7e7c60ff --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-blank-nested-frame.html @@ -0,0 +1,21 @@ + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-frame.py b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-frame.py new file mode 100644 index 00000000000..a29ff9d4134 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-frame.py @@ -0,0 +1,31 @@ +def main(request, response): + if b'nested' in request.GET: + return ( + [(b'Content-Type', b'text/html')], + b'failed: nested frame was not intercepted by the service worker' + ) + + return ([(b'Content-Type', b'text/html')], b""" + + + + + + + + +""") diff --git a/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-ping-frame.py b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-ping-frame.py new file mode 100644 index 00000000000..30fbbbb5357 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-ping-frame.py @@ -0,0 +1,49 @@ +def main(request, response): + if b'nested' in request.GET: + return ( + [(b'Content-Type', b'text/html')], + b'failed: nested frame was not intercepted by the service worker' + ) + + return ([(b'Content-Type', b'text/html')], b""" + + + + + + + + +""") diff --git a/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-popup-frame.py b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-popup-frame.py new file mode 100644 index 00000000000..04c12a6037d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-popup-frame.py @@ -0,0 +1,32 @@ +def main(request, response): + if b'nested' in request.GET: + return ( + [(b'Content-Type', b'text/html')], + b'failed: nested frame was not intercepted by the service worker' + ) + + return ([(b'Content-Type', b'text/html')], b""" + + + + + + +""") diff --git a/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-srcdoc-nested-frame.html b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-srcdoc-nested-frame.html new file mode 100644 index 00000000000..0122a00aa43 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-srcdoc-nested-frame.html @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-uncontrolled-nested-frame.html b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-uncontrolled-nested-frame.html new file mode 100644 index 00000000000..89509159a41 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-uncontrolled-nested-frame.html @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-worker.js b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-worker.js new file mode 100644 index 00000000000..f43598e41c1 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/about-blank-replacement-worker.js @@ -0,0 +1,95 @@ +// Helper routine to find a client that matches a particular URL. Note, we +// require that Client to be controlled to avoid false matches with other +// about:blank windows the browser might have. The initial about:blank should +// inherit the controller from its parent. +async function getClientByURL(url) { + let list = await clients.matchAll(); + return list.find(client => client.url === url); +} + +// Helper routine to perform a ping-pong with the given target client. We +// expect the Client to respond with its location URL. +async function pingPong(target) { + function waitForPong() { + return new Promise(resolve => { + self.addEventListener('message', function onMessage(evt) { + if (evt.data.type === 'PONG') { + resolve(evt.data.location); + } + }); + }); + } + + target.postMessage({ type: 'PING' }) + return await waitForPong(target); +} + +addEventListener('fetch', async evt => { + let url = new URL(evt.request.url); + if (!url.searchParams.get('nested')) { + return; + } + + evt.respondWith(async function() { + // Find the initial about:blank document. + const client = await getClientByURL('about:blank'); + if (!client) { + return new Response('failure: could not find about:blank client'); + } + + // If the nested frame is configured to support a ping-pong, then + // ping it now to verify its message listener exists. We also + // verify the Client's idea of its own location URL while we are doing + // this. + if (url.searchParams.get('ping')) { + const loc = await pingPong(client); + if (loc !== 'about:blank') { + return new Response(`failure: got location {$loc}, expected about:blank`); + } + } + + // Finally, allow the nested frame to complete loading. We place the + // Client ID we found for the initial about:blank in the body. + return new Response(client.id); + }()); +}); + +addEventListener('message', evt => { + if (evt.data.type !== 'GET_CLIENT_ID') { + return; + } + + evt.waitUntil(async function() { + let url = new URL(evt.data.url); + + // Find the given Client by its URL. + let client = await getClientByURL(evt.data.url); + if (!client) { + evt.source.postMessage({ + type: 'GET_CLIENT_ID', + result: `failure: could not find ${evt.data.url} client` + }); + return; + } + + // If the Client supports a ping-pong, then do it now to verify + // the message listener exists and its location matches the + // Client object. + if (url.searchParams.get('ping')) { + let loc = await pingPong(client); + if (loc !== evt.data.url) { + evt.source.postMessage({ + type: 'GET_CLIENT_ID', + result: `failure: got location ${loc}, expected ${evt.data.url}` + }); + return; + } + } + + // Finally, send the client ID back. + evt.source.postMessage({ + type: 'GET_CLIENT_ID', + result: client.id + }); + }()); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/basic-module-2.js b/test/wpt/tests/service-workers/service-worker/resources/basic-module-2.js new file mode 100644 index 00000000000..189b1c87fee --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/basic-module-2.js @@ -0,0 +1 @@ +export default 'hello again!'; diff --git a/test/wpt/tests/service-workers/service-worker/resources/basic-module.js b/test/wpt/tests/service-workers/service-worker/resources/basic-module.js new file mode 100644 index 00000000000..789a89bc630 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/basic-module.js @@ -0,0 +1 @@ +export default 'hello!'; diff --git a/test/wpt/tests/service-workers/service-worker/resources/blank.html b/test/wpt/tests/service-workers/service-worker/resources/blank.html new file mode 100644 index 00000000000..a3c3a4689a6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/blank.html @@ -0,0 +1,2 @@ + +Empty doc diff --git a/test/wpt/tests/service-workers/service-worker/resources/bytecheck-worker-imported-script.py b/test/wpt/tests/service-workers/service-worker/resources/bytecheck-worker-imported-script.py new file mode 100644 index 00000000000..1931c77b678 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/bytecheck-worker-imported-script.py @@ -0,0 +1,20 @@ +import time + +def main(request, response): + headers = [(b'Content-Type', b'application/javascript'), + (b'Cache-Control', b'max-age=0'), + (b'Access-Control-Allow-Origin', b'*')] + + imported_content_type = b'' + if b'imported' in request.GET: + imported_content_type = request.GET[b'imported'] + + imported_content = b'default' + if imported_content_type == b'time': + imported_content = b'%f' % time.time() + + body = b''' + // %s + ''' % (imported_content) + + return headers, body diff --git a/test/wpt/tests/service-workers/service-worker/resources/bytecheck-worker.py b/test/wpt/tests/service-workers/service-worker/resources/bytecheck-worker.py new file mode 100644 index 00000000000..10f3bceb4fd --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/bytecheck-worker.py @@ -0,0 +1,38 @@ +import time + +def main(request, response): + headers = [(b'Content-Type', b'application/javascript'), + (b'Cache-Control', b'max-age=0')] + + main_content_type = b'' + if b'main' in request.GET: + main_content_type = request.GET[b'main'] + + main_content = b'default' + if main_content_type == b'time': + main_content = b'%f' % time.time() + + imported_request_path = b'' + if b'path' in request.GET: + imported_request_path = request.GET[b'path'] + + imported_request_type = b'' + if b'imported' in request.GET: + imported_request_type = request.GET[b'imported'] + + imported_request = b'' + if imported_request_type == b'time': + imported_request = b'?imported=time' + + if b'type' in request.GET and request.GET[b'type'] == b'module': + body = b''' + // %s + import '%sbytecheck-worker-imported-script.py%s'; + ''' % (main_content, imported_request_path, imported_request) + else: + body = b''' + // %s + importScripts('%sbytecheck-worker-imported-script.py%s'); + ''' % (main_content, imported_request_path, imported_request) + + return headers, body diff --git a/test/wpt/tests/service-workers/service-worker/resources/claim-blob-url-worker-fetch-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/claim-blob-url-worker-fetch-iframe.html new file mode 100644 index 00000000000..12ae1a87258 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/claim-blob-url-worker-fetch-iframe.html @@ -0,0 +1,21 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/claim-nested-worker-fetch-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/claim-nested-worker-fetch-iframe.html new file mode 100644 index 00000000000..2fa15db61d9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/claim-nested-worker-fetch-iframe.html @@ -0,0 +1,16 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/claim-nested-worker-fetch-parent-worker.js b/test/wpt/tests/service-workers/service-worker/resources/claim-nested-worker-fetch-parent-worker.js new file mode 100644 index 00000000000..f5ff7c234b4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/claim-nested-worker-fetch-parent-worker.js @@ -0,0 +1,12 @@ +try { + var worker = new Worker('./claim-worker-fetch-worker.js'); + + self.onmessage = (event) => { + worker.postMessage(event.data); + } + worker.onmessage = (event) => { + self.postMessage(event.data); + }; +} catch (e) { + self.postMessage("Fail: " + e.data); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/claim-shared-worker-fetch-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/claim-shared-worker-fetch-iframe.html new file mode 100644 index 00000000000..ad865b848f8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/claim-shared-worker-fetch-iframe.html @@ -0,0 +1,13 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/claim-shared-worker-fetch-worker.js b/test/wpt/tests/service-workers/service-worker/resources/claim-shared-worker-fetch-worker.js new file mode 100644 index 00000000000..ddc8bea7af4 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/claim-shared-worker-fetch-worker.js @@ -0,0 +1,8 @@ +self.onconnect = (event) => { + var port = event.ports[0]; + event.ports[0].onmessage = (evt) => { + fetch(evt.data) + .then(response => response.text()) + .then(text => port.postMessage(text)); + }; +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/claim-with-redirect-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/claim-with-redirect-iframe.html new file mode 100644 index 00000000000..4150d7e6857 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/claim-with-redirect-iframe.html @@ -0,0 +1,48 @@ + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/claim-worker-fetch-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/claim-worker-fetch-iframe.html new file mode 100644 index 00000000000..92c5d15def0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/claim-worker-fetch-iframe.html @@ -0,0 +1,13 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/claim-worker-fetch-worker.js b/test/wpt/tests/service-workers/service-worker/resources/claim-worker-fetch-worker.js new file mode 100644 index 00000000000..7080181c85c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/claim-worker-fetch-worker.js @@ -0,0 +1,5 @@ +self.onmessage = (event) => { + fetch(event.data) + .then(response => response.text()) + .then(text => self.postMessage(text)); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/claim-worker.js b/test/wpt/tests/service-workers/service-worker/resources/claim-worker.js new file mode 100644 index 00000000000..18004079475 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/claim-worker.js @@ -0,0 +1,19 @@ +self.addEventListener('message', function(event) { + self.clients.claim() + .then(function(result) { + if (result !== undefined) { + event.data.port.postMessage( + 'FAIL: claim() should be resolved with undefined'); + return; + } + event.data.port.postMessage('PASS'); + }) + .catch(function(error) { + event.data.port.postMessage('FAIL: exception: ' + error.name); + }); + }); + +self.addEventListener('fetch', function(event) { + if (!/404/.test(event.request.url)) + event.respondWith(new Response('Intercepted!')); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/classic-worker.js b/test/wpt/tests/service-workers/service-worker/resources/classic-worker.js new file mode 100644 index 00000000000..36a32b1a1f8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/classic-worker.js @@ -0,0 +1 @@ +importScripts('./imported-classic-script.js'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/client-id-worker.js b/test/wpt/tests/service-workers/service-worker/resources/client-id-worker.js new file mode 100644 index 00000000000..ec71b3458b7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/client-id-worker.js @@ -0,0 +1,27 @@ +self.onmessage = function(e) { + var port = e.data.port; + var message = []; + + var promise = Promise.resolve() + .then(function() { + // 1st matchAll() + return self.clients.matchAll().then(function(clients) { + clients.forEach(function(client) { + message.push(client.id); + }); + }); + }) + .then(function() { + // 2nd matchAll() + return self.clients.matchAll().then(function(clients) { + clients.forEach(function(client) { + message.push(client.id); + }); + }); + }) + .then(function() { + // Send an array containing ids of clients from 1st and 2nd matchAll() + port.postMessage(message); + }); + e.waitUntil(promise); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/client-navigate-frame.html b/test/wpt/tests/service-workers/service-worker/resources/client-navigate-frame.html new file mode 100644 index 00000000000..7e186f8ee79 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/client-navigate-frame.html @@ -0,0 +1,12 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/client-navigate-worker.js b/test/wpt/tests/service-workers/service-worker/resources/client-navigate-worker.js new file mode 100644 index 00000000000..6101d5d8f92 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/client-navigate-worker.js @@ -0,0 +1,92 @@ +importScripts("worker-testharness.js"); +importScripts("test-helpers.sub.js"); +importScripts("/common/get-host-info.sub.js") +importScripts("testharness-helpers.js") + +setup({ explicit_done: true }); + +self.onfetch = function(e) { + if (e.request.url.indexOf("client-navigate-frame.html") >= 0) { + return; + } + e.respondWith(new Response(e.clientId)); +}; + +function pass(test, url) { + return { result: test, + url: url }; +} + +function fail(test, reason) { + return { result: "FAILED " + test + " " + reason } +} + +self.onmessage = function(e) { + var port = e.data.port; + var test = e.data.test; + var clientId = e.data.clientId; + var clientUrl = ""; + if (test === "test_client_navigate_success") { + promise_test(function(t) { + this.add_cleanup(() => port.postMessage(pass(test, clientUrl))); + return self.clients.get(clientId) + .then(client => client.navigate("client-navigated-frame.html")) + .then(client => { + clientUrl = client.url; + assert_true(client instanceof WindowClient); + }) + .catch(unreached_rejection(t)); + }, "Return value should be instance of WindowClient"); + done(); + } else if (test === "test_client_navigate_cross_origin") { + promise_test(function(t) { + this.add_cleanup(() => port.postMessage(pass(test, clientUrl))); + var path = new URL('client-navigated-frame.html', self.location.href).pathname; + var url = get_host_info()['HTTPS_REMOTE_ORIGIN'] + path; + return self.clients.get(clientId) + .then(client => client.navigate(url)) + .then(client => { + clientUrl = (client && client.url) || ""; + assert_equals(client, null, + 'cross-origin navigate resolves with null'); + }) + .catch(unreached_rejection(t)); + }, "Navigating to different origin should resolve with null"); + done(); + } else if (test === "test_client_navigate_about_blank") { + promise_test(function(t) { + this.add_cleanup(function() { port.postMessage(pass(test, "")); }); + return self.clients.get(clientId) + .then(client => promise_rejects_js(t, TypeError, client.navigate("about:blank"))) + .catch(unreached_rejection(t)); + }, "Navigating to about:blank should reject with TypeError"); + done(); + } else if (test === "test_client_navigate_mixed_content") { + promise_test(function(t) { + this.add_cleanup(function() { port.postMessage(pass(test, "")); }); + var path = new URL('client-navigated-frame.html', self.location.href).pathname; + // Insecure URL should fail since the frame is owned by a secure parent + // and navigating to http:// would create a mixed-content violation. + var url = get_host_info()['HTTP_REMOTE_ORIGIN'] + path; + return self.clients.get(clientId) + .then(client => promise_rejects_js(t, TypeError, client.navigate(url))) + .catch(unreached_rejection(t)); + }, "Navigating to mixed-content iframe should reject with TypeError"); + done(); + } else if (test === "test_client_navigate_redirect") { + var host_info = get_host_info(); + var url = new URL(host_info['HTTPS_REMOTE_ORIGIN']).toString() + + new URL("client-navigated-frame.html", location).pathname.substring(1); + promise_test(function(t) { + this.add_cleanup(() => port.postMessage(pass(test, clientUrl))); + return self.clients.get(clientId) + .then(client => client.navigate("redirect.py?Redirect=" + url)) + .then(client => { + clientUrl = (client && client.url) || "" + assert_equals(client, null); + }) + .catch(unreached_rejection(t)); + }, "Redirecting to another origin should resolve with null"); + done(); + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/client-navigated-frame.html b/test/wpt/tests/service-workers/service-worker/resources/client-navigated-frame.html new file mode 100644 index 00000000000..307f7f9ac6e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/client-navigated-frame.html @@ -0,0 +1,3 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/client-url-of-blob-url-worker.html b/test/wpt/tests/service-workers/service-worker/resources/client-url-of-blob-url-worker.html new file mode 100644 index 00000000000..00f6acede8e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/client-url-of-blob-url-worker.html @@ -0,0 +1,26 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/client-url-of-blob-url-worker.js b/test/wpt/tests/service-workers/service-worker/resources/client-url-of-blob-url-worker.js new file mode 100644 index 00000000000..fd754f8250d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/client-url-of-blob-url-worker.js @@ -0,0 +1,10 @@ +addEventListener('fetch', e => { + if (e.request.url.includes('get-worker-client-url')) { + e.respondWith((async () => { + const clients = await self.clients.matchAll({type: 'worker'}); + if (clients.length != 1) + return new Response('one worker client should exist'); + return new Response(clients[0].url); + })()); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-frame-freeze.html b/test/wpt/tests/service-workers/service-worker/resources/clients-frame-freeze.html new file mode 100644 index 00000000000..7468a660e90 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-frame-freeze.html @@ -0,0 +1,15 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-frame-worker.js b/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-frame-worker.js new file mode 100644 index 00000000000..0a1461b40e0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-frame-worker.js @@ -0,0 +1,11 @@ +onmessage = function(e) { + if (e.data.cmd == 'GetClientId') { + fetch('clientId') + .then(function(response) { + return response.text(); + }) + .then(function(text) { + e.data.port.postMessage({clientId: text}); + }); + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-frame.html b/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-frame.html new file mode 100644 index 00000000000..4324e6d4054 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-frame.html @@ -0,0 +1,17 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-shared-worker.js b/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-shared-worker.js new file mode 100644 index 00000000000..fadef970374 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-shared-worker.js @@ -0,0 +1,10 @@ +onconnect = function(e) { + var port = e.ports[0]; + fetch('clientId') + .then(function(response) { + return response.text(); + }) + .then(function(text) { + port.postMessage({clientId: text}); + }); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-worker.js b/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-worker.js new file mode 100644 index 00000000000..0a1461b40e0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-get-client-types-worker.js @@ -0,0 +1,11 @@ +onmessage = function(e) { + if (e.data.cmd == 'GetClientId') { + fetch('clientId') + .then(function(response) { + return response.text(); + }) + .then(function(text) { + e.data.port.postMessage({clientId: text}); + }); + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-get-cross-origin-frame.html b/test/wpt/tests/service-workers/service-worker/resources/clients-get-cross-origin-frame.html new file mode 100644 index 00000000000..e16bb1116dc --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-get-cross-origin-frame.html @@ -0,0 +1,50 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-get-frame.html b/test/wpt/tests/service-workers/service-worker/resources/clients-get-frame.html new file mode 100644 index 00000000000..27143d4b99b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-get-frame.html @@ -0,0 +1,12 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-get-other-origin.html b/test/wpt/tests/service-workers/service-worker/resources/clients-get-other-origin.html new file mode 100644 index 00000000000..6342fe04f40 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-get-other-origin.html @@ -0,0 +1,64 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-get-resultingClientId-worker.js b/test/wpt/tests/service-workers/service-worker/resources/clients-get-resultingClientId-worker.js new file mode 100644 index 00000000000..5a46ff9cf44 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-get-resultingClientId-worker.js @@ -0,0 +1,60 @@ +let savedPort = null; +let savedResultingClientId = null; + +async function getTestingPage() { + const clientList = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); + for (let c of clientList) { + if (c.url.endsWith('clients-get.https.html')) { + c.focus(); + return c; + } + } + return null; +} + +async function destroyResultingClient(testingPage) { + const destroyedPromise = new Promise(resolve => { + self.addEventListener('message', e => { + if (e.data.msg == 'resultingClientDestroyed') { + resolve(); + } + }, {once: true}); + }); + testingPage.postMessage({ msg: 'destroyResultingClient' }); + return destroyedPromise; +} + +self.addEventListener('fetch', async (e) => { + let { resultingClientId } = e; + savedResultingClientId = resultingClientId; + + if (e.request.url.endsWith('simple.html?fail')) { + e.waitUntil((async () => { + const testingPage = await getTestingPage(); + await destroyResultingClient(testingPage); + testingPage.postMessage({ msg: 'resultingClientDestroyedAck', + resultingDestroyedClientId: savedResultingClientId }); + })()); + return; + } + + e.respondWith(fetch(e.request)); +}); + +self.addEventListener('message', (e) => { + let { msg, resultingClientId } = e.data; + e.waitUntil((async () => { + if (msg == 'getIsResultingClientUndefined') { + const client = await self.clients.get(resultingClientId); + let isUndefined = typeof client == 'undefined'; + e.source.postMessage({ msg: 'getIsResultingClientUndefined', + isResultingClientUndefined: isUndefined }); + return; + } + if (msg == 'getResultingClientId') { + e.source.postMessage({ msg: 'getResultingClientId', + resultingClientId: savedResultingClientId }); + return; + } + })()); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-get-worker.js b/test/wpt/tests/service-workers/service-worker/resources/clients-get-worker.js new file mode 100644 index 00000000000..8effa56c98c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-get-worker.js @@ -0,0 +1,41 @@ +// This worker is designed to expose information about clients that is only available from Service Worker contexts. +// +// In the case of the `onfetch` handler, it provides the `clientId` property of +// the `event` object. In the case of the `onmessage` handler, it provides the +// Client instance attributes of the requested clients. +self.onfetch = function(e) { + if (/\/clientId$/.test(e.request.url)) { + e.respondWith(new Response(e.clientId)); + return; + } +}; + +self.onmessage = function(e) { + var client_ids = e.data.clientIds; + var message = []; + + e.waitUntil(Promise.all( + client_ids.map(function(client_id) { + return self.clients.get(client_id); + })) + .then(function(clients) { + // No matching client for a given id or a matched client is off-origin + // from the service worker. + if (clients.length == 1 && clients[0] == undefined) { + e.source.postMessage(clients[0]); + } else { + clients.forEach(function(client) { + if (client instanceof Client) { + message.push([client.visibilityState, + client.focused, + client.url, + client.type, + client.frameType]); + } else { + message.push(client); + } + }); + e.source.postMessage(message); + } + })); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-blob-url-worker.html b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-blob-url-worker.html new file mode 100644 index 00000000000..ee89a0d8b3e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-blob-url-worker.html @@ -0,0 +1,20 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-client-types-dedicated-worker.js b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-client-types-dedicated-worker.js new file mode 100644 index 00000000000..5a3f04d33aa --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-client-types-dedicated-worker.js @@ -0,0 +1,3 @@ +onmessage = function(e) { + postMessage(e.data); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html new file mode 100644 index 00000000000..7607b035de3 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html @@ -0,0 +1,8 @@ + +Empty doc + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js new file mode 100644 index 00000000000..1ae72fb8944 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-client-types-shared-worker.js @@ -0,0 +1,4 @@ +onconnect = function(e) { + var port = e.ports[0]; + port.postMessage('started'); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-on-evaluation-worker.js b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-on-evaluation-worker.js new file mode 100644 index 00000000000..f1559aca39b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-on-evaluation-worker.js @@ -0,0 +1,11 @@ +importScripts('test-helpers.sub.js'); + +var page_url = normalizeURL('../clients-matchall-on-evaluation.https.html'); + +self.clients.matchAll({includeUncontrolled: true}) + .then(function(clients) { + clients.forEach(function(client) { + if (client.url == page_url) + client.postMessage('matched'); + }); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-worker.js b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-worker.js new file mode 100644 index 00000000000..13e111a2f91 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/clients-matchall-worker.js @@ -0,0 +1,40 @@ +self.onmessage = function(e) { + var port = e.data.port; + var options = e.data.options; + + e.waitUntil(self.clients.matchAll(options) + .then(function(clients) { + var message = []; + clients.forEach(function(client) { + var frame_type = client.frameType; + if (client.url.indexOf('clients-matchall-include-uncontrolled.https.html') > -1 && + client.frameType == 'auxiliary') { + // The test tab might be opened using window.open() by the test framework. + // In that case, just pretend it's top-level! + frame_type = 'top-level'; + } + if (e.data.includeLifecycleState) { + message.push({visibilityState: client.visibilityState, + focused: client.focused, + url: client.url, + lifecycleState: client.lifecycleState, + type: client.type, + frameType: frame_type}); + } else { + message.push([client.visibilityState, + client.focused, + client.url, + client.type, + frame_type]); + } + }); + // Sort by url + if (!e.data.disableSort) { + message.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; }); + } + port.postMessage(message); + }) + .catch(e => { + port.postMessage('clients.matchAll() rejected: ' + e); + })); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/cors-approved.txt b/test/wpt/tests/service-workers/service-worker/resources/cors-approved.txt new file mode 100644 index 00000000000..1cd89bb14d6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/cors-approved.txt @@ -0,0 +1 @@ +plaintext diff --git a/test/wpt/tests/service-workers/service-worker/resources/cors-approved.txt.headers b/test/wpt/tests/service-workers/service-worker/resources/cors-approved.txt.headers new file mode 100644 index 00000000000..f7985fd9bd5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/cors-approved.txt.headers @@ -0,0 +1,3 @@ +Content-Type: text/plain +Access-Control-Allow-Origin: * + diff --git a/test/wpt/tests/service-workers/service-worker/resources/cors-denied.txt b/test/wpt/tests/service-workers/service-worker/resources/cors-denied.txt new file mode 100644 index 00000000000..ff333bd97da --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/cors-denied.txt @@ -0,0 +1,2 @@ +this file is served without Access-Control-Allow-Origin headers so it should not +be readable from cross-origin. diff --git a/test/wpt/tests/service-workers/service-worker/resources/create-blob-url-worker.js b/test/wpt/tests/service-workers/service-worker/resources/create-blob-url-worker.js new file mode 100644 index 00000000000..57e4882c24f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/create-blob-url-worker.js @@ -0,0 +1,22 @@ +const childWorkerScript = ` + self.onmessage = async (e) => { + const response = await fetch(e.data); + const text = await response.text(); + self.postMessage(text); + }; +`; +const blob = new Blob([childWorkerScript], { type: 'text/javascript' }); +const blobUrl = URL.createObjectURL(blob); +const childWorker = new Worker(blobUrl); + +// When a message comes from the parent frame, sends a resource url to the child +// worker. +self.onmessage = (e) => { + childWorker.postMessage(e.data); +}; + +// When a message comes from the child worker, sends a content of fetch() to the +// parent frame. +childWorker.onmessage = (e) => { + self.postMessage(e.data); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/create-out-of-scope-worker.html b/test/wpt/tests/service-workers/service-worker/resources/create-out-of-scope-worker.html new file mode 100644 index 00000000000..b51c4517509 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/create-out-of-scope-worker.html @@ -0,0 +1,19 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/echo-content.py b/test/wpt/tests/service-workers/service-worker/resources/echo-content.py new file mode 100644 index 00000000000..70ae4b60254 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/echo-content.py @@ -0,0 +1,16 @@ +# This is a copy of fetch/api/resources/echo-content.py since it's more +# convenient in this directory due to service worker's path restriction. +from wptserve.utils import isomorphic_encode + +def main(request, response): + + headers = [(b"X-Request-Method", isomorphic_encode(request.method)), + (b"X-Request-Content-Length", request.headers.get(b"Content-Length", b"NO")), + (b"X-Request-Content-Type", request.headers.get(b"Content-Type", b"NO")), + + # Avoid any kind of content sniffing on the response. + (b"Content-Type", b"text/plain")] + + content = request.body + + return headers, content diff --git a/test/wpt/tests/service-workers/service-worker/resources/echo-cookie-worker.py b/test/wpt/tests/service-workers/service-worker/resources/echo-cookie-worker.py new file mode 100644 index 00000000000..561f64a35ad --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/echo-cookie-worker.py @@ -0,0 +1,24 @@ +def main(request, response): + headers = [(b"Content-Type", b"text/javascript")] + + values = [] + for key in request.cookies: + for cookie in request.cookies.get_list(key): + values.append(b'"%s": "%s"' % (key, cookie.value)) + + # Update the counter to change the script body for every request to trigger + # update of the service worker. + key = request.GET[b'key'] + counter = request.server.stash.take(key) + if counter is None: + counter = 0 + counter += 1 + request.server.stash.put(key, counter) + + body = b""" +// %d +self.addEventListener('message', e => { + e.source.postMessage({%s}) +});""" % (counter, b','.join(values)) + + return headers, body diff --git a/test/wpt/tests/service-workers/service-worker/resources/echo-message-to-source-worker.js b/test/wpt/tests/service-workers/service-worker/resources/echo-message-to-source-worker.js new file mode 100644 index 00000000000..bbbd35fb4f6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/echo-message-to-source-worker.js @@ -0,0 +1,3 @@ +addEventListener('message', evt => { + evt.source.postMessage(evt.data); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/embed-and-object-are-not-intercepted-worker.js b/test/wpt/tests/service-workers/service-worker/resources/embed-and-object-are-not-intercepted-worker.js new file mode 100644 index 00000000000..ffcdb751288 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/embed-and-object-are-not-intercepted-worker.js @@ -0,0 +1,14 @@ +// This worker intercepts a request for EMBED/OBJECT and responds with a +// response that indicates that interception occurred. The tests expect +// that interception does not occur. +self.addEventListener('fetch', e => { + if (e.request.url.indexOf('embedded-content-from-server.html') != -1) { + e.respondWith(fetch('embedded-content-from-service-worker.html')); + return; + } + + if (e.request.url.indexOf('green.png') != -1) { + e.respondWith(Promise.reject('network error to show interception occurred')); + return; + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/embed-image-is-not-intercepted-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/embed-image-is-not-intercepted-iframe.html new file mode 100644 index 00000000000..7b8b257203a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/embed-image-is-not-intercepted-iframe.html @@ -0,0 +1,21 @@ + + +iframe for embed-and-object-are-not-intercepted test + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html new file mode 100644 index 00000000000..39149915cc5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html @@ -0,0 +1,17 @@ + + +iframe for embed-and-object-are-not-intercepted test + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/embed-navigation-is-not-intercepted-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/embed-navigation-is-not-intercepted-iframe.html new file mode 100644 index 00000000000..5e86f67735f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/embed-navigation-is-not-intercepted-iframe.html @@ -0,0 +1,23 @@ + + +iframe for embed-and-object-are-not-intercepted test + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/embedded-content-from-server.html b/test/wpt/tests/service-workers/service-worker/resources/embedded-content-from-server.html new file mode 100644 index 00000000000..ff50a9c7526 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/embedded-content-from-server.html @@ -0,0 +1,6 @@ + + +embed for embed-and-object-are-not-intercepted test + diff --git a/test/wpt/tests/service-workers/service-worker/resources/embedded-content-from-service-worker.html b/test/wpt/tests/service-workers/service-worker/resources/embedded-content-from-service-worker.html new file mode 100644 index 00000000000..2e2b9236082 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/embedded-content-from-service-worker.html @@ -0,0 +1,7 @@ + + +embed for embed-and-object-are-not-intercepted test + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/empty-but-slow-worker.js b/test/wpt/tests/service-workers/service-worker/resources/empty-but-slow-worker.js new file mode 100644 index 00000000000..92abac7a384 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/empty-but-slow-worker.js @@ -0,0 +1,8 @@ +addEventListener('fetch', evt => { + if (evt.request.url.endsWith('slow')) { + // Performance.now() might be a bit better here, but Date.now() has + // better compat in workers right now. + let start = Date.now(); + while(Date.now() - start < 2000); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/empty-worker.js b/test/wpt/tests/service-workers/service-worker/resources/empty-worker.js new file mode 100644 index 00000000000..49ceb2648a9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/empty-worker.js @@ -0,0 +1 @@ +// Do nothing. diff --git a/test/wpt/tests/service-workers/service-worker/resources/empty.h2.js b/test/wpt/tests/service-workers/service-worker/resources/empty.h2.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/wpt/tests/service-workers/service-worker/resources/empty.html b/test/wpt/tests/service-workers/service-worker/resources/empty.html new file mode 100644 index 00000000000..6feb11946b8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/empty.html @@ -0,0 +1,6 @@ + + + +hello world + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/empty.js b/test/wpt/tests/service-workers/service-worker/resources/empty.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/wpt/tests/service-workers/service-worker/resources/enable-client-message-queue.html b/test/wpt/tests/service-workers/service-worker/resources/enable-client-message-queue.html new file mode 100644 index 00000000000..512bd14bc67 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/enable-client-message-queue.html @@ -0,0 +1,39 @@ + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/end-to-end-worker.js b/test/wpt/tests/service-workers/service-worker/resources/end-to-end-worker.js new file mode 100644 index 00000000000..d45a50556a9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/end-to-end-worker.js @@ -0,0 +1,7 @@ +onmessage = function(e) { + var message = e.data; + if (typeof message === 'object' && 'port' in message) { + var response = 'Ack for: ' + message.from; + message.port.postMessage(response); + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/events-worker.js b/test/wpt/tests/service-workers/service-worker/resources/events-worker.js new file mode 100644 index 00000000000..80a2188677b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/events-worker.js @@ -0,0 +1,12 @@ +var eventsSeen = []; + +function handler(event) { eventsSeen.push(event.type); } + +['activate', 'install'].forEach(function(type) { + self.addEventListener(type, handler); + }); + +onmessage = function(e) { + var message = e.data; + message.port.postMessage({events: eventsSeen}); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js b/test/wpt/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js new file mode 100644 index 00000000000..8a975b0d2e9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/extendable-event-async-waituntil.js @@ -0,0 +1,210 @@ +// This worker calls waitUntil() and respondWith() asynchronously and +// reports back to the test whether they threw. +// +// These test cases are confusing. Bear in mind that the event is active +// (calling waitUntil() is allowed) if: +// * The pending promise count is not 0, or +// * The event dispatch flag is set. + +// Controlled by 'init'/'done' messages. +var resolveLockPromise; +var port; + +self.addEventListener('message', function(event) { + var waitPromise; + var resolveTestPromise; + + switch (event.data.step) { + case 'init': + event.waitUntil(new Promise((res) => { resolveLockPromise = res; })); + port = event.data.port; + break; + case 'done': + resolveLockPromise(); + break; + + // Throws because waitUntil() is called in a task after event dispatch + // finishes. + case 'no-current-extension-different-task': + async_task_waituntil(event).then(reportResultExpecting('InvalidStateError')); + break; + + // OK because waitUntil() is called in a microtask that runs after the + // event handler runs, while the event dispatch flag is still set. + case 'no-current-extension-different-microtask': + async_microtask_waituntil(event).then(reportResultExpecting('OK')); + break; + + // OK because the second waitUntil() is called while the first waitUntil() + // promise is still pending. + case 'current-extension-different-task': + event.waitUntil(new Promise((res) => { resolveTestPromise = res; })); + async_task_waituntil(event).then(reportResultExpecting('OK')).then(resolveTestPromise); + break; + + // OK because all promises involved resolve "immediately", so the second + // waitUntil() is called during the microtask checkpoint at the end of + // event dispatching, when the event dispatch flag is still set. + case 'during-event-dispatch-current-extension-expired-same-microtask-turn': + waitPromise = Promise.resolve(); + event.waitUntil(waitPromise); + waitPromise.then(() => { return sync_waituntil(event); }) + .then(reportResultExpecting('OK')) + break; + + // OK for the same reason as above. + case 'during-event-dispatch-current-extension-expired-same-microtask-turn-extra': + waitPromise = Promise.resolve(); + event.waitUntil(waitPromise); + waitPromise.then(() => { return async_microtask_waituntil(event); }) + .then(reportResultExpecting('OK')) + break; + + + // OK because the pending promise count is decremented in a microtask + // queued upon fulfillment of the first waitUntil() promise, so the second + // waitUntil() is called while the pending promise count is still + // positive. + case 'after-event-dispatch-current-extension-expired-same-microtask-turn': + waitPromise = makeNewTaskPromise(); + event.waitUntil(waitPromise); + waitPromise.then(() => { return sync_waituntil(event); }) + .then(reportResultExpecting('OK')) + break; + + // Throws because the second waitUntil() is called after the pending + // promise count was decremented to 0. + case 'after-event-dispatch-current-extension-expired-same-microtask-turn-extra': + waitPromise = makeNewTaskPromise(); + event.waitUntil(waitPromise); + waitPromise.then(() => { return async_microtask_waituntil(event); }) + .then(reportResultExpecting('InvalidStateError')) + break; + + // Throws because the second waitUntil() is called in a new task, after + // first waitUntil() promise settled and the event dispatch flag is unset. + case 'current-extension-expired-different-task': + event.waitUntil(Promise.resolve()); + async_task_waituntil(event).then(reportResultExpecting('InvalidStateError')); + break; + + case 'script-extendable-event': + self.dispatchEvent(new ExtendableEvent('nontrustedevent')); + break; + } + + event.source.postMessage('ACK'); + }); + +self.addEventListener('fetch', function(event) { + const path = new URL(event.request.url).pathname; + const step = path.substring(path.lastIndexOf('/') + 1); + let response; + switch (step) { + // OK because waitUntil() is called while the respondWith() promise is still + // unsettled, so the pending promise count is positive. + case 'pending-respondwith-async-waituntil': + var resolveFetch; + response = new Promise((res) => { resolveFetch = res; }); + event.respondWith(response); + async_task_waituntil(event) + .then(reportResultExpecting('OK')) + .then(() => { resolveFetch(new Response('OK')); }); + break; + + // OK because all promises involved resolve "immediately", so waitUntil() is + // called during the microtask checkpoint at the end of event dispatching, + // when the event dispatch flag is still set. + case 'during-event-dispatch-respondwith-microtask-sync-waituntil': + response = Promise.resolve(new Response('RESP')); + event.respondWith(response); + response.then(() => { return sync_waituntil(event); }) + .then(reportResultExpecting('OK')); + break; + + // OK because all promises involved resolve "immediately", so waitUntil() is + // called during the microtask checkpoint at the end of event dispatching, + // when the event dispatch flag is still set. + case 'during-event-dispatch-respondwith-microtask-async-waituntil': + response = Promise.resolve(new Response('RESP')); + event.respondWith(response); + response.then(() => { return async_microtask_waituntil(event); }) + .then(reportResultExpecting('OK')); + break; + + // OK because the pending promise count is decremented in a microtask queued + // upon fulfillment of the respondWith() promise, so waitUntil() is called + // while the pending promise count is still positive. + case 'after-event-dispatch-respondwith-microtask-sync-waituntil': + response = makeNewTaskPromise().then(() => {return new Response('RESP');}); + event.respondWith(response); + response.then(() => { return sync_waituntil(event); }) + .then(reportResultExpecting('OK')); + break; + + + // Throws because waitUntil() is called after the pending promise count was + // decremented to 0. + case 'after-event-dispatch-respondwith-microtask-async-waituntil': + response = makeNewTaskPromise().then(() => {return new Response('RESP');}); + event.respondWith(response); + response.then(() => { return async_microtask_waituntil(event); }) + .then(reportResultExpecting('InvalidStateError')) + break; + } +}); + +self.addEventListener('nontrustedevent', function(event) { + sync_waituntil(event).then(reportResultExpecting('InvalidStateError')); + }); + +function reportResultExpecting(expectedResult) { + return function (result) { + port.postMessage({result : result, expected: expectedResult}); + return result; + }; +} + +function sync_waituntil(event) { + return new Promise((res, rej) => { + try { + event.waitUntil(Promise.resolve()); + res('OK'); + } catch (error) { + res(error.name); + } + }); +} + +function async_microtask_waituntil(event) { + return new Promise((res, rej) => { + Promise.resolve().then(() => { + try { + event.waitUntil(Promise.resolve()); + res('OK'); + } catch (error) { + res(error.name); + } + }); + }); +} + +function async_task_waituntil(event) { + return new Promise((res, rej) => { + setTimeout(() => { + try { + event.waitUntil(Promise.resolve()); + res('OK'); + } catch (error) { + res(error.name); + } + }, 0); + }); +} + +// Returns a promise that settles in a separate task. +function makeNewTaskPromise() { + return new Promise(resolve => { + setTimeout(resolve, 0); + }); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/extendable-event-waituntil.js b/test/wpt/tests/service-workers/service-worker/resources/extendable-event-waituntil.js new file mode 100644 index 00000000000..20a9eb023f6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/extendable-event-waituntil.js @@ -0,0 +1,87 @@ +var pendingPorts = []; +var portResolves = []; + +onmessage = function(e) { + var message = e.data; + if ('port' in message) { + var resolve = self.portResolves.shift(); + if (resolve) + resolve(message.port); + else + self.pendingPorts.push(message.port); + } +}; + +function fulfillPromise() { + return new Promise(function(resolve) { + // Make sure the oninstall/onactivate callback returns first. + Promise.resolve().then(function() { + var port = self.pendingPorts.shift(); + if (port) + resolve(port); + else + self.portResolves.push(resolve); + }); + }).then(function(port) { + port.postMessage('SYNC'); + return new Promise(function(resolve) { + port.onmessage = function(e) { + if (e.data == 'ACK') + resolve(); + }; + }); + }); +} + +function rejectPromise() { + return new Promise(function(resolve, reject) { + // Make sure the oninstall/onactivate callback returns first. + Promise.resolve().then(reject); + }); +} + +function stripScopeName(url) { + return url.split('/').slice(-1)[0]; +} + +oninstall = function(e) { + switch (stripScopeName(self.location.href)) { + case 'install-fulfilled': + e.waitUntil(fulfillPromise()); + break; + case 'install-rejected': + e.waitUntil(rejectPromise()); + break; + case 'install-multiple-fulfilled': + e.waitUntil(fulfillPromise()); + e.waitUntil(fulfillPromise()); + break; + case 'install-reject-precedence': + // Three "extend lifetime promises" are needed to verify that the user + // agent waits for all promises to settle even in the event of rejection. + // The first promise is fulfilled on demand by the client, the second is + // immediately scheduled for rejection, and the third is fulfilled on + // demand by the client (but only after the first promise has been + // fulfilled). + // + // User agents which simply expose `Promise.all` semantics in this case + // (by entering the "redundant state" following the rejection of the + // second promise but prior to the fulfillment of the third) can be + // identified from the client context. + e.waitUntil(fulfillPromise()); + e.waitUntil(rejectPromise()); + e.waitUntil(fulfillPromise()); + break; + } +}; + +onactivate = function(e) { + switch (stripScopeName(self.location.href)) { + case 'activate-fulfilled': + e.waitUntil(fulfillPromise()); + break; + case 'activate-rejected': + e.waitUntil(rejectPromise()); + break; + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js new file mode 100644 index 00000000000..517f289fbc8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fail-on-fetch-worker.js @@ -0,0 +1,5 @@ +importScripts('worker-testharness.js'); + +this.addEventListener('fetch', function(event) { + event.respondWith(new Response('ERROR')); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control-login.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control-login.html new file mode 100644 index 00000000000..ee296807ed1 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control-login.html @@ -0,0 +1,16 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control.py b/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control.py new file mode 100644 index 00000000000..446af87b249 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control.py @@ -0,0 +1,109 @@ +import json +import os +from base64 import decodebytes + +from wptserve.utils import isomorphic_decode, isomorphic_encode + +def main(request, response): + headers = [] + headers.append((b'X-ServiceWorker-ServerHeader', b'SetInTheServer')) + + if b"ACAOrigin" in request.GET: + for item in request.GET[b"ACAOrigin"].split(b","): + headers.append((b"Access-Control-Allow-Origin", item)) + + for suffix in [b"Headers", b"Methods", b"Credentials"]: + query = b"ACA%s" % suffix + header = b"Access-Control-Allow-%s" % suffix + if query in request.GET: + headers.append((header, request.GET[query])) + + if b"ACEHeaders" in request.GET: + headers.append((b"Access-Control-Expose-Headers", request.GET[b"ACEHeaders"])) + + if (b"Auth" in request.GET and not request.auth.username) or b"AuthFail" in request.GET: + status = 401 + headers.append((b'WWW-Authenticate', b'Basic realm="Restricted"')) + body = b'Authentication canceled' + return status, headers, body + + if b"PNGIMAGE" in request.GET: + headers.append((b"Content-Type", b"image/png")) + body = decodebytes(b"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1B" + b"AACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAhSURBVDhPY3wro/KfgQLABKXJBqMG" + b"jBoAAqMGDLwBDAwAEsoCTFWunmQAAAAASUVORK5CYII=") + return headers, body + + if b"VIDEO" in request.GET: + headers.append((b"Content-Type", b"video/ogg")) + body = open(os.path.join(request.doc_root, u"media", u"movie_5.ogv"), "rb").read() + length = len(body) + # If "PartialContent" is specified, the requestor wants to test range + # requests. For the initial request, respond with "206 Partial Content" + # and don't send the entire content. Then expect subsequent requests to + # have a "Range" header with a byte range. Respond with that range. + if b"PartialContent" in request.GET: + if length < 1: + return 500, headers, b"file is too small for range requests" + start = 0 + end = length - 1 + if b"Range" in request.headers: + range_header = request.headers[b"Range"] + prefix = b"bytes=" + split_header = range_header[len(prefix):].split(b"-") + # The first request might be "bytes=0-". We want to force a range + # request, so just return the first byte. + if split_header[0] == b"0" and split_header[1] == b"": + end = start + # Otherwise, it is a range request. Respect the values sent. + if split_header[0] != b"": + start = int(split_header[0]) + if split_header[1] != b"": + end = int(split_header[1]) + else: + # The request doesn't have a range. Force a range request by + # returning the first byte. + end = start + + headers.append((b"Accept-Ranges", b"bytes")) + headers.append((b"Content-Length", isomorphic_encode(str(end -start + 1)))) + headers.append((b"Content-Range", b"bytes %d-%d/%d" % (start, end, length))) + chunk = body[start:(end + 1)] + return 206, headers, chunk + return headers, body + + username = request.auth.username if request.auth.username else b"undefined" + password = request.auth.password if request.auth.username else b"undefined" + cookie = request.cookies[b'cookie'].value if b'cookie' in request.cookies else b"undefined" + + files = [] + for key, values in request.POST.items(): + assert len(values) == 1 + value = values[0] + if not hasattr(value, u"file"): + continue + data = value.file.read() + files.append({u"key": isomorphic_decode(key), + u"name": value.file.name, + u"type": value.type, + u"error": 0, #TODO, + u"size": len(data), + u"content": data}) + + get_data = {isomorphic_decode(key):isomorphic_decode(request.GET[key]) for key, value in request.GET.items()} + post_data = {isomorphic_decode(key):isomorphic_decode(request.POST[key]) for key, value in request.POST.items() + if not hasattr(request.POST[key], u"file")} + headers_data = {isomorphic_decode(key):isomorphic_decode(request.headers[key]) for key, value in request.headers.items()} + + data = {u"jsonpResult": u"success", + u"method": request.method, + u"headers": headers_data, + u"body": isomorphic_decode(request.body), + u"files": files, + u"GET": get_data, + u"POST": post_data, + u"username": isomorphic_decode(username), + u"password": isomorphic_decode(password), + u"cookie": isomorphic_decode(cookie)} + + return headers, u"report( %s )" % json.dumps(data) diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-canvas-tainting-double-write-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-canvas-tainting-double-write-worker.js new file mode 100644 index 00000000000..17723dcdda2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-canvas-tainting-double-write-worker.js @@ -0,0 +1,7 @@ +self.addEventListener('fetch', (event) => { + url = new URL(event.request.url); + if (url.search == '?PNGIMAGE') { + localUrl = new URL(url.pathname + url.search, self.location); + event.respondWith(fetch(localUrl)); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html new file mode 100644 index 00000000000..75d766c193a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-canvas-tainting-iframe.html @@ -0,0 +1,70 @@ + +iframe for fetch canvas tainting test + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-canvas-tainting-tests.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-canvas-tainting-tests.js new file mode 100644 index 00000000000..2aada3669ef --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-canvas-tainting-tests.js @@ -0,0 +1,241 @@ +// This is the main driver of the canvas tainting tests. +const NOT_TAINTED = 'NOT_TAINTED'; +const TAINTED = 'TAINTED'; +const LOAD_ERROR = 'LOAD_ERROR'; + +let frame; + +// Creates a single promise_test. +function canvas_taint_test(url, cross_origin, expected_result) { + promise_test(t => { + return frame.contentWindow.create_test_case_promise(url, cross_origin) + .then(result => { + assert_equals(result, expected_result); + }); + }, 'url "' + url + '" with crossOrigin "' + cross_origin + '" should be ' + + expected_result); +} + + +// Runs all the tests. The given |params| has these properties: +// * |resource_path|: the relative path to the (image/video) resource to test. +// * |cache|: when true, the service worker bounces responses into +// Cache Storage and back out before responding with them. +function do_canvas_tainting_tests(params) { + const host_info = get_host_info(); + let resource_path = params.resource_path; + if (params.cache) + resource_path += "&cache=true"; + const resource_url = host_info['HTTPS_ORIGIN'] + resource_path; + const remote_resource_url = host_info['HTTPS_REMOTE_ORIGIN'] + resource_path; + + // Set up the service worker and the frame. + promise_test(function(t) { + const SCOPE = 'resources/fetch-canvas-tainting-iframe.html'; + const SCRIPT = 'resources/fetch-rewrite-worker.js'; + const host_info = get_host_info(); + + // login_https() is needed because some test cases use credentials. + return login_https(t) + .then(function() { + return service_worker_unregister_and_register(t, SCRIPT, SCOPE); + }) + .then(function(registration) { + promise_test(() => { + if (frame) + frame.remove(); + return registration.unregister(); + }, 'restore global state'); + + return wait_for_state(t, registration.installing, 'activated'); + }) + .then(function() { return with_iframe(SCOPE); }) + .then(f => { + frame = f; + }); + }, 'initialize global state'); + + // Reject tests. Add '&reject' so the service worker responds with a rejected promise. + // A load error is expected. + canvas_taint_test(resource_url + '&reject', '', LOAD_ERROR); + canvas_taint_test(resource_url + '&reject', 'anonymous', LOAD_ERROR); + canvas_taint_test(resource_url + '&reject', 'use-credentials', LOAD_ERROR); + + // Fallback tests. Add '&ignore' so the service worker does not respond to the fetch + // request, and we fall back to network. + canvas_taint_test(resource_url + '&ignore', '', NOT_TAINTED); + canvas_taint_test(remote_resource_url + '&ignore', '', TAINTED); + canvas_taint_test(remote_resource_url + '&ignore', 'anonymous', LOAD_ERROR); + canvas_taint_test( + remote_resource_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ignore', + 'anonymous', + NOT_TAINTED); + canvas_taint_test(remote_resource_url + '&ignore', 'use-credentials', LOAD_ERROR); + canvas_taint_test( + remote_resource_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ignore', + 'use-credentials', + LOAD_ERROR); + canvas_taint_test( + remote_resource_url + '&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true&ignore', + 'use-credentials', + NOT_TAINTED); + + // Credential tests (with fallback). Add '&Auth' so the server requires authentication. + // Furthermore, add '&ignore' so the service worker falls back to network. + canvas_taint_test(resource_url + '&Auth&ignore', '', NOT_TAINTED); + canvas_taint_test(remote_resource_url + '&Auth&ignore', '', TAINTED); + canvas_taint_test( + remote_resource_url + '&Auth&ignore', 'anonymous', LOAD_ERROR); + canvas_taint_test( + remote_resource_url + '&Auth&ignore', + 'use-credentials', + LOAD_ERROR); + canvas_taint_test( + remote_resource_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ignore', + 'use-credentials', + LOAD_ERROR); + canvas_taint_test( + remote_resource_url + '&Auth&ACAOrigin=' + host_info['HTTPS_ORIGIN'] + + '&ACACredentials=true&ignore', + 'use-credentials', + NOT_TAINTED); + + // In the following tests, the service worker provides a response. + // Add '&url' so the service worker responds with fetch(url). + // Add '&mode' to configure the fetch request options. + + // Basic response tests. Set &url to the original url. + canvas_taint_test( + resource_url + '&mode=same-origin&url=' + encodeURIComponent(resource_url), + '', + NOT_TAINTED); + canvas_taint_test( + resource_url + '&mode=same-origin&url=' + encodeURIComponent(resource_url), + 'anonymous', + NOT_TAINTED); + canvas_taint_test( + resource_url + '&mode=same-origin&url=' + encodeURIComponent(resource_url), + 'use-credentials', + NOT_TAINTED); + canvas_taint_test( + remote_resource_url + '&mode=same-origin&url=' + + encodeURIComponent(resource_url), + '', + NOT_TAINTED); + canvas_taint_test( + remote_resource_url + '&mode=same-origin&url=' + + encodeURIComponent(resource_url), + 'anonymous', + NOT_TAINTED); + canvas_taint_test( + remote_resource_url + '&mode=same-origin&url=' + + encodeURIComponent(resource_url), + 'use-credentials', + NOT_TAINTED); + + // Opaque response tests. Set &url to the cross-origin URL, and &mode to + // 'no-cors' so we expect an opaque response. + canvas_taint_test( + resource_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url), + '', + TAINTED); + canvas_taint_test( + resource_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url), + 'anonymous', + LOAD_ERROR); + canvas_taint_test( + resource_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url), + 'use-credentials', + LOAD_ERROR); + canvas_taint_test( + remote_resource_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url), + '', + TAINTED); + canvas_taint_test( + remote_resource_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url), + 'anonymous', + LOAD_ERROR); + canvas_taint_test( + remote_resource_url + + '&mode=no-cors&url=' + encodeURIComponent(remote_resource_url), + 'use-credentials', + LOAD_ERROR); + + // CORS response tests. Set &url to the cross-origin URL, and &mode + // to 'cors' to attempt a CORS request. + canvas_taint_test( + resource_url + '&mode=cors&url=' + + encodeURIComponent(remote_resource_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + '', + LOAD_ERROR); // We expect LOAD_ERROR since the server doesn't respond + // with an Access-Control-Allow-Credentials header. + canvas_taint_test( + resource_url + '&mode=cors&credentials=same-origin&url=' + + encodeURIComponent(remote_resource_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + '', + NOT_TAINTED); + canvas_taint_test( + resource_url + '&mode=cors&url=' + + encodeURIComponent(remote_resource_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'anonymous', + NOT_TAINTED); + canvas_taint_test( + resource_url + '&mode=cors&url=' + + encodeURIComponent(remote_resource_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'use-credentials', + LOAD_ERROR); // We expect LOAD_ERROR since the server doesn't respond + // with an Access-Control-Allow-Credentials header. + canvas_taint_test( + resource_url + '&mode=cors&url=' + + encodeURIComponent( + remote_resource_url + + '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'use-credentials', + NOT_TAINTED); + canvas_taint_test( + remote_resource_url + '&mode=cors&url=' + + encodeURIComponent(remote_resource_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + '', + LOAD_ERROR); // We expect LOAD_ERROR since the server doesn't respond + // with an Access-Control-Allow-Credentials header. + canvas_taint_test( + remote_resource_url + '&mode=cors&credentials=same-origin&url=' + + encodeURIComponent(remote_resource_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + '', + NOT_TAINTED); + canvas_taint_test( + remote_resource_url + '&mode=cors&url=' + + encodeURIComponent(remote_resource_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'anonymous', + NOT_TAINTED); + canvas_taint_test( + remote_resource_url + '&mode=cors&url=' + + encodeURIComponent(remote_resource_url + + '&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'use-credentials', + LOAD_ERROR); // We expect LOAD_ERROR since the server doesn't respond + // with an Access-Control-Allow-Credentials header. + canvas_taint_test( + remote_resource_url + '&mode=cors&url=' + + encodeURIComponent( + remote_resource_url + + '&ACACredentials=true&ACAOrigin=' + host_info['HTTPS_ORIGIN']), + 'use-credentials', + NOT_TAINTED); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-cors-exposed-header-names-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-cors-exposed-header-names-worker.js new file mode 100644 index 00000000000..145952a22cf --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-cors-exposed-header-names-worker.js @@ -0,0 +1,3 @@ +self.addEventListener('fetch', (e) => { + e.respondWith(fetch(e.request)); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html new file mode 100644 index 00000000000..d88c5103d35 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html @@ -0,0 +1,170 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-csp-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-csp-iframe.html new file mode 100644 index 00000000000..33bf0416d58 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-csp-iframe.html @@ -0,0 +1,16 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers b/test/wpt/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers new file mode 100644 index 00000000000..5a1c7b941ac --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-csp-iframe.html.sub.headers @@ -0,0 +1 @@ +Content-Security-Policy: img-src https://{{host}}:{{ports[https][0]}}; connect-src 'unsafe-inline' 'self' diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-error-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-error-worker.js new file mode 100644 index 00000000000..788252cf3b3 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-error-worker.js @@ -0,0 +1,22 @@ +importScripts("/resources/testharness.js"); + +function doTest(event) +{ + if (!event.request.url.includes("fetch-error-test")) + return; + + let counter = 0; + const stream = new ReadableStream({ pull: controller => { + switch (++counter) { + case 1: + controller.enqueue(new Uint8Array([1])); + return; + default: + // We asynchronously error the stream so that there is ample time to resolve the fetch promise and call text() on the response. + step_timeout(() => controller.error("Sorry"), 50); + } + }}); + event.respondWith(new Response(stream)); +} + +self.addEventListener("fetch", doTest); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-add-async-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-add-async-worker.js new file mode 100644 index 00000000000..a5a44a57c99 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-add-async-worker.js @@ -0,0 +1,6 @@ +importScripts('/resources/testharness.js'); + +promise_test(async () => { + await new Promise(handler => { step_timeout(handler, 0); }); + self.addEventListener('fetch', () => {}); +}, 'fetch event added asynchronously does not throw'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html new file mode 100644 index 00000000000..bf8a6d5ce51 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-after-navigation-within-page-iframe.html @@ -0,0 +1,22 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js new file mode 100644 index 00000000000..dc3f1a1e985 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js @@ -0,0 +1,66 @@ +// This worker attempts to call respondWith() asynchronously after the +// fetch event handler finished. It reports back to the test whether +// an exception was thrown. + +// These get reset at the start of a test case. +let reportResult; + +// The test page sends a message to tell us that a new test case is starting. +// We expect a fetch event after this. +self.addEventListener('message', (event) => { + // Ensure tests run mutually exclusive. + if (reportResult) { + event.source.postMessage('testAlreadyRunning'); + return; + } + + const resultPromise = new Promise((resolve) => { + reportResult = resolve; + // Tell the client that everything is initialized and that it's safe to + // proceed with the test without relying on the order of events (which some + // browsers like Chrome may not guarantee). + event.source.postMessage('messageHandlerInitialized'); + }); + + // Keep the worker alive until the test case finishes, and report + // back the result to the test page. + event.waitUntil(resultPromise.then(result => { + reportResult = null; + event.source.postMessage(result); + })); +}); + +// Calls respondWith() and reports back whether an exception occurred. +function tryRespondWith(event) { + try { + event.respondWith(new Response()); + reportResult({didThrow: false}); + } catch (error) { + reportResult({didThrow: true, error: error.name}); + } +} + +function respondWithInTask(event) { + setTimeout(() => { + tryRespondWith(event); + }, 0); +} + +function respondWithInMicrotask(event) { + Promise.resolve().then(() => { + tryRespondWith(event); + }); +} + +self.addEventListener('fetch', function(event) { + const path = new URL(event.request.url).pathname; + const test = path.substring(path.lastIndexOf('/') + 1); + + // If this is a test case, try respondWith() and report back to the test page + // the result. + if (test == 'respondWith-in-task') { + respondWithInTask(event); + } else if (test == 'respondWith-in-microtask') { + respondWithInMicrotask(event); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-handled-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-handled-worker.js new file mode 100644 index 00000000000..53ee1493743 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-handled-worker.js @@ -0,0 +1,37 @@ +// This worker reports back the final state of FetchEvent.handled (RESOLVED or +// REJECTED) to the test. + +self.addEventListener('message', function(event) { + self.port = event.data.port; +}); + +self.addEventListener('fetch', function(event) { + try { + event.handled.then(() => { + self.port.postMessage('RESOLVED'); + }, () => { + self.port.postMessage('REJECTED'); + }); + } catch (e) { + self.port.postMessage('FAILED'); + return; + } + + const search = new URL(event.request.url).search; + switch (search) { + case '?respondWith-not-called': + break; + case '?respondWith-not-called-and-event-canceled': + event.preventDefault(); + break; + case '?respondWith-called-and-promise-resolved': + event.respondWith(Promise.resolve(new Response('body'))); + break; + case '?respondWith-called-and-promise-resolved-to-invalid-response': + event.respondWith(Promise.resolve('invalid response')); + break; + case '?respondWith-called-and-promise-rejected': + event.respondWith(Promise.reject(new Error('respondWith rejected'))); + break; + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html new file mode 100644 index 00000000000..f6c1919bbcf --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html @@ -0,0 +1,60 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js new file mode 100644 index 00000000000..5bfe3a0bbd9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-network-error-worker.js @@ -0,0 +1,49 @@ +// Test that multiple fetch handlers do not confuse the implementation. +self.addEventListener('fetch', function(event) {}); + +self.addEventListener('fetch', function(event) { + var testcase = new URL(event.request.url).search; + switch (testcase) { + case '?reject': + event.respondWith(Promise.reject()); + break; + case '?prevent-default': + event.preventDefault(); + break; + case '?prevent-default-and-respond-with': + event.preventDefault(); + break; + case '?unused-body': + event.respondWith(new Response('body')); + break; + case '?used-body': + var res = new Response('body'); + res.text(); + event.respondWith(res); + break; + case '?unused-fetched-body': + event.respondWith(fetch('other.html').then(function(res){ + return res; + })); + break; + case '?used-fetched-body': + event.respondWith(fetch('other.html').then(function(res){ + res.text(); + return res; + })); + break; + case '?throw-exception': + throw('boom'); + break; + } + }); + +self.addEventListener('fetch', function(event) {}); + +self.addEventListener('fetch', function(event) { + var testcase = new URL(event.request.url).search; + if (testcase == '?prevent-default-and-respond-with') + event.respondWith(new Response('responding!')); + }); + +self.addEventListener('fetch', function(event) {}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-network-fallback-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-network-fallback-worker.js new file mode 100644 index 00000000000..376bdbed05e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-network-fallback-worker.js @@ -0,0 +1,3 @@ +self.addEventListener('fetch', () => { + // Do nothing. +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-argument-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-argument-iframe.html new file mode 100644 index 00000000000..0ebd1ca8153 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-argument-iframe.html @@ -0,0 +1,55 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-argument-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-argument-worker.js new file mode 100644 index 00000000000..712c4b73c9b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-argument-worker.js @@ -0,0 +1,14 @@ +self.addEventListener('fetch', function(event) { + var testcase = new URL(event.request.url).search; + switch (testcase) { + case '?response-object': + event.respondWith(new Response('body')); + break; + case '?response-promise-object': + event.respondWith(Promise.resolve(new Response('body'))); + break; + case '?other-value': + event.respondWith(new Object()); + break; + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-body-loaded-in-chunk-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-body-loaded-in-chunk-worker.js new file mode 100644 index 00000000000..d3ba8a8df2e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-body-loaded-in-chunk-worker.js @@ -0,0 +1,7 @@ +'use strict'; + +self.addEventListener('fetch', event => { + if (!event.request.url.match(/body-in-chunk$/)) + return; + event.respondWith(fetch("../../../fetch/api/resources/trickle.py?count=4&delay=50")); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-custom-response-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-custom-response-worker.js new file mode 100644 index 00000000000..ff24aed1282 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-custom-response-worker.js @@ -0,0 +1,45 @@ +'use strict'; + +addEventListener('fetch', event => { + const url = new URL(event.request.url); + const type = url.searchParams.get('type'); + + if (!type) return; + + if (type === 'string') { + event.respondWith(new Response('PASS')); + } + else if (type === 'blob') { + event.respondWith( + new Response(new Blob(['PASS'])) + ); + } + else if (type === 'buffer-view') { + const encoder = new TextEncoder(); + event.respondWith( + new Response(encoder.encode('PASS')) + ); + } + else if (type === 'buffer') { + const encoder = new TextEncoder(); + event.respondWith( + new Response(encoder.encode('PASS').buffer) + ); + } + else if (type === 'form-data') { + const body = new FormData(); + body.set('result', 'PASS'); + event.respondWith( + new Response(body) + ); + } + else if (type === 'search-params') { + const body = new URLSearchParams(); + body.set('result', 'PASS'); + event.respondWith( + new Response(body, { + headers: { 'Content-Type': 'text/plain' } + }) + ); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-partial-stream-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-partial-stream-worker.js new file mode 100644 index 00000000000..b7307f29f57 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-partial-stream-worker.js @@ -0,0 +1,28 @@ +let waitUntilResolve; + +let bodyController; + +self.addEventListener('message', evt => { + if (evt.data === 'done') { + bodyController.close(); + waitUntilResolve(); + } +}); + +self.addEventListener('fetch', evt => { + if (!evt.request.url.includes('partial-stream.txt')) { + return; + } + + evt.waitUntil(new Promise(resolve => waitUntilResolve = resolve)); + + let body = new ReadableStream({ + start: controller => { + let encoder = new TextEncoder(); + controller.enqueue(encoder.encode('partial-stream-content')); + bodyController = controller; + }, + }); + + evt.respondWith(new Response(body)); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-chunk-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-chunk-worker.js new file mode 100644 index 00000000000..f954e3a18a5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-chunk-worker.js @@ -0,0 +1,40 @@ +'use strict'; + +self.addEventListener('fetch', event => { + if (!event.request.url.match(/body-stream$/)) + return; + + var counter = 0; + const encoder = new TextEncoder(); + const stream = new ReadableStream({ pull: controller => { + switch (++counter) { + case 1: + controller.enqueue(encoder.encode('')); + return; + case 2: + controller.enqueue(encoder.encode('chunk #1')); + return; + case 3: + controller.enqueue(encoder.encode(' ')); + return; + case 4: + controller.enqueue(encoder.encode('chunk #2')); + return; + case 5: + controller.enqueue(encoder.encode(' ')); + return; + case 6: + controller.enqueue(encoder.encode('chunk #3')); + return; + case 7: + controller.enqueue(encoder.encode(' ')); + return; + case 8: + controller.enqueue(encoder.encode('chunk #4')); + return; + default: + controller.close(); + } + }}); + event.respondWith(new Response(stream)); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-worker.js new file mode 100644 index 00000000000..056351322c6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-readable-stream-worker.js @@ -0,0 +1,81 @@ +'use strict'; +importScripts("/resources/testharness.js"); + +const map = new Map(); + +self.addEventListener('fetch', event => { + const url = new URL(event.request.url); + if (!url.searchParams.has('stream')) return; + + if (url.searchParams.has('observe-cancel')) { + const id = url.searchParams.get('id'); + if (id === undefined) { + event.respondWith(new Error('error')); + return; + } + event.waitUntil(new Promise(resolve => { + map.set(id, {label: 'pending', resolve}); + })); + + const stream = new ReadableStream({ + pull(c) { + if (url.searchParams.get('enqueue') === 'true') { + url.searchParams.delete('enqueue'); + c.enqueue(new Uint8Array([65])); + } + }, + cancel() { + map.get(id).label = 'cancelled'; + } + }); + event.respondWith(new Response(stream)); + return; + } + + if (url.searchParams.has('query-cancel')) { + const id = url.searchParams.get('id'); + if (id === undefined) { + event.respondWith(new Error('error')); + return; + } + const entry = map.get(id); + if (entry === undefined) { + event.respondWith(new Error('not found')); + return; + } + map.delete(id); + entry.resolve(); + event.respondWith(new Response(entry.label)); + return; + } + + if (url.searchParams.has('use-fetch-stream')) { + event.respondWith(async function() { + const response = await fetch('pass.txt'); + return new Response(response.body); + }()); + return; + } + + const delayEnqueue = url.searchParams.has('delay'); + + const stream = new ReadableStream({ + start(controller) { + const encoder = new TextEncoder(); + + const populate = () => { + controller.enqueue(encoder.encode('PASS')); + controller.close(); + } + + if (delayEnqueue) { + step_timeout(populate, 16); + } + else { + populate(); + } + } + }); + + event.respondWith(new Response(stream)); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-response-body-with-invalid-chunk-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-response-body-with-invalid-chunk-iframe.html new file mode 100644 index 00000000000..d15454daa5b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-response-body-with-invalid-chunk-iframe.html @@ -0,0 +1,15 @@ + + +respond-with-response-body-with-invalid-chunk + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-response-body-with-invalid-chunk-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-response-body-with-invalid-chunk-worker.js new file mode 100644 index 00000000000..0254e24f94a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-response-body-with-invalid-chunk-worker.js @@ -0,0 +1,12 @@ +'use strict'; + +self.addEventListener('fetch', event => { + if (!event.request.url.match(/body-stream-with-invalid-chunk$/)) + return; + const stream = new ReadableStream({start: controller => { + // The argument is intentionally a string, not a Uint8Array. + controller.enqueue('hello'); + }}); + const headers = { 'x-content-type-options': 'nosniff' }; + event.respondWith(new Response(stream, { headers })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js new file mode 100644 index 00000000000..18da049d69f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-respond-with-stops-propagation-worker.js @@ -0,0 +1,15 @@ +var result = null; + +self.addEventListener('message', function(event) { + event.data.port.postMessage(result); + }); + +self.addEventListener('fetch', function(event) { + if (!result) + result = 'PASS'; + event.respondWith(new Response()); + }); + +self.addEventListener('fetch', function(event) { + result = 'FAIL: fetch event propagated'; + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-test-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-test-worker.js new file mode 100644 index 00000000000..813f79d1b07 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-test-worker.js @@ -0,0 +1,224 @@ +function handleHeaders(event) { + const headers = Array.from(event.request.headers); + event.respondWith(new Response(JSON.stringify(headers))); +} + +function handleString(event) { + event.respondWith(new Response('Test string')); +} + +function handleBlob(event) { + event.respondWith(new Response(new Blob(['Test blob']))); +} + +function handleReferrer(event) { + event.respondWith(new Response(new Blob( + ['Referrer: ' + event.request.referrer]))); +} + +function handleReferrerPolicy(event) { + event.respondWith(new Response(new Blob( + ['ReferrerPolicy: ' + event.request.referrerPolicy]))); +} + +function handleReferrerFull(event) { + event.respondWith(new Response(new Blob( + ['Referrer: ' + event.request.referrer + '\n' + + 'ReferrerPolicy: ' + event.request.referrerPolicy]))); +} + +function handleClientId(event) { + var body; + if (event.clientId !== "") { + body = 'Client ID Found: ' + event.clientId; + } else { + body = 'Client ID Not Found'; + } + event.respondWith(new Response(body)); +} + +function handleResultingClientId(event) { + var body; + if (event.resultingClientId !== "") { + body = 'Resulting Client ID Found: ' + event.resultingClientId; + } else { + body = 'Resulting Client ID Not Found'; + } + event.respondWith(new Response(body)); +} + +function handleNullBody(event) { + event.respondWith(new Response()); +} + +function handleFetch(event) { + event.respondWith(fetch('other.html')); +} + +function handleFormPost(event) { + event.respondWith(new Promise(function(resolve) { + event.request.text() + .then(function(result) { + resolve(new Response(event.request.method + ':' + + event.request.headers.get('Content-Type') + ':' + + result)); + }); + })); +} + +function handleMultipleRespondWith(event) { + var logForMultipleRespondWith = ''; + for (var i = 0; i < 3; ++i) { + logForMultipleRespondWith += '(' + i + ')'; + try { + event.respondWith(new Promise(function(resolve) { + setTimeout(function() { + resolve(new Response(logForMultipleRespondWith)); + }, 0); + })); + } catch (e) { + logForMultipleRespondWith += '[' + e.name + ']'; + } + } +} + +var lastResponseForUsedCheck = undefined; + +function handleUsedCheck(event) { + if (!lastResponseForUsedCheck) { + event.respondWith(fetch('other.html').then(function(response) { + lastResponseForUsedCheck = response; + return response; + })); + } else { + event.respondWith(new Response( + 'bodyUsed: ' + lastResponseForUsedCheck.bodyUsed)); + } +} +function handleFragmentCheck(event) { + var body; + if (event.request.url.indexOf('#') === -1) { + body = 'Fragment Not Found'; + } else { + body = 'Fragment Found :' + + event.request.url.substring(event.request.url.indexOf('#')); + } + event.respondWith(new Response(body)); +} +function handleCache(event) { + event.respondWith(new Response(event.request.cache)); +} +function handleEventSource(event) { + if (event.request.mode === 'navigate') { + return; + } + var data = { + mode: event.request.mode, + cache: event.request.cache, + credentials: event.request.credentials + }; + var body = 'data:' + JSON.stringify(data) + '\n\n'; + event.respondWith(new Response(body, { + headers: { 'Content-Type': 'text/event-stream' } + } + )); +} + +function handleIntegrity(event) { + event.respondWith(new Response(event.request.integrity)); +} + +function handleRequestBody(event) { + event.respondWith(event.request.text().then(text => { + return new Response(text); + })); +} + +function handleKeepalive(event) { + event.respondWith(new Response(event.request.keepalive)); +} + +function handleIsReloadNavigation(event) { + const request = event.request; + const body = + `method = ${request.method}, ` + + `isReloadNavigation = ${request.isReloadNavigation}`; + event.respondWith(new Response(body)); +} + +function handleIsHistoryNavigation(event) { + const request = event.request; + const body = + `method = ${request.method}, ` + + `isHistoryNavigation = ${request.isHistoryNavigation}`; + event.respondWith(new Response(body)); +} + +function handleUseAndIgnore(event) { + const request = event.request; + request.text(); + return; +} + +function handleCloneAndIgnore(event) { + const request = event.request; + request.clone().text(); + return; +} + +var handle_status_count = 0; +function handleStatus(event) { + handle_status_count++; + event.respondWith(async function() { + const res = await fetch(event.request); + const text = await res.text(); + return new Response(`${text}. Request was sent ${handle_status_count} times.`, + {"status": new URL(event.request.url).searchParams.get("status")}); + }()); +} + +self.addEventListener('fetch', function(event) { + var url = event.request.url; + var handlers = [ + { pattern: '?headers', fn: handleHeaders }, + { pattern: '?string', fn: handleString }, + { pattern: '?blob', fn: handleBlob }, + { pattern: '?referrerFull', fn: handleReferrerFull }, + { pattern: '?referrerPolicy', fn: handleReferrerPolicy }, + { pattern: '?referrer', fn: handleReferrer }, + { pattern: '?clientId', fn: handleClientId }, + { pattern: '?resultingClientId', fn: handleResultingClientId }, + { pattern: '?ignore', fn: function() {} }, + { pattern: '?null', fn: handleNullBody }, + { pattern: '?fetch', fn: handleFetch }, + { pattern: '?form-post', fn: handleFormPost }, + { pattern: '?multiple-respond-with', fn: handleMultipleRespondWith }, + { pattern: '?used-check', fn: handleUsedCheck }, + { pattern: '?fragment-check', fn: handleFragmentCheck }, + { pattern: '?cache', fn: handleCache }, + { pattern: '?eventsource', fn: handleEventSource }, + { pattern: '?integrity', fn: handleIntegrity }, + { pattern: '?request-body', fn: handleRequestBody }, + { pattern: '?keepalive', fn: handleKeepalive }, + { pattern: '?isReloadNavigation', fn: handleIsReloadNavigation }, + { pattern: '?isHistoryNavigation', fn: handleIsHistoryNavigation }, + { pattern: '?use-and-ignore', fn: handleUseAndIgnore }, + { pattern: '?clone-and-ignore', fn: handleCloneAndIgnore }, + { pattern: '?status', fn: handleStatus }, + ]; + + var handler = null; + for (var i = 0; i < handlers.length; ++i) { + if (url.indexOf(handlers[i].pattern) != -1) { + handler = handlers[i]; + break; + } + } + + if (handler) { + handler.fn(event); + } else { + event.respondWith(new Response(new Blob( + ['Service Worker got an unexpected request: ' + url]))); + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-event-within-sw-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-within-sw-worker.js new file mode 100644 index 00000000000..5903bab968d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-event-within-sw-worker.js @@ -0,0 +1,48 @@ +skipWaiting(); + +addEventListener('fetch', event => { + const url = new URL(event.request.url); + + if (url.origin != location.origin) return; + + if (url.pathname.endsWith('/sample.txt')) { + event.respondWith(new Response('intercepted')); + return; + } + + if (url.pathname.endsWith('/sample.txt-inner-fetch')) { + event.respondWith(fetch('sample.txt')); + return; + } + + if (url.pathname.endsWith('/sample.txt-inner-cache')) { + event.respondWith( + caches.open('test-inner-cache').then(cache => + cache.add('sample.txt').then(() => cache.match('sample.txt')) + ) + ); + return; + } + + if (url.pathname.endsWith('/show-notification')) { + // Copy the currect search string onto the icon url + const iconURL = new URL('notification_icon.py', location); + iconURL.search = url.search; + + event.respondWith( + registration.showNotification('test', { + icon: iconURL + }).then(() => registration.getNotifications()).then(notifications => { + for (const n of notifications) n.close(); + return new Response('done'); + }) + ); + return; + } + + if (url.pathname.endsWith('/notification_icon.py')) { + new BroadcastChannel('icon-request').postMessage('yay'); + event.respondWith(new Response('done')); + return; + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html new file mode 100644 index 00000000000..0d9ab6ff904 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-header-visibility-iframe.html @@ -0,0 +1,66 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html new file mode 100644 index 00000000000..64a634e9db7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-inscope.html @@ -0,0 +1,71 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html new file mode 100644 index 00000000000..be0b5c8f561 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe-inscope-to-outscope.html @@ -0,0 +1,80 @@ + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html new file mode 100644 index 00000000000..2831c381b5e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-mixed-content-iframe.html @@ -0,0 +1,71 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html new file mode 100644 index 00000000000..504e1043564 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-base-url-iframe.html @@ -0,0 +1,20 @@ + + +iframe for css base url test + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css new file mode 100644 index 00000000000..f14fcaae727 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-base-url-style.css @@ -0,0 +1 @@ +body { background-image: url("./sample.png");} diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js new file mode 100644 index 00000000000..f3d6a73bdde --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-base-url-worker.js @@ -0,0 +1,45 @@ +let source; +let resolveDone; +let done = new Promise(resolve => resolveDone = resolve); + +// The page messages this worker to ask for the result. Keep the worker alive +// via waitUntil() until the result is sent. +self.addEventListener('message', event => { + source = event.data.port; + source.postMessage('pong'); + event.waitUntil(done); +}); + +self.addEventListener('fetch', event => { + const url = new URL(event.request.url); + + // For the CSS file, respond in a way that may change the response URL, + // depending on |url.search|. + const cssPath = 'request-url-path/fetch-request-css-base-url-style.css'; + if (url.pathname.indexOf(cssPath) != -1) { + // Respond with a different URL, deleting "request-url-path/". + if (url.search == '?fetch') { + event.respondWith(fetch('fetch-request-css-base-url-style.css?fetch')); + } + // Respond with new Response(). + else if (url.search == '?newResponse') { + const styleString = 'body { background-image: url("./sample.png");}'; + const headers = {'content-type': 'text/css'}; + event.respondWith(new Response(styleString, headers)); + } + } + + // The image request indicates what the base URL of the CSS was. Message the + // result back to the test page. + else if (url.pathname.indexOf('sample.png') != -1) { + // For some reason |source| is undefined here when running the test manually + // in Firefox. The test author experimented with both using Client + // (event.source) and MessagePort to try to get the test to pass, but + // failed. + source.postMessage({ + url: event.request.url, + referrer: event.request.referrer + }); + resolveDone(); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-cross.css b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-cross.css new file mode 100644 index 00000000000..9a7545d0702 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-cross.css @@ -0,0 +1 @@ +#crossOriginCss { color: blue; } diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-cross.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-cross.html new file mode 100644 index 00000000000..3211f78084d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-cross.html @@ -0,0 +1 @@ +#crossOriginHtml { color: red; } diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-iframe.html new file mode 100644 index 00000000000..9a4adedb84d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-iframe.html @@ -0,0 +1,17 @@ + + + + + + +

I should be blue

+

I should be blue

+

I should be blue

+

I should be blue

+

I should be blue

diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-same.css b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-same.css new file mode 100644 index 00000000000..55455bd5da5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-same.css @@ -0,0 +1 @@ +#sameOriginCss { color: blue; } diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-same.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-same.html new file mode 100644 index 00000000000..6fad4b9ff04 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-mime-check-same.html @@ -0,0 +1 @@ +#sameOriginHtml { color: blue; } diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-read-contents.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-read-contents.html new file mode 100644 index 00000000000..c902366b023 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-read-contents.html @@ -0,0 +1,15 @@ + + +iframe: cross-origin CSS via service worker + + + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-worker.js new file mode 100644 index 00000000000..a71e91216c1 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-css-cross-origin-worker.js @@ -0,0 +1,65 @@ +importScripts('/common/get-host-info.sub.js'); +importScripts('test-helpers.sub.js'); + +const HOST_INFO = get_host_info(); +const REMOTE_ORIGIN = HOST_INFO.HTTPS_REMOTE_ORIGIN; +const BASE_PATH = base_path(); +const CSS_FILE = 'fetch-request-css-cross-origin-mime-check-cross.css'; +const HTML_FILE = 'fetch-request-css-cross-origin-mime-check-cross.html'; + +function add_pipe_header(url_str, header) { + if (url_str.indexOf('?pipe=') == -1) { + url_str += '?pipe='; + } else { + url_str += '|'; + } + url_str += `header${header}`; + return url_str; +} + +self.addEventListener('fetch', function(event) { + const url = new URL(event.request.url); + + const use_mime = + (url.searchParams.get('mime') != 'no'); + const mime_header = '(Content-Type, text/css)'; + + const use_cors = + (url.searchParams.has('cors')); + const cors_header = '(Access-Control-Allow-Origin, *)'; + + const file = url.pathname.substring(url.pathname.lastIndexOf('/') + 1); + + // Respond with a cross-origin CSS resource, using CORS if desired. + if (file == 'cross-origin-css.css') { + let fetch_url = REMOTE_ORIGIN + BASE_PATH + CSS_FILE; + if (use_mime) + fetch_url = add_pipe_header(fetch_url, mime_header); + if (use_cors) + fetch_url = add_pipe_header(fetch_url, cors_header); + const mode = use_cors ? 'cors' : 'no-cors'; + event.respondWith(fetch(fetch_url, {'mode': mode})); + return; + } + + // Respond with a cross-origin CSS resource with an HTML name. This is only + // used in the MIME sniffing test, so MIME is never added. + if (file == 'cross-origin-html.css') { + const fetch_url = REMOTE_ORIGIN + BASE_PATH + HTML_FILE; + event.respondWith(fetch(fetch_url, {mode: 'no-cors'})); + return; + } + + // Respond with synthetic CSS. + if (file == 'synthetic.css') { + let headers = {}; + if (use_mime) { + headers['Content-Type'] = 'text/css'; + } + + event.respondWith(new Response("#synthetic { color: blue; }", {headers})); + return; + } + + // Otherwise, fallback to network. + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html new file mode 100644 index 00000000000..d117d0f55ef --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-fallback-iframe.html @@ -0,0 +1,32 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js new file mode 100644 index 00000000000..3b028b24bde --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-fallback-worker.js @@ -0,0 +1,13 @@ +var requests = []; + +self.addEventListener('message', function(event) { + event.data.port.postMessage({requests: requests}); + requests = []; + }); + +self.addEventListener('fetch', function(event) { + requests.push({ + url: event.request.url, + mode: event.request.mode + }); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-html-imports-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-html-imports-iframe.html new file mode 100644 index 00000000000..07a084257a8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-html-imports-iframe.html @@ -0,0 +1,13 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-html-imports-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-html-imports-worker.js new file mode 100644 index 00000000000..110727bd52c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-html-imports-worker.js @@ -0,0 +1,30 @@ +importScripts('/common/get-host-info.sub.js'); +var host_info = get_host_info(); + +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('sample-dir') == -1) { + return; + } + var result = 'mode=' + event.request.mode + + ' credentials=' + event.request.credentials; + if (url == host_info.HTTPS_ORIGIN + '/sample-dir/same.html') { + event.respondWith(new Response( + result + + '' + + '')); + } else if (url == host_info.HTTPS_REMOTE_ORIGIN + '/sample-dir/other.html') { + event.respondWith(new Response( + result + + '' + + '')); + } else { + event.respondWith(new Response(result)); + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html new file mode 100644 index 00000000000..e6e9380ba62 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-iframe.html @@ -0,0 +1 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-script.py b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-script.py new file mode 100644 index 00000000000..bf8df154a88 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-script.py @@ -0,0 +1,6 @@ +def main(request, response): + headers = [] + # Sets an ETag header to check the cache revalidation behavior. + headers.append((b"ETag", b"abc123")) + headers.append((b"Content-Type", b"text/javascript")) + return headers, b"/* empty script */" diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js new file mode 100644 index 00000000000..2bd59d73922 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-no-freshness-headers-worker.js @@ -0,0 +1,18 @@ +var requests = []; + +self.addEventListener('message', function(event) { + event.data.port.postMessage({requests: requests}); + }); + +self.addEventListener('fetch', function(event) { + var url = event.request.url; + var headers = []; + for (var header of event.request.headers) { + headers.push(header); + } + requests.push({ + url: url, + headers: headers + }); + event.respondWith(fetch(event.request)); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html new file mode 100644 index 00000000000..ffd76bfc499 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-redirect-iframe.html @@ -0,0 +1,35 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html new file mode 100644 index 00000000000..86e9f4bb359 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-resources-iframe.https.html @@ -0,0 +1,87 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js new file mode 100644 index 00000000000..983cccb8db7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-resources-worker.js @@ -0,0 +1,26 @@ +const requests = []; +let port = undefined; + +self.onmessage = e => { + const message = e.data; + if ('port' in message) { + port = message.port; + port.postMessage({ready: true}); + } +}; + +self.addEventListener('fetch', e => { + const url = e.request.url; + if (!url.includes('sample?test')) { + return; + } + port.postMessage({ + url: url, + mode: e.request.mode, + redirect: e.request.redirect, + credentials: e.request.credentials, + integrity: e.request.integrity, + destination: e.request.destination + }); + e.respondWith(Promise.reject()); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html new file mode 100644 index 00000000000..b3ddec1a701 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html @@ -0,0 +1,208 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-error-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-error-worker.js new file mode 100644 index 00000000000..b8d3db99bcc --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-error-worker.js @@ -0,0 +1,19 @@ +"use strict"; + +self.onfetch = event => { + if (event.request.url.endsWith("non-existent-stream-1.txt")) { + const rs1 = new ReadableStream(); + event.respondWith(new Response(rs1)); + rs1.cancel(1); + } else if (event.request.url.endsWith("non-existent-stream-2.txt")) { + const rs2 = new ReadableStream({ + start(controller) { controller.error(1) } + }); + event.respondWith(new Response(rs2)); + } else if (event.request.url.endsWith("non-existent-stream-3.txt")) { + const rs3 = new ReadableStream({ + pull(controller) { controller.error(1) } + }); + event.respondWith(new Response(rs3)); + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-iframe.html new file mode 100644 index 00000000000..900762ffc6c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-iframe.html @@ -0,0 +1,13 @@ + +Service Worker: Synchronous XHR is intercepted iframe + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-on-worker-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-on-worker-worker.js new file mode 100644 index 00000000000..0d24ffc1f33 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-on-worker-worker.js @@ -0,0 +1,41 @@ +'use strict'; + +self.onfetch = function(event) { + if (event.request.url.indexOf('non-existent-file.txt') !== -1) { + event.respondWith(new Response('Response from service worker')); + } else if (event.request.url.indexOf('/iframe_page') !== -1) { + event.respondWith(new Response( + '\n' + + '', + { + headers: [['content-type', 'text/html']] + })); + } else if (event.request.url.indexOf('/worker_script') !== -1) { + event.respondWith(new Response( + 'self.onmessage = (msg) => {' + + ' const syncXhr = new XMLHttpRequest();' + + ' syncXhr.open(\'GET\', msg.data.url, false);' + + ' syncXhr.send();' + + ' self.postMessage({' + + ' status: syncXhr.status,' + + ' responseText: syncXhr.responseText' + + ' });' + + '}', + { + headers: [['content-type', 'application/javascript']] + })); + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-worker.js new file mode 100644 index 00000000000..070e572f400 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-sync-worker.js @@ -0,0 +1,7 @@ +'use strict'; + +self.onfetch = function(event) { + if (event.request.url.indexOf('non-existent-file.txt') !== -1) { + event.respondWith(new Response('Response from service worker')); + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js new file mode 100644 index 00000000000..4e428374bc2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-request-xhr-worker.js @@ -0,0 +1,22 @@ +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('sample?test') == -1) { + return; + } + event.respondWith(new Promise(function(resolve) { + var headers = []; + for (var header of event.request.headers) { + headers.push(header); + } + event.request.text() + .then(function(result) { + resolve(new Response(JSON.stringify({ + method: event.request.method, + mode: event.request.mode, + credentials: event.request.credentials, + headers: headers, + body: result + }))); + }); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-response-taint-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-response-taint-iframe.html new file mode 100644 index 00000000000..5f09efe28df --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-response-taint-iframe.html @@ -0,0 +1,2 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html new file mode 100644 index 00000000000..c26eebee49d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-response-xhr-iframe.https.html @@ -0,0 +1,53 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js new file mode 100644 index 00000000000..0301b12c18a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-response-xhr-worker.js @@ -0,0 +1,12 @@ +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('sample?test') == -1) { + return; + } + event.respondWith(new Promise(function(resolve) { + var headers = new Headers; + headers.append('foo', 'foo'); + headers.append('foo', 'bar'); + resolve(new Response('hello world', {'headers': headers})); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-response.html b/test/wpt/tests/service-workers/service-worker/resources/fetch-response.html new file mode 100644 index 00000000000..6d27cf19e56 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-response.html @@ -0,0 +1,29 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-response.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-response.js new file mode 100644 index 00000000000..775efc0bbd2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-response.js @@ -0,0 +1,35 @@ +self.addEventListener('fetch', event => { + const path = event.request.url.match(/\/(?[^\/]+)$/); + switch (path?.groups?.name) { + case 'constructed': + event.respondWith(new Response(new Uint8Array([1, 2, 3]))); + break; + case 'forward': + event.respondWith(fetch('/common/text-plain.txt')); + break; + case 'stream': + event.respondWith((async() => { + const res = await fetch('/common/text-plain.txt'); + const body = await res.body; + const reader = await body.getReader(); + const stream = new ReadableStream({ + async start(controller) { + while (true) { + const {done, value} = await reader.read(); + if (done) + break; + + controller.enqueue(value); + } + controller.close(); + reader.releaseLock(); + } + }); + return new Response(stream); + })()); + break; + default: + event.respondWith(fetch(event.request)); + break; + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker-referrer-policy.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker-referrer-policy.js new file mode 100644 index 00000000000..64c99c95d86 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker-referrer-policy.js @@ -0,0 +1,4 @@ +// This script is intended to be served with the `Referrer-Policy` header as +// defined in the corresponding `.headers` file. + +importScripts('fetch-rewrite-worker.js'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker-referrer-policy.js.headers b/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker-referrer-policy.js.headers new file mode 100644 index 00000000000..5ae4265418e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker-referrer-policy.js.headers @@ -0,0 +1,2 @@ +Content-Type: application/javascript +Referrer-Policy: origin diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js new file mode 100644 index 00000000000..20a80665270 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js @@ -0,0 +1,166 @@ +// By default, this worker responds to fetch events with +// respondWith(fetch(request)). Additionally, if the request has a &url +// parameter, it fetches the provided URL instead. Because it forwards fetch +// events to this other URL, it is called the "fetch rewrite" worker. +// +// The worker also looks for other params on the request to do more custom +// behavior, like falling back to network or throwing an error. + +function get_query_params(url) { + var search = (new URL(url)).search; + if (!search) { + return {}; + } + var ret = {}; + var params = search.substring(1).split('&'); + params.forEach(function(param) { + var element = param.split('='); + ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]); + }); + return ret; +} + +function get_request_init(base, params) { + var init = {}; + init['method'] = params['method'] || base['method']; + init['mode'] = params['mode'] || base['mode']; + if (init['mode'] == 'navigate') { + init['mode'] = 'same-origin'; + } + init['credentials'] = params['credentials'] || base['credentials']; + init['redirect'] = params['redirect-mode'] || base['redirect']; + return init; +} + +self.addEventListener('fetch', function(event) { + var params = get_query_params(event.request.url); + var init = get_request_init(event.request, params); + var url = params['url']; + if (params['ignore']) { + return; + } + if (params['throw']) { + throw new Error('boom'); + } + if (params['reject']) { + event.respondWith(new Promise(function(resolve, reject) { + reject(); + })); + return; + } + if (params['resolve-null']) { + event.respondWith(new Promise(function(resolve) { + resolve(null); + })); + return; + } + if (params['generate-png']) { + var binary = atob( + 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAA' + + 'RnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAhSURBVDhPY3wro/Kf' + + 'gQLABKXJBqMGjBoAAqMGDLwBDAwAEsoCTFWunmQAAAAASUVORK5CYII='); + var array = new Uint8Array(binary.length); + for(var i = 0; i < binary.length; i++) { + array[i] = binary.charCodeAt(i); + }; + event.respondWith(new Response(new Blob([array], {type: 'image/png'}))); + return; + } + if (params['check-ua-header']) { + var ua = event.request.headers.get('User-Agent'); + if (ua) { + // We have a user agent! + event.respondWith(new Response(new Blob([ua]))); + } else { + // We don't have a user-agent! + event.respondWith(new Response(new Blob(["NO_UA"]))); + } + return; + } + if (params['check-accept-header']) { + var accept = event.request.headers.get('Accept'); + if (accept) { + event.respondWith(new Response(accept)); + } else { + event.respondWith(new Response('NO_ACCEPT')); + } + return; + } + event.respondWith(new Promise(function(resolve, reject) { + var request = event.request; + if (url) { + request = new Request(url, init); + } else if (params['change-request']) { + request = new Request(request, init); + } + const response_promise = params['navpreload'] ? event.preloadResponse + : fetch(request); + response_promise.then(function(response) { + var expectedType = params['expected_type']; + if (expectedType && response.type !== expectedType) { + // Resolve a JSON object with a failure instead of rejecting + // in order to distinguish this from a NetworkError, which + // may be expected even if the type is correct. + resolve(new Response(JSON.stringify({ + result: 'failure', + detail: 'got ' + response.type + ' Response.type instead of ' + + expectedType + }))); + } + + var expectedRedirected = params['expected_redirected']; + if (typeof expectedRedirected !== 'undefined') { + var expected_redirected = (expectedRedirected === 'true'); + if(response.redirected !== expected_redirected) { + // This is simply determining how to pass an error to the outer + // test case(fetch-request-redirect.https.html). + var execptedResolves = params['expected_resolves']; + if (execptedResolves === 'true') { + // Reject a JSON object with a failure since promise is expected + // to be resolved. + reject(new Response(JSON.stringify({ + result: 'failure', + detail: 'got '+ response.redirected + + ' Response.redirected instead of ' + + expectedRedirected + }))); + } else { + // Resolve a JSON object with a failure since promise is + // expected to be rejected. + resolve(new Response(JSON.stringify({ + result: 'failure', + detail: 'got '+ response.redirected + + ' Response.redirected instead of ' + + expectedRedirected + }))); + } + } + } + + if (params['clone']) { + response = response.clone(); + } + + // |cache| means to bounce responses through Cache Storage and back. + if (params['cache']) { + var cacheName = "cached-fetches-" + performance.now() + "-" + + event.request.url; + var cache; + var cachedResponse; + return self.caches.open(cacheName).then(function(opened) { + cache = opened; + return cache.put(request, response); + }).then(function() { + return cache.match(request); + }).then(function(cached) { + cachedResponse = cached; + return self.caches.delete(cacheName); + }).then(function() { + resolve(cachedResponse); + }); + } else { + resolve(response); + } + }, reject) + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js.headers b/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js.headers new file mode 100644 index 00000000000..123053b38c6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js.headers @@ -0,0 +1,2 @@ +Content-Type: text/javascript +Service-Worker-Allowed: / diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-variants-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-variants-worker.js new file mode 100644 index 00000000000..b950b9a18a6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-variants-worker.js @@ -0,0 +1,35 @@ +importScripts('/common/get-host-info.sub.js'); +importScripts('test-helpers.sub.js'); +importScripts('/resources/testharness.js'); + +const storedResponse = new Response(new Blob(['a simple text file'])) +const absolultePath = `${base_path()}/simple.txt` + +self.addEventListener('fetch', event => { + const search = new URLSearchParams(new URL(event.request.url).search.substr(1)) + const variant = search.get('variant') + const delay = search.get('delay') + if (!variant) + return + + switch (variant) { + case 'forward': + event.respondWith(fetch(event.request.url)) + break + case 'redirect': + event.respondWith(fetch(`/xhr/resources/redirect.py?location=${base_path()}/simple.txt`)) + break + case 'delay-before-fetch': + event.respondWith( + new Promise(resolve => { + step_timeout(() => fetch(event.request.url).then(resolve), delay) + })) + break + case 'delay-after-fetch': + event.respondWith(new Promise(resolve => { + fetch(event.request.url) + .then(response => step_timeout(() => resolve(response), delay)) + })) + break + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js b/test/wpt/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js new file mode 100644 index 00000000000..92a96ff88fb --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js @@ -0,0 +1,31 @@ +var activatePromiseResolve; + +addEventListener('activate', function(evt) { + evt.waitUntil(new Promise(function(resolve) { + activatePromiseResolve = resolve; + })); +}); + +addEventListener('message', async function(evt) { + switch (evt.data) { + case 'CLAIM': + evt.waitUntil(new Promise(async resolve => { + await clients.claim(); + evt.source.postMessage('CLAIMED'); + resolve(); + })); + break; + case 'ACTIVATE': + if (typeof activatePromiseResolve !== 'function') { + throw new Error('Not activating!'); + } + activatePromiseResolve(); + break; + default: + throw new Error('Unknown message!'); + } +}); + +addEventListener('fetch', function(evt) { + evt.respondWith(new Response('Hello world')); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/form-poster.html b/test/wpt/tests/service-workers/service-worker/resources/form-poster.html new file mode 100644 index 00000000000..cd11a30a5e8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/form-poster.html @@ -0,0 +1,13 @@ + + + +
+ diff --git a/test/wpt/tests/service-workers/service-worker/resources/frame-for-getregistrations.html b/test/wpt/tests/service-workers/service-worker/resources/frame-for-getregistrations.html new file mode 100644 index 00000000000..7fc35f18914 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/frame-for-getregistrations.html @@ -0,0 +1,19 @@ + +Service Worker: frame for getRegistrations() + diff --git a/test/wpt/tests/service-workers/service-worker/resources/get-resultingClientId-worker.js b/test/wpt/tests/service-workers/service-worker/resources/get-resultingClientId-worker.js new file mode 100644 index 00000000000..f0e6c7becab --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/get-resultingClientId-worker.js @@ -0,0 +1,107 @@ +// This worker expects a fetch event for a navigation and messages back the +// result of clients.get(event.resultingClientId). + +// Resolves when the test finishes. +let testFinishPromise; +let resolveTestFinishPromise; +let rejectTestFinishPromise; + +// Resolves to clients.get(event.resultingClientId) from the fetch event. +let getPromise; +let resolveGetPromise; +let rejectGetPromise; + +let resultingClientId; + +function startTest() { + testFinishPromise = new Promise((resolve, reject) => { + resolveTestFinishPromise = resolve; + rejectTestFinishPromise = reject; + }); + + getPromise = new Promise((resolve, reject) => { + resolveGetPromise = resolve; + rejectGetPromise = reject; + }); +} + +async function describeGetPromiseResult(promise) { + const result = {}; + + await promise.then( + (client) => { + result.promiseState = 'fulfilled'; + if (client === undefined) { + result.promiseValue = 'undefinedValue'; + } else if (client instanceof Client) { + result.promiseValue = 'client'; + result.client = { + id: client.id, + url: client.url + }; + } else { + result.promiseValue = 'unknown'; + } + }, + (error) => { + result.promiseState = 'rejected'; + }); + + return result; +} + +async function handleGetResultingClient(event) { + // Note that this message can arrive before |resultingClientId| is populated. + const result = await describeGetPromiseResult(getPromise); + // |resultingClientId| must be populated by now. + result.queriedId = resultingClientId; + event.source.postMessage(result); +}; + +async function handleGetClient(event) { + const id = event.data.id; + const result = await describeGetPromiseResult(self.clients.get(id)); + result.queriedId = id; + event.source.postMessage(result); +}; + +self.addEventListener('message', (event) => { + if (event.data.command == 'startTest') { + startTest(); + event.waitUntil(testFinishPromise); + event.source.postMessage('ok'); + return; + } + + if (event.data.command == 'finishTest') { + resolveTestFinishPromise(); + event.source.postMessage('ok'); + return; + } + + if (event.data.command == 'getResultingClient') { + event.waitUntil(handleGetResultingClient(event)); + return; + } + + if (event.data.command == 'getClient') { + event.waitUntil(handleGetClient(event)); + return; + } +}); + +async function handleFetch(event) { + try { + resultingClientId = event.resultingClientId; + const client = await self.clients.get(resultingClientId); + resolveGetPromise(client); + } catch (error) { + rejectGetPromise(error); + } +} + +self.addEventListener('fetch', (event) => { + if (event.request.mode != 'navigate') + return; + event.waitUntil(handleFetch(event)); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/http-to-https-redirect-and-register-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/http-to-https-redirect-and-register-iframe.html new file mode 100644 index 00000000000..bcab35364df --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/http-to-https-redirect-and-register-iframe.html @@ -0,0 +1,25 @@ + +register, unregister, and report result to opener + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/iframe-with-fetch-variants.html b/test/wpt/tests/service-workers/service-worker/resources/iframe-with-fetch-variants.html new file mode 100644 index 00000000000..3a61d7bb890 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/iframe-with-fetch-variants.html @@ -0,0 +1,14 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/iframe-with-image.html b/test/wpt/tests/service-workers/service-worker/resources/iframe-with-image.html new file mode 100644 index 00000000000..ce78840cb28 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/iframe-with-image.html @@ -0,0 +1,2 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/immutable-prototype-serviceworker.js b/test/wpt/tests/service-workers/service-worker/resources/immutable-prototype-serviceworker.js new file mode 100644 index 00000000000..d8a94ad46be --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/immutable-prototype-serviceworker.js @@ -0,0 +1,19 @@ +function prototypeChain(global) { + let result = []; + while (global !== null) { + let thrown = false; + let next = Object.getPrototypeOf(global); + try { + Object.setPrototypeOf(global, {}); + result.push('mutable'); + } catch (e) { + result.push('immutable'); + } + global = next; + } + return result; +} + +self.onmessage = function(e) { + e.data.postMessage(prototypeChain(self)); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-echo-cookie-worker-module.py b/test/wpt/tests/service-workers/service-worker/resources/import-echo-cookie-worker-module.py new file mode 100644 index 00000000000..8f0b68e5a3a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-echo-cookie-worker-module.py @@ -0,0 +1,6 @@ +def main(request, response): + # This script generates a worker script for static imports from module + # service workers. + headers = [(b'Content-Type', b'text/javascript')] + body = b"import './echo-cookie-worker.py?key=%s'" % request.GET[b'key'] + return headers, body diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-echo-cookie-worker.js b/test/wpt/tests/service-workers/service-worker/resources/import-echo-cookie-worker.js new file mode 100644 index 00000000000..f5eac9508c9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-echo-cookie-worker.js @@ -0,0 +1 @@ +importScripts(`echo-cookie-worker.py${location.search}`); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-mime-type-worker.py b/test/wpt/tests/service-workers/service-worker/resources/import-mime-type-worker.py new file mode 100644 index 00000000000..b6e82f31d37 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-mime-type-worker.py @@ -0,0 +1,10 @@ +def main(request, response): + if b'mime' in request.GET: + return ( + [(b'Content-Type', b'application/javascript')], + b"importScripts('./mime-type-worker.py?mime=%s');" % request.GET[b'mime'] + ) + return ( + [(b'Content-Type', b'application/javascript')], + b"importScripts('./mime-type-worker.py');" + ) diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-relative.xsl b/test/wpt/tests/service-workers/service-worker/resources/import-relative.xsl new file mode 100644 index 00000000000..063a62d0314 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-relative.xsl @@ -0,0 +1,5 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-404-after-update-plus-update-worker.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-404-after-update-plus-update-worker.js new file mode 100644 index 00000000000..e9899d8e727 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-404-after-update-plus-update-worker.js @@ -0,0 +1,8 @@ +// This worker imports a script that returns 200 on the first request and 404 +// on the second request, and a script that is updated every time when +// requesting it. +const params = new URLSearchParams(location.search); +const key = params.get('Key'); +const additional_key = params.get('AdditionalKey'); +importScripts(`update-worker.py?Key=${key}&Mode=not_found`, + `update-worker.py?Key=${additional_key}&Mode=normal`); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-404-after-update.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-404-after-update.js new file mode 100644 index 00000000000..b569346035a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-404-after-update.js @@ -0,0 +1,6 @@ +// This worker imports a script that returns 200 on the first request and 404 +// on the second request. The resulting body also changes each time it is +// requested. +const params = new URLSearchParams(location.search); +const key = params.get('Key'); +importScripts(`update-worker.py?Key=${key}&Mode=not_found`); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-404.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-404.js new file mode 100644 index 00000000000..19c7a4b8e56 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-404.js @@ -0,0 +1 @@ +importScripts('404.py'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-cross-origin-worker.sub.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-cross-origin-worker.sub.js new file mode 100644 index 00000000000..b432854db8b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-cross-origin-worker.sub.js @@ -0,0 +1 @@ +importScripts('https://{{domains[www1]}}:{{ports[https][0]}}/service-workers/service-worker/resources/import-scripts-version.py'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-data-url-worker.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-data-url-worker.js new file mode 100644 index 00000000000..fdabdafc630 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-data-url-worker.js @@ -0,0 +1 @@ +importScripts('data:text/javascript,'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-diff-resource-map-worker.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-diff-resource-map-worker.js new file mode 100644 index 00000000000..0fdcb0fcf80 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-diff-resource-map-worker.js @@ -0,0 +1,10 @@ +importScripts('/resources/testharness.js'); + +let echo1 = null; +let echo2 = null; +let arg1 = 'import-scripts-get.py?output=echo1&msg=test1'; +let arg2 = 'import-scripts-get.py?output=echo2&msg=test2'; + +importScripts(arg1, arg2); +assert_equals(echo1, 'test1'); +assert_equals(echo2, 'test2'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-echo.py b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-echo.py new file mode 100644 index 00000000000..d38d660e659 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-echo.py @@ -0,0 +1,6 @@ +def main(req, res): + return ([ + (b'Cache-Control', b'no-cache, must-revalidate'), + (b'Pragma', b'no-cache'), + (b'Content-Type', b'application/javascript')], + b'echo_output = "%s";\n' % req.GET[b'msg']) diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-get.py b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-get.py new file mode 100644 index 00000000000..ab7b84e3e34 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-get.py @@ -0,0 +1,6 @@ +def main(req, res): + return ([ + (b'Cache-Control', b'no-cache, must-revalidate'), + (b'Pragma', b'no-cache'), + (b'Content-Type', b'application/javascript')], + b'%s = "%s";\n' % (req.GET[b'output'], req.GET[b'msg'])) diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js new file mode 100644 index 00000000000..d4f1f3e26d8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-mime-types-worker.js @@ -0,0 +1,49 @@ +const badMimeTypes = [ + null, // no MIME type + 'text/plain', +]; + +const validMimeTypes = [ + 'application/ecmascript', + 'application/javascript', + 'application/x-ecmascript', + 'application/x-javascript', + 'text/ecmascript', + 'text/javascript', + 'text/javascript1.0', + 'text/javascript1.1', + 'text/javascript1.2', + 'text/javascript1.3', + 'text/javascript1.4', + 'text/javascript1.5', + 'text/jscript', + 'text/livescript', + 'text/x-ecmascript', + 'text/x-javascript', +]; + +function importScriptsWithMimeType(mimeType) { + importScripts(`./mime-type-worker.py${mimeType ? '?mime=' + mimeType : ''}`); +} + +importScripts('/resources/testharness.js'); + +for (const mimeType of badMimeTypes) { + test(() => { + assert_throws_dom( + 'NetworkError', + () => { importScriptsWithMimeType(mimeType); }, + `importScripts with ${mimeType ? 'bad' : 'no'} MIME type ${mimeType || ''} throws NetworkError`, + ); + }, `Importing script with ${mimeType ? 'bad' : 'no'} MIME type ${mimeType || ''}`); +} + +for (const mimeType of validMimeTypes) { + test(() => { + try { + importScriptsWithMimeType(mimeType); + } catch { + assert_unreached(`importScripts with MIME type ${mimeType} should not throw`); + } + }, `Importing script with valid JavaScript MIME type ${mimeType}`); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-redirect-import.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-redirect-import.js new file mode 100644 index 00000000000..56c04f09460 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-redirect-import.js @@ -0,0 +1 @@ +// empty script diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-redirect-on-second-time-worker.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-redirect-on-second-time-worker.js new file mode 100644 index 00000000000..f612ab8e6aa --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-redirect-on-second-time-worker.js @@ -0,0 +1,7 @@ +// This worker imports a script that returns 200 on the first request and a +// redirect on the second request. The resulting body also changes each time it +// is requested. +const params = new URLSearchParams(location.search); +const key = params.get('Key'); +importScripts(`update-worker.py?Key=${key}&Mode=redirect&` + + `Redirect=update-worker.py?Key=${key}%26Mode=normal`); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-redirect-worker.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-redirect-worker.js new file mode 100644 index 00000000000..d02a45349c2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-redirect-worker.js @@ -0,0 +1 @@ +importScripts('redirect.py?Redirect=import-scripts-version.py'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-resource-map-worker.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-resource-map-worker.js new file mode 100644 index 00000000000..b3b9bc46a02 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-resource-map-worker.js @@ -0,0 +1,15 @@ +importScripts('/resources/testharness.js'); + +let version = null; +importScripts('import-scripts-version.py'); +// Once imported, the stored script should be loaded for subsequent importScripts. +const expected_version = version; + +version = null; +importScripts('import-scripts-version.py'); +assert_equals(expected_version, version, 'second import'); + +version = null; +importScripts('import-scripts-version.py', 'import-scripts-version.py', + 'import-scripts-version.py'); +assert_equals(expected_version, version, 'multiple imports'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-updated-flag-worker.js b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-updated-flag-worker.js new file mode 100644 index 00000000000..e01664662ef --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-updated-flag-worker.js @@ -0,0 +1,31 @@ +importScripts('/resources/testharness.js'); + +let echo_output = null; + +// Tests importing a script that sets |echo_output| to the query string. +function test_import(str) { + echo_output = null; + importScripts('import-scripts-echo.py?msg=' + str); + assert_equals(echo_output, str); +} + +test_import('root'); +test_import('root-and-message'); + +self.addEventListener('install', () => { + test_import('install'); + test_import('install-and-message'); + }); + +self.addEventListener('message', e => { + var error = null; + echo_output = null; + + try { + importScripts('import-scripts-echo.py?msg=' + e.data); + } catch (e) { + error = e && e.name; + } + + e.source.postMessage({ error: error, value: echo_output }); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/import-scripts-version.py b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-version.py new file mode 100644 index 00000000000..cde28544e60 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/import-scripts-version.py @@ -0,0 +1,17 @@ +import datetime +import time + +epoch = datetime.datetime(1970, 1, 1) + +def main(req, res): + # Artificially delay response time in order to ensure uniqueness of + # computed value + time.sleep(0.1) + + now = (datetime.datetime.now() - epoch).total_seconds() + + return ([ + (b'Cache-Control', b'no-cache, must-revalidate'), + (b'Pragma', b'no-cache'), + (b'Content-Type', b'application/javascript')], + u'version = "%s";\n' % now) diff --git a/test/wpt/tests/service-workers/service-worker/resources/imported-classic-script.js b/test/wpt/tests/service-workers/service-worker/resources/imported-classic-script.js new file mode 100644 index 00000000000..5fc52040513 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/imported-classic-script.js @@ -0,0 +1 @@ +const imported = 'A classic script.'; diff --git a/test/wpt/tests/service-workers/service-worker/resources/imported-module-script.js b/test/wpt/tests/service-workers/service-worker/resources/imported-module-script.js new file mode 100644 index 00000000000..56d196df040 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/imported-module-script.js @@ -0,0 +1 @@ +export const imported = 'A module script.'; diff --git a/test/wpt/tests/service-workers/service-worker/resources/indexeddb-worker.js b/test/wpt/tests/service-workers/service-worker/resources/indexeddb-worker.js new file mode 100644 index 00000000000..9add4768388 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/indexeddb-worker.js @@ -0,0 +1,57 @@ +self.addEventListener('message', function(e) { + var message = e.data; + if (message.action === 'create') { + e.waitUntil(deleteDB() + .then(doIndexedDBTest) + .then(function() { + message.port.postMessage({ type: 'created' }); + }) + .catch(function(reason) { + message.port.postMessage({ type: 'error', value: reason }); + })); + } else if (message.action === 'cleanup') { + e.waitUntil(deleteDB() + .then(function() { + message.port.postMessage({ type: 'done' }); + }) + .catch(function(reason) { + message.port.postMessage({ type: 'error', value: reason }); + })); + } + }); + +function deleteDB() { + return new Promise(function(resolve, reject) { + var delete_request = indexedDB.deleteDatabase('db'); + + delete_request.onsuccess = resolve; + delete_request.onerror = reject; + }); +} + +function doIndexedDBTest(port) { + return new Promise(function(resolve, reject) { + var open_request = indexedDB.open('db'); + + open_request.onerror = reject; + open_request.onupgradeneeded = function() { + var db = open_request.result; + db.createObjectStore('store'); + }; + open_request.onsuccess = function() { + var db = open_request.result; + var tx = db.transaction('store', 'readwrite'); + var store = tx.objectStore('store'); + store.put('value', 'key'); + + tx.onerror = function() { + db.close(); + reject(tx.error); + }; + tx.oncomplete = function() { + db.close(); + resolve(); + }; + }; + }); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/install-event-type-worker.js b/test/wpt/tests/service-workers/service-worker/resources/install-event-type-worker.js new file mode 100644 index 00000000000..1c94ae21ea9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/install-event-type-worker.js @@ -0,0 +1,9 @@ +importScripts('worker-testharness.js'); + +self.oninstall = function(event) { + assert_true(event instanceof ExtendableEvent, 'instance of ExtendableEvent'); + assert_true(event instanceof InstallEvent, 'instance of InstallEvent'); + assert_equals(event.type, 'install', '`type` property value'); + assert_false(event.cancelable, '`cancelable` property value'); + assert_false(event.bubbles, '`bubbles` property value'); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/install-worker.html b/test/wpt/tests/service-workers/service-worker/resources/install-worker.html new file mode 100644 index 00000000000..ed20cd4dca6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/install-worker.html @@ -0,0 +1,22 @@ + + + +

Loading...

+ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/interface-requirements-worker.sub.js b/test/wpt/tests/service-workers/service-worker/resources/interface-requirements-worker.sub.js new file mode 100644 index 00000000000..a3f239b6548 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/interface-requirements-worker.sub.js @@ -0,0 +1,59 @@ +'use strict'; + +// This file checks additional interface requirements, on top of the basic IDL +// that is validated in service-workers/idlharness.any.js + +importScripts('/resources/testharness.js'); + +test(function() { + var req = new Request('http://{{host}}/', + {method: 'POST', + headers: [['Content-Type', 'Text/Html']]}); + assert_equals( + new ExtendableEvent('ExtendableEvent').type, + 'ExtendableEvent', 'Type of ExtendableEvent should be ExtendableEvent'); + assert_throws_js(TypeError, function() { + new FetchEvent('FetchEvent'); + }, 'FetchEvent constructor with one argument throws'); + assert_throws_js(TypeError, function() { + new FetchEvent('FetchEvent', {}); + }, 'FetchEvent constructor with empty init dict throws'); + assert_throws_js(TypeError, function() { + new FetchEvent('FetchEvent', {request: null}); + }, 'FetchEvent constructor with null request member throws'); + assert_equals( + new FetchEvent('FetchEvent', {request: req}).type, + 'FetchEvent', 'Type of FetchEvent should be FetchEvent'); + assert_equals( + new FetchEvent('FetchEvent', {request: req}).cancelable, + false, 'Default FetchEvent.cancelable should be false'); + assert_equals( + new FetchEvent('FetchEvent', {request: req}).bubbles, + false, 'Default FetchEvent.bubbles should be false'); + assert_equals( + new FetchEvent('FetchEvent', {request: req}).clientId, + '', 'Default FetchEvent.clientId should be the empty string'); + assert_equals( + new FetchEvent('FetchEvent', {request: req, cancelable: false}).cancelable, + false, 'FetchEvent.cancelable should be false'); + assert_equals( + new FetchEvent('FetchEvent', {request: req, clientId : 'test-client-id'}).clientId, 'test-client-id', + 'FetchEvent.clientId with option {clientId : "test-client-id"} should be "test-client-id"'); + assert_equals( + new FetchEvent('FetchEvent', {request : req}).request.url, + 'http://{{host}}/', + 'FetchEvent.request.url should return the value it was initialized to'); + assert_equals( + new FetchEvent('FetchEvent', {request : req}).isReload, + undefined, + 'FetchEvent.isReload should not exist'); + + }, 'Event constructors'); + +test(() => { + assert_false('XMLHttpRequest' in self); + }, 'xhr is not exposed'); + +test(() => { + assert_false('createObjectURL' in self.URL); + }, 'URL.createObjectURL is not exposed') diff --git a/test/wpt/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html b/test/wpt/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html new file mode 100644 index 00000000000..04a9cb515e0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/invalid-blobtype-iframe.https.html @@ -0,0 +1,28 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js b/test/wpt/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js new file mode 100644 index 00000000000..865dc30d428 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js @@ -0,0 +1,10 @@ +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('sample?test') == -1) { + return; + } + event.respondWith(new Promise(function(resolve) { + // null byte in blob type + resolve(new Response(new Blob([],{type: 'a\0b'}))); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py b/test/wpt/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py new file mode 100644 index 00000000000..05977c6ab0b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/invalid-chunked-encoding-with-flush.py @@ -0,0 +1,9 @@ +import time +def main(request, response): + response.headers.set(b"Content-Type", b"application/javascript") + response.headers.set(b"Transfer-encoding", b"chunked") + response.write_status_headers() + + time.sleep(1) + + response.writer.write(b"XX\r\n\r\n") diff --git a/test/wpt/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py b/test/wpt/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py new file mode 100644 index 00000000000..a8edd06b8dc --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/invalid-chunked-encoding.py @@ -0,0 +1,2 @@ +def main(request, response): + return [(b"Content-Type", b"application/javascript"), (b"Transfer-encoding", b"chunked")], b"XX\r\n\r\n" diff --git a/test/wpt/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html b/test/wpt/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html new file mode 100644 index 00000000000..8f0e6baca17 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/invalid-header-iframe.https.html @@ -0,0 +1,25 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/invalid-header-worker.js b/test/wpt/tests/service-workers/service-worker/resources/invalid-header-worker.js new file mode 100644 index 00000000000..850874b8116 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/invalid-header-worker.js @@ -0,0 +1,12 @@ +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('sample?test') == -1) { + return; + } + event.respondWith(new Promise(function(resolve) { + var headers = new Headers; + headers.append('foo', 'foo'); + headers.append('foo', 'b\0r'); // header value with a null byte + resolve(new Response('hello world', {'headers': headers})); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/iso-latin1-header-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/iso-latin1-header-iframe.html new file mode 100644 index 00000000000..cf2fa8d14f7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/iso-latin1-header-iframe.html @@ -0,0 +1,23 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/iso-latin1-header-worker.js b/test/wpt/tests/service-workers/service-worker/resources/iso-latin1-header-worker.js new file mode 100644 index 00000000000..d9ecca277b3 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/iso-latin1-header-worker.js @@ -0,0 +1,12 @@ +self.addEventListener('fetch', function(event) { + var url = event.request.url; + if (url.indexOf('sample?test') == -1) { + return; + } + + event.respondWith(new Promise(function(resolve) { + var headers = new Headers; + headers.append('TEST', 'ßÀ¿'); // header value holds the Latin1 (ISO8859-1) string. + resolve(new Response('hello world', {'headers': headers})); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/load_worker.js b/test/wpt/tests/service-workers/service-worker/resources/load_worker.js new file mode 100644 index 00000000000..18c673bebca --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/load_worker.js @@ -0,0 +1,29 @@ +function run_test(data, sender) { + if (data === 'xhr') { + const xhr = new XMLHttpRequest(); + xhr.open('GET', 'synthesized-response.txt', true); + xhr.responseType = 'text'; + xhr.send(); + xhr.onload = evt => sender.postMessage(xhr.responseText); + xhr.onerror = () => sender.postMessage('XHR failed!'); + } else if (data === 'fetch') { + fetch('synthesized-response.txt') + .then(response => response.text()) + .then(data => sender.postMessage(data)) + .catch(error => sender.postMessage('Fetch failed!')); + } else if (data === 'importScripts') { + importScripts('synthesized-response.js'); + // |message| is provided by 'synthesized-response.js'; + sender.postMessage(message); + } else { + sender.postMessage('Unexpected message! ' + data); + } +} + +// Entry point for dedicated workers. +self.onmessage = evt => run_test(evt.data, self); + +// Entry point for shared workers. +self.onconnect = evt => { + evt.ports[0].onmessage = e => run_test(e.data, evt.ports[0]); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/loaded.html b/test/wpt/tests/service-workers/service-worker/resources/loaded.html new file mode 100644 index 00000000000..0cabce69f8e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/loaded.html @@ -0,0 +1,9 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/local-url-inherit-controller-frame.html b/test/wpt/tests/service-workers/service-worker/resources/local-url-inherit-controller-frame.html new file mode 100644 index 00000000000..b1e554d2204 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/local-url-inherit-controller-frame.html @@ -0,0 +1,130 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/local-url-inherit-controller-worker.js b/test/wpt/tests/service-workers/service-worker/resources/local-url-inherit-controller-worker.js new file mode 100644 index 00000000000..4b7aad0f58a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/local-url-inherit-controller-worker.js @@ -0,0 +1,5 @@ +addEventListener('fetch', evt => { + if (evt.request.url.includes('sample')) { + evt.respondWith(new Response('intercepted')); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/location-setter.html b/test/wpt/tests/service-workers/service-worker/resources/location-setter.html new file mode 100644 index 00000000000..f0ced06ec26 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/location-setter.html @@ -0,0 +1,10 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/malformed-http-response.asis b/test/wpt/tests/service-workers/service-worker/resources/malformed-http-response.asis new file mode 100644 index 00000000000..bc3c68d46d5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/malformed-http-response.asis @@ -0,0 +1 @@ +HAHAHA THIS IS NOT HTTP AND THE BROWSER SHOULD CONSIDER IT A NETWORK ERROR diff --git a/test/wpt/tests/service-workers/service-worker/resources/malformed-worker.py b/test/wpt/tests/service-workers/service-worker/resources/malformed-worker.py new file mode 100644 index 00000000000..319b6e277ba --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/malformed-worker.py @@ -0,0 +1,14 @@ +def main(request, response): + headers = [(b"Content-Type", b"application/javascript")] + + body = {u'parse-error': u'var foo = function() {;', + u'undefined-error': u'foo.bar = 42;', + u'uncaught-exception': u'throw new DOMException("AbortError");', + u'caught-exception': u'try { throw new Error; } catch(e) {}', + u'import-malformed-script': u'importScripts("malformed-worker.py?parse-error");', + u'import-no-such-script': u'importScripts("no-such-script.js");', + u'top-level-await': u'await Promise.resolve(1);', + u'instantiation-error': u'import nonexistent from "./imported-module-script.js";', + u'instantiation-error-and-top-level-await': u'import nonexistent from "./imported-module-script.js"; await Promise.resolve(1);'}[request.url_parts.query] + + return headers, body diff --git a/test/wpt/tests/service-workers/service-worker/resources/message-vs-microtask.html b/test/wpt/tests/service-workers/service-worker/resources/message-vs-microtask.html new file mode 100644 index 00000000000..2c45c59a475 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/message-vs-microtask.html @@ -0,0 +1,18 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/mime-sniffing-worker.js b/test/wpt/tests/service-workers/service-worker/resources/mime-sniffing-worker.js new file mode 100644 index 00000000000..5c34a7a49e8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/mime-sniffing-worker.js @@ -0,0 +1,9 @@ +self.addEventListener('fetch', function(event) { + // Use an empty content-type value to force mime-sniffing. Note, this + // must be passed to the constructor since the mime-type of the Response + // is fixed and cannot be later changed. + var res = new Response('\n

test

', { + headers: { 'content-type': '' } + }); + event.respondWith(res); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/mime-type-worker.py b/test/wpt/tests/service-workers/service-worker/resources/mime-type-worker.py new file mode 100644 index 00000000000..92a602e634c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/mime-type-worker.py @@ -0,0 +1,4 @@ +def main(request, response): + if b'mime' in request.GET: + return [(b'Content-Type', request.GET[b'mime'])], b"" + return [], b"" diff --git a/test/wpt/tests/service-workers/service-worker/resources/mint-new-worker.py b/test/wpt/tests/service-workers/service-worker/resources/mint-new-worker.py new file mode 100644 index 00000000000..ebee4ff8e8d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/mint-new-worker.py @@ -0,0 +1,27 @@ +import random + +import time + +body = u''' +onactivate = (e) => e.waitUntil(clients.claim()); +var resolve_wait_until; +var wait_until = new Promise(resolve => { + resolve_wait_until = resolve; + }); +onmessage = (e) => { + if (e.data == 'wait') + e.waitUntil(wait_until); + if (e.data == 'go') + resolve_wait_until(); + };''' + +def main(request, response): + headers = [(b'Cache-Control', b'no-cache, must-revalidate'), + (b'Pragma', b'no-cache'), + (b'Content-Type', b'application/javascript')] + + skipWaiting = u'' + if b'skip-waiting' in request.GET: + skipWaiting = u'skipWaiting();' + + return headers, u'/* %s %s */ %s %s' % (time.time(), random.random(), skipWaiting, body) diff --git a/test/wpt/tests/service-workers/service-worker/resources/module-worker.js b/test/wpt/tests/service-workers/service-worker/resources/module-worker.js new file mode 100644 index 00000000000..385fe710150 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/module-worker.js @@ -0,0 +1 @@ +import * as module from './imported-module-script.js'; diff --git a/test/wpt/tests/service-workers/service-worker/resources/multipart-image-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/multipart-image-iframe.html new file mode 100644 index 00000000000..c59b95594ff --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/multipart-image-iframe.html @@ -0,0 +1,19 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/multipart-image-worker.js b/test/wpt/tests/service-workers/service-worker/resources/multipart-image-worker.js new file mode 100644 index 00000000000..a38fe54d34f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/multipart-image-worker.js @@ -0,0 +1,21 @@ +importScripts('/common/get-host-info.sub.js'); +importScripts('test-helpers.sub.js'); + +const host_info = get_host_info(); + +const multipart_image_path = base_path() + 'multipart-image.py'; +const sameorigin_url = host_info['HTTPS_ORIGIN'] + multipart_image_path; +const cross_origin_url = host_info['HTTPS_REMOTE_ORIGIN'] + multipart_image_path; + +self.addEventListener('fetch', event => { + const url = event.request.url; + if (url.indexOf('cross-origin-multipart-image-with-no-cors') >= 0) { + event.respondWith(fetch(cross_origin_url, {mode: 'no-cors'})); + } else if (url.indexOf('cross-origin-multipart-image-with-cors-rejected') >= 0) { + event.respondWith(fetch(cross_origin_url, {mode: 'cors'})); + } else if (url.indexOf('cross-origin-multipart-image-with-cors-approved') >= 0) { + event.respondWith(fetch(cross_origin_url + '?approvecors', {mode: 'cors'})); + } else if (url.indexOf('same-origin-multipart-image') >= 0) { + event.respondWith(fetch(sameorigin_url)); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/multipart-image.py b/test/wpt/tests/service-workers/service-worker/resources/multipart-image.py new file mode 100644 index 00000000000..9a3c035f492 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/multipart-image.py @@ -0,0 +1,23 @@ +# A request handler that serves a multipart image. + +import os + + +BOUNDARY = b'cutHere' + + +def create_part(path): + with open(path, u'rb') as f: + return b'Content-Type: image/png\r\n\r\n' + f.read() + b'--%s' % BOUNDARY + + +def main(request, response): + content_type = b'multipart/x-mixed-replace; boundary=%s' % BOUNDARY + headers = [(b'Content-Type', content_type)] + if b'approvecors' in request.GET: + headers.append((b'Access-Control-Allow-Origin', b'*')) + + image_path = os.path.join(request.doc_root, u'images') + body = create_part(os.path.join(image_path, u'red.png')) + body = body + create_part(os.path.join(image_path, u'red-16x16.png')) + return headers, body diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigate-window-worker.js b/test/wpt/tests/service-workers/service-worker/resources/navigate-window-worker.js new file mode 100644 index 00000000000..f9617439fc6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigate-window-worker.js @@ -0,0 +1,21 @@ +addEventListener('message', function(evt) { + if (evt.data.type === 'GET_CLIENTS') { + clients.matchAll(evt.data.opts).then(function(clientList) { + var resultList = clientList.map(function(c) { + return { url: c.url, frameType: c.frameType, id: c.id }; + }); + evt.source.postMessage({ type: 'success', detail: resultList }); + }).catch(function(err) { + evt.source.postMessage({ + type: 'failure', + detail: 'matchAll() rejected with "' + err + '"' + }); + }); + return; + } + + evt.source.postMessage({ + type: 'failure', + detail: 'Unexpected message type "' + evt.data.type + '"' + }); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-headers-server.py b/test/wpt/tests/service-workers/service-worker/resources/navigation-headers-server.py new file mode 100644 index 00000000000..5b2e044f8b5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-headers-server.py @@ -0,0 +1,19 @@ +def main(request, response): + response.status = (200, b"OK") + response.headers.set(b"Content-Type", b"text/html") + return b""" + """ % (request.headers.get( + b"origin", b"not set"), request.headers.get(b"referer", b"not set"), + request.headers.get(b"sec-fetch-site", b"not set"), + request.headers.get(b"sec-fetch-mode", b"not set"), + request.headers.get(b"sec-fetch-dest", b"not set")) diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-body-worker.js b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-body-worker.js new file mode 100644 index 00000000000..39f11baf8cb --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-body-worker.js @@ -0,0 +1,11 @@ +self.addEventListener('fetch', function(event) { + event.respondWith( + fetch(event.request) + .then( + function(response) { + return response; + }, + function(error) { + return new Response('Error:' + error); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-body.py b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-body.py new file mode 100644 index 00000000000..d10329e7836 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-body.py @@ -0,0 +1,11 @@ +import os + +from wptserve.utils import isomorphic_encode + +filename = os.path.basename(isomorphic_encode(__file__)) + +def main(request, response): + if request.method == u'POST': + return 302, [(b'Location', b'./%s?redirect' % filename)], b'' + + return [(b'Content-Type', b'text/plain')], request.request_path diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html new file mode 100644 index 00000000000..d82571d1a3c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-other-origin.html @@ -0,0 +1,89 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py new file mode 100644 index 00000000000..9b90b146955 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-out-scope.py @@ -0,0 +1,22 @@ +def main(request, response): + if b"url" in request.GET: + headers = [(b"Location", request.GET[b"url"])] + return 302, headers, b'' + + status = 200 + + if b"noLocationRedirect" in request.GET: + status = 302 + + return status, [(b"content-type", b"text/html")], b''' + + +''' diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py new file mode 100644 index 00000000000..9b90b146955 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-scope1.py @@ -0,0 +1,22 @@ +def main(request, response): + if b"url" in request.GET: + headers = [(b"Location", request.GET[b"url"])] + return 302, headers, b'' + + status = 200 + + if b"noLocationRedirect" in request.GET: + status = 302 + + return status, [(b"content-type", b"text/html")], b''' + + +''' diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py new file mode 100644 index 00000000000..9b90b146955 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-scope2.py @@ -0,0 +1,22 @@ +def main(request, response): + if b"url" in request.GET: + headers = [(b"Location", request.GET[b"url"])] + return 302, headers, b'' + + status = 200 + + if b"noLocationRedirect" in request.GET: + status = 302 + + return status, [(b"content-type", b"text/html")], b''' + + +''' diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-to-http-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-to-http-iframe.html new file mode 100644 index 00000000000..40e27c630d2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-to-http-iframe.html @@ -0,0 +1,42 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-to-http-worker.js b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-to-http-worker.js new file mode 100644 index 00000000000..6f2a8ae1d74 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-redirect-to-http-worker.js @@ -0,0 +1,22 @@ +importScripts('/resources/testharness.js'); + +self.addEventListener('fetch', function(event) { + event.respondWith(new Promise(function(resolve) { + Promise.resolve() + .then(function() { + assert_equals( + event.request.redirect, 'manual', + 'The redirect mode of navigation request must be manual.'); + return fetch(event.request); + }) + .then(function(response) { + assert_equals( + response.type, 'opaqueredirect', + 'The response type of 302 response must be opaqueredirect.'); + resolve(new Response('OK')); + }) + .catch(function(error) { + resolve(new Response('Failed in SW: ' + error)); + }); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-timing-worker-extended.js b/test/wpt/tests/service-workers/service-worker/resources/navigation-timing-worker-extended.js new file mode 100644 index 00000000000..79c54088ff6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-timing-worker-extended.js @@ -0,0 +1,22 @@ +importScripts("/resources/testharness.js"); +const timings = {} + +const DELAY_ACTIVATION = 500 + +self.addEventListener('activate', event => { + event.waitUntil(new Promise(resolve => { + timings.activateWorkerStart = performance.now() + performance.timeOrigin; + + // This gives us enough time to ensure activation would delay fetch handling + step_timeout(resolve, DELAY_ACTIVATION); + }).then(() => timings.activateWorkerEnd = performance.now() + performance.timeOrigin)); +}) + +self.addEventListener('fetch', event => { + timings.handleFetchEvent = performance.now() + performance.timeOrigin; + event.respondWith(Promise.resolve(new Response(new Blob([` + + `])))); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/navigation-timing-worker.js b/test/wpt/tests/service-workers/service-worker/resources/navigation-timing-worker.js new file mode 100644 index 00000000000..8539b40066d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/navigation-timing-worker.js @@ -0,0 +1,15 @@ +self.addEventListener('fetch', (event) => { + const url = event.request.url; + + // Network fallback. + if (url.indexOf('network-fallback') >= 0) { + return; + } + + // Don't intercept redirect. + if (url.indexOf('redirect.py') >= 0) { + return; + } + + event.respondWith(fetch(url)); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/nested-blob-url-worker-created-from-worker.html b/test/wpt/tests/service-workers/service-worker/resources/nested-blob-url-worker-created-from-worker.html new file mode 100644 index 00000000000..fc048e288e9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/nested-blob-url-worker-created-from-worker.html @@ -0,0 +1,16 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/nested-blob-url-workers.html b/test/wpt/tests/service-workers/service-worker/resources/nested-blob-url-workers.html new file mode 100644 index 00000000000..f0eafcd3e01 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/nested-blob-url-workers.html @@ -0,0 +1,38 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/nested-iframe-parent.html b/test/wpt/tests/service-workers/service-worker/resources/nested-iframe-parent.html new file mode 100644 index 00000000000..115ab26e122 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/nested-iframe-parent.html @@ -0,0 +1,5 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/nested-parent.html b/test/wpt/tests/service-workers/service-worker/resources/nested-parent.html new file mode 100644 index 00000000000..b4832d461d5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/nested-parent.html @@ -0,0 +1,18 @@ + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/nested-worker-created-from-blob-url-worker.html b/test/wpt/tests/service-workers/service-worker/resources/nested-worker-created-from-blob-url-worker.html new file mode 100644 index 00000000000..3fad2c9228c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/nested-worker-created-from-blob-url-worker.html @@ -0,0 +1,33 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/nested_load_worker.js b/test/wpt/tests/service-workers/service-worker/resources/nested_load_worker.js new file mode 100644 index 00000000000..ef0ed8fc704 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/nested_load_worker.js @@ -0,0 +1,23 @@ +// Entry point for dedicated workers. +self.onmessage = evt => { + try { + const worker = new Worker('load_worker.js'); + worker.onmessage = evt => self.postMessage(evt.data); + worker.postMessage(evt.data); + } catch (err) { + self.postMessage('Unexpected error! ' + err.message); + } +}; + +// Entry point for shared workers. +self.onconnect = evt => { + evt.ports[0].onmessage = e => { + try { + const worker = new Worker('load_worker.js'); + worker.onmessage = e => evt.ports[0].postMessage(e.data); + worker.postMessage(evt.data); + } catch (err) { + evt.ports[0].postMessage('Unexpected error! ' + err.message); + } + }; +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/no-dynamic-import.js b/test/wpt/tests/service-workers/service-worker/resources/no-dynamic-import.js new file mode 100644 index 00000000000..ecedd6c5d75 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/no-dynamic-import.js @@ -0,0 +1,18 @@ +/** @type {[name: string, url: string][]} */ +const importUrlTests = [ + ["Module URL", "./basic-module.js"], + // In no-dynamic-import-in-module.any.js, this module is also statically imported + ["Another module URL", "./basic-module-2.js"], + [ + "Module data: URL", + "data:text/javascript;charset=utf-8," + + encodeURIComponent(`export default 'hello!';`), + ], +]; + +for (const [name, url] of importUrlTests) { + promise_test( + (t) => promise_rejects_js(t, TypeError, import(url), "Import must reject"), + name + ); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/notification_icon.py b/test/wpt/tests/service-workers/service-worker/resources/notification_icon.py new file mode 100644 index 00000000000..71f5a9d488d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/notification_icon.py @@ -0,0 +1,11 @@ +from urllib.parse import parse_qs + +from wptserve.utils import isomorphic_encode + +def main(req, res): + qs_cookie_val = parse_qs(req.url_parts.query).get(u'set-cookie-notification') + + if qs_cookie_val: + res.set_cookie(b'notification', isomorphic_encode(qs_cookie_val[0])) + + return b'not really an icon' diff --git a/test/wpt/tests/service-workers/service-worker/resources/object-image-is-not-intercepted-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/object-image-is-not-intercepted-iframe.html new file mode 100644 index 00000000000..5a20a58ab16 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/object-image-is-not-intercepted-iframe.html @@ -0,0 +1,21 @@ + + +iframe for embed-and-object-are-not-intercepted test + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/object-is-not-intercepted-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/object-is-not-intercepted-iframe.html new file mode 100644 index 00000000000..0aeb81951ed --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/object-is-not-intercepted-iframe.html @@ -0,0 +1,18 @@ + + +iframe for embed-and-object-are-not-intercepted test + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/object-navigation-is-not-intercepted-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/object-navigation-is-not-intercepted-iframe.html new file mode 100644 index 00000000000..5c8ab79a500 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/object-navigation-is-not-intercepted-iframe.html @@ -0,0 +1,24 @@ + + +iframe for embed-and-object-are-not-intercepted test + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js new file mode 100644 index 00000000000..7c97014fd04 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-from-nested-event-worker.js @@ -0,0 +1,13 @@ +var max_nesting_level = 8; + +self.addEventListener('message', function(event) { + var level = event.data; + if (level < max_nesting_level) + dispatchEvent(new MessageEvent('message', { data: level + 1 })); + throw Error('error at level ' + level); + }); + +self.addEventListener('activate', function(event) { + dispatchEvent(new MessageEvent('message', { data: 1 })); + }); + diff --git a/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js new file mode 100644 index 00000000000..0bd9d318b24 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-then-cancel-worker.js @@ -0,0 +1,3 @@ +self.onerror = function(event) { return true; }; + +self.addEventListener('activate', function(event) { throw new Error(); }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js new file mode 100644 index 00000000000..d56c9511391 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-then-prevent-default-worker.js @@ -0,0 +1,7 @@ +// Ensure we can handle multiple error handlers. One error handler +// calling preventDefault should cause the event to be treated as +// handled. +self.addEventListener('error', function(event) {}); +self.addEventListener('error', function(event) { event.preventDefault(); }); +self.addEventListener('error', function(event) {}); +self.addEventListener('activate', function(event) { throw new Error(); }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js new file mode 100644 index 00000000000..eb12ae862c5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-with-empty-onerror-worker.js @@ -0,0 +1,2 @@ +self.addEventListener('error', function(event) {}); +self.addEventListener('activate', function(event) { throw new Error(); }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js new file mode 100644 index 00000000000..1e88ac5c4e7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/onactivate-throw-error-worker.js @@ -0,0 +1,7 @@ +// Ensure we can handle multiple activate handlers. One handler throwing an +// error should cause the event dispatch to be treated as having unhandled +// errors. +self.addEventListener('activate', function(event) {}); +self.addEventListener('activate', function(event) {}); +self.addEventListener('activate', function(event) { throw new Error(); }); +self.addEventListener('activate', function(event) {}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/onactivate-waituntil-forever.js b/test/wpt/tests/service-workers/service-worker/resources/onactivate-waituntil-forever.js new file mode 100644 index 00000000000..65b02b12b36 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/onactivate-waituntil-forever.js @@ -0,0 +1,8 @@ +'use strict'; + +self.addEventListener('activate', event => { + event.waitUntil(new Promise(() => { + // Use a promise that never resolves to prevent this service worker from + // advancing past the 'activating' state. + })); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/onfetch-waituntil-forever.js b/test/wpt/tests/service-workers/service-worker/resources/onfetch-waituntil-forever.js new file mode 100644 index 00000000000..b905d555986 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/onfetch-waituntil-forever.js @@ -0,0 +1,10 @@ +'use strict'; + +self.addEventListener('fetch', event => { + if (event.request.url.endsWith('waituntil-forever')) { + event.respondWith(new Promise(() => { + // Use a promise that never resolves to prevent this fetch from + // completing. + })); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js new file mode 100644 index 00000000000..6729ab61a37 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-from-nested-event-worker.js @@ -0,0 +1,12 @@ +var max_nesting_level = 8; + +self.addEventListener('message', function(event) { + var level = event.data; + if (level < max_nesting_level) + dispatchEvent(new MessageEvent('message', { data: level + 1 })); + throw Error('error at level ' + level); + }); + +self.addEventListener('install', function(event) { + dispatchEvent(new MessageEvent('message', { data: 1 })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js new file mode 100644 index 00000000000..c2c499ab1a3 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-then-cancel-worker.js @@ -0,0 +1,3 @@ +self.onerror = function(event) { return true; }; + +self.addEventListener('install', function(event) { throw new Error(); }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js new file mode 100644 index 00000000000..7667c2781d0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-then-prevent-default-worker.js @@ -0,0 +1,7 @@ +// Ensure we can handle multiple error handlers. One error handler +// calling preventDefault should cause the event to be treated as +// handled. +self.addEventListener('error', function(event) {}); +self.addEventListener('error', function(event) { event.preventDefault(); }); +self.addEventListener('error', function(event) {}); +self.addEventListener('install', function(event) { throw new Error(); }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js new file mode 100644 index 00000000000..8f56d1bf149 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-with-empty-onerror-worker.js @@ -0,0 +1,2 @@ +self.addEventListener('error', function(event) {}); +self.addEventListener('install', function(event) { throw new Error(); }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js new file mode 100644 index 00000000000..cc2f6d7e5e7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/oninstall-throw-error-worker.js @@ -0,0 +1,7 @@ +// Ensure we can handle multiple install handlers. One handler throwing an +// error should cause the event dispatch to be treated as having unhandled +// errors. +self.addEventListener('install', function(event) {}); +self.addEventListener('install', function(event) {}); +self.addEventListener('install', function(event) { throw new Error(); }); +self.addEventListener('install', function(event) {}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/oninstall-waituntil-forever.js b/test/wpt/tests/service-workers/service-worker/resources/oninstall-waituntil-forever.js new file mode 100644 index 00000000000..964483f2f42 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/oninstall-waituntil-forever.js @@ -0,0 +1,8 @@ +'use strict'; + +self.addEventListener('install', event => { + event.waitUntil(new Promise(() => { + // Use a promise that never resolves to prevent this service worker from + // advancing past the 'installing' state. + })); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/oninstall-waituntil-throw-error-worker.js b/test/wpt/tests/service-workers/service-worker/resources/oninstall-waituntil-throw-error-worker.js new file mode 100644 index 00000000000..6cb8f6ede63 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/oninstall-waituntil-throw-error-worker.js @@ -0,0 +1,5 @@ +self.addEventListener('install', function(event) { + event.waitUntil(new Promise(function(aRequest, aResponse) { + throw new Error(); + })); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/onparse-infiniteloop-worker.js b/test/wpt/tests/service-workers/service-worker/resources/onparse-infiniteloop-worker.js new file mode 100644 index 00000000000..6f439aee94d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/onparse-infiniteloop-worker.js @@ -0,0 +1,8 @@ +'use strict'; + +// Use an infinite loop to prevent this service worker from advancing past the +// 'parsed' state. +let i = 0; +while (true) { + ++i; +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/opaque-response-being-preloaded-xhr.html b/test/wpt/tests/service-workers/service-worker/resources/opaque-response-being-preloaded-xhr.html new file mode 100644 index 00000000000..9c6d8bd504a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/opaque-response-being-preloaded-xhr.html @@ -0,0 +1,33 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/opaque-response-preloaded-worker.js b/test/wpt/tests/service-workers/service-worker/resources/opaque-response-preloaded-worker.js new file mode 100644 index 00000000000..4fbe35df277 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/opaque-response-preloaded-worker.js @@ -0,0 +1,12 @@ +importScripts('/common/get-host-info.sub.js'); + +var remoteUrl = get_host_info()['HTTPS_REMOTE_ORIGIN'] + + '/service-workers/service-worker/resources/sample.js' + +self.addEventListener('fetch', event => { + if (!event.request.url.match(/opaque-response\?from=/)) { + return; + } + + event.respondWith(fetch(remoteUrl, {mode: 'no-cors'})); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/opaque-response-preloaded-xhr.html b/test/wpt/tests/service-workers/service-worker/resources/opaque-response-preloaded-xhr.html new file mode 100644 index 00000000000..f31ac9b5c4c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/opaque-response-preloaded-xhr.html @@ -0,0 +1,35 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/opaque-script-frame.html b/test/wpt/tests/service-workers/service-worker/resources/opaque-script-frame.html new file mode 100644 index 00000000000..a57aacec7c6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/opaque-script-frame.html @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/opaque-script-large.js b/test/wpt/tests/service-workers/service-worker/resources/opaque-script-large.js new file mode 100644 index 00000000000..7e1c598efc5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/opaque-script-large.js @@ -0,0 +1,41 @@ +function runScript() { + throw new Error("Intentional error."); +} + +function unused() { + // The following string is intended to be relatively large since some + // browsers trigger different code paths based on script size. + return "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a " + + "tortor ut orci bibendum blandit non quis diam. Aenean sit amet " + + "urna sit amet neque malesuada ultricies at vel nisi. Nunc et lacus " + + "est. Nam posuere erat enim, ac fringilla purus pellentesque " + + "cursus. Proin sodales eleifend lorem, eu semper massa scelerisque " + + "ac. Maecenas pharetra leo malesuada vulputate vulputate. Sed at " + + "efficitur odio. In rhoncus neque varius nibh efficitur gravida. " + + "Curabitur vitae dolor enim. Mauris semper lobortis libero sed " + + "congue. Donec felis ante, fringilla eget urna ut, finibus " + + "hendrerit lacus. Donec at interdum diam. Proin a neque vitae diam " + + "egestas euismod. Mauris posuere elementum lorem, eget convallis " + + "nisl elementum et. In ut leo ac neque dapibus pharetra quis ac " + + "velit. Integer pretium lectus non urna vulputate, in interdum mi " + + "lobortis. Sed laoreet ex et metus pharetra blandit. Curabitur " + + "sollicitudin non neque eu varius. Phasellus posuere congue arcu, " + + "in aliquam nunc fringilla a. Morbi id facilisis libero. Phasellus " + + "metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "tortor ut orci bibendum blandit non quis diam. Aenean sit amet " + + "urna sit amet neque malesuada ultricies at vel nisi. Nunc et lacus " + + "est. Nam posuere erat enim, ac fringilla purus pellentesque " + + "cursus. Proin sodales eleifend lorem, eu semper massa scelerisque " + + "ac. Maecenas pharetra leo malesuada vulputate vulputate. Sed at " + + "efficitur odio. In rhoncus neque varius nibh efficitur gravida. " + + "Curabitur vitae dolor enim. Mauris semper lobortis libero sed " + + "congue. Donec felis ante, fringilla eget urna ut, finibus " + + "hendrerit lacus. Donec at interdum diam. Proin a neque vitae diam " + + "egestas euismod. Mauris posuere elementum lorem, eget convallis " + + "nisl elementum et. In ut leo ac neque dapibus pharetra quis ac " + + "velit. Integer pretium lectus non urna vulputate, in interdum mi " + + "lobortis. Sed laoreet ex et metus pharetra blandit. Curabitur " + + "sollicitudin non neque eu varius. Phasellus posuere congue arcu, " + + "in aliquam nunc fringilla a. Morbi id facilisis libero. Phasellus " + + "metus."; +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/opaque-script-small.js b/test/wpt/tests/service-workers/service-worker/resources/opaque-script-small.js new file mode 100644 index 00000000000..8b890985752 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/opaque-script-small.js @@ -0,0 +1,3 @@ +function runScript() { + throw new Error("Intentional error."); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/opaque-script-sw.js b/test/wpt/tests/service-workers/service-worker/resources/opaque-script-sw.js new file mode 100644 index 00000000000..4d882c617d8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/opaque-script-sw.js @@ -0,0 +1,37 @@ +importScripts('test-helpers.sub.js'); +importScripts('/common/get-host-info.sub.js'); + +const NAME = 'foo'; +const SAME_ORIGIN_BASE = new URL('./', self.location.href).href; +const CROSS_ORIGIN_BASE = new URL('./', + get_host_info().HTTPS_REMOTE_ORIGIN + base_path()).href; + +const urls = [ + `${SAME_ORIGIN_BASE}opaque-script-small.js`, + `${SAME_ORIGIN_BASE}opaque-script-large.js`, + `${CROSS_ORIGIN_BASE}opaque-script-small.js`, + `${CROSS_ORIGIN_BASE}opaque-script-large.js`, +]; + +self.addEventListener('install', evt => { + evt.waitUntil(async function() { + const c = await caches.open(NAME); + const promises = urls.map(async function(u) { + const r = await fetch(u, { mode: 'no-cors' }); + await c.put(u, r); + }); + await Promise.all(promises); + }()); +}); + +self.addEventListener('fetch', evt => { + const url = new URL(evt.request.url); + if (!url.pathname.includes('opaque-script-small.js') && + !url.pathname.includes('opaque-script-large.js')) { + return; + } + evt.respondWith(async function() { + const c = await caches.open(NAME); + return c.match(evt.request); + }()); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/other.html b/test/wpt/tests/service-workers/service-worker/resources/other.html new file mode 100644 index 00000000000..b9f35043877 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/other.html @@ -0,0 +1,3 @@ + +Other +Here's an other html file. diff --git a/test/wpt/tests/service-workers/service-worker/resources/override_assert_object_equals.js b/test/wpt/tests/service-workers/service-worker/resources/override_assert_object_equals.js new file mode 100644 index 00000000000..835046d472b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/override_assert_object_equals.js @@ -0,0 +1,58 @@ +// .body attribute of Request and Response object are experimental feture. It is +// enabled when --enable-experimental-web-platform-features flag is set. +// Touching this attribute can change the behavior of the objects. To avoid +// touching it while comparing the objects in LayoutTest, we overwrite +// assert_object_equals method. + +(function() { + var original_assert_object_equals = self.assert_object_equals; + function _brand(object) { + return Object.prototype.toString.call(object).match(/^\[object (.*)\]$/)[1]; + } + var assert_request_equals = function(actual, expected, prefix) { + if (typeof actual !== 'object') { + assert_equals(actual, expected, prefix); + return; + } + assert_true(actual instanceof Request, prefix); + assert_true(expected instanceof Request, prefix); + assert_equals(actual.bodyUsed, expected.bodyUsed, prefix + '.bodyUsed'); + assert_equals(actual.method, expected.method, prefix + '.method'); + assert_equals(actual.url, expected.url, prefix + '.url'); + original_assert_object_equals(actual.headers, expected.headers, + prefix + '.headers'); + assert_equals(actual.context, expected.context, prefix + '.context'); + assert_equals(actual.referrer, expected.referrer, prefix + '.referrer'); + assert_equals(actual.mode, expected.mode, prefix + '.mode'); + assert_equals(actual.credentials, expected.credentials, + prefix + '.credentials'); + assert_equals(actual.cache, expected.cache, prefix + '.cache'); + }; + var assert_response_equals = function(actual, expected, prefix) { + if (typeof actual !== 'object') { + assert_equals(actual, expected, prefix); + return; + } + assert_true(actual instanceof Response, prefix); + assert_true(expected instanceof Response, prefix); + assert_equals(actual.bodyUsed, expected.bodyUsed, prefix + '.bodyUsed'); + assert_equals(actual.type, expected.type, prefix + '.type'); + assert_equals(actual.url, expected.url, prefix + '.url'); + assert_equals(actual.status, expected.status, prefix + '.status'); + assert_equals(actual.statusText, expected.statusText, + prefix + '.statusText'); + original_assert_object_equals(actual.headers, expected.headers, + prefix + '.headers'); + }; + var assert_object_equals = function(actual, expected, description) { + var prefix = (description ? description + ': ' : '') + _brand(expected); + if (expected instanceof Request) { + assert_request_equals(actual, expected, prefix); + } else if (expected instanceof Response) { + assert_response_equals(actual, expected, prefix); + } else { + original_assert_object_equals(actual, expected, description); + } + }; + self.assert_object_equals = assert_object_equals; +})(); diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-iframe-claim.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-iframe-claim.html new file mode 100644 index 00000000000..12b048ee04c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-iframe-claim.html @@ -0,0 +1,59 @@ + +Service Worker: 3P iframe for partitioned service workers + + + + + + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-nested-iframe-child.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-nested-iframe-child.html new file mode 100644 index 00000000000..d05fef48bf0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-nested-iframe-child.html @@ -0,0 +1,44 @@ + +Service Worker: Innermost nested iframe for partitioned service workers + + + + + +Innermost 1p iframe (A2) with 3p ancestor (A1-B-A2-A3): this iframe will +register a service worker when it loads and then add its own iframe (A3) that +will attempt to navigate to a url. ServiceWorker will intercept this navigation +and resolve the ServiceWorker's internal Promise. When +ThirdPartyStoragePartitioning is enabled, this iframe should be partitioned +from the main frame and should not share a ServiceWorker. + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-nested-iframe-parent.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-nested-iframe-parent.html new file mode 100644 index 00000000000..f748e2f78d9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-nested-iframe-parent.html @@ -0,0 +1,30 @@ + +Service Worker: Middle nested iframe for partitioned service workers + + + + + +Middle of the nested iframes (3p ancestor or B in A1-B-A2). + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-iframe-getRegistrations.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-iframe-getRegistrations.html new file mode 100644 index 00000000000..747c0589460 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-iframe-getRegistrations.html @@ -0,0 +1,40 @@ + +Service Worker: 3P iframe for partitioned service workers + + + + + + This iframe will register a service worker when it loads and then will use + getRegistrations to get a handle to the SW. It will then postMessage to the + SW to retrieve the SW's ID. This iframe will then forward that message up, + eventually, to the test. + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-iframe-matchAll.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-iframe-matchAll.html new file mode 100644 index 00000000000..7a2c36693e1 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-iframe-matchAll.html @@ -0,0 +1,27 @@ + +Service Worker: 3P iframe for partitioned service workers + + + + + + This iframe will register a service worker when it loads and then will use + getRegistrations to get a handle to the SW. It will then postMessage to the + SW to get the SW's clients via matchAll(). This iframe will then forward the + SW's response up, eventually, to the test. + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-iframe.html new file mode 100644 index 00000000000..1b7f671b371 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-iframe.html @@ -0,0 +1,36 @@ + +Service Worker: 3P iframe for partitioned service workers + + + + + + +This iframe will register a service worker when it loads and then add its own +iframe that will attempt to navigate to a url that service worker will intercept +and use to resolve the service worker's internal Promise. + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-window.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-window.html new file mode 100644 index 00000000000..86384ce2808 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-service-worker-third-party-window.html @@ -0,0 +1,41 @@ + +Service Worker: 3P window for partitioned service workers + + + + + +This page should be opened as a third-party window. It then loads an iframe +specified by the query parameter. Finally it forwards the postMessage from the +iframe up to the opener (the test). + + + \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-storage-sw.js b/test/wpt/tests/service-workers/service-worker/resources/partitioned-storage-sw.js new file mode 100644 index 00000000000..00f79798103 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-storage-sw.js @@ -0,0 +1,81 @@ +// Holds the promise that the "resolve.fakehtml" call attempts to resolve. +// This is "the SW's promise" that other parts of the test refer to. +var promise; +// Stores the resolve funcution for the current promise. +var pending_resolve_func = null; +// Unique ID to determine which service worker is being used. +const ID = Math.random(); + +function callAndResetResolve() { + var local_resolve = pending_resolve_func; + pending_resolve_func = null; + local_resolve(); +} + +self.addEventListener('fetch', function(event) { + fetchEventHandler(event); +}) + +self.addEventListener('message', (event) => { + event.waitUntil(async function() { + if(!event.data) + return; + + if (event.data.type === "get-id") { + event.source.postMessage({ID: ID}); + } + else if(event.data.type === "get-match-all") { + clients.matchAll({includeUncontrolled: true}).then(clients_list => { + const url_list = clients_list.map(item => item.url); + event.source.postMessage({urls_list: url_list}); + }); + } + else if(event.data.type === "claim") { + await clients.claim(); + } + }()); +}); + +async function fetchEventHandler(event){ + var request_url = new URL(event.request.url); + var url_search = request_url.search.substr(1); + request_url.search = ""; + if ( request_url.href.endsWith('waitUntilResolved.fakehtml') ) { + + if (pending_resolve_func != null) { + // Respond with an error if there is already a pending promise + event.respondWith(Response.error()); + return; + } + + // Create the new promise. + promise = new Promise(function(resolve) { + pending_resolve_func = resolve; + }); + event.waitUntil(promise); + + event.respondWith(new Response(` + + Promise created by ${url_search} + + + `, {headers: {'Content-Type': 'text/html'}} + )); + + } + else if ( request_url.href.endsWith('resolve.fakehtml') ) { + var has_pending = !!pending_resolve_func; + event.respondWith(new Response(` + + Promise settled for ${url_search} + + + `, {headers: {'Content-Type': 'text/html'}})); + + if (has_pending) { + callAndResetResolve(); + } + } +} \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-utils.js b/test/wpt/tests/service-workers/service-worker/resources/partitioned-utils.js new file mode 100644 index 00000000000..22e90beaec7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-utils.js @@ -0,0 +1,110 @@ +// The resolve function for the current pending event listener's promise. +// It is nulled once the promise is resolved. +var message_event_promise_resolve = null; + +function messageEventHandler(evt) { + if (message_event_promise_resolve) { + local_resolve = message_event_promise_resolve; + message_event_promise_resolve = null; + local_resolve(evt.data); + } +} + +function makeMessagePromise() { + if (message_event_promise_resolve != null) { + // Do not create a new promise until the previous is settled. + return; + } + + return new Promise(resolve => { + message_event_promise_resolve = resolve; + }); +} + +// Loads a url for the frame type and then returns a promise for +// the data that was postMessage'd from the loaded frame. +// If the frame type is 'window' then `url` is encoded into the search param +// as the url the 3p window is meant to iframe. +function loadAndReturnSwData(t, url, frame_type) { + if (frame_type !== 'iframe' && frame_type !== 'window') { + return; + } + + const message_promise = makeMessagePromise(); + + // Create the iframe or window and then return the promise for data. + if ( frame_type === 'iframe' ) { + const frame = with_iframe(url, false); + t.add_cleanup(async () => { + const f = await frame; + f.remove(); + }); + } + else { + // 'window' case. + const search_param = new URLSearchParams(); + search_param.append('target', url); + + const third_party_window_url = new URL( + './resources/partitioned-service-worker-third-party-window.html' + + '?' + search_param, + get_host_info().HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname); + + const w = window.open(third_party_window_url); + t.add_cleanup(() => w.close()); + } + + return message_promise; +} + +// Checks for an existing service worker registration. If not present, +// registers and maintains a service worker. Used in windows or iframes +// that will be partitioned from the main frame. +async function setupServiceWorker() { + + const script = './partitioned-storage-sw.js'; + const scope = './partitioned-'; + + var reg = await navigator.serviceWorker.register(script, { scope: scope }); + + // We should keep track if we installed a worker or not. If we did then we + // need to uninstall it. Otherwise we let the top level test uninstall it + // (If partitioning is not working). + var installed_a_worker = true; + await new Promise(resolve => { + // Check if a worker is already activated. + var worker = reg.active; + // If so, just resolve. + if ( worker ) { + installed_a_worker = false; + resolve(); + return; + } + + //Otherwise check if one is waiting. + worker = reg.waiting; + // If not waiting, grab the installing worker. + if ( !worker ) { + worker = reg.installing; + } + + // Resolve once it's activated. + worker.addEventListener('statechange', evt => { + if (worker.state === 'activated') { + resolve(); + } + }); + }); + + self.addEventListener('unload', async () => { + // If we didn't install a worker then that means the top level test did, and + // that test is therefore responsible for cleaning it up. + if ( !installed_a_worker ) { + return; + } + + await reg.unregister(); + }); + + return reg; +} \ No newline at end of file diff --git a/test/wpt/tests/service-workers/service-worker/resources/pass-through-worker.js b/test/wpt/tests/service-workers/service-worker/resources/pass-through-worker.js new file mode 100644 index 00000000000..5eaf48d5887 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/pass-through-worker.js @@ -0,0 +1,3 @@ +addEventListener('fetch', evt => { + evt.respondWith(fetch(evt.request)); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/pass.txt b/test/wpt/tests/service-workers/service-worker/resources/pass.txt new file mode 100644 index 00000000000..7ef22e9a431 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/pass.txt @@ -0,0 +1 @@ +PASS diff --git a/test/wpt/tests/service-workers/service-worker/resources/performance-timeline-worker.js b/test/wpt/tests/service-workers/service-worker/resources/performance-timeline-worker.js new file mode 100644 index 00000000000..6c6dfcbd281 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/performance-timeline-worker.js @@ -0,0 +1,62 @@ +importScripts('/resources/testharness.js'); + +promise_test(function(test) { + var durationMsec = 100; + // There are limits to our accuracy here. Timers may fire up to a + // millisecond early due to platform-dependent rounding. In addition + // the performance API introduces some rounding as well to prevent + // timing attacks. + var accuracy = 1.5; + return new Promise(function(resolve) { + performance.mark('startMark'); + setTimeout(resolve, durationMsec); + }).then(function() { + performance.mark('endMark'); + performance.measure('measure', 'startMark', 'endMark'); + var startMark = performance.getEntriesByName('startMark')[0]; + var endMark = performance.getEntriesByName('endMark')[0]; + var measure = performance.getEntriesByType('measure')[0]; + assert_equals(measure.startTime, startMark.startTime); + assert_approx_equals(endMark.startTime - startMark.startTime, + measure.duration, 0.001); + assert_greater_than(measure.duration, durationMsec - accuracy); + assert_equals(performance.getEntriesByType('mark').length, 2); + assert_equals(performance.getEntriesByType('measure').length, 1); + performance.clearMarks('startMark'); + performance.clearMeasures('measure'); + assert_equals(performance.getEntriesByType('mark').length, 1); + assert_equals(performance.getEntriesByType('measure').length, 0); + }); + }, 'User Timing'); + +promise_test(function(test) { + return fetch('sample.txt') + .then(function(resp) { + return resp.text(); + }) + .then(function(text) { + var expectedResources = ['testharness.js', 'sample.txt']; + assert_equals(performance.getEntriesByType('resource').length, expectedResources.length); + for (var i = 0; i < expectedResources.length; i++) { + var entry = performance.getEntriesByType('resource')[i]; + assert_true(entry.name.endsWith(expectedResources[i])); + assert_equals(entry.workerStart, 0); + assert_greater_than(entry.startTime, 0); + assert_greater_than(entry.responseEnd, entry.startTime); + } + return new Promise(function(resolve) { + performance.onresourcetimingbufferfull = _ => { + resolve('bufferfull'); + } + performance.setResourceTimingBufferSize(expectedResources.length); + fetch('sample.txt'); + }); + }) + .then(function(result) { + assert_equals(result, 'bufferfull'); + performance.clearResourceTimings(); + assert_equals(performance.getEntriesByType('resource').length, 0); + }) + }, 'Resource Timing'); + +done(); diff --git a/test/wpt/tests/service-workers/service-worker/resources/postmessage-blob-url.js b/test/wpt/tests/service-workers/service-worker/resources/postmessage-blob-url.js new file mode 100644 index 00000000000..9095194a4c0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/postmessage-blob-url.js @@ -0,0 +1,5 @@ +self.onmessage = e => { + fetch(e.data) + .then(response => response.text()) + .then(text => e.source.postMessage('Worker reply:' + text)); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/postmessage-dictionary-transferables-worker.js b/test/wpt/tests/service-workers/service-worker/resources/postmessage-dictionary-transferables-worker.js new file mode 100644 index 00000000000..87a4500d754 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/postmessage-dictionary-transferables-worker.js @@ -0,0 +1,24 @@ +var messageHandler = function(port, e) { + var text_decoder = new TextDecoder; + port.postMessage({ + content: text_decoder.decode(e.data), + byteLength: e.data.byteLength + }); + + // Send back the array buffer via Client.postMessage. + port.postMessage(e.data, {transfer: [e.data.buffer]}); + + port.postMessage({ + content: text_decoder.decode(e.data), + byteLength: e.data.byteLength + }); +}; + +self.addEventListener('message', e => { + if (e.ports[0]) { + // Wait for messages sent via MessagePort. + e.ports[0].onmessage = messageHandler.bind(null, e.ports[0]); + return; + } + messageHandler(e.source, e); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/postmessage-echo-worker.js b/test/wpt/tests/service-workers/service-worker/resources/postmessage-echo-worker.js new file mode 100644 index 00000000000..f088ad12780 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/postmessage-echo-worker.js @@ -0,0 +1,3 @@ +self.addEventListener('message', event => { + event.source.postMessage(event.data); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/postmessage-fetched-text.js b/test/wpt/tests/service-workers/service-worker/resources/postmessage-fetched-text.js new file mode 100644 index 00000000000..9fc67171d05 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/postmessage-fetched-text.js @@ -0,0 +1,5 @@ +self.onmessage = async (e) => { + const response = await fetch(e.data); + const text = await response.text(); + self.postMessage(text); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js b/test/wpt/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js new file mode 100644 index 00000000000..7af935f4f8f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/postmessage-msgport-to-client-worker.js @@ -0,0 +1,19 @@ +self.onmessage = function(e) { + e.waitUntil(self.clients.matchAll().then(function(clients) { + clients.forEach(function(client) { + var messageChannel = new MessageChannel(); + messageChannel.port1.onmessage = + onMessageViaMessagePort.bind(null, messageChannel.port1); + client.postMessage(undefined, [messageChannel.port2]); + }); + })); +}; + +function onMessageViaMessagePort(port, e) { + var message = e.data; + if ('value' in message) { + port.postMessage({ack: 'Acking value: ' + message.value}); + } else if ('done' in message) { + port.postMessage({done: true}); + } +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/postmessage-on-load-worker.js b/test/wpt/tests/service-workers/service-worker/resources/postmessage-on-load-worker.js new file mode 100644 index 00000000000..c2b0bcb8bfb --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/postmessage-on-load-worker.js @@ -0,0 +1,9 @@ +if ('DedicatedWorkerGlobalScope' in self && + self instanceof DedicatedWorkerGlobalScope) { + postMessage('dedicated worker script loaded'); +} else if ('SharedWorkerGlobalScope' in self && + self instanceof SharedWorkerGlobalScope) { + self.onconnect = evt => { + evt.ports[0].postMessage('shared worker script loaded'); + }; +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js b/test/wpt/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js new file mode 100644 index 00000000000..17913063583 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/postmessage-to-client-worker.js @@ -0,0 +1,10 @@ +self.onmessage = function(e) { + e.waitUntil(self.clients.matchAll().then(function(clients) { + clients.forEach(function(client) { + client.postMessage('Sending message via clients'); + if (!Array.isArray(clients)) + client.postMessage('clients is not an array'); + client.postMessage('quit'); + }); + })); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/postmessage-transferables-worker.js b/test/wpt/tests/service-workers/service-worker/resources/postmessage-transferables-worker.js new file mode 100644 index 00000000000..d35c1c952b8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/postmessage-transferables-worker.js @@ -0,0 +1,24 @@ +var messageHandler = function(port, e) { + var text_decoder = new TextDecoder; + port.postMessage({ + content: text_decoder.decode(e.data), + byteLength: e.data.byteLength + }); + + // Send back the array buffer via Client.postMessage. + port.postMessage(e.data, [e.data.buffer]); + + port.postMessage({ + content: text_decoder.decode(e.data), + byteLength: e.data.byteLength + }); +}; + +self.addEventListener('message', e => { + if (e.ports[0]) { + // Wait for messages sent via MessagePort. + e.ports[0].onmessage = messageHandler.bind(null, e.ports[0]); + return; + } + messageHandler(e.source, e); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/postmessage-worker.js b/test/wpt/tests/service-workers/service-worker/resources/postmessage-worker.js new file mode 100644 index 00000000000..858cf04267c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/postmessage-worker.js @@ -0,0 +1,19 @@ +var port; + +// Exercise the 'onmessage' handler: +self.onmessage = function(e) { + var message = e.data; + if ('port' in message) { + port = message.port; + } +}; + +// And an event listener: +self.addEventListener('message', function(e) { + var message = e.data; + if ('value' in message) { + port.postMessage('Acking value: ' + message.value); + } else if ('done' in message) { + port.postMessage('quit'); + } + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/range-request-to-different-origins-worker.js b/test/wpt/tests/service-workers/service-worker/resources/range-request-to-different-origins-worker.js new file mode 100644 index 00000000000..cab60583390 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/range-request-to-different-origins-worker.js @@ -0,0 +1,40 @@ +// This worker is meant to test range requests where the responses come from +// multiple origins. It forwards the first request to a cross-origin URL +// (generating an opaque response). The server is expected to return a 206 +// Partial Content response. Then the worker lets subsequent range requests +// fall back to network (generating same-origin responses). The intent is to try +// to trick the browser into treating the resource as same-origin. +// +// It would also be interesting to do the reverse test where the first request +// goes to the same-origin URL, and subsequent range requests go cross-origin in +// 'no-cors' mode to receive opaque responses. But the service worker cannot do +// this, because in 'no-cors' mode the 'range' HTTP header is disallowed. + +importScripts('/common/get-host-info.sub.js') + +let initial = true; +function is_initial_request() { + const old = initial; + initial = false; + return old; +} + +self.addEventListener('fetch', e => { + const url = new URL(e.request.url); + if (url.search.indexOf('VIDEO') == -1) { + // Fall back for non-video. + return; + } + + // Make the first request go cross-origin. + if (is_initial_request()) { + const cross_origin_url = get_host_info().HTTPS_REMOTE_ORIGIN + + url.pathname + url.search; + const cross_origin_request = new Request(cross_origin_url, + {mode: 'no-cors', headers: e.request.headers}); + e.respondWith(fetch(cross_origin_request)); + return; + } + + // Fall back to same origin for subsequent range requests. + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/range-request-with-different-cors-modes-worker.js b/test/wpt/tests/service-workers/service-worker/resources/range-request-with-different-cors-modes-worker.js new file mode 100644 index 00000000000..7580b0b68a9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/range-request-with-different-cors-modes-worker.js @@ -0,0 +1,60 @@ +// This worker is meant to test range requests where the responses are a mix of +// opaque ones and non-opaque ones. It forwards the first request to a +// cross-origin URL (generating an opaque response). The server is expected to +// return a 206 Partial Content response. Then the worker forwards subsequent +// range requests to that URL, with CORS sharing generating a non-opaque +// responses. The intent is to try to trick the browser into treating the +// resource as non-opaque. +// +// It would also be interesting to do the reverse test where the first request +// uses 'cors', and subsequent range requests use 'no-cors' mode. But the +// service worker cannot do this, because in 'no-cors' mode the 'range' HTTP +// header is disallowed. + +importScripts('/common/get-host-info.sub.js') + +let initial = true; +function is_initial_request() { + const old = initial; + initial = false; + return old; +} + +self.addEventListener('fetch', e => { + const url = new URL(e.request.url); + if (url.search.indexOf('VIDEO') == -1) { + // Fall back for non-video. + return; + } + + let cross_origin_url = get_host_info().HTTPS_REMOTE_ORIGIN + + url.pathname + url.search; + + // The first request is no-cors. + if (is_initial_request()) { + const init = { mode: 'no-cors', headers: e.request.headers }; + const cross_origin_request = new Request(cross_origin_url, init); + e.respondWith(fetch(cross_origin_request)); + return; + } + + // Subsequent range requests are cors. + + // Copy headers needed for range requests. + let my_headers = new Headers; + if (e.request.headers.get('accept')) + my_headers.append('accept', e.request.headers.get('accept')); + if (e.request.headers.get('range')) + my_headers.append('range', e.request.headers.get('range')); + + // Add &ACAOrigin to allow CORS. + cross_origin_url += '&ACAOrigin=' + get_host_info().HTTPS_ORIGIN; + // Add &ACAHeaders to allow range requests. + cross_origin_url += '&ACAHeaders=accept,range'; + + // Make the CORS request. + const init = { mode: 'cors', headers: my_headers }; + const cross_origin_request = new Request(cross_origin_url, init); + e.respondWith(fetch(cross_origin_request)); + }); + diff --git a/test/wpt/tests/service-workers/service-worker/resources/redirect-worker.js b/test/wpt/tests/service-workers/service-worker/resources/redirect-worker.js new file mode 100644 index 00000000000..82e21fc26fd --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/redirect-worker.js @@ -0,0 +1,145 @@ +// We store an empty response for each fetch event request we see +// in this Cache object so we can get the list of urls in the +// message event. +var cacheName = 'urls-' + self.registration.scope; + +var waitUntilPromiseList = []; + +// Sends the requests seen by this worker. The output is: +// { +// requestInfos: [ +// {url: url1, resultingClientId: id1}, +// {url: url2, resultingClientId: id2}, +// ] +// } +async function getRequestInfos(event) { + // Wait for fetch events to finish. + await Promise.all(waitUntilPromiseList); + waitUntilPromiseList = []; + + // Generate the message. + const cache = await caches.open(cacheName); + const requestList = await cache.keys(); + const requestInfos = []; + for (let i = 0; i < requestList.length; i++) { + const response = await cache.match(requestList[i]); + const body = await response.json(); + requestInfos[i] = { + url: requestList[i].url, + resultingClientId: body.resultingClientId + }; + } + await caches.delete(cacheName); + + event.data.port.postMessage({requestInfos}); +} + +// Sends the results of clients.get(id) from this worker. The +// input is: +// { +// actual_ids: {a: id1, b: id2, x: id3} +// } +// +// The output is: +// { +// clients: { +// a: {found: false}, +// b: {found: false}, +// x: { +// id: id3, +// url: url1, +// found: true +// } +// } +// } +async function getClients(event) { + // |actual_ids| is like: + // {a: id1, b: id2, x: id3} + const actual_ids = event.data.actual_ids; + const result = {} + for (let key of Object.keys(actual_ids)) { + const id = actual_ids[key]; + const client = await self.clients.get(id); + if (client === undefined) + result[key] = {found: false}; + else + result[key] = {found: true, url: client.url, id: client.id}; + } + event.data.port.postMessage({clients: result}); +} + +self.addEventListener('message', async function(event) { + if (event.data.command == 'getRequestInfos') { + event.waitUntil(getRequestInfos(event)); + return; + } + + if (event.data.command == 'getClients') { + event.waitUntil(getClients(event)); + return; + } +}); + +function get_query_params(url) { + var search = (new URL(url)).search; + if (!search) { + return {}; + } + var ret = {}; + var params = search.substring(1).split('&'); + params.forEach(function(param) { + var element = param.split('='); + ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]); + }); + return ret; +} + +self.addEventListener('fetch', function(event) { + var waitUntilPromise = caches.open(cacheName).then(function(cache) { + const responseBody = {}; + responseBody['resultingClientId'] = event.resultingClientId; + const headers = new Headers({'Content-Type': 'application/json'}); + const response = new Response(JSON.stringify(responseBody), {headers}); + return cache.put(event.request, response); + }); + event.waitUntil(waitUntilPromise); + + var params = get_query_params(event.request.url); + if (!params['sw']) { + // To avoid races, add the waitUntil() promise to our global list. + // If we get a message event before we finish here, it will wait + // these promises to complete before proceeding to read from the + // cache. + waitUntilPromiseList.push(waitUntilPromise); + return; + } + + event.respondWith(waitUntilPromise.then(async () => { + if (params['sw'] == 'gen') { + return Response.redirect(params['url']); + } else if (params['sw'] == 'gen-manual') { + // Note this differs from Response.redirect() in that relative URLs are + // preserved. + return new Response("", { + status: 301, + headers: {location: params['url']}, + }); + } else if (params['sw'] == 'fetch') { + return fetch(event.request); + } else if (params['sw'] == 'fetch-url') { + return fetch(params['url']); + } else if (params['sw'] == 'follow') { + return fetch(new Request(event.request.url, {redirect: 'follow'})); + } else if (params['sw'] == 'manual') { + return fetch(new Request(event.request.url, {redirect: 'manual'})); + } else if (params['sw'] == 'manualThroughCache') { + const url = event.request.url; + await caches.delete(url) + const cache = await self.caches.open(url); + const response = await fetch(new Request(url, {redirect: 'manual'})); + await cache.put(event.request, response); + return cache.match(url); + } + // unexpected... trigger an interception failure + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/redirect.py b/test/wpt/tests/service-workers/service-worker/resources/redirect.py new file mode 100644 index 00000000000..bd559d5d1e2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/redirect.py @@ -0,0 +1,27 @@ +from wptserve.utils import isomorphic_decode + +def main(request, response): + if b'Status' in request.GET: + status = int(request.GET[b"Status"]) + else: + status = 302 + + headers = [] + + url = isomorphic_decode(request.GET[b'Redirect']) + headers.append((b"Location", url)) + + if b"ACAOrigin" in request.GET: + for item in request.GET[b"ACAOrigin"].split(b","): + headers.append((b"Access-Control-Allow-Origin", item)) + + for suffix in [b"Headers", b"Methods", b"Credentials"]: + query = b"ACA%s" % suffix + header = b"Access-Control-Allow-%s" % suffix + if query in request.GET: + headers.append((header, request.GET[query])) + + if b"ACEHeaders" in request.GET: + headers.append((b"Access-Control-Expose-Headers", request.GET[b"ACEHeaders"])) + + return status, headers, b"" diff --git a/test/wpt/tests/service-workers/service-worker/resources/referer-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/referer-iframe.html new file mode 100644 index 00000000000..295ff456715 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/referer-iframe.html @@ -0,0 +1,39 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/referrer-policy-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/referrer-policy-iframe.html new file mode 100644 index 00000000000..9ef3cd19a98 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/referrer-policy-iframe.html @@ -0,0 +1,32 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/register-closed-window-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/register-closed-window-iframe.html new file mode 100644 index 00000000000..117f25477b0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/register-closed-window-iframe.html @@ -0,0 +1,19 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/register-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/register-iframe.html new file mode 100644 index 00000000000..f5a040e41d9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/register-iframe.html @@ -0,0 +1,4 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/register-rewrite-worker.html b/test/wpt/tests/service-workers/service-worker/resources/register-rewrite-worker.html new file mode 100644 index 00000000000..bf06317ad9e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/register-rewrite-worker.html @@ -0,0 +1,32 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/registration-tests-mime-types.js b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-mime-types.js new file mode 100644 index 00000000000..037e6c0fde2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-mime-types.js @@ -0,0 +1,96 @@ +// Registration tests that mostly verify the MIME type. +// +// This file tests every MIME type so it necessarily starts many service +// workers, so it may be slow. +function registration_tests_mime_types(register_method) { + promise_test(function(t) { + var script = 'resources/mime-type-worker.py'; + var scope = 'resources/scope/no-mime-type-worker/'; + return promise_rejects_dom(t, + 'SecurityError', + register_method(script, {scope: scope}), + 'Registration of no MIME type script should fail.'); + }, 'Registering script with no MIME type'); + + promise_test(function(t) { + var script = 'resources/mime-type-worker.py?mime=text/plain'; + var scope = 'resources/scope/bad-mime-type-worker/'; + return promise_rejects_dom(t, + 'SecurityError', + register_method(script, {scope: scope}), + 'Registration of plain text script should fail.'); + }, 'Registering script with bad MIME type'); + + /** + * ServiceWorkerContainer.register() should throw a TypeError, according to + * step 17.1 of https://w3c.github.io/ServiceWorker/#importscripts + * + * "[17] If an uncaught runtime script error occurs during the above step, then: + * [17.1] Invoke Reject Job Promise with job and TypeError" + * + * (Where the "uncaught runtime script error" is thrown by an unsuccessful + * importScripts()) + */ + promise_test(function(t) { + var script = 'resources/import-mime-type-worker.py'; + var scope = 'resources/scope/no-mime-type-worker/'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of no MIME type imported script should fail.'); + }, 'Registering script that imports script with no MIME type'); + + promise_test(function(t) { + var script = 'resources/import-mime-type-worker.py?mime=text/plain'; + var scope = 'resources/scope/bad-mime-type-worker/'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of plain text imported script should fail.'); + }, 'Registering script that imports script with bad MIME type'); + + const validMimeTypes = [ + 'application/ecmascript', + 'application/javascript', + 'application/x-ecmascript', + 'application/x-javascript', + 'text/ecmascript', + 'text/javascript', + 'text/javascript1.0', + 'text/javascript1.1', + 'text/javascript1.2', + 'text/javascript1.3', + 'text/javascript1.4', + 'text/javascript1.5', + 'text/jscript', + 'text/livescript', + 'text/x-ecmascript', + 'text/x-javascript' + ]; + + for (const validMimeType of validMimeTypes) { + promise_test(() => { + var script = `resources/mime-type-worker.py?mime=${validMimeType}`; + var scope = 'resources/scope/good-mime-type-worker/'; + + return register_method(script, {scope}).then(registration => { + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + return registration.unregister(); + }); + }, `Registering script with good MIME type ${validMimeType}`); + + promise_test(() => { + var script = `resources/import-mime-type-worker.py?mime=${validMimeType}`; + var scope = 'resources/scope/good-mime-type-worker/'; + + return register_method(script, { scope }).then(registration => { + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + return registration.unregister(); + }); + }, `Registering script that imports script with good MIME type ${validMimeType}`); + } +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/registration-tests-scope.js b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-scope.js new file mode 100644 index 00000000000..30c424b2b42 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-scope.js @@ -0,0 +1,120 @@ +// Registration tests that mostly exercise the scope option. +function registration_tests_scope(register_method) { + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/scope%2fencoded-slash-in-scope'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'URL-encoded slash in the scope should be rejected.'); + }, 'Scope including URL-encoded slash'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/scope%5cencoded-slash-in-scope'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'URL-encoded backslash in the scope should be rejected.'); + }, 'Scope including URL-encoded backslash'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'data:text/html,'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'scope URL scheme is not "http" or "https"'); + }, 'Scope URL scheme is a data: URL'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = new URL('resources', location).href.replace('https:', 'ftp:'); + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'scope URL scheme is not "http" or "https"'); + }, 'Scope URL scheme is an ftp: URL'); + + promise_test(function(t) { + // URL-encoded full-width 'scope'. + var name = '%ef%bd%93%ef%bd%83%ef%bd%8f%ef%bd%90%ef%bd%85'; + var script = 'resources/empty-worker.js'; + var scope = 'resources/' + name + '/escaped-multibyte-character-scope'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL(scope), + 'URL-encoded multibyte characters should be available.'); + return registration.unregister(); + }); + }, 'Scope including URL-encoded multibyte characters'); + + promise_test(function(t) { + // Non-URL-encoded full-width "scope". + var name = String.fromCodePoint(0xff53, 0xff43, 0xff4f, 0xff50, 0xff45); + var script = 'resources/empty-worker.js'; + var scope = 'resources/' + name + '/non-escaped-multibyte-character-scope'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL(scope), + 'Non-URL-encoded multibyte characters should be available.'); + return registration.unregister(); + }); + }, 'Scope including non-escaped multibyte characters'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/././scope/self-reference-in-scope'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL('resources/scope/self-reference-in-scope'), + 'Scope including self-reference should be normalized.'); + return registration.unregister(); + }); + }, 'Scope including self-reference'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/../resources/scope/parent-reference-in-scope'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + registration.scope, + normalizeURL('resources/scope/parent-reference-in-scope'), + 'Scope including parent-reference should be normalized.'); + return registration.unregister(); + }); + }, 'Scope including parent-reference'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/scope////consecutive-slashes-in-scope'; + return register_method(script, {scope: scope}) + .then(function(registration) { + // Although consecutive slashes in the scope are not unified, the + // scope is under the script directory and registration should + // succeed. + assert_equals( + registration.scope, + normalizeURL(scope), + 'Should successfully be registered.'); + return registration.unregister(); + }) + }, 'Scope including consecutive slashes'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'filesystem:' + normalizeURL('resources/scope/filesystem-scope-url'); + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registering with the scope that has same-origin filesystem: URL ' + + 'should fail with TypeError.'); + }, 'Scope URL is same-origin filesystem: URL'); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/registration-tests-script-url.js b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-script-url.js new file mode 100644 index 00000000000..55cbe6fa959 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-script-url.js @@ -0,0 +1,82 @@ +// Registration tests that mostly exercise the scriptURL parameter. +function registration_tests_script_url(register_method) { + promise_test(function(t) { + var script = 'resources%2fempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'URL-encoded slash in the script URL should be rejected.'); + }, 'Script URL including URL-encoded slash'); + + promise_test(function(t) { + var script = 'resources%2Fempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'URL-encoded slash in the script URL should be rejected.'); + }, 'Script URL including uppercase URL-encoded slash'); + + promise_test(function(t) { + var script = 'resources%5cempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'URL-encoded backslash in the script URL should be rejected.'); + }, 'Script URL including URL-encoded backslash'); + + promise_test(function(t) { + var script = 'resources%5Cempty-worker.js'; + var scope = 'resources/scope/encoded-slash-in-script-url'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'URL-encoded backslash in the script URL should be rejected.'); + }, 'Script URL including uppercase URL-encoded backslash'); + + promise_test(function(t) { + var script = 'data:application/javascript,'; + var scope = 'resources/scope/data-url-in-script-url'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Data URLs should not be registered as service workers.'); + }, 'Script URL is a data URL'); + + promise_test(function(t) { + var script = 'data:application/javascript,'; + var scope = new URL('resources/scope/data-url-in-script-url', location); + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Data URLs should not be registered as service workers.'); + }, 'Script URL is a data URL and scope URL is not relative'); + + promise_test(function(t) { + var script = 'resources/././empty-worker.js'; + var scope = 'resources/scope/parent-reference-in-script-url'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + get_newest_worker(registration).scriptURL, + normalizeURL('resources/empty-worker.js'), + 'Script URL including self-reference should be normalized.'); + return registration.unregister(); + }); + }, 'Script URL including self-reference'); + + promise_test(function(t) { + var script = 'resources/../resources/empty-worker.js'; + var scope = 'resources/scope/parent-reference-in-script-url'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_equals( + get_newest_worker(registration).scriptURL, + normalizeURL('resources/empty-worker.js'), + 'Script URL including parent-reference should be normalized.'); + return registration.unregister(); + }); + }, 'Script URL including parent-reference'); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/registration-tests-script.js b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-script.js new file mode 100644 index 00000000000..e5bdaf4291a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-script.js @@ -0,0 +1,121 @@ +// Registration tests that mostly exercise the service worker script contents or +// response. +function registration_tests_script(register_method, type) { + promise_test(function(t) { + var script = 'resources/invalid-chunked-encoding.py'; + var scope = 'resources/scope/invalid-chunked-encoding/'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of invalid chunked encoding script should fail.'); + }, 'Registering invalid chunked encoding script'); + + promise_test(function(t) { + var script = 'resources/invalid-chunked-encoding-with-flush.py'; + var scope = 'resources/scope/invalid-chunked-encoding-with-flush/'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of invalid chunked encoding script should fail.'); + }, 'Registering invalid chunked encoding script with flush'); + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?parse-error'; + var scope = 'resources/scope/parse-error'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of script including parse error should fail.'); + }, 'Registering script including parse error'); + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?undefined-error'; + var scope = 'resources/scope/undefined-error'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of script including undefined error should fail.'); + }, 'Registering script including undefined error'); + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?uncaught-exception'; + var scope = 'resources/scope/uncaught-exception'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of script including uncaught exception should fail.'); + }, 'Registering script including uncaught exception'); + + if (type === 'classic') { + promise_test(function(t) { + var script = 'resources/malformed-worker.py?import-malformed-script'; + var scope = 'resources/scope/import-malformed-script'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of script importing malformed script should fail.'); + }, 'Registering script importing malformed script'); + } + + if (type === 'module') { + promise_test(function(t) { + var script = 'resources/malformed-worker.py?top-level-await'; + var scope = 'resources/scope/top-level-await'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of script with top-level await should fail.'); + }, 'Registering script with top-level await'); + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?instantiation-error'; + var scope = 'resources/scope/instantiation-error'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of script with module instantiation error should fail.'); + }, 'Registering script with module instantiation error'); + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?instantiation-error-and-top-level-await'; + var scope = 'resources/scope/instantiation-error-and-top-level-await'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of script with module instantiation error and top-level await should fail.'); + }, 'Registering script with module instantiation error and top-level await'); + } + + promise_test(function(t) { + var script = 'resources/no-such-worker.js'; + var scope = 'resources/scope/no-such-worker'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of non-existent script should fail.'); + }, 'Registering non-existent script'); + + if (type === 'classic') { + promise_test(function(t) { + var script = 'resources/malformed-worker.py?import-no-such-script'; + var scope = 'resources/scope/import-no-such-script'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registration of script importing non-existent script should fail.'); + }, 'Registering script importing non-existent script'); + } + + promise_test(function(t) { + var script = 'resources/malformed-worker.py?caught-exception'; + var scope = 'resources/scope/caught-exception'; + return register_method(script, {scope: scope}) + .then(function(registration) { + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + return registration.unregister(); + }); + }, 'Registering script including caught exception'); + +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/registration-tests-security-error.js b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-security-error.js new file mode 100644 index 00000000000..c45fbd45789 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/registration-tests-security-error.js @@ -0,0 +1,78 @@ +// Registration tests that mostly exercise SecurityError cases. +function registration_tests_security_error(register_method) { + promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'resources'; + return promise_rejects_dom(t, + 'SecurityError', + register_method(script, {scope: scope}), + 'Registering same scope as the script directory without the last ' + + 'slash should fail with SecurityError.'); + }, 'Registering same scope as the script directory without the last slash'); + + promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'different-directory/'; + return promise_rejects_dom(t, + 'SecurityError', + register_method(script, {scope: scope}), + 'Registration scope outside the script directory should fail ' + + 'with SecurityError.'); + }, 'Registration scope outside the script directory'); + + promise_test(function(t) { + var script = 'resources/registration-worker.js'; + var scope = 'http://example.com/'; + return promise_rejects_dom(t, + 'SecurityError', + register_method(script, {scope: scope}), + 'Registration scope outside domain should fail with SecurityError.'); + }, 'Registering scope outside domain'); + + promise_test(function(t) { + var script = 'http://example.com/worker.js'; + var scope = 'http://example.com/scope/'; + return promise_rejects_dom(t, + 'SecurityError', + register_method(script, {scope: scope}), + 'Registration script outside domain should fail with SecurityError.'); + }, 'Registering script outside domain'); + + promise_test(function(t) { + var script = 'resources/redirect.py?Redirect=' + + encodeURIComponent('/resources/registration-worker.js'); + var scope = 'resources/scope/redirect/'; + return promise_rejects_dom(t, + 'SecurityError', + register_method(script, {scope: scope}), + 'Registration of redirected script should fail.'); + }, 'Registering redirected script'); + + promise_test(function(t) { + var script = 'resources/empty-worker.js'; + var scope = 'resources/../scope/parent-reference-in-scope'; + return promise_rejects_dom(t, + 'SecurityError', + register_method(script, {scope: scope}), + 'Scope not under the script directory should be rejected.'); + }, 'Scope including parent-reference and not under the script directory'); + + promise_test(function(t) { + var script = 'resources////empty-worker.js'; + var scope = 'resources/scope/consecutive-slashes-in-script-url'; + return promise_rejects_dom(t, + 'SecurityError', + register_method(script, {scope: scope}), + 'Consecutive slashes in the script url should not be unified.'); + }, 'Script URL including consecutive slashes'); + + promise_test(function(t) { + var script = 'filesystem:' + normalizeURL('resources/empty-worker.js'); + var scope = 'resources/scope/filesystem-script-url'; + return promise_rejects_js(t, + TypeError, + register_method(script, {scope: scope}), + 'Registering a script which has same-origin filesystem: URL should ' + + 'fail with TypeError.'); + }, 'Script URL is same-origin filesystem: URL'); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/registration-worker.js b/test/wpt/tests/service-workers/service-worker/resources/registration-worker.js new file mode 100644 index 00000000000..44d1d2774a2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/registration-worker.js @@ -0,0 +1 @@ +// empty for now diff --git a/test/wpt/tests/service-workers/service-worker/resources/reject-install-worker.js b/test/wpt/tests/service-workers/service-worker/resources/reject-install-worker.js new file mode 100644 index 00000000000..41f07fd5db8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/reject-install-worker.js @@ -0,0 +1,3 @@ +self.oninstall = function(event) { + event.waitUntil(Promise.reject()); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/reply-to-message.html b/test/wpt/tests/service-workers/service-worker/resources/reply-to-message.html new file mode 100644 index 00000000000..8a70e2ad933 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/reply-to-message.html @@ -0,0 +1,7 @@ + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/request-end-to-end-worker.js b/test/wpt/tests/service-workers/service-worker/resources/request-end-to-end-worker.js new file mode 100644 index 00000000000..6bd2b72137e --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/request-end-to-end-worker.js @@ -0,0 +1,34 @@ +'use strict'; + +onfetch = function(e) { + var headers = {}; + for (var header of e.request.headers) { + var key = header[0], value = header[1]; + headers[key] = value; + } + var append_header_error = ''; + try { + e.request.headers.append('Test-Header', 'TestValue'); + } catch (error) { + append_header_error = error.name; + } + + var request_construct_error = ''; + try { + new Request(e.request, {method: 'GET'}); + } catch (error) { + request_construct_error = error.name; + } + + e.respondWith(new Response(JSON.stringify({ + url: e.request.url, + method: e.request.method, + referrer: e.request.referrer, + headers: headers, + mode: e.request.mode, + credentials: e.request.credentials, + redirect: e.request.redirect, + append_header_error: append_header_error, + request_construct_error: request_construct_error + }))); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/request-headers.py b/test/wpt/tests/service-workers/service-worker/resources/request-headers.py new file mode 100644 index 00000000000..6ab148e22e7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/request-headers.py @@ -0,0 +1,8 @@ +import json + +from wptserve.utils import isomorphic_decode + +def main(request, response): + data = {isomorphic_decode(key):isomorphic_decode(request.headers[key]) for key, value in request.headers.items()} + + return [(b"Content-Type", b"application/json")], json.dumps(data) diff --git a/test/wpt/tests/service-workers/service-worker/resources/resource-timing-iframe.sub.html b/test/wpt/tests/service-workers/service-worker/resources/resource-timing-iframe.sub.html new file mode 100644 index 00000000000..384c29b536b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/resource-timing-iframe.sub.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/resource-timing-worker.js b/test/wpt/tests/service-workers/service-worker/resources/resource-timing-worker.js new file mode 100644 index 00000000000..b74e8cd6a23 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/resource-timing-worker.js @@ -0,0 +1,12 @@ +self.addEventListener('fetch', function(event) { + if (event.request.url.indexOf('sample.js') != -1) { + event.respondWith(new Promise(resolve => { + // Slightly delay the response so we ensure we get a non-zero + // duration. + setTimeout(_ => resolve(new Response('// Empty javascript')), 50); + })); + } + else if (event.request.url.indexOf('missing.jpg?SWRespondsWithFetch') != -1) { + event.respondWith(fetch('sample.txt?SWFetched')); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/respond-then-throw-worker.js b/test/wpt/tests/service-workers/service-worker/resources/respond-then-throw-worker.js new file mode 100644 index 00000000000..adb48de69e7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/respond-then-throw-worker.js @@ -0,0 +1,40 @@ +var syncport = null; + +self.addEventListener('message', function(e) { + if ('port' in e.data) { + if (syncport) { + syncport(e.data.port); + } else { + syncport = e.data.port; + } + } +}); + +function sync() { + return new Promise(function(resolve) { + if (syncport) { + resolve(syncport); + } else { + syncport = resolve; + } + }).then(function(port) { + port.postMessage('SYNC'); + return new Promise(function(resolve) { + port.onmessage = function(e) { + if (e.data === 'ACK') { + resolve(); + } + } + }); + }); +} + + +self.addEventListener('fetch', function(event) { + // In Firefox the result would depend on a race between fetch handling + // and exception handling code. On the assumption that this might be a common + // design error, we explicitly allow the exception to be handled first. + event.respondWith(sync().then(() => new Response('intercepted'))); + + throw("error"); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/respond-with-body-accessed-response-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/respond-with-body-accessed-response-iframe.html new file mode 100644 index 00000000000..7be31487949 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/respond-with-body-accessed-response-iframe.html @@ -0,0 +1,20 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/respond-with-body-accessed-response-worker.js b/test/wpt/tests/service-workers/service-worker/resources/respond-with-body-accessed-response-worker.js new file mode 100644 index 00000000000..c602109bc68 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/respond-with-body-accessed-response-worker.js @@ -0,0 +1,93 @@ +importScripts('/common/get-host-info.sub.js'); +importScripts('test-helpers.sub.js'); + +function getQueryParams(url) { + var search = (new URL(url)).search; + if (!search) { + return {}; + } + var ret = {}; + var params = search.substring(1).split('&'); + params.forEach(function(param) { + var element = param.split('='); + ret[decodeURIComponent(element[0])] = decodeURIComponent(element[1]); + }); + return ret; +} + +function createResponse(params) { + if (params['type'] == 'basic') { + return fetch('respond-with-body-accessed-response.jsonp'); + } + if (params['type'] == 'opaque') { + return fetch(get_host_info()['HTTPS_REMOTE_ORIGIN'] + base_path() + + 'respond-with-body-accessed-response.jsonp', + {mode: 'no-cors'}); + } + if (params['type'] == 'default') { + return Promise.resolve(new Response('callback(\'OK\');')); + } + + return Promise.reject(new Error('unexpected type :' + params['type'])); +} + +function cloneResponseIfNeeded(params, response) { + if (params['clone'] == '1') { + return response.clone(); + } else if (params['clone'] == '2') { + response.clone(); + return response; + } + return response; +} + +function passThroughCacheIfNeeded(params, request, response) { + return new Promise(function(resolve) { + if (params['passThroughCache'] == 'true') { + var cache_name = request.url; + var cache; + self.caches.delete(cache_name) + .then(function() { + return self.caches.open(cache_name); + }) + .then(function(c) { + cache = c; + return cache.put(request, response); + }) + .then(function() { + return cache.match(request.url); + }) + .then(function(res) { + // Touch .body here to test the behavior after touching it. + res.body; + resolve(res); + }); + } else { + resolve(response); + } + }) +} + +self.addEventListener('fetch', function(event) { + if (event.request.url.indexOf('TestRequest') == -1) { + return; + } + var params = getQueryParams(event.request.url); + event.respondWith( + createResponse(params) + .then(function(response) { + // Touch .body here to test the behavior after touching it. + response.body; + return cloneResponseIfNeeded(params, response); + }) + .then(function(response) { + // Touch .body here to test the behavior after touching it. + response.body; + return passThroughCacheIfNeeded(params, event.request, response); + }) + .then(function(response) { + // Touch .body here to test the behavior after touching it. + response.body; + return response; + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/respond-with-body-accessed-response.jsonp b/test/wpt/tests/service-workers/service-worker/resources/respond-with-body-accessed-response.jsonp new file mode 100644 index 00000000000..b9c28f51f90 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/respond-with-body-accessed-response.jsonp @@ -0,0 +1 @@ +callback('OK'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/sample-worker-interceptor.js b/test/wpt/tests/service-workers/service-worker/resources/sample-worker-interceptor.js new file mode 100644 index 00000000000..c06f8dd77b7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/sample-worker-interceptor.js @@ -0,0 +1,62 @@ +importScripts('/common/get-host-info.sub.js'); + +const text = 'worker loading intercepted by service worker'; +const dedicated_worker_script = `postMessage('${text}');`; +const shared_worker_script = + `onconnect = evt => evt.ports[0].postMessage('${text}');`; + +let source; +let resolveDone; +let done = new Promise(resolve => resolveDone = resolve); + +// The page messages this worker to ask for the result. Keep the worker alive +// via waitUntil() until the result is sent. +self.addEventListener('message', event => { + source = event.data.port; + source.postMessage({id: event.source.id}); + source.onmessage = resolveDone; + event.waitUntil(done); +}); + +self.onfetch = event => { + const url = event.request.url; + const destination = event.request.destination; + + if (source) + source.postMessage({clientId:event.clientId, resultingClientId: event.resultingClientId}); + + // Request handler for a synthesized response. + if (url.indexOf('synthesized') != -1) { + let script_headers = new Headers({ "Content-Type": "text/javascript" }); + if (destination === 'worker') + event.respondWith(new Response(dedicated_worker_script, { 'headers': script_headers })); + else if (destination === 'sharedworker') + event.respondWith(new Response(shared_worker_script, { 'headers': script_headers })); + else + event.respondWith(new Response('Unexpected request! ' + destination)); + return; + } + + // Request handler for a same-origin response. + if (url.indexOf('same-origin') != -1) { + event.respondWith(fetch('postmessage-on-load-worker.js')); + return; + } + + // Request handler for a cross-origin response. + if (url.indexOf('cors') != -1) { + const filename = 'postmessage-on-load-worker.js'; + const path = (new URL(filename, self.location)).pathname; + let new_url = get_host_info()['HTTPS_REMOTE_ORIGIN'] + path; + let mode; + if (url.indexOf('no-cors') != -1) { + // Test no-cors mode. + mode = 'no-cors'; + } else { + // Test cors mode. + new_url += '?pipe=header(Access-Control-Allow-Origin,*)'; + mode = 'cors'; + } + event.respondWith(fetch(new_url, { mode: mode })); + } +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/sample.html b/test/wpt/tests/service-workers/service-worker/resources/sample.html new file mode 100644 index 00000000000..12a179980df --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/sample.html @@ -0,0 +1,2 @@ + +Hello world diff --git a/test/wpt/tests/service-workers/service-worker/resources/sample.js b/test/wpt/tests/service-workers/service-worker/resources/sample.js new file mode 100644 index 00000000000..b8889db05d0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/sample.js @@ -0,0 +1 @@ +var hello = "world"; diff --git a/test/wpt/tests/service-workers/service-worker/resources/sample.txt b/test/wpt/tests/service-workers/service-worker/resources/sample.txt new file mode 100644 index 00000000000..802992c4220 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/sample.txt @@ -0,0 +1 @@ +Hello world diff --git a/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.html new file mode 100644 index 00000000000..239fa733037 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.html @@ -0,0 +1,63 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.py b/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.py new file mode 100644 index 00000000000..0281b6c2755 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-iframe.py @@ -0,0 +1,18 @@ +import os.path + +from wptserve.utils import isomorphic_decode + +def main(request, response): + header = [(b'Content-Type', b'text/html')] + if b'test' in request.GET: + with open(os.path.join(os.path.dirname(isomorphic_decode(__file__)), u'sample.js'), u'r') as f: + body = f.read() + return (header, body) + + if b'sandbox' in request.GET: + header.append((b'Content-Security-Policy', + b'sandbox %s' % request.GET[b'sandbox'])) + with open(os.path.join(os.path.dirname(isomorphic_decode(__file__)), + u'sandboxed-iframe-fetch-event-iframe.html'), u'r') as f: + body = f.read() + return (header, body) diff --git a/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-worker.js b/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-worker.js new file mode 100644 index 00000000000..4035a8b19b9 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-fetch-event-worker.js @@ -0,0 +1,20 @@ +var requests = []; + +self.addEventListener('message', function(event) { + event.waitUntil(self.clients.matchAll() + .then(function(clients) { + var client_urls = []; + for(var client of clients){ + client_urls.push(client.url); + } + client_urls = client_urls.sort(); + event.data.port.postMessage( + {clients: client_urls, requests: requests}); + requests = []; + })); + }); + +self.addEventListener('fetch', function(event) { + requests.push(event.request.url); + event.respondWith(fetch(event.request)); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-navigator-serviceworker-iframe.html b/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-navigator-serviceworker-iframe.html new file mode 100644 index 00000000000..1d682e47ef5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/sandboxed-iframe-navigator-serviceworker-iframe.html @@ -0,0 +1,25 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/scope1/module-worker-importing-redirect-to-scope2.js b/test/wpt/tests/service-workers/service-worker/resources/scope1/module-worker-importing-redirect-to-scope2.js new file mode 100644 index 00000000000..ae681ba30e0 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/scope1/module-worker-importing-redirect-to-scope2.js @@ -0,0 +1 @@ +import * as module from './redirect.py?Redirect=/service-workers/service-worker/resources/scope2/imported-module-script.js'; diff --git a/test/wpt/tests/service-workers/service-worker/resources/scope1/module-worker-importing-scope2.js b/test/wpt/tests/service-workers/service-worker/resources/scope1/module-worker-importing-scope2.js new file mode 100644 index 00000000000..e28505249c2 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/scope1/module-worker-importing-scope2.js @@ -0,0 +1 @@ +import * as module from '../scope2/imported-module-script.js'; diff --git a/test/wpt/tests/service-workers/service-worker/resources/scope1/redirect.py b/test/wpt/tests/service-workers/service-worker/resources/scope1/redirect.py new file mode 100644 index 00000000000..bb4c874aace --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/scope1/redirect.py @@ -0,0 +1,6 @@ +import os +import imp +# Use the file from the parent directory. +mod = imp.load_source("_parent", os.path.join(os.path.dirname(os.path.dirname(__file__)), + os.path.basename(__file__))) +main = mod.main diff --git a/test/wpt/tests/service-workers/service-worker/resources/scope2/import-scripts-echo.py b/test/wpt/tests/service-workers/service-worker/resources/scope2/import-scripts-echo.py new file mode 100644 index 00000000000..5f785b5cc27 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/scope2/import-scripts-echo.py @@ -0,0 +1,6 @@ +def main(req, res): + return ([ + (b'Cache-Control', b'no-cache, must-revalidate'), + (b'Pragma', b'no-cache'), + (b'Content-Type', b'application/javascript')], + b'echo_output = "%s (scope2/)";\n' % req.GET[b'msg']) diff --git a/test/wpt/tests/service-workers/service-worker/resources/scope2/imported-module-script.js b/test/wpt/tests/service-workers/service-worker/resources/scope2/imported-module-script.js new file mode 100644 index 00000000000..a18e704a3c5 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/scope2/imported-module-script.js @@ -0,0 +1,4 @@ +export const imported = 'A module script.'; +onmessage = msg => { + msg.source.postMessage('pong'); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/scope2/simple.txt b/test/wpt/tests/service-workers/service-worker/resources/scope2/simple.txt new file mode 100644 index 00000000000..cd876676e85 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/scope2/simple.txt @@ -0,0 +1 @@ +a simple text file (scope2/) diff --git a/test/wpt/tests/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py b/test/wpt/tests/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py new file mode 100644 index 00000000000..bb4c874aace --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py @@ -0,0 +1,6 @@ +import os +import imp +# Use the file from the parent directory. +mod = imp.load_source("_parent", os.path.join(os.path.dirname(os.path.dirname(__file__)), + os.path.basename(__file__))) +main = mod.main diff --git a/test/wpt/tests/service-workers/service-worker/resources/secure-context-service-worker.js b/test/wpt/tests/service-workers/service-worker/resources/secure-context-service-worker.js new file mode 100644 index 00000000000..5ba99f07536 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/secure-context-service-worker.js @@ -0,0 +1,21 @@ +self.addEventListener('fetch', event => { + let url = new URL(event.request.url); + if (url.pathname.indexOf('sender.html') != -1) { + event.respondWith(new Response( + "", + { headers: { 'Content-Type': 'text/html'} } + )); + } else if (url.pathname.indexOf('report') != -1) { + self.clients.matchAll().then(clients => { + for (client of clients) { + client.postMessage(url.searchParams.get('result')); + } + }); + event.respondWith( + new Response( + '', + { headers: { 'Content-Type': 'text/html'} } + ) + ); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/secure-context/sender.html b/test/wpt/tests/service-workers/service-worker/resources/secure-context/sender.html new file mode 100644 index 00000000000..05e58822a86 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/secure-context/sender.html @@ -0,0 +1 @@ + diff --git a/test/wpt/tests/service-workers/service-worker/resources/secure-context/window.html b/test/wpt/tests/service-workers/service-worker/resources/secure-context/window.html new file mode 100644 index 00000000000..071a507cb33 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/secure-context/window.html @@ -0,0 +1,15 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/service-worker-csp-worker.py b/test/wpt/tests/service-workers/service-worker/resources/service-worker-csp-worker.py new file mode 100644 index 00000000000..35a46964a78 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/service-worker-csp-worker.py @@ -0,0 +1,183 @@ +bodyDefault = b''' +importScripts('worker-testharness.js'); +importScripts('test-helpers.sub.js'); +importScripts('/common/get-host-info.sub.js'); + +var host_info = get_host_info(); + +test(function() { + var import_script_failed = false; + try { + importScripts(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'empty.js'); + } catch(e) { + import_script_failed = true; + } + assert_true(import_script_failed, + 'Importing the other origins script should fail.'); + }, 'importScripts test for default-src'); + +test(function() { + assert_throws_js(EvalError, + function() { eval('1 + 1'); }, + 'eval() should throw EvalError.') + assert_throws_js(EvalError, + function() { new Function('1 + 1'); }, + 'new Function() should throw EvalError.') + }, 'eval test for default-src'); + +async_test(function(t) { + fetch(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?ACAOrigin=*', + {mode: 'cors'}) + .then(function(response){ + assert_unreached('fetch should fail.'); + }, function(){ + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Fetch test for default-src'); + +async_test(function(t) { + var REDIRECT_URL = host_info.HTTPS_ORIGIN + + base_path() + 'redirect.py?Redirect='; + var OTHER_BASE_URL = host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?' + fetch(REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'), + {mode: 'cors'}) + .then(function(response){ + assert_unreached('Redirected fetch should fail.'); + }, function(){ + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Redirected fetch test for default-src');''' + +bodyScript = b''' +importScripts('worker-testharness.js'); +importScripts('test-helpers.sub.js'); +importScripts('/common/get-host-info.sub.js'); + +var host_info = get_host_info(); + +test(function() { + var import_script_failed = false; + try { + importScripts(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'empty.js'); + } catch(e) { + import_script_failed = true; + } + assert_true(import_script_failed, + 'Importing the other origins script should fail.'); + }, 'importScripts test for script-src'); + +test(function() { + assert_throws_js(EvalError, + function() { eval('1 + 1'); }, + 'eval() should throw EvalError.') + assert_throws_js(EvalError, + function() { new Function('1 + 1'); }, + 'new Function() should throw EvalError.') + }, 'eval test for script-src'); + +async_test(function(t) { + fetch(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?ACAOrigin=*', + {mode: 'cors'}) + .then(function(response){ + t.done(); + }, function(){ + assert_unreached('fetch should not fail.'); + }) + .catch(unreached_rejection(t)); + }, 'Fetch test for script-src'); + +async_test(function(t) { + var REDIRECT_URL = host_info.HTTPS_ORIGIN + + base_path() + 'redirect.py?Redirect='; + var OTHER_BASE_URL = host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?' + fetch(REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'), + {mode: 'cors'}) + .then(function(response){ + t.done(); + }, function(){ + assert_unreached('Redirected fetch should not fail.'); + }) + .catch(unreached_rejection(t)); + }, 'Redirected fetch test for script-src');''' + +bodyConnect = b''' +importScripts('worker-testharness.js'); +importScripts('test-helpers.sub.js'); +importScripts('/common/get-host-info.sub.js'); + +var host_info = get_host_info(); + +test(function() { + var import_script_failed = false; + try { + importScripts(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'empty.js'); + } catch(e) { + import_script_failed = true; + } + assert_false(import_script_failed, + 'Importing the other origins script should not fail.'); + }, 'importScripts test for connect-src'); + +test(function() { + var eval_failed = false; + try { + eval('1 + 1'); + new Function('1 + 1'); + } catch(e) { + eval_failed = true; + } + assert_false(eval_failed, + 'connect-src without unsafe-eval should not block eval().'); + }, 'eval test for connect-src'); + +async_test(function(t) { + fetch(host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?ACAOrigin=*', + {mode: 'cors'}) + .then(function(response){ + assert_unreached('fetch should fail.'); + }, function(){ + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Fetch test for connect-src'); + +async_test(function(t) { + var REDIRECT_URL = host_info.HTTPS_ORIGIN + + base_path() + 'redirect.py?Redirect='; + var OTHER_BASE_URL = host_info.HTTPS_REMOTE_ORIGIN + + base_path() + 'fetch-access-control.py?' + fetch(REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL + 'ACAOrigin=*'), + {mode: 'cors'}) + .then(function(response){ + assert_unreached('Redirected fetch should fail.'); + }, function(){ + t.done(); + }) + .catch(unreached_rejection(t)); + }, 'Redirected fetch test for connect-src');''' + +def main(request, response): + headers = [] + headers.append((b'Content-Type', b'application/javascript')) + directive = request.GET[b'directive'] + body = b'ERROR: Unknown directive' + if directive == b'default': + headers.append((b'Content-Security-Policy', b"default-src 'self'")) + body = bodyDefault + elif directive == b'script': + headers.append((b'Content-Security-Policy', b"script-src 'self'")) + body = bodyScript + elif directive == b'connect': + headers.append((b'Content-Security-Policy', b"connect-src 'self'")) + body = bodyConnect + return headers, body diff --git a/test/wpt/tests/service-workers/service-worker/resources/service-worker-header.py b/test/wpt/tests/service-workers/service-worker/resources/service-worker-header.py new file mode 100644 index 00000000000..d64a9d24945 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/service-worker-header.py @@ -0,0 +1,20 @@ +def main(request, response): + service_worker_header = request.headers.get(b'service-worker') + + if b'header' in request.GET and service_worker_header != b'script': + return 400, [(b'Content-Type', b'text/plain')], b'Bad Request' + + if b'no-header' in request.GET and service_worker_header == b'script': + return 400, [(b'Content-Type', b'text/plain')], b'Bad Request' + + # no-cache itself to ensure the user agent finds a new version for each + # update. + headers = [(b'Cache-Control', b'no-cache, must-revalidate'), + (b'Pragma', b'no-cache'), + (b'Content-Type', b'application/javascript')] + body = b'/* This is a service worker script */\n' + + if b'import' in request.GET: + body += b"importScripts('%s');" % request.GET[b'import'] + + return 200, headers, body diff --git a/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-dynamic-import-worker.js b/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-dynamic-import-worker.js new file mode 100644 index 00000000000..680e07ff588 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-dynamic-import-worker.js @@ -0,0 +1 @@ +import('./service-worker-interception-network-worker.js'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-network-worker.js b/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-network-worker.js new file mode 100644 index 00000000000..5ff39001013 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-network-worker.js @@ -0,0 +1 @@ +postMessage('LOADED_FROM_NETWORK'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-service-worker.js b/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-service-worker.js new file mode 100644 index 00000000000..6b43a376963 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-service-worker.js @@ -0,0 +1,9 @@ +const kURL = '/service-worker-interception-network-worker.js'; +const kScript = 'postMessage("LOADED_FROM_SERVICE_WORKER")'; +const kHeaders = [['content-type', 'text/javascript']]; + +self.addEventListener('fetch', e => { + // Serve a generated response for kURL. + if (e.request.url.indexOf(kURL) != -1) + e.respondWith(new Response(kScript, { headers: kHeaders })); +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-static-import-worker.js b/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-static-import-worker.js new file mode 100644 index 00000000000..e570958701d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/service-worker-interception-static-import-worker.js @@ -0,0 +1 @@ +import './service-worker-interception-network-worker.js'; diff --git a/test/wpt/tests/service-workers/service-worker/resources/silence.oga b/test/wpt/tests/service-workers/service-worker/resources/silence.oga new file mode 100644 index 00000000000..af591880436 Binary files /dev/null and b/test/wpt/tests/service-workers/service-worker/resources/silence.oga differ diff --git a/test/wpt/tests/service-workers/service-worker/resources/simple-intercept-worker.js b/test/wpt/tests/service-workers/service-worker/resources/simple-intercept-worker.js new file mode 100644 index 00000000000..f8b5f8c5cb7 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/simple-intercept-worker.js @@ -0,0 +1,5 @@ +self.onfetch = function(event) { + if (event.request.url.indexOf('simple') != -1) + event.respondWith( + new Response(new Blob(['intercepted by service worker']))); +}; diff --git a/test/wpt/tests/service-workers/service-worker/resources/simple-intercept-worker.js.headers b/test/wpt/tests/service-workers/service-worker/resources/simple-intercept-worker.js.headers new file mode 100644 index 00000000000..a17a9a3a12c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/simple-intercept-worker.js.headers @@ -0,0 +1 @@ +Content-Type: application/javascript diff --git a/test/wpt/tests/service-workers/service-worker/resources/simple.html b/test/wpt/tests/service-workers/service-worker/resources/simple.html new file mode 100644 index 00000000000..0c3e3e78707 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/simple.html @@ -0,0 +1,3 @@ + +Simple +Here's a simple html file. diff --git a/test/wpt/tests/service-workers/service-worker/resources/simple.txt b/test/wpt/tests/service-workers/service-worker/resources/simple.txt new file mode 100644 index 00000000000..9e3cb91fb9b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/simple.txt @@ -0,0 +1 @@ +a simple text file diff --git a/test/wpt/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js b/test/wpt/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js new file mode 100644 index 00000000000..6f7008bddcd --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/skip-waiting-installed-worker.js @@ -0,0 +1,33 @@ +var saw_activate_event = false + +self.addEventListener('activate', function() { + saw_activate_event = true; + }); + +self.addEventListener('message', function(event) { + var port = event.data.port; + event.waitUntil(self.skipWaiting() + .then(function(result) { + if (result !== undefined) { + port.postMessage('FAIL: Promise should be resolved with undefined'); + return; + } + + if (!saw_activate_event) { + port.postMessage( + 'FAIL: Promise should be resolved after activate event is dispatched'); + return; + } + + if (self.registration.active.state !== 'activating') { + port.postMessage( + 'FAIL: Promise should be resolved before ServiceWorker#state is set to activated'); + return; + } + + port.postMessage('PASS'); + }) + .catch(function(e) { + port.postMessage('FAIL: unexpected exception: ' + e); + })); + }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/skip-waiting-worker.js b/test/wpt/tests/service-workers/service-worker/resources/skip-waiting-worker.js new file mode 100644 index 00000000000..3fc1d1e237a --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/skip-waiting-worker.js @@ -0,0 +1,21 @@ +importScripts('worker-testharness.js'); + +promise_test(function() { + return skipWaiting() + .then(function(result) { + assert_equals(result, undefined, + 'Promise should be resolved with undefined'); + }) + .then(function() { + var promises = []; + for (var i = 0; i < 8; ++i) + promises.push(self.skipWaiting()); + return Promise.all(promises); + }) + .then(function(results) { + results.forEach(function(r) { + assert_equals(r, undefined, + 'Promises should be resolved with undefined'); + }); + }); + }, 'skipWaiting'); diff --git a/test/wpt/tests/service-workers/service-worker/resources/square.png b/test/wpt/tests/service-workers/service-worker/resources/square.png new file mode 100644 index 00000000000..01c9666a8de Binary files /dev/null and b/test/wpt/tests/service-workers/service-worker/resources/square.png differ diff --git a/test/wpt/tests/service-workers/service-worker/resources/square.png.sub.headers b/test/wpt/tests/service-workers/service-worker/resources/square.png.sub.headers new file mode 100644 index 00000000000..7341132745b --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/square.png.sub.headers @@ -0,0 +1,2 @@ +Content-Type: image/png +Access-Control-Allow-Origin: * diff --git a/test/wpt/tests/service-workers/service-worker/resources/stalling-service-worker.js b/test/wpt/tests/service-workers/service-worker/resources/stalling-service-worker.js new file mode 100644 index 00000000000..fdf1e6cac04 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/stalling-service-worker.js @@ -0,0 +1,54 @@ +async function post_message_to_client(role, message, ports) { + (await clients.matchAll()).forEach(client => { + if (new URL(client.url).searchParams.get('role') === role) { + client.postMessage(message, ports); + } + }); +} + +async function post_message_to_child(message, ports) { + await post_message_to_client('child', message, ports); +} + +function ping_message(data) { + return { type: 'ping', data }; +} + +self.onmessage = event => { + const message = ping_message(event.data); + post_message_to_child(message); + post_message_to_parent(message); +} + +async function post_message_to_parent(message, ports) { + await post_message_to_client('parent', message, ports); +} + +function fetch_message(key) { + return { type: 'fetch', key }; +} + +// Send a message to the parent along with a MessagePort to respond +// with. +function report_fetch_request(key) { + const channel = new MessageChannel(); + const reply = new Promise(resolve => { + channel.port1.onmessage = resolve; + }).then(event => event.data); + return post_message_to_parent(fetch_message(key), [channel.port2]).then(() => reply); +} + +function respond_with_script(script) { + return new Response(new Blob(script, { type: 'text/javascript' })); +} + +// Whenever a controlled document requests a URL with a 'key' search +// parameter we report the request to the parent frame and wait for +// a response. The content of the response is then used to respond to +// the fetch request. +addEventListener('fetch', event => { + let key = new URL(event.request.url).searchParams.get('key'); + if (key) { + event.respondWith(report_fetch_request(key).then(respond_with_script)); + } +}); diff --git a/test/wpt/tests/service-workers/service-worker/resources/subdir/blank.html b/test/wpt/tests/service-workers/service-worker/resources/subdir/blank.html new file mode 100644 index 00000000000..a3c3a4689a6 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/subdir/blank.html @@ -0,0 +1,2 @@ + +Empty doc diff --git a/test/wpt/tests/service-workers/service-worker/resources/subdir/import-scripts-echo.py b/test/wpt/tests/service-workers/service-worker/resources/subdir/import-scripts-echo.py new file mode 100644 index 00000000000..f745d7ae46f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/subdir/import-scripts-echo.py @@ -0,0 +1,6 @@ +def main(req, res): + return ([ + (b'Cache-Control', b'no-cache, must-revalidate'), + (b'Pragma', b'no-cache'), + (b'Content-Type', b'application/javascript')], + b'echo_output = "%s (subdir/)";\n' % req.GET[b'msg']) diff --git a/test/wpt/tests/service-workers/service-worker/resources/subdir/simple.txt b/test/wpt/tests/service-workers/service-worker/resources/subdir/simple.txt new file mode 100644 index 00000000000..86bcdd7dc56 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/subdir/simple.txt @@ -0,0 +1 @@ +a simple text file (subdir/) diff --git a/test/wpt/tests/service-workers/service-worker/resources/subdir/worker_interception_redirect_webworker.py b/test/wpt/tests/service-workers/service-worker/resources/subdir/worker_interception_redirect_webworker.py new file mode 100644 index 00000000000..bb4c874aace --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/subdir/worker_interception_redirect_webworker.py @@ -0,0 +1,6 @@ +import os +import imp +# Use the file from the parent directory. +mod = imp.load_source("_parent", os.path.join(os.path.dirname(os.path.dirname(__file__)), + os.path.basename(__file__))) +main = mod.main diff --git a/test/wpt/tests/service-workers/service-worker/resources/success.py b/test/wpt/tests/service-workers/service-worker/resources/success.py new file mode 100644 index 00000000000..a0269918ee8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/success.py @@ -0,0 +1,8 @@ +def main(request, response): + headers = [] + + if b"ACAOrigin" in request.GET: + for item in request.GET[b"ACAOrigin"].split(b","): + headers.append((b"Access-Control-Allow-Origin", item)) + + return headers, b"{ \"result\": \"success\" }" diff --git a/test/wpt/tests/service-workers/service-worker/resources/svg-target-reftest-001-frame.html b/test/wpt/tests/service-workers/service-worker/resources/svg-target-reftest-001-frame.html new file mode 100644 index 00000000000..59fb524049d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/svg-target-reftest-001-frame.html @@ -0,0 +1,3 @@ + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/svg-target-reftest-001.html b/test/wpt/tests/service-workers/service-worker/resources/svg-target-reftest-001.html new file mode 100644 index 00000000000..9a93d3b3704 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/svg-target-reftest-001.html @@ -0,0 +1,5 @@ + + +Green svg box reference file +

Pass if you see a green box below.

+ + diff --git a/test/wpt/tests/storage/resources/partitioned-estimate-usage-details-caches-helper-frame.html b/test/wpt/tests/storage/resources/partitioned-estimate-usage-details-caches-helper-frame.html new file mode 100644 index 00000000000..0679c1decf5 --- /dev/null +++ b/test/wpt/tests/storage/resources/partitioned-estimate-usage-details-caches-helper-frame.html @@ -0,0 +1,30 @@ + + + +Helper frame + + diff --git a/test/wpt/tests/storage/resources/partitioned-estimate-usage-details-indexeddb-helper-frame.html b/test/wpt/tests/storage/resources/partitioned-estimate-usage-details-indexeddb-helper-frame.html new file mode 100644 index 00000000000..fd2cfb669bd --- /dev/null +++ b/test/wpt/tests/storage/resources/partitioned-estimate-usage-details-indexeddb-helper-frame.html @@ -0,0 +1,28 @@ + + +Helper frame + + diff --git a/test/wpt/tests/storage/resources/partitioned-estimate-usage-details-service-workers-helper-frame.html b/test/wpt/tests/storage/resources/partitioned-estimate-usage-details-service-workers-helper-frame.html new file mode 100644 index 00000000000..25d7554868f --- /dev/null +++ b/test/wpt/tests/storage/resources/partitioned-estimate-usage-details-service-workers-helper-frame.html @@ -0,0 +1,30 @@ + + +Helper frame + + diff --git a/test/wpt/tests/storage/resources/worker.js b/test/wpt/tests/storage/resources/worker.js new file mode 100644 index 00000000000..9271c769b25 --- /dev/null +++ b/test/wpt/tests/storage/resources/worker.js @@ -0,0 +1,3 @@ +// Dummy service worker to observe some weight when querying the storage usage +// details from of the service worker from estimate(). +globalThis.oninstall = e => {}; diff --git a/test/wpt/tests/storage/storagemanager-estimate.https.any.js b/test/wpt/tests/storage/storagemanager-estimate.https.any.js new file mode 100644 index 00000000000..c2f5c569dc5 --- /dev/null +++ b/test/wpt/tests/storage/storagemanager-estimate.https.any.js @@ -0,0 +1,60 @@ +// META: title=StorageManager: estimate() + +test(function(t) { + assert_true(navigator.storage.estimate() instanceof Promise); +}, 'estimate() method returns a Promise'); + +promise_test(function(t) { + return navigator.storage.estimate().then(function(result) { + assert_equals(typeof result, 'object'); + assert_true('usage' in result); + assert_equals(typeof result.usage, 'number'); + assert_true('quota' in result); + assert_equals(typeof result.quota, 'number'); + }); +}, 'estimate() resolves to dictionary with members'); + +promise_test(function(t) { + const large_value = new Uint8Array(1e6); + const dbname = `db-${location}-${t.name}`; + let db, before, after; + + indexedDB.deleteDatabase(dbname); + return new Promise((resolve, reject) => { + const open = indexedDB.open(dbname); + open.onerror = () => { reject(open.error); }; + open.onupgradeneeded = () => { + const connection = open.result; + connection.createObjectStore('store'); + }; + open.onsuccess = () => { + const connection = open.result; + t.add_cleanup(() => { + connection.close(); + indexedDB.deleteDatabase(dbname); + }); + resolve(connection); + }; + }) + .then(connection => { + db = connection; + return navigator.storage.estimate(); + }) + .then(estimate => { + before = estimate.usage; + return new Promise((resolve, reject) => { + const tx = db.transaction('store', 'readwrite'); + tx.objectStore('store').put(large_value, 'key'); + tx.onabort = () => { reject(tx.error); }; + tx.oncomplete = () => { resolve(); }; + }); + }) + .then(() => { + return navigator.storage.estimate(); + }) + .then(estimate => { + after = estimate.usage; + assert_greater_than(after, before, + 'estimated usage should increase'); + }); +}, 'estimate() shows usage increase after 1MB IndexedDB record is stored'); diff --git a/test/wpt/tests/storage/storagemanager-persist.https.window.js b/test/wpt/tests/storage/storagemanager-persist.https.window.js new file mode 100644 index 00000000000..13e17a16e14 --- /dev/null +++ b/test/wpt/tests/storage/storagemanager-persist.https.window.js @@ -0,0 +1,10 @@ +// META: title=StorageManager: persist() + +promise_test(function() { + var promise = navigator.storage.persist(); + assert_true(promise instanceof Promise, + 'navigator.storage.persist() returned a Promise.'); + return promise.then(function(result) { + assert_equals(typeof result, 'boolean', result + ' should be boolean'); + }); +}, 'navigator.storage.persist() returns a promise that resolves.'); diff --git a/test/wpt/tests/storage/storagemanager-persist.https.worker.js b/test/wpt/tests/storage/storagemanager-persist.https.worker.js new file mode 100644 index 00000000000..fcf8175f706 --- /dev/null +++ b/test/wpt/tests/storage/storagemanager-persist.https.worker.js @@ -0,0 +1,8 @@ +// META: title=StorageManager: persist() (worker) +importScripts("/resources/testharness.js"); + +test(function() { + assert_false('persist' in navigator.storage); +}, 'navigator.storage.persist should not exist in workers'); + +done(); diff --git a/test/wpt/tests/storage/storagemanager-persisted.https.any.js b/test/wpt/tests/storage/storagemanager-persisted.https.any.js new file mode 100644 index 00000000000..70999406690 --- /dev/null +++ b/test/wpt/tests/storage/storagemanager-persisted.https.any.js @@ -0,0 +1,10 @@ +// META: title=StorageManager: persisted() + +promise_test(function() { + var promise = navigator.storage.persisted(); + assert_true(promise instanceof Promise, + 'navigator.storage.persisted() returned a Promise.'); + return promise.then(function (result) { + assert_equals(typeof result, 'boolean', result + ' should be boolean'); + }); +}, 'navigator.storage.persisted() returns a promise that resolves.'); diff --git a/types/cache.d.ts b/types/cache.d.ts new file mode 100644 index 00000000000..4767091be4b --- /dev/null +++ b/types/cache.d.ts @@ -0,0 +1,29 @@ +import type { RequestInfo, Response, Request } from './fetch' + +export interface CacheStorage { + match (request: RequestInfo, options?: MultiCacheQueryOptions): Promise, + has (cacheName: string): Promise, + open (cacheName: string): Promise, + delete (cacheName: string): Promise, + keys (): Promise +} + +export interface Cache { + match (request: RequestInfo, options?: CacheQueryOptions): Promise, + matchAll (request?: RequestInfo, options?: CacheQueryOptions): Promise, + add (request: RequestInfo): Promise, + addAll (requests: RequestInfo[]): Promise, + put (request: RequestInfo, response: Response): Promise, + delete (request: RequestInfo, options?: CacheQueryOptions): Promise, + keys (request?: RequestInfo, options?: CacheQueryOptions): Promise +} + +export interface CacheQueryOptions { + ignoreSearch?: boolean, + ignoreMethod?: boolean, + ignoreVary?: boolean +} + +export interface MultiCacheQueryOptions extends CacheQueryOptions { + cacheName?: string +} diff --git a/types/webidl.d.ts b/types/webidl.d.ts index 182d18e0d4c..40cfe064f8f 100644 --- a/types/webidl.d.ts +++ b/types/webidl.d.ts @@ -170,6 +170,8 @@ export interface Webidl { */ sequenceConverter (C: Converter): SequenceConverter + illegalConstructor (): never + /** * @see https://webidl.spec.whatwg.org/#es-to-record * @description Convert a value, V, to a WebIDL record type.