@mearl/browser-core 3.0.0-beta.5 → 3.0.0-beta.8
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/README.md +1 -1
- package/dist/agentChat.d.ts +2 -0
- package/dist/agentSkills.d.ts +1 -1
- package/dist/app-profile-runtime/types.d.ts +4 -8
- package/dist/appProfileDefinition.d.ts +4 -7
- package/dist/appProfileDefinition.js +11 -49
- package/dist/axSnapshot.js +1 -1
- package/dist/boundedRequestBuffer.d.ts +4 -4
- package/dist/browserActionProtocol.d.ts +36 -32
- package/dist/browserActionProtocol.js +3 -6
- package/dist/browserCapabilities.d.ts +2 -0
- package/dist/browserCapabilities.js +19 -5
- package/dist/browserDiscovery.d.ts +3 -0
- package/dist/browserDiscovery.js +20 -0
- package/dist/browserEnhancement.d.ts +161 -0
- package/dist/browserEnhancement.js +80 -0
- package/dist/deviceEnvironmentObservation.d.ts +48 -0
- package/dist/deviceEnvironmentObservation.js +77 -0
- package/dist/domClicking.d.ts +14 -0
- package/dist/domClicking.js +46 -10
- package/dist/extensionServiceProtocol.d.ts +6 -0
- package/dist/extensionServiceProtocol.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/pageAct.d.ts +2 -0
- package/dist/pageAct.js +42 -10
- package/dist/pageDialog.d.ts +66 -0
- package/dist/pageDialog.js +175 -0
- package/dist/pageObserver.js +16 -5
- package/dist/pagePressing.d.ts +1 -1
- package/dist/pluginPageHost.js +2 -2
- package/dist/providerAppResources.d.ts +39 -0
- package/dist/providerAppResources.js +2 -0
- package/dist/proxyPermissions.js +2 -3
- package/dist/requestMetadata.d.ts +9 -0
- package/dist/requestMetadata.js +8 -0
- package/dist/requestSerializer.d.ts +2 -2
- package/dist/requestStreamCapture.d.ts +5 -3
- package/dist/requestStreamCapture.js +2 -0
- package/dist/types.d.ts +4 -2
- package/package.json +2 -2
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { BrowserActionMap } from './browserActionProtocol.js';
|
|
2
|
+
import { type DeviceEnvironmentObservation } from './deviceEnvironmentObservation.js';
|
|
3
|
+
export declare const BROWSER_ENHANCEMENT_PROTOCOL_VERSION = 1;
|
|
4
|
+
export declare const BROWSER_ENHANCEMENT_NAVIGATION_ORIGIN_VERSION = 1;
|
|
5
|
+
export declare const BROWSER_ENHANCEMENT_PREPARATION_VERSION = 1;
|
|
6
|
+
export declare const BROWSER_ENHANCEMENT_HOST_ENVIRONMENT_VERSION = 1;
|
|
7
|
+
export declare const BROWSER_ENHANCEMENT_PREPARATION_PATH = "src/pages/browser-transport/index.html";
|
|
8
|
+
/** Only the installed Extension's inert top-level page can prepare a new document. */
|
|
9
|
+
export declare function isBrowserEnhancementPreparationUrl(value: unknown): value is string;
|
|
10
|
+
/** Narrow internal host contract. Ordinary input, navigation and browser lifecycle are excluded. */
|
|
11
|
+
export type BrowserEnhancementAction = 'set_device_emulation' | 'set_app_profile' | 'set_timezone';
|
|
12
|
+
export declare function isBrowserEnhancementAction(value: unknown): value is BrowserEnhancementAction;
|
|
13
|
+
export type BrowserEnhancementInvocation = {
|
|
14
|
+
[A in BrowserEnhancementAction]: {
|
|
15
|
+
action: A;
|
|
16
|
+
data: BrowserActionMap[A]['request'];
|
|
17
|
+
};
|
|
18
|
+
}[BrowserEnhancementAction];
|
|
19
|
+
export declare function requiredBrowserEnhancementActions(invocation: BrowserEnhancementInvocation): BrowserEnhancementAction[];
|
|
20
|
+
export interface BrowserEnhancementBinding {
|
|
21
|
+
bindingId: string;
|
|
22
|
+
generation: number;
|
|
23
|
+
tabId: number;
|
|
24
|
+
mainFrameId: string;
|
|
25
|
+
targetId?: string;
|
|
26
|
+
actions: BrowserEnhancementAction[];
|
|
27
|
+
}
|
|
28
|
+
export interface BrowserEnhancementCapabilities {
|
|
29
|
+
protocolVersion: typeof BROWSER_ENHANCEMENT_PROTOCOL_VERSION;
|
|
30
|
+
actions: BrowserEnhancementAction[];
|
|
31
|
+
navigationOriginVersion?: typeof BROWSER_ENHANCEMENT_NAVIGATION_ORIGIN_VERSION;
|
|
32
|
+
preparationVersion?: typeof BROWSER_ENHANCEMENT_PREPARATION_VERSION;
|
|
33
|
+
hostEnvironmentVersion?: typeof BROWSER_ENHANCEMENT_HOST_ENVIRONMENT_VERSION;
|
|
34
|
+
}
|
|
35
|
+
export interface BrowserEnhancementPreparation {
|
|
36
|
+
tabId: number;
|
|
37
|
+
targetId: string;
|
|
38
|
+
url: string;
|
|
39
|
+
capabilities: BrowserEnhancementCapabilities;
|
|
40
|
+
}
|
|
41
|
+
export interface ProviderEnhancementPreparation extends BrowserEnhancementPreparation {
|
|
42
|
+
nativeBrowserId: string;
|
|
43
|
+
providerTabId: number;
|
|
44
|
+
}
|
|
45
|
+
/** Captured by the accepted external owner immediately after creating this target. */
|
|
46
|
+
export interface ProviderInitialEnvironment {
|
|
47
|
+
device: DeviceEnvironmentObservation;
|
|
48
|
+
/** Keep the physical root's captured platform while logical App/device identities remain unchanged. */
|
|
49
|
+
preserveHostPlatform?: true;
|
|
50
|
+
/** The external host owns native pointer/touch delivery; Chrome must not convert its mouse events. */
|
|
51
|
+
preserveHostInput?: true;
|
|
52
|
+
}
|
|
53
|
+
export declare function isProviderInitialEnvironment(value: unknown): value is ProviderInitialEnvironment;
|
|
54
|
+
export declare function isEnhancementNavigationOrigin(value: unknown): value is string;
|
|
55
|
+
/** owner is derived by the accepted Provider control host, never from an ordinary action payload. */
|
|
56
|
+
export type BrowserEnhancementRequest = ({
|
|
57
|
+
operation: 'probe';
|
|
58
|
+
tabId: number;
|
|
59
|
+
} | {
|
|
60
|
+
operation: 'inspect';
|
|
61
|
+
owner: string;
|
|
62
|
+
tabId: number;
|
|
63
|
+
targetId: string;
|
|
64
|
+
actions: BrowserEnhancementAction[];
|
|
65
|
+
navigationOrigin: string;
|
|
66
|
+
} | {
|
|
67
|
+
operation: 'bind';
|
|
68
|
+
owner: string;
|
|
69
|
+
tabId: number;
|
|
70
|
+
mainFrameId: string;
|
|
71
|
+
targetId?: string;
|
|
72
|
+
actions: BrowserEnhancementAction[];
|
|
73
|
+
navigationOrigin?: string;
|
|
74
|
+
initialEnvironment?: ProviderInitialEnvironment;
|
|
75
|
+
} | ({
|
|
76
|
+
operation: 'invoke';
|
|
77
|
+
owner: string;
|
|
78
|
+
bindingId: string;
|
|
79
|
+
generation: number;
|
|
80
|
+
} & BrowserEnhancementInvocation) | {
|
|
81
|
+
operation: 'release';
|
|
82
|
+
owner: string;
|
|
83
|
+
bindingId: string;
|
|
84
|
+
generation: number;
|
|
85
|
+
}) & {
|
|
86
|
+
deadline?: number;
|
|
87
|
+
};
|
|
88
|
+
export type BrowserEnhancementResponse = {
|
|
89
|
+
capabilities: BrowserEnhancementCapabilities;
|
|
90
|
+
tabId: number;
|
|
91
|
+
mainFrameId: string;
|
|
92
|
+
targetId?: string;
|
|
93
|
+
} | {
|
|
94
|
+
preparation: BrowserEnhancementPreparation;
|
|
95
|
+
} | {
|
|
96
|
+
binding: BrowserEnhancementBinding;
|
|
97
|
+
} | {
|
|
98
|
+
result: unknown;
|
|
99
|
+
} | {
|
|
100
|
+
released: boolean;
|
|
101
|
+
conflicts?: string[];
|
|
102
|
+
};
|
|
103
|
+
export interface ProviderEnhancementBinding extends BrowserEnhancementBinding {
|
|
104
|
+
nativeBrowserId: string;
|
|
105
|
+
providerTabId: number;
|
|
106
|
+
}
|
|
107
|
+
/** Read-only control/identity preflight supplied by the external page owner. */
|
|
108
|
+
export type ProviderEnhancementTarget = {
|
|
109
|
+
tabId: number;
|
|
110
|
+
ready: false;
|
|
111
|
+
} | {
|
|
112
|
+
tabId: number;
|
|
113
|
+
ready: true;
|
|
114
|
+
mainFrameId?: string;
|
|
115
|
+
targetId?: string;
|
|
116
|
+
url: string;
|
|
117
|
+
initialEnvironment?: ProviderInitialEnvironment;
|
|
118
|
+
};
|
|
119
|
+
export declare function isProviderEnhancementTarget(value: unknown): value is ProviderEnhancementTarget;
|
|
120
|
+
/** Match every identity asserted by the external owner; URL alone is never proof. */
|
|
121
|
+
export declare function matchesProviderEnhancementTarget(target: Extract<ProviderEnhancementTarget, {
|
|
122
|
+
ready: true;
|
|
123
|
+
}>, physical: {
|
|
124
|
+
mainFrameId: string;
|
|
125
|
+
targetId?: string;
|
|
126
|
+
}): boolean;
|
|
127
|
+
export declare function requireReadyProviderEnhancementTarget(value: unknown, tabId: number): Extract<ProviderEnhancementTarget, {
|
|
128
|
+
ready: true;
|
|
129
|
+
}>;
|
|
130
|
+
/** Provider-side request. The control host supplies ownership and physical tab mappings. */
|
|
131
|
+
export type ProviderEnhancementRequest = {
|
|
132
|
+
operation: 'inspect';
|
|
133
|
+
providerTabId: number;
|
|
134
|
+
nativeBrowserId?: string;
|
|
135
|
+
actions: BrowserEnhancementAction[];
|
|
136
|
+
navigationOrigin: string;
|
|
137
|
+
} | {
|
|
138
|
+
operation: 'bind';
|
|
139
|
+
providerTabId: number;
|
|
140
|
+
nativeBrowserId?: string;
|
|
141
|
+
actions: BrowserEnhancementAction[];
|
|
142
|
+
navigationOrigin?: string;
|
|
143
|
+
} | ({
|
|
144
|
+
operation: 'invoke';
|
|
145
|
+
bindingId: string;
|
|
146
|
+
generation: number;
|
|
147
|
+
} & BrowserEnhancementInvocation) | {
|
|
148
|
+
operation: 'release';
|
|
149
|
+
bindingId: string;
|
|
150
|
+
generation: number;
|
|
151
|
+
};
|
|
152
|
+
export type ProviderEnhancementResponse = {
|
|
153
|
+
preparation: ProviderEnhancementPreparation;
|
|
154
|
+
} | {
|
|
155
|
+
binding: ProviderEnhancementBinding;
|
|
156
|
+
} | {
|
|
157
|
+
result: unknown;
|
|
158
|
+
} | {
|
|
159
|
+
released: boolean;
|
|
160
|
+
conflicts?: string[];
|
|
161
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { isDeviceEnvironmentObservation } from './deviceEnvironmentObservation.js';
|
|
2
|
+
export const BROWSER_ENHANCEMENT_PROTOCOL_VERSION = 1;
|
|
3
|
+
export const BROWSER_ENHANCEMENT_NAVIGATION_ORIGIN_VERSION = 1;
|
|
4
|
+
export const BROWSER_ENHANCEMENT_PREPARATION_VERSION = 1;
|
|
5
|
+
export const BROWSER_ENHANCEMENT_HOST_ENVIRONMENT_VERSION = 1;
|
|
6
|
+
export const BROWSER_ENHANCEMENT_PREPARATION_PATH = 'src/pages/browser-transport/index.html';
|
|
7
|
+
/** Only the installed Extension's inert top-level page can prepare a new document. */
|
|
8
|
+
export function isBrowserEnhancementPreparationUrl(value) {
|
|
9
|
+
if (typeof value !== 'string')
|
|
10
|
+
return false;
|
|
11
|
+
try {
|
|
12
|
+
const url = new URL(value);
|
|
13
|
+
return /^[a-p]{32}$/.test(url.hostname) && value === `chrome-extension://${url.hostname}/${BROWSER_ENHANCEMENT_PREPARATION_PATH}`;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function isBrowserEnhancementAction(value) {
|
|
20
|
+
return value === 'set_device_emulation' || value === 'set_app_profile' || value === 'set_timezone';
|
|
21
|
+
}
|
|
22
|
+
export function requiredBrowserEnhancementActions(invocation) {
|
|
23
|
+
return invocation.action === 'set_app_profile' && invocation.data?.emulation !== undefined
|
|
24
|
+
? ['set_app_profile', 'set_device_emulation'] : [invocation.action];
|
|
25
|
+
}
|
|
26
|
+
export function isProviderInitialEnvironment(value) {
|
|
27
|
+
if (!value || typeof value !== 'object')
|
|
28
|
+
return false;
|
|
29
|
+
const device = value.device;
|
|
30
|
+
const only = (record, keys) => Object.keys(record).every(key => keys.includes(key));
|
|
31
|
+
try {
|
|
32
|
+
return isDeviceEnvironmentObservation(device) && device.userAgent.length <= 4096 &&
|
|
33
|
+
device.platform.length <= 256 && JSON.stringify(value).length <= 32_768 && only(value, ['device', 'preserveHostPlatform', 'preserveHostInput']) &&
|
|
34
|
+
(value.preserveHostPlatform === undefined || value.preserveHostPlatform === true) &&
|
|
35
|
+
(value.preserveHostInput === undefined || value.preserveHostInput === true) &&
|
|
36
|
+
only(device, ['userAgent', 'platform', 'userAgentMetadata', 'screen', 'touchPoints', 'safeAreaInsets']) &&
|
|
37
|
+
only(device.screen, ['width', 'height', 'deviceScaleFactor', 'orientation', 'angle']) && only(device.safeAreaInsets, ['top', 'right', 'bottom', 'left']) &&
|
|
38
|
+
(device.userAgentMetadata === null || (only(device.userAgentMetadata, ['brands', 'fullVersionList', 'fullVersion', 'platform', 'platformVersion', 'architecture', 'model', 'mobile', 'bitness', 'wow64', 'formFactors']) &&
|
|
39
|
+
[...device.userAgentMetadata.brands, ...device.userAgentMetadata.fullVersionList].every(brand => only(brand, ['brand', 'version']))));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function isEnhancementNavigationOrigin(value) {
|
|
46
|
+
if (typeof value !== 'string' || value.length > 4096)
|
|
47
|
+
return false;
|
|
48
|
+
try {
|
|
49
|
+
const url = new URL(value);
|
|
50
|
+
return ['http:', 'https:'].includes(url.protocol) && url.origin === value;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function isProviderEnhancementTarget(value) {
|
|
57
|
+
if (!value || typeof value !== 'object')
|
|
58
|
+
return false;
|
|
59
|
+
const target = value;
|
|
60
|
+
return Number.isSafeInteger(target.tabId) && target.tabId >= 0 &&
|
|
61
|
+
(value.ready === false || (target.ready === true &&
|
|
62
|
+
(target.mainFrameId === undefined || validIdentity(target.mainFrameId)) &&
|
|
63
|
+
(target.targetId === undefined || validIdentity(target.targetId)) &&
|
|
64
|
+
(target.initialEnvironment === undefined || isProviderInitialEnvironment(target.initialEnvironment)) &&
|
|
65
|
+
(target.mainFrameId !== undefined || target.targetId !== undefined) && typeof target.url === 'string' && Boolean(target.url)));
|
|
66
|
+
}
|
|
67
|
+
function validIdentity(value) { return typeof value === 'string' && value.length > 0 && value.length <= 256; }
|
|
68
|
+
/** Match every identity asserted by the external owner; URL alone is never proof. */
|
|
69
|
+
export function matchesProviderEnhancementTarget(target, physical) {
|
|
70
|
+
return Boolean(target.mainFrameId || target.targetId) &&
|
|
71
|
+
(target.mainFrameId === undefined || target.mainFrameId === physical.mainFrameId) &&
|
|
72
|
+
(target.targetId === undefined || target.targetId === physical.targetId);
|
|
73
|
+
}
|
|
74
|
+
export function requireReadyProviderEnhancementTarget(value, tabId) {
|
|
75
|
+
if (!isProviderEnhancementTarget(value) || value.tabId !== tabId)
|
|
76
|
+
throw new Error('BROWSER_ENHANCEMENT_PAGE_IDENTITY_UNAVAILABLE');
|
|
77
|
+
if (!value.ready)
|
|
78
|
+
throw new Error('EXTERNAL_CONTROL_PAUSED: the external page owner has not granted Agent control');
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ResolvedEmulationConfig } from './deviceEmulation.js';
|
|
2
|
+
export interface DeviceUserAgentMetadata {
|
|
3
|
+
brands: Array<{
|
|
4
|
+
brand: string;
|
|
5
|
+
version: string;
|
|
6
|
+
}>;
|
|
7
|
+
fullVersionList: Array<{
|
|
8
|
+
brand: string;
|
|
9
|
+
version: string;
|
|
10
|
+
}>;
|
|
11
|
+
fullVersion?: string;
|
|
12
|
+
platform: string;
|
|
13
|
+
platformVersion: string;
|
|
14
|
+
architecture: string;
|
|
15
|
+
model: string;
|
|
16
|
+
mobile: boolean;
|
|
17
|
+
bitness?: string;
|
|
18
|
+
wow64?: boolean;
|
|
19
|
+
formFactors?: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface DeviceEnvironmentObservation {
|
|
22
|
+
userAgent: string;
|
|
23
|
+
platform: string;
|
|
24
|
+
userAgentMetadata: DeviceUserAgentMetadata | null;
|
|
25
|
+
screen: {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
deviceScaleFactor: number;
|
|
29
|
+
orientation: string;
|
|
30
|
+
angle: number;
|
|
31
|
+
};
|
|
32
|
+
touchPoints: number;
|
|
33
|
+
safeAreaInsets: {
|
|
34
|
+
top: number;
|
|
35
|
+
right: number;
|
|
36
|
+
bottom: number;
|
|
37
|
+
left: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Explicitly disables UA Client Hints for an emulated UA, matching Chrome's omitted-metadata behavior. */
|
|
41
|
+
export declare function emptyDeviceUserAgentMetadata(): DeviceUserAgentMetadata;
|
|
42
|
+
/** Self-contained observation for an isolated page world; no retained DOM or page globals. */
|
|
43
|
+
export declare function observeDeviceEnvironment(): Promise<DeviceEnvironmentObservation>;
|
|
44
|
+
export declare function isDeviceUserAgentMetadata(value: unknown): value is DeviceUserAgentMetadata;
|
|
45
|
+
export declare function isDeviceEnvironmentObservation(value: unknown): value is DeviceEnvironmentObservation;
|
|
46
|
+
/** CDP transports can reorder object keys. Values and array order remain significant. */
|
|
47
|
+
export declare function deviceEnvironmentObservationsEqual(left: DeviceEnvironmentObservation, right: DeviceEnvironmentObservation): boolean;
|
|
48
|
+
export declare function matchesEmulatedDeviceEnvironment(config: ResolvedEmulationConfig, observed: DeviceEnvironmentObservation): boolean;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Explicitly disables UA Client Hints for an emulated UA, matching Chrome's omitted-metadata behavior. */
|
|
2
|
+
export function emptyDeviceUserAgentMetadata() {
|
|
3
|
+
return { brands: [], fullVersionList: [], fullVersion: '', platform: '', platformVersion: '', architecture: '', model: '',
|
|
4
|
+
mobile: false, bitness: '', wow64: false, formFactors: [] };
|
|
5
|
+
}
|
|
6
|
+
/** Self-contained observation for an isolated page world; no retained DOM or page globals. */
|
|
7
|
+
export async function observeDeviceEnvironment() {
|
|
8
|
+
const data = navigator.userAgentData;
|
|
9
|
+
let userAgentMetadata = null;
|
|
10
|
+
if (data) {
|
|
11
|
+
const values = await data.getHighEntropyValues(['architecture', 'bitness', 'fullVersionList', 'model', 'platformVersion', 'uaFullVersion', 'wow64', 'formFactors']);
|
|
12
|
+
userAgentMetadata = {
|
|
13
|
+
brands: values.brands, fullVersionList: values.fullVersionList,
|
|
14
|
+
...(typeof values.uaFullVersion === 'string' ? { fullVersion: values.uaFullVersion } : {}),
|
|
15
|
+
platform: values.platform, platformVersion: values.platformVersion, architecture: values.architecture, model: values.model, mobile: values.mobile,
|
|
16
|
+
...(typeof values.bitness === 'string' ? { bitness: values.bitness } : {}),
|
|
17
|
+
...(typeof values.wow64 === 'boolean' ? { wow64: values.wow64 } : {}),
|
|
18
|
+
...(Array.isArray(values.formFactors) ? { formFactors: values.formFactors } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const probe = document.createElement('div');
|
|
22
|
+
probe.style.cssText = 'all:initial!important;position:fixed!important;visibility:hidden!important;pointer-events:none!important;width:0!important;height:0!important;padding-top:env(safe-area-inset-top)!important;padding-right:env(safe-area-inset-right)!important;padding-bottom:env(safe-area-inset-bottom)!important;padding-left:env(safe-area-inset-left)!important;';
|
|
23
|
+
document.documentElement.appendChild(probe);
|
|
24
|
+
try {
|
|
25
|
+
const style = getComputedStyle(probe);
|
|
26
|
+
return {
|
|
27
|
+
userAgent: navigator.userAgent, platform: navigator.platform, userAgentMetadata,
|
|
28
|
+
screen: { width: screen.width, height: screen.height, deviceScaleFactor: devicePixelRatio, orientation: screen.orientation.type, angle: screen.orientation.angle },
|
|
29
|
+
touchPoints: navigator.maxTouchPoints,
|
|
30
|
+
safeAreaInsets: { top: parseFloat(style.paddingTop) || 0, right: parseFloat(style.paddingRight) || 0, bottom: parseFloat(style.paddingBottom) || 0, left: parseFloat(style.paddingLeft) || 0 },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
probe.remove();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function isDeviceUserAgentMetadata(value) {
|
|
38
|
+
if (!value || typeof value !== 'object')
|
|
39
|
+
return false;
|
|
40
|
+
const metadata = value;
|
|
41
|
+
const brands = (value) => Array.isArray(value) && value.every(item => item && typeof item.brand === 'string' && typeof item.version === 'string');
|
|
42
|
+
return brands(metadata.brands) && brands(metadata.fullVersionList) &&
|
|
43
|
+
[metadata.platform, metadata.platformVersion, metadata.architecture, metadata.model].every(value => typeof value === 'string') &&
|
|
44
|
+
typeof metadata.mobile === 'boolean' && (metadata.fullVersion === undefined || typeof metadata.fullVersion === 'string') &&
|
|
45
|
+
(metadata.bitness === undefined || typeof metadata.bitness === 'string') && (metadata.wow64 === undefined || typeof metadata.wow64 === 'boolean') &&
|
|
46
|
+
(metadata.formFactors === undefined || (Array.isArray(metadata.formFactors) && metadata.formFactors.every(value => typeof value === 'string')));
|
|
47
|
+
}
|
|
48
|
+
export function isDeviceEnvironmentObservation(value) {
|
|
49
|
+
if (!value || typeof value !== 'object')
|
|
50
|
+
return false;
|
|
51
|
+
const current = value;
|
|
52
|
+
return typeof current.userAgent === 'string' && typeof current.platform === 'string' &&
|
|
53
|
+
(current.userAgentMetadata === null || isDeviceUserAgentMetadata(current.userAgentMetadata)) &&
|
|
54
|
+
Boolean(current.screen) && [current.screen.width, current.screen.height, current.screen.deviceScaleFactor].every(value => Number.isFinite(value) && value > 0) &&
|
|
55
|
+
typeof current.screen.orientation === 'string' && Number.isFinite(current.screen.angle) &&
|
|
56
|
+
Number.isSafeInteger(current.touchPoints) && current.touchPoints >= 0 && Boolean(current.safeAreaInsets) &&
|
|
57
|
+
[current.safeAreaInsets.top, current.safeAreaInsets.right, current.safeAreaInsets.bottom, current.safeAreaInsets.left].every(value => Number.isFinite(value) && value >= 0);
|
|
58
|
+
}
|
|
59
|
+
/** CDP transports can reorder object keys. Values and array order remain significant. */
|
|
60
|
+
export function deviceEnvironmentObservationsEqual(left, right) {
|
|
61
|
+
const ordered = (value) => {
|
|
62
|
+
if (Array.isArray(value))
|
|
63
|
+
return value.map(ordered);
|
|
64
|
+
if (value && typeof value === 'object')
|
|
65
|
+
return Object.fromEntries(Object.keys(value).sort().map(key => [key, ordered(value[key])]));
|
|
66
|
+
return value;
|
|
67
|
+
};
|
|
68
|
+
return JSON.stringify(ordered(left)) === JSON.stringify(ordered(right));
|
|
69
|
+
}
|
|
70
|
+
export function matchesEmulatedDeviceEnvironment(config, observed) {
|
|
71
|
+
return observed.userAgent === config.userAgent && observed.platform === config.platform &&
|
|
72
|
+
observed.screen.width === config.width && observed.screen.height === config.height &&
|
|
73
|
+
Math.abs(observed.screen.deviceScaleFactor - config.deviceScaleFactor) < 0.0001 &&
|
|
74
|
+
observed.screen.orientation.startsWith(config.orientation) && observed.screen.angle === (config.orientation === 'landscape' ? 90 : 0) &&
|
|
75
|
+
observed.touchPoints === (config.touch ? 5 : 0) &&
|
|
76
|
+
['top', 'right', 'bottom', 'left'].every(edge => observed.safeAreaInsets[edge] === config.safeAreaInsets[edge]);
|
|
77
|
+
}
|
package/dist/domClicking.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface PageClickDispatchTarget {
|
|
|
40
40
|
frame?: PageClickFrameTarget;
|
|
41
41
|
coordinates?: PageClickCoordinates;
|
|
42
42
|
controlState?: PageClickControlState;
|
|
43
|
+
selectionState?: PageClickSelectionState;
|
|
43
44
|
dispatchMode?: PageClickDispatchMode;
|
|
44
45
|
pointerType?: PageClickPointerType;
|
|
45
46
|
}
|
|
@@ -84,6 +85,15 @@ export interface PageClickControlState {
|
|
|
84
85
|
checkedBefore: boolean;
|
|
85
86
|
checkedAfter: boolean;
|
|
86
87
|
}
|
|
88
|
+
export interface PageClickSelectionState {
|
|
89
|
+
valueBefore: string;
|
|
90
|
+
valueAfter: string;
|
|
91
|
+
selectedAfter: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** Native options need value selection; clicking their select only opens the menu. */
|
|
94
|
+
export declare function isStateAwareClickTarget(target: PageClickDispatchTarget & {
|
|
95
|
+
inputType?: string;
|
|
96
|
+
}): boolean;
|
|
87
97
|
export interface PageClickResolvedTarget {
|
|
88
98
|
tag: string;
|
|
89
99
|
text: string;
|
|
@@ -102,6 +112,7 @@ export interface PageClickResultData {
|
|
|
102
112
|
coordinates?: PageClickCoordinates;
|
|
103
113
|
actionableAncestorCandidates?: PageClickAncestorCandidate[];
|
|
104
114
|
controlState?: PageClickControlState;
|
|
115
|
+
selectionState?: PageClickSelectionState;
|
|
105
116
|
dispatchMode?: PageClickDispatchMode;
|
|
106
117
|
pointerType?: PageClickPointerType;
|
|
107
118
|
fallbackReason?: PageClickFallbackReason;
|
|
@@ -127,6 +138,7 @@ export interface PageClickDispatchAttemptEvidence {
|
|
|
127
138
|
coordinates?: PageClickCoordinates;
|
|
128
139
|
actionableAncestorCandidates?: Array<Pick<PageClickAncestorCandidate, 'selector' | 'distance' | 'reason'>>;
|
|
129
140
|
controlState?: PageClickControlState;
|
|
141
|
+
selectionState?: PageClickSelectionState;
|
|
130
142
|
dispatchMode?: PageClickDispatchMode;
|
|
131
143
|
pointerType?: PageClickPointerType;
|
|
132
144
|
}
|
|
@@ -161,6 +173,7 @@ export declare function clickElementViaJS(this: any, expectedText?: string): {
|
|
|
161
173
|
text: string;
|
|
162
174
|
};
|
|
163
175
|
controlState?: PageClickControlState;
|
|
176
|
+
selectionState?: PageClickSelectionState;
|
|
164
177
|
error?: string;
|
|
165
178
|
} | null;
|
|
166
179
|
/**
|
|
@@ -179,6 +192,7 @@ export declare function clickElementBySelector(selector: string, invalidSelector
|
|
|
179
192
|
strategy?: PageClickTargetStrategy;
|
|
180
193
|
distance?: number;
|
|
181
194
|
controlState?: PageClickControlState;
|
|
195
|
+
selectionState?: PageClickSelectionState;
|
|
182
196
|
error?: string;
|
|
183
197
|
};
|
|
184
198
|
/**
|
package/dist/domClicking.js
CHANGED
|
@@ -12,6 +12,12 @@ export const PAGE_CLICK_INPUT_TIMEOUT_MS = 10_000;
|
|
|
12
12
|
export const PAGE_CLICK_HIDDEN_TOUCH_ERROR = 'page_click: clickMode "touch" requires a visible page, but the target is hidden or in a background tab. ' +
|
|
13
13
|
'Use clickMode "auto" for DOM fallback, or call tab_open for the same URL with reuse "require" and active true. ' +
|
|
14
14
|
'Extension activation uses background focus emulation while Do not disturb is enabled; check activation.visibilityState before retrying trusted input; use "mouse" only when touch semantics are not required.';
|
|
15
|
+
/** Native options need value selection; clicking their select only opens the menu. */
|
|
16
|
+
export function isStateAwareClickTarget(target) {
|
|
17
|
+
return (target.tag === 'option' ||
|
|
18
|
+
target.sourceTarget?.tag === 'option' ||
|
|
19
|
+
/^(checkbox|radio)$/i.test(target.inputType ?? ''));
|
|
20
|
+
}
|
|
15
21
|
/** Resolve the click path from the requested mode, page visibility, and device emulation state. */
|
|
16
22
|
export function resolvePageClickDispatchPolicy(clickMode, visibilityState, touchEmulationEnabled) {
|
|
17
23
|
if (clickMode !== undefined && clickMode !== 'auto') {
|
|
@@ -62,6 +68,7 @@ export function createPageClickResult(requestedTarget, dispatchTarget) {
|
|
|
62
68
|
? { actionableAncestorCandidates: evidence.actionableAncestorCandidates }
|
|
63
69
|
: {}),
|
|
64
70
|
...(evidence.controlState ? { controlState: evidence.controlState } : {}),
|
|
71
|
+
...(evidence.selectionState ? { selectionState: evidence.selectionState } : {}),
|
|
65
72
|
...(evidence.dispatchMode ? { dispatchMode: evidence.dispatchMode } : {}),
|
|
66
73
|
...(evidence.pointerType ? { pointerType: evidence.pointerType } : {}),
|
|
67
74
|
};
|
|
@@ -90,6 +97,7 @@ export function describePageClickDispatchTarget(dispatchTarget) {
|
|
|
90
97
|
? { actionableAncestorCandidates: dispatchTarget.actionableAncestorCandidates }
|
|
91
98
|
: {}),
|
|
92
99
|
...(dispatchTarget.controlState ? { controlState: dispatchTarget.controlState } : {}),
|
|
100
|
+
...(dispatchTarget.selectionState ? { selectionState: dispatchTarget.selectionState } : {}),
|
|
93
101
|
...(dispatchTarget.dispatchMode ? { dispatchMode: dispatchTarget.dispatchMode } : {}),
|
|
94
102
|
...(dispatchTarget.pointerType ? { pointerType: dispatchTarget.pointerType } : {}),
|
|
95
103
|
};
|
|
@@ -126,6 +134,7 @@ export function describePageClickDispatchAttempt(dispatchTarget) {
|
|
|
126
134
|
}
|
|
127
135
|
: {}),
|
|
128
136
|
...(evidence.controlState ? { controlState: evidence.controlState } : {}),
|
|
137
|
+
...(evidence.selectionState ? { selectionState: evidence.selectionState } : {}),
|
|
129
138
|
...(evidence.dispatchMode ? { dispatchMode: evidence.dispatchMode } : {}),
|
|
130
139
|
...(evidence.pointerType ? { pointerType: evidence.pointerType } : {}),
|
|
131
140
|
};
|
|
@@ -149,11 +158,24 @@ export function clickElementViaJS(expectedText) {
|
|
|
149
158
|
if (el.tagName === 'OPTION') {
|
|
150
159
|
const select = el.closest('select');
|
|
151
160
|
if (select) {
|
|
152
|
-
|
|
161
|
+
if (select.matches(':disabled') || el.disabled || el.closest('optgroup')?.disabled) {
|
|
162
|
+
return {
|
|
163
|
+
tag: 'option',
|
|
164
|
+
text: sourceTarget.text,
|
|
165
|
+
cx: 0,
|
|
166
|
+
cy: 0,
|
|
167
|
+
strategy: 'self',
|
|
168
|
+
distance: 0,
|
|
169
|
+
sourceTarget,
|
|
170
|
+
error: 'Cannot select a disabled option',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const valueBefore = select.value;
|
|
174
|
+
const desc = Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, 'selectedIndex');
|
|
153
175
|
if (desc && desc.set)
|
|
154
|
-
desc.set.call(select, el.
|
|
176
|
+
desc.set.call(select, el.index);
|
|
155
177
|
else
|
|
156
|
-
select.
|
|
178
|
+
select.selectedIndex = el.index;
|
|
157
179
|
select.dispatchEvent(new Event('input', { bubbles: true }));
|
|
158
180
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
159
181
|
const rect = select.getBoundingClientRect();
|
|
@@ -165,6 +187,7 @@ export function clickElementViaJS(expectedText) {
|
|
|
165
187
|
strategy: 'self',
|
|
166
188
|
distance: 0,
|
|
167
189
|
sourceTarget,
|
|
190
|
+
selectionState: { valueBefore, valueAfter: select.value, selectedAfter: el.selected },
|
|
168
191
|
};
|
|
169
192
|
}
|
|
170
193
|
}
|
|
@@ -524,11 +547,23 @@ export function clickElementBySelector(selector, invalidSelectorHint) {
|
|
|
524
547
|
if (element.tagName === 'OPTION') {
|
|
525
548
|
const select = element.closest('select');
|
|
526
549
|
if (select) {
|
|
527
|
-
const
|
|
550
|
+
const option = element;
|
|
551
|
+
if (select.matches(':disabled') || option.disabled || option.closest('optgroup')?.disabled) {
|
|
552
|
+
return {
|
|
553
|
+
clicked: false,
|
|
554
|
+
tag: 'option',
|
|
555
|
+
text: (option.textContent || '').trim().slice(0, 100),
|
|
556
|
+
cx: 0,
|
|
557
|
+
cy: 0,
|
|
558
|
+
error: 'Cannot select a disabled option',
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const valueBefore = select.value;
|
|
562
|
+
const desc = Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, 'selectedIndex');
|
|
528
563
|
if (desc && desc.set)
|
|
529
|
-
desc.set.call(select,
|
|
564
|
+
desc.set.call(select, option.index);
|
|
530
565
|
else
|
|
531
|
-
select.
|
|
566
|
+
select.selectedIndex = option.index;
|
|
532
567
|
select.dispatchEvent(new Event('input', { bubbles: true }));
|
|
533
568
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
534
569
|
const rect = select.getBoundingClientRect();
|
|
@@ -540,6 +575,7 @@ export function clickElementBySelector(selector, invalidSelectorHint) {
|
|
|
540
575
|
cy: rect.y + rect.height / 2,
|
|
541
576
|
strategy: 'self',
|
|
542
577
|
distance: 0,
|
|
578
|
+
selectionState: { valueBefore, valueAfter: select.value, selectedAfter: option.selected },
|
|
543
579
|
};
|
|
544
580
|
}
|
|
545
581
|
}
|
|
@@ -1405,12 +1441,12 @@ export function showClickIndicator(px, py, mode) {
|
|
|
1405
1441
|
ring +
|
|
1406
1442
|
';' +
|
|
1407
1443
|
'pointer-events:none; z-index:2147483647;' +
|
|
1408
|
-
'animation:
|
|
1409
|
-
if (!document.getElementById('
|
|
1444
|
+
'animation:__mearl_click_fade 600ms ease-out forwards;';
|
|
1445
|
+
if (!document.getElementById('__mearl_click_style')) {
|
|
1410
1446
|
const style = document.createElement('style');
|
|
1411
|
-
style.id = '
|
|
1447
|
+
style.id = '__mearl_click_style';
|
|
1412
1448
|
style.textContent =
|
|
1413
|
-
'@keyframes
|
|
1449
|
+
'@keyframes __mearl_click_fade {' +
|
|
1414
1450
|
'0% { opacity:1; transform:scale(1); }' +
|
|
1415
1451
|
'50% { opacity:0.8; transform:scale(1.8); }' +
|
|
1416
1452
|
'100% { opacity:0; transform:scale(2.2); }' +
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BrowserAccessResult, BrowserActionMap, BrowserJsonValue, BrowserProviderListResult, BrowserProviderLaunchForm, ControlSessionInfo, CookieCopyScope, ManagedBrowserInfo, BrowserReleaseParams, BrowserReleaseResult, PageDownloadParams } from './browserActionProtocol.js';
|
|
2
2
|
import { type AgentServiceActionMap } from './agentServiceProtocol.js';
|
|
3
|
+
import type { BrowserEnhancementRequest, BrowserEnhancementResponse } from './browserEnhancement.js';
|
|
3
4
|
import { type ToolboxServiceActionMap, type ToolboxTaskRequest, type ToolboxTaskResponse } from './toolbox.js';
|
|
4
5
|
export declare const EXTENSION_RPC_VERSION = 1;
|
|
5
6
|
export type OperationRequest<M> = {
|
|
@@ -171,6 +172,10 @@ export type ExtensionUiServiceRequest<A extends ExtensionUiServiceAction> = Exte
|
|
|
171
172
|
export type ExtensionUiServiceResponse<A extends ExtensionUiServiceAction, R> = A extends BrowserManagementAction ? BrowserManagementResponse<A, R> : A extends 'toolbox_task' ? R extends ToolboxTaskRequest ? ToolboxTaskResponse<R> : never : ExtensionUiServiceActionMap[A]['response'];
|
|
172
173
|
export declare function isExtensionUiServiceAction(value: unknown): value is ExtensionUiServiceAction;
|
|
173
174
|
export interface HostToExtensionActionMap {
|
|
175
|
+
__browser_enhancement: {
|
|
176
|
+
request: BrowserEnhancementRequest;
|
|
177
|
+
response: BrowserEnhancementResponse;
|
|
178
|
+
};
|
|
174
179
|
__preset_installation: {
|
|
175
180
|
request: Record<string, unknown>;
|
|
176
181
|
response: unknown;
|
|
@@ -250,6 +255,7 @@ export interface HostToExtensionActionMap {
|
|
|
250
255
|
};
|
|
251
256
|
}
|
|
252
257
|
export declare const HOST_TO_EXTENSION_ACTIONS: {
|
|
258
|
+
readonly __browser_enhancement: true;
|
|
253
259
|
readonly __preset_installation: true;
|
|
254
260
|
readonly __preset_browser: true;
|
|
255
261
|
readonly __export_cookies: true;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from './appProfileDefinition.js';
|
|
2
2
|
export * from './types.js';
|
|
3
|
+
export * from './browserEnhancement.js';
|
|
4
|
+
export * from './browserDiscovery.js';
|
|
5
|
+
export * from './providerAppResources.js';
|
|
3
6
|
export * from './eventFilters.js';
|
|
4
7
|
export * from './requestSerializer.js';
|
|
5
8
|
export * from './boundedRequestBuffer.js';
|
|
@@ -10,6 +13,7 @@ export * from './domScrolling.js';
|
|
|
10
13
|
export * from './domInspection.js';
|
|
11
14
|
export * from './domSnapshot.js';
|
|
12
15
|
export * from './deviceEmulation.js';
|
|
16
|
+
export * from './deviceEnvironmentObservation.js';
|
|
13
17
|
export * from './debuggerTarget.js';
|
|
14
18
|
export * from './rootWebViewNavigation.js';
|
|
15
19
|
export type { AppBridgeStyle, AppBridgeSpec, AppBridgeBehavior, BridgeActionMap, AppSchemeRule, AppMiniProgramConfig, AppMiniProgramPaymentRoute, AppMiniProgramMessageAction, AppMiniProgramMessageRule, AppMiniProgramNativeAction, AppMiniProgramNativeRoute, AppImagePickerConfig, AppOverlayConfig, AppNativeBackConfig, AppLayerUrlProxyConfig, AppTitleBarConfig, AppTitleBarHostStyle, AppSafeAreaInsets, AppRootContainerCookie, AppRootContainerConfig, InjectedAppProfileConfig, AppProfileHostCapabilities, } from './app-profile-runtime/types.js';
|
|
@@ -21,6 +25,7 @@ export * from './axSnapshot.js';
|
|
|
21
25
|
export * from './pageObserver.js';
|
|
22
26
|
export * from './navigationTracker.js';
|
|
23
27
|
export * from './pageAct.js';
|
|
28
|
+
export * from './pageDialog.js';
|
|
24
29
|
export * from './backendNodeRefs.js';
|
|
25
30
|
export * from './pageEffectRefs.js';
|
|
26
31
|
export * from './pageWait.js';
|
|
@@ -59,3 +64,4 @@ export { parseQueryString } from './queryString.js';
|
|
|
59
64
|
export * from './extensionVersion.js';
|
|
60
65
|
export { MEARL_NATIVE_HOST_NAMES, type NativeHostIntegrationMode } from './nativeHostNames.js';
|
|
61
66
|
export * from './dataStructure.js';
|
|
67
|
+
export * from './requestMetadata.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from './appProfileDefinition.js';
|
|
2
2
|
export * from './types.js';
|
|
3
|
+
export * from './browserEnhancement.js';
|
|
4
|
+
export * from './browserDiscovery.js';
|
|
5
|
+
export * from './providerAppResources.js';
|
|
3
6
|
export * from './eventFilters.js';
|
|
4
7
|
export * from './requestSerializer.js';
|
|
5
8
|
export * from './boundedRequestBuffer.js';
|
|
@@ -10,6 +13,7 @@ export * from './domScrolling.js';
|
|
|
10
13
|
export * from './domInspection.js';
|
|
11
14
|
export * from './domSnapshot.js';
|
|
12
15
|
export * from './deviceEmulation.js';
|
|
16
|
+
export * from './deviceEnvironmentObservation.js';
|
|
13
17
|
export * from './debuggerTarget.js';
|
|
14
18
|
export * from './rootWebViewNavigation.js';
|
|
15
19
|
export * from './axTextMatch.js';
|
|
@@ -20,6 +24,7 @@ export * from './axSnapshot.js';
|
|
|
20
24
|
export * from './pageObserver.js';
|
|
21
25
|
export * from './navigationTracker.js';
|
|
22
26
|
export * from './pageAct.js';
|
|
27
|
+
export * from './pageDialog.js';
|
|
23
28
|
export * from './backendNodeRefs.js';
|
|
24
29
|
export * from './pageEffectRefs.js';
|
|
25
30
|
export * from './pageWait.js';
|
|
@@ -58,3 +63,4 @@ export { parseQueryString } from './queryString.js';
|
|
|
58
63
|
export * from './extensionVersion.js';
|
|
59
64
|
export { MEARL_NATIVE_HOST_NAMES } from './nativeHostNames.js';
|
|
60
65
|
export * from './dataStructure.js';
|
|
66
|
+
export * from './requestMetadata.js';
|
package/dist/pageAct.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export interface PageActNavigationObserver {
|
|
|
61
61
|
dispose(): void | Promise<void>;
|
|
62
62
|
}
|
|
63
63
|
export interface PageActDependencies {
|
|
64
|
+
dialogs?: import('./pageDialog.js').PageDialogState;
|
|
64
65
|
startObservation(): Promise<PageObservationCheckpoint>;
|
|
65
66
|
finishObservation(checkpoint: PageObservationCheckpoint, options: PageObservationWaitOptions): Promise<PageActObservation>;
|
|
66
67
|
startNavigationObserver(options: {
|
|
@@ -89,6 +90,7 @@ interface PageActUnavailableObservation {
|
|
|
89
90
|
export type PageActObservation = PageChangeSet | PageNavigationChange | PageActUnavailableObservation;
|
|
90
91
|
export type PageActObservationResponse = (Omit<PageChangeSet, 'url' | 'title'> & Partial<Pick<PageChangeSet, 'url' | 'title'>>) | PageNavigationChange | PageActUnavailableObservation;
|
|
91
92
|
export interface PageActResult {
|
|
93
|
+
dialog?: import('./pageDialog.js').PageDialogInfo;
|
|
92
94
|
action: {
|
|
93
95
|
name: string;
|
|
94
96
|
success: boolean;
|