diff --git a/api/internal/geolocation.js b/api/internal/geolocation.js index 7c247cba8c..3d1fd94e22 100644 --- a/api/internal/geolocation.js +++ b/api/internal/geolocation.js @@ -64,7 +64,7 @@ function createGeolocationPosition (data) { altitude: { configurable: false, writable: false, - value: data.coords.altitude + value: data.coords.altitude ?? null }, accuracy: { @@ -95,12 +95,34 @@ function createGeolocationPosition (data) { configurable: false, writable: false, value: data.coords.speed <= 0 ? null : data.coords.speed + }, + + toJSON: { + configurable: false, + writable: true, + value: () => ({ + accuracy: data.coords.accuracy, + latitude: data.coords.latitude, + longitude: data.coords.longitude, + altitude: data.coords.altitude ?? null, + altitudeAccuracy: data.coords.altitudeAccuracy ?? null, + heading: data.coords.heading ?? null, + speed: data.coords.speed ?? null + }) } }) return Object.create(GeolocationPosition.prototype, { coords: { configurable: false, writable: false, value: coords }, - timestamp: { configurable: false, writable: false, value: Date.now() } + timestamp: { configurable: false, writable: false, value: Date.now() }, + toJSON: { + configurable: false, + writable: true, + value: () => ({ + coords: coords.toJSON(), + timestamp: Date.now() + }) + } }) } @@ -176,7 +198,8 @@ export async function getCurrentPosition ( timer = setTimeout(() => { didTimeout = true const error = Object.create(GeolocationPositionError.prototype, { - code: { value: GeolocationPositionError.TIMEOUT } + code: { value: GeolocationPositionError.TIMEOUT }, + message: { value: 'Position acquisition timed out' } }) if (typeof onError === 'function') { diff --git a/api/ipc.js b/api/ipc.js index 4d022f1f13..ea96b9abf3 100644 --- a/api/ipc.js +++ b/api/ipc.js @@ -1772,39 +1772,51 @@ export function findIPCMessageTransfers (transfers, object) { object[i] = findIPCMessageTransfers(transfers, object[i]) } } else if (object && typeof object === 'object') { - if (object instanceof MessagePort) { - if (object instanceof IPCMessagePort) { - const port = IPCMessagePort.create(object) - ports.get(port.id).channel.onmessage = (event) => { - if (port.closed === true) { - port.onmessage = null - event.preventDefault() - event.stopImmediatePropagation() - return false - } + if ( + object instanceof MessagePort || ( + typeof object.postMessage === 'function' && + Object.getPrototypeOf(object).constructor.name === 'MessagePort' + ) + ) { + const port = IPCMessagePort.create(object) + object.addEventListener('message', function onMessage (event) { + if (port.closed === true) { + port.onmessage = null + event.preventDefault() + event.stopImmediatePropagation() + object.removeEventListener('message', onMessage) + return false + } - if (port.started && event.data?.token !== port.token) { - console.log(event.data) - const transfers = new Set() - findIPCMessageTransfers(transfers, event.data) - object.postMessage(event.data, { - transfer: Array.from(transfers) - }) - } + port.dispatchEvent(new MessageEvent('message', event)) + }) + + port.onmessage = (event) => { + if (port.closed === true) { + port.onmessage = null + event.preventDefault() + event.stopImmediatePropagation() + return false } - add(port) - return port - } else { - add(object) - return object - } - } else if (Object.getPrototypeOf(object) === Object.prototype) { - for (const key in object) { - object[key] = findIPCMessageTransfers( - transfers, - object[key] - ) + + const transfers = new Set() + findIPCMessageTransfers(transfers, event.data) + object.postMessage(event.data, { + transfer: Array.from(transfers) + }) } + add(port) + return port + } else { + add(object) + return object + } + } else if (Object.getPrototypeOf(object) === Object.prototype) { + for (const key in object) { + object[key] = findIPCMessageTransfers( + transfers, + object[key] + ) } } diff --git a/api/mime/index.js b/api/mime/index.js index 81dc241d7f..b16afd923f 100644 --- a/api/mime/index.js +++ b/api/mime/index.js @@ -157,8 +157,16 @@ export class Database { query (query) { query = query.toLowerCase() - const queryParts = query.split('+') + if (query.startsWith('.')) { + query = query.slice(1) + } + const results = [] + const queryParts = query + .split('+') + .map((query) => query.trim()) + .map((query) => query.startsWith('.') ? query.slice(1) : query) + .filter(Boolean) for (const [key, value] of this.map.entries()) { const name = key.toLowerCase() diff --git a/api/service-worker/container.js b/api/service-worker/container.js index 061f5eef5e..65bdbbc007 100644 --- a/api/service-worker/container.js +++ b/api/service-worker/container.js @@ -1,7 +1,6 @@ /* global EventTarget */ import { ServiceWorkerRegistration } from './registration.js' -import { createServiceWorker, SHARED_WORKER_URL } from './instance.js' -import { SharedWorker } from '../shared-worker/index.js' +import { createServiceWorker } from './instance.js' import { Deferred } from '../async.js' import application from '../application.js' import location from '../location.js' @@ -23,8 +22,7 @@ class ServiceWorkerContainerInternalStateMap extends Map { class ServiceWorkerContainerInternalState { currentWindow = null controller = null - sharedWorker = null - channel = new BroadcastChannel('socket.runtime.ServiceWorkerContainer') + channel = new BroadcastChannel('socket.runtime.serviceWorker') ready = new Deferred() init = new Deferred() @@ -487,52 +485,26 @@ export class ServiceWorkerContainer extends EventTarget { return globalThis.top.navigator.serviceWorker.startMessages() } - if (!internal.get(this).sharedWorker && globalThis.RUNTIME_WORKER_LOCATION !== SHARED_WORKER_URL) { - internal.get(this).sharedWorker = new SharedWorker(SHARED_WORKER_URL) - internal.get(this).sharedWorker.port.start() - internal.get(this).sharedWorker.port.onmessage = async (event) => { - if ( - event.data?.from === 'realm' && - event.data?.registration?.id && - event.data?.client?.id === globalThis.__args.client.id && - event.data?.client?.type === globalThis.__args.client.type && - event.data?.client?.frameType === globalThis.__args.client.frameType - ) { - const registrations = await this.getRegistrations() - for (const registration of registrations) { - const info = registration[Symbol.for('socket.runtime.ServiceWorkerRegistration.info')] - if (info?.id === event.data.registration.id) { - const serviceWorker = createServiceWorker(state.serviceWorker.state, { - subscribe: false, - scriptURL: info.scriptURL, - id: info.id - }) - - const messageEvent = new MessageEvent('message', { - origin: new URL(info.scriptURL, location.origin).origin, - data: event.data.message - }) - - Object.defineProperty(messageEvent, 'source', { - configurable: false, - enumerable: false, - writable: false, - value: serviceWorker - }) - - this.dispatchEvent(messageEvent) - break - } - } - } else if ( - event.data?.from === 'instance' && - event.data?.registration?.id && - event.data?.client?.id && - event.data?.client?.id !== globalThis.__args.client.id - ) { - if (globalThis.isWorkerScope && globalThis.serviceWorker) { + internal.get(this).channel.onmessage = async (event) => { + if ( + event.data?.from === 'realm' && + event.data?.registration?.id && + event.data?.client?.id === globalThis.__args.client.id && + event.data?.client?.type === globalThis.__args.client.type && + event.data?.client?.frameType === globalThis.__args.client.frameType + ) { + const registrations = await this.getRegistrations() + for (const registration of registrations) { + const info = registration[Symbol.for('socket.runtime.ServiceWorkerRegistration.info')] + if (info?.id === event.data.registration.id) { + const serviceWorker = createServiceWorker(state.serviceWorker.state, { + subscribe: false, + scriptURL: info.scriptURL, + id: info.id + }) + const messageEvent = new MessageEvent('message', { - origin: event.data.client.origin.origin, + origin: new URL(info.scriptURL, location.origin).origin, data: event.data.message }) @@ -540,12 +512,34 @@ export class ServiceWorkerContainer extends EventTarget { configurable: false, enumerable: false, writable: false, - value: await globalThis.clients.get(event.data.client.id) + value: serviceWorker }) this.dispatchEvent(messageEvent) + break } } + } else if ( + event.data?.from === 'instance' && + event.data?.registration?.id && + event.data?.client?.id && + event.data?.client?.id !== globalThis.__args.client.id + ) { + if (globalThis.isWorkerScope && globalThis.serviceWorker) { + const messageEvent = new MessageEvent('message', { + origin: event.data.client.origin.origin, + data: event.data.message + }) + + Object.defineProperty(messageEvent, 'source', { + configurable: false, + enumerable: false, + writable: false, + value: await globalThis.clients.get(event.data.client.id) + }) + + this.dispatchEvent(messageEvent) + } } } } diff --git a/api/service-worker/frame.html b/api/service-worker/frame.html deleted file mode 100644 index 828400c539..0000000000 --- a/api/service-worker/frame.html +++ /dev/null @@ -1,145 +0,0 @@ - - -
- - - - - - - - - - - diff --git a/api/service-worker/init.js b/api/service-worker/init.js index a2f6339db2..5b54f7239d 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -1,6 +1,4 @@ /* global Worker */ -import { SHARED_WORKER_URL } from './instance.js' -import { SharedWorker } from '../shared-worker/index.js' import { Notification } from '../notification.js' import { sleep } from '../timers.js' import globals from '../internal/globals.js' @@ -9,13 +7,12 @@ import hooks from '../hooks.js' import ipc from '../ipc.js' export const workers = new Map() +export const channel = new BroadcastChannel('socket.runtime.serviceWorker') globals.register('ServiceWorkerContext.workers', workers) globals.register('ServiceWorkerContext.info', new Map()) -const sharedWorker = new SharedWorker(SHARED_WORKER_URL) -sharedWorker.port.start() -sharedWorker.port.onmessage = (event) => { +channel.onmessage = (event) => { if (event.data?.from === 'instance' && event.data.registration?.id) { for (const worker of workers.values()) { if (worker.info.id === event.data.registration.id) { @@ -24,9 +21,9 @@ sharedWorker.port.onmessage = (event) => { } } } else if (event.data?.showNotification && event.data.registration?.id) { - onNotificationShow(event, sharedWorker.port) + onNotificationShow(event) } else if (event.data?.getNotifications && event.data.registration?.id) { - onGetNotifications(event, sharedWorker.port) + onGetNotifications(event) } } @@ -79,7 +76,7 @@ export class ServiceWorkerInstance extends Worker { this.postMessage({ install: info }) info.promise.resolve() } else if (event.data?.message && event?.data.client?.id) { - sharedWorker.port.postMessage({ + channel.postMessage({ ...event.data, from: 'realm' }) @@ -344,7 +341,7 @@ export function onNotificationShow (event, target) { } } -export function onNotificationClose (event, target) { +export function onNotificationClose (event) { for (const worker of workers.values()) { for (const notification of worker.notifications) { if (event.data.notificationclose.id === notification.id) { @@ -355,10 +352,10 @@ export function onNotificationClose (event, target) { } } -export function onGetNotifications (event, target) { +export function onGetNotifications (event) { for (const worker of workers.values()) { if (worker.info.id === event.data.registration.id) { - return target.postMessage({ + return channel.postMessage({ nonce: event.data.nonce, notifications: worker.notifications .filter((notification) => @@ -392,10 +389,12 @@ hooks.onReady(async () => { __service_worker_frame_init: true }) - const result = await ipc.request('serviceWorker.getRegistrations') - if (Array.isArray(result.data)) { - for (const info of result.data) { - await navigator.serviceWorker.register(info.scriptURL, info) + if (workers.size === 0) { + const result = await ipc.request('serviceWorker.getRegistrations') + if (Array.isArray(result.data)) { + for (const info of result.data) { + await navigator.serviceWorker.register(info.scriptURL, info) + } } } }) diff --git a/api/service-worker/instance.js b/api/service-worker/instance.js index 7a2965d51b..46a6e45c0c 100644 --- a/api/service-worker/instance.js +++ b/api/service-worker/instance.js @@ -1,13 +1,10 @@ -import { SharedWorker } from '../shared-worker/index.js' import location from '../location.js' import state from './state.js' import ipc from '../ipc.js' const serviceWorkers = new Map() -let sharedWorker = null - -export const SHARED_WORKER_URL = `${globalThis.origin}/socket/service-worker/shared-worker.js` +export const channel = new BroadcastChannel('socket.runtime.serviceWorker') export const ServiceWorker = globalThis.ServiceWorker ?? class ServiceWorker extends EventTarget { get onmessage () { return null } set onmessage (_) {} @@ -32,8 +29,6 @@ export function createServiceWorker ( } } - const channel = new BroadcastChannel('socket.runtime.serviceWorker.state') - // events const eventTarget = new EventTarget() let onstatechange = null @@ -43,21 +38,13 @@ export function createServiceWorker ( let serviceWorker = null let scriptURL = options?.scriptURL ?? null - if ( - globalThis.RUNTIME_WORKER_LOCATION !== SHARED_WORKER_URL && - globalThis.location.pathname !== '/socket/service-worker/index.html' - ) { - sharedWorker = new SharedWorker(SHARED_WORKER_URL) - sharedWorker.port.start() - } - serviceWorker = Object.create(ServiceWorker.prototype, { postMessage: { enumerable: false, configurable: false, value (message, ...args) { - if (sharedWorker && globalThis.__args?.client) { - sharedWorker.port.postMessage({ + if (globalThis.__args?.client) { + channel.postMessage({ message, from: 'instance', registration: { id }, diff --git a/api/service-worker/notification.js b/api/service-worker/notification.js index ae094bfdf2..1bc1ddeb30 100644 --- a/api/service-worker/notification.js +++ b/api/service-worker/notification.js @@ -1,12 +1,9 @@ import { Notification, NotificationOptions } from '../notification.js' -import { SHARED_WORKER_URL } from './instance.js' import { NotAllowedError } from '../errors.js' -import { SharedWorker } from '../shared-worker/index.js' import permissions from '../internal/permissions.js' -let sharedWorker = null - const observedNotifications = new Set() +const channel = new BroadcastChannel('socket.runtime.serviceWorker') if (globalThis.isServiceWorkerScope) { globalThis.addEventListener('notificationclose', (event) => { @@ -19,16 +16,7 @@ if (globalThis.isServiceWorkerScope) { }) } -function ensureSharedWorker () { - if (!globalThis.isServiceWorkerScope && !sharedWorker) { - sharedWorker = new SharedWorker(SHARED_WORKER_URL) - sharedWorker.port.start() - } -} - export async function showNotification (registration, title, options) { - ensureSharedWorker() - if (title && typeof title === 'object') { options = title title = options.title ?? '' @@ -53,7 +41,7 @@ export async function showNotification (registration, title, options) { // will throw if invalid options are given options = new NotificationOptions(options, /* allowServiceWorkerGlobalScope= */ true) const nonce = Math.random().toString(16).slice(2) - const target = globalThis.isServiceWorkerScope ? globalThis : sharedWorker.port + const target = globalThis.isServiceWorkerScope ? globalThis : channel const message = { nonce, registration: { id: info.id }, @@ -77,8 +65,6 @@ export async function showNotification (registration, title, options) { } export async function getNotifications (registration, options = null) { - ensureSharedWorker() - const info = registration[Symbol.for('socket.runtime.ServiceWorkerRegistration.info')] if (!info) { @@ -90,7 +76,7 @@ export async function getNotifications (registration, options = null) { } const nonce = Math.random().toString(16).slice(2) - const target = globalThis.isServiceWorkerScope ? globalThis : sharedWorker.port + const target = globalThis.isServiceWorkerScope ? globalThis : channel const message = { nonce, registration: { id: info.id }, diff --git a/api/service-worker/shared-worker.js b/api/service-worker/shared-worker.js deleted file mode 100644 index 9821a8351a..0000000000 --- a/api/service-worker/shared-worker.js +++ /dev/null @@ -1,61 +0,0 @@ -const Uint8ArrayPrototype = Uint8Array.prototype -const TypedArrayPrototype = Object.getPrototypeOf(Uint8ArrayPrototype) -const TypedArray = TypedArrayPrototype.constructor - -function isTypedArray (object) { - return object instanceof TypedArray -} - -function isArrayBuffer (object) { - return object instanceof ArrayBuffer -} - -function findMessageTransfers (transfers, object, options = null) { - if (isTypedArray(object) || ArrayBuffer.isView(object)) { - add(object.buffer) - } else if (isArrayBuffer(object)) { - add(object) - } else if (object instanceof MessagePort) { - add(object) - } else if (Array.isArray(object)) { - for (const value of object) { - findMessageTransfers(transfers, value, options) - } - } else if (object && typeof object === 'object') { - for (const key in object) { - if ( - key.startsWith('__vmScriptReferenceArgs_') && - options?.ignoreScriptReferenceArgs === true - ) { - continue - } - - findMessageTransfers(transfers, object[key], options) - } - } - - return transfers - - function add (value) { - if (!transfers.includes(value)) { - transfers.push(value) - } - } -} - -const ports = [] -globalThis.addEventListener('connect', (event) => { - for (const port of event.ports) { - port.start() - ports.push(port) - port.addEventListener('message', (event) => { - for (const p of ports) { - if (p !== port) { - const transfer = [] - findMessageTransfers(transfer, event.data) - p.postMessage(event.data, { transfer }) - } - } - }) - } -}) diff --git a/api/service-worker/state.js b/api/service-worker/state.js index 2e35fec2bf..63e1090eff 100644 --- a/api/service-worker/state.js +++ b/api/service-worker/state.js @@ -2,7 +2,7 @@ import application from '../application.js' import debug from './debug.js' import ipc from '../ipc.js' -export const channel = new BroadcastChannel('socket.runtime.serviceWorker.state') +export const channel = new BroadcastChannel('socket.runtime.serviceWorker') const descriptors = { channel: {