Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache storage #2076

Merged
merged 17 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
834 changes: 834 additions & 0 deletions lib/cache/cache.js

Large diffs are not rendered by default.

133 changes: 133 additions & 0 deletions lib/cache/cachestorage.js
Original file line number Diff line number Diff line change
@@ -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<string, import('./cache').requestResponseList}
*/
#caches = new Map()

constructor () {
if (arguments[0] !== kConstruct) {
webidl.illegalConstructor()
}
}

async match (request, options = {}) {
webidl.brandCheck(this, CacheStorage)
webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.match' })

request = webidl.converters.RequestInfo(request)
options = webidl.converters.CacheQueryOptions(options)
}

/**
* @see https://w3c.github.io/ServiceWorker/#cache-storage-has
* @param {string} cacheName
* @returns {Promise<boolean>}
*/
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<Cache>}
*/
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<boolean>}
*/
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
}
5 changes: 5 additions & 0 deletions lib/cache/symbols.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict'

module.exports = {
kConstruct: Symbol('constructable')
}
49 changes: 49 additions & 0 deletions lib/cache/util.js
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 2 additions & 1 deletion lib/fetch/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,5 +569,6 @@ module.exports = {
makeResponse,
makeAppropriateNetworkError,
filterResponse,
Response
Response,
cloneResponse
}
3 changes: 2 additions & 1 deletion lib/fetch/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1028,5 +1028,6 @@ module.exports = {
isomorphicDecode,
urlIsLocal,
urlHasHttpsScheme,
urlIsHttpHttpsScheme
urlIsHttpHttpsScheme,
readAllBytes
}
7 changes: 7 additions & 0 deletions lib/fetch/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
15 changes: 15 additions & 0 deletions test/wpt/runner/worker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}
})

Expand Down
17 changes: 16 additions & 1 deletion test/wpt/server/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions test/wpt/start-cacheStorage.mjs
Original file line number Diff line number Diff line change
@@ -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')
}
})
}
}
54 changes: 54 additions & 0 deletions test/wpt/status/service-workers/cache-storage.status.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
}
}
6 changes: 6 additions & 0 deletions test/wpt/tests/service-workers/META.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
spec: https://w3c.github.io/ServiceWorker/
suggested_reviewers:
- asutherland
- mkruisselbrink
- mattto
- wanderview
3 changes: 3 additions & 0 deletions test/wpt/tests/service-workers/cache-storage/META.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
suggested_reviewers:
- inexorabletash
- wanderview
Loading