browser-debugger-cli 0.11.0 → 0.13.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/.claude/skills/bdg/SKILL.md +4 -4
- package/README.md +143 -79
- package/dist/commands/cdp.d.ts +22 -1
- package/dist/commands/cdp.js +100 -43
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/console.js +62 -12
- package/dist/commands/css.d.ts +13 -0
- package/dist/commands/css.js +53 -0
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.js +3 -2
- package/dist/commands/dom/audit.d.ts +14 -0
- package/dist/commands/dom/audit.js +87 -0
- package/dist/commands/dom/eval.d.ts +3 -2
- package/dist/commands/dom/eval.js +11 -5
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +42 -11
- package/dist/commands/dom/get.js +8 -8
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
- package/dist/commands/dom/helpers/keyAttributes.js +6 -4
- package/dist/commands/dom/helpers/query.d.ts +27 -3
- package/dist/commands/dom/helpers/query.js +152 -64
- package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
- package/dist/commands/dom/helpers/screenshot.js +169 -49
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +19 -2
- package/dist/commands/dom/query.js +37 -6
- package/dist/commands/dom/screenshot.js +12 -7
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/dom/wait.js +5 -3
- package/dist/commands/helpJson.d.ts +82 -19
- package/dist/commands/helpJson.js +112 -41
- package/dist/commands/helpTopic.d.ts +16 -1
- package/dist/commands/helpTopic.js +59 -1
- package/dist/commands/installSkill.d.ts +15 -5
- package/dist/commands/installSkill.js +86 -16
- package/dist/commands/network/list.js +22 -12
- package/dist/commands/optionBehaviors.js +53 -16
- package/dist/commands/page.js +7 -4
- package/dist/commands/peek.d.ts +7 -0
- package/dist/commands/peek.js +65 -23
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +20 -9
- package/dist/commands/shared/dataFetcher.d.ts +12 -4
- package/dist/commands/shared/dataFetcher.js +12 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/optionTypes.d.ts +9 -2
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/start.d.ts +20 -5
- package/dist/commands/start.js +84 -23
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands/tail.d.ts +7 -1
- package/dist/commands/tail.js +13 -62
- package/dist/commands.js +2 -0
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/launcher.js +3 -2
- package/dist/daemon/SessionController.js +6 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +4 -1
- package/dist/daemon/session/Session.js +33 -2
- package/dist/daemon/session/TelemetryStore.d.ts +8 -1
- package/dist/daemon/session/TelemetryStore.js +13 -1
- package/dist/daemon/session/commandRegistry.js +36 -14
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/plugins.js +19 -53
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon.js +9234 -7222
- package/dist/errors/messages.d.ts +88 -15
- package/dist/errors/messages.js +177 -27
- package/dist/index.js +19322 -13961
- package/dist/ipc/client.d.ts +22 -2
- package/dist/ipc/client.js +34 -5
- package/dist/ipc/protocol/auditTypes.d.ts +135 -0
- package/dist/ipc/protocol/auditTypes.js +6 -0
- package/dist/ipc/protocol/commands.d.ts +35 -0
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +16 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
- package/dist/ipc/session/types.d.ts +2 -0
- package/dist/runtime/css/search.d.ts +39 -0
- package/dist/runtime/css/search.js +122 -0
- package/dist/runtime/dom/actionEffects.d.ts +9 -2
- package/dist/runtime/dom/actionEffects.js +30 -14
- package/dist/runtime/dom/audit.d.ts +19 -0
- package/dist/runtime/dom/audit.js +37 -0
- package/dist/runtime/dom/auditModel.d.ts +45 -0
- package/dist/runtime/dom/auditModel.js +220 -0
- package/dist/runtime/dom/auditScripts.d.ts +113 -0
- package/dist/runtime/dom/auditScripts.js +148 -0
- package/dist/runtime/dom/elementGeometry.d.ts +16 -3
- package/dist/runtime/dom/elementGeometry.js +49 -10
- package/dist/runtime/dom/elementInfo.d.ts +74 -17
- package/dist/runtime/dom/elementInfo.js +187 -34
- package/dist/runtime/dom/evalHelpers.d.ts +12 -2
- package/dist/runtime/dom/evalHelpers.js +67 -7
- package/dist/runtime/dom/formDiscovery.d.ts +6 -2
- package/dist/runtime/dom/formDiscovery.js +20 -3
- package/dist/runtime/dom/formFillHelpers/fill.js +8 -12
- package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
- package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
- package/dist/runtime/dom/formSubmitHelpers.js +4 -3
- package/dist/runtime/dom/frameLayout.js +1 -0
- package/dist/runtime/dom/inspect.d.ts +7 -0
- package/dist/runtime/dom/inspect.js +92 -28
- package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
- package/dist/runtime/dom/inspectAllStyles.js +90 -7
- package/dist/runtime/dom/inspectCascade.d.ts +19 -2
- package/dist/runtime/dom/inspectCascade.js +214 -44
- package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
- package/dist/runtime/dom/inspectCascadeModel.js +108 -34
- package/dist/runtime/dom/inspectHints.d.ts +26 -3
- package/dist/runtime/dom/inspectHints.js +125 -9
- package/dist/runtime/dom/inspectModel.d.ts +5 -1
- package/dist/runtime/dom/inspectModel.js +37 -10
- package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
- package/dist/runtime/dom/inspectPaintModel.js +182 -68
- package/dist/runtime/dom/inspectRules.d.ts +19 -0
- package/dist/runtime/dom/inspectRules.js +21 -5
- package/dist/runtime/dom/inspectScripts.d.ts +112 -12
- package/dist/runtime/dom/inspectScripts.js +357 -32
- package/dist/runtime/dom/inspectTree.js +10 -2
- package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
- package/dist/runtime/dom/inspectWhyModel.js +52 -10
- package/dist/runtime/dom/layout.js +40 -16
- package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
- package/dist/runtime/dom/reactEventHelpers.js +90 -36
- package/dist/runtime/dom/targetNode.d.ts +18 -5
- package/dist/runtime/dom/targetNode.js +268 -8
- package/dist/runtime/dom/wait.js +2 -1
- package/dist/runtime/page/bdgWorld.d.ts +57 -0
- package/dist/runtime/page/bdgWorld.js +180 -0
- package/dist/runtime/page/emulation.d.ts +13 -4
- package/dist/runtime/page/emulation.js +69 -4
- package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -0
- package/dist/runtime/page/userAgent.d.ts +17 -0
- package/dist/runtime/page/userAgent.js +57 -0
- package/dist/session/QueryCacheManager.d.ts +4 -1
- package/dist/session/QueryCacheManager.js +5 -2
- package/dist/session/chrome.d.ts +4 -1
- package/dist/session/chrome.js +7 -1
- package/dist/session/cleanup/staleSession.d.ts +21 -4
- package/dist/session/cleanup/staleSession.js +79 -9
- package/dist/session/cleanup/userCommands.d.ts +4 -1
- package/dist/session/cleanup/userCommands.js +10 -5
- package/dist/session/daemonSocket.d.ts +10 -0
- package/dist/session/daemonSocket.js +22 -0
- package/dist/session/lastSession.d.ts +6 -3
- package/dist/session/lastSession.js +11 -5
- package/dist/session/paths.d.ts +3 -1
- package/dist/session/paths.js +5 -5
- package/dist/session/portClaims.js +4 -3
- package/dist/session/sessionList.d.ts +13 -5
- package/dist/session/sessionList.js +31 -7
- package/dist/telemetry/a11y.js +2 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +20 -0
- package/dist/ui/formatters/audit.d.ts +19 -0
- package/dist/ui/formatters/audit.js +115 -0
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +3 -1
- package/dist/ui/formatters/console/follow.d.ts +2 -1
- package/dist/ui/formatters/console/follow.js +2 -2
- package/dist/ui/formatters/console/json.d.ts +2 -2
- package/dist/ui/formatters/console/json.js +11 -5
- package/dist/ui/formatters/console/shared.d.ts +30 -0
- package/dist/ui/formatters/console/shared.js +16 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +40 -9
- package/dist/ui/formatters/console.d.ts +2 -1
- package/dist/ui/formatters/console.js +7 -5
- package/dist/ui/formatters/details.js +3 -1
- package/dist/ui/formatters/dom.d.ts +2 -2
- package/dist/ui/formatters/dom.js +10 -8
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +50 -17
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/networkList.d.ts +1 -1
- package/dist/ui/formatters/networkList.js +1 -2
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +17 -7
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/commands.d.ts +168 -11
- package/dist/ui/messages/commands.js +245 -18
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/preview.d.ts +12 -0
- package/dist/ui/messages/preview.js +18 -2
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- package/dist/utils/cssValues.js +36 -4
- package/dist/utils/decisionTrees.js +0 -5
- package/dist/utils/directories.d.ts +34 -0
- package/dist/utils/directories.js +88 -0
- package/dist/utils/display.d.ts +16 -0
- package/dist/utils/display.js +42 -0
- package/dist/utils/exitCodes.d.ts +1 -0
- package/dist/utils/exitCodes.js +6 -0
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/dist/utils/suggestions.d.ts +4 -2
- package/dist/utils/suggestions.js +7 -5
- package/dist/utils/taskMappings.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bdg's own JavaScript world in the page (a CDP isolated world): it shares
|
|
3
|
+
* the DOM with the page but has its own built-ins, so a page that replaces
|
|
4
|
+
* `Element.prototype.querySelectorAll`, `JSON.stringify` or
|
|
5
|
+
* `Array.prototype.map` (polyfills, old frameworks, anti-bot scripts) cannot
|
|
6
|
+
* change what bdg's page scripts find or return.
|
|
7
|
+
*
|
|
8
|
+
* Only the entry points need the world: `Runtime.evaluate` runs in it with
|
|
9
|
+
* its `contextId`, and `DOM.resolveNode` hands out objects of it with its
|
|
10
|
+
* `executionContextId`; `Runtime.callFunctionOn` on such an object runs in
|
|
11
|
+
* the same world. The world belongs to the top frame (same-origin iframes are
|
|
12
|
+
* reached through it); a node of another frame, a connection without the
|
|
13
|
+
* Page domain (a frame-scoped one) or a failure to create it fall back to the
|
|
14
|
+
* main world, as before.
|
|
15
|
+
*/
|
|
16
|
+
import { createLogger } from '../../ui/logging/index.js';
|
|
17
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
18
|
+
const log = createLogger('cdp');
|
|
19
|
+
/** Name of bdg's isolated world (shown in DevTools' context selector) */
|
|
20
|
+
export const BDG_WORLD_NAME = 'bdg';
|
|
21
|
+
/** Errors of a context that is gone (navigation, reload) */
|
|
22
|
+
const CONTEXT_GONE = /Cannot find context with specified id|Execution context was destroyed/;
|
|
23
|
+
/** The world's context id per connection, until the page navigates */
|
|
24
|
+
const worlds = new WeakMap();
|
|
25
|
+
/**
|
|
26
|
+
* The execution context id of bdg's world in the top frame, created on first
|
|
27
|
+
* use and forgotten when the top frame navigates or its contexts are cleared.
|
|
28
|
+
*
|
|
29
|
+
* @param cdp - Connection to the page
|
|
30
|
+
* @returns Context id, or null when no world can be made on this connection
|
|
31
|
+
*/
|
|
32
|
+
function worldContext(cdp) {
|
|
33
|
+
const known = worlds.get(cdp);
|
|
34
|
+
if (known)
|
|
35
|
+
return known;
|
|
36
|
+
const created = createWorld(cdp);
|
|
37
|
+
worlds.set(cdp, created);
|
|
38
|
+
return created;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create the world in the top frame and forget it when the page changes.
|
|
42
|
+
*
|
|
43
|
+
* @param cdp - Connection to the page
|
|
44
|
+
* @returns Context id, or null when it cannot be created
|
|
45
|
+
*/
|
|
46
|
+
async function createWorld(cdp) {
|
|
47
|
+
const { on } = cdp;
|
|
48
|
+
if (!on)
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
const { frameTree } = (await cdp.send('Page.getFrameTree'));
|
|
52
|
+
const { executionContextId } = (await cdp.send('Page.createIsolatedWorld', {
|
|
53
|
+
frameId: frameTree.frame.id,
|
|
54
|
+
worldName: BDG_WORLD_NAME,
|
|
55
|
+
grantUniveralAccess: false,
|
|
56
|
+
}));
|
|
57
|
+
const forget = () => {
|
|
58
|
+
if (worlds.get(cdp) === created)
|
|
59
|
+
worlds.delete(cdp);
|
|
60
|
+
stopNavigated();
|
|
61
|
+
stopCleared();
|
|
62
|
+
};
|
|
63
|
+
const created = worlds.get(cdp);
|
|
64
|
+
const stopNavigated = on.call(cdp, 'Page.frameNavigated', (params) => {
|
|
65
|
+
if (params.frame.parentId === undefined)
|
|
66
|
+
forget();
|
|
67
|
+
});
|
|
68
|
+
const stopCleared = on.call(cdp, 'Runtime.executionContextsCleared', forget);
|
|
69
|
+
return executionContextId;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
log.debug(`bdg's isolated world not available: ${getErrorMessage(error)}`);
|
|
73
|
+
worlds.delete(cdp);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Send a command with the world's context, once more with a new world when
|
|
79
|
+
* the context is gone, and in the main world when there is none.
|
|
80
|
+
*
|
|
81
|
+
* @param cdp - Connection to the page
|
|
82
|
+
* @param method - CDP method
|
|
83
|
+
* @param params - Its parameters
|
|
84
|
+
* @param withContext - The parameters with the world's context id
|
|
85
|
+
* @returns The command's result
|
|
86
|
+
*/
|
|
87
|
+
async function sendInWorld(cdp, method, params, withContext) {
|
|
88
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
89
|
+
const contextId = await worldContext(cdp);
|
|
90
|
+
if (contextId === null)
|
|
91
|
+
break;
|
|
92
|
+
try {
|
|
93
|
+
return await cdp.send(method, withContext(contextId));
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (!CONTEXT_GONE.test(getErrorMessage(error)))
|
|
97
|
+
throw error;
|
|
98
|
+
worlds.delete(cdp);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return cdp.send(method, params);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* `Runtime.evaluate` in bdg's world of the top frame. A call that names its
|
|
105
|
+
* own context is sent as it is.
|
|
106
|
+
*
|
|
107
|
+
* @param cdp - Connection to the page
|
|
108
|
+
* @param params - Evaluate parameters
|
|
109
|
+
* @returns Evaluate response
|
|
110
|
+
*/
|
|
111
|
+
export async function evaluateInBdgWorld(cdp, params) {
|
|
112
|
+
if (params.contextId !== undefined || params.uniqueContextId !== undefined) {
|
|
113
|
+
return (await cdp.send('Runtime.evaluate', { ...params }));
|
|
114
|
+
}
|
|
115
|
+
return (await sendInWorld(cdp, 'Runtime.evaluate', { ...params }, (contextId) => ({
|
|
116
|
+
...params,
|
|
117
|
+
contextId,
|
|
118
|
+
})));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* `DOM.resolveNode` into bdg's world of the top frame, so functions called on
|
|
122
|
+
* the node run there. A node the world cannot use (one of a cross-origin
|
|
123
|
+
* frame, or one no longer in the page) is resolved in its own frame's main
|
|
124
|
+
* world, as before.
|
|
125
|
+
*
|
|
126
|
+
* @param cdp - Connection to the page
|
|
127
|
+
* @param params - Resolve parameters
|
|
128
|
+
* @returns Resolve response
|
|
129
|
+
*/
|
|
130
|
+
export async function resolveNodeInBdgWorld(cdp, params) {
|
|
131
|
+
if (params.executionContextId !== undefined) {
|
|
132
|
+
return (await cdp.send('DOM.resolveNode', { ...params }));
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const resolved = (await sendInWorld(cdp, 'DOM.resolveNode', { ...params }, (executionContextId) => ({ ...params, executionContextId })));
|
|
136
|
+
if (await usableInWorld(cdp, resolved.object.objectId))
|
|
137
|
+
return resolved;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
log.debug(`Node resolved in the page's world: ${getErrorMessage(error)}`);
|
|
141
|
+
}
|
|
142
|
+
return (await cdp.send('DOM.resolveNode', { ...params }));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Whether a node resolved into bdg's world can be used there: a node of a
|
|
146
|
+
* cross-origin frame resolves, but every access to it throws.
|
|
147
|
+
*
|
|
148
|
+
* @param cdp - Connection to the page
|
|
149
|
+
* @param objectId - The node in bdg's world
|
|
150
|
+
* @returns True when it can be read
|
|
151
|
+
*/
|
|
152
|
+
async function usableInWorld(cdp, objectId) {
|
|
153
|
+
if (!objectId)
|
|
154
|
+
return false;
|
|
155
|
+
const probe = (await cdp.send('Runtime.callFunctionOn', {
|
|
156
|
+
objectId,
|
|
157
|
+
functionDeclaration: 'function () { try { return this.isConnected === true; } catch (e) { return false; } }',
|
|
158
|
+
returnByValue: true,
|
|
159
|
+
}));
|
|
160
|
+
return probe.result.value === true;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Send a CDP command for bdg's own scripts: `Runtime.evaluate` and
|
|
164
|
+
* `DOM.resolveNode` go to bdg's world, other methods as they are.
|
|
165
|
+
*
|
|
166
|
+
* @param cdp - Connection to the page
|
|
167
|
+
* @param method - CDP method
|
|
168
|
+
* @param params - Its parameters
|
|
169
|
+
* @returns The command's result
|
|
170
|
+
*/
|
|
171
|
+
export function sendForBdgScript(cdp, method, params) {
|
|
172
|
+
if (method === 'Runtime.evaluate') {
|
|
173
|
+
return evaluateInBdgWorld(cdp, params);
|
|
174
|
+
}
|
|
175
|
+
if (method === 'DOM.resolveNode') {
|
|
176
|
+
return resolveNodeInBdgWorld(cdp, params);
|
|
177
|
+
}
|
|
178
|
+
return cdp.send(method, params);
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=bdgWorld.js.map
|
|
@@ -10,17 +10,18 @@ import type { CDPConnection } from '../../connection/cdp.js';
|
|
|
10
10
|
import type { ColorScheme, ViewportSize } from '../../types.js';
|
|
11
11
|
/**
|
|
12
12
|
* `Emulation.setDeviceMetricsOverride` parameters for a session viewport: a
|
|
13
|
-
* desktop viewport of that size at the display's own pixel ratio
|
|
13
|
+
* desktop viewport of that size at the display's own pixel ratio, or a
|
|
14
|
+
* phone's (mobile layout: meta viewport, overlay scrollbars; pixel ratio 3).
|
|
14
15
|
*
|
|
15
|
-
* @param viewport - Viewport size in CSS px
|
|
16
|
-
* @param deviceScaleFactor - Pixel ratio (0 keeps the display's)
|
|
16
|
+
* @param viewport - Viewport size in CSS px, `mobile` for a phone
|
|
17
|
+
* @param deviceScaleFactor - Pixel ratio (0 keeps the display's, or a phone's)
|
|
17
18
|
* @returns CDP parameters
|
|
18
19
|
*/
|
|
19
20
|
export declare function viewportOverride(viewport: ViewportSize, deviceScaleFactor?: number): {
|
|
20
21
|
width: number;
|
|
21
22
|
height: number;
|
|
22
23
|
deviceScaleFactor: number;
|
|
23
|
-
mobile:
|
|
24
|
+
mobile: boolean;
|
|
24
25
|
};
|
|
25
26
|
/**
|
|
26
27
|
* Apply the session's emulation to its page.
|
|
@@ -32,6 +33,14 @@ export declare function applySessionEmulation(cdp: CDPConnection, emulation: {
|
|
|
32
33
|
viewport?: ViewportSize;
|
|
33
34
|
colorScheme?: ColorScheme;
|
|
34
35
|
}): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* A mobile user agent from a desktop one: an Android platform, `Mobile`
|
|
38
|
+
* before `Safari`, and `Chrome` for `HeadlessChrome`.
|
|
39
|
+
*
|
|
40
|
+
* @param desktop - The browser's user agent
|
|
41
|
+
* @returns Mobile user agent
|
|
42
|
+
*/
|
|
43
|
+
export declare function mobileUserAgent(desktop: string): string;
|
|
35
44
|
/** Page emulation of a session: what `--viewport` and `--color-scheme` set */
|
|
36
45
|
export interface SessionEmulation {
|
|
37
46
|
viewport?: ViewportSize;
|
|
@@ -7,19 +7,29 @@
|
|
|
7
7
|
* an attached Chrome (`--chrome-ws-url`) gets its own settings back.
|
|
8
8
|
*/
|
|
9
9
|
import { VIEWPORT_SIZE_JS } from '../dom/elementGeometry.js';
|
|
10
|
+
import { hideHeadlessUserAgent } from './userAgent.js';
|
|
10
11
|
import { createLogger } from '../../ui/logging/index.js';
|
|
11
12
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
12
13
|
const log = createLogger('session');
|
|
14
|
+
/** Pixel ratio of an emulated phone (a common one) */
|
|
15
|
+
const MOBILE_PIXEL_RATIO = 3;
|
|
13
16
|
/**
|
|
14
17
|
* `Emulation.setDeviceMetricsOverride` parameters for a session viewport: a
|
|
15
|
-
* desktop viewport of that size at the display's own pixel ratio
|
|
18
|
+
* desktop viewport of that size at the display's own pixel ratio, or a
|
|
19
|
+
* phone's (mobile layout: meta viewport, overlay scrollbars; pixel ratio 3).
|
|
16
20
|
*
|
|
17
|
-
* @param viewport - Viewport size in CSS px
|
|
18
|
-
* @param deviceScaleFactor - Pixel ratio (0 keeps the display's)
|
|
21
|
+
* @param viewport - Viewport size in CSS px, `mobile` for a phone
|
|
22
|
+
* @param deviceScaleFactor - Pixel ratio (0 keeps the display's, or a phone's)
|
|
19
23
|
* @returns CDP parameters
|
|
20
24
|
*/
|
|
21
25
|
export function viewportOverride(viewport, deviceScaleFactor = 0) {
|
|
22
|
-
|
|
26
|
+
const mobile = viewport.mobile === true;
|
|
27
|
+
return {
|
|
28
|
+
width: viewport.width,
|
|
29
|
+
height: viewport.height,
|
|
30
|
+
deviceScaleFactor: deviceScaleFactor || (mobile ? MOBILE_PIXEL_RATIO : 0),
|
|
31
|
+
mobile,
|
|
32
|
+
};
|
|
23
33
|
}
|
|
24
34
|
/**
|
|
25
35
|
* Apply the session's emulation to its page.
|
|
@@ -30,6 +40,8 @@ export function viewportOverride(viewport, deviceScaleFactor = 0) {
|
|
|
30
40
|
export async function applySessionEmulation(cdp, emulation) {
|
|
31
41
|
if (emulation.viewport) {
|
|
32
42
|
await cdp.send('Emulation.setDeviceMetricsOverride', viewportOverride(emulation.viewport));
|
|
43
|
+
if (emulation.viewport.mobile)
|
|
44
|
+
await emulatePhone(cdp, true);
|
|
33
45
|
}
|
|
34
46
|
if (emulation.colorScheme) {
|
|
35
47
|
await cdp.send('Emulation.setEmulatedMedia', {
|
|
@@ -37,6 +49,55 @@ export async function applySessionEmulation(cdp, emulation) {
|
|
|
37
49
|
});
|
|
38
50
|
}
|
|
39
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Turn the rest of a phone's emulation on or off: touch input (and
|
|
54
|
+
* `pointer: coarse`) and a mobile user agent derived from the browser's own
|
|
55
|
+
* (an Android one), or the browser's own back: the override cleared, or in
|
|
56
|
+
* headless Chrome its regular-Chrome identity ({@link hideHeadlessUserAgent}).
|
|
57
|
+
*
|
|
58
|
+
* @param cdp - Session connection
|
|
59
|
+
* @param on - Emulate a phone
|
|
60
|
+
*/
|
|
61
|
+
async function emulatePhone(cdp, on) {
|
|
62
|
+
await cdp.send('Emulation.setTouchEmulationEnabled', { enabled: on, maxTouchPoints: on ? 5 : 1 });
|
|
63
|
+
const { userAgent } = (await cdp.send('Browser.getVersion', {}));
|
|
64
|
+
if (!on) {
|
|
65
|
+
const headless = userAgent.includes('HeadlessChrome');
|
|
66
|
+
await cdp.send('Emulation.setUserAgentOverride', { userAgent: headless ? userAgent : '' });
|
|
67
|
+
if (headless)
|
|
68
|
+
await hideHeadlessUserAgent(cdp, log);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const major = /Chrome\/(\d+)/.exec(userAgent)?.[1] ?? '';
|
|
72
|
+
await cdp.send('Emulation.setUserAgentOverride', {
|
|
73
|
+
userAgent: mobileUserAgent(userAgent.replace('HeadlessChrome/', 'Chrome/')),
|
|
74
|
+
platform: 'Android',
|
|
75
|
+
userAgentMetadata: {
|
|
76
|
+
brands: [
|
|
77
|
+
{ brand: 'Google Chrome', version: major },
|
|
78
|
+
{ brand: 'Chromium', version: major },
|
|
79
|
+
],
|
|
80
|
+
platform: 'Android',
|
|
81
|
+
platformVersion: '10.0.0',
|
|
82
|
+
architecture: '',
|
|
83
|
+
model: 'K',
|
|
84
|
+
mobile: true,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A mobile user agent from a desktop one: an Android platform, `Mobile`
|
|
90
|
+
* before `Safari`, and `Chrome` for `HeadlessChrome`.
|
|
91
|
+
*
|
|
92
|
+
* @param desktop - The browser's user agent
|
|
93
|
+
* @returns Mobile user agent
|
|
94
|
+
*/
|
|
95
|
+
export function mobileUserAgent(desktop) {
|
|
96
|
+
return desktop
|
|
97
|
+
.replace(/\([^)]*\)/, '(Linux; Android 10; K)')
|
|
98
|
+
.replace('HeadlessChrome/', 'Chrome/')
|
|
99
|
+
.replace(/ (Mobile )?Safari\//, ' Mobile Safari/');
|
|
100
|
+
}
|
|
40
101
|
/**
|
|
41
102
|
* Change the page emulation mid-session (`bdg page emulate`): set the
|
|
42
103
|
* viewport or the color scheme, or clear both (back to the browser window
|
|
@@ -58,6 +119,8 @@ export async function emulatePage(cdp, current, change, record) {
|
|
|
58
119
|
};
|
|
59
120
|
const { viewport: _viewport, colorScheme, ...rest } = state;
|
|
60
121
|
if (change.reset || change.viewport) {
|
|
122
|
+
const wasPhone = current.viewport?.mobile === true;
|
|
123
|
+
const isPhone = change.viewport?.mobile === true;
|
|
61
124
|
await step(() => change.viewport
|
|
62
125
|
? cdp.send('Emulation.setDeviceMetricsOverride', viewportOverride(change.viewport))
|
|
63
126
|
: cdp.send('Emulation.clearDeviceMetricsOverride', {}), {
|
|
@@ -65,6 +128,8 @@ export async function emulatePage(cdp, current, change, record) {
|
|
|
65
128
|
...(colorScheme && { colorScheme }),
|
|
66
129
|
...(change.viewport && { viewport: change.viewport }),
|
|
67
130
|
});
|
|
131
|
+
if (wasPhone !== isPhone)
|
|
132
|
+
await step(() => emulatePhone(cdp, isPhone), state);
|
|
68
133
|
}
|
|
69
134
|
if (change.reset || change.colorScheme) {
|
|
70
135
|
const { colorScheme: _scheme, ...withoutScheme } = state;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which browser built-ins a page replaced (polyfills, old frameworks,
|
|
3
|
+
* anti-bot scripts). Scripts that must run in the page's own world (actions,
|
|
4
|
+
* `dom eval` results) depend on them.
|
|
5
|
+
*
|
|
6
|
+
* The check does not trust the page: a small page script only collects the
|
|
7
|
+
* functions (plain property reads and the page's
|
|
8
|
+
* `Object.getOwnPropertyDescriptor`, itself checked), and whether each is
|
|
9
|
+
* native comes from the description CDP gives a function, which V8 builds
|
|
10
|
+
* itself, so a replaced `Function.prototype.toString` or `.call` changes
|
|
11
|
+
* nothing. A getter or setter counts as the property (a page-defined getter
|
|
12
|
+
* on `window.getComputedStyle`, a replaced `value` setter); a property that
|
|
13
|
+
* throws or is gone counts as replaced. An iterator is not checked when the
|
|
14
|
+
* page replaced `Symbol` (iteration does not use it).
|
|
15
|
+
*/
|
|
16
|
+
import type { CDPSender } from '../../telemetry/objectExpander.js';
|
|
17
|
+
/**
|
|
18
|
+
* The built-ins of a list that the page replaced.
|
|
19
|
+
*
|
|
20
|
+
* @param cdp - CDP connection (or session) of the page
|
|
21
|
+
* @param names - Dotted paths, e.g. `Element.prototype.matches`,
|
|
22
|
+
* `window.getComputedStyle`, `NodeList.prototype[Symbol.iterator]`
|
|
23
|
+
* @param objectId - An object of the realm to check (e.g. a frame's eval
|
|
24
|
+
* result); default: the page's main world
|
|
25
|
+
* @returns The replaced ones, in list order (empty when the check failed)
|
|
26
|
+
*/
|
|
27
|
+
export declare function findReplacedBuiltins(cdp: CDPSender, names: readonly string[], objectId?: string): Promise<string[]>;
|
|
28
|
+
//# sourceMappingURL=replacedBuiltins.d.ts.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which browser built-ins a page replaced (polyfills, old frameworks,
|
|
3
|
+
* anti-bot scripts). Scripts that must run in the page's own world (actions,
|
|
4
|
+
* `dom eval` results) depend on them.
|
|
5
|
+
*
|
|
6
|
+
* The check does not trust the page: a small page script only collects the
|
|
7
|
+
* functions (plain property reads and the page's
|
|
8
|
+
* `Object.getOwnPropertyDescriptor`, itself checked), and whether each is
|
|
9
|
+
* native comes from the description CDP gives a function, which V8 builds
|
|
10
|
+
* itself, so a replaced `Function.prototype.toString` or `.call` changes
|
|
11
|
+
* nothing. A getter or setter counts as the property (a page-defined getter
|
|
12
|
+
* on `window.getComputedStyle`, a replaced `value` setter); a property that
|
|
13
|
+
* throws or is gone counts as replaced. An iterator is not checked when the
|
|
14
|
+
* page replaced `Symbol` (iteration does not use it).
|
|
15
|
+
*/
|
|
16
|
+
import { createLogger } from '../../ui/logging/index.js';
|
|
17
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
18
|
+
const log = createLogger('dom');
|
|
19
|
+
/** Description V8 gives a native function, e.g. `function querySelector() { [native code] }` */
|
|
20
|
+
const NATIVE_FUNCTION = /\{\s*\[native code\]\s*\}$/;
|
|
21
|
+
/** Suffix naming a prototype's iterator, e.g. `NodeList.prototype[Symbol.iterator]` */
|
|
22
|
+
const ITERATOR_SUFFIX = '[Symbol.iterator]';
|
|
23
|
+
/** Path segment the collector reads as `Symbol.iterator` */
|
|
24
|
+
const ITERATOR_KEY = '@@iterator';
|
|
25
|
+
/** Object group of the collected functions (released after each check) */
|
|
26
|
+
const OBJECT_GROUP = 'bdg-builtins';
|
|
27
|
+
/**
|
|
28
|
+
* Page-side collector: for each name, the function(s) behind it, as a flat
|
|
29
|
+
* list of `index, function` pairs (`undefined` for one that throws or is
|
|
30
|
+
* gone). Uses no array or object helpers of the page.
|
|
31
|
+
*
|
|
32
|
+
* @param names - Dotted paths from the global object (a leading `window.` is
|
|
33
|
+
* skipped; a trailing `[Symbol.iterator]` names the iterator)
|
|
34
|
+
* @returns Function declaration
|
|
35
|
+
*/
|
|
36
|
+
function collectorFunction(names) {
|
|
37
|
+
const paths = names.map((name) => name
|
|
38
|
+
.replace(/^window\./, '')
|
|
39
|
+
.replace(ITERATOR_SUFFIX, `.${ITERATOR_KEY}`)
|
|
40
|
+
.split('.'));
|
|
41
|
+
return `function () {
|
|
42
|
+
const paths = ${JSON.stringify(paths)};
|
|
43
|
+
const describe = Object.getOwnPropertyDescriptor;
|
|
44
|
+
const found = [];
|
|
45
|
+
const add = (i, value) => { found[found.length] = i; found[found.length] = value; };
|
|
46
|
+
for (let i = 0; i < paths.length; i++) {
|
|
47
|
+
const path = paths[i];
|
|
48
|
+
let key = path[path.length - 1];
|
|
49
|
+
let owner = window;
|
|
50
|
+
try {
|
|
51
|
+
for (let j = 0; j < path.length - 1; j++) owner = owner[path[j]];
|
|
52
|
+
} catch (e) { add(i, undefined); continue; }
|
|
53
|
+
if (key === '${ITERATOR_KEY}') {
|
|
54
|
+
try { key = Symbol.iterator; } catch (e) { continue; }
|
|
55
|
+
if (typeof key !== 'symbol') continue;
|
|
56
|
+
}
|
|
57
|
+
let own;
|
|
58
|
+
try { own = describe(owner, key); } catch (e) { own = undefined; }
|
|
59
|
+
if (own && (own.get || own.set)) {
|
|
60
|
+
if (own.get) add(i, own.get);
|
|
61
|
+
if (own.set) add(i, own.set);
|
|
62
|
+
} else if (own) {
|
|
63
|
+
add(i, own.value);
|
|
64
|
+
} else {
|
|
65
|
+
try { add(i, owner[key]); } catch (e) { continue; }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return found;
|
|
69
|
+
}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The built-ins of a list that the page replaced.
|
|
73
|
+
*
|
|
74
|
+
* @param cdp - CDP connection (or session) of the page
|
|
75
|
+
* @param names - Dotted paths, e.g. `Element.prototype.matches`,
|
|
76
|
+
* `window.getComputedStyle`, `NodeList.prototype[Symbol.iterator]`
|
|
77
|
+
* @param objectId - An object of the realm to check (e.g. a frame's eval
|
|
78
|
+
* result); default: the page's main world
|
|
79
|
+
* @returns The replaced ones, in list order (empty when the check failed)
|
|
80
|
+
*/
|
|
81
|
+
export async function findReplacedBuiltins(cdp, names, objectId) {
|
|
82
|
+
const functionDeclaration = collectorFunction(names);
|
|
83
|
+
try {
|
|
84
|
+
const collected = (await (objectId
|
|
85
|
+
? cdp.send('Runtime.callFunctionOn', {
|
|
86
|
+
objectId,
|
|
87
|
+
functionDeclaration,
|
|
88
|
+
objectGroup: OBJECT_GROUP,
|
|
89
|
+
})
|
|
90
|
+
: cdp.send('Runtime.evaluate', {
|
|
91
|
+
expression: `(${functionDeclaration})()`,
|
|
92
|
+
objectGroup: OBJECT_GROUP,
|
|
93
|
+
})));
|
|
94
|
+
const listId = collected.result.objectId;
|
|
95
|
+
if (collected.exceptionDetails || !listId)
|
|
96
|
+
return [];
|
|
97
|
+
const { result } = (await cdp.send('Runtime.getProperties', {
|
|
98
|
+
objectId: listId,
|
|
99
|
+
ownProperties: true,
|
|
100
|
+
}));
|
|
101
|
+
return replacedNames(names, result);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
log.debug(`Built-ins not checked: ${getErrorMessage(error)}`);
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await cdp
|
|
109
|
+
.send('Runtime.releaseObjectGroup', { objectGroup: OBJECT_GROUP })
|
|
110
|
+
.catch((error) => log.debug(`Built-ins not released: ${getErrorMessage(error)}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Names whose collected function is not native.
|
|
115
|
+
*
|
|
116
|
+
* @param names - The names checked
|
|
117
|
+
* @param properties - Properties of the collected `index, function` list
|
|
118
|
+
* @returns Replaced names, in list order
|
|
119
|
+
*/
|
|
120
|
+
function replacedNames(names, properties) {
|
|
121
|
+
const entries = new Map();
|
|
122
|
+
for (const property of properties) {
|
|
123
|
+
if (/^\d+$/.test(property.name))
|
|
124
|
+
entries.set(Number(property.name), property.value);
|
|
125
|
+
}
|
|
126
|
+
const replaced = new Set();
|
|
127
|
+
for (let i = 0; entries.has(i); i += 2) {
|
|
128
|
+
const index = entries.get(i)?.value;
|
|
129
|
+
const value = entries.get(i + 1);
|
|
130
|
+
const native = value?.type === 'function' && NATIVE_FUNCTION.test(value.description ?? '');
|
|
131
|
+
if (typeof index === 'number' && !native)
|
|
132
|
+
replaced.add(index);
|
|
133
|
+
}
|
|
134
|
+
return names.filter((_name, i) => replaced.has(i));
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=replacedBuiltins.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session's user agent in headless Chrome: regular Chrome's string and
|
|
3
|
+
* client hints, so sites serve the page a user sees.
|
|
4
|
+
*/
|
|
5
|
+
import type { CDPConnection } from '../../connection/cdp.js';
|
|
6
|
+
import type { Logger } from '../../ui/logging/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Send the user agent and client hints of regular Chrome from headless
|
|
9
|
+
* Chrome: sites serve "HeadlessChrome" a different page (or a bot
|
|
10
|
+
* challenge), so the page would not be the one a user sees. Not for a
|
|
11
|
+
* session emulating a phone, whose emulation sets a mobile user agent.
|
|
12
|
+
*
|
|
13
|
+
* @param cdp - CDP connection
|
|
14
|
+
* @param logger - Logger for failures (the session works without it)
|
|
15
|
+
*/
|
|
16
|
+
export declare function hideHeadlessUserAgent(cdp: CDPConnection, logger: Logger): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=userAgent.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session's user agent in headless Chrome: regular Chrome's string and
|
|
3
|
+
* client hints, so sites serve the page a user sees.
|
|
4
|
+
*/
|
|
5
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
6
|
+
/** Reads the client hints Chrome reports, renaming the HeadlessChrome brand */
|
|
7
|
+
const USER_AGENT_METADATA_SCRIPT = `(async () => {
|
|
8
|
+
const data = navigator.userAgentData;
|
|
9
|
+
if (!data) return null;
|
|
10
|
+
const values = await data.getHighEntropyValues(
|
|
11
|
+
['architecture', 'bitness', 'model', 'platformVersion', 'fullVersionList', 'wow64']
|
|
12
|
+
);
|
|
13
|
+
const rename = (list) => (list || []).map((entry) => ({
|
|
14
|
+
brand: entry.brand.replace('HeadlessChrome', 'Google Chrome'),
|
|
15
|
+
version: entry.version,
|
|
16
|
+
}));
|
|
17
|
+
return {
|
|
18
|
+
brands: rename(values.brands),
|
|
19
|
+
fullVersionList: rename(values.fullVersionList),
|
|
20
|
+
platform: values.platform,
|
|
21
|
+
platformVersion: values.platformVersion,
|
|
22
|
+
architecture: values.architecture,
|
|
23
|
+
model: values.model,
|
|
24
|
+
mobile: values.mobile,
|
|
25
|
+
bitness: values.bitness,
|
|
26
|
+
wow64: values.wow64,
|
|
27
|
+
};
|
|
28
|
+
})()`;
|
|
29
|
+
/**
|
|
30
|
+
* Send the user agent and client hints of regular Chrome from headless
|
|
31
|
+
* Chrome: sites serve "HeadlessChrome" a different page (or a bot
|
|
32
|
+
* challenge), so the page would not be the one a user sees. Not for a
|
|
33
|
+
* session emulating a phone, whose emulation sets a mobile user agent.
|
|
34
|
+
*
|
|
35
|
+
* @param cdp - CDP connection
|
|
36
|
+
* @param logger - Logger for failures (the session works without it)
|
|
37
|
+
*/
|
|
38
|
+
export async function hideHeadlessUserAgent(cdp, logger) {
|
|
39
|
+
try {
|
|
40
|
+
const { userAgent } = (await cdp.send('Browser.getVersion'));
|
|
41
|
+
if (!userAgent.includes('HeadlessChrome'))
|
|
42
|
+
return;
|
|
43
|
+
const metadata = (await cdp.send('Runtime.evaluate', {
|
|
44
|
+
expression: USER_AGENT_METADATA_SCRIPT,
|
|
45
|
+
awaitPromise: true,
|
|
46
|
+
returnByValue: true,
|
|
47
|
+
}));
|
|
48
|
+
await cdp.send('Emulation.setUserAgentOverride', {
|
|
49
|
+
userAgent: userAgent.replace('HeadlessChrome', 'Chrome'),
|
|
50
|
+
...(metadata.result?.value ? { userAgentMetadata: metadata.result.value } : {}),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
logger.debug(`User agent left as is: ${getErrorMessage(error)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=userAgent.js.map
|
|
@@ -71,8 +71,11 @@ export declare class QueryCacheManager {
|
|
|
71
71
|
* Store a query result.
|
|
72
72
|
*
|
|
73
73
|
* @param result - Query result whose nodes carry backend node ids
|
|
74
|
+
* @param document - Identity of the page document queried
|
|
75
|
+
* ({@link pageDocumentId}): backend node ids are only meaningful in it, and
|
|
76
|
+
* a page in a new renderer process reuses them for other elements
|
|
74
77
|
*/
|
|
75
|
-
set(result: DomQueryResult): Promise<void>;
|
|
78
|
+
set(result: DomQueryResult, document?: string): Promise<void>;
|
|
76
79
|
/**
|
|
77
80
|
* Read the cache and check that it can be used.
|
|
78
81
|
*
|
|
@@ -88,11 +88,14 @@ export class QueryCacheManager {
|
|
|
88
88
|
* Store a query result.
|
|
89
89
|
*
|
|
90
90
|
* @param result - Query result whose nodes carry backend node ids
|
|
91
|
+
* @param document - Identity of the page document queried
|
|
92
|
+
* ({@link pageDocumentId}): backend node ids are only meaningful in it, and
|
|
93
|
+
* a page in a new renderer process reuses them for other elements
|
|
91
94
|
*/
|
|
92
|
-
async set(result) {
|
|
95
|
+
async set(result, document) {
|
|
93
96
|
try {
|
|
94
97
|
const cachePath = this.getCachePath();
|
|
95
|
-
await writeFile(cachePath, JSON.stringify({ version: CACHE_VERSION, ...result }), 'utf-8');
|
|
98
|
+
await writeFile(cachePath, JSON.stringify({ version: CACHE_VERSION, ...result, ...(document && { document }) }), 'utf-8');
|
|
96
99
|
log.debug(`Cached ${result.nodes.length} query results to ${cachePath}`);
|
|
97
100
|
}
|
|
98
101
|
catch (error) {
|
package/dist/session/chrome.d.ts
CHANGED
|
@@ -41,8 +41,11 @@ export declare function readChromePid(): number | null;
|
|
|
41
41
|
* Remove Chrome PID from persistent cache.
|
|
42
42
|
*
|
|
43
43
|
* Safe to call multiple times (idempotent).
|
|
44
|
+
*
|
|
45
|
+
* @param expectedPid - Remove it only while it still names this Chrome (a
|
|
46
|
+
* daemon clearing its own, which another daemon may have replaced)
|
|
44
47
|
*/
|
|
45
|
-
export declare function clearChromePid(): void;
|
|
48
|
+
export declare function clearChromePid(expectedPid?: number): void;
|
|
46
49
|
/** What `status --verbose` shows about the session's Chrome */
|
|
47
50
|
export interface RunningChromeInfo {
|
|
48
51
|
/** Executable the process was started from */
|
package/dist/session/chrome.js
CHANGED
|
@@ -10,6 +10,7 @@ import { AtomicFileWriter } from '../utils/atomicFile.js';
|
|
|
10
10
|
import { getErrorMessage } from '../utils/errors.js';
|
|
11
11
|
import { getProcessCommand, isProcessAlive } from '../utils/process.js';
|
|
12
12
|
import { getSessionFilePath, ensureSessionDir } from './paths.js';
|
|
13
|
+
import { readPidFromFile } from './pid.js';
|
|
13
14
|
const log = createLogger('chrome');
|
|
14
15
|
/**
|
|
15
16
|
* Parse Chrome PID from cache file content.
|
|
@@ -100,9 +101,14 @@ export function readChromePid() {
|
|
|
100
101
|
* Remove Chrome PID from persistent cache.
|
|
101
102
|
*
|
|
102
103
|
* Safe to call multiple times (idempotent).
|
|
104
|
+
*
|
|
105
|
+
* @param expectedPid - Remove it only while it still names this Chrome (a
|
|
106
|
+
* daemon clearing its own, which another daemon may have replaced)
|
|
103
107
|
*/
|
|
104
|
-
export function clearChromePid() {
|
|
108
|
+
export function clearChromePid(expectedPid) {
|
|
105
109
|
const cachePath = getSessionFilePath('CHROME_PID');
|
|
110
|
+
if (expectedPid !== undefined && readPidFromFile(cachePath) !== expectedPid)
|
|
111
|
+
return;
|
|
106
112
|
try {
|
|
107
113
|
fs.rmSync(cachePath, { force: true });
|
|
108
114
|
}
|