selenium-webdriver 4.47.0 → 4.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +19 -0
- package/bidi/domain.js +87 -0
- package/bidi/generated/browsing_context.d.ts +5 -0
- package/bidi/generated/emulation.d.ts +30 -4
- package/bidi/index.js +187 -15
- package/bidi/serialization/enum.js +35 -0
- package/bidi/serialization/record.js +345 -0
- package/bidi/serialization/registry.js +34 -0
- package/bidi/serialization/union.js +102 -0
- package/bin/linux-arm64/selenium-manager +0 -0
- package/bin/{linux → linux-x86_64}/selenium-manager +0 -0
- package/bin/macos/selenium-manager +0 -0
- package/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +343 -129
- package/bin/selenium-manager.cdx.json +947 -868
- package/bin/windows/selenium-manager.exe +0 -0
- package/chromium.js +1 -1
- package/common/seleniumManager.js +2 -2
- package/example/chrome_mobile_emulation.js +1 -1
- package/lib/bidi_connection.js +84 -0
- package/lib/logging.js +35 -0
- package/lib/webdriver.js +56 -19
- package/package.json +4 -4
package/CHANGES.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 4.49.0
|
|
2
|
+
|
|
3
|
+
- Support CDP versions: v151, v152, v153
|
|
4
|
+
- [build] track the generated BiDi schema (#17962)
|
|
5
|
+
- derive BiDi field names and enum value types in the schema (#17966)
|
|
6
|
+
- [build] Automated Dependency Update (#17937)
|
|
7
|
+
- [build] Automated Dependency Update (#17991)
|
|
8
|
+
- [build] ship prebuilt Selenium Manager Linux arm64 binary in all bindings (#17999)
|
|
9
|
+
- [build] Automated Browser Version Update (major) with CDP (#18005)
|
|
10
|
+
|
|
11
|
+
## 4.48.0
|
|
12
|
+
|
|
13
|
+
- Support CDP versions: v150, v151, v152
|
|
14
|
+
- Normalize empty custom locator results (#17851)
|
|
15
|
+
- [build] Automated Browser Version Update (major) with CDP (#17910)
|
|
16
|
+
- Ensure BiDi is not exposed on Driver (#17926)
|
|
17
|
+
- Add serialization and domain layer (#17927)
|
|
18
|
+
- [bidi] Add BiDi connection-level event subscription (#17946)
|
|
19
|
+
|
|
1
20
|
## 4.47.0
|
|
2
21
|
|
|
3
22
|
- Support CDP versions: v149, v150, v151
|
package/bidi/domain.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
2
|
+
// or more contributor license agreements. See the NOTICE file
|
|
3
|
+
// distributed with this work for additional information
|
|
4
|
+
// regarding copyright ownership. The SFC licenses this file
|
|
5
|
+
// to you under the Apache License, Version 2.0 (the
|
|
6
|
+
// "License"); you may not use this file except in compliance
|
|
7
|
+
// with the License. You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing,
|
|
12
|
+
// software distributed under the License is distributed on an
|
|
13
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
// KIND, either express or implied. See the License for the
|
|
15
|
+
// specific language governing permissions and limitations
|
|
16
|
+
// under the License.
|
|
17
|
+
|
|
18
|
+
const { getBidiConnection } = require('../lib/bidi_connection')
|
|
19
|
+
|
|
20
|
+
// Gates Domain's constructor so `new Network(someRandomThing)` fails loudly
|
|
21
|
+
// instead of silently producing a broken instance. A Symbol can't be forged
|
|
22
|
+
// or guessed, so this is real runtime enforcement, not just a TS annotation —
|
|
23
|
+
// only a generated `Class.create(driver)` (and this package's own tests) may
|
|
24
|
+
// pass it. It's exported deliberately, not hidden: the point is to stop
|
|
25
|
+
// accidental misuse of the normal `new Network(x)` shape, not to defend
|
|
26
|
+
// against someone who deliberately imports and passes this.
|
|
27
|
+
const DOMAIN_TOKEN = Symbol('Domain internal construction token — obtained only via Class.create(driver)')
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Describes one subscribable BiDi event, for use with Domain#addCallback().
|
|
31
|
+
* @param {string} method
|
|
32
|
+
* @param {{fromWire(payload: unknown): unknown}} [type] Runtime record/union
|
|
33
|
+
* class for the event's params, if the schema declares one. When present,
|
|
34
|
+
* addCallback() parses each delivered payload through it before the
|
|
35
|
+
* caller's handler runs — inbound wire payloads are validated against
|
|
36
|
+
* their resolved type; an event's params is such a payload just as much
|
|
37
|
+
* as a command's result is.
|
|
38
|
+
* @returns {{method: string, type: ({fromWire(payload: unknown): unknown}|undefined)}}
|
|
39
|
+
* The event descriptor, ready to pass to Domain#addCallback().
|
|
40
|
+
*/
|
|
41
|
+
function event(method, type) {
|
|
42
|
+
return { method, type }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Shared base for every generated BiDi domain class. See domain.d.ts for the typed surface. */
|
|
46
|
+
class Domain {
|
|
47
|
+
#bidi
|
|
48
|
+
|
|
49
|
+
constructor(bidi, token) {
|
|
50
|
+
if (token !== DOMAIN_TOKEN) {
|
|
51
|
+
throw new TypeError(`${new.target.name} must be constructed via ${new.target.name}.create(driver), not \`new\``)
|
|
52
|
+
}
|
|
53
|
+
this.#bidi = bidi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static async connect(driver) {
|
|
57
|
+
return getBidiConnection(driver)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async send(method, params) {
|
|
61
|
+
const response = await this.#bidi.send({ method, params })
|
|
62
|
+
if (response?.error !== undefined) {
|
|
63
|
+
throw new Error(`${response.error}: ${response.message}`)
|
|
64
|
+
}
|
|
65
|
+
return response?.result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Subscribes `handler` to a BiDi event. All the actual subscription-lifecycle
|
|
70
|
+
* work — remote subscribe/unsubscribe, per-subscription bookkeeping — lives on
|
|
71
|
+
* the connection itself (see Index#addCallback in bidi/index.js); Domain only
|
|
72
|
+
* adds the one thing the connection can't do on its own: parsing a delivered
|
|
73
|
+
* payload through the descriptor's type before the caller's handler runs.
|
|
74
|
+
* @param {{method: string, type: ({fromWire(payload: unknown): unknown}|undefined)}} descriptor
|
|
75
|
+
* An event descriptor from event().
|
|
76
|
+
* @param {function(unknown): void} handler Invoked with the event's params
|
|
77
|
+
* (parsed through descriptor.type first, if one was given) each time it fires.
|
|
78
|
+
* @returns {Promise<{id: string, unsubscribe: function(): Promise<void>}>}
|
|
79
|
+
* A handle for this subscription — call `unsubscribe()` to stop receiving the event.
|
|
80
|
+
*/
|
|
81
|
+
async addCallback(descriptor, handler) {
|
|
82
|
+
const dispatch = descriptor.type === undefined ? handler : (params) => handler(descriptor.type.fromWire(params))
|
|
83
|
+
return this.#bidi.addCallback(descriptor.method, dispatch)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { Domain, event, DOMAIN_TOKEN }
|
|
@@ -77,11 +77,16 @@ export interface BrowsingContextCaptureScreenshotParameters {
|
|
|
77
77
|
origin?: "viewport" | "document";
|
|
78
78
|
format?: BrowsingContextImageFormat;
|
|
79
79
|
clip?: BrowsingContextClipRectangle;
|
|
80
|
+
imageSize?: BrowsingContextImageSize;
|
|
80
81
|
}
|
|
81
82
|
export interface BrowsingContextImageFormat {
|
|
82
83
|
type: string;
|
|
83
84
|
quality?: number;
|
|
84
85
|
}
|
|
86
|
+
export interface BrowsingContextImageSize {
|
|
87
|
+
maxWidth?: JsUint;
|
|
88
|
+
maxHeight?: JsUint;
|
|
89
|
+
}
|
|
85
90
|
export type BrowsingContextClipRectangle = BrowsingContextBoxClipRectangle | BrowsingContextElementClipRectangle;
|
|
86
91
|
export interface BrowsingContextElementClipRectangle {
|
|
87
92
|
type: "element";
|
|
@@ -73,10 +73,36 @@ export interface EmulationSetMediaFeaturesOverrideParameters {
|
|
|
73
73
|
contexts?: BrowsingContextBrowsingContext[];
|
|
74
74
|
userContexts?: BrowserUserContext[];
|
|
75
75
|
}
|
|
76
|
-
export
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
export interface EmulationMediaFeatures {
|
|
77
|
+
anyHover?: "none" | "hover" | null;
|
|
78
|
+
anyPointer?: "none" | "coarse" | "fine" | null;
|
|
79
|
+
color?: JsUint | null;
|
|
80
|
+
colorGamut?: "srgb" | "p3" | "rec2020" | null;
|
|
81
|
+
colorIndex?: JsUint | null;
|
|
82
|
+
displayMode?: "fullscreen" | "standalone" | "minimal-ui" | "browser" | "picture-in-picture" | null;
|
|
83
|
+
dynamicRange?: "standard" | "high" | null;
|
|
84
|
+
environmentBlending?: "opaque" | "additive" | "subtractive" | null;
|
|
85
|
+
forcedColors?: "none" | "active" | null;
|
|
86
|
+
grid?: 0 | 1 | null;
|
|
87
|
+
horizontalViewportSegments?: JsUint | null;
|
|
88
|
+
hover?: "none" | "hover" | null;
|
|
89
|
+
invertedColors?: "none" | "inverted" | null;
|
|
90
|
+
monochrome?: JsUint | null;
|
|
91
|
+
navControls?: "none" | "back" | null;
|
|
92
|
+
overflowBlock?: "none" | "scroll" | "optional-paged" | "paged" | null;
|
|
93
|
+
overflowInline?: "none" | "scroll" | null;
|
|
94
|
+
pointer?: "none" | "coarse" | "fine" | null;
|
|
95
|
+
prefersColorScheme?: "light" | "dark" | null;
|
|
96
|
+
prefersContrast?: "no-preference" | "more" | "less" | "custom" | null;
|
|
97
|
+
prefersReducedData?: "no-preference" | "reduce" | null;
|
|
98
|
+
prefersReducedMotion?: "no-preference" | "reduce" | null;
|
|
99
|
+
prefersReducedTransparency?: "no-preference" | "reduce" | null;
|
|
100
|
+
scan?: "interlace" | "progressive" | null;
|
|
101
|
+
scripting?: "none" | "initial-only" | "enabled" | null;
|
|
102
|
+
update?: "none" | "slow" | "fast" | null;
|
|
103
|
+
verticalViewportSegments?: JsUint | null;
|
|
104
|
+
videoColorGamut?: "srgb" | "p3" | "rec2020" | null;
|
|
105
|
+
videoDynamicRange?: "standard" | "high" | null;
|
|
80
106
|
}
|
|
81
107
|
export type EmulationSetMediaFeaturesOverrideResult = EmptyResult;
|
|
82
108
|
export interface EmulationSetNetworkConditions {
|
package/bidi/index.js
CHANGED
|
@@ -36,6 +36,10 @@ class Index extends EventEmitter {
|
|
|
36
36
|
this._closed = false
|
|
37
37
|
this._pending = new Map()
|
|
38
38
|
this._connectWaiters = new Set()
|
|
39
|
+
// removeCallback(id) only receives the subscriptionId — off() needs the event
|
|
40
|
+
// name and the exact handler function too, so this holds what it needs to
|
|
41
|
+
// detach the right listener without the caller having to keep them around.
|
|
42
|
+
this._callbacks = new Map()
|
|
39
43
|
this._ws = new WebSocket(_webSocketUrl)
|
|
40
44
|
this._ws.on('open', () => {
|
|
41
45
|
// The handshake can complete after close()/_failPending() has already
|
|
@@ -71,12 +75,7 @@ class Index extends EventEmitter {
|
|
|
71
75
|
} catch (err) {
|
|
72
76
|
// Surface protocol parse failures rather than silently dropping —
|
|
73
77
|
// otherwise callers see misleading send() timeouts.
|
|
74
|
-
|
|
75
|
-
if (this.listenerCount('error') > 0) {
|
|
76
|
-
this.emit('error', wrapped)
|
|
77
|
-
} else {
|
|
78
|
-
process.emitWarning(wrapped.message, 'BiDiProtocolWarning')
|
|
79
|
-
}
|
|
78
|
+
this._emitOrWarn(new Error(`Failed to parse BiDi message: ${err.message}`), 'BiDiProtocolWarning')
|
|
80
79
|
return
|
|
81
80
|
}
|
|
82
81
|
// Messages without a numeric id are BiDi events, not command responses.
|
|
@@ -94,14 +93,31 @@ class Index extends EventEmitter {
|
|
|
94
93
|
// method named 'error' through the same guarded path used for JSON
|
|
95
94
|
// parse failures rather than forwarding it directly.
|
|
96
95
|
if (payload.method === 'error') {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
process.emitWarning(err.message, 'BiDiProtocolWarning')
|
|
102
|
-
}
|
|
96
|
+
this._emitOrWarn(
|
|
97
|
+
new Error(`BiDi protocol error event: ${JSON.stringify(payload.params)}`),
|
|
98
|
+
'BiDiProtocolWarning',
|
|
99
|
+
)
|
|
103
100
|
} else {
|
|
104
|
-
|
|
101
|
+
// A listener can throw synchronously — most notably a typed
|
|
102
|
+
// addCallback() dispatcher's fromWire() rejecting a corrupted
|
|
103
|
+
// payload, which is meant to error rather than warn. Dispatched
|
|
104
|
+
// one listener at a time (not via a single this.emit() call) so a
|
|
105
|
+
// throwing listener doesn't prevent a sibling listener registered
|
|
106
|
+
// for the same event from still receiving this delivery — emit()
|
|
107
|
+
// itself aborts the rest of its iteration once one listener throws.
|
|
108
|
+
// rawListeners(), not listeners(): listeners() unwraps a once()
|
|
109
|
+
// registration to the caller's original function, so invoking it
|
|
110
|
+
// here directly (bypassing emit()) would skip the internal wrapper
|
|
111
|
+
// that removes it after one call — rawListeners() returns that
|
|
112
|
+
// wrapper itself, preserving once()'s self-removal.
|
|
113
|
+
for (const listener of this.rawListeners(payload.method)) {
|
|
114
|
+
try {
|
|
115
|
+
listener(payload.params)
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const wrapped = err instanceof Error ? err : new Error(String(err))
|
|
118
|
+
this._emitOrWarn(wrapped, 'BiDiEventHandlerWarning')
|
|
119
|
+
}
|
|
120
|
+
}
|
|
105
121
|
}
|
|
106
122
|
}
|
|
107
123
|
return
|
|
@@ -147,6 +163,38 @@ class Index extends EventEmitter {
|
|
|
147
163
|
reject(error)
|
|
148
164
|
}
|
|
149
165
|
this._connectWaiters.clear()
|
|
166
|
+
// Detach every addCallback() listener too. Once closed, removeCallback()
|
|
167
|
+
// can no longer reach the remote end (send() would just throw), so nothing
|
|
168
|
+
// else will ever detach these listeners. Drop them here instead of leaving
|
|
169
|
+
// them attached to an EventEmitter nothing will ever emit on again.
|
|
170
|
+
for (const { method, handler } of this._callbacks.values()) {
|
|
171
|
+
this.off(method, handler)
|
|
172
|
+
}
|
|
173
|
+
this._callbacks.clear()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Emits `err` as an 'error' event if anything is listening for one,
|
|
178
|
+
* otherwise reports it as a process warning under `warningType` — never
|
|
179
|
+
* emits 'error' with no listener attached, which would itself throw and
|
|
180
|
+
* crash the process. Also guards against the 'error' listener itself
|
|
181
|
+
* throwing, so a broken listener can't cause the exact kind of crash this
|
|
182
|
+
* helper exists to prevent, just one level removed.
|
|
183
|
+
* @param {Error} err
|
|
184
|
+
* @param {string} warningType
|
|
185
|
+
* @private
|
|
186
|
+
*/
|
|
187
|
+
_emitOrWarn(err, warningType) {
|
|
188
|
+
if (this.listenerCount('error') === 0) {
|
|
189
|
+
process.emitWarning(err.message, warningType)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
this.emit('error', err)
|
|
194
|
+
} catch (listenerErr) {
|
|
195
|
+
const wrapped = listenerErr instanceof Error ? listenerErr : new Error(String(listenerErr))
|
|
196
|
+
process.emitWarning(`BiDi 'error' listener threw: ${wrapped.message}`, warningType)
|
|
197
|
+
}
|
|
150
198
|
}
|
|
151
199
|
|
|
152
200
|
/**
|
|
@@ -229,7 +277,17 @@ class Index extends EventEmitter {
|
|
|
229
277
|
}
|
|
230
278
|
|
|
231
279
|
/**
|
|
232
|
-
* Subscribe to events
|
|
280
|
+
* Subscribe to events.
|
|
281
|
+
*
|
|
282
|
+
* Not the correct implementation — this is not tied to a subscription id, so
|
|
283
|
+
* {@link unsubscribe} below cancels by event/context name and can affect a
|
|
284
|
+
* subscription made elsewhere (including via {@link addCallback}) for the
|
|
285
|
+
* same event. Kept as-is only because the existing hand-written bidi/*.js
|
|
286
|
+
* modules already depend on this exact shape; new code should use
|
|
287
|
+
* {@link addCallback} instead, which is properly scoped by subscription id
|
|
288
|
+
* (mirroring Java's BiDi#addListener/removeListener — see BiDi.java). Once
|
|
289
|
+
* those hand-written modules are replaced by generated code built on
|
|
290
|
+
* addCallback/removeCallback, this method (and unsubscribe) can be removed.
|
|
233
291
|
* @param events
|
|
234
292
|
* @param browsingContexts
|
|
235
293
|
* @returns {Promise<void>}
|
|
@@ -273,7 +331,10 @@ class Index extends EventEmitter {
|
|
|
273
331
|
}
|
|
274
332
|
|
|
275
333
|
/**
|
|
276
|
-
* Unsubscribe to events
|
|
334
|
+
* Unsubscribe to events. See the note on {@link subscribe} above — this
|
|
335
|
+
* cancels by event/context name, not by subscription id, so it can affect a
|
|
336
|
+
* subscription this same connection made elsewhere. New code should call
|
|
337
|
+
* the returned handle's `unsubscribe()` from {@link addCallback} instead.
|
|
277
338
|
* @param events
|
|
278
339
|
* @param browsingContexts
|
|
279
340
|
* @returns {Promise<void>}
|
|
@@ -311,6 +372,117 @@ class Index extends EventEmitter {
|
|
|
311
372
|
await this.send(params)
|
|
312
373
|
}
|
|
313
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Registers `handler` to be called on every delivered `method` event,
|
|
377
|
+
* globally (no context/user-context scoping). This is the correct mechanism
|
|
378
|
+
* — see the note on {@link subscribe}/{@link unsubscribe} above, which is a
|
|
379
|
+
* separate, imprecise, event-name-scoped mechanism kept only for the
|
|
380
|
+
* existing hand-written bidi/*.js modules until they're replaced by
|
|
381
|
+
* generated code built on this method instead.
|
|
382
|
+
*
|
|
383
|
+
* Keyed by the server-assigned `subscription` id from `session.subscribe`'s
|
|
384
|
+
* response, which the spec mints fresh on every call — so multiple
|
|
385
|
+
* independent subscriptions to the same event coexist safely, and the
|
|
386
|
+
* returned handle's `unsubscribe()` never affects another callback
|
|
387
|
+
* registered for the same method. No client-side ref-counting is needed:
|
|
388
|
+
* the protocol's own per-subscription id already gives each caller its own
|
|
389
|
+
* independent, individually-cancellable registration.
|
|
390
|
+
*
|
|
391
|
+
* The local listener is attached before `session.subscribe` is awaited, not
|
|
392
|
+
* after — so an event the browser starts sending as soon as it processes
|
|
393
|
+
* the subscription can't arrive in a gap where nothing is listening yet.
|
|
394
|
+
* If the subscribe call then fails (or returns no usable id), the listener
|
|
395
|
+
* is removed again before the error propagates, so a failed subscription
|
|
396
|
+
* doesn't leak one.
|
|
397
|
+
* @param {string} method
|
|
398
|
+
* @param {function(unknown): void} handler
|
|
399
|
+
* @returns {Promise<{id: string, unsubscribe: function(): Promise<void>}>}
|
|
400
|
+
*/
|
|
401
|
+
async addCallback(method, handler) {
|
|
402
|
+
this.on(method, handler)
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const response = await this.send({
|
|
406
|
+
method: 'session.subscribe',
|
|
407
|
+
params: { events: [method] },
|
|
408
|
+
})
|
|
409
|
+
// send() resolves with the raw reply on any response, including a
|
|
410
|
+
// wire-level error — check for one explicitly and surface it plainly,
|
|
411
|
+
// rather than letting it fall through to the generic "no subscription
|
|
412
|
+
// id" message below (matching how Domain#send() reports the same shape).
|
|
413
|
+
if (response?.error !== undefined) {
|
|
414
|
+
throw new Error(`${response.error}: ${response.message}`)
|
|
415
|
+
}
|
|
416
|
+
const subscriptionId = response?.result?.subscription
|
|
417
|
+
if (typeof subscriptionId !== 'string' || subscriptionId === '') {
|
|
418
|
+
throw new Error(`session.subscribe did not return a valid subscription id: ${JSON.stringify(response)}`)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this._callbacks.set(subscriptionId, { method, handler })
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
id: subscriptionId,
|
|
425
|
+
unsubscribe: () => this.removeCallback(subscriptionId),
|
|
426
|
+
}
|
|
427
|
+
} catch (err) {
|
|
428
|
+
this.off(method, handler)
|
|
429
|
+
throw err
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Removes exactly the callback registered under `subscriptionId` (as
|
|
435
|
+
* returned by {@link addCallback}). A no-op if already removed — including
|
|
436
|
+
* once the connection is closed, since _failPending() has already cleaned
|
|
437
|
+
* up local state at that point (and there is no remote end left to reach:
|
|
438
|
+
* entry is only ever defined here while _closed is still false, since
|
|
439
|
+
* _failPending() clears every entry in the same synchronous call that sets
|
|
440
|
+
* _closed). Never affects any other callback, including another one
|
|
441
|
+
* registered for the same method.
|
|
442
|
+
*
|
|
443
|
+
* Local state is only cleaned up once the remote end has confirmed the
|
|
444
|
+
* subscription is actually gone — not before sending session.unsubscribe,
|
|
445
|
+
* and not on a wire-level error response. Cleaning up first would leave a
|
|
446
|
+
* phantom "removed" subscription if the send failed or was rejected: local
|
|
447
|
+
* delivery would stop while the browser kept sending it, and a retry would
|
|
448
|
+
* silently no-op since this method's own early return above would find no
|
|
449
|
+
* entry left to act on.
|
|
450
|
+
*
|
|
451
|
+
* Concurrent calls for the same subscriptionId share one in-flight removal
|
|
452
|
+
* instead of each sending their own session.unsubscribe — a second, racing
|
|
453
|
+
* call would otherwise find the subscription already gone (removed by the
|
|
454
|
+
* first) and get a wire-level error for what was a perfectly valid call.
|
|
455
|
+
* The in-flight marker is cleared once the attempt settles, either way, so
|
|
456
|
+
* a later retry after a failure starts a fresh attempt rather than reusing
|
|
457
|
+
* a rejected one.
|
|
458
|
+
* @param {string} subscriptionId
|
|
459
|
+
* @returns {Promise<void>}
|
|
460
|
+
*/
|
|
461
|
+
async removeCallback(subscriptionId) {
|
|
462
|
+
const entry = this._callbacks.get(subscriptionId)
|
|
463
|
+
if (entry === undefined) {
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (entry.removing === undefined) {
|
|
468
|
+
entry.removing = (async () => {
|
|
469
|
+
const response = await this.send({
|
|
470
|
+
method: 'session.unsubscribe',
|
|
471
|
+
params: { subscriptions: [subscriptionId] },
|
|
472
|
+
})
|
|
473
|
+
if (response?.error !== undefined) {
|
|
474
|
+
throw new Error(`${response.error}: ${response.message}`)
|
|
475
|
+
}
|
|
476
|
+
this._callbacks.delete(subscriptionId)
|
|
477
|
+
this.off(entry.method, entry.handler)
|
|
478
|
+
})().finally(() => {
|
|
479
|
+
entry.removing = undefined
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return entry.removing
|
|
484
|
+
}
|
|
485
|
+
|
|
314
486
|
/**
|
|
315
487
|
* Close ws connection.
|
|
316
488
|
* @returns {Promise<unknown>}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
2
|
+
// or more contributor license agreements. See the NOTICE file
|
|
3
|
+
// distributed with this work for additional information
|
|
4
|
+
// regarding copyright ownership. The SFC licenses this file
|
|
5
|
+
// to you under the Apache License, Version 2.0 (the
|
|
6
|
+
// "License"); you may not use this file except in compliance
|
|
7
|
+
// with the License. You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing,
|
|
12
|
+
// software distributed under the License is distributed on an
|
|
13
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
// KIND, either express or implied. See the License for the
|
|
15
|
+
// specific language governing permissions and limitations
|
|
16
|
+
// under the License.
|
|
17
|
+
|
|
18
|
+
const { register } = require('./registry')
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Registers a schema `enum` — a closed set of string values a field may hold.
|
|
22
|
+
* @param {string} name Schema type name, e.g. 'network.InterceptPhase'.
|
|
23
|
+
* @param {string[]} values The enum's valid values.
|
|
24
|
+
* @returns {{kind: 'enum', values: string[], includes: function(unknown): boolean}}
|
|
25
|
+
* The registered entry, used by validateValue() to check a ref'd value's
|
|
26
|
+
* membership; also returned so a generator can build a discoverable constant from it.
|
|
27
|
+
*/
|
|
28
|
+
function defineEnum(name, values) {
|
|
29
|
+
const allowed = new Set(values)
|
|
30
|
+
const entry = { kind: 'enum', values, includes: (value) => allowed.has(value) }
|
|
31
|
+
register(name, entry)
|
|
32
|
+
return entry
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { defineEnum }
|