Cookie API

Cookie 是一小段保存在用户端的信息,用于改善用户的网络浏览体验,通常包括用户的选项或识别信息,用户可以选择网站使用 Cookie 的方式

可用使用 navigator.cookieEnabled 判断页面是否允许使用 Cookie

可以通过 Set-Cookie 响应头设置 Cookie 或使用 JS 脚本 document.cookie 设置 Cookie

1
2
HTTP/2.0 200 OK
Set-Cookie: cookie_a=a
1
document.cookie = 'cookie_b=b';

执行请求时可以通过 Cookie 请求头自动带上 Cookie

JS 脚本中可以通过 document.cookie 读取 Cookie

1
const cookies = document.cookie;
  • Expires 属性

    指定 Cookie 在给定时间后失效

  • Max-Age 属性

    指定 Cookie 在超出给定时长后失效

  • Secure 属性

    指定 Cookie 仅在 Secure Context 下才发送;并且非 Secure Context 下无法设置该属性

  • HttpOnly 属性

    指定 Cookie 无法通过 document.cookie 读取和修改

    常用于防御 XSS 攻击

  • Domain 属性

    指定 Cookie 可用的域名(及子域名),默认会包含当前的域名

  • Path 属性

    指定 Cookie 可用的路径(及子路径)

  • SameSite 属性

    指定 Cookie 是否在跨域请求中发送

    常用于防御 CSRF 攻击

    Strict 值指定仅在同域请求中发送

    Lax 值允许在用户导航至 Cookie 的域名时发送,该值是默认的行为

    None 值指定允许在跨域请求中发送,但需同时指定 Secure 属性

1
Set-Cookie: cookie_b=b; Expires=Thu, 31 Oct 2021 07:28:00 GMT; Secure; HttpOnly; Domain=example.com; Path=/; SameSite=Strict
  • __Host- 前缀

    指定对应的 Cookie 需指定 Secure 属性(即需要在 Secure Context 发送),不得指定 Domain 属性,Path 属性需指定为 /

  • __Secure- 前缀

    指定对应的 Cookie 需指定 Secure 属性(即需要在 Secure Context 发送)

链接

Geolocation API

Geolocation API 提供了访问用户地理位置的方法

通过 navigator.geolocation 暴露的 Geolocation 接口实例访问

使用 API 需要向用户请求授权并获得允许,并且仅在 Secure Context 环境下启用

获取地理位置

使用 Geolocation 接口的 getCurrentPosition() 方法获取地理位置

方法需要传入一个在获取地理位置成功时调用的回调函数,该回调函数会被传递一个 GeolocationPosition 参数

方法可以传入一个在获取地理位置失败时调用的回调函数,该回调函数会被传递一个 GeolocationPositionError 参数

方法同样可以传入一个可选的配置项:

maximumAge 可选选项接收一个正数值,指定使用缓存的地理位置的允许的最长的期限,默认为 0

timeout 可选选项接收一个正数值,指定获取地理位置等待的超时时间,默认为 Infinity

enableHighAccuracy 可选选项接收一个布尔值,指定是否尝试获取最精确的可能值,默认值 false

权限策略

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

默认值是 *,即允许任意源的浏览上下文使用该 API

权限 API

该 API 调用需要获得用户 geolocation 权限的允许,可以调用 Permission.query() 方法检查用户是否已授予了该权限

1
2
3
4
5
6
7
8
9
10
11
12
13
navigator.geolocation.getCurrentPosition(
(position) => {
// todo something with the position data
},
(error) => {
// todo something when getting position failed
},
{
maximumAge: 0,
timeout: Infinity,
enableHighAccuracy: false,
}
)

监听地理位置

使用 Geolocation 接口的 watchPosition() 方法注册监听地理位置的更新

方法的参数类似于 getCurrentPosition() 方法

方法返回一个数字,代表注册回调的 ID,可用于 clearWatch() 方法取消监听

使用 Geolocation 接口的 clearWatch() 方法取消注册监听地理位置的更新

地理位置信息

GeolocationPosition 接口用于表示一组地址位置信息记录

coords 只读属性代表一个 GeolocationCoordinates 实例,表示地址位置信息的具体内容

timestamp 只读属性返回一个时间戳,代表获取地址位置信息的时间点

GeolocationCoordinates 接口用于表示一个地址位置信息详情

latitude 只读属性代表纬度

longitude 只读属性代表经度

