Skip to content

Commit

Permalink
Cache storage (nodejs#2076)
Browse files Browse the repository at this point in the history
* feat: implement basic outlines

* lint

* webidl cachestorage

* cache: implement CacheStorage methods

* cache: implement Cache.prototype.delete, partially?

* cache: implement Cache.prototype.add/All, partially?

* cache: implement Cache.prototype.put, partially?

* cache: implement Cache.prototype.match/All, partially?

* test: add cache-storage wpts

* cache: re-do CacheStorage implementation

* cache: remove unneeded utility

* cache: fix put, matchAll, addAll

* cache: fix multiple tests

* cache: implement Cache.prototype.keys

* cache: fix more bugs

* cache: skip more tests

* cache: fix more bugs, skip all failing tests
  • Loading branch information
KhafraDev authored and metcoder95 committed Jul 21, 2023
1 parent bb13eb2 commit 0f10efb
Show file tree
Hide file tree
Showing 745 changed files with 40,858 additions and 3 deletions.
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

0 comments on commit 0f10efb

Please sign in to comment.