@mearl/browser-core 3.0.0-beta.5 → 3.0.0-beta.7

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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/dist/agentChat.d.ts +2 -0
  3. package/dist/agentSkills.d.ts +1 -1
  4. package/dist/app-profile-runtime/types.d.ts +4 -8
  5. package/dist/appProfileDefinition.d.ts +4 -7
  6. package/dist/appProfileDefinition.js +11 -49
  7. package/dist/axSnapshot.js +1 -1
  8. package/dist/boundedRequestBuffer.d.ts +4 -4
  9. package/dist/browserActionProtocol.d.ts +46 -29
  10. package/dist/browserActionProtocol.js +6 -6
  11. package/dist/browserCapabilities.d.ts +2 -0
  12. package/dist/browserCapabilities.js +19 -5
  13. package/dist/browserDiscovery.d.ts +3 -0
  14. package/dist/browserDiscovery.js +20 -0
  15. package/dist/browserEnhancement.d.ts +161 -0
  16. package/dist/browserEnhancement.js +80 -0
  17. package/dist/deviceEnvironmentObservation.d.ts +48 -0
  18. package/dist/deviceEnvironmentObservation.js +77 -0
  19. package/dist/domClicking.d.ts +14 -0
  20. package/dist/domClicking.js +46 -10
  21. package/dist/extensionServiceProtocol.d.ts +6 -0
  22. package/dist/extensionServiceProtocol.js +1 -0
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.js +6 -0
  25. package/dist/pageAct.d.ts +2 -0
  26. package/dist/pageAct.js +42 -10
  27. package/dist/pageDialog.d.ts +66 -0
  28. package/dist/pageDialog.js +175 -0
  29. package/dist/pageObserver.js +16 -5
  30. package/dist/pagePressing.d.ts +1 -1
  31. package/dist/providerAppResources.d.ts +39 -0
  32. package/dist/providerAppResources.js +2 -0
  33. package/dist/proxyPermissions.js +3 -3
  34. package/dist/requestMetadata.d.ts +9 -0
  35. package/dist/requestMetadata.js +8 -0
  36. package/dist/requestSerializer.d.ts +2 -2
  37. package/dist/requestStreamCapture.d.ts +5 -3
  38. package/dist/requestStreamCapture.js +2 -0
  39. package/dist/types.d.ts +4 -2
  40. 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
+ }
@@ -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
  /**
@@ -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
- const desc = Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, 'value');
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.value);
176
+ desc.set.call(select, el.index);
155
177
  else
156
- select.value = el.value;
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 desc = Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, 'value');
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, element.value);
564
+ desc.set.call(select, option.index);
530
565
  else
531
- select.value = element.value;
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:__mtop_click_fade 600ms ease-out forwards;';
1409
- if (!document.getElementById('__mtop_click_style')) {
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 = '__mtop_click_style';
1447
+ style.id = '__mearl_click_style';
1412
1448
  style.textContent =
1413
- '@keyframes __mtop_click_fade {' +
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;
@@ -28,6 +28,7 @@ export function isExtensionUiServiceAction(value) {
28
28
  isCloudConnectorAction(value));
29
29
  }
30
30
  export const HOST_TO_EXTENSION_ACTIONS = {
31
+ __browser_enhancement: true,
31
32
  __preset_installation: true,
32
33
  __preset_browser: true,
33
34
  __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;