selenium-webdriver 4.43.0 → 4.45.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 +21 -0
- package/README.md +57 -185
- package/bidi/browsingContextInspector.js +1 -1
- package/bidi/generated/bluetooth.d.ts +223 -0
- package/bidi/generated/bluetooth.js +154 -0
- package/bidi/generated/browser.d.ts +104 -0
- package/bidi/generated/browser.js +107 -0
- package/bidi/generated/browsing_context.d.ts +394 -0
- package/bidi/generated/browsing_context.js +260 -0
- package/bidi/generated/common.d.ts +38 -0
- package/bidi/generated/common.js +30 -0
- package/bidi/generated/emulation.d.ts +176 -0
- package/bidi/generated/emulation.js +133 -0
- package/bidi/generated/input.d.ts +151 -0
- package/bidi/generated/input.js +81 -0
- package/bidi/generated/log.d.ts +39 -0
- package/bidi/generated/log.js +67 -0
- package/bidi/generated/network.d.ts +359 -0
- package/bidi/generated/network.js +230 -0
- package/bidi/generated/permissions.d.ts +22 -0
- package/bidi/generated/permissions.js +43 -0
- package/bidi/generated/script.d.ts +410 -0
- package/bidi/generated/script.js +146 -0
- package/bidi/generated/session.d.ts +119 -0
- package/bidi/generated/session.js +82 -0
- package/bidi/generated/speculation.d.ts +17 -0
- package/bidi/generated/speculation.js +40 -0
- package/bidi/generated/storage.d.ts +84 -0
- package/bidi/generated/storage.js +70 -0
- package/bidi/generated/user_agent_client_hints.d.ts +33 -0
- package/bidi/generated/user_agent_client_hints.js +43 -0
- package/bidi/generated/webextension.d.ts +42 -0
- package/bidi/generated/webextension.js +53 -0
- package/bidi/index.js +133 -21
- package/bin/linux/selenium-manager +0 -0
- package/bin/macos/selenium-manager +0 -0
- package/bin/windows/selenium-manager.exe +0 -0
- package/common/driverFinder.js +2 -1
- package/common/seleniumManager.js +1 -1
- package/eslint.config.js +5 -5
- package/io/zip.js +1 -1
- package/lib/atoms/find-elements.js +272 -68
- package/lib/atoms/get-attribute.js +152 -17
- package/lib/atoms/is-displayed.js +385 -39
- package/lib/http.js +2 -1
- package/lib/pinnedScript.js +0 -1
- package/lib/webdriver.js +2 -2
- package/package.json +18 -16
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
3
|
+
// or more contributor license agreements. See the NOTICE file
|
|
4
|
+
// distributed with this work for additional information
|
|
5
|
+
// regarding copyright ownership. The SFC licenses this file
|
|
6
|
+
// to you under the Apache License, Version 2.0 (the
|
|
7
|
+
// "License"); you may not use this file except in compliance
|
|
8
|
+
// with the License. You may obtain a copy of the License at
|
|
9
|
+
//
|
|
10
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
//
|
|
12
|
+
// Unless required by applicable law or agreed to in writing,
|
|
13
|
+
// software distributed under the License is distributed on an
|
|
14
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
// KIND, either express or implied. See the License for the
|
|
16
|
+
// specific language governing permissions and limitations
|
|
17
|
+
// under the License.
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.Session = void 0;
|
|
20
|
+
// --- Implementation ---
|
|
21
|
+
class Session {
|
|
22
|
+
constructor(bidi) {
|
|
23
|
+
this.bidi = bidi;
|
|
24
|
+
}
|
|
25
|
+
static async create(driver) {
|
|
26
|
+
const caps = await driver.getCapabilities();
|
|
27
|
+
if (!caps.get('webSocketUrl')) {
|
|
28
|
+
throw new Error('WebDriver instance must support BiDi protocol');
|
|
29
|
+
}
|
|
30
|
+
const bidi = await driver.getBidi();
|
|
31
|
+
return new Session(bidi);
|
|
32
|
+
}
|
|
33
|
+
async end() {
|
|
34
|
+
const response = await this.bidi.send({
|
|
35
|
+
method: 'session.end',
|
|
36
|
+
params: {},
|
|
37
|
+
});
|
|
38
|
+
if (response['type'] === 'error') {
|
|
39
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async new(params) {
|
|
43
|
+
const response = await this.bidi.send({
|
|
44
|
+
method: 'session.new',
|
|
45
|
+
params: params,
|
|
46
|
+
});
|
|
47
|
+
if (response['type'] === 'error') {
|
|
48
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
49
|
+
}
|
|
50
|
+
return response.result;
|
|
51
|
+
}
|
|
52
|
+
async status() {
|
|
53
|
+
const response = await this.bidi.send({
|
|
54
|
+
method: 'session.status',
|
|
55
|
+
params: {},
|
|
56
|
+
});
|
|
57
|
+
if (response['type'] === 'error') {
|
|
58
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
59
|
+
}
|
|
60
|
+
return response.result;
|
|
61
|
+
}
|
|
62
|
+
async subscribe(params) {
|
|
63
|
+
const response = await this.bidi.send({
|
|
64
|
+
method: 'session.subscribe',
|
|
65
|
+
params: params,
|
|
66
|
+
});
|
|
67
|
+
if (response['type'] === 'error') {
|
|
68
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
69
|
+
}
|
|
70
|
+
return response.result;
|
|
71
|
+
}
|
|
72
|
+
async unsubscribe(params) {
|
|
73
|
+
const response = await this.bidi.send({
|
|
74
|
+
method: 'session.unsubscribe',
|
|
75
|
+
params: params,
|
|
76
|
+
});
|
|
77
|
+
if (response['type'] === 'error') {
|
|
78
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.Session = Session;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type SpeculationPreloadingStatus = "pending" | "ready" | "success" | "failure";
|
|
2
|
+
export type SpeculationEvent = SpeculationPrefetchStatusUpdated;
|
|
3
|
+
export interface SpeculationPrefetchStatusUpdated {
|
|
4
|
+
method: "speculation.prefetchStatusUpdated";
|
|
5
|
+
params: SpeculationPrefetchStatusUpdatedParameters;
|
|
6
|
+
}
|
|
7
|
+
export interface SpeculationPrefetchStatusUpdatedParameters {
|
|
8
|
+
context: string;
|
|
9
|
+
url: string;
|
|
10
|
+
status: SpeculationPreloadingStatus;
|
|
11
|
+
}
|
|
12
|
+
export declare class Speculation {
|
|
13
|
+
private readonly bidi;
|
|
14
|
+
private constructor();
|
|
15
|
+
static create(driver: unknown): Promise<Speculation>;
|
|
16
|
+
onPrefetchStatusUpdated(callback: (params: SpeculationPrefetchStatusUpdatedParameters) => void): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
3
|
+
// or more contributor license agreements. See the NOTICE file
|
|
4
|
+
// distributed with this work for additional information
|
|
5
|
+
// regarding copyright ownership. The SFC licenses this file
|
|
6
|
+
// to you under the Apache License, Version 2.0 (the
|
|
7
|
+
// "License"); you may not use this file except in compliance
|
|
8
|
+
// with the License. You may obtain a copy of the License at
|
|
9
|
+
//
|
|
10
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
//
|
|
12
|
+
// Unless required by applicable law or agreed to in writing,
|
|
13
|
+
// software distributed under the License is distributed on an
|
|
14
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
// KIND, either express or implied. See the License for the
|
|
16
|
+
// specific language governing permissions and limitations
|
|
17
|
+
// under the License.
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.Speculation = void 0;
|
|
20
|
+
// --- Implementation ---
|
|
21
|
+
class Speculation {
|
|
22
|
+
constructor(bidi) {
|
|
23
|
+
this.bidi = bidi;
|
|
24
|
+
}
|
|
25
|
+
static async create(driver) {
|
|
26
|
+
const caps = await driver.getCapabilities();
|
|
27
|
+
if (!caps.get('webSocketUrl')) {
|
|
28
|
+
throw new Error('WebDriver instance must support BiDi protocol');
|
|
29
|
+
}
|
|
30
|
+
const bidi = await driver.getBidi();
|
|
31
|
+
return new Speculation(bidi);
|
|
32
|
+
}
|
|
33
|
+
async onPrefetchStatusUpdated(callback) {
|
|
34
|
+
await this.bidi.subscribe('speculation.prefetchStatusUpdated');
|
|
35
|
+
this.bidi.on('speculation.prefetchStatusUpdated', (params) => {
|
|
36
|
+
callback(params);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.Speculation = Speculation;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { BrowsingContextBrowsingContext } from './browsing_context.js';
|
|
2
|
+
import type { Extensible, JsUint } from './common.js';
|
|
3
|
+
import type { NetworkBytesValue, NetworkCookie, NetworkSameSite } from './network.js';
|
|
4
|
+
export type StorageCommand = StorageDeleteCookies | StorageGetCookies | StorageSetCookie;
|
|
5
|
+
export type StorageResult = StorageDeleteCookiesResult | StorageGetCookiesResult | StorageSetCookieResult;
|
|
6
|
+
export type StoragePartitionKey = Extensible & {
|
|
7
|
+
userContext?: string;
|
|
8
|
+
sourceOrigin?: string;
|
|
9
|
+
};
|
|
10
|
+
export interface StorageGetCookies {
|
|
11
|
+
method: "storage.getCookies";
|
|
12
|
+
params: StorageGetCookiesParameters;
|
|
13
|
+
}
|
|
14
|
+
export type StorageCookieFilter = Extensible & {
|
|
15
|
+
name?: string;
|
|
16
|
+
value?: NetworkBytesValue;
|
|
17
|
+
domain?: string;
|
|
18
|
+
path?: string;
|
|
19
|
+
size?: JsUint;
|
|
20
|
+
httpOnly?: boolean;
|
|
21
|
+
secure?: boolean;
|
|
22
|
+
sameSite?: NetworkSameSite;
|
|
23
|
+
expiry?: JsUint;
|
|
24
|
+
};
|
|
25
|
+
export interface StorageBrowsingContextPartitionDescriptor {
|
|
26
|
+
type: "context";
|
|
27
|
+
context: BrowsingContextBrowsingContext;
|
|
28
|
+
}
|
|
29
|
+
export type StorageStorageKeyPartitionDescriptor = Extensible & {
|
|
30
|
+
type: "storageKey";
|
|
31
|
+
userContext?: string;
|
|
32
|
+
sourceOrigin?: string;
|
|
33
|
+
};
|
|
34
|
+
export type StoragePartitionDescriptor = StorageBrowsingContextPartitionDescriptor | StorageStorageKeyPartitionDescriptor;
|
|
35
|
+
export interface StorageGetCookiesParameters {
|
|
36
|
+
filter?: StorageCookieFilter;
|
|
37
|
+
partition?: StoragePartitionDescriptor;
|
|
38
|
+
}
|
|
39
|
+
export interface StorageGetCookiesResult {
|
|
40
|
+
cookies: NetworkCookie[];
|
|
41
|
+
partitionKey: StoragePartitionKey;
|
|
42
|
+
}
|
|
43
|
+
export interface StorageSetCookie {
|
|
44
|
+
method: "storage.setCookie";
|
|
45
|
+
params: StorageSetCookieParameters;
|
|
46
|
+
}
|
|
47
|
+
export type StoragePartialCookie = Extensible & {
|
|
48
|
+
name: string;
|
|
49
|
+
value: NetworkBytesValue;
|
|
50
|
+
domain: string;
|
|
51
|
+
path?: string;
|
|
52
|
+
httpOnly?: boolean;
|
|
53
|
+
secure?: boolean;
|
|
54
|
+
sameSite?: NetworkSameSite;
|
|
55
|
+
expiry?: JsUint;
|
|
56
|
+
};
|
|
57
|
+
export interface StorageSetCookieParameters {
|
|
58
|
+
cookie: StoragePartialCookie;
|
|
59
|
+
partition?: StoragePartitionDescriptor;
|
|
60
|
+
}
|
|
61
|
+
export interface StorageSetCookieResult {
|
|
62
|
+
partitionKey: StoragePartitionKey;
|
|
63
|
+
}
|
|
64
|
+
export interface StorageDeleteCookies {
|
|
65
|
+
method: "storage.deleteCookies";
|
|
66
|
+
params: StorageDeleteCookiesParameters;
|
|
67
|
+
}
|
|
68
|
+
export interface StorageDeleteCookiesParameters {
|
|
69
|
+
filter?: StorageCookieFilter;
|
|
70
|
+
partition?: StoragePartitionDescriptor;
|
|
71
|
+
}
|
|
72
|
+
export interface StorageDeleteCookiesResult {
|
|
73
|
+
partitionKey: StoragePartitionKey;
|
|
74
|
+
}
|
|
75
|
+
/** @deprecated Use {@link Storage.create} instead — will be removed in a future major version. */
|
|
76
|
+
export declare function getStorageInstance(driver: unknown): Promise<Storage>;
|
|
77
|
+
export declare class Storage {
|
|
78
|
+
private readonly bidi;
|
|
79
|
+
private constructor();
|
|
80
|
+
static create(driver: unknown): Promise<Storage>;
|
|
81
|
+
deleteCookies(params: StorageDeleteCookiesParameters): Promise<StorageDeleteCookiesResult>;
|
|
82
|
+
getCookies(params: StorageGetCookiesParameters): Promise<StorageGetCookiesResult>;
|
|
83
|
+
setCookie(params: StorageSetCookieParameters): Promise<StorageSetCookieResult>;
|
|
84
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
3
|
+
// or more contributor license agreements. See the NOTICE file
|
|
4
|
+
// distributed with this work for additional information
|
|
5
|
+
// regarding copyright ownership. The SFC licenses this file
|
|
6
|
+
// to you under the Apache License, Version 2.0 (the
|
|
7
|
+
// "License"); you may not use this file except in compliance
|
|
8
|
+
// with the License. You may obtain a copy of the License at
|
|
9
|
+
//
|
|
10
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
//
|
|
12
|
+
// Unless required by applicable law or agreed to in writing,
|
|
13
|
+
// software distributed under the License is distributed on an
|
|
14
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
// KIND, either express or implied. See the License for the
|
|
16
|
+
// specific language governing permissions and limitations
|
|
17
|
+
// under the License.
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.Storage = exports.getStorageInstance = void 0;
|
|
20
|
+
// --- Additional Types ---
|
|
21
|
+
/** @deprecated Use {@link Storage.create} instead — will be removed in a future major version. */
|
|
22
|
+
async function getStorageInstance(driver) {
|
|
23
|
+
return Storage.create(driver);
|
|
24
|
+
}
|
|
25
|
+
exports.getStorageInstance = getStorageInstance;
|
|
26
|
+
// --- Implementation ---
|
|
27
|
+
class Storage {
|
|
28
|
+
constructor(bidi) {
|
|
29
|
+
this.bidi = bidi;
|
|
30
|
+
}
|
|
31
|
+
static async create(driver) {
|
|
32
|
+
const caps = await driver.getCapabilities();
|
|
33
|
+
if (!caps.get('webSocketUrl')) {
|
|
34
|
+
throw new Error('WebDriver instance must support BiDi protocol');
|
|
35
|
+
}
|
|
36
|
+
const bidi = await driver.getBidi();
|
|
37
|
+
return new Storage(bidi);
|
|
38
|
+
}
|
|
39
|
+
async deleteCookies(params) {
|
|
40
|
+
const response = await this.bidi.send({
|
|
41
|
+
method: 'storage.deleteCookies',
|
|
42
|
+
params: params,
|
|
43
|
+
});
|
|
44
|
+
if (response['type'] === 'error') {
|
|
45
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
46
|
+
}
|
|
47
|
+
return response.result;
|
|
48
|
+
}
|
|
49
|
+
async getCookies(params) {
|
|
50
|
+
const response = await this.bidi.send({
|
|
51
|
+
method: 'storage.getCookies',
|
|
52
|
+
params: params,
|
|
53
|
+
});
|
|
54
|
+
if (response['type'] === 'error') {
|
|
55
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
56
|
+
}
|
|
57
|
+
return response.result;
|
|
58
|
+
}
|
|
59
|
+
async setCookie(params) {
|
|
60
|
+
const response = await this.bidi.send({
|
|
61
|
+
method: 'storage.setCookie',
|
|
62
|
+
params: params,
|
|
63
|
+
});
|
|
64
|
+
if (response['type'] === 'error') {
|
|
65
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
66
|
+
}
|
|
67
|
+
return response.result;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.Storage = Storage;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type UserAgentClientHintsCommand = UserAgentClientHintsSetClientHintsOverrideCommand;
|
|
2
|
+
export interface UserAgentClientHintsSetClientHintsOverrideCommand {
|
|
3
|
+
method: "userAgentClientHints.setClientHintsOverride";
|
|
4
|
+
params: {
|
|
5
|
+
clientHints: UserAgentClientHintsClientHintsMetadata | null;
|
|
6
|
+
contexts?: string[];
|
|
7
|
+
userContexts?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface UserAgentClientHintsClientHintsMetadata {
|
|
11
|
+
brands?: UserAgentClientHintsBrandVersion[];
|
|
12
|
+
fullVersionList?: UserAgentClientHintsBrandVersion[];
|
|
13
|
+
platform?: string;
|
|
14
|
+
platformVersion?: string;
|
|
15
|
+
architecture?: string;
|
|
16
|
+
model?: string;
|
|
17
|
+
mobile?: boolean;
|
|
18
|
+
bitness?: string;
|
|
19
|
+
wow64?: boolean;
|
|
20
|
+
formFactors?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface UserAgentClientHintsBrandVersion {
|
|
23
|
+
brand: string;
|
|
24
|
+
version: string;
|
|
25
|
+
}
|
|
26
|
+
export interface UserAgentClientHintsSetClientHintsOverrideResult {
|
|
27
|
+
}
|
|
28
|
+
export declare class UserAgentClientHints {
|
|
29
|
+
private readonly bidi;
|
|
30
|
+
private constructor();
|
|
31
|
+
static create(driver: unknown): Promise<UserAgentClientHints>;
|
|
32
|
+
setClientHintsOverride(): Promise<void>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
3
|
+
// or more contributor license agreements. See the NOTICE file
|
|
4
|
+
// distributed with this work for additional information
|
|
5
|
+
// regarding copyright ownership. The SFC licenses this file
|
|
6
|
+
// to you under the Apache License, Version 2.0 (the
|
|
7
|
+
// "License"); you may not use this file except in compliance
|
|
8
|
+
// with the License. You may obtain a copy of the License at
|
|
9
|
+
//
|
|
10
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
//
|
|
12
|
+
// Unless required by applicable law or agreed to in writing,
|
|
13
|
+
// software distributed under the License is distributed on an
|
|
14
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
// KIND, either express or implied. See the License for the
|
|
16
|
+
// specific language governing permissions and limitations
|
|
17
|
+
// under the License.
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.UserAgentClientHints = void 0;
|
|
20
|
+
// --- Implementation ---
|
|
21
|
+
class UserAgentClientHints {
|
|
22
|
+
constructor(bidi) {
|
|
23
|
+
this.bidi = bidi;
|
|
24
|
+
}
|
|
25
|
+
static async create(driver) {
|
|
26
|
+
const caps = await driver.getCapabilities();
|
|
27
|
+
if (!caps.get('webSocketUrl')) {
|
|
28
|
+
throw new Error('WebDriver instance must support BiDi protocol');
|
|
29
|
+
}
|
|
30
|
+
const bidi = await driver.getBidi();
|
|
31
|
+
return new UserAgentClientHints(bidi);
|
|
32
|
+
}
|
|
33
|
+
async setClientHintsOverride() {
|
|
34
|
+
const response = await this.bidi.send({
|
|
35
|
+
method: 'userAgentClientHints.setClientHintsOverride',
|
|
36
|
+
params: {},
|
|
37
|
+
});
|
|
38
|
+
if (response['type'] === 'error') {
|
|
39
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.UserAgentClientHints = UserAgentClientHints;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EmptyResult } from './common.js';
|
|
2
|
+
export type WebExtensionCommand = WebExtensionInstall | WebExtensionUninstall;
|
|
3
|
+
export type WebExtensionResult = WebExtensionInstallResult | WebExtensionUninstallResult;
|
|
4
|
+
export type WebExtensionExtension = string;
|
|
5
|
+
export interface WebExtensionInstall {
|
|
6
|
+
method: "webExtension.install";
|
|
7
|
+
params: WebExtensionInstallParameters;
|
|
8
|
+
}
|
|
9
|
+
export interface WebExtensionInstallParameters {
|
|
10
|
+
extensionData: WebExtensionExtensionData;
|
|
11
|
+
}
|
|
12
|
+
export type WebExtensionExtensionData = WebExtensionExtensionArchivePath | WebExtensionExtensionBase64Encoded | WebExtensionExtensionPath;
|
|
13
|
+
export interface WebExtensionExtensionPath {
|
|
14
|
+
type: "path";
|
|
15
|
+
path: string;
|
|
16
|
+
}
|
|
17
|
+
export interface WebExtensionExtensionArchivePath {
|
|
18
|
+
type: "archivePath";
|
|
19
|
+
path: string;
|
|
20
|
+
}
|
|
21
|
+
export interface WebExtensionExtensionBase64Encoded {
|
|
22
|
+
type: "base64";
|
|
23
|
+
value: string;
|
|
24
|
+
}
|
|
25
|
+
export interface WebExtensionInstallResult {
|
|
26
|
+
extension: WebExtensionExtension;
|
|
27
|
+
}
|
|
28
|
+
export interface WebExtensionUninstall {
|
|
29
|
+
method: "webExtension.uninstall";
|
|
30
|
+
params: WebExtensionUninstallParameters;
|
|
31
|
+
}
|
|
32
|
+
export interface WebExtensionUninstallParameters {
|
|
33
|
+
extension: WebExtensionExtension;
|
|
34
|
+
}
|
|
35
|
+
export type WebExtensionUninstallResult = EmptyResult;
|
|
36
|
+
export declare class WebExtension {
|
|
37
|
+
private readonly bidi;
|
|
38
|
+
private constructor();
|
|
39
|
+
static create(driver: unknown): Promise<WebExtension>;
|
|
40
|
+
install(params: WebExtensionInstallParameters): Promise<WebExtensionInstallResult>;
|
|
41
|
+
uninstall(params: WebExtensionUninstallParameters): Promise<void>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
3
|
+
// or more contributor license agreements. See the NOTICE file
|
|
4
|
+
// distributed with this work for additional information
|
|
5
|
+
// regarding copyright ownership. The SFC licenses this file
|
|
6
|
+
// to you under the Apache License, Version 2.0 (the
|
|
7
|
+
// "License"); you may not use this file except in compliance
|
|
8
|
+
// with the License. You may obtain a copy of the License at
|
|
9
|
+
//
|
|
10
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
//
|
|
12
|
+
// Unless required by applicable law or agreed to in writing,
|
|
13
|
+
// software distributed under the License is distributed on an
|
|
14
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
// KIND, either express or implied. See the License for the
|
|
16
|
+
// specific language governing permissions and limitations
|
|
17
|
+
// under the License.
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.WebExtension = void 0;
|
|
20
|
+
// --- Implementation ---
|
|
21
|
+
class WebExtension {
|
|
22
|
+
constructor(bidi) {
|
|
23
|
+
this.bidi = bidi;
|
|
24
|
+
}
|
|
25
|
+
static async create(driver) {
|
|
26
|
+
const caps = await driver.getCapabilities();
|
|
27
|
+
if (!caps.get('webSocketUrl')) {
|
|
28
|
+
throw new Error('WebDriver instance must support BiDi protocol');
|
|
29
|
+
}
|
|
30
|
+
const bidi = await driver.getBidi();
|
|
31
|
+
return new WebExtension(bidi);
|
|
32
|
+
}
|
|
33
|
+
async install(params) {
|
|
34
|
+
const response = await this.bidi.send({
|
|
35
|
+
method: 'webExtension.install',
|
|
36
|
+
params: params,
|
|
37
|
+
});
|
|
38
|
+
if (response['type'] === 'error') {
|
|
39
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
40
|
+
}
|
|
41
|
+
return response.result;
|
|
42
|
+
}
|
|
43
|
+
async uninstall(params) {
|
|
44
|
+
const response = await this.bidi.send({
|
|
45
|
+
method: 'webExtension.uninstall',
|
|
46
|
+
params: params,
|
|
47
|
+
});
|
|
48
|
+
if (response['type'] === 'error') {
|
|
49
|
+
throw new Error(`${response['error']}: ${response['message']}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.WebExtension = WebExtension;
|
package/bidi/index.js
CHANGED
|
@@ -33,10 +33,120 @@ class Index extends EventEmitter {
|
|
|
33
33
|
constructor(_webSocketUrl) {
|
|
34
34
|
super()
|
|
35
35
|
this.connected = false
|
|
36
|
+
this._closed = false
|
|
37
|
+
this._pending = new Map()
|
|
38
|
+
this._connectWaiters = new Set()
|
|
36
39
|
this._ws = new WebSocket(_webSocketUrl)
|
|
37
40
|
this._ws.on('open', () => {
|
|
41
|
+
// The handshake can complete after close()/_failPending() has already
|
|
42
|
+
// marked the connection closed. Don't flip connected back to true and
|
|
43
|
+
// proactively close the now-orphan socket so it does not leak.
|
|
44
|
+
if (this._closed) {
|
|
45
|
+
try {
|
|
46
|
+
this._ws.close()
|
|
47
|
+
} catch {
|
|
48
|
+
/* socket already closing */
|
|
49
|
+
}
|
|
50
|
+
return
|
|
51
|
+
}
|
|
38
52
|
this.connected = true
|
|
53
|
+
for (const { resolve } of this._connectWaiters) {
|
|
54
|
+
resolve()
|
|
55
|
+
}
|
|
56
|
+
this._connectWaiters.clear()
|
|
57
|
+
})
|
|
58
|
+
// Single shared response dispatcher. Avoids attaching a new 'message'
|
|
59
|
+
// listener for every in-flight send(), which previously caused
|
|
60
|
+
// MaxListenersExceededWarning under concurrent BiDi traffic
|
|
61
|
+
// (e.g. network interception during a page navigation).
|
|
62
|
+
this._ws.on('message', (data) => {
|
|
63
|
+
// Frames can arrive after close() has cleared _pending; ignore them
|
|
64
|
+
// rather than re-emitting parse errors or dispatching to nothing.
|
|
65
|
+
if (this._closed) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
let payload
|
|
69
|
+
try {
|
|
70
|
+
payload = JSON.parse(data.toString())
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// Surface protocol parse failures rather than silently dropping —
|
|
73
|
+
// otherwise callers see misleading send() timeouts.
|
|
74
|
+
const wrapped = new Error(`Failed to parse BiDi message: ${err.message}`)
|
|
75
|
+
if (this.listenerCount('error') > 0) {
|
|
76
|
+
this.emit('error', wrapped)
|
|
77
|
+
} else {
|
|
78
|
+
process.emitWarning(wrapped.message, 'BiDiProtocolWarning')
|
|
79
|
+
}
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
// Messages without a numeric id are BiDi events, not command responses.
|
|
83
|
+
// Re-emit them on this EventEmitter by method name (e.g.
|
|
84
|
+
// 'browsingContext.contextCreated') so that generated domain classes can
|
|
85
|
+
// subscribe via bidi.on(methodName, callback) instead of each attaching
|
|
86
|
+
// a new raw ws.on('message', ...) listener. The existing hand-written
|
|
87
|
+
// modules (logInspector, network, etc.) continue to use their own
|
|
88
|
+
// ws.on('message', ...) listeners unchanged — this emission is purely
|
|
89
|
+
// additive and does not affect those code paths.
|
|
90
|
+
if (payload == null || typeof payload.id !== 'number') {
|
|
91
|
+
if (payload != null && typeof payload.method === 'string') {
|
|
92
|
+
// 'error' is a reserved EventEmitter event — emitting it without a
|
|
93
|
+
// listener throws and crashes the process. Route any peer-supplied
|
|
94
|
+
// method named 'error' through the same guarded path used for JSON
|
|
95
|
+
// parse failures rather than forwarding it directly.
|
|
96
|
+
if (payload.method === 'error') {
|
|
97
|
+
const err = new Error(`BiDi protocol error event: ${JSON.stringify(payload.params)}`)
|
|
98
|
+
if (this.listenerCount('error') > 0) {
|
|
99
|
+
this.emit('error', err)
|
|
100
|
+
} else {
|
|
101
|
+
process.emitWarning(err.message, 'BiDiProtocolWarning')
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
this.emit(payload.method, payload.params)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
const entry = this._pending.get(payload.id)
|
|
110
|
+
if (entry === undefined) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
clearTimeout(entry.timeoutId)
|
|
114
|
+
this._pending.delete(payload.id)
|
|
115
|
+
entry.resolve(payload)
|
|
116
|
+
})
|
|
117
|
+
// Fail any in-flight send() calls promptly when the peer disconnects
|
|
118
|
+
// or the socket errors, instead of waiting for RESPONSE_TIMEOUT.
|
|
119
|
+
this._ws.on('close', () => {
|
|
120
|
+
this._failPending(new Error('BiDi connection closed unexpectedly'))
|
|
39
121
|
})
|
|
122
|
+
this._ws.on('error', (err) => {
|
|
123
|
+
this._failPending(new Error(`BiDi connection error: ${err.message}`))
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Reject any in-flight sends and mark the connection failed. Idempotent so
|
|
129
|
+
* that close() and the underlying 'close'/'error' events do not double-reject.
|
|
130
|
+
* @param {Error} error
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
_failPending(error) {
|
|
134
|
+
if (this._closed) {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
this._closed = true
|
|
138
|
+
this.connected = false
|
|
139
|
+
for (const { reject, timeoutId } of this._pending.values()) {
|
|
140
|
+
clearTimeout(timeoutId)
|
|
141
|
+
reject(error)
|
|
142
|
+
}
|
|
143
|
+
this._pending.clear()
|
|
144
|
+
// Reject any callers parked in waitForConnection() so close() (or an
|
|
145
|
+
// unexpected disconnect) cannot leave them hanging forever.
|
|
146
|
+
for (const { reject } of this._connectWaiters) {
|
|
147
|
+
reject(error)
|
|
148
|
+
}
|
|
149
|
+
this._connectWaiters.clear()
|
|
40
150
|
}
|
|
41
151
|
|
|
42
152
|
/**
|
|
@@ -69,14 +179,19 @@ class Index extends EventEmitter {
|
|
|
69
179
|
* @returns {Promise<unknown>}
|
|
70
180
|
*/
|
|
71
181
|
async waitForConnection() {
|
|
72
|
-
return new Promise((resolve) => {
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
if (this._closed) {
|
|
184
|
+
reject(new Error('BiDi connection is closed'))
|
|
185
|
+
return
|
|
186
|
+
}
|
|
73
187
|
if (this.connected) {
|
|
74
188
|
resolve()
|
|
75
|
-
|
|
76
|
-
this._ws.once('open', () => {
|
|
77
|
-
resolve()
|
|
78
|
-
})
|
|
189
|
+
return
|
|
79
190
|
}
|
|
191
|
+
// Park the waiter in a Set so the constructor's 'open' handler can
|
|
192
|
+
// resolve it and _failPending() can reject it. Avoids attaching socket
|
|
193
|
+
// listeners that close()'s removeAllListeners('close') would strip.
|
|
194
|
+
this._connectWaiters.add({ resolve, reject })
|
|
80
195
|
})
|
|
81
196
|
}
|
|
82
197
|
|
|
@@ -86,9 +201,18 @@ class Index extends EventEmitter {
|
|
|
86
201
|
* @returns {Promise<unknown>}
|
|
87
202
|
*/
|
|
88
203
|
async send(params) {
|
|
204
|
+
if (this._closed) {
|
|
205
|
+
throw new Error('BiDi connection is closed')
|
|
206
|
+
}
|
|
89
207
|
if (!this.connected) {
|
|
90
208
|
await this.waitForConnection()
|
|
91
209
|
}
|
|
210
|
+
// Defense in depth: even after waitForConnection() resolves, the socket
|
|
211
|
+
// may have transitioned to CLOSING/CLOSED (e.g. caller closed the raw
|
|
212
|
+
// socket). Refuse rather than throwing from inside ws.send().
|
|
213
|
+
if (this._ws.readyState !== WebSocket.OPEN) {
|
|
214
|
+
throw new Error('BiDi connection is not open')
|
|
215
|
+
}
|
|
92
216
|
|
|
93
217
|
const id = ++this.id
|
|
94
218
|
|
|
@@ -96,25 +220,11 @@ class Index extends EventEmitter {
|
|
|
96
220
|
|
|
97
221
|
return new Promise((resolve, reject) => {
|
|
98
222
|
const timeoutId = setTimeout(() => {
|
|
223
|
+
this._pending.delete(id)
|
|
99
224
|
reject(new Error(`Request with id ${id} timed out`))
|
|
100
|
-
handler.off('message', listener)
|
|
101
225
|
}, RESPONSE_TIMEOUT)
|
|
102
226
|
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const payload = JSON.parse(data.toString())
|
|
106
|
-
if (payload.id === id) {
|
|
107
|
-
clearTimeout(timeoutId)
|
|
108
|
-
handler.off('message', listener)
|
|
109
|
-
resolve(payload)
|
|
110
|
-
}
|
|
111
|
-
} catch (err) {
|
|
112
|
-
// eslint-disable-next-line no-undef
|
|
113
|
-
log.error(`Failed parse message: ${err.message}`)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const handler = this._ws.on('message', listener)
|
|
227
|
+
this._pending.set(id, { resolve, reject, timeoutId })
|
|
118
228
|
})
|
|
119
229
|
}
|
|
120
230
|
|
|
@@ -206,6 +316,8 @@ class Index extends EventEmitter {
|
|
|
206
316
|
* @returns {Promise<unknown>}
|
|
207
317
|
*/
|
|
208
318
|
close() {
|
|
319
|
+
this._failPending(new Error('BiDi connection closed before response was received'))
|
|
320
|
+
|
|
209
321
|
const closeWebSocket = (callback) => {
|
|
210
322
|
// don't close if it's already closed
|
|
211
323
|
if (this._ws.readyState === 3) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|