/** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { mime } from '../utilsBundle'; import * as injectedScriptSource from '../generated/injectedScriptSource'; import type * as channels from '../protocol/channels'; import { isSessionClosedError } from './protocolError'; import type { ScreenshotOptions } from './screenshotter'; import type * as frames from './frames'; import type { InjectedScript, InjectedScriptPoll, LogEntry, HitTargetInterceptionResult, ElementState } from './injected/injectedScript'; import type { CallMetadata } from './instrumentation'; import * as js from './javascript'; import type { Page } from './page'; import type { Progress } from './progress'; import { ProgressController } from './progress'; import type { SelectorInfo } from './selectors'; import type * as types from './types'; import type { TimeoutOptions } from '../common/types'; import { isUnderTest } from '../utils'; type SetInputFilesFiles = channels.ElementHandleSetInputFilesParams['files']; export type InputFilesItems = { files?: SetInputFilesFiles, localPaths?: string[] }; type ActionName = 'click' | 'hover' | 'dblclick' | 'tap' | 'move and up' | 'move and down'; export class NonRecoverableDOMError extends Error { } export function isNonRecoverableDOMError(error: Error) { return error instanceof NonRecoverableDOMError; } export class FrameExecutionContext extends js.ExecutionContext { readonly frame: frames.Frame; private _injectedScriptPromise?: Promise; readonly world: types.World | null; constructor(delegate: js.ExecutionContextDelegate, frame: frames.Frame, world: types.World|null) { super(frame, delegate); this.frame = frame; this.world = world; } override async waitForSignalsCreatedBy(action: () => Promise): Promise { return this.frame._page._frameManager.waitForSignalsCreatedBy(null, false, action); } override adoptIfNeeded(handle: js.JSHandle): Promise | null { if (handle instanceof ElementHandle && handle._context !== this) return this.frame._page._delegate.adoptElementHandle(handle, this); return null; } async evaluate(pageFunction: js.Func1, arg?: Arg): Promise { return js.evaluate(this, true /* returnByValue */, pageFunction, arg); } async evaluateHandle(pageFunction: js.Func1, arg?: Arg): Promise> { return js.evaluate(this, false /* returnByValue */, pageFunction, arg); } async evaluateExpression(expression: string, isFunction: boolean | undefined, arg?: any): Promise { return js.evaluateExpression(this, true /* returnByValue */, expression, isFunction, arg); } async evaluateExpressionAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg?: any): Promise { return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => { return this.evaluateExpression(expression, isFunction, arg); }); } async evaluateExpressionHandleAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg: any): Promise { return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => { return js.evaluateExpression(this, false /* returnByValue */, expression, isFunction, arg); }); } override createHandle(remoteObject: js.RemoteObject): js.JSHandle { if (this.frame._page._delegate.isElementHandle(remoteObject)) return new ElementHandle(this, remoteObject.objectId!); return super.createHandle(remoteObject); } injectedScript(): Promise> { if (!this._injectedScriptPromise) { const custom: string[] = []; for (const [name, { source }] of this.frame._page.selectors._engines) custom.push(`{ name: '${name}', engine: (${source}) }`); const source = ` (() => { const module = {}; ${injectedScriptSource.source} return new module.exports( ${isUnderTest()}, ${this.frame._page._delegate.rafCountForStablePosition()}, "${this.frame._page._browserContext._browser.options.name}", [${custom.join(',\n')}] ); })(); `; this._injectedScriptPromise = this.rawEvaluateHandle(source).then(objectId => new js.JSHandle(this, 'object', undefined, objectId)); } return this._injectedScriptPromise; } override async doSlowMo() { return this.frame._page._doSlowMo(); } } export class ElementHandle extends js.JSHandle { declare readonly _context: FrameExecutionContext; readonly _page: Page; declare readonly _objectId: string; readonly _frame: frames.Frame; constructor(context: FrameExecutionContext, objectId: string) { super(context, 'node', undefined, objectId); this._page = context.frame._page; this._frame = context.frame; this._initializePreview().catch(e => {}); } async _initializePreview() { const utility = await this._context.injectedScript(); this._setPreview(await utility.evaluate((injected, e) => 'JSHandle@' + injected.previewNode(e), this)); } override asElement(): ElementHandle | null { return this; } async evaluateInUtility(pageFunction: js.Func1<[js.JSHandle, ElementHandle, Arg], R>, arg: Arg): Promise { try { const utility = await this._frame._utilityContext(); return await utility.evaluate(pageFunction, [await utility.injectedScript(), this, arg]); } catch (e) { if (js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e)) throw e; return 'error:notconnected'; } } async evaluateHandleInUtility(pageFunction: js.Func1<[js.JSHandle, ElementHandle, Arg], R>, arg: Arg): Promise | 'error:notconnected'> { try { const utility = await this._frame._utilityContext(); return await utility.evaluateHandle(pageFunction, [await utility.injectedScript(), this, arg]); } catch (e) { if (js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e)) throw e; return 'error:notconnected'; } } async evaluatePoll(progress: Progress, pageFunction: js.Func1<[js.JSHandle, ElementHandle, Arg], InjectedScriptPoll>, arg: Arg): Promise { try { const utility = await this._frame._utilityContext(); const poll = await utility.evaluateHandle(pageFunction, [await utility.injectedScript(), this, arg]); const pollHandler = new InjectedScriptPollHandler(progress, poll); return await pollHandler.finish(); } catch (e) { if (js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e)) throw e; return 'error:notconnected'; } } async ownerFrame(): Promise { const frameId = await this._page._delegate.getOwnerFrame(this); if (!frameId) return null; const frame = this._page._frameManager.frame(frameId); if (frame) return frame; for (const page of this._page._browserContext.pages()) { const frame = page._frameManager.frame(frameId); if (frame) return frame; } return null; } async isIframeElement(): Promise { return this.evaluateInUtility(([injected, node]) => node && (node.nodeName === 'IFRAME' || node.nodeName === 'FRAME'), {}); } async contentFrame(): Promise { const isFrameElement = throwRetargetableDOMError(await this.isIframeElement()); if (!isFrameElement) return null; return this._page._delegate.getContentFrame(this); } async getAttribute(name: string): Promise { return throwRetargetableDOMError(await this.evaluateInUtility(([injected, node, name]) => { if (node.nodeType !== Node.ELEMENT_NODE) throw injected.createStacklessError('Node is not an element'); const element = node as unknown as Element; return { value: element.getAttribute(name) }; }, name)).value; } async inputValue(): Promise { return throwRetargetableDOMError(await this.evaluateInUtility(([injected, node]) => { const element = injected.retarget(node, 'follow-label'); if (!element || (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT')) throw injected.createStacklessError('Node is not an ,