Window Controls Overlay API

Window Controls Overlay API 给 PWA 应用提供了管理默认系统的应用标题栏的能力,允许应用完全掌控应用窗口的区域,不过仅支持 PC 端 PWA 应用

该 API 通过 WindowControlsOverlay 接口提供了相关功能,并通过 navigator.windowControlsOverlay 对外暴露该接口实例

使用该 API 需要在 PWA 应用的 Manifest 文件的 display_override 选项指定 window-controls-overlay

WindowControlsOverlay 的信息

WindowControlsOverlay 接口的 visible 只读属性表示了应用标题栏的可见性

WindowControlsOverlay 接口的 getTitlebarAreaRect() 方法返回了应用标题栏的几何信息,方法返回一个 DOMRect 接口实例

WindowControlsOverlay 接口的 geometrychange 事件在应用标题栏的可见性和几何信息变化时触发,事件返回一个 WindowControlsOverlayGeometryChangeEvent 事件实例

1
2
3
4
5
6
7
8
9
10
11
navigator.windowControlsOverlay.addEventListener('geometrychange', (e) => {
const { visible, titlebarAreaRect: rect } = e

if (visible) {
console.log('visible')

console.log('rect info', rect)
} else {
console.log('not visible')
}
})

相关的 CSS 环境变量

  • titlebar-area-x 应用标题栏左上角横坐标

  • titlebar-area-y 应用标题栏左上角纵坐标

  • titlebar-area-width 应用标题栏宽度

  • titlebar-area-height 应用标题栏高度

可以通过 env() CSS 函数使用 CSS 环境变量

1
2
3
4
env(titlebar-area-x)
env(titlebar-area-y)
env(titlebar-area-width)
env(titlebar-area-height)

示例

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Navigator {
readonly windowControlsOverlay: WindowControlsOverlay
}

interface WindowControlsOverlay extends EventTarget {
readonly visible: boolean
getTitlebarAreaRect(): DOMRect
ongeometrychange: ((this: WindowControlsOverlay, ev: WindowControlsOverlayGeometryChangeEvent) => any) | null
}

interface WindowControlsOverlayGeometryChangeEvent extends Event {
readonly titlebarAreaRect: DOMRect
readonly visible: boolean
}

链接

Badging API

Badging API 用于设置 PWA 应用的图标上的徽章信息

该 API 需要在 Secure Context 下使用

某些情况下该 API 需要请求用户授予 notifications 权限,并可以调用 Notification.requestPermission() 方法来请求获取相关权限

设置 Badge

使用 Navigator 接口上的 setAppBadge() 方法给图标设置徽章

方法支持传递一个可选的数字参数,徽章将显示为对应的数字;若未传递,徽章将显示为对应的点

方法返回一个 Promise 的 undefined

方法不支持时会抛出 NotSupportedError 异常

1
2
navigator.setAppBadge()
navigator.setAppBadge(10)

清除 Badge

使用 Navigator 接口的 clearAppBadge() 方法清除图标上设置的徽章

方法返回一个 Promise 的 undefined

方法不支持时会抛出 NotSupportedError 异常

1
2
navigator.setAppBadge(0)
navigator.clearAppBadge()

使用 Navigator 接口的 setAppBadge() 方法并传递参数 0 同样具有类似的效果

权限 API

该 API 调用需要用户授予 notifications 权限,可以调用 Permission.query() 方法或读取 Notification.permission 属性检查用户是否已授予了该权限

示例

类型

1
2
3
4
5
6
7
8
9
interface Navigator {
clearAppBadge(): Promise<void>
setAppBadge(contents?: number): Promise<void>
}

interface WorkerNavigator {
clearAppBadge(): Promise<void>
setAppBadge(contents?: number): Promise<void>
}

链接

Content Index API

Content Index API 允许网站注册离线启用的内容,向用户告知网站支持的离线内容并允许开发者对其进行管理

该 API 仅支持 HTML 文档对应的 URL,不支持如媒体资源等类型的 URL

该 API 仅支持列举已注册的内容,不支持进行查找等操作

一般的使用方式是利用一个列表页,用于展示已注册的内容

需要注意的是,该 API 并非直接缓存内容,实际的缓存需要利用 Cache Storage 等策略实施

介于该 API 依赖于 ServiceWorker,因此该 API 同样需在 Secure Context 下使用,且需遵循同源策略

通过 ServiceWorkerRegistration.index 暴露 ContentIndex 接口实例使用

添加离线内容

通过 ContentIndex 接口的 add() 方法添加离线内容

方法支持传入一组配置项

  • 参数 id 指定离线内容的唯一标识符
  • 参数 url 指定离线内容的 URL,需要与当前网页或脚本同源
  • 参数 title 指定离线内容的标题
  • 参数 description 指定离线内容的描述
  • 可选参数 icons 指定离线内容的图标组,每组图标对象支持指定 src 参数和 可选的 sizestype 参数,默认值是一个空数组
  • 可选参数 category 指定离线内容的类别,可选的值为 '''homepage''article''video''audio',默认值是 ''

方法返回一个 Promise 的 undefined

方法在以下情况下会抛出一个 TypeError 异常

  • 当前 ContentIndex 对应的 ServiceWorker 未激活或 ServiceWorker 未包含 FetchEvent
  • idtitledescriptionurl 参数未指定或参数类型不为字符串或参数为空串
  • icons 参数某个 icon 的 URL 的类型不是图像或获取对应 icon 出现网络异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
self.registration.index.add({
id: 'post',
url: '/posts/post.html',
title: 'Post',
description: 'Post Information',
icons: [
{
src: '/media/dark.png',
sizes: '128x128',
type: 'image/png',
},
],
category: 'article',
})

获取离线内容

通过 ContentIndex 接口的 getAll() 方法获取离线内容

方法返回一个 Promise 的代表离线内容的数组,结构同 ContentIndex.add() 方法的配置项参数

1
self.registration.index.getAll()

删除离线内容

通过 ContentIndex 接口的 delete() 方法删除离线内容

方法传入一个代表待删除的离线内容的 id 的字符串

方法返回一个 Promise 的 undefined

1
2
3
4
5
self.registration.index.delete(id).then(() => (
self.caches.open('v1').then((cache) => (
cache.delete(e.id)
))
))

需要注意的是,调用该方法同时,需要手动从存储中移除对应的离线内容

此外,当离线内容被通过用户代理移除而非手动调用 ContentIndex.delete() 方法移除时,会在 ServiceWorker 全局触发 contentdelete 事件,并返回一个 ContentIndexEvent 事件

ContentIndexEvent 事件继承自 ExtendableEvent 事件,其属性 id 反映了被删除的离线内容的 id

1
2
3
4
5
6
7
self.addEventListener('contentdelete', (e) => {
e.waitUntil(
self.caches.open('v1').then((cache) => (
cache.delete(e.id)
))
)
})

通常利用该事件同步移除与待移除页面的相关的资源存储

示例

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type ContentCategory = '' | 'homepage' | 'article' | 'video' | 'audio'

interface ContentDescription {
id: string
title: string
description: string
category?: ContentCategory
icons?: ImageResource[];
url: string
}

interface ContentIndex {
add(description: ContentDescription): Promise<undefined>
delete(id: string): Promise<undefined>
getAll(): Promise<ContentDescription[]>
};

链接

ServiceWorker VI

ServiceWorker 导航预加载

导航预加载通过 NavigationPreloadManager 接口提供,并通过 ServiceWorkerRegistration.navigationPreload 属性暴露

对于使用 ServiceWorker 的页面,网页的网络请求会向 ServiceWorker 发送 fetch 事件直至返回响应,若此时 ServiceWorker 未启动,网页的网络请求会等待 ServiceWorker 激活后再进行处理;导航预加载允许网页的获取资源请求在 ServiceWorker 激活前提前开始下载,以避免阻碍页面的显示

启用导航预加载

NavigationPreloadManager 接口的 enable() 方法用于启用资源预加载管理

停用导航预加载

NavigationPreloadManager 接口的 disable() 方法用于停用资源预加载管理

管理导航预加载

NavigationPreloadManager 接口的 setHeaderValue() 方法用于设置导航预加载中发送的请求的请求头 Service-Worker-Navigation-Preload 的值

NavigationPreloadManager 接口的 getState() 方法用于获取导航预加载的状态

基本使用

启用导航预加载

1
2
3
4
5
self.addEventListener('activate', (e) => {
e.waitUntil(
self.registration.navigationPreload.enable()
)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
self.addEventListener('fetch', (e) => {
e.responseWith(
(async () => {
const cache = await self.caches.match(e.request)

if (cache != null) {
return cache
}

const preload = await e.preloadResponse

if (preload != null) {
return preload
}

return fetch(e.request)
})
)
})

链接

SharedWorker

SharedWorker 是 HTML 标准定义的 Web API 的一部分,是一种特殊的 Worker,支持在多个上下文(例如 window、iframe 甚至 Worker)之间共享

同时,SharedWorker 的全局上下文 SharedWorkerGlobalScope 也与 Worker 不同

创建 SharedWorker

和 Worker 一样,通过调用 SharedWorker() 构造函数来创建

1
const worker = new SharedWorker('./worker.js')

SharedWorker() 构造函数支持传入一组可选的配置项,与 Worker() 构造函数相同
SharedWorker() 构造函数也支持直接传入一个字符串,同配置项的 name 参数;特别的,SharedWorker() 中的 name 参数作为唯一的一个标识符,在创建新的与之前的拥有相同 URL 的 SharedWorker 时有一定作用

SharedWorker 通过脚本文件 URL 和 name 参数确定是否为同一个 SharedWorker

SharedWorker 消息传递

与 Worker 不同,Client 端通过创建的 SharedWorker 实例上的 port 属性暴露的 MessagePort 接口实例,调用其上的 postMessage() 方法实现发送消息

Client 端通过监听 SharedWorker 实例上的 message 事件实现接收到消息

1
2
3
4
5
6
7
8
9
worker.port.start()

worker.port.postMessage('message from client')

worker.port.close()

worker.port.addEventListener('message', (e) => {
console.log('receive message in client: ', e.data)
})

SharedWorker 环境下接收消息,需要监听 connect 事件,从而获取到新的对应的 MessagePort 实例;监听 MessagePort 实例的 message 事件接收消息

SharedWorker 环境下接收消息,同样需要通过调用 MessagePort 实例的 postMessage() 方法实现发送消息

1
2
3
4
5
6
7
8
9
10
11
12
13
self.addEventListener('connect', (e) => {
const port = e.ports.at(0)

port.addEventListener('message', (e) => {
console.log('receive message in worker: ', e.data)
})

port.start()

port.postMessage('message from worker')

port.close()
})

通常,在 connect 事件回调函数内,会把接收到的 port 存储下来,以便之后使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ports = []

self.addEventListener('connect', (e) => {
const port = e.ports.at(0)

port.addEventListener('message', handleReceiveMessage)

port.start()

ports.push(port)
})

function sendMessage() {
for (const port of ports) {
port.postMessage('message from worker')
}
}

卸载 SharedWorker

仅支持在 SharedWorker 环境内调用 close() 方法,来卸载当前 Worker

1
self.close()

SharedWorker 生命周期

SharedWorker 生命周期与 Client 端的生命周期独立,当任一页面创建 SharedWorker 时其生命周期开始,在没有页面使用 SharedWorker 时其生命周期结束

SharedWorker 全局环境

SharedWorker 全局环境通过 SharedWorkerGlobalScope 表示,该接口继承自 WorkerGlobalScope,它与 Worker 全局环境差别不大

示例

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface SharedWorker extends EventTarget, AbstractWorker {
constructor(scriptURL: string | URL, options?: string | WorkerOptions);
readonly port: MessagePort;
}

interface SharedWorkerGlobalScope extends WorkerGlobalScope {
readonly name: string;
close(): void;
onconnect: ((this: SharedWorkerGlobalScope, ev: MessageEvent) => any) | null;
}

interface WorkerOptions {
credentials?: RequestCredentials
name?: string
type?: WorkerType
}

type WorkerType = 'classic' | 'module'

链接

Worker

Web Worker 是 HTML 标准定义的 Web API 的一部分,可以在后台运行一个耗时的任务,避免因长期执行 JS 任务而阻塞用户界面渲染与交互

Web Worker 可以被 Window 环境创建,也可以被其他的 Worker 创建

Web Worker 是独立于主线程的一个线程,具有独立的作用域,其中运行的任务不会阻塞主线程

Web Worker 中的全局作用域 DedicatedWorkerGlobalScope 与 Window 的全局作用域不同,Window 环境中部分 API 在 Worker 环境中不可用或受到一定的限制

Web Worker 线程与主线程之间的通信通过 message 机制实现,传递的数据通过结构化拷贝算法传递,因此通常不存在处理线程安全的需要

创建 Worker

通过调用 Worker() 构造函数,传入 Worker 脚本的 URL,来创建一个 Worker

1
const worker = new Worker('./worker.js')

Worker 脚本需要与 Client 同域

Worker() 构造函数支持传入一组可选的配置项
type 参数指定脚本的类型,值可以是 classicmodule,默认值是 classic
name 参数指定 Worker 的名称,在 debug 时候有一定作用,在 Worker 内可以通过 name 只读属性访问
credentials 参数指定 Worker 的 credentials 选项,允许的值可以是 omitsame-origininclude
若传入的 URL 解析失败,会抛出一个 SyntaxError 错误
若接收到的脚本文件并非 JavaScript 格式,会抛出 NetworkError 错误
若当前文档环境不支持创建 Worker,如未遵守同源策略,会抛出 SecurityError 错误

Worker 消息传递

无论是 Worker 端还是 Client 端,通过调用 postMessage() 方法实现发送消息,通过监听 message 事件实现接收消息

Client 发送消息

1
worker.postMessage('message from client')

Client 接收消息

1
2
3
worker.addEventListener('message', (e) => {
console.log('receive message in client: ', e.data)
})

Worker 发送消息

1
self.postMessage('message from worker')

Worker 发送消息

1
2
3
self.addEventListener('message', (e) => {
console.log('receive message in worker: ', e.data)
})

此外,可以选择传入一组数组或包含 transfer 参数的配置项,定义需要转移所有权的对象

所有权被转移后,对应对象在原环境内不再可用,而是仅在新环境内可用

普通消息

当然,传递的消息可以不仅仅是 string 类型,可以是其他任何可以被结构化拷贝算法执行的数据,包括:

  • number
  • string
  • boolean
  • null
  • undefined
  • bigint
  • 普通 object
  • Array
  • RegExp
  • Date
  • Error
  • Set
  • Map
  • Blob
  • ArrayBuffer
  • TypedArray
  • 等等

结构化拷贝算法,严格来说,与 JSON.stringfy()JSON.parse() 行为上不同。在结构化拷贝算法中,试图复制 Function 参数会抛出异常;但结构化拷贝算法支持复制包含循环对象的对象

可转移对象

可以转移的对象可以是:

  • ArrayBuffer
  • MessagePort
  • ReadableStream
  • WritableStream
  • TransformStream
  • WebTransportReceiveStream
  • AudioData
  • ImageBitmap
  • VideoFrame
  • OffscreenCanvas
  • RTCDataChannel

可共享对象

SharedArrayBuffer 可以用于多个线程之间的共享数据,并利用 Atomics 实现线程同步与线程锁功能。

启用该 API 需要 secure context,并且需要 cross-origin isolate,可以通过检测 isSecureContext 全局变量和 crossOriginIsolated 全局变量来确定是否可以使用 SharedArrayBuffer

卸载 Worker

通过调用 worker 实例的 terminate() 方法,来卸载一个 Worker

1
worker.terminate()

或者调用 Worker 环境中的 close() 方法,来卸载当前的 Worker

1
self.close()

卸载是立即执行的,不会等待 worker 内部任务的完成

Worker 全局环境

Worker 全局环境通过 DedicatedWorkerGlobalScope 表示,该接口继承自 WorkerGlobalScope

Worker 全局环境的 messageerror 事件会在传递的消息无法解析时触发,可用用于监听发送失败的消息(Worker 对象上同样存在)

Worker 全局环境的 importScripts() 方法可以导入一组同源的脚本文件,并在 Worker 全局环境下执行

示例

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

interface Worker extends EventTarget, AbstractWorker {
constructor(scriptURL: string | URL, options?: WorkerOptions)
postMessage(message: any, transfer: Transferable[]): void
postMessage(message: any, options?: StructuredSerializeOptions): void
terminate(): void
}

interface DedicatedWorkerGlobalScope extends WorkerGlobalScope {
readonly name: string
close(): void
onmessage: ((this: DedicatedWorkerGlobalScope, ev: MessageEvent) => any) | null
onmessageerror: ((this: DedicatedWorkerGlobalScope, ev: MessageEvent) => any) | null
postMessage(message: any, transfer: Transferable[]): void
postMessage(message: any, options?: StructuredSerializeOptions): void
}

interface StructuredSerializeOptions {
transfer?: Transferable[]
}

interface WorkerOptions {
credentials?: RequestCredentials
name?: string
type?: WorkerType
}

type WorkerType = 'classic' | 'module'

链接

ServiceWorker II

ServiceWorker 全局上下文

ServiceWorkerGlobalScope 接口代表 ServiceWorker 的全局上下文,在 ServiceWorker 内通过 self 全局变量或者 globalThis 全局变量访问(该接口继承自 WorkerGlobalScope)。

ServiceWorkerGlobalScope

以下代表在 ServiceWorkerGlobalScope 接口本身的属性、方法和事件

  • ServiceWorkerGlobalScope 接口的 clients 属性代表一个 Clients 实例,可用于获取 Client (可执行上下文)实例。

  • ServiceWorkerGlobalScope 接口的 registration 属性代表一个 ServiceWorkerRegistration 实例,即当前 ServiceWorker 注册的引用。

  • ServiceWorkerGlobalScope 接口的 serviceWorker 属性代表一个 ServiceWorker 实例,即当前 ServiceWorker 实例的引用。

  • ServiceWorkerGlobalScope 接口的 skipWaiting 方法强制当前 ServiceWorker 从等待状态变成激活状态,返回一个该 ServiceWorker 激活后完成的 Promise。其在 install 事件的回调中调用才具有实际意义。

WorkerGlobalScope

以下代表继承自 WorkerGlobalScope 接口的属性、方法和事件

  • WorkerGlobalScope 接口的 location 属性代表一个 WorkerLocation 实例,是 Location 的字集。

  • WorkerGlobalScope 接口的 navigator 属性代表一个 WorkerNavigator 实例,是 Navigator 的字集。

  • WorkerGlobalScope 接口的 self 属性代表 WorkerGlobalScope 接口本身。

  • WorkerGlobalScope 接口的 importScripts 方法同步导入一组脚本文件并执行,接受一组参数,代表脚本文件的 URL,其可以为绝对路径或相对路径(相对文档路径)。

  • WorkerGlobalScope 接口的 error 事件在 ServiceWorker 内发生脚本错误时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 languagechange 事件在用户的首选语言更改时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 online 事件在浏览器获得网络访问权限并且 navigator.onLine 值切换到 true 时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 offline 事件在浏览器获得网络访问权限并且 navigator.onLine 值切换到 false 时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 rejectionhandled 事件在 ServiceWorker 内处理的 Promise 拒绝事件时触发,返回一个 Event 实例。

  • WorkerGlobalScope 接口的 unhandledrejection 事件在 ServiceWorker 内未处理的 Promise 拒绝事件时触发,返回一个 Event 实例。

以下代表暴露在全局的属性、方法和事件

1
2
3
4
5
6
7
8
self.fonts
self.caches
self.crossOriginIsolated
self.crypto
self.indexedDB
self.isSecureContext
self.origin
self.performance
1
2
3
4
5
6
7
8
9
10
11
self.atob()
self.btoa()
self.clearInterval()
self.clearTimeout()
self.createImageBitmap()
self.fetch()
self.queueMicrotask()
self.reportError()
self.setInterval()
self.setTimeout()
self.structuredClone()

示例

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
readonly clients: Clients;
readonly registration: ServiceWorkerRegistration;
readonly serviceWorker: ServiceWorker;
onactivate: ((this: ServiceWorkerGlobalScope, ev: ExtendableEvent) => any) | null;
onfetch: ((this: ServiceWorkerGlobalScope, ev: FetchEvent) => any) | null;
oninstall: ((this: ServiceWorkerGlobalScope, ev: ExtendableEvent) => any) | null;
onmessage: ((this: ServiceWorkerGlobalScope, ev: ExtendableMessageEvent) => any) | null;
onmessageerror: ((this: ServiceWorkerGlobalScope, ev: MessageEvent) => any) | null;
onnotificationclick: ((this: ServiceWorkerGlobalScope, ev: NotificationEvent) => any) | null;
onnotificationclose: ((this: ServiceWorkerGlobalScope, ev: NotificationEvent) => any) | null;
onpush: ((this: ServiceWorkerGlobalScope, ev: PushEvent) => any) | null;
onpushsubscriptionchange: ((this: ServiceWorkerGlobalScope, ev: Event) => any) | null;
skipWaiting(): Promise<void>;
}

interface WorkerGlobalScope extends EventTarget, FontFaceSource, WindowOrWorkerGlobalScope {
readonly location: WorkerLocation;
readonly navigator: WorkerNavigator;
readonly self: WorkerGlobalScope & typeof globalThis;
onerror: ((this: WorkerGlobalScope, ev: ErrorEvent) => any) | null;
onlanguagechange: ((this: WorkerGlobalScope, ev: Event) => any) | null;
onoffline: ((this: WorkerGlobalScope, ev: Event) => any) | null;
ononline: ((this: WorkerGlobalScope, ev: Event) => any) | null;
onrejectionhandled: ((this: WorkerGlobalScope, ev: PromiseRejectionEvent) => any) | null;
onunhandledrejection: ((this: WorkerGlobalScope, ev: PromiseRejectionEvent) => any) | null;
importScripts(...urls: (string | URL)[]): void;
}

interface WindowOrWorkerGlobalScope {
readonly caches: CacheStorage;
readonly crossOriginIsolated: boolean;
readonly crypto: Crypto;
readonly fonts: FontFaceSet;
readonly indexedDB: IDBFactory;
readonly isSecureContext: boolean;
readonly origin: string;
readonly performance: Performance;
atob(data: string): string;
btoa(data: string): string;
clearInterval(id: number | undefined): void;
clearTimeout(id: number | undefined): void;
createImageBitmap(image: ImageBitmapSource, options?: ImageBitmapOptions): Promise<ImageBitmap>;
createImageBitmap(image: ImageBitmapSource, sx: number, sy: number, sw: number, sh: number, options?: ImageBitmapOptions): Promise<ImageBitmap>;
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
queueMicrotask(callback: VoidFunction): void;
reportError(e: any): void;
setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
structuredClone<T = any>(value: T, options?: StructuredSerializeOptions): T;
}

链接

Local Font Access API

Local Font Access API 向开发者提供了获取用户本地安装的字体的信息的方式,包括字体名称、字体样式及字体族等等

获取本地字体

调用 window.queryLocalFonts() 方法来获取本地安装的字体

方法允许传入一组可选的配置项,其 postscriptNames 参数允许传入一组字符串数组,代表希望筛选的 postscriptName 名称

方法会返回 Promise 的 FontData 数组,表示本地安装的字体的列表

方法可能抛出 NotAllowedError 异常,表示用户拒绝授予开发者 'local-fonts' 权限

方法可能抛出 SecurityError 异常,表示该 API 受 Permissions Policy 的限制无法被调用或调用该方法并非缘于用户交互行为

1
2
3
window.queryLocalFonts().then((fonts) => {
// to do something
})

处理本地字体

字体信息使用 FontData 接口表示

FontData 接口的 family 属性表示字体的字体族,可以用于 CSS 的 font-family 属性或者 @font-face 规则中的 local() 函数等;

FontData 接口的 fullName 属性表示字体的全名,通常是一个用户可辨识的名称,可以用于向用户展示;

FontData 接口的 postscriptName 属性表示字体的 PostScript 名称,可以用于唯一地辨识字体;

FontData 接口的 style 属性表示字体的样式,可以用于 CSS 的 font-style 属性;

1
2
3
4
5
6
7
8
9
10
async function logFonts() {
const fonts = await window.queryLocalFonts()

for (const data of fonts) {
console.log(data.postscriptName)
console.log(data.fullName)
console.log(data.family)
console.log(data.style)
}
}

FontData 接口的 blob() 方法以 Blob 形式返回字体的源数据;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function getFontFormat(font) {
const data = await fontData.blob()

const version = await data.slice(0, 4).text()

let format: 'unknown' | 'truetype' | 'cff' = 'unknown'

switch (format) {
case "\x00\x01\x00\x00":
case "true":
case "typ1":
format = "truetype"
break
case "OTTO":
format = "cff"
break
}

return format
}

权限策略

该 API 调用受到 local-fonts 权限策略的控制,可以通过 Permissions-Policy 响应头指定,或通过 <iframe> 标签的 allow 属性指定

默认为 self,即允许在当前上下文或内嵌的其他同源上下文中使用

权限 API

该 API 调用需要用户授予 local-fonts 权限,可以调用 Permission.query() 方法检查用户是否已授予了该权限

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Window {
queryLocalFonts: (options?: QueryOptions) => Promise<FontData[]>
}

interface QueryOptions {
postscriptNames?: string[]
}

interface FontData {
readonly family: string
readonly fullName: string
readonly postscriptName: string
readonly style: string
blob: () => Promise<Blob>
}

链接

ServiceWorker III

ServiceWorker 中 Client 与 Worker

在 Worker 中获取 Client

在 ServiceWorker 中,会对应着多个 Client 实例,代表着受 ServiceWorker 控制的上下文,可以是网页、Worker 乃至 SharedWorker 等,而 Client 实例的访问与控制通过 Clients 实例实现,通过 clients 属性获取

同时,在 ServiceWorker 中,通过 registration 属性暴露 ServiceWorkerRegistration 实例,即当前 ServiceWorker 注册;通过 serviceWorker 属性暴露 ServiceWorker 实例,即当前 ServiceWorker 实例

Clients

在 ServiceWorker 中,Clients 接口提供对 Client 接口的访问

  • Clients 接口的 get() 方法根据给定的 id 获取对应的 Client 实例

    接收一个字符串,代表 Client 的 id

    返回一个 Promise 的 Client(或 undefined

  • Clients 接口的 matchAll() 方法获取所有匹配的 Client 实例

    接收一组可选的配置项

    配置项的 type 参数指定需匹配的 Client 类型,允许的值为 "window", "worker", "sharedworker""all",默认为 "window"

    配置项的 includeUncontrolled 参数指定是否返回未受控制的 Client,默认为 false

    返回一个 Promise 的 Client 数组

  • Clients 接口的 claim() 方法将所有匹配的 Client 实例置于当前 ServiceWorker 的控制之下

    返回一个 Promise

  • Clients 接口的 openWindow() 方法创建新的顶层上下文并加载给定的 URL

    要求该 URL 需与 ServiceWorker 同源,并且要求调用前一段时间用户需发生交互行为

    Firefox 中要求该方法必须在通知点击回调方法中调用

    返回一个 Promise 的 WindowClient 实例或 null

Client

在 ServiceWorker 中,Client 接口表示受 ServiceWorker 控制的执行上下文

其中 WindowClient 接口继承自 Client 接口,表示受 ServiceWorker 控制的 Window 执行上下文

受 ServiceWorker 控制的各执行上下文可以是 Worker、Shared Worker 以及 Window

  • Client 接口的 id 属性返回一个字符串,代表对应的执行上下文的 ID

  • Client 接口的 type 属性返回一个字符串枚举,可能为 "window""worker""sharedworker" 之一,代表对应的执行上下文的类型

  • Client 接口的 url 属性返回一个字符串,代表对应的执行上下文的 URL

  • Client 接口的 frameType 属性返回一个字符串枚举,可能为 "auxiliary""top-level""nested""none" 之一,代表对应的执行上下文的类型

  • WindowClient 接口的 focused 属性返回一个布尔值,代表对应的 Window 执行上下文是否处于聚焦状态

  • WindowClient 接口的 visibilityState 属性返回一个字符串枚举,可能的值为 "hidden""visible""prerender",代表对应的 Window 执行上下文的可见性类型

  • WindowClient 接口的 focus() 方法控制对应的执行上下文聚焦

    返回一个 Promise 的 WindowClient 实例

  • WindowClient 接口的 navigate() 方法控制对应的执行上下文加载指定 URL

    方法接收一个字符串或 URL 实例,代表 URL

    若 ServiceWorker 执行上下文与 URL 同源,返回 Promise 的 WindowClient;否则返回 Promise 的 null

在 Client 中获取 Worker

在 Client 端中,通过 ready 属性暴露控制当前页面的 ServiceWorkerRegistration 实例,通过 controller 属性暴露控制当前页面的 ServiceWorker 实例

ServiceWorkerContainer

在 ServiceWorker 中,ServiceWorkerContainer 接口包含 ServiceWorker 的相关状态与相关控制方法,用于实现对 ServiceWorker 的管理

  • ServiceWorkerContainer 接口的 ready 属性返回一个 Promise 的 ServiceWorkerRegistration,表示控制当前页面的 ServiceWorker 的注册;该属性与 ServiceWorkerGlobalScope 接口的 registration 属性类似

  • ServiceWorkerContainer 接口的 controller 属性返回一个 ServiceWorkernull,表示是否存在控制当前页面的 ServiceWorker 的实例;该属性与 ServiceWorkerGlobalScope 接口的 serviceWorker 属性类似

  • ServiceWorkerContainer 接口的 getRegistration() 方法根据给定的 URL (默认使用当前 Client 的 URL)返回与之匹配的 ServiceWorkerRegistration 对象

    方法可以接受一个参数

    方法返回一个 Promise 的 ServiceWorkerRegistration 或者 undefined,根据是否存在对应的注册对象

  • ServiceWorkerContainer 接口的 getRegistrations() 方法获取所有与当前 Client 相关的 ServiceWorkerRegistration 对象

    返回一个 ServiceWorkerRegistration 的数组

  • ServiceWorkerContainer 接口的 startMessages() 方法强制当前上下文提前开始接收发送自 ServiceWorker 的消息

  • ServiceWorkerContainer 接口的 controllerchange 事件在控制当前 Client 的 ServiceWorker 变化时触发,返回一个 Event 事件

ServiceWorkerRegistration

在 ServiceWorker 中,ServiceWorkerRegistration 接口表示 ServiceWorker 的注册对象

  • ServiceWorkerRegistration 接口的 active 属性返回一个 ServiceWorker 或者 null,代表最新注册的 state 属性为 activatingactivated 的 ServiceWorker

  • ServiceWorkerRegistration 接口的 installing 属性返回一个 ServiceWorker 或者 null,代表最新注册的 state 属性为 installing 的 ServiceWorker

  • ServiceWorkerRegistration 接口的 waiting 属性返回一个 ServiceWorker 或者 null,代表最新注册的 state 属性为 installed 的 ServiceWorker

通常而言,active 属性、installing 属性、waiting 属性三个中最多只有一个是非 null 值。

  • ServiceWorkerRegistration 接口的 scope 属性返回一个字符串,代表 ServiceWorker 的注册域

  • ServiceWorkerRegistration 接口的 updateViaCache 属性返回一个字符串枚举,可能的值为 'all''imports''none',指定 HTTP 缓存的脚本如何应用于 ServiceWorker

以上两个属性的值在注册 ServiceWorker 时指定

  • ServiceWorkerRegistration 接口的 updatefound 事件在新的 ServiceWorker 开始下载时触发,返回一个 Event 事件

ServiceWorker

在 ServiceWorker 中,ServiceWorker 接口表示 ServiceWorker 对象

  • ServiceWorker 接口的 scriptURL 属性返回一个字符串,表示 ServiceWorker 的注册脚本 URL

  • ServiceWorker 接口的 state 属性返回一个字符串枚举,可能的值包括 "parsed""installing""installed""activating""activated""redundant",表示 ServiceWorker 的状态

    • "parsed" ServiceWorker 在下载完成并且验证可运行后的初始值
    • "installing" ServiceWorker 在安装中
    • "installed" ServiceWorker 安装完成
    • "activating" ServiceWorker 在激活中
    • "activated" ServiceWorker 激活完成
    • "redundant" ServiceWorker 被替代或安装失败
  • ServiceWorker 接口的 statechange 事件在 ServiceWorker 的状态更新时触发,返回一个 Event 事件

示例

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
interface Client {
readonly frameType: FrameType;
readonly id: string;
readonly type: ClientTypes;
readonly url: string;
postMessage(message: any, transfer: Transferable[]): void;
postMessage(message: any, options?: StructuredSerializeOptions): void;
}

interface Clients {
claim(): Promise<void>;
get(id: string): Promise<Client | undefined>;
matchAll<T extends ClientQueryOptions>(options?: T): Promise<ReadonlyArray<T["type"] extends "window" ? WindowClient : Client>>;
openWindow(url: string | URL): Promise<WindowClient | null>;
}

interface WindowClient extends Client {
readonly focused: boolean;
readonly visibilityState: DocumentVisibilityState;
focus(): Promise<WindowClient>;
navigate(url: string | URL): Promise<WindowClient | null>;
}

interface ServiceWorker extends EventTarget, AbstractWorker {
readonly scriptURL: string;
readonly state: ServiceWorkerState;
postMessage(message: any, transfer: Transferable[]): void;
postMessage(message: any, options?: StructuredSerializeOptions): void;
onstatechange: ((this: ServiceWorker, ev: Event) => any) | null;
}

interface ServiceWorkerContainer extends EventTarget {
readonly controller: ServiceWorker | null;
readonly ready: Promise<ServiceWorkerRegistration>;
getRegistration(clientURL?: string | URL): Promise<ServiceWorkerRegistration | undefined>;
getRegistrations(): Promise<ReadonlyArray<ServiceWorkerRegistration>>;
register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>;
startMessages(): void;
oncontrollerchange: ((this: ServiceWorkerContainer, ev: Event) => any) | null;
onmessage: ((this: ServiceWorkerContainer, ev: MessageEvent) => any) | null;
onmessageerror: ((this: ServiceWorkerContainer, ev: MessageEvent) => any) | null;
}

interface ServiceWorkerRegistration extends EventTarget {
readonly active: ServiceWorker | null;
readonly installing: ServiceWorker | null;
readonly navigationPreload: NavigationPreloadManager;
readonly pushManager: PushManager;
readonly scope: string;
readonly updateViaCache: ServiceWorkerUpdateViaCache;
readonly waiting: ServiceWorker | null;
getNotifications(filter?: GetNotificationOptions): Promise<Notification[]>;
showNotification(title: string, options?: NotificationOptions): Promise<void>;
unregister(): Promise<boolean>;
update(): Promise<void>;
onupdatefound: ((this: ServiceWorkerRegistration, ev: Event) => any) | null;
}

链接

Web Periodic Background Synchronization API

Web Periodic Background Synchronization API 提供了一种注册在网络状态允许下周期性执行任务的方法,这些任务被称为周期后台同步请求。

API 用途包括在设备连接到网络时获取最新内容,或允许对应用程序进行后台更新。

调用 API 时需设置最小时间间隔,但是用户代理通常还会考虑网络连接情况或者之前网站的用户参与程度来决定触发任务的周期。

该 API 通过 PeriodicSyncManager 接口提供,并基于 ServiceWorkerRegistration 接口的 periodicSync 属性向开发者暴露。

注册周期后台同步任务

PeriodicSyncManager 接口的 register() 方法用于注册周期后台同步任务。

方法接收一个字符串,作为周期后台同步任务的唯一标识符;可接收一组可选的配置项,其唯一属性为 minInterval,指定周期后台同步任务的执行周期。

返回一个 Promise。

1
2
3
4
5
6
7
const TAG = 'tag'

window.navigator.serviceWorker.ready.then((registration) => {
registration.periodicSync.register(TAG, {
minInterval: 24 * 60 * 60 * 1000,
})
})

查看周期后台同步任务

PeriodicSyncManager 接口的 getTags() 方法用于获取周期后台同步任务。

返回一个 Promise 的字符串数组,代表当前已注册的周期后台同步任务的标识符列表。

1
2
3
4
5
6
7
8
9
const TAG = 'tag'

window.navigator.serviceWorker.ready.then((registration) => {
registration.periodicSync.getTags().then((tags) => {
if (tags.includes(TAG)) {
// do something
}
})
})

卸载周期后台同步任务

PeriodicSyncManager 接口的 unregister() 方法用于卸载周期后台同步任务。

方法接收一个字符串,代表周期后台同步任务的唯一标识符。

返回一个 Promise。

1
2
3
4
5
const TAG = 'tag'

window.navigator.serviceWorker.ready.then((registration) => {
registration.periodicSync.unregister(TAG)
})

执行周期后台同步任务

ServiceWorkerGlobalScope 接口的 periodicsync 事件在触发周期后台同步任务时触发。返回一个 PeriodicSyncEvent 事件。

事件触发周期大于或等于在注册时设置的最小执行周期。

1
2
3
4
5
6
7
const TAG = 'tag'

self.addEventListener('periodicsync', (e) => {
if (e.tag === TAG) {
// do something
}
})

PeriodicSyncEvent 事件继承自 ExtendableEvent 事件。其 tag 属性返回事件对应的周期后台同步任务的唯一标识符。

权限 API

该 API 调用需要用户授予 periodic-background-sync 权限,可以调用 Permission.query() 方法检查用户是否已授予了该权限

示例

类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface PeriodicSyncEvent extends ExtendableEvent {
readonly tag: string;
}

interface BackgroundSyncOptions {
minInterval: number;
}

interface PeriodicSyncManager {
getTags(): Promise<ReadonlyArray<string>>;
register(tag: string, options?: BackgroundSyncOptions): Promise<void>;
unregister(tag: string): Promise<void>;
}

interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
onperiodicsync: ((this: ServiceWorkerGlobalScope, ev: PeriodicSyncEvent) => any) | null;
}

interface ServiceWorkerRegistration extends EventTarget {
readonly periodicSync: PeriodicSyncManager;
}

链接


:D 一言句子获取中...