feat(engines): move querySelectorAll to css engine (#61)

This commit is contained in:
Dmitry Gozman 2019-11-22 16:21:30 -08:00 committed by Andrey Lushnikov
parent 7633518272
commit 7c69f8c457
7 changed files with 45 additions and 44 deletions

View File

@ -27,6 +27,8 @@ import { Protocol } from './protocol';
import { releaseObject, valueFromRemoteObject } from './protocolHelper'; import { releaseObject, valueFromRemoteObject } from './protocolHelper';
import Injected from '../injected/injected'; import Injected from '../injected/injected';
type SelectorRoot = Element | ShadowRoot | Document;
type Point = { type Point = {
x: number; x: number;
y: number; y: number;
@ -57,7 +59,7 @@ export class JSHandle {
return this._context; return this._context;
} }
async evaluate(pageFunction: Function | string, ...args: any[]): Promise<(any)> { async evaluate(pageFunction: Function | string, ...args: any[]): Promise<any> {
return await this.executionContext().evaluate(pageFunction, this, ...args); return await this.executionContext().evaluate(pageFunction, this, ...args);
} }
@ -431,10 +433,8 @@ export class ElementHandle extends JSHandle {
async $(selector: string): Promise<ElementHandle | null> { async $(selector: string): Promise<ElementHandle | null> {
const handle = await this.evaluateHandle( const handle = await this.evaluateHandle(
(element, selector, injected: Injected) => { (root: SelectorRoot, selector: string, injected: Injected) => injected.querySelector('css=' + selector, root),
return injected.querySelector('css=' + selector, element); selector, await this._context._injected()
},
selector, await this._context._injected()
); );
const element = handle.asElement(); const element = handle.asElement();
if (element) if (element)
@ -445,8 +445,8 @@ export class ElementHandle extends JSHandle {
async $$(selector: string): Promise<ElementHandle[]> { async $$(selector: string): Promise<ElementHandle[]> {
const arrayHandle = await this.evaluateHandle( const arrayHandle = await this.evaluateHandle(
(element, selector) => element.querySelectorAll(selector), (root: SelectorRoot, selector: string, injected: Injected) => injected.querySelectorAll('css=' + selector, root),
selector selector, await this._context._injected()
); );
const properties = await arrayHandle.getProperties(); const properties = await arrayHandle.getProperties();
await arrayHandle.dispose(); await arrayHandle.dispose();
@ -459,7 +459,7 @@ export class ElementHandle extends JSHandle {
return result; return result;
} }
async $eval(selector: string, pageFunction: Function | string, ...args: any[]): Promise<(object | undefined)> { async $eval(selector: string, pageFunction: Function | string, ...args: any[]): Promise<object | undefined> {
const elementHandle = await this.$(selector); const elementHandle = await this.$(selector);
if (!elementHandle) if (!elementHandle)
throw new Error(`Error: failed to find element matching selector "${selector}"`); throw new Error(`Error: failed to find element matching selector "${selector}"`);
@ -468,10 +468,10 @@ export class ElementHandle extends JSHandle {
return result; return result;
} }
async $$eval(selector: string, pageFunction: Function | string, ...args: any[]): Promise<(object | undefined)> { async $$eval(selector: string, pageFunction: Function | string, ...args: any[]): Promise<object | undefined> {
const arrayHandle = await this.evaluateHandle( const arrayHandle = await this.evaluateHandle(
(element, selector) => Array.from(element.querySelectorAll(selector)), (root: SelectorRoot, selector: string, injected: Injected) => injected.querySelectorAll('css=' + selector, root),
selector selector, await this._context._injected()
); );
const result = await arrayHandle.evaluate(pageFunction, ...args); const result = await arrayHandle.evaluate(pageFunction, ...args);

View File

@ -3,6 +3,7 @@ import {TimeoutError} from '../Errors';
import * as fs from 'fs'; import * as fs from 'fs';
import * as util from 'util'; import * as util from 'util';
import {ElementHandle, JSHandle} from './JSHandle'; import {ElementHandle, JSHandle} from './JSHandle';
import { ExecutionContext } from './ExecutionContext';
const readFileAsync = util.promisify(fs.readFile); const readFileAsync = util.promisify(fs.readFile);
@ -51,7 +52,7 @@ export class DOMWorld {
waitTask.terminate(new Error('waitForFunction failed: frame got detached.')); waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
} }
async executionContext() { async executionContext(): Promise<ExecutionContext> {
if (this._detached) if (this._detached)
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`); throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
return this._contextPromise; return this._contextPromise;
@ -60,12 +61,12 @@ export class DOMWorld {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
async evaluateHandle(pageFunction, ...args) { async evaluateHandle(pageFunction, ...args): Promise<JSHandle> {
const context = await this.executionContext(); const context = await this.executionContext();
return context.evaluateHandle(pageFunction, ...args); return context.evaluateHandle(pageFunction, ...args);
} }
async evaluate(pageFunction, ...args) { async evaluate(pageFunction, ...args): Promise<any> {
const context = await this.executionContext(); const context = await this.executionContext();
return context.evaluate(pageFunction, ...args); return context.evaluate(pageFunction, ...args);
} }

View File

@ -33,7 +33,7 @@ export class ExecutionContext {
this._executionContextId = executionContextId; this._executionContextId = executionContextId;
} }
async evaluateHandle(pageFunction, ...args) { async evaluateHandle(pageFunction, ...args): Promise<JSHandle> {
if (helper.isString(pageFunction)) { if (helper.isString(pageFunction)) {
const payload = await this._session.send('Runtime.evaluate', { const payload = await this._session.send('Runtime.evaluate', {
expression: pageFunction.trim(), expression: pageFunction.trim(),
@ -105,7 +105,7 @@ export class ExecutionContext {
return this._frame; return this._frame;
} }
async evaluate(pageFunction, ...args) { async evaluate(pageFunction, ...args): Promise<any> {
try { try {
const handle = await this.evaluateHandle(pageFunction, ...args); const handle = await this.evaluateHandle(pageFunction, ...args);
const result = await handle.jsonValue(); const result = await handle.jsonValue();

View File

@ -352,7 +352,7 @@ export class Frame {
return this._mainWorld.setContent(html); return this._mainWorld.setContent(html);
} }
async evaluate(pageFunction, ...args) { async evaluate(pageFunction, ...args): Promise<any> {
return this._mainWorld.evaluate(pageFunction, ...args); return this._mainWorld.evaluate(pageFunction, ...args);
} }
@ -376,7 +376,7 @@ export class Frame {
return this._mainWorld.$x(expression); return this._mainWorld.$x(expression);
} }
async evaluateHandle(pageFunction, ...args) { async evaluateHandle(pageFunction, ...args): Promise<JSHandle> {
return this._mainWorld.evaluateHandle(pageFunction, ...args); return this._mainWorld.evaluateHandle(pageFunction, ...args);
} }

View File

@ -23,6 +23,8 @@ import { JugglerSession } from './Connection';
import { MultiClickOptions, ClickOptions, selectFunction, SelectOption } from '../input'; import { MultiClickOptions, ClickOptions, selectFunction, SelectOption } from '../input';
import Injected from '../injected/injected'; import Injected from '../injected/injected';
type SelectorRoot = Element | ShadowRoot | Document;
export class JSHandle { export class JSHandle {
_context: ExecutionContext; _context: ExecutionContext;
protected _session: JugglerSession; protected _session: JugglerSession;
@ -203,9 +205,7 @@ export class ElementHandle extends JSHandle {
async $(selector: string): Promise<ElementHandle | null> { async $(selector: string): Promise<ElementHandle | null> {
const handle = await this._frame.evaluateHandle( const handle = await this._frame.evaluateHandle(
(element, selector, injected: Injected) => { (root: SelectorRoot, selector: string, injected: Injected) => injected.querySelector('css=' + selector, root),
return injected.querySelector('css=' + selector, element);
},
this, selector, await this._context._injected() this, selector, await this._context._injected()
); );
const element = handle.asElement(); const element = handle.asElement();
@ -215,10 +215,10 @@ export class ElementHandle extends JSHandle {
return null; return null;
} }
async $$(selector: string): Promise<Array<ElementHandle>> { async $$(selector: string): Promise<ElementHandle[]> {
const arrayHandle = await this._frame.evaluateHandle( const arrayHandle = await this._frame.evaluateHandle(
(element, selector) => element.querySelectorAll(selector), (root: SelectorRoot, selector: string, injected: Injected) => injected.querySelectorAll('css=' + selector, root),
this, selector this, selector, await this._context._injected()
); );
const properties = await arrayHandle.getProperties(); const properties = await arrayHandle.getProperties();
await arrayHandle.dispose(); await arrayHandle.dispose();
@ -231,7 +231,7 @@ export class ElementHandle extends JSHandle {
return result; return result;
} }
async $eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> { async $eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<object | undefined> {
const elementHandle = await this.$(selector); const elementHandle = await this.$(selector);
if (!elementHandle) if (!elementHandle)
throw new Error(`Error: failed to find element matching selector "${selector}"`); throw new Error(`Error: failed to find element matching selector "${selector}"`);
@ -240,10 +240,10 @@ export class ElementHandle extends JSHandle {
return result; return result;
} }
async $$eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<(object | undefined)> { async $$eval(selector: string, pageFunction: Function | string, ...args: Array<any>): Promise<object | undefined> {
const arrayHandle = await this._frame.evaluateHandle( const arrayHandle = await this._frame.evaluateHandle(
(element, selector) => Array.from(element.querySelectorAll(selector)), (root: SelectorRoot, selector: string, injected: Injected) => injected.querySelectorAll('css=' + selector, root),
this, selector this, selector, await this._context._injected()
); );
const result = await this._frame.evaluate(pageFunction, arrayHandle, ...args); const result = await this._frame.evaluate(pageFunction, arrayHandle, ...args);

View File

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT license. // Licensed under the MIT license.
import { SelectorEngine } from './selectorEngine'; import { SelectorEngine, SelectorRoot } from './selectorEngine';
import { Utils } from './utils'; import { Utils } from './utils';
type ParsedSelector = { engine: SelectorEngine, selector: string }[]; type ParsedSelector = { engine: SelectorEngine, selector: string }[];
@ -17,25 +17,25 @@ export class Injected {
this.engines.set(engine.name, engine); this.engines.set(engine.name, engine);
} }
querySelector(selector: string, root: Element): Element | undefined { querySelector(selector: string, root: SelectorRoot): Element | undefined {
const parsed = this._parseSelector(selector); const parsed = this._parseSelector(selector);
let element = root; let element = root;
for (const { engine, selector } of parsed) { for (const { engine, selector } of parsed) {
const next = engine.query(element.shadowRoot || element, selector); const next = engine.query((element as Element).shadowRoot || element, selector);
if (!next) if (!next)
return; return;
element = next; element = next;
} }
return element; return element as Element;
} }
querySelectorAll(selector: string, root: Element): Element[] { querySelectorAll(selector: string, root: SelectorRoot): Element[] {
const parsed = this._parseSelector(selector); const parsed = this._parseSelector(selector);
let set = new Set<Element>([ root ]); let set = new Set<SelectorRoot>([ root ]);
for (const { engine, selector } of parsed) { for (const { engine, selector } of parsed) {
const newSet = new Set<Element>(); const newSet = new Set<Element>();
for (const prev of set) { for (const prev of set) {
for (const next of engine.queryAll(prev.shadowRoot || prev, selector)) { for (const next of engine.queryAll((prev as Element).shadowRoot || prev, selector)) {
if (newSet.has(next)) if (newSet.has(next))
continue; continue;
newSet.add(next); newSet.add(next);
@ -43,7 +43,7 @@ export class Injected {
} }
set = newSet; set = newSet;
} }
return Array.from(set); return Array.from(set) as Element[];
} }
private _parseSelector(selector: string): ParsedSelector { private _parseSelector(selector: string): ParsedSelector {

View File

@ -25,6 +25,8 @@ import { Protocol } from './protocol';
import { releaseObject, valueFromRemoteObject } from './protocolHelper'; import { releaseObject, valueFromRemoteObject } from './protocolHelper';
import Injected from '../injected/injected'; import Injected from '../injected/injected';
type SelectorRoot = Element | ShadowRoot | Document;
const writeFileAsync = helper.promisify(fs.writeFile); const writeFileAsync = helper.promisify(fs.writeFile);
export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) { export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) {
@ -52,7 +54,7 @@ export class JSHandle {
return this._context; return this._context;
} }
async evaluate(pageFunction: Function | string, ...args: any[]): Promise<(any)> { async evaluate(pageFunction: Function | string, ...args: any[]): Promise<any> {
return await this.executionContext().evaluate(pageFunction, this, ...args); return await this.executionContext().evaluate(pageFunction, this, ...args);
} }
@ -310,9 +312,7 @@ export class ElementHandle extends JSHandle {
async $(selector: string): Promise<ElementHandle | null> { async $(selector: string): Promise<ElementHandle | null> {
const handle = await this.evaluateHandle( const handle = await this.evaluateHandle(
(element, selector, injected: Injected) => { (root: SelectorRoot, selector: string, injected: Injected) => injected.querySelector('css=' + selector, root),
return injected.querySelector('css=' + selector, element);
},
selector, await this._context._injected() selector, await this._context._injected()
); );
const element = handle.asElement(); const element = handle.asElement();
@ -338,7 +338,7 @@ export class ElementHandle extends JSHandle {
return result; return result;
} }
async $eval(selector: string, pageFunction: Function | string, ...args: any[]): Promise<(object | undefined)> { async $eval(selector: string, pageFunction: Function | string, ...args: any[]): Promise<object | undefined> {
const elementHandle = await this.$(selector); const elementHandle = await this.$(selector);
if (!elementHandle) if (!elementHandle)
throw new Error(`Error: failed to find element matching selector "${selector}"`); throw new Error(`Error: failed to find element matching selector "${selector}"`);
@ -347,10 +347,10 @@ export class ElementHandle extends JSHandle {
return result; return result;
} }
async $$eval(selector: string, pageFunction: Function | string, ...args: any[]): Promise<(object | undefined)> { async $$eval(selector: string, pageFunction: Function | string, ...args: any[]): Promise<object | undefined> {
const arrayHandle = await this.evaluateHandle( const arrayHandle = await this.evaluateHandle(
(element, selector) => Array.from(element.querySelectorAll(selector)), (root: SelectorRoot, selector: string, injected: Injected) => injected.querySelectorAll('css=' + selector, root),
selector selector, await this._context._injected()
); );
const result = await arrayHandle.evaluate(pageFunction, ...args); const result = await arrayHandle.evaluate(pageFunction, ...args);