839 lines
20 KiB
JavaScript
839 lines
20 KiB
JavaScript
|
'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<RequestInfo>'](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<typeof fetching>[]} */
|
||
|
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 (request instanceof Request) {
|
||
|
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).then(bodyReadPromise.resolve, bodyReadPromise.reject)
|
||
|
} 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(request)[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-algorithm
|
||
|
* @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
|
||
|
// }
|
||
|
|
||
|
const queryURL = new URL(requestQuery.url)
|
||
|
|
||
|
const cachedURL = new URL(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
|
||
|
})
|
||
|
|
||
|
const cacheQueryOptionConverters = [
|
||
|
{
|
||
|
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.CacheQueryOptions = webidl.dictionaryConverter(cacheQueryOptionConverters)
|
||
|
|
||
|
webidl.converters.MultiCacheQueryOptions = webidl.dictionaryConverter([
|
||
|
...cacheQueryOptionConverters,
|
||
|
{
|
||
|
key: 'cacheName',
|
||
|
converter: webidl.converters.DOMString
|
||
|
}
|
||
|
])
|
||
|
|
||
|
webidl.converters.Response = webidl.interfaceConverter(Response)
|
||
|
|
||
|
webidl.converters['sequence<RequestInfo>'] = webidl.sequenceConverter(
|
||
|
webidl.converters.RequestInfo
|
||
|
)
|
||
|
|
||
|
module.exports = {
|
||
|
Cache
|
||
|
}
|