accuracy 只读属性代表经纬度精度

altitude 只读属性代表海平面高度,设备不支持时返回 null

altitudeAccuracy 只读属性代表海平面高度精度,设备不支持时返回 null

heading 只读属性代表设备方向,设备不支持时返回 null

speed 只读属性代表设备移动速度,设备不支持时返回 null

异常处理

GeolocationPositionError 接口表示获取地理位置失败的异常

code 只读属性代表错误状态码,可能为常量枚举 PERMISSION_DENIEDPOSITION_UNAVAILABLETIMEOUT 之一,常量枚举可以通过 GeolocationPositionError 本身或其实例访问

message 只读属性代表错误信息,通常是用于调试目的而非直接向用户展示

权限策略

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

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

权限 API

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

类型

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
interface Navigator {
readonly geolocation: Geolocation
}

interface Geolocation {
clearWatch(watchId: number): void
getCurrentPosition(successCallback: PositionCallback, errorCallback?: PositionErrorCallback | null, options?: PositionOptions): void
watchPosition(successCallback: PositionCallback, errorCallback?: PositionErrorCallback | null, options?: PositionOptions): number
}

interface PositionCallback {
(position: GeolocationPosition): void
}

interface PositionErrorCallback {
(positionError: GeolocationPositionError): void
}

interface PositionOptions {
enableHighAccuracy?: boolean
maximumAge?: number
timeout?: number
}

interface GeolocationPosition {
readonly coords: GeolocationCoordinates
readonly timestamp: EpochTimeStamp
}

interface GeolocationPositionError {
readonly code: number
readonly message: string
readonly PERMISSION_DENIED: 1
readonly POSITION_UNAVAILABLE: 2
readonly TIMEOUT: 3
}

interface GeolocationCoordinates {
readonly accuracy: number
readonly altitude: number | null
readonly altitudeAccuracy: number | null
readonly heading: number | null
readonly latitude: number
readonly longitude: number
readonly speed: number | null
}

链接

File System API

File System API 允许使用内置文件系统,包含读取、修改和移除文件,扩展的 File System Access API 允许访问本地文件系统

  • FileSystemHandle 接口作为文件句柄或目录句柄的通用接口
  • FileSystemFileHandle 接口作为文件句柄的接口
  • FileSystemDirectoryHandle 接口作为目录句柄的接口
  • FileSystemSyncAccessHandle 接口作为同步访问句柄的接口
  • FileSystemWritableFileStream 接口作为读写本地文件流的接口
  • StorageManager 接口上的 getDirectory() 方法用于获取 Origin Private File System 的根目录句柄

通常而言,File System API 需要用户明确的许可,但 OPFS —— Origin Private File System 机制除外。

通常 File System API 的大多数操作是异步的,支持同步的 FileSystemSyncAccessHandle 接口仅在 Web Worker 内可用且仅允许使用于 OPFS。

初始化

获取 OPFS 的根目录句柄

使用 StorageManager 接口的 getDirectory() 方法获取 OPFS 的根目录句柄

返回一个 Promise 的 FileSystemDirectoryHandle 对象

并在用户代理无法使用请求的文件目录建立本地 OPFS 索引时抛出 SecurityError 异常

文件及目录操作

通过 FileSystemHandle 接口进行文件目录相关的操作,该接口是 FileSystemFileHandle 接口与 FileSystemDirectoryHandle 接口的父接口

句柄类型

FileSystemHandle 接口的 kind 属性返回一个字符串枚举值,代表句柄的类型

当当前句柄为 FileSystemFileHandle 时,返回 'file'

当当前句柄为 FileSystemDirectoryHandle 时,返回 'directory'

句柄名称

FileSystemHandle 接口的 name 属性返回一个字符串,代表句柄对应的文件或目录的名称

句柄比较

FileSystemHandle 接口的 isSameEntry() 方法判断当前句柄与传入的句柄是否指向同一个文件或目录

方法接收一个 FileSystemHandle

方法返回一个 Promise 的 boolean

权限操作

FileSystemHandle 接口的 queryPermission() 方法用于枚举当前句柄的指定权限

FileSystemHandle 接口的 requestPermission() 方法用于请求当前句柄的指定权限

方法均支持传入一组可选的描述符,唯一 mode 参数可以为 'read''readwrite' 之一,默认为 'read'

方法均返回一个 Promise 的 PermissionStatus 接口实例

文件操作

通过 FileSystemFileHandle 接口进行文件相关的操作,该接口继承自 FileSystemHandle 接口

获取文件

FileSystemFileHandle 接口的 getFile() 方法可以用于读取文件相关信息及用于网络传输

返回一个 Promise 的 File 对象

抛出一个 NotAllowedError 若未授予访问权限

获取同步访问句柄

FileSystemFileHandle 接口的 createSyncAccessHandle() 方法可以用于同步读写文件内容,但仅允许在专属 Worker 内部使用,且目前仅适用于 OPFS 机制

返回一个 Promise 的 FileSystemSyncAccessHandle 对象

抛出一个 NotAllowedError 若未授予访问权限

抛出一个 NoModificationAllowedError 若无法建立文件锁

抛出一个 InvalidStateError 若句柄无法表示 OPFS 中的文件

获取可写文件流

FileSystemFileHandle 接口的 createWritable() 方法可以用于修改文件内容

可以传入一组可选的配置项,唯一选项 keepExistingData 指定

返回一个 Promise 的 FileSystemWritableFileStream 对象

抛出一个 NotAllowedError 若未授予访问权限

目录操作

通过 FileSystemDirectoryHandle 接口进行目录相关的操作,该接口继承自 FileSystemHandle 接口

目录遍历

FileSystemDirectoryHandle 接口支持异步遍历,包含 [@@asyncIterator]() 以及 entries()keys()values() 等方法

获取路径

FileSystemDirectoryHandle 接口的 resolve() 方法用于获取当前句柄到指定句柄的相对路径

需要传入一个 FileSystemHandle,代表目标句柄

返回一个 Promise 的字符串数组或 null

获取子目录

FileSystemDirectoryHandle 接口的 getDirectoryHandle() 方法用于获取当前目录下的指定名称的子目录的句柄

需要传入一个字符串参数,代表目录的名称,与 FileSystemHandle.name 相符

可以传入一组配置项,唯一参数 create 为一个布尔值,指定目录不存在情况下是否创建目录,默认值为 false

返回一个 Promise 的 FileSystemDirectoryHandle 对象

抛出一个 TypeErrorname 参数不是字符串或为非法的文件系统名称

抛出一个 TypeMismatchError 若匹配到的为文件而非目录

抛出一个 NotFoundError 若未找到目录且 create 选项设定为 false

抛出一个 NotAllowedError 若未授予访问权限

获取子文件

FileSystemDirectoryHandle 接口的 getFileHandle() 方法用于获取当前目录下的指定名称的子文件的句柄

需要传入一个字符串参数,代表目录的名称,与 FileSystemHandle.name 相符

可以传入一组配置项,唯一参数 create 为一个布尔值,指定目录不存在情况下是否创建目录,默认值为 false

返回一个 Promise 的 FileSystemFileHandle 对象

抛出一个 TypeErrorname 参数不是字符串或为非法的文件系统名称

抛出一个 TypeMismatchError 若匹配到的为目录而非文件

抛出一个 NotFoundError 若未找到文件且 create 选项设定为 false

抛出一个 NotAllowedError 若未授予访问权限

删除子目录或子文件

FileSystemDirectoryHandle 接口的 removeEntry() 方法用于删除当前目录下的指定名称的子目录或子文件的句柄

需要传入一个字符串参数,代表目录的名称,与 FileSystemHandle.name 相符

可以传入一组配置项,唯一参数 recursive 为一个布尔值,指定是否递归删除,默认值为 false

返回一个 Promise 的 undefined

抛出一个 TypeErrorname 参数不是字符串或为非法的文件系统名称

抛出一个 InvalidModificationError 若目标为目录,并且包含子文件或子目录,并且 recursive 设置为 false

抛出一个 NotFoundError 若未找到文件或目录

抛出一个 NotAllowedError 若未授予访问权限

文件读取

文件读取通过 FileSystemFileHandle 接口的 getFile() 方法获取到对应的 File 实例实现

文件修改

文件修改通过 FileSystemFileHandle 接口的 createWritable() 方法获取到对应的 FileSystemWritableFileStream 实例实现

FileSystemWritableFileStream 接口继承自 WritableStream 接口

FileSystemWritableFileStream 接口的更改不会立即反映到实际的文件上,仅在关闭流之后才会同步其产生的更改;原因是对流的更改,至少会存储到一个临时文件中,仅在流关闭之后,才会将更改同步到实际的文件中

移动指针

使用 FileSystemWritableFileStream 接口的 seek() 方法移动文件指针的位置

需要传入一个正整数 position 参数,代表文件指针的位置

返回一个 Promise

抛出一个 NotAllowedError 若未授予访问权限

抛出一个 TypeErrorposition 参数不是正整数或未传递

文件写入

使用 FileSystemWritableFileStream 接口的 write() 方法用于向文件中写入内容

可以传入一个 data 参数,代表需要写入文件的内容,可以是 ArrayBuffer TypedArray DataView Blobstring

亦可以传入一组配置项:

type 选项传入一组字符串枚举,指定操作的模式,可以是 "write" "seek""truncate"

data 选项,代表需要写入文件的内容,可以是 ArrayBuffer TypedArray DataView Blobstring,在 "write" 模式下是必须的

position 选项,代表需要移动的文件指针的目标位置,是一个正整数,在 "seek" 模式下是必须的;同样可以在 "write" 模式下使用,此时代表写入内容的目标位置

size 选项,代表需要文件流的大小,是一个正整数,在 "truncate" 模式下是必须的

返回一个 Promise

抛出一个 NotAllowedError 若未授予访问权限

抛出一个 TypeError 若传入参数非法

抛出一个 InvalidStateErrorposition 选项的值超出文件大小

文件尺寸修改

使用 FileSystemWritableFileStream 接口的 truncate() 方法改变文件的尺寸;方法可能会改变文件指针的位置

需要传入一个正整数 size 参数,代表目标的文件尺寸;若参数超出原有尺寸,则扩大当前文件并使用空内容填充扩大的部分,反之会裁剪当前文件

返回一个 Promise

抛出一个 NotAllowedError 若未授予访问权限

抛出一个 TypeErrorsize 参数不是正整数或未传递

文件同步读写

文件同步读写通过 FileSystemFileHandle 接口的 createSyncAccessHandle() 方法获取到对应的 FileSystemSyncAccessHandle 实例实现

FileSystemSyncAccessHandle 实例仅支持在专属 Worker 中使用,且目前仅适用于 OPFS 机制

因为其无需进行权限检查,其相对而言具有更好的性能

创建 FileSystemSyncAccessHandle 实例会创建与之对应的文件锁,阻止对该文件创建其他的 FileSystemSyncAccessHandle 实例或 FileSystemWritableFileStream 实例,直到 FileSystemSyncAccessHandle 实例被销毁

FileSystemSyncAccessHandle 接口的 read() 方法读取文件内容

方法传入一个 buffer 参数,可以是 ArrayBuffer SharedArrayBuffer TypedArrayDataView,代表用于存储读取文件内容的缓存区

方法支持传入一个可选的配置项:其 at 参数指定开始读取文件内容的起始位置

方法返回一个正整数,代表读取的文件内容的字节数

方法在若对应的句柄已关闭的情况下,抛出一个 InvalidStateError

FileSystemSyncAccessHandle 接口的 write() 方法向文件写入内容

方法传入一个 buffer 参数,可以是 ArrayBuffer SharedArrayBuffer TypedArrayDataView,代表将用于写入的文件内容

方法可以传入一个可选的配置项:其 at 参数指定开始写入文件内容的起始位置

方法返回一个正整数,代表写入的文件内容的字节数

方法在若对应的句柄已关闭的情况下,抛出一个 InvalidStateError

读取尺寸

FileSystemSyncAccessHandle 接口的 getSize() 方法返回文件的尺寸

方法返回一个正整数,代表文件内容的字节数

方法在若对应的句柄已关闭的情况下,抛出一个 InvalidStateError

更改尺寸

FileSystemSyncAccessHandle 接口的 truncate() 方法用于更改文件的尺寸

方法传入一个 newSize 参数,需要是一个正整数,代表将更改的文件的目标大小

方法在若对应的句柄已关闭的情况下,抛出一个 InvalidStateError

方法在若对应的句柄已关闭的情况下,抛出一个 InvalidStateError

刷新缓冲区

FileSystemSyncAccessHandle 接口的 flush() 方法用于将缓冲的更改同步至存储,通常只在特定时间段需要将缓冲的更改同步至存储时使用,否则可以让底层自行处理

关闭句柄

FileSystemSyncAccessHandle 接口的 close() 方法用于关闭当前句柄,释放文件锁

类型

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 FileSystemHandle {
readonly kind: FileSystemHandleKind
readonly name: string
isSameEntry(other: FileSystemHandle): Promise<boolean>
}

interface FileSystemFileHandle extends FileSystemHandle {
readonly kind: 'file'
createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>
createWritable(options?: FileSystemCreateWritableOptions): Promise<FileSystemWritableFileStream>
getFile(): Promise<File>
}

interface FileSystemDirectoryHandle extends FileSystemHandle {
readonly kind: 'directory'
getDirectoryHandle(name: string, options?: FileSystemGetDirectoryOptions): Promise<FileSystemDirectoryHandle>
getFileHandle(name: string, options?: FileSystemGetFileOptions): Promise<FileSystemFileHandle>
removeEntry(name: string, options?: FileSystemRemoveOptions): Promise<void>
resolve(possibleDescendant: FileSystemHandle): Promise<string[] | null>
}

interface FileSystemWritableFileStream extends WritableStream {
seek(position: number): Promise<void>
truncate(size: number): Promise<void>
write(data: FileSystemWriteChunkType): Promise<void>
}

interface FileSystemSyncAccessHandle {
close(): void;
flush(): void;
getSize(): number;
read(buffer: AllowSharedBufferSource, options?: FileSystemReadWriteOptions): number;
truncate(newSize: number): void;
write(buffer: AllowSharedBufferSource, options?: FileSystemReadWriteOptions): number;
}

interface FileSystemCreateWritableOptions {
keepExistingData?: boolean
}

interface FileSystemGetDirectoryOptions {
create?: boolean
}

interface FileSystemGetFileOptions {
create?: boolean
}

interface FileSystemReadWriteOptions {
at?: number
}

interface FileSystemRemoveOptions {
recursive?: boolean
}

type FileSystemWriteChunkType = BufferSource | Blob | string | WriteParams

链接

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[]>
};

链接

PWA

PWA 即 Progressive Web App ———— 渐进式网络应用

PWA 技术允许用户像 Native 应用一样使用 Web 应用,支持多平台和多设备访问,并且支持在线或离线访问

PWA 技术的核心是 ServiceWorker 技术,并基于 ServiceWorker 技术支持离线访问

PWA 特点

  • 可通过 URL 访问并被搜索引擎抓取
  • 可以安装到本地(A2HS)
  • 可以使用 URL 分享
  • 可以在离线状态访问
  • 适配老版浏览器访问,并支持在新浏览器中使用更多新特性
  • 支持在有新内容时更新
  • 能适配各种尺寸屏幕
  • 仅通过 HTTPS 提供服务

PWA 创建

  1. 页面通过 link 标签引入 manifest 文件

    文件后缀名可以是 json 或者 webapp、webmanifest

    1
    <link rel="manifest" href="manifest.json" />

    该文件是一个 JSON 的语法,必须指定的项为 nameicons(对于 Chromium 系浏览器 start_urldisplaydisplay_override 也是需要指定的)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "name": "My PWA",
    "icons": [
    {
    "src": "icons/512.png",
    "type": "image/png",
    "sizes": "512x512"
    }
    ]
    }

    name 指定应用的名称,接受一个字符串

    icons 指定应用的图标,接受一个数组,数组各项可以指定 srcsizestype 项,分别代表图标的 URL、尺寸及 MIME 类型

    manifest.json 文件的详细配置可以参考相关的文档

  2. 页面中注册 ServiceWorker,且 ServiceWorker 中监听了 fetch 事件

  3. 页面须启用 Secure Context,即使用 HTTPS 协议或者为本地资源

PWA 下载

PWA 下载可以通过浏览器访问对应的网页实现

通常浏览器检测到网页支持下载为 PWA 时,会显示一个默认的“下载为 PWA”的按钮,也可以自定义“下载为 PWA”的按钮

通过监听全局的 beforeinstallprompt 事件获取到 BeforeInstallPromptEvent 事件实例并存储,通常该事件在页面加载时即触发;在必要时刻调用 BeforeInstallPromptEvent.prompt() 方法以使用户确认下载 PWA 应用;下载完成后会在全局触发 appinstalled 事件

PWA 下载亦可以通过应用商店等场景下载

PWA 原理

PWA 实质是仅将应用行为上类似于原生应用,如在桌面显示图标,在应用列表显示,支持卸载等;并不会将应用程序的资源文件主动下载至本地,具体策略由开发者通过 IndexedDB、ServiceWorker、Cache Storage 等开发实施

示例

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'

链接


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