local-operator-ui 0.32.13 → 0.33.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/out/main/index.js +863 -41
- package/out/renderer/assets/{_basePickBy-BoRKUSnJ.js → _basePickBy-D28vC22o.js} +1 -1
- package/out/renderer/assets/{_baseUniq-Dr1t0_F9.js → _baseUniq-o_JUYW6i.js} +1 -1
- package/out/renderer/assets/{agent-details-page-C0OkQqAk.js → agent-details-page-ChzjnPRU.js} +1 -1
- package/out/renderer/assets/{agent-hub-page-Bh5-0705.js → agent-hub-page-D4sGuq6W.js} +1 -1
- package/out/renderer/assets/{agents-page-BrS1Gx_Z.js → agents-page-DLbb0HLY.js} +3 -3
- package/out/renderer/assets/{arc-Cog9YnT-.js → arc-C7H_y-lB.js} +1 -1
- package/out/renderer/assets/{architectureDiagram-IEHRJDOE-CXzxg4a4.js → architectureDiagram-IEHRJDOE-ByV1G-th.js} +1 -1
- package/out/renderer/assets/{blockDiagram-JOT3LUYC-CMwAviav.js → blockDiagram-JOT3LUYC-CTmNa-ju.js} +1 -1
- package/out/renderer/assets/{book-open-CTP21S75.js → book-open-DYd3X6Ks.js} +1 -1
- package/out/renderer/assets/browser-page-DWoUyFr9.js +1 -0
- package/out/renderer/assets/{browser-webauthn-prompt-BsxwkRuK.js → browser-webauthn-prompt-pJFYpzxg.js} +1 -1
- package/out/renderer/assets/{c4Diagram-VJAJSXHY-8mRM-Zep.js → c4Diagram-VJAJSXHY-BdU1Z3Zd.js} +1 -1
- package/out/renderer/assets/channel-C6MsVi8e.js +1 -0
- package/out/renderer/assets/{chunk-4BMEZGHF-BmqMjZ-3.js → chunk-4BMEZGHF-C1KSNvam.js} +1 -1
- package/out/renderer/assets/{chunk-A2AXSNBT-B1j2iIPV.js → chunk-A2AXSNBT-BeyZ24d5.js} +1 -1
- package/out/renderer/assets/{chunk-AEK57VVT-BMch5iKw.js → chunk-AEK57VVT-BNvqJbtU.js} +1 -1
- package/out/renderer/assets/{chunk-D6G4REZN-B81C9zFT.js → chunk-D6G4REZN-C-nv9uoV.js} +1 -1
- package/out/renderer/assets/{chunk-RZ5BOZE2-D2DKwnyZ.js → chunk-RZ5BOZE2-CjHsbcIj.js} +1 -1
- package/out/renderer/assets/{chunk-XZIHB7SX-BBwUaNug.js → chunk-XZIHB7SX-BcA9mNMe.js} +1 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-BJPo82xK.js +1 -0
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-BJPo82xK.js +1 -0
- package/out/renderer/assets/clone-p5PCMoIy.js +1 -0
- package/out/renderer/assets/{console-mirror-zvZtREj7.js → console-mirror-BsrFX_mx.js} +1 -1
- package/out/renderer/assets/{consoleCapture-rT_Z-gkA.js → consoleCapture-BhdNznhO.js} +1 -1
- package/out/renderer/assets/{conversation-input-sync-C859v2bK.js → conversation-input-sync-CRJ3UpLx.js} +235 -235
- package/out/renderer/assets/{dagre-OKDRZEBW-dLmDksCb.js → dagre-OKDRZEBW-b_ooMjhL.js} +1 -1
- package/out/renderer/assets/{diagram-SSKATNLV-CKjDs7YM.js → diagram-SSKATNLV-BQ61bPZw.js} +1 -1
- package/out/renderer/assets/{diagram-VNBRO52H-CXDkS6Ob.js → diagram-VNBRO52H-BJPjbqDG.js} +1 -1
- package/out/renderer/assets/{erDiagram-Q7BY3M3F-BXs7CjxB.js → erDiagram-Q7BY3M3F-DWqkTt0O.js} +1 -1
- package/out/renderer/assets/{error-boundary-BD3BOwfn.js → error-boundary-BytCV_16.js} +1 -1
- package/out/renderer/assets/{flowDiagram-4HSFHLVR-8Vkpt8tS.js → flowDiagram-4HSFHLVR-DYJ_zZRJ.js} +1 -1
- package/out/renderer/assets/{ganttDiagram-APWFNJXF-D5qyzLP2.js → ganttDiagram-APWFNJXF-BcQIW2kK.js} +1 -1
- package/out/renderer/assets/{gitGraphDiagram-7IBYFJ6S-BFxKpCR_.js → gitGraphDiagram-7IBYFJ6S-BozzPR7Y.js} +1 -1
- package/out/renderer/assets/{graph-Bsm8ZLON.js → graph-2oj8R69e.js} +1 -1
- package/out/renderer/assets/{index-C4Aa6V6W.js → index-BpyyqcP2.js} +196 -196
- package/out/renderer/assets/{index-DYdJijXO.js → index-DFCe_aj6.js} +1 -1
- package/out/renderer/assets/index-DxRO8eiH.css +1 -0
- package/out/renderer/assets/{index-DlhMZSRL.js → index-HFzeSJx1.js} +1 -1
- package/out/renderer/assets/{infoDiagram-PH2N3AL5-DQmemcSD.js → infoDiagram-PH2N3AL5-CmFzwS1O.js} +1 -1
- package/out/renderer/assets/{installer-DHyvaAUM.js → installer-DjcPDTZm.js} +1 -1
- package/out/renderer/assets/{journeyDiagram-U35MCT3I-DDryOZGX.js → journeyDiagram-U35MCT3I-DVz3LHSl.js} +1 -1
- package/out/renderer/assets/{kanban-definition-NDS4AKOZ-sA1o8n3q.js → kanban-definition-NDS4AKOZ-D5k-En4m.js} +1 -1
- package/out/renderer/assets/{layout-D9U1d0RP.js → layout-Hn2ZMp7l.js} +1 -1
- package/out/renderer/assets/{legacy-agents-page-Cq00h9zC.js → legacy-agents-page-CZNoRN6U.js} +1 -1
- package/out/renderer/assets/{mermaid.core-BqieMZ--.js → mermaid.core-Cp1rt_e9.js} +5 -5
- package/out/renderer/assets/{mesh-page-CEjb4jqT.js → mesh-page-CQahM_X_.js} +1 -1
- package/out/renderer/assets/{mindmap-definition-ALO5MXBD-CifupmsT.js → mindmap-definition-ALO5MXBD-cEPXOHRg.js} +1 -1
- package/out/renderer/assets/{mini-DSEfC1pJ.js → mini-Dc6PF53Y.js} +1 -1
- package/out/renderer/assets/{org-surface-gate-DkzI2Q4h.js → org-surface-gate-C1m8ERFA.js} +1 -1
- package/out/renderer/assets/{page-header-BUHGwAKD.js → page-header-BK4_E8_S.js} +1 -1
- package/out/renderer/assets/{pieDiagram-IB7DONF6-DaGaNF0v.js → pieDiagram-IB7DONF6-CNw8zo8M.js} +1 -1
- package/out/renderer/assets/{projects-page-ettaMDRe.js → projects-page-B1NTfBlT.js} +1 -1
- package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-q28L9lP9.js → quadrantDiagram-7GDLP6J5-DUvurVgl.js} +1 -1
- package/out/renderer/assets/{radar-MK3ICKWK-DFslsKkj.js → radar-MK3ICKWK-Bzpnihrv.js} +1 -1
- package/out/renderer/assets/{radient-auth-buttons-ynh6tFGU.js → radient-auth-buttons-Dw-mDyUk.js} +1 -1
- package/out/renderer/assets/{requirementDiagram-KVF5MWMF-B6EzoRWW.js → requirementDiagram-KVF5MWMF-Cz10GD-t.js} +1 -1
- package/out/renderer/assets/{sankeyDiagram-QLVOVGJD-DbxdIZk1.js → sankeyDiagram-QLVOVGJD-DhQ1QL8x.js} +1 -1
- package/out/renderer/assets/{schedules-page-CZ7JG0ZF.js → schedules-page-pGHMkDcb.js} +1 -1
- package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-Ca4yaS0I.js → sequenceDiagram-X6HHIX6F-Dt97gRJT.js} +1 -1
- package/out/renderer/assets/{settings-page-Dm6aGYc2.js → settings-page-DN34EQgz.js} +1 -1
- package/out/renderer/assets/{settings-section-C9gHPe8E.js → settings-section-DbH2vofx.js} +1 -1
- package/out/renderer/assets/{stateDiagram-DGXRK772-efJ-8Anl.js → stateDiagram-DGXRK772-DiHxs26f.js} +1 -1
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-TW6Z0SoU.js +1 -0
- package/out/renderer/assets/{timeline-definition-BDJGKUSR-CLFRcZOl.js → timeline-definition-BDJGKUSR-BeuVfVtI.js} +1 -1
- package/out/renderer/assets/ui-preferences-store-33sdgyd1.js +1 -0
- package/out/renderer/assets/{use-download-agent-mutation-C1eFAun1.js → use-download-agent-mutation-Bs5MsvaK.js} +1 -1
- package/out/renderer/assets/{use-memberships-query-CePUDYPt.js → use-memberships-query-DUTZNAaq.js} +1 -1
- package/out/renderer/assets/{use-session-snapshot-bpgCjX-6.js → use-session-snapshot-U8KyPEV9.js} +1 -1
- package/out/renderer/assets/{xychartDiagram-VJFVF3MP-JS54qlhD.js → xychartDiagram-VJFVF3MP-C81pe8RF.js} +1 -1
- package/out/renderer/console-capture.html +5 -5
- package/out/renderer/index.html +7 -7
- package/out/renderer/installer.html +4 -4
- package/out/renderer/mini.html +7 -7
- package/package.json +1 -1
- package/out/renderer/assets/browser-page-DgCHsQaa.js +0 -1
- package/out/renderer/assets/channel-BOoA2-Le.js +0 -1
- package/out/renderer/assets/classDiagram-GIVACNV2-VLx1ZC8-.js +0 -1
- package/out/renderer/assets/classDiagram-v2-COTLJTTW-VLx1ZC8-.js +0 -1
- package/out/renderer/assets/clone-kic0oeI7.js +0 -1
- package/out/renderer/assets/index-D9v4KyH5.css +0 -1
- package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-BcDiMHGH.js +0 -1
- package/out/renderer/assets/ui-preferences-store-BWxVGilA.js +0 -1
- /package/out/renderer/assets/{index-Gll2cP3b.js → index-DDd7NnXP.js} +0 -0
package/out/main/index.js
CHANGED
|
@@ -18758,6 +18758,8 @@ function deadline(op, ms, what) {
|
|
|
18758
18758
|
}
|
|
18759
18759
|
const PROTOCOL_VERSION = "1.3";
|
|
18760
18760
|
const ALREADY_ATTACHED = /another debugger|already attached/i;
|
|
18761
|
+
const STALE_SESSION = /no session|session with given id|no target|target closed/i;
|
|
18762
|
+
const FRAME_DETACHED = "frame_detached";
|
|
18761
18763
|
class CdpPool {
|
|
18762
18764
|
constructor(options = {}) {
|
|
18763
18765
|
this.options = options;
|
|
@@ -18765,6 +18767,19 @@ class CdpPool {
|
|
|
18765
18767
|
attachments = /* @__PURE__ */ new Map();
|
|
18766
18768
|
/** Per-view CDP event subscribers, by webContents id. See `subscribe`. */
|
|
18767
18769
|
subscribers = /* @__PURE__ */ new Map();
|
|
18770
|
+
/**
|
|
18771
|
+
* Child-frame sessions, by webContents id then frameId (an out-of-process
|
|
18772
|
+
* iframe's frameId IS its targetId — measured, ARCH-1). Attached on demand and
|
|
18773
|
+
* cached rather than auto-attached: a standing `Target.setAutoAttach` attaches
|
|
18774
|
+
* to every ad frame on every driven page and, with `waitForDebuggerOnStart`,
|
|
18775
|
+
* can hang a page whose resume is missed. Dropped on `detachedFromTarget`, on
|
|
18776
|
+
* a stale-session error, and with the page's own attachment — a child session
|
|
18777
|
+
* never outlives its parent.
|
|
18778
|
+
*/
|
|
18779
|
+
frames = /* @__PURE__ */ new Map();
|
|
18780
|
+
/** Attaches in flight, by `<webContentsId>:<frameId>`, shared by concurrent
|
|
18781
|
+
* callers (see `attachFrame`). */
|
|
18782
|
+
frameAttaches = /* @__PURE__ */ new Map();
|
|
18768
18783
|
/**
|
|
18769
18784
|
* Observe this view's CDP events for as long as the returned function has not
|
|
18770
18785
|
* been called.
|
|
@@ -18814,6 +18829,7 @@ class CdpPool {
|
|
|
18814
18829
|
const existing = this.attachments.get(contents.id);
|
|
18815
18830
|
if (existing && existing.detachedReason === null) return;
|
|
18816
18831
|
if (existing) this.releaseListeners(existing);
|
|
18832
|
+
this.frames.delete(contents.id);
|
|
18817
18833
|
if (!contents.getURL()) {
|
|
18818
18834
|
await deadline(
|
|
18819
18835
|
contents.loadURL("about:blank"),
|
|
@@ -18839,7 +18855,14 @@ class CdpPool {
|
|
|
18839
18855
|
);
|
|
18840
18856
|
}
|
|
18841
18857
|
}
|
|
18842
|
-
const messageListener = (_event, method, params) => {
|
|
18858
|
+
const messageListener = (_event, method, params, sessionId2) => {
|
|
18859
|
+
if (sessionId2) return;
|
|
18860
|
+
if (method === "Target.detachedFromTarget") {
|
|
18861
|
+
this.dropFrameSession(
|
|
18862
|
+
contents.id,
|
|
18863
|
+
String(params?.sessionId ?? "")
|
|
18864
|
+
);
|
|
18865
|
+
}
|
|
18843
18866
|
this.routeEvent(
|
|
18844
18867
|
contents.id,
|
|
18845
18868
|
method,
|
|
@@ -18848,6 +18871,7 @@ class CdpPool {
|
|
|
18848
18871
|
};
|
|
18849
18872
|
const detachListener = (_event, reason) => {
|
|
18850
18873
|
attachment.detachedReason = reason || "detached";
|
|
18874
|
+
this.frames.delete(contents.id);
|
|
18851
18875
|
this.options.log?.(
|
|
18852
18876
|
`[browser] debugger detached from view ${contents.id}: ${reason}`
|
|
18853
18877
|
);
|
|
@@ -18911,10 +18935,86 @@ class CdpPool {
|
|
|
18911
18935
|
contents,
|
|
18912
18936
|
method,
|
|
18913
18937
|
params,
|
|
18914
|
-
options.deadlineMs ?? CDP_DEADLINE_MS
|
|
18938
|
+
options.deadlineMs ?? CDP_DEADLINE_MS,
|
|
18939
|
+
options.sessionId
|
|
18915
18940
|
);
|
|
18916
18941
|
}
|
|
18917
|
-
|
|
18942
|
+
/**
|
|
18943
|
+
* The flattened session for one child frame, attached on first use.
|
|
18944
|
+
*
|
|
18945
|
+
* Attached from the PAGE session even for a grandchild (measured: the top
|
|
18946
|
+
* session reaches any frame target by id). Focus emulation goes to the child
|
|
18947
|
+
* for the reason `attach` gives it to the page: `Input.insertText` on an
|
|
18948
|
+
* unfocused target delivers but never replies. `Fetch` is NEVER enabled on a
|
|
18949
|
+
* child: a pause nobody continues would hang the frame.
|
|
18950
|
+
*/
|
|
18951
|
+
async attachFrame(contents, frameId) {
|
|
18952
|
+
const cached2 = this.frames.get(contents.id)?.get(frameId);
|
|
18953
|
+
if (cached2) return cached2;
|
|
18954
|
+
const key = `${contents.id}:${frameId}`;
|
|
18955
|
+
const pending = this.frameAttaches.get(key);
|
|
18956
|
+
if (pending) return pending;
|
|
18957
|
+
const attaching = this.openFrameSession(contents, frameId).finally(() => {
|
|
18958
|
+
this.frameAttaches.delete(key);
|
|
18959
|
+
});
|
|
18960
|
+
this.frameAttaches.set(key, attaching);
|
|
18961
|
+
return attaching;
|
|
18962
|
+
}
|
|
18963
|
+
async openFrameSession(contents, frameId) {
|
|
18964
|
+
let attached;
|
|
18965
|
+
try {
|
|
18966
|
+
attached = await this.send(
|
|
18967
|
+
contents,
|
|
18968
|
+
"Target.attachToTarget",
|
|
18969
|
+
{ targetId: frameId, flatten: true }
|
|
18970
|
+
);
|
|
18971
|
+
} catch (error) {
|
|
18972
|
+
if (error instanceof BridgeCommandError) throw error;
|
|
18973
|
+
throw new BridgeCommandError(
|
|
18974
|
+
"element_not_found",
|
|
18975
|
+
"that frame is gone or still loading; take a new snapshot and retry",
|
|
18976
|
+
{ reason: FRAME_DETACHED }
|
|
18977
|
+
);
|
|
18978
|
+
}
|
|
18979
|
+
const sessionId2 = attached?.sessionId;
|
|
18980
|
+
if (!sessionId2) {
|
|
18981
|
+
throw new BridgeCommandError(
|
|
18982
|
+
"element_not_found",
|
|
18983
|
+
"that frame could not be attached to; take a new snapshot and retry",
|
|
18984
|
+
{ reason: FRAME_DETACHED }
|
|
18985
|
+
);
|
|
18986
|
+
}
|
|
18987
|
+
try {
|
|
18988
|
+
await this.send(
|
|
18989
|
+
contents,
|
|
18990
|
+
"Emulation.setFocusEmulationEnabled",
|
|
18991
|
+
{ enabled: true },
|
|
18992
|
+
{ sessionId: sessionId2 }
|
|
18993
|
+
);
|
|
18994
|
+
} catch (error) {
|
|
18995
|
+
if (!(error instanceof BridgeCommandError && error.data?.reason === FRAME_DETACHED)) {
|
|
18996
|
+
await this.send(contents, "Target.detachFromTarget", {
|
|
18997
|
+
sessionId: sessionId2
|
|
18998
|
+
}).catch(() => void 0);
|
|
18999
|
+
}
|
|
19000
|
+
throw error;
|
|
19001
|
+
}
|
|
19002
|
+
let sessions = this.frames.get(contents.id);
|
|
19003
|
+
if (!sessions) {
|
|
19004
|
+
sessions = /* @__PURE__ */ new Map();
|
|
19005
|
+
this.frames.set(contents.id, sessions);
|
|
19006
|
+
}
|
|
19007
|
+
sessions.set(frameId, sessionId2);
|
|
19008
|
+
return sessionId2;
|
|
19009
|
+
}
|
|
19010
|
+
dropFrameSession(webContentsId, sessionId2) {
|
|
19011
|
+
const sessions = this.frames.get(webContentsId);
|
|
19012
|
+
if (!sessions || !sessionId2) return;
|
|
19013
|
+
for (const [frameId, cached2] of sessions) {
|
|
19014
|
+
if (cached2 === sessionId2) sessions.delete(frameId);
|
|
19015
|
+
}
|
|
19016
|
+
}
|
|
19017
|
+
async sendTo(contents, method, params, deadlineMs, sessionId2) {
|
|
18918
19018
|
const attachment = this.attachments.get(contents.id);
|
|
18919
19019
|
if (!attachment) {
|
|
18920
19020
|
throw new BridgeCommandError(
|
|
@@ -18930,11 +19030,29 @@ class CdpPool {
|
|
|
18930
19030
|
{ reason: attachment.detachedReason }
|
|
18931
19031
|
);
|
|
18932
19032
|
}
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
|
|
18936
|
-
|
|
18937
|
-
|
|
19033
|
+
if (!sessionId2) {
|
|
19034
|
+
return deadline(
|
|
19035
|
+
contents.debugger.sendCommand(method, params),
|
|
19036
|
+
deadlineMs,
|
|
19037
|
+
method
|
|
19038
|
+
);
|
|
19039
|
+
}
|
|
19040
|
+
try {
|
|
19041
|
+
return await deadline(
|
|
19042
|
+
contents.debugger.sendCommand(method, params, sessionId2),
|
|
19043
|
+
deadlineMs,
|
|
19044
|
+
method
|
|
19045
|
+
);
|
|
19046
|
+
} catch (error) {
|
|
19047
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19048
|
+
if (!STALE_SESSION.test(message)) throw error;
|
|
19049
|
+
this.dropFrameSession(contents.id, sessionId2);
|
|
19050
|
+
throw new BridgeCommandError(
|
|
19051
|
+
"element_not_found",
|
|
19052
|
+
"that frame navigated or closed while it was being driven; retry",
|
|
19053
|
+
{ reason: FRAME_DETACHED }
|
|
19054
|
+
);
|
|
19055
|
+
}
|
|
18938
19056
|
}
|
|
18939
19057
|
/** Remove this attachment's debugger listeners. Called before the record is
|
|
18940
19058
|
* replaced by a re-`attach` and before it is dropped, so a view is never left
|
|
@@ -18957,6 +19075,7 @@ class CdpPool {
|
|
|
18957
19075
|
if (attachment) this.releaseListeners(attachment);
|
|
18958
19076
|
this.attachments.delete(webContentsId);
|
|
18959
19077
|
this.subscribers.delete(webContentsId);
|
|
19078
|
+
this.frames.delete(webContentsId);
|
|
18960
19079
|
stopLogCapture(webContentsId);
|
|
18961
19080
|
}
|
|
18962
19081
|
/** Detach cleanly: release the debugger session and the log buffer. */
|
|
@@ -18965,6 +19084,7 @@ class CdpPool {
|
|
|
18965
19084
|
if (attachment) this.releaseListeners(attachment);
|
|
18966
19085
|
this.attachments.delete(webContentsId);
|
|
18967
19086
|
this.subscribers.delete(webContentsId);
|
|
19087
|
+
this.frames.delete(webContentsId);
|
|
18968
19088
|
stopLogCapture(webContentsId);
|
|
18969
19089
|
if (!attachment) return;
|
|
18970
19090
|
try {
|
|
@@ -19080,15 +19200,15 @@ async function withOriginGate(ctx, view, requester, approved, operation) {
|
|
|
19080
19200
|
const contents = view.webContents;
|
|
19081
19201
|
const blocked = [];
|
|
19082
19202
|
const seen = /* @__PURE__ */ new Set();
|
|
19083
|
-
const
|
|
19084
|
-
const mainFrameId =
|
|
19203
|
+
const frameTree2 = await resolveMainFrameId(ctx, contents);
|
|
19204
|
+
const mainFrameId = frameTree2.mainFrameId;
|
|
19085
19205
|
if (mainFrameId === null) {
|
|
19086
19206
|
ctx.log(
|
|
19087
19207
|
`[browser] could not read the frame tree for ${requester}; this navigation is gated on every Document request, which may refuse cross-origin frames the page needs${// The domain's state travels with the fallback because it is not
|
|
19088
19208
|
// inferable from the failure: an enable that resolved and a read that
|
|
19089
19209
|
// did not left `Page` armed, an enable that never resolved did not
|
|
19090
19210
|
// (agent review round 2, R3).
|
|
19091
|
-
|
|
19211
|
+
frameTree2.pageEnabled ? " (the Page domain may still be enabled)" : ""}`
|
|
19092
19212
|
);
|
|
19093
19213
|
}
|
|
19094
19214
|
let unattributed = 0;
|
|
@@ -19160,7 +19280,7 @@ async function withOriginGate(ctx, view, requester, approved, operation) {
|
|
|
19160
19280
|
await ctx.cdp.send(contents, "Fetch.disable", {});
|
|
19161
19281
|
} catch {
|
|
19162
19282
|
}
|
|
19163
|
-
if (
|
|
19283
|
+
if (frameTree2.pageEnabled) {
|
|
19164
19284
|
try {
|
|
19165
19285
|
await ctx.cdp.send(contents, "Page.disable", {});
|
|
19166
19286
|
} catch {
|
|
@@ -19191,6 +19311,399 @@ function pageOf(view) {
|
|
|
19191
19311
|
title: view.webContents.isDestroyed() ? "" : view.webContents.getTitle()
|
|
19192
19312
|
};
|
|
19193
19313
|
}
|
|
19314
|
+
const FRAME_HOP = ">>>";
|
|
19315
|
+
const MAX_FRAME_DEPTH = 3;
|
|
19316
|
+
const MAX_FRAMES_SEARCHED = 20;
|
|
19317
|
+
function sendIn(ctx, contents, sessionId2, method, params) {
|
|
19318
|
+
return sessionId2 ? ctx.cdp.send(contents, method, params, { sessionId: sessionId2 }) : ctx.cdp.send(contents, method, params);
|
|
19319
|
+
}
|
|
19320
|
+
function parseFrameHops(target2) {
|
|
19321
|
+
if (!target2.includes(FRAME_HOP)) return null;
|
|
19322
|
+
const hops = splitTopLevel(target2);
|
|
19323
|
+
if (hops.length === 1) return null;
|
|
19324
|
+
if (hops.some((hop) => hop === "")) {
|
|
19325
|
+
throw new BridgeCommandError(
|
|
19326
|
+
"element_not_found",
|
|
19327
|
+
`${target2} has an empty side of '${FRAME_HOP}'; write it as <iframe-selector> ${FRAME_HOP} <selector>`
|
|
19328
|
+
);
|
|
19329
|
+
}
|
|
19330
|
+
if (hops.length - 1 > MAX_FRAME_DEPTH) {
|
|
19331
|
+
throw new BridgeCommandError(
|
|
19332
|
+
"element_not_found",
|
|
19333
|
+
`${target2} hops through ${hops.length - 1} frames; at most ${MAX_FRAME_DEPTH} are followed`
|
|
19334
|
+
);
|
|
19335
|
+
}
|
|
19336
|
+
return hops;
|
|
19337
|
+
}
|
|
19338
|
+
function splitTopLevel(target2) {
|
|
19339
|
+
const parts = [];
|
|
19340
|
+
let quote = null;
|
|
19341
|
+
let depth = 0;
|
|
19342
|
+
let start = 0;
|
|
19343
|
+
for (let i = 0; i < target2.length; i += 1) {
|
|
19344
|
+
const ch = target2[i];
|
|
19345
|
+
if (ch === "\\") {
|
|
19346
|
+
i += 1;
|
|
19347
|
+
continue;
|
|
19348
|
+
}
|
|
19349
|
+
if (quote) {
|
|
19350
|
+
if (ch === quote) quote = null;
|
|
19351
|
+
continue;
|
|
19352
|
+
}
|
|
19353
|
+
if (ch === '"' || ch === "'") quote = ch;
|
|
19354
|
+
else if (ch === "[" || ch === "(") depth += 1;
|
|
19355
|
+
else if ((ch === "]" || ch === ")") && depth > 0) depth -= 1;
|
|
19356
|
+
else if (depth === 0 && target2.startsWith(FRAME_HOP, i)) {
|
|
19357
|
+
parts.push(target2.slice(start, i).trim());
|
|
19358
|
+
i += FRAME_HOP.length - 1;
|
|
19359
|
+
start = i + 1;
|
|
19360
|
+
}
|
|
19361
|
+
}
|
|
19362
|
+
parts.push(target2.slice(start).trim());
|
|
19363
|
+
return parts;
|
|
19364
|
+
}
|
|
19365
|
+
function assertFrameNotDenied(ctx, scope) {
|
|
19366
|
+
if (!isDeniedFrame(ctx, scope)) return;
|
|
19367
|
+
throw new BridgeCommandError(
|
|
19368
|
+
"origin_not_allowed",
|
|
19369
|
+
`the user denied ${scope.origin} for agent access, and that element is inside a frame from it`,
|
|
19370
|
+
{ origin: scope.origin, reason: "denied" }
|
|
19371
|
+
);
|
|
19372
|
+
}
|
|
19373
|
+
function isDeniedRefusal(error) {
|
|
19374
|
+
return error instanceof BridgeCommandError && error.code === "origin_not_allowed" && error.data?.reason === "denied";
|
|
19375
|
+
}
|
|
19376
|
+
function isDeniedFrame(ctx, scope) {
|
|
19377
|
+
if (!scope.origin) return false;
|
|
19378
|
+
let url;
|
|
19379
|
+
try {
|
|
19380
|
+
url = new URL(scope.origin);
|
|
19381
|
+
} catch {
|
|
19382
|
+
return false;
|
|
19383
|
+
}
|
|
19384
|
+
return ctx.approvals?.refusalReason(url) === "denied";
|
|
19385
|
+
}
|
|
19386
|
+
function isFatalWalkError(error) {
|
|
19387
|
+
return error instanceof BridgeCommandError && (error.code === "debugger_conflict" || error.code === "tab_closed");
|
|
19388
|
+
}
|
|
19389
|
+
async function topScope(ctx, contents) {
|
|
19390
|
+
const document = await ctx.cdp.send(
|
|
19391
|
+
contents,
|
|
19392
|
+
"DOM.getDocument",
|
|
19393
|
+
{ depth: 0 }
|
|
19394
|
+
);
|
|
19395
|
+
const rootNodeId = document?.root?.nodeId;
|
|
19396
|
+
if (rootNodeId === void 0) {
|
|
19397
|
+
throw new BridgeCommandError("internal", "the page has no document to query");
|
|
19398
|
+
}
|
|
19399
|
+
return { rootNodeId, hops: [], path: [] };
|
|
19400
|
+
}
|
|
19401
|
+
async function queryIn(ctx, contents, scope, selector) {
|
|
19402
|
+
const queried = await sendIn(
|
|
19403
|
+
ctx,
|
|
19404
|
+
contents,
|
|
19405
|
+
scope.sessionId,
|
|
19406
|
+
"DOM.querySelector",
|
|
19407
|
+
{ nodeId: scope.rootNodeId, selector }
|
|
19408
|
+
);
|
|
19409
|
+
return queried?.nodeId ?? 0;
|
|
19410
|
+
}
|
|
19411
|
+
const FRAME_NODE_NAMES = /* @__PURE__ */ new Set(["IFRAME", "FRAME"]);
|
|
19412
|
+
function originOf(url) {
|
|
19413
|
+
if (!url) return void 0;
|
|
19414
|
+
try {
|
|
19415
|
+
return new URL(url).origin;
|
|
19416
|
+
} catch {
|
|
19417
|
+
return void 0;
|
|
19418
|
+
}
|
|
19419
|
+
}
|
|
19420
|
+
const PLAIN_ID = /^[A-Za-z_][\w-]*$/;
|
|
19421
|
+
function frameSelectorFor(attributes = []) {
|
|
19422
|
+
const attrs = /* @__PURE__ */ new Map();
|
|
19423
|
+
for (let i = 0; i + 1 < attributes.length; i += 2) {
|
|
19424
|
+
attrs.set(attributes[i], attributes[i + 1]);
|
|
19425
|
+
}
|
|
19426
|
+
const id2 = attrs.get("id");
|
|
19427
|
+
if (id2 && PLAIN_ID.test(id2)) return `iframe#${id2}`;
|
|
19428
|
+
for (const key of ["name", "title", "src"]) {
|
|
19429
|
+
const value = attrs.get(key);
|
|
19430
|
+
if (value) return `iframe[${key}=${JSON.stringify(value)}]`;
|
|
19431
|
+
}
|
|
19432
|
+
return "iframe";
|
|
19433
|
+
}
|
|
19434
|
+
async function enterFrame(ctx, contents, parent, iframeNodeId) {
|
|
19435
|
+
const described = await sendIn(
|
|
19436
|
+
ctx,
|
|
19437
|
+
contents,
|
|
19438
|
+
parent.sessionId,
|
|
19439
|
+
"DOM.describeNode",
|
|
19440
|
+
{ nodeId: iframeNodeId, depth: 1 }
|
|
19441
|
+
);
|
|
19442
|
+
const node = described?.node;
|
|
19443
|
+
if (!node || !FRAME_NODE_NAMES.has(String(node.nodeName).toUpperCase())) {
|
|
19444
|
+
throw new BridgeCommandError(
|
|
19445
|
+
"element_not_found",
|
|
19446
|
+
"that element is not an iframe, so there is no frame to step into",
|
|
19447
|
+
{ reason: NOT_A_FRAME }
|
|
19448
|
+
);
|
|
19449
|
+
}
|
|
19450
|
+
const hops = [
|
|
19451
|
+
...parent.hops,
|
|
19452
|
+
{ sessionId: parent.sessionId, nodeId: iframeNodeId }
|
|
19453
|
+
];
|
|
19454
|
+
const path2 = [...parent.path, frameSelectorFor(node.attributes)];
|
|
19455
|
+
const inProcess = node.contentDocument?.backendNodeId;
|
|
19456
|
+
if (inProcess !== void 0) {
|
|
19457
|
+
assertFrameNotDenied(ctx, {
|
|
19458
|
+
origin: originOf(node.contentDocument?.documentURL)
|
|
19459
|
+
});
|
|
19460
|
+
const pushed = await sendIn(
|
|
19461
|
+
ctx,
|
|
19462
|
+
contents,
|
|
19463
|
+
parent.sessionId,
|
|
19464
|
+
"DOM.pushNodesByBackendIdsToFrontend",
|
|
19465
|
+
{ backendNodeIds: [inProcess] }
|
|
19466
|
+
);
|
|
19467
|
+
const rootNodeId = pushed?.nodeIds?.[0];
|
|
19468
|
+
if (!rootNodeId) {
|
|
19469
|
+
throw new BridgeCommandError(
|
|
19470
|
+
"element_not_found",
|
|
19471
|
+
"that frame has no document yet; retry once it has loaded"
|
|
19472
|
+
);
|
|
19473
|
+
}
|
|
19474
|
+
return {
|
|
19475
|
+
sessionId: parent.sessionId,
|
|
19476
|
+
frameTargetId: parent.frameTargetId,
|
|
19477
|
+
frameId: node.frameId,
|
|
19478
|
+
rootNodeId,
|
|
19479
|
+
origin: originOf(node.contentDocument?.documentURL),
|
|
19480
|
+
hops,
|
|
19481
|
+
path: path2
|
|
19482
|
+
};
|
|
19483
|
+
}
|
|
19484
|
+
const frameId = node.frameId;
|
|
19485
|
+
if (!frameId) {
|
|
19486
|
+
throw new BridgeCommandError(
|
|
19487
|
+
"element_not_found",
|
|
19488
|
+
"that frame has no document yet; retry once it has loaded"
|
|
19489
|
+
);
|
|
19490
|
+
}
|
|
19491
|
+
const rooted = await childRoot(ctx, contents, frameId);
|
|
19492
|
+
assertFrameNotDenied(ctx, { origin: rooted.origin });
|
|
19493
|
+
return {
|
|
19494
|
+
sessionId: rooted.sessionId,
|
|
19495
|
+
frameTargetId: frameId,
|
|
19496
|
+
frameId,
|
|
19497
|
+
rootNodeId: rooted.rootNodeId,
|
|
19498
|
+
origin: rooted.origin,
|
|
19499
|
+
hops,
|
|
19500
|
+
path: path2
|
|
19501
|
+
};
|
|
19502
|
+
}
|
|
19503
|
+
async function childRoot(ctx, contents, frameId) {
|
|
19504
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
19505
|
+
try {
|
|
19506
|
+
const sessionId2 = await ctx.cdp.attachFrame(contents, frameId);
|
|
19507
|
+
const document = await ctx.cdp.send(contents, "DOM.getDocument", { depth: 0 }, { sessionId: sessionId2 });
|
|
19508
|
+
const rootNodeId = document?.root?.nodeId;
|
|
19509
|
+
if (rootNodeId === void 0) {
|
|
19510
|
+
throw new BridgeCommandError(
|
|
19511
|
+
"element_not_found",
|
|
19512
|
+
"that frame has no document yet; retry once it has loaded"
|
|
19513
|
+
);
|
|
19514
|
+
}
|
|
19515
|
+
return {
|
|
19516
|
+
sessionId: sessionId2,
|
|
19517
|
+
rootNodeId,
|
|
19518
|
+
origin: originOf(document?.root?.documentURL)
|
|
19519
|
+
};
|
|
19520
|
+
} catch (error) {
|
|
19521
|
+
if (attempt === 0 && isFrameDetached(error)) continue;
|
|
19522
|
+
throw error;
|
|
19523
|
+
}
|
|
19524
|
+
}
|
|
19525
|
+
}
|
|
19526
|
+
const NOT_A_FRAME = "not_a_frame";
|
|
19527
|
+
function isNotAFrame(error) {
|
|
19528
|
+
return error instanceof BridgeCommandError && error.data?.reason === NOT_A_FRAME;
|
|
19529
|
+
}
|
|
19530
|
+
function isFrameDetached(error) {
|
|
19531
|
+
return error instanceof BridgeCommandError && error.data?.reason === FRAME_DETACHED;
|
|
19532
|
+
}
|
|
19533
|
+
async function resolveHops(ctx, contents, hops) {
|
|
19534
|
+
let scope = await topScope(ctx, contents);
|
|
19535
|
+
for (const hop of hops.slice(0, -1)) {
|
|
19536
|
+
const iframe = await queryIn(ctx, contents, scope, hop);
|
|
19537
|
+
if (!iframe) {
|
|
19538
|
+
throw new BridgeCommandError(
|
|
19539
|
+
"element_not_found",
|
|
19540
|
+
`frame selector ${hop} matched nothing`
|
|
19541
|
+
);
|
|
19542
|
+
}
|
|
19543
|
+
scope = await enterFrame(ctx, contents, scope, iframe).catch((error) => {
|
|
19544
|
+
if (isNotAFrame(error)) {
|
|
19545
|
+
throw new BridgeCommandError(
|
|
19546
|
+
"element_not_found",
|
|
19547
|
+
`${hop} is not an iframe, so '${FRAME_HOP}' cannot step into it`
|
|
19548
|
+
);
|
|
19549
|
+
}
|
|
19550
|
+
throw error;
|
|
19551
|
+
});
|
|
19552
|
+
}
|
|
19553
|
+
const selector = hops[hops.length - 1];
|
|
19554
|
+
const nodeId = await queryIn(ctx, contents, scope, selector);
|
|
19555
|
+
if (!nodeId) {
|
|
19556
|
+
throw new BridgeCommandError(
|
|
19557
|
+
"element_not_found",
|
|
19558
|
+
`selector ${selector} matched nothing inside ${hops.slice(0, -1).join(` ${FRAME_HOP} `)}`
|
|
19559
|
+
);
|
|
19560
|
+
}
|
|
19561
|
+
return { scope, nodeId };
|
|
19562
|
+
}
|
|
19563
|
+
async function searchFrames(ctx, contents, selector) {
|
|
19564
|
+
const matches = [];
|
|
19565
|
+
const walk = await listFrames(ctx, contents);
|
|
19566
|
+
for (const scope of walk.frames) {
|
|
19567
|
+
const nodeId = await queryIn(ctx, contents, scope, selector).catch(
|
|
19568
|
+
(error) => {
|
|
19569
|
+
if (isFatalWalkError(error)) throw error;
|
|
19570
|
+
return 0;
|
|
19571
|
+
}
|
|
19572
|
+
);
|
|
19573
|
+
if (nodeId) matches.push({ scope, nodeId });
|
|
19574
|
+
}
|
|
19575
|
+
return { matches, walk };
|
|
19576
|
+
}
|
|
19577
|
+
async function listFrames(ctx, contents) {
|
|
19578
|
+
const walk = {
|
|
19579
|
+
frames: [],
|
|
19580
|
+
truncated: false,
|
|
19581
|
+
skipped: 0,
|
|
19582
|
+
denied: 0
|
|
19583
|
+
};
|
|
19584
|
+
let level = [await topScope(ctx, contents)];
|
|
19585
|
+
for (let depth = 0; depth < MAX_FRAME_DEPTH && level.length; depth += 1) {
|
|
19586
|
+
const next = [];
|
|
19587
|
+
for (const parent of level) {
|
|
19588
|
+
const listed = await sendIn(
|
|
19589
|
+
ctx,
|
|
19590
|
+
contents,
|
|
19591
|
+
parent.sessionId,
|
|
19592
|
+
"DOM.querySelectorAll",
|
|
19593
|
+
{ nodeId: parent.rootNodeId, selector: "iframe, frame" }
|
|
19594
|
+
);
|
|
19595
|
+
for (const iframe of listed?.nodeIds ?? []) {
|
|
19596
|
+
if (walk.frames.length + walk.skipped >= MAX_FRAMES_SEARCHED) {
|
|
19597
|
+
walk.truncated = true;
|
|
19598
|
+
return walk;
|
|
19599
|
+
}
|
|
19600
|
+
let scope;
|
|
19601
|
+
try {
|
|
19602
|
+
scope = await enterFrame(ctx, contents, parent, iframe);
|
|
19603
|
+
} catch (error) {
|
|
19604
|
+
if (isFatalWalkError(error)) throw error;
|
|
19605
|
+
if (isDeniedRefusal(error)) {
|
|
19606
|
+
walk.denied += 1;
|
|
19607
|
+
continue;
|
|
19608
|
+
}
|
|
19609
|
+
if (error instanceof BridgeCommandError) {
|
|
19610
|
+
walk.skipped += 1;
|
|
19611
|
+
continue;
|
|
19612
|
+
}
|
|
19613
|
+
throw error;
|
|
19614
|
+
}
|
|
19615
|
+
walk.frames.push(scope);
|
|
19616
|
+
next.push(scope);
|
|
19617
|
+
}
|
|
19618
|
+
}
|
|
19619
|
+
level = next;
|
|
19620
|
+
}
|
|
19621
|
+
return walk;
|
|
19622
|
+
}
|
|
19623
|
+
function candidatePath(scope, selector) {
|
|
19624
|
+
return [...scope.path, selector].join(` ${FRAME_HOP} `);
|
|
19625
|
+
}
|
|
19626
|
+
const EDITABLE_SELECTOR = 'input:not([type=hidden]):not([type=button]):not([type=submit]):not([type=reset]):not([type=checkbox]):not([type=radio]):not([type=file]):not([type=image]):not([type=range]):not([type=color]):not([disabled]):not([readonly]), textarea:not([disabled]):not([readonly]), [contenteditable]:not([contenteditable="false"])';
|
|
19627
|
+
const FRAME_EDITABLE_FUNCTION_SOURCE = `function (selector, mode) {
|
|
19628
|
+
const all = Array.from(this.querySelectorAll(selector));
|
|
19629
|
+
if (mode === 'active') {
|
|
19630
|
+
const active = this.activeElement;
|
|
19631
|
+
return active && all.indexOf(active) !== -1 ? active : null;
|
|
19632
|
+
}
|
|
19633
|
+
if (mode === 'only') return all.length === 1 ? all[0] : null;
|
|
19634
|
+
return all.map(function (el) {
|
|
19635
|
+
const tag = String(el.tagName || '').toLowerCase();
|
|
19636
|
+
if (el.id && /^[A-Za-z_][\\w-]*$/.test(el.id)) return '#' + el.id;
|
|
19637
|
+
const name = el.getAttribute && el.getAttribute('name');
|
|
19638
|
+
if (name) return tag + '[name=' + JSON.stringify(name) + ']';
|
|
19639
|
+
// A real selector, not a label: the n-th element of its tag under its parent,
|
|
19640
|
+
// walked up to the document so the path is unique and addressable.
|
|
19641
|
+
const steps = [];
|
|
19642
|
+
for (let node = el; node && node.nodeType === 1 && node.tagName; node = node.parentElement) {
|
|
19643
|
+
const name2 = String(node.tagName).toLowerCase();
|
|
19644
|
+
let nth = 1;
|
|
19645
|
+
for (let sib = node.previousElementSibling; sib; sib = sib.previousElementSibling) {
|
|
19646
|
+
if (sib.tagName === node.tagName) nth += 1;
|
|
19647
|
+
}
|
|
19648
|
+
steps.unshift(name2 + ':nth-of-type(' + nth + ')');
|
|
19649
|
+
if (name2 === 'html') break;
|
|
19650
|
+
}
|
|
19651
|
+
return steps.length ? steps.join(' > ') : tag;
|
|
19652
|
+
});
|
|
19653
|
+
}`;
|
|
19654
|
+
async function editableInFrame(ctx, contents, scope, target2) {
|
|
19655
|
+
const documentObject = await sendIn(
|
|
19656
|
+
ctx,
|
|
19657
|
+
contents,
|
|
19658
|
+
scope.sessionId,
|
|
19659
|
+
"DOM.resolveNode",
|
|
19660
|
+
{ nodeId: scope.rootNodeId }
|
|
19661
|
+
);
|
|
19662
|
+
const documentId = documentObject?.object?.objectId;
|
|
19663
|
+
if (!documentId) {
|
|
19664
|
+
throw new BridgeCommandError(
|
|
19665
|
+
"element_not_found",
|
|
19666
|
+
`could not read the document inside ${target2}`
|
|
19667
|
+
);
|
|
19668
|
+
}
|
|
19669
|
+
const pick = (mode, returnByValue) => sendIn(
|
|
19670
|
+
ctx,
|
|
19671
|
+
contents,
|
|
19672
|
+
scope.sessionId,
|
|
19673
|
+
"Runtime.callFunctionOn",
|
|
19674
|
+
{
|
|
19675
|
+
objectId: documentId,
|
|
19676
|
+
functionDeclaration: FRAME_EDITABLE_FUNCTION_SOURCE,
|
|
19677
|
+
arguments: [{ value: EDITABLE_SELECTOR }, { value: mode }],
|
|
19678
|
+
returnByValue
|
|
19679
|
+
}
|
|
19680
|
+
);
|
|
19681
|
+
let objectId = (await pick("active", false))?.result?.objectId;
|
|
19682
|
+
if (!objectId) objectId = (await pick("only", false))?.result?.objectId;
|
|
19683
|
+
if (!objectId) {
|
|
19684
|
+
const listed = (await pick("list", true))?.result?.value;
|
|
19685
|
+
const fields = Array.isArray(listed) ? listed.map(String) : [];
|
|
19686
|
+
if (fields.length === 0) {
|
|
19687
|
+
throw new BridgeCommandError(
|
|
19688
|
+
"element_not_found",
|
|
19689
|
+
`${target2} is not an editable field (no value setter, not contenteditable), so nothing was typed; it is an iframe and its document holds no editable field either`
|
|
19690
|
+
);
|
|
19691
|
+
}
|
|
19692
|
+
throw new BridgeCommandError(
|
|
19693
|
+
"element_not_found",
|
|
19694
|
+
`${target2} is an iframe holding ${fields.length} editable fields, so nothing was typed; name one: ${fields.map((field) => candidatePath(scope, field)).join(", ")}`,
|
|
19695
|
+
{ candidates: fields.map((field) => candidatePath(scope, field)) }
|
|
19696
|
+
);
|
|
19697
|
+
}
|
|
19698
|
+
const requested = await sendIn(
|
|
19699
|
+
ctx,
|
|
19700
|
+
contents,
|
|
19701
|
+
scope.sessionId,
|
|
19702
|
+
"DOM.requestNode",
|
|
19703
|
+
{ objectId }
|
|
19704
|
+
);
|
|
19705
|
+
return { nodeId: requested?.nodeId ?? 0, objectId };
|
|
19706
|
+
}
|
|
19194
19707
|
const LANDMARKS = /* @__PURE__ */ new Set(["banner", "main", "navigation", "complementary", "contentinfo", "form", "region"]);
|
|
19195
19708
|
const INTERACTIVE = /* @__PURE__ */ new Set(["button", "link", "textbox", "checkbox", "radio", "combobox", "menuitem", "tab", "switch", "slider", "spinbutton"]);
|
|
19196
19709
|
function compactAX(nodes, epoch) {
|
|
@@ -19321,7 +19834,16 @@ async function snapshot(ctx, params) {
|
|
|
19321
19834
|
await ctx.cdp.send(contents, "Accessibility.disable", {}).catch(() => {
|
|
19322
19835
|
});
|
|
19323
19836
|
}
|
|
19324
|
-
const { snapshot:
|
|
19837
|
+
const { snapshot: topRendered, refs: topRefs } = compactAX(
|
|
19838
|
+
nodes,
|
|
19839
|
+
record.epoch
|
|
19840
|
+
);
|
|
19841
|
+
const refs = { ...topRefs };
|
|
19842
|
+
let rendered = topRendered;
|
|
19843
|
+
if (nodes.some(isFrameNode)) {
|
|
19844
|
+
const blocks = await frameSnapshots(ctx, record, nodes, refs);
|
|
19845
|
+
if (blocks.length) rendered = [rendered, ...blocks].join("\n");
|
|
19846
|
+
}
|
|
19325
19847
|
record.refs = refs;
|
|
19326
19848
|
ctx.registry.touch(record);
|
|
19327
19849
|
return {
|
|
@@ -19331,6 +19853,88 @@ async function snapshot(ctx, params) {
|
|
|
19331
19853
|
...pageOf(record.view)
|
|
19332
19854
|
};
|
|
19333
19855
|
}
|
|
19856
|
+
function isFrameNode(node) {
|
|
19857
|
+
return node.role?.value === "Iframe" && node.backendDOMNodeId !== void 0;
|
|
19858
|
+
}
|
|
19859
|
+
const REF_LABEL = /\[e(\d+)\]/g;
|
|
19860
|
+
async function frameSnapshots(ctx, record, topNodes, refs) {
|
|
19861
|
+
const contents = record.view.webContents;
|
|
19862
|
+
const blocks = [];
|
|
19863
|
+
let entered = 0;
|
|
19864
|
+
let level = [
|
|
19865
|
+
{ scope: await topScope(ctx, contents), nodes: topNodes }
|
|
19866
|
+
];
|
|
19867
|
+
for (let depth = 0; depth < MAX_FRAME_DEPTH && level.length; depth += 1) {
|
|
19868
|
+
const next = [];
|
|
19869
|
+
for (const { scope: parent, nodes } of level) {
|
|
19870
|
+
for (const frameNode of nodes.filter(isFrameNode)) {
|
|
19871
|
+
if (entered >= MAX_FRAMES_SEARCHED) return blocks;
|
|
19872
|
+
entered += 1;
|
|
19873
|
+
try {
|
|
19874
|
+
const pushed = await sendIn(
|
|
19875
|
+
ctx,
|
|
19876
|
+
contents,
|
|
19877
|
+
parent.sessionId,
|
|
19878
|
+
"DOM.pushNodesByBackendIdsToFrontend",
|
|
19879
|
+
{ backendNodeIds: [frameNode.backendDOMNodeId] }
|
|
19880
|
+
);
|
|
19881
|
+
const iframeNodeId = pushed?.nodeIds?.[0];
|
|
19882
|
+
if (!iframeNodeId) continue;
|
|
19883
|
+
const scope = await enterFrame(ctx, contents, parent, iframeNodeId);
|
|
19884
|
+
const frameNodes = await frameTree(ctx, contents, scope);
|
|
19885
|
+
const { snapshot: inner, refs: innerRefs } = compactAX(
|
|
19886
|
+
frameNodes,
|
|
19887
|
+
record.epoch
|
|
19888
|
+
);
|
|
19889
|
+
const offset = Object.keys(refs).length;
|
|
19890
|
+
for (const [name, ref] of Object.entries(innerRefs)) {
|
|
19891
|
+
refs[`e${Number(name.slice(1)) + offset}`] = {
|
|
19892
|
+
...ref,
|
|
19893
|
+
...scope.frameTargetId ? { frameId: scope.frameTargetId } : {},
|
|
19894
|
+
frameOrigin: scope.origin ?? ""
|
|
19895
|
+
};
|
|
19896
|
+
}
|
|
19897
|
+
const indent = " ".repeat(depth);
|
|
19898
|
+
const body = inner.replace(REF_LABEL, (_m, n) => `[e${Number(n) + offset}]`).split("\n").filter(Boolean).map((line) => `${indent} ${line}`);
|
|
19899
|
+
blocks.push(
|
|
19900
|
+
`${indent}- frame ${scope.path.join(` ${FRAME_HOP} `)}${scope.origin ? ` (${scope.origin})` : ""}:`,
|
|
19901
|
+
...body
|
|
19902
|
+
);
|
|
19903
|
+
next.push({ scope, nodes: frameNodes });
|
|
19904
|
+
} catch (error) {
|
|
19905
|
+
if (error instanceof BridgeCommandError && error.code !== "debugger_conflict" && error.code !== "tab_closed")
|
|
19906
|
+
continue;
|
|
19907
|
+
throw error;
|
|
19908
|
+
}
|
|
19909
|
+
}
|
|
19910
|
+
}
|
|
19911
|
+
level = next;
|
|
19912
|
+
}
|
|
19913
|
+
return blocks;
|
|
19914
|
+
}
|
|
19915
|
+
async function frameTree(ctx, contents, scope) {
|
|
19916
|
+
const params = scope.frameTargetId === scope.frameId || !scope.frameId ? {} : { frameId: scope.frameId };
|
|
19917
|
+
await sendIn(ctx, contents, scope.sessionId, "Accessibility.enable", {});
|
|
19918
|
+
try {
|
|
19919
|
+
const tree = await sendIn(
|
|
19920
|
+
ctx,
|
|
19921
|
+
contents,
|
|
19922
|
+
scope.sessionId,
|
|
19923
|
+
"Accessibility.getFullAXTree",
|
|
19924
|
+
params
|
|
19925
|
+
);
|
|
19926
|
+
return Array.isArray(tree?.nodes) ? tree.nodes : [];
|
|
19927
|
+
} finally {
|
|
19928
|
+
await sendIn(
|
|
19929
|
+
ctx,
|
|
19930
|
+
contents,
|
|
19931
|
+
scope.sessionId,
|
|
19932
|
+
"Accessibility.disable",
|
|
19933
|
+
{}
|
|
19934
|
+
).catch(() => {
|
|
19935
|
+
});
|
|
19936
|
+
}
|
|
19937
|
+
}
|
|
19334
19938
|
const METRICS_EXPRESSION = `(() => {
|
|
19335
19939
|
const de = document.scrollingElement || document.documentElement;
|
|
19336
19940
|
const x = window.scrollX, y = window.scrollY;
|
|
@@ -19550,11 +20154,13 @@ const READ_VALUE_FUNCTION = `function () {
|
|
|
19550
20154
|
return String(this.textContent || '');
|
|
19551
20155
|
}`;
|
|
19552
20156
|
const SNAPSHOT_REF = /^e\d+$/;
|
|
20157
|
+
function frameFields(node) {
|
|
20158
|
+
return node.frame ? { frame_origin: node.frame.origin ?? "" } : {};
|
|
20159
|
+
}
|
|
19553
20160
|
async function resolveNode(ctx, record, target2) {
|
|
19554
20161
|
const view = record.view;
|
|
19555
20162
|
const contents = view.webContents;
|
|
19556
20163
|
const ref = SNAPSHOT_REF.test(target2) ? record.refs[target2] : void 0;
|
|
19557
|
-
let nodeId;
|
|
19558
20164
|
if (ref) {
|
|
19559
20165
|
if (ref.epoch !== record.epoch) {
|
|
19560
20166
|
throw new BridgeCommandError(
|
|
@@ -19562,6 +20168,7 @@ async function resolveNode(ctx, record, target2) {
|
|
|
19562
20168
|
"the page navigated since that snapshot; take a new snapshot and retry"
|
|
19563
20169
|
);
|
|
19564
20170
|
}
|
|
20171
|
+
if (ref.frameId) return resolveFrameRef(ctx, contents, target2, ref);
|
|
19565
20172
|
await ctx.cdp.send(contents, "DOM.getDocument", { depth: 0 });
|
|
19566
20173
|
const pushed = await ctx.cdp.send(
|
|
19567
20174
|
contents,
|
|
@@ -19572,10 +20179,72 @@ async function resolveNode(ctx, record, target2) {
|
|
|
19572
20179
|
if (pushedId === void 0) {
|
|
19573
20180
|
throw new BridgeCommandError("element_not_found", "snapshot ref is stale");
|
|
19574
20181
|
}
|
|
19575
|
-
|
|
19576
|
-
}
|
|
20182
|
+
return finishTop(ctx, contents, target2, pushedId, ref.frameOrigin);
|
|
20183
|
+
}
|
|
20184
|
+
const hops = parseFrameHops(target2);
|
|
20185
|
+
if (hops) {
|
|
20186
|
+
const hit = await resolveHops(ctx, contents, hops);
|
|
20187
|
+
return finishInFrame(ctx, contents, target2, hit.scope, hit.nodeId);
|
|
20188
|
+
}
|
|
20189
|
+
let nodeId;
|
|
20190
|
+
try {
|
|
19577
20191
|
nodeId = await nodeIdForSelector(ctx, view, target2);
|
|
20192
|
+
} catch (error) {
|
|
20193
|
+
if (!(error instanceof BridgeCommandError) || !TOP_MISS.test(error.message)) {
|
|
20194
|
+
throw error;
|
|
20195
|
+
}
|
|
20196
|
+
const { matches, walk } = await searchFrames(ctx, contents, target2);
|
|
20197
|
+
if (matches.length === 0) {
|
|
20198
|
+
if (!walk.truncated && walk.skipped === 0 && walk.denied === 0)
|
|
20199
|
+
throw error;
|
|
20200
|
+
throw new BridgeCommandError(
|
|
20201
|
+
"element_not_found",
|
|
20202
|
+
`selector ${target2} matched nothing in the page itself or the frames searched (${walkNote(walk)}); address the frame explicitly as <iframe-selector> ${FRAME_HOP} <selector>`
|
|
20203
|
+
);
|
|
20204
|
+
}
|
|
20205
|
+
if (matches.length === 1 && (walk.truncated || walk.skipped > 0)) {
|
|
20206
|
+
const candidate = candidatePath(matches[0].scope, target2);
|
|
20207
|
+
throw new BridgeCommandError(
|
|
20208
|
+
"element_not_found",
|
|
20209
|
+
`selector ${target2} matched nothing in the page itself and one element in the frames searched, but not every frame was searched (${walkNote(walk)}), so nothing was done; if this is the one, name it: ${candidate}`,
|
|
20210
|
+
{ candidates: [candidate] }
|
|
20211
|
+
);
|
|
20212
|
+
}
|
|
20213
|
+
if (matches.length > 1) {
|
|
20214
|
+
const candidates = matches.map(
|
|
20215
|
+
(match) => candidatePath(match.scope, target2)
|
|
20216
|
+
);
|
|
20217
|
+
throw new BridgeCommandError(
|
|
20218
|
+
"element_not_found",
|
|
20219
|
+
`selector ${target2} matched nothing in the page itself and ${matches.length} elements inside its frames, so nothing was done; name one: ${candidates.join(", ")}`,
|
|
20220
|
+
{ candidates }
|
|
20221
|
+
);
|
|
20222
|
+
}
|
|
20223
|
+
return finishInFrame(
|
|
20224
|
+
ctx,
|
|
20225
|
+
contents,
|
|
20226
|
+
target2,
|
|
20227
|
+
matches[0].scope,
|
|
20228
|
+
matches[0].nodeId
|
|
20229
|
+
);
|
|
19578
20230
|
}
|
|
20231
|
+
return finishTop(ctx, contents, target2, nodeId);
|
|
20232
|
+
}
|
|
20233
|
+
function walkNote(walk) {
|
|
20234
|
+
const notes = [];
|
|
20235
|
+
if (walk.truncated) notes.push(`stopped at ${MAX_FRAMES_SEARCHED} frames`);
|
|
20236
|
+
if (walk.skipped > 0)
|
|
20237
|
+
notes.push(`${walk.skipped} frame(s) could not be entered`);
|
|
20238
|
+
if (walk.denied > 0)
|
|
20239
|
+
notes.push(
|
|
20240
|
+
`${walk.denied} frame(s) from an origin the user denied were not searched`
|
|
20241
|
+
);
|
|
20242
|
+
return notes.join("; ") || "every frame searched";
|
|
20243
|
+
}
|
|
20244
|
+
const TOP_MISS = /^selector .* matched nothing$/s;
|
|
20245
|
+
async function finishTop(ctx, contents, target2, nodeId, frameOrigin) {
|
|
20246
|
+
if (frameOrigin !== void 0)
|
|
20247
|
+
assertFrameNotDenied(ctx, { origin: frameOrigin });
|
|
19579
20248
|
await ctx.cdp.send(contents, "DOM.scrollIntoViewIfNeeded", { nodeId });
|
|
19580
20249
|
const resolved = await ctx.cdp.send(
|
|
19581
20250
|
contents,
|
|
@@ -19589,7 +20258,83 @@ async function resolveNode(ctx, record, target2) {
|
|
|
19589
20258
|
`could not resolve ${target2}`
|
|
19590
20259
|
);
|
|
19591
20260
|
}
|
|
19592
|
-
return { nodeId, objectId };
|
|
20261
|
+
if (frameOrigin === void 0) return { nodeId, objectId };
|
|
20262
|
+
return {
|
|
20263
|
+
nodeId,
|
|
20264
|
+
objectId,
|
|
20265
|
+
frame: { rootNodeId: 0, origin: frameOrigin, hops: [], path: [] }
|
|
20266
|
+
};
|
|
20267
|
+
}
|
|
20268
|
+
async function finishInFrame(ctx, contents, target2, scope, nodeId) {
|
|
20269
|
+
assertFrameNotDenied(ctx, scope);
|
|
20270
|
+
for (const hop of scope.hops) {
|
|
20271
|
+
await sendIn(ctx, contents, hop.sessionId, "DOM.scrollIntoViewIfNeeded", {
|
|
20272
|
+
nodeId: hop.nodeId
|
|
20273
|
+
}).catch(() => void 0);
|
|
20274
|
+
}
|
|
20275
|
+
await sendIn(ctx, contents, scope.sessionId, "DOM.scrollIntoViewIfNeeded", {
|
|
20276
|
+
nodeId
|
|
20277
|
+
}).catch(() => void 0);
|
|
20278
|
+
const resolved = await sendIn(
|
|
20279
|
+
ctx,
|
|
20280
|
+
contents,
|
|
20281
|
+
scope.sessionId,
|
|
20282
|
+
"DOM.resolveNode",
|
|
20283
|
+
{ nodeId }
|
|
20284
|
+
);
|
|
20285
|
+
const objectId = resolved?.object?.objectId;
|
|
20286
|
+
if (!objectId) {
|
|
20287
|
+
throw new BridgeCommandError(
|
|
20288
|
+
"element_not_found",
|
|
20289
|
+
`could not resolve ${target2}`
|
|
20290
|
+
);
|
|
20291
|
+
}
|
|
20292
|
+
return { nodeId, objectId, sessionId: scope.sessionId, frame: scope };
|
|
20293
|
+
}
|
|
20294
|
+
async function resolveFrameRef(ctx, contents, target2, ref) {
|
|
20295
|
+
assertFrameNotDenied(ctx, { origin: ref.frameOrigin });
|
|
20296
|
+
let rooted;
|
|
20297
|
+
try {
|
|
20298
|
+
rooted = await childRoot(ctx, contents, String(ref.frameId));
|
|
20299
|
+
} catch (error) {
|
|
20300
|
+
if (isFrameDetached(error)) {
|
|
20301
|
+
throw new BridgeCommandError("element_not_found", "snapshot ref is stale");
|
|
20302
|
+
}
|
|
20303
|
+
throw error;
|
|
20304
|
+
}
|
|
20305
|
+
assertFrameNotDenied(ctx, { origin: rooted.origin });
|
|
20306
|
+
await ctx.cdp.send(contents, "DOM.getFrameOwner", {
|
|
20307
|
+
frameId: ref.frameId
|
|
20308
|
+
}).then(
|
|
20309
|
+
(owner) => owner?.backendNodeId ? ctx.cdp.send(contents, "DOM.scrollIntoViewIfNeeded", {
|
|
20310
|
+
backendNodeId: owner.backendNodeId
|
|
20311
|
+
}) : void 0
|
|
20312
|
+
).catch(() => void 0);
|
|
20313
|
+
const pushed = await ctx.cdp.send(
|
|
20314
|
+
contents,
|
|
20315
|
+
"DOM.pushNodesByBackendIdsToFrontend",
|
|
20316
|
+
{ backendNodeIds: [ref.backendNodeId] },
|
|
20317
|
+
{ sessionId: rooted.sessionId }
|
|
20318
|
+
);
|
|
20319
|
+
const nodeId = pushed?.nodeIds?.[0];
|
|
20320
|
+
if (!nodeId) {
|
|
20321
|
+
throw new BridgeCommandError("element_not_found", "snapshot ref is stale");
|
|
20322
|
+
}
|
|
20323
|
+
return finishInFrame(
|
|
20324
|
+
ctx,
|
|
20325
|
+
contents,
|
|
20326
|
+
target2,
|
|
20327
|
+
{
|
|
20328
|
+
sessionId: rooted.sessionId,
|
|
20329
|
+
frameTargetId: ref.frameId,
|
|
20330
|
+
frameId: ref.frameId,
|
|
20331
|
+
rootNodeId: rooted.rootNodeId,
|
|
20332
|
+
origin: ref.frameOrigin ?? rooted.origin,
|
|
20333
|
+
hops: [],
|
|
20334
|
+
path: []
|
|
20335
|
+
},
|
|
20336
|
+
nodeId
|
|
20337
|
+
);
|
|
19593
20338
|
}
|
|
19594
20339
|
function targetOf(params) {
|
|
19595
20340
|
const ref = stringParam(params, "ref");
|
|
@@ -19616,7 +20361,7 @@ async function click(ctx, params) {
|
|
|
19616
20361
|
};
|
|
19617
20362
|
contents.on("did-start-navigation", onStart);
|
|
19618
20363
|
try {
|
|
19619
|
-
await ctx
|
|
20364
|
+
await sendIn(ctx, contents, node.sessionId, "Runtime.callFunctionOn", {
|
|
19620
20365
|
objectId: node.objectId,
|
|
19621
20366
|
functionDeclaration: CLICK_FUNCTION,
|
|
19622
20367
|
returnByValue: true
|
|
@@ -19630,26 +20375,78 @@ async function click(ctx, params) {
|
|
|
19630
20375
|
}
|
|
19631
20376
|
ctx.registry.touch(record);
|
|
19632
20377
|
const after = pageOf(record.view);
|
|
19633
|
-
return {
|
|
20378
|
+
return {
|
|
20379
|
+
navigated: navigationSeen || after.url !== before,
|
|
20380
|
+
...frameFields(node),
|
|
20381
|
+
...after
|
|
20382
|
+
};
|
|
19634
20383
|
}
|
|
19635
20384
|
async function type(ctx, params) {
|
|
19636
20385
|
const record = ctx.registry.requireSurface(params.tab);
|
|
19637
20386
|
const contents = record.view.webContents;
|
|
19638
|
-
const
|
|
20387
|
+
const target2 = targetOf(params);
|
|
20388
|
+
const node = await resolveNode(ctx, record, target2);
|
|
19639
20389
|
const text = typeof params.text === "string" ? params.text : "";
|
|
19640
|
-
await ctx
|
|
20390
|
+
const typed = await typeInto(ctx, contents, node, text, target2);
|
|
20391
|
+
if (typed) {
|
|
20392
|
+
ctx.registry.touch(record);
|
|
20393
|
+
return { ...typed, ...frameFields(node), ...pageOf(record.view) };
|
|
20394
|
+
}
|
|
20395
|
+
const frame = await iframeScopeOf(ctx, contents, node);
|
|
20396
|
+
if (frame) {
|
|
20397
|
+
assertFrameNotDenied(ctx, frame);
|
|
20398
|
+
const field = await editableInFrame(ctx, contents, frame, target2);
|
|
20399
|
+
const inner = {
|
|
20400
|
+
nodeId: field.nodeId,
|
|
20401
|
+
objectId: field.objectId,
|
|
20402
|
+
sessionId: frame.sessionId,
|
|
20403
|
+
frame
|
|
20404
|
+
};
|
|
20405
|
+
const typedInFrame = await typeInto(ctx, contents, inner, text, target2);
|
|
20406
|
+
if (typedInFrame) {
|
|
20407
|
+
ctx.registry.touch(record);
|
|
20408
|
+
return {
|
|
20409
|
+
...typedInFrame,
|
|
20410
|
+
...frameFields(inner),
|
|
20411
|
+
...pageOf(record.view)
|
|
20412
|
+
};
|
|
20413
|
+
}
|
|
20414
|
+
}
|
|
20415
|
+
throw new BridgeCommandError(
|
|
20416
|
+
"element_not_found",
|
|
20417
|
+
`${target2} is not an editable field (no value setter, not contenteditable), so nothing was typed${frame ? "; it is an iframe and its document holds no field that took the text either" : `; if the field lives inside an iframe, it cannot be targeted from the top document; address it as <iframe-selector> ${FRAME_HOP} <selector>`}`
|
|
20418
|
+
);
|
|
20419
|
+
}
|
|
20420
|
+
const NON_ALPHANUMERIC = /[^\p{L}\p{N}]+/gu;
|
|
20421
|
+
function foldForComparison(value) {
|
|
20422
|
+
return value.normalize("NFKC").toLowerCase().replace(NON_ALPHANUMERIC, "");
|
|
20423
|
+
}
|
|
20424
|
+
function holdsTypedText(readBack2, text) {
|
|
20425
|
+
const folded = foldForComparison(text);
|
|
20426
|
+
if (folded === "") return readBack2.includes(text);
|
|
20427
|
+
return foldForComparison(readBack2).includes(folded);
|
|
20428
|
+
}
|
|
20429
|
+
const SETTLE_BEFORE_REREAD_MS = 100;
|
|
20430
|
+
const MAX_QUOTED_READBACK = 80;
|
|
20431
|
+
function quoteReadBack(value) {
|
|
20432
|
+
if (value.length <= MAX_QUOTED_READBACK) return `'${value}'`;
|
|
20433
|
+
return `'${value.slice(0, MAX_QUOTED_READBACK)}…' (${value.length} chars)`;
|
|
20434
|
+
}
|
|
20435
|
+
async function typeInto(ctx, contents, node, text, target2) {
|
|
20436
|
+
await sendIn(ctx, contents, node.sessionId, "Runtime.callFunctionOn", {
|
|
19641
20437
|
objectId: node.objectId,
|
|
19642
20438
|
functionDeclaration: FOCUS_AND_SELECT_FUNCTION,
|
|
19643
20439
|
returnByValue: true
|
|
19644
20440
|
});
|
|
19645
|
-
await ctx
|
|
19646
|
-
const readBack2 = await conversationReadBack(ctx, node
|
|
19647
|
-
if (readBack2
|
|
19648
|
-
|
|
19649
|
-
return { value: readBack2, via: "insert_text", ...pageOf(record.view) };
|
|
20441
|
+
await sendIn(ctx, contents, node.sessionId, "Input.insertText", { text });
|
|
20442
|
+
const readBack2 = await conversationReadBack(ctx, node, contents);
|
|
20443
|
+
if (holdsTypedText(readBack2, text)) {
|
|
20444
|
+
return { value: readBack2, via: "insert_text" };
|
|
19650
20445
|
}
|
|
19651
|
-
const set2 = await
|
|
20446
|
+
const set2 = await sendIn(
|
|
20447
|
+
ctx,
|
|
19652
20448
|
contents,
|
|
20449
|
+
node.sessionId,
|
|
19653
20450
|
"Runtime.callFunctionOn",
|
|
19654
20451
|
{
|
|
19655
20452
|
objectId: node.objectId,
|
|
@@ -19659,26 +20456,44 @@ async function type(ctx, params) {
|
|
|
19659
20456
|
}
|
|
19660
20457
|
);
|
|
19661
20458
|
const setValue = set2?.result?.value;
|
|
19662
|
-
if (setValue === null)
|
|
19663
|
-
|
|
19664
|
-
|
|
19665
|
-
|
|
19666
|
-
|
|
20459
|
+
if (setValue === null) return null;
|
|
20460
|
+
await sleep(SETTLE_BEFORE_REREAD_MS);
|
|
20461
|
+
const settled = await conversationReadBack(ctx, node, contents);
|
|
20462
|
+
if (holdsTypedText(settled, text)) {
|
|
20463
|
+
return {
|
|
20464
|
+
value: settled,
|
|
20465
|
+
via: "value_setter",
|
|
20466
|
+
insert_text_readback: readBack2
|
|
20467
|
+
};
|
|
19667
20468
|
}
|
|
19668
|
-
|
|
19669
|
-
|
|
19670
|
-
value
|
|
19671
|
-
|
|
19672
|
-
|
|
19673
|
-
|
|
20469
|
+
throw new BridgeCommandError(
|
|
20470
|
+
"element_not_found",
|
|
20471
|
+
`${target2} took the value-setter write but does not hold the text (read back ${quoteReadBack(settled)}), so nothing was reported typed`
|
|
20472
|
+
);
|
|
20473
|
+
}
|
|
20474
|
+
async function iframeScopeOf(ctx, contents, node) {
|
|
20475
|
+
const parent = node.frame ?? {
|
|
20476
|
+
rootNodeId: 0,
|
|
20477
|
+
hops: [],
|
|
20478
|
+
path: []
|
|
19674
20479
|
};
|
|
20480
|
+
try {
|
|
20481
|
+
return await enterFrame(ctx, contents, parent, node.nodeId);
|
|
20482
|
+
} catch (error) {
|
|
20483
|
+
if (isNotAFrame(error)) {
|
|
20484
|
+
return null;
|
|
20485
|
+
}
|
|
20486
|
+
throw error;
|
|
20487
|
+
}
|
|
19675
20488
|
}
|
|
19676
|
-
async function conversationReadBack(ctx,
|
|
19677
|
-
const out = await
|
|
20489
|
+
async function conversationReadBack(ctx, node, contents) {
|
|
20490
|
+
const out = await sendIn(
|
|
20491
|
+
ctx,
|
|
19678
20492
|
contents,
|
|
20493
|
+
node.sessionId,
|
|
19679
20494
|
"Runtime.callFunctionOn",
|
|
19680
20495
|
{
|
|
19681
|
-
objectId,
|
|
20496
|
+
objectId: node.objectId,
|
|
19682
20497
|
functionDeclaration: READ_VALUE_FUNCTION,
|
|
19683
20498
|
returnByValue: true
|
|
19684
20499
|
}
|
|
@@ -20661,6 +21476,13 @@ async function upload(ctx, params) {
|
|
|
20661
21476
|
const paths = uploadPaths(params);
|
|
20662
21477
|
const contents = record.view.webContents;
|
|
20663
21478
|
const node = await resolveNode(ctx, record, selector);
|
|
21479
|
+
if (node.frame) {
|
|
21480
|
+
throw new BridgeCommandError(
|
|
21481
|
+
"element_not_found",
|
|
21482
|
+
`${selector} resolved to an element inside a frame${node.frame.origin ? ` (${node.frame.origin})` : ""}; upload into a frame is not supported yet, so nothing was attached`,
|
|
21483
|
+
{ selector, frame_origin: node.frame.origin ?? "" }
|
|
21484
|
+
);
|
|
21485
|
+
}
|
|
20664
21486
|
const accept = await acceptsOf(ctx, record, node.nodeId);
|
|
20665
21487
|
await ctx.cdp.send(contents, "DOM.setFileInputFiles", {
|
|
20666
21488
|
files: paths,
|