Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/client/lib/FrameEnvironment.ts
1028 views
1
import inspectInstanceProperties from 'awaited-dom/base/inspectInstanceProperties';
2
import * as Util from 'util';
3
import StateMachine from 'awaited-dom/base/StateMachine';
4
import { ISuperElement } from 'awaited-dom/base/interfaces/super';
5
import AwaitedPath from 'awaited-dom/base/AwaitedPath';
6
import { IRequestInit } from 'awaited-dom/base/interfaces/official';
7
import SuperDocument from 'awaited-dom/impl/super-klasses/SuperDocument';
8
import Storage from 'awaited-dom/impl/official-klasses/Storage';
9
import CSSStyleDeclaration from 'awaited-dom/impl/official-klasses/CSSStyleDeclaration';
10
import {
11
createCSSStyleDeclaration,
12
createResponse,
13
createStorage,
14
createSuperDocument,
15
} from 'awaited-dom/impl/create';
16
import Request from 'awaited-dom/impl/official-klasses/Request';
17
import { ILocationTrigger, LocationStatus } from '@secret-agent/interfaces/Location';
18
import IWaitForElementOptions from '@secret-agent/interfaces/IWaitForElementOptions';
19
import Response from 'awaited-dom/impl/official-klasses/Response';
20
import IWaitForOptions from '@secret-agent/interfaces/IWaitForOptions';
21
import {
22
IElementIsolate,
23
IHTMLFrameElementIsolate,
24
IHTMLIFrameElementIsolate,
25
IHTMLObjectElementIsolate,
26
INodeIsolate,
27
} from 'awaited-dom/base/interfaces/isolate';
28
import { INodeVisibility } from '@secret-agent/interfaces/INodeVisibility';
29
import { getComputedVisibilityFnName } from '@secret-agent/interfaces/jsPathFnNames';
30
import IAwaitedOptions from '../interfaces/IAwaitedOptions';
31
import RequestGenerator, { getRequestIdOrUrl } from './Request';
32
import CookieStorage, { createCookieStorage } from './CookieStorage';
33
import Agent, { IState as IAgentState } from './Agent';
34
import { delegate as AwaitedHandler, getAwaitedPathAsMethodArg } from './SetupAwaitedHandler';
35
import CoreFrameEnvironment from './CoreFrameEnvironment';
36
import Tab, { IState as ITabState } from './Tab';
37
import { IMousePosition } from '../interfaces/IInteractions';
38
import Resource, { createResource } from './Resource';
39
40
const { getState, setState } = StateMachine<FrameEnvironment, IState>();
41
const { getState: getTabState } = StateMachine<Tab, ITabState>();
42
const agentState = StateMachine<Agent, IAgentState>();
43
const awaitedPathState = StateMachine<
44
any,
45
{ awaitedPath: AwaitedPath; awaitedOptions: IAwaitedOptions }
46
>();
47
48
export interface IState {
49
secretAgent: Agent;
50
tab: Tab;
51
coreFrame: Promise<CoreFrameEnvironment>;
52
}
53
54
const propertyKeys: (keyof FrameEnvironment)[] = [
55
'frameId',
56
'url',
57
'name',
58
'parentFrameId',
59
'cookieStorage',
60
'localStorage',
61
'sessionStorage',
62
'document',
63
'Request',
64
];
65
66
export default class FrameEnvironment {
67
constructor(secretAgent: Agent, tab: Tab, coreFrame: Promise<CoreFrameEnvironment>) {
68
setState(this, {
69
secretAgent,
70
tab,
71
coreFrame,
72
});
73
async function sendToFrameEnvironment(pluginId: string, ...args: any[]): Promise<any> {
74
return (await coreFrame).commandQueue.run(
75
'FrameEnvironment.runPluginCommand',
76
pluginId,
77
args,
78
);
79
}
80
81
for (const clientPlugin of agentState.getState(secretAgent).clientPlugins) {
82
if (clientPlugin.onFrameEnvironment)
83
clientPlugin.onFrameEnvironment(secretAgent, this, sendToFrameEnvironment);
84
}
85
}
86
87
public get isMainFrame(): Promise<boolean> {
88
return this.parentFrameId.then(x => !x);
89
}
90
91
public get frameId(): Promise<number> {
92
return getCoreFrameEnvironment(this).then(x => x.frameId);
93
}
94
95
public get children(): Promise<FrameEnvironment[]> {
96
return getState(this).tab.frameEnvironments.then(async frames => {
97
const frameId = await this.frameId;
98
99
const childFrames: FrameEnvironment[] = [];
100
for (const frame of frames) {
101
const parentFrameId = await frame.parentFrameId;
102
if (parentFrameId === frameId) {
103
childFrames.push(frame);
104
}
105
}
106
return childFrames;
107
});
108
}
109
110
public get url(): Promise<string> {
111
return getCoreFrameEnvironment(this).then(x => x.getUrl());
112
}
113
114
public get name(): Promise<string> {
115
return getCoreFrameEnvironment(this)
116
.then(x => x.getFrameMeta())
117
.then(x => x.name);
118
}
119
120
public get parentFrameId(): Promise<number | null> {
121
return getCoreFrameEnvironment(this).then(x => x.parentFrameId);
122
}
123
124
public get cookieStorage(): CookieStorage {
125
return createCookieStorage(getCoreFrameEnvironment(this));
126
}
127
128
public get document(): SuperDocument {
129
const awaitedPath = new AwaitedPath(null, 'document');
130
const awaitedOptions = { ...getState(this) };
131
return createSuperDocument<IAwaitedOptions>(awaitedPath, awaitedOptions) as SuperDocument;
132
}
133
134
public get localStorage(): Storage {
135
const awaitedPath = new AwaitedPath(null, 'localStorage');
136
const awaitedOptions = { ...getState(this) };
137
return createStorage<IAwaitedOptions>(awaitedPath, awaitedOptions) as Storage;
138
}
139
140
public get sessionStorage(): Storage {
141
const awaitedPath = new AwaitedPath(null, 'sessionStorage');
142
const awaitedOptions = { ...getState(this) };
143
return createStorage<IAwaitedOptions>(awaitedPath, awaitedOptions) as Storage;
144
}
145
146
public get Request(): typeof Request {
147
return RequestGenerator(getCoreFrameEnvironment(this));
148
}
149
150
// METHODS
151
152
public async fetch(request: Request | string, init?: IRequestInit): Promise<Response> {
153
const requestInput = await getRequestIdOrUrl(request);
154
const coreFrame = await getCoreFrameEnvironment(this);
155
const nodePointer = await coreFrame.fetch(requestInput, init);
156
157
const awaitedPath = new AwaitedPath(null).withNodeId(null, nodePointer.id);
158
return createResponse(awaitedPath, { ...getState(this) });
159
}
160
161
public async getFrameEnvironment(
162
element: IHTMLFrameElementIsolate | IHTMLIFrameElementIsolate | IHTMLObjectElementIsolate,
163
): Promise<FrameEnvironment | null> {
164
const { tab } = getState(this);
165
return await tab.getFrameEnvironment(element);
166
}
167
168
public getComputedStyle(element: IElementIsolate, pseudoElement?: string): CSSStyleDeclaration {
169
return FrameEnvironment.getComputedStyle(element, pseudoElement);
170
}
171
172
public async getComputedVisibility(node: INodeIsolate): Promise<INodeVisibility> {
173
return await FrameEnvironment.getComputedVisibility(node);
174
}
175
176
// @deprecated 2021-04-30: Replaced with getComputedVisibility
177
public async isElementVisible(element: IElementIsolate): Promise<boolean> {
178
return await this.getComputedVisibility(element as any).then(x => x.isVisible);
179
}
180
181
public async getJsValue<T>(path: string): Promise<T> {
182
const coreFrame = await getCoreFrameEnvironment(this);
183
return coreFrame.getJsValue<T>(path);
184
}
185
186
public async waitForPaintingStable(options?: IWaitForOptions): Promise<void> {
187
const coreFrame = await getCoreFrameEnvironment(this);
188
await coreFrame.waitForLoad(LocationStatus.PaintingStable, options);
189
}
190
191
public async waitForLoad(status: LocationStatus, options?: IWaitForOptions): Promise<void> {
192
const coreFrame = await getCoreFrameEnvironment(this);
193
await coreFrame.waitForLoad(status, options);
194
}
195
196
public async waitForElement(
197
element: ISuperElement,
198
options?: IWaitForElementOptions,
199
): Promise<void> {
200
if (!element) throw new Error('Element being waited for is null');
201
const { awaitedPath } = awaitedPathState.getState(element);
202
const coreFrame = await getCoreFrameEnvironment(this);
203
await coreFrame.waitForElement(awaitedPath.toJSON(), options);
204
}
205
206
public async waitForLocation(
207
trigger: ILocationTrigger,
208
options?: IWaitForOptions,
209
): Promise<Resource> {
210
const coreFrame = await getCoreFrameEnvironment(this);
211
const resourceMeta = await coreFrame.waitForLocation(trigger, options);
212
const { tab } = getState(this);
213
const { coreTab } = getTabState(tab);
214
return createResource(coreTab, resourceMeta);
215
}
216
217
public toJSON(): any {
218
// return empty so we can avoid infinite "stringifying" in jest
219
return {
220
type: this.constructor.name,
221
};
222
}
223
224
public [Util.inspect.custom](): any {
225
return inspectInstanceProperties(this, propertyKeys as any);
226
}
227
228
public static getComputedStyle(
229
element: IElementIsolate,
230
pseudoElement?: string,
231
): CSSStyleDeclaration {
232
const { awaitedPath: elementAwaitedPath, awaitedOptions } = awaitedPathState.getState(element);
233
const awaitedPath = new AwaitedPath(null, 'window', [
234
'getComputedStyle',
235
getAwaitedPathAsMethodArg(elementAwaitedPath),
236
pseudoElement,
237
]);
238
return createCSSStyleDeclaration<IAwaitedOptions>(
239
awaitedPath,
240
awaitedOptions,
241
) as CSSStyleDeclaration;
242
}
243
244
public static async getComputedVisibility(node: INodeIsolate): Promise<INodeVisibility> {
245
if (!node) return { isVisible: false, nodeExists: false };
246
return await AwaitedHandler.runMethod(awaitedPathState, node, getComputedVisibilityFnName, []);
247
}
248
}
249
250
export function getFrameState(object: any): IState {
251
return getState(object);
252
}
253
254
export function getCoreFrameEnvironment(
255
frameEnvironment: FrameEnvironment,
256
): Promise<CoreFrameEnvironment> {
257
return getState(frameEnvironment).coreFrame;
258
}
259
260
export function getCoreFrameEnvironmentForPosition(
261
mousePosition: IMousePosition,
262
): Promise<CoreFrameEnvironment> {
263
const state = awaitedPathState.getState(mousePosition);
264
if (!state) return;
265
return state?.awaitedOptions?.coreFrame;
266
}
267
268
// CREATE
269
270
export function createFrame(
271
secretAgent: Agent,
272
tab: Tab,
273
coreFrame: Promise<CoreFrameEnvironment>,
274
): FrameEnvironment {
275
return new FrameEnvironment(secretAgent, tab, coreFrame);
276
}
277
278