Socket
Socket
Sign inDemoInstall

playwright-core

Package Overview
Dependencies
Maintainers
1
Versions
4547
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

playwright-core - npm Package Compare versions

Comparing version 0.9.24 to 0.10.0

lib/selectors.d.ts

7

index.d.ts

@@ -20,5 +20,6 @@ /**

export const errors: { TimeoutError: typeof import('./lib/errors').TimeoutError };
export const chromium: import('./lib/api').Chromium;
export const firefox: import('./lib/api').Firefox;
export const webkit: import('./lib/api').WebKit;
export const chromium: import('./lib/server/chromium').Chromium;
export const firefox: import('./lib/server/firefox').Firefox;
export const webkit: import('./lib/server/webkit').WebKit;
export const selectors: import('./lib/api').Selectors;
export type PlaywrightWeb = typeof import('./lib/web');

@@ -34,2 +34,3 @@ /**

errors: { TimeoutError },
selectors: api.Selectors._instance(),
chromium: new Chromium(__dirname, packageJson.playwright.chromium_revision),

@@ -36,0 +37,0 @@ firefox: new Firefox(__dirname, packageJson.playwright.firefox_revision),

@@ -28,2 +28,3 @@ /**

export { Coverage, FileChooser, Page, Worker } from './page';
export { Selectors } from './selectors';
export { CRBrowser as ChromiumBrowser } from './chromium/crBrowser';

@@ -30,0 +31,0 @@ export { CRSession as ChromiumSession } from './chromium/crConnection';

@@ -44,2 +44,4 @@ "use strict";

exports.Worker = page_1.Worker;
var selectors_1 = require("./selectors");
exports.Selectors = selectors_1.Selectors;
var crBrowser_1 = require("./chromium/crBrowser");

@@ -46,0 +48,0 @@ exports.ChromiumBrowser = crBrowser_1.CRBrowser;

@@ -67,3 +67,5 @@ /**

continue(overrides?: {
method?: string;
headers?: network.Headers;
postData?: string;
}): Promise<void>;

@@ -70,0 +72,0 @@ fulfill(response: {

@@ -251,2 +251,4 @@ "use strict";

headers: overrides.headers ? headersArray(overrides.headers) : undefined,
method: overrides.method,
postData: overrides.postData
}).catch(error => {

@@ -253,0 +255,0 @@ // In certain cases, protocol will return error if the request was already canceled

@@ -75,2 +75,4 @@ /**

authenticate(credentials: types.Credentials | null): Promise<void>;
setFileChooserIntercepted(enabled: boolean): Promise<void>;
opener(): Promise<Page | null>;
reload(): Promise<void>;

@@ -77,0 +79,0 @@ private _go;

@@ -34,2 +34,3 @@ "use strict";

const platform = require("../platform");
const crTarget_1 = require("./crTarget");
const UTILITY_WORLD_NAME = '__playwright_utility_world__';

@@ -77,3 +78,2 @@ class CRPage {

this._client.send('Log.enable', {}),
this._client.send('Page.setInterceptFileChooserDialog', { enabled: true }),
this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }),

@@ -288,2 +288,11 @@ this._client.send('Runtime.enable', {}).then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),

}
async setFileChooserIntercepted(enabled) {
await this._client.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => { }); // target can be closed.
}
async opener() {
const openerTarget = crTarget_1.CRTarget.fromPage(this._page).opener();
if (!openerTarget)
return null;
return await openerTarget.page();
}
async reload() {

@@ -290,0 +299,0 @@ await this._client.send('Page.reload');

@@ -411,2 +411,12 @@ "use strict";

{
'name': 'iPhone 11 landscape',
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
'viewport': {
'width': 896,
'height': 414,
'deviceScaleFactor': 2,
'isMobile': true
}
},
{
'name': 'iPhone 11 Pro',

@@ -422,2 +432,12 @@ 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',

{
'name': 'iPhone 11 Pro landscape',
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
'viewport': {
'width': 812,
'height': 375,
'deviceScaleFactor': 3,
'isMobile': true
}
},
{
'name': 'iPhone 11 Pro Max',

@@ -433,2 +453,12 @@ 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',

{
'name': 'iPhone 11 Pro Max landscape',
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
'viewport': {
'width': 896,
'height': 414,
'deviceScaleFactor': 3,
'isMobile': true
}
},
{
'name': 'JioPhone 2',

@@ -504,2 +534,12 @@ 'userAgent': 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5',

{
'name': 'Microsoft Lumia 550 landscape',
'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',
'viewport': {
'width': 360,
'height': 640,
'deviceScaleFactor': 2,
'isMobile': true
}
},
{
'name': 'Microsoft Lumia 950',

@@ -506,0 +546,0 @@ 'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',

@@ -25,2 +25,3 @@ /**

private _injectedPromise?;
private _injectedGeneration;
constructor(delegate: js.ExecutionContextDelegate, frame: frames.Frame);

@@ -27,0 +28,0 @@ _evaluate(returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise<any>;

@@ -20,8 +20,9 @@ "use strict";

const injectedSource = require("./generated/injectedSource");
const zsSelectorEngineSource = require("./generated/zsSelectorEngineSource");
const helper_1 = require("./helper");
const platform = require("./platform");
const selectors_1 = require("./selectors");
class FrameExecutionContext extends js.ExecutionContext {
constructor(delegate, frame) {
super(delegate);
this._injectedGeneration = -1;
this.frame = frame;

@@ -60,10 +61,15 @@ }

_injected() {
const selectors = selectors_1.Selectors._instance();
if (this._injectedPromise && selectors._generation !== this._injectedGeneration) {
this._injectedPromise.then(handle => handle.dispose());
this._injectedPromise = undefined;
}
if (!this._injectedPromise) {
const additionalEngineSources = [zsSelectorEngineSource.source];
const source = `
new (${injectedSource.source})([
${additionalEngineSources.join(',\n')},
${selectors._sources.join(',\n')},
])
`;
this._injectedPromise = this.evaluateHandle(source);
this._injectedGeneration = selectors._generation;
}

@@ -138,3 +144,3 @@ return this._injectedPromise;

async contentFrame() {
const isFrameElement = await this._evaluateInUtility(node => node && (node instanceof HTMLIFrameElement || node instanceof HTMLFrameElement));
const isFrameElement = await this._evaluateInUtility(node => node && (node.nodeName === 'IFRAME' || node.nodeName === 'FRAME'));
if (!isFrameElement)

@@ -141,0 +147,0 @@ return null;

@@ -231,3 +231,8 @@ "use strict";

const session = await this._connection.createSession(this._targetId);
this._ffPage = new ffPage_1.FFPage(session, this._context);
this._ffPage = new ffPage_1.FFPage(session, this._context, async () => {
const openerTarget = this.opener();
if (!openerTarget)
return null;
return await openerTarget.page();
});
const page = this._ffPage._page;

@@ -234,0 +239,0 @@ session.once(ffConnection_1.FFSessionEvents.Disconnected, () => page._didDisconnect());

@@ -136,7 +136,9 @@ "use strict";

}
async continue(overrides = {}) {
const { headers, } = overrides;
async continue(overrides) {
const { method, headers, postData } = overrides;
await this._session.send('Network.resumeInterceptedRequest', {
requestId: this._id,
method,
headers: headers ? headersArray(headers) : undefined,
postData: postData ? Buffer.from(postData).toString('base64') : undefined
}).catch(error => {

@@ -143,0 +145,0 @@ helper_1.debugError(error);

@@ -34,6 +34,7 @@ /**

readonly _networkManager: FFNetworkManager;
private readonly _openerResolver;
private readonly _contextIdToContext;
private _eventListeners;
private _workers;
constructor(session: FFSession, browserContext: BrowserContext);
constructor(session: FFSession, browserContext: BrowserContext, openerResolver: () => Promise<Page | null>);
_initialize(): Promise<void>;

@@ -69,2 +70,4 @@ _ensureIsolatedWorld(name: string): Promise<void>;

authenticate(credentials: types.Credentials | null): Promise<void>;
setFileChooserIntercepted(enabled: boolean): Promise<void>;
opener(): Promise<Page | null>;
reload(): Promise<void>;

@@ -71,0 +74,0 @@ goBack(): Promise<boolean>;

@@ -33,5 +33,6 @@ "use strict";

class FFPage {
constructor(session, browserContext) {
constructor(session, browserContext, openerResolver) {
this._workers = new Map();
this._session = session;
this._openerResolver = openerResolver;
this.rawKeyboard = new ffInput_1.RawKeyboardImpl(session);

@@ -68,3 +69,2 @@ this.rawMouse = new ffInput_1.RawMouseImpl(session);

this._session.send('Page.enable'),
this._session.send('Page.setInterceptFileChooserDialog', { enabled: true })
];

@@ -253,2 +253,8 @@ const options = this._page.browserContext()._options;

}
async setFileChooserIntercepted(enabled) {
await this._session.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => { }); // target can be closed.
}
async opener() {
return await this._openerResolver();
}
async reload() {

@@ -255,0 +261,0 @@ await this._session.send('Page.reload', { frameId: this._page.mainFrame()._id });

@@ -687,2 +687,3 @@ export declare module Protocol {

requestId: string;
method?: string;
headers?: {

@@ -692,2 +693,3 @@ name: string;

}[];
postData?: string;
};

@@ -694,0 +696,0 @@ type resumeInterceptedRequestReturnValue = void;

@@ -1,1 +0,1 @@

export declare const source = "(/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// define __esModule on exports\n/******/ \t__webpack_require__.r = function(exports) {\n/******/ \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n/******/ \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n/******/ \t\t}\n/******/ \t\tObject.defineProperty(exports, '__esModule', { value: true });\n/******/ \t};\n/******/\n/******/ \t// create a fake namespace object\n/******/ \t// mode & 1: value is a module id, require it\n/******/ \t// mode & 2: merge all properties of value into the ns\n/******/ \t// mode & 4: return value when already ns object\n/******/ \t// mode & 8|1: behave like require\n/******/ \t__webpack_require__.t = function(value, mode) {\n/******/ \t\tif(mode & 1) value = __webpack_require__(value);\n/******/ \t\tif(mode & 8) return value;\n/******/ \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n/******/ \t\tvar ns = Object.create(null);\n/******/ \t\t__webpack_require__.r(ns);\n/******/ \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n/******/ \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n/******/ \t\treturn ns;\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = \"./src/injected/zsSelectorEngine.ts\");\n/******/ })\n/************************************************************************/\n/******/ ({\n\n/***/ \"./src/injected/zsSelectorEngine.ts\":\n/*!******************************************!*\\\n !*** ./src/injected/zsSelectorEngine.ts ***!\n \\******************************************/\n/*! no static exports found */\n/***/ (function(module, exports, __webpack_require__) {\n\n\"use strict\";\n\n/**\n * Copyright (c) Microsoft Corporation.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nfunction tokenize(selector) {\n const tokens = [];\n let pos = 0;\n const skipWhitespace = () => {\n while (pos < selector.length && selector[pos] === ' ')\n pos++;\n };\n while (pos < selector.length) {\n skipWhitespace();\n if (pos === selector.length)\n break;\n if (!tokens.length && '^>~'.includes(selector[pos]))\n return pos;\n const token = { combinator: '' };\n if (selector[pos] === '^') {\n token.combinator = '^';\n tokens.push(token);\n pos++;\n continue;\n }\n if (selector[pos] === '>') {\n token.combinator = '>';\n pos++;\n skipWhitespace();\n if (pos === selector.length)\n return pos;\n }\n else if (selector[pos] === '~') {\n token.combinator = '~';\n pos++;\n skipWhitespace();\n if (pos === selector.length)\n return pos;\n }\n let text = '';\n let end = pos;\n let stringQuote;\n const isText = '`\"\\''.includes(selector[pos]);\n while (end < selector.length) {\n if (stringQuote) {\n if (selector[end] === '\\\\' && end + 1 < selector.length) {\n if (!isText)\n text += selector[end];\n text += selector[end + 1];\n end += 2;\n }\n else if (selector[end] === stringQuote) {\n text += selector[end++];\n stringQuote = undefined;\n if (isText)\n break;\n }\n else {\n text += selector[end++];\n }\n }\n else if (' >~^#'.includes(selector[end])) {\n break;\n }\n else if ('`\"\\''.includes(selector[end])) {\n stringQuote = selector[end];\n text += selector[end++];\n }\n else {\n text += selector[end++];\n }\n }\n if (stringQuote)\n return end;\n if (isText)\n token.text = JSON.stringify(text.substring(1, text.length - 1));\n else\n token.css = text;\n pos = end;\n if (pos < selector.length && selector[pos] === '#') {\n pos++;\n let end = pos;\n while (end < selector.length && selector[end] >= '0' && selector[end] <= '9')\n end++;\n if (end === pos)\n return pos;\n const num = Number(selector.substring(pos, end));\n if (isNaN(num))\n return pos;\n token.index = num;\n pos = end;\n }\n tokens.push(token);\n }\n return tokens;\n}\nfunction pathFromRoot(root, targetElement) {\n let target = targetElement;\n const path = [target];\n while (target !== root) {\n if (!target.parentNode || target.parentNode.nodeType !== 1 /* Node.ELEMENT_NODE */ && target.parentNode.nodeType !== 11 /* Node.DOCUMENT_FRAGMENT_NODE */)\n throw new Error('Target does not belong to the root subtree');\n target = target.parentNode;\n path.push(target);\n }\n path.reverse();\n return path;\n}\nfunction detectLists(root, shouldConsider, getBox) {\n const lists = new Map();\n const add = (map, element, key) => {\n let list = map.get(key);\n if (!list) {\n list = [];\n map.set(key, list);\n }\n list.push(element);\n };\n const mark = (parent, map, used) => {\n for (let list of map.values()) {\n list = list.filter(item => !used.has(item));\n if (list.length < 2)\n continue;\n let collection = lists.get(parent);\n if (!collection) {\n collection = [];\n lists.set(parent, collection);\n }\n collection.push(list);\n list.forEach(item => used.add(item));\n }\n };\n // hashes list: s, vh, v, h\n const kHashes = 4;\n const visit = (element, produceHashes) => {\n const consider = shouldConsider(element);\n let size = 1;\n let maps;\n if (consider)\n maps = new Array(kHashes).fill(0).map(_ => new Map());\n let structure;\n if (produceHashes)\n structure = [element.nodeName];\n for (let child = element.firstElementChild; child; child = child.nextElementSibling) {\n const childResult = visit(child, consider);\n size += childResult.size;\n if (consider) {\n for (let i = 0; i < childResult.hashes.length; i++) {\n if (childResult.hashes[i])\n add(maps[i], child, childResult.hashes[i]);\n }\n }\n if (structure)\n structure.push(child.nodeName);\n }\n if (consider) {\n const used = new Set();\n maps.forEach(map => mark(element, map, used));\n }\n let hashes;\n if (produceHashes) {\n const box = getBox(element);\n hashes = [];\n hashes.push((structure.length >= 4) || (size >= 10) ? structure.join('') : '');\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.width | 0}`);\n if (size <= 5)\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.width | 0},${box.left | 0}`);\n else\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.width | 0},${box.left | 0},${2 * Math.log(box.height) | 0}`);\n if (size <= 5)\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.top | 0}`);\n else\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.top | 0},${2 * Math.log(box.width) | 0}`);\n }\n return { size, hashes };\n };\n visit(root, false);\n return lists;\n}\nconst defaultOptions = {\n genericTagScore: 10,\n textScore: 1,\n imgAltScore: 2,\n ariaLabelScore: 2,\n detectLists: true,\n avoidShortText: false,\n usePlaceholders: true,\n debug: false,\n};\nfunction parentOrRoot(element) {\n return element.parentNode;\n}\nclass Engine {\n constructor(options = defaultOptions) {\n this._cues = new Map();\n this._metrics = new Map();\n this.options = options;\n }\n query(root, selector, all) {\n const tokens = tokenize(selector);\n if (typeof tokens === 'number')\n throw new Error('Cannot parse selector at position ' + tokens);\n if (!tokens.length)\n throw new Error('Empty selector');\n if (!this._cues.has(root)) {\n const cueMap = new Map();\n const pathCues = this._preprocess(root, [root], Infinity).pathCues;\n for (const [text, cue] of pathCues) {\n cueMap.set(text, {\n type: cue.type,\n score: cue.score,\n elements: cue.elements[0]\n });\n }\n this._cues.set(root, cueMap);\n }\n // Map from the element to the boundary used. We never go outside the boundary when doing '~'.\n let currentStep = new Map();\n currentStep.set(root, root);\n for (const token of tokens) {\n const nextStep = new Map();\n for (let [element, boundary] of currentStep) {\n let next = [];\n if (token.combinator === '^') {\n if (element === boundary) {\n next = [];\n }\n else {\n const parent = parentOrRoot(element);\n next = parent ? [parent] : [];\n }\n }\n else if (token.combinator === '>') {\n boundary = element;\n next = this._matchChildren(element, token, all);\n }\n else if (token.combinator === '') {\n boundary = element;\n next = this._matchSubtree(element, token, all);\n }\n else if (token.combinator === '~') {\n while (true) {\n next = this._matchSubtree(element, token, all);\n if (next.length) {\n // Further '~' / '^' will not go outside of this boundary, which is\n // a container with both the cue and the target elements inside.\n boundary = element;\n break;\n }\n if (element === boundary)\n break;\n element = parentOrRoot(element);\n }\n }\n for (const nextElement of next) {\n if (!nextStep.has(nextElement))\n nextStep.set(nextElement, boundary);\n }\n }\n currentStep = nextStep;\n }\n return Array.from(currentStep.keys()).filter(e => e.nodeType === 1 /* Node.ELEMENT_NODE */);\n }\n create(root, target, type) {\n const path = pathFromRoot(root, target);\n const maxCueCount = type === 'notext' ? 50 : 10;\n const { pathCues, lcaMap } = this._preprocess(root, path, maxCueCount);\n const lists = this.options.detectLists ?\n this._buildLists(root, path) : undefined;\n const queue = path.map(_ => new Map());\n const startStep = {\n token: { combinator: '' },\n element: root,\n depth: 0,\n score: 0,\n totalScore: 0\n };\n for (let stepDepth = -1; stepDepth < path.length; stepDepth++) {\n const stepsMap = stepDepth === -1 ? new Map([[undefined, startStep]]) : queue[stepDepth];\n const ancestorDepth = stepDepth === -1 ? 0 : stepDepth;\n for (const [text, cue] of pathCues) {\n const elements = cue.elements[ancestorDepth];\n for (let index = 0; index < elements.length; index++) {\n const element = elements[index];\n const lca = lcaMap.get(element);\n const lcaDepth = lca.lcaDepth;\n // Always go deeper in the tree.\n if (lcaDepth <= stepDepth)\n continue;\n // 'notext' - do not use elements from the target's subtree.\n if (type === 'notext' && lcaDepth === path.length - 1 && lca.depth > 0)\n continue;\n // 'notext' - do not use target's own text.\n if (type === 'notext' && lcaDepth === path.length - 1 && !lca.depth && cue.type !== 'tag')\n continue;\n const targetAnchor = path[lcaDepth + 1];\n if (lists && lca.anchor && targetAnchor && lca.anchor !== targetAnchor) {\n const oldList = lists.get(lca.anchor);\n // Do not use cues from sibling list items (lca.anchor and targetAnchor).\n if (oldList && oldList === lists.get(targetAnchor))\n continue;\n }\n if (cue.type !== 'tag' && !this._isVisible(element))\n continue;\n const distanceToTarget = path.length - stepDepth;\n // Short text can be used more effectively in a smaller scope.\n let shortTextScore = 0;\n if (this.options.avoidShortText && cue.type === 'text')\n shortTextScore = Math.max(0, distanceToTarget - 2 * (text.length - 2));\n const score = (cue.score + shortTextScore) * (\n // Unique cues are heavily favored.\n 1 * (index + elements.length * 1000) +\n // Larger text is preferred.\n 5 * (cue.type === 'text' ? this._elementMetrics(element).fontMetric : 1) +\n // The closer to the target, the better.\n 1 * lca.depth);\n for (const [anchor, step] of stepsMap) {\n // This ensures uniqueness when resolving the selector.\n if (anchor && (cue.anchorCount.get(anchor) || 0) > index)\n continue;\n let newStep = {\n token: {\n combinator: stepDepth === -1 ? '' : '~',\n text: cue.type === 'text' ? text : undefined,\n css: cue.type === 'text' ? undefined : text,\n index: index || undefined,\n },\n previous: step,\n depth: lca.depth,\n element,\n score,\n totalScore: step.totalScore + score\n };\n let nextStep = queue[lcaDepth].get(lca.anchor);\n if (!nextStep || nextStep.totalScore > newStep.totalScore)\n queue[lcaDepth].set(lca.anchor, newStep);\n // Try going to the ancestor.\n if (newStep.depth) {\n newStep = {\n token: { combinator: '^' },\n previous: newStep,\n depth: 0,\n element: lca.lca,\n score: 2000 * newStep.depth,\n totalScore: newStep.totalScore + 2000 * newStep.depth,\n repeat: newStep.depth\n };\n nextStep = queue[lcaDepth].get(undefined);\n if (!nextStep || nextStep.totalScore > newStep.totalScore)\n queue[lcaDepth].set(undefined, newStep);\n }\n }\n }\n }\n }\n let best;\n for (const [, step] of queue[path.length - 1]) {\n if (!best || step.totalScore < best.totalScore)\n best = step;\n }\n if (!best)\n return '';\n const tokens = new Array(best.depth).fill({ combinator: '^' });\n while (best && best !== startStep) {\n for (let repeat = best.repeat || 1; repeat; repeat--)\n tokens.push(best.token);\n best = best.previous;\n }\n tokens.reverse();\n return this._serialize(tokens);\n }\n _textMetric(text) {\n // Text which looks like a float number or counter is most likely volatile.\n if (/^\\$?[\\d,]+(\\.\\d+|(\\.\\d+)?[kKmMbBgG])?$/.test(text))\n return 12;\n const num = Number(text);\n // Large numbers are likely volatile.\n if (!isNaN(num) && (num >= 32 || num < 0))\n return 6;\n return 1;\n }\n _elementMetrics(element) {\n let metrics = this._metrics.get(element);\n if (!metrics) {\n const style = element.ownerDocument ?\n element.ownerDocument.defaultView.getComputedStyle(element) :\n {};\n const box = element.getBoundingClientRect();\n const fontSize = (parseInt(style.fontSize || '', 10) || 12) / 12; // default 12 px\n const fontWeight = (parseInt(style.fontWeight || '', 10) || 400) / 400; // default normal weight\n let fontMetric = fontSize * (1 + (fontWeight - 1) / 5);\n fontMetric = 1 / Math.exp(fontMetric - 1);\n metrics = { box, style, fontMetric };\n this._metrics.set(element, metrics);\n }\n return metrics;\n }\n _isVisible(element) {\n const metrics = this._elementMetrics(element);\n return metrics.box.width > 1 && metrics.box.height > 1;\n }\n _preprocess(root, path, maxCueCount) {\n const pathCues = new Map();\n const lcaMap = new Map();\n const textScore = this.options.textScore || 1;\n const appendCue = (text, type, score, element, lca, textValue) => {\n let pathCue = pathCues.get(text);\n if (!pathCue) {\n pathCue = { type, score: (textValue ? this._textMetric(textValue) : 1) * score, elements: [], anchorCount: new Map() };\n for (let i = 0; i < path.length; i++)\n pathCue.elements.push([]);\n pathCues.set(text, pathCue);\n }\n for (let index = lca.lcaDepth; index >= 0; index--) {\n const elements = pathCue.elements[index];\n if (elements.length < maxCueCount)\n elements.push(element);\n }\n if (lca.anchor)\n pathCue.anchorCount.set(lca.anchor, 1 + (pathCue.anchorCount.get(lca.anchor) || 0));\n };\n const appendElementCues = (element, lca, detached) => {\n const nodeName = element.nodeName;\n if (!detached && this.options.usePlaceholders && nodeName === 'INPUT') {\n const placeholder = element.getAttribute('placeholder');\n if (placeholder)\n appendCue(JSON.stringify(placeholder), 'text', textScore, element, lca, placeholder);\n }\n if (!detached && nodeName === 'INPUT' && element.getAttribute('type') === 'button') {\n const value = element.getAttribute('value');\n if (value)\n appendCue(JSON.stringify(value), 'text', textScore, element, lca, value);\n }\n if (!nodeName.startsWith('<pseudo') && !nodeName.startsWith('::'))\n appendCue(nodeName, 'tag', this.options.genericTagScore, element, lca, '');\n if (this.options.imgAltScore && nodeName === 'IMG') {\n const alt = element.getAttribute('alt');\n if (alt)\n appendCue(`img[alt=${JSON.stringify(alt)}]`, 'imgAlt', this.options.imgAltScore, element, lca, alt);\n }\n if (this.options.ariaLabelScore) {\n const ariaLabel = element.getAttribute('aira-label');\n if (ariaLabel)\n appendCue(JSON.stringify(`[aria-label=${JSON.stringify(ariaLabel)}]`), 'ariaLabel', this.options.ariaLabelScore, element, lca, ariaLabel);\n }\n };\n const visit = (element, lca, depth) => {\n // Check for elements STYLE, NOSCRIPT, SCRIPT, OPTION and other elements\n // that have |display:none| behavior.\n const detached = !element.offsetParent;\n if (element.nodeType === 1 /* Node.ELEMENT_NODE */)\n appendElementCues(element, lca, detached);\n lcaMap.set(element, lca);\n for (let childNode = element.firstChild; childNode; childNode = childNode.nextSibling) {\n if (element.nodeType === 1 /* Node.ELEMENT_NODE */ && !detached && childNode.nodeType === 3 /* Node.TEXT_NODE */ && childNode.nodeValue) {\n const textValue = childNode.nodeValue.trim();\n if (textValue)\n appendCue(JSON.stringify(textValue), 'text', textScore, element, lca, textValue);\n }\n if (childNode.nodeType !== 1 /* Node.ELEMENT_NODE */)\n continue;\n const childElement = childNode;\n if (childElement.nodeName.startsWith('<pseudo:'))\n continue;\n if (path[depth + 1] === childElement) {\n const childLca = { depth: 0, lca: childElement, lcaDepth: depth + 1, anchor: undefined };\n visit(childElement, childLca, depth + 1);\n }\n else {\n const childLca = { depth: lca.depth + 1, lca: lca.lca, lcaDepth: lca.lcaDepth, anchor: lca.anchor || element };\n visit(childElement, childLca, depth + 1);\n }\n }\n };\n visit(root, { depth: 0, lca: root, lcaDepth: 0, anchor: undefined }, 0);\n return { pathCues: pathCues, lcaMap };\n }\n _filterCues(cues, root) {\n const result = new Map();\n for (const [text, cue] of cues) {\n const filtered = cue.elements.filter(element => root.contains(element));\n if (!filtered.length)\n continue;\n const newCue = { type: cue.type, score: cue.score, elements: filtered };\n result.set(text, newCue);\n }\n return result;\n }\n _buildLists(root, path) {\n const pathSet = new Set(path);\n const map = detectLists(root, e => pathSet.has(e), e => this._elementMetrics(e).box);\n const result = new Map();\n let listNumber = 1;\n for (const collection of map.values()) {\n for (const list of collection) {\n for (const child of list)\n result.set(child, listNumber);\n ++listNumber;\n }\n }\n return result;\n }\n _matchChildren(parent, token, all) {\n const result = [];\n if (token.index !== undefined)\n all = false;\n let index = token.index || 0;\n if (token.css !== undefined) {\n for (let child = parent.firstElementChild; child; child = child.nextElementSibling) {\n if (child.matches(token.css) && (all || !index--)) {\n result.push(child);\n if (!all)\n return result;\n }\n }\n return result;\n }\n if (token.text !== undefined) {\n const cue = this._getCues(parent).get(token.text);\n if (!cue || cue.type !== 'text')\n return [];\n for (const element of cue.elements) {\n if (parentOrRoot(element) === parent && (all || !index--)) {\n result.push(element);\n if (!all)\n return result;\n }\n }\n return result;\n }\n throw new Error('Unsupported token');\n }\n _matchSubtree(root, token, all) {\n const result = [];\n if (token.index !== undefined)\n all = false;\n let index = token.index || 0;\n if (token.css !== undefined) {\n if (root.nodeType === 1 /* Node.ELEMENT_NODE */) {\n const rootElement = root;\n if (rootElement.matches(token.css) && (all || !index--)) {\n result.push(rootElement);\n if (!all)\n return result;\n }\n }\n const queried = root.querySelectorAll(token.css);\n if (all)\n result.push(...Array.from(queried));\n else if (queried.length > index)\n result.push(queried.item(index));\n return result;\n }\n if (token.text !== undefined) {\n const texts = this._getCues(root);\n const cue = texts.get(token.text);\n if (!cue || cue.type !== 'text')\n return result;\n if (all)\n return cue.elements;\n if (index < cue.elements.length)\n result.push(cue.elements[index]);\n return result;\n }\n throw new Error('Unsupported token');\n }\n _getCues(element) {\n if (!this._cues.has(element)) {\n let parent = element;\n while (!this._cues.has(parent))\n parent = parentOrRoot(parent);\n this._cues.set(element, this._filterCues(this._cues.get(parent), element));\n }\n return this._cues.get(element);\n }\n _serialize(tokens) {\n const result = tokens.map(token => (token.combinator === '' ? ' ' : token.combinator) +\n (token.text !== undefined ? token.text : '') +\n (token.css !== undefined ? token.css : '') +\n (token.index !== undefined ? '#' + token.index : '')).join('');\n if (result[0] !== ' ')\n throw new Error('First token is wrong');\n return result.substring(1);\n }\n}\nconst ZSSelectorEngine = {\n name: 'zs',\n create(root, element, type) {\n return new Engine().create(root, element, type || 'default');\n },\n query(root, selector) {\n return new Engine().query(root, selector, false /* all */)[0];\n },\n queryAll(root, selector) {\n return new Engine().query(root, selector, true /* all */);\n }\n};\nZSSelectorEngine.test = () => {\n const elements = Array.from(document.querySelectorAll('*')).slice(1500, 2000);\n console.time('test'); // eslint-disable-line no-console\n const failures = elements.filter((e, index) => {\n const name = e.tagName.toUpperCase();\n if (name === 'SCRIPT' || name === 'STYLE' || name === 'NOSCRIPT' || name === 'META' || name === 'LINK' || name === 'OPTION')\n return false;\n if (index % 100 === 0)\n console.log(`${index} / ${elements.length}`); // eslint-disable-line no-console\n if (e.nodeName.toLowerCase().startsWith('<pseudo:'))\n e = e.parentElement;\n while (e && e.namespaceURI && e.namespaceURI.endsWith('svg') && e.nodeName.toLowerCase() !== 'svg')\n e = e.parentElement;\n try {\n document.documentElement.style.outline = '1px solid red';\n const selector = new Engine().create(document.documentElement, e, 'default');\n document.documentElement.style.outline = '1px solid green';\n const e2 = new Engine().query(document.documentElement, selector, false)[0];\n return e !== e2;\n }\n catch (e) {\n return false;\n }\n });\n console.timeEnd('test'); // eslint-disable-line no-console\n console.log(failures); // eslint-disable-line no-console\n};\nexports.default = ZSSelectorEngine;\n\n\n/***/ })\n\n/******/ })).default";
export declare const source = "(/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// define __esModule on exports\n/******/ \t__webpack_require__.r = function(exports) {\n/******/ \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n/******/ \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n/******/ \t\t}\n/******/ \t\tObject.defineProperty(exports, '__esModule', { value: true });\n/******/ \t};\n/******/\n/******/ \t// create a fake namespace object\n/******/ \t// mode & 1: value is a module id, require it\n/******/ \t// mode & 2: merge all properties of value into the ns\n/******/ \t// mode & 4: return value when already ns object\n/******/ \t// mode & 8|1: behave like require\n/******/ \t__webpack_require__.t = function(value, mode) {\n/******/ \t\tif(mode & 1) value = __webpack_require__(value);\n/******/ \t\tif(mode & 8) return value;\n/******/ \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n/******/ \t\tvar ns = Object.create(null);\n/******/ \t\t__webpack_require__.r(ns);\n/******/ \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n/******/ \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n/******/ \t\treturn ns;\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = \"./src/injected/zsSelectorEngine.ts\");\n/******/ })\n/************************************************************************/\n/******/ ({\n\n/***/ \"./src/injected/zsSelectorEngine.ts\":\n/*!******************************************!*\\\n !*** ./src/injected/zsSelectorEngine.ts ***!\n \\******************************************/\n/*! no static exports found */\n/***/ (function(module, exports, __webpack_require__) {\n\n\"use strict\";\n\n/**\n * Copyright (c) Microsoft Corporation.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nfunction tokenize(selector) {\n const tokens = [];\n let pos = 0;\n const skipWhitespace = () => {\n while (pos < selector.length && selector[pos] === ' ')\n pos++;\n };\n while (pos < selector.length) {\n skipWhitespace();\n if (pos === selector.length)\n break;\n if (!tokens.length && '^>~'.includes(selector[pos]))\n return pos;\n const token = { combinator: '' };\n if (selector[pos] === '^') {\n token.combinator = '^';\n tokens.push(token);\n pos++;\n continue;\n }\n if (selector[pos] === '>') {\n token.combinator = '>';\n pos++;\n skipWhitespace();\n if (pos === selector.length)\n return pos;\n }\n else if (selector[pos] === '~') {\n token.combinator = '~';\n pos++;\n skipWhitespace();\n if (pos === selector.length)\n return pos;\n }\n let text = '';\n let end = pos;\n let stringQuote;\n const isText = '`\"\\''.includes(selector[pos]);\n while (end < selector.length) {\n if (stringQuote) {\n if (selector[end] === '\\\\' && end + 1 < selector.length) {\n if (!isText)\n text += selector[end];\n text += selector[end + 1];\n end += 2;\n }\n else if (selector[end] === stringQuote) {\n text += selector[end++];\n stringQuote = undefined;\n if (isText)\n break;\n }\n else {\n text += selector[end++];\n }\n }\n else if (' >~^#'.includes(selector[end])) {\n break;\n }\n else if ('`\"\\''.includes(selector[end])) {\n stringQuote = selector[end];\n text += selector[end++];\n }\n else {\n text += selector[end++];\n }\n }\n if (stringQuote)\n return end;\n if (isText)\n token.text = JSON.stringify(text.substring(1, text.length - 1));\n else\n token.css = text;\n pos = end;\n if (pos < selector.length && selector[pos] === '#') {\n pos++;\n let end = pos;\n while (end < selector.length && selector[end] >= '0' && selector[end] <= '9')\n end++;\n if (end === pos)\n return pos;\n const num = Number(selector.substring(pos, end));\n if (isNaN(num))\n return pos;\n token.index = num;\n pos = end;\n }\n tokens.push(token);\n }\n return tokens;\n}\nfunction pathFromRoot(root, targetElement) {\n let target = targetElement;\n const path = [target];\n while (target !== root) {\n if (!target.parentNode || target.parentNode.nodeType !== 1 /* Node.ELEMENT_NODE */ && target.parentNode.nodeType !== 11 /* Node.DOCUMENT_FRAGMENT_NODE */)\n throw new Error('Target does not belong to the root subtree');\n target = target.parentNode;\n path.push(target);\n }\n path.reverse();\n return path;\n}\nfunction detectLists(root, shouldConsider, getBox) {\n const lists = new Map();\n const add = (map, element, key) => {\n let list = map.get(key);\n if (!list) {\n list = [];\n map.set(key, list);\n }\n list.push(element);\n };\n const mark = (parent, map, used) => {\n for (let list of map.values()) {\n list = list.filter(item => !used.has(item));\n if (list.length < 2)\n continue;\n let collection = lists.get(parent);\n if (!collection) {\n collection = [];\n lists.set(parent, collection);\n }\n collection.push(list);\n list.forEach(item => used.add(item));\n }\n };\n // hashes list: s, vh, v, h\n const kHashes = 4;\n const visit = (element, produceHashes) => {\n const consider = shouldConsider(element);\n let size = 1;\n let maps;\n if (consider)\n maps = new Array(kHashes).fill(0).map(_ => new Map());\n let structure;\n if (produceHashes)\n structure = [element.nodeName];\n for (let child = element.firstElementChild; child; child = child.nextElementSibling) {\n const childResult = visit(child, consider);\n size += childResult.size;\n if (consider) {\n for (let i = 0; i < childResult.hashes.length; i++) {\n if (childResult.hashes[i])\n add(maps[i], child, childResult.hashes[i]);\n }\n }\n if (structure)\n structure.push(child.nodeName);\n }\n if (consider) {\n const used = new Set();\n maps.forEach(map => mark(element, map, used));\n }\n let hashes;\n if (produceHashes) {\n const box = getBox(element);\n hashes = [];\n hashes.push((structure.length >= 4) || (size >= 10) ? structure.join('') : '');\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.width | 0}`);\n if (size <= 5)\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.width | 0},${box.left | 0}`);\n else\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.width | 0},${box.left | 0},${2 * Math.log(box.height) | 0}`);\n if (size <= 5)\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.top | 0}`);\n else\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.top | 0},${2 * Math.log(box.width) | 0}`);\n }\n return { size, hashes };\n };\n visit(root, false);\n return lists;\n}\nconst defaultOptions = {\n genericTagScore: 10,\n textScore: 1,\n imgAltScore: 2,\n ariaLabelScore: 2,\n detectLists: true,\n avoidShortText: false,\n usePlaceholders: true,\n debug: false,\n};\nfunction parentOrRoot(element) {\n return element.parentNode;\n}\nclass Engine {\n constructor(options = defaultOptions) {\n this._cues = new Map();\n this._metrics = new Map();\n this.options = options;\n }\n query(root, selector, all) {\n const tokens = tokenize(selector);\n if (typeof tokens === 'number')\n throw new Error('Cannot parse selector at position ' + tokens);\n if (!tokens.length)\n throw new Error('Empty selector');\n if (!this._cues.has(root)) {\n const cueMap = new Map();\n const pathCues = this._preprocess(root, [root], Infinity).pathCues;\n for (const [text, cue] of pathCues) {\n cueMap.set(text, {\n type: cue.type,\n score: cue.score,\n elements: cue.elements[0]\n });\n }\n this._cues.set(root, cueMap);\n }\n // Map from the element to the boundary used. We never go outside the boundary when doing '~'.\n let currentStep = new Map();\n currentStep.set(root, root);\n for (const token of tokens) {\n const nextStep = new Map();\n for (let [element, boundary] of currentStep) {\n let next = [];\n if (token.combinator === '^') {\n if (element === boundary) {\n next = [];\n }\n else {\n const parent = parentOrRoot(element);\n next = parent ? [parent] : [];\n }\n }\n else if (token.combinator === '>') {\n boundary = element;\n next = this._matchChildren(element, token, all);\n }\n else if (token.combinator === '') {\n boundary = element;\n next = this._matchSubtree(element, token, all);\n }\n else if (token.combinator === '~') {\n while (true) {\n next = this._matchSubtree(element, token, all);\n if (next.length) {\n // Further '~' / '^' will not go outside of this boundary, which is\n // a container with both the cue and the target elements inside.\n boundary = element;\n break;\n }\n if (element === boundary)\n break;\n element = parentOrRoot(element);\n }\n }\n for (const nextElement of next) {\n if (!nextStep.has(nextElement))\n nextStep.set(nextElement, boundary);\n }\n }\n currentStep = nextStep;\n }\n return Array.from(currentStep.keys()).filter(e => e.nodeType === 1 /* Node.ELEMENT_NODE */);\n }\n create(root, target, type) {\n const path = pathFromRoot(root, target);\n const maxCueCount = type === 'notext' ? 50 : 10;\n const { pathCues, lcaMap } = this._preprocess(root, path, maxCueCount);\n const lists = this.options.detectLists ?\n this._buildLists(root, path) : undefined;\n const queue = path.map(_ => new Map());\n const startStep = {\n token: { combinator: '' },\n element: root,\n depth: 0,\n score: 0,\n totalScore: 0\n };\n for (let stepDepth = -1; stepDepth < path.length; stepDepth++) {\n const stepsMap = stepDepth === -1 ? new Map([[undefined, startStep]]) : queue[stepDepth];\n const ancestorDepth = stepDepth === -1 ? 0 : stepDepth;\n for (const [text, cue] of pathCues) {\n const elements = cue.elements[ancestorDepth];\n for (let index = 0; index < elements.length; index++) {\n const element = elements[index];\n const lca = lcaMap.get(element);\n const lcaDepth = lca.lcaDepth;\n // Always go deeper in the tree.\n if (lcaDepth <= stepDepth)\n continue;\n // 'notext' - do not use elements from the target's subtree.\n if (type === 'notext' && lcaDepth === path.length - 1 && lca.depth > 0)\n continue;\n // 'notext' - do not use target's own text.\n if (type === 'notext' && lcaDepth === path.length - 1 && !lca.depth && cue.type !== 'tag')\n continue;\n const targetAnchor = path[lcaDepth + 1];\n if (lists && lca.anchor && targetAnchor && lca.anchor !== targetAnchor) {\n const oldList = lists.get(lca.anchor);\n // Do not use cues from sibling list items (lca.anchor and targetAnchor).\n if (oldList && oldList === lists.get(targetAnchor))\n continue;\n }\n if (cue.type !== 'tag' && !this._isVisible(element))\n continue;\n const distanceToTarget = path.length - stepDepth;\n // Short text can be used more effectively in a smaller scope.\n let shortTextScore = 0;\n if (this.options.avoidShortText && cue.type === 'text')\n shortTextScore = Math.max(0, distanceToTarget - 2 * (text.length - 2));\n const score = (cue.score + shortTextScore) * (\n // Unique cues are heavily favored.\n 1 * (index + elements.length * 1000) +\n // Larger text is preferred.\n 5 * (cue.type === 'text' ? this._elementMetrics(element).fontMetric : 1) +\n // The closer to the target, the better.\n 1 * lca.depth);\n for (const [anchor, step] of stepsMap) {\n // This ensures uniqueness when resolving the selector.\n if (anchor && (cue.anchorCount.get(anchor) || 0) > index)\n continue;\n let newStep = {\n token: {\n combinator: stepDepth === -1 ? '' : '~',\n text: cue.type === 'text' ? text : undefined,\n css: cue.type === 'text' ? undefined : text,\n index: index || undefined,\n },\n previous: step,\n depth: lca.depth,\n element,\n score,\n totalScore: step.totalScore + score\n };\n let nextStep = queue[lcaDepth].get(lca.anchor);\n if (!nextStep || nextStep.totalScore > newStep.totalScore)\n queue[lcaDepth].set(lca.anchor, newStep);\n // Try going to the ancestor.\n if (newStep.depth) {\n newStep = {\n token: { combinator: '^' },\n previous: newStep,\n depth: 0,\n element: lca.lca,\n score: 2000 * newStep.depth,\n totalScore: newStep.totalScore + 2000 * newStep.depth,\n repeat: newStep.depth\n };\n nextStep = queue[lcaDepth].get(undefined);\n if (!nextStep || nextStep.totalScore > newStep.totalScore)\n queue[lcaDepth].set(undefined, newStep);\n }\n }\n }\n }\n }\n let best;\n for (const [, step] of queue[path.length - 1]) {\n if (!best || step.totalScore < best.totalScore)\n best = step;\n }\n if (!best)\n return '';\n const tokens = new Array(best.depth).fill({ combinator: '^' });\n while (best && best !== startStep) {\n for (let repeat = best.repeat || 1; repeat; repeat--)\n tokens.push(best.token);\n best = best.previous;\n }\n tokens.reverse();\n return this._serialize(tokens);\n }\n _textMetric(text) {\n // Text which looks like a float number or counter is most likely volatile.\n if (/^\\$?[\\d,]+(\\.\\d+|(\\.\\d+)?[kKmMbBgG])?$/.test(text))\n return 12;\n const num = Number(text);\n // Large numbers are likely volatile.\n if (!isNaN(num) && (num >= 32 || num < 0))\n return 6;\n return 1;\n }\n _elementMetrics(element) {\n let metrics = this._metrics.get(element);\n if (!metrics) {\n const style = element.ownerDocument ?\n element.ownerDocument.defaultView.getComputedStyle(element) :\n {};\n const box = element.getBoundingClientRect();\n const fontSize = (parseInt(style.fontSize || '', 10) || 12) / 12; // default 12 px\n const fontWeight = (parseInt(style.fontWeight || '', 10) || 400) / 400; // default normal weight\n let fontMetric = fontSize * (1 + (fontWeight - 1) / 5);\n fontMetric = 1 / Math.exp(fontMetric - 1);\n metrics = { box, style, fontMetric };\n this._metrics.set(element, metrics);\n }\n return metrics;\n }\n _isVisible(element) {\n const metrics = this._elementMetrics(element);\n return metrics.box.width > 1 && metrics.box.height > 1;\n }\n _preprocess(root, path, maxCueCount) {\n const pathCues = new Map();\n const lcaMap = new Map();\n const textScore = this.options.textScore || 1;\n const appendCue = (text, type, score, element, lca, textValue) => {\n let pathCue = pathCues.get(text);\n if (!pathCue) {\n pathCue = { type, score: (textValue ? this._textMetric(textValue) : 1) * score, elements: [], anchorCount: new Map() };\n for (let i = 0; i < path.length; i++)\n pathCue.elements.push([]);\n pathCues.set(text, pathCue);\n }\n for (let index = lca.lcaDepth; index >= 0; index--) {\n const elements = pathCue.elements[index];\n if (elements.length < maxCueCount)\n elements.push(element);\n }\n if (lca.anchor)\n pathCue.anchorCount.set(lca.anchor, 1 + (pathCue.anchorCount.get(lca.anchor) || 0));\n };\n const appendElementCues = (element, lca, detached) => {\n const nodeName = element.nodeName;\n if (!detached && this.options.usePlaceholders && nodeName === 'INPUT') {\n const placeholder = element.getAttribute('placeholder');\n if (placeholder)\n appendCue(JSON.stringify(placeholder), 'text', textScore, element, lca, placeholder);\n }\n if (!detached && nodeName === 'INPUT' && element.getAttribute('type') === 'button') {\n const value = element.getAttribute('value');\n if (value)\n appendCue(JSON.stringify(value), 'text', textScore, element, lca, value);\n }\n if (!nodeName.startsWith('<pseudo') && !nodeName.startsWith('::'))\n appendCue(nodeName, 'tag', this.options.genericTagScore, element, lca, '');\n if (this.options.imgAltScore && nodeName === 'IMG') {\n const alt = element.getAttribute('alt');\n if (alt)\n appendCue(`img[alt=${JSON.stringify(alt)}]`, 'imgAlt', this.options.imgAltScore, element, lca, alt);\n }\n if (this.options.ariaLabelScore) {\n const ariaLabel = element.getAttribute('aria-label');\n if (ariaLabel)\n appendCue(JSON.stringify(`[aria-label=${JSON.stringify(ariaLabel)}]`), 'ariaLabel', this.options.ariaLabelScore, element, lca, ariaLabel);\n }\n };\n const visit = (element, lca, depth) => {\n // Check for elements STYLE, NOSCRIPT, SCRIPT, OPTION and other elements\n // that have |display:none| behavior.\n const detached = !element.offsetParent;\n if (element.nodeType === 1 /* Node.ELEMENT_NODE */)\n appendElementCues(element, lca, detached);\n lcaMap.set(element, lca);\n for (let childNode = element.firstChild; childNode; childNode = childNode.nextSibling) {\n if (element.nodeType === 1 /* Node.ELEMENT_NODE */ && !detached && childNode.nodeType === 3 /* Node.TEXT_NODE */ && childNode.nodeValue) {\n const textValue = childNode.nodeValue.trim();\n if (textValue)\n appendCue(JSON.stringify(textValue), 'text', textScore, element, lca, textValue);\n }\n if (childNode.nodeType !== 1 /* Node.ELEMENT_NODE */)\n continue;\n const childElement = childNode;\n if (childElement.nodeName.startsWith('<pseudo:'))\n continue;\n if (path[depth + 1] === childElement) {\n const childLca = { depth: 0, lca: childElement, lcaDepth: depth + 1, anchor: undefined };\n visit(childElement, childLca, depth + 1);\n }\n else {\n const childLca = { depth: lca.depth + 1, lca: lca.lca, lcaDepth: lca.lcaDepth, anchor: lca.anchor || element };\n visit(childElement, childLca, depth + 1);\n }\n }\n };\n visit(root, { depth: 0, lca: root, lcaDepth: 0, anchor: undefined }, 0);\n return { pathCues: pathCues, lcaMap };\n }\n _filterCues(cues, root) {\n const result = new Map();\n for (const [text, cue] of cues) {\n const filtered = cue.elements.filter(element => root.contains(element));\n if (!filtered.length)\n continue;\n const newCue = { type: cue.type, score: cue.score, elements: filtered };\n result.set(text, newCue);\n }\n return result;\n }\n _buildLists(root, path) {\n const pathSet = new Set(path);\n const map = detectLists(root, e => pathSet.has(e), e => this._elementMetrics(e).box);\n const result = new Map();\n let listNumber = 1;\n for (const collection of map.values()) {\n for (const list of collection) {\n for (const child of list)\n result.set(child, listNumber);\n ++listNumber;\n }\n }\n return result;\n }\n _matchChildren(parent, token, all) {\n const result = [];\n if (token.index !== undefined)\n all = false;\n let index = token.index || 0;\n if (token.css !== undefined) {\n for (let child = parent.firstElementChild; child; child = child.nextElementSibling) {\n if (child.matches(token.css) && (all || !index--)) {\n result.push(child);\n if (!all)\n return result;\n }\n }\n return result;\n }\n if (token.text !== undefined) {\n const cue = this._getCues(parent).get(token.text);\n if (!cue || cue.type !== 'text')\n return [];\n for (const element of cue.elements) {\n if (parentOrRoot(element) === parent && (all || !index--)) {\n result.push(element);\n if (!all)\n return result;\n }\n }\n return result;\n }\n throw new Error('Unsupported token');\n }\n _matchSubtree(root, token, all) {\n const result = [];\n if (token.index !== undefined)\n all = false;\n let index = token.index || 0;\n if (token.css !== undefined) {\n if (root.nodeType === 1 /* Node.ELEMENT_NODE */) {\n const rootElement = root;\n if (rootElement.matches(token.css) && (all || !index--)) {\n result.push(rootElement);\n if (!all)\n return result;\n }\n }\n const queried = root.querySelectorAll(token.css);\n if (all)\n result.push(...Array.from(queried));\n else if (queried.length > index)\n result.push(queried.item(index));\n return result;\n }\n if (token.text !== undefined) {\n const texts = this._getCues(root);\n const cue = texts.get(token.text);\n if (!cue || cue.type !== 'text')\n return result;\n if (all)\n return cue.elements;\n if (index < cue.elements.length)\n result.push(cue.elements[index]);\n return result;\n }\n throw new Error('Unsupported token');\n }\n _getCues(element) {\n if (!this._cues.has(element)) {\n let parent = element;\n while (!this._cues.has(parent))\n parent = parentOrRoot(parent);\n this._cues.set(element, this._filterCues(this._cues.get(parent), element));\n }\n return this._cues.get(element);\n }\n _serialize(tokens) {\n const result = tokens.map(token => (token.combinator === '' ? ' ' : token.combinator) +\n (token.text !== undefined ? token.text : '') +\n (token.css !== undefined ? token.css : '') +\n (token.index !== undefined ? '#' + token.index : '')).join('');\n if (result[0] !== ' ')\n throw new Error('First token is wrong');\n return result.substring(1);\n }\n}\nconst ZSSelectorEngine = {\n name: 'zs',\n create(root, element, type) {\n return new Engine().create(root, element, type || 'default');\n },\n query(root, selector) {\n return new Engine().query(root, selector, false /* all */)[0];\n },\n queryAll(root, selector) {\n return new Engine().query(root, selector, true /* all */);\n }\n};\nZSSelectorEngine.test = () => {\n const elements = Array.from(document.querySelectorAll('*')).slice(1500, 2000);\n console.time('test'); // eslint-disable-line no-console\n const failures = elements.filter((e, index) => {\n const name = e.tagName.toUpperCase();\n if (name === 'SCRIPT' || name === 'STYLE' || name === 'NOSCRIPT' || name === 'META' || name === 'LINK' || name === 'OPTION')\n return false;\n if (index % 100 === 0)\n console.log(`${index} / ${elements.length}`); // eslint-disable-line no-console\n if (e.nodeName.toLowerCase().startsWith('<pseudo:'))\n e = e.parentElement;\n while (e && e.namespaceURI && e.namespaceURI.endsWith('svg') && e.nodeName.toLowerCase() !== 'svg')\n e = e.parentElement;\n try {\n document.documentElement.style.outline = '1px solid red';\n const selector = new Engine().create(document.documentElement, e, 'default');\n document.documentElement.style.outline = '1px solid green';\n const e2 = new Engine().query(document.documentElement, selector, false)[0];\n return e !== e2;\n }\n catch (e) {\n return false;\n }\n });\n console.timeEnd('test'); // eslint-disable-line no-console\n console.log(failures); // eslint-disable-line no-console\n};\nexports.default = ZSSelectorEngine;\n\n\n/***/ })\n\n/******/ })).default";
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.source = "(/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// define __esModule on exports\n/******/ \t__webpack_require__.r = function(exports) {\n/******/ \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n/******/ \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n/******/ \t\t}\n/******/ \t\tObject.defineProperty(exports, '__esModule', { value: true });\n/******/ \t};\n/******/\n/******/ \t// create a fake namespace object\n/******/ \t// mode & 1: value is a module id, require it\n/******/ \t// mode & 2: merge all properties of value into the ns\n/******/ \t// mode & 4: return value when already ns object\n/******/ \t// mode & 8|1: behave like require\n/******/ \t__webpack_require__.t = function(value, mode) {\n/******/ \t\tif(mode & 1) value = __webpack_require__(value);\n/******/ \t\tif(mode & 8) return value;\n/******/ \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n/******/ \t\tvar ns = Object.create(null);\n/******/ \t\t__webpack_require__.r(ns);\n/******/ \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n/******/ \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n/******/ \t\treturn ns;\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = \"./src/injected/zsSelectorEngine.ts\");\n/******/ })\n/************************************************************************/\n/******/ ({\n\n/***/ \"./src/injected/zsSelectorEngine.ts\":\n/*!******************************************!*\\\n !*** ./src/injected/zsSelectorEngine.ts ***!\n \\******************************************/\n/*! no static exports found */\n/***/ (function(module, exports, __webpack_require__) {\n\n\"use strict\";\n\n/**\n * Copyright (c) Microsoft Corporation.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nfunction tokenize(selector) {\n const tokens = [];\n let pos = 0;\n const skipWhitespace = () => {\n while (pos < selector.length && selector[pos] === ' ')\n pos++;\n };\n while (pos < selector.length) {\n skipWhitespace();\n if (pos === selector.length)\n break;\n if (!tokens.length && '^>~'.includes(selector[pos]))\n return pos;\n const token = { combinator: '' };\n if (selector[pos] === '^') {\n token.combinator = '^';\n tokens.push(token);\n pos++;\n continue;\n }\n if (selector[pos] === '>') {\n token.combinator = '>';\n pos++;\n skipWhitespace();\n if (pos === selector.length)\n return pos;\n }\n else if (selector[pos] === '~') {\n token.combinator = '~';\n pos++;\n skipWhitespace();\n if (pos === selector.length)\n return pos;\n }\n let text = '';\n let end = pos;\n let stringQuote;\n const isText = '`\"\\''.includes(selector[pos]);\n while (end < selector.length) {\n if (stringQuote) {\n if (selector[end] === '\\\\' && end + 1 < selector.length) {\n if (!isText)\n text += selector[end];\n text += selector[end + 1];\n end += 2;\n }\n else if (selector[end] === stringQuote) {\n text += selector[end++];\n stringQuote = undefined;\n if (isText)\n break;\n }\n else {\n text += selector[end++];\n }\n }\n else if (' >~^#'.includes(selector[end])) {\n break;\n }\n else if ('`\"\\''.includes(selector[end])) {\n stringQuote = selector[end];\n text += selector[end++];\n }\n else {\n text += selector[end++];\n }\n }\n if (stringQuote)\n return end;\n if (isText)\n token.text = JSON.stringify(text.substring(1, text.length - 1));\n else\n token.css = text;\n pos = end;\n if (pos < selector.length && selector[pos] === '#') {\n pos++;\n let end = pos;\n while (end < selector.length && selector[end] >= '0' && selector[end] <= '9')\n end++;\n if (end === pos)\n return pos;\n const num = Number(selector.substring(pos, end));\n if (isNaN(num))\n return pos;\n token.index = num;\n pos = end;\n }\n tokens.push(token);\n }\n return tokens;\n}\nfunction pathFromRoot(root, targetElement) {\n let target = targetElement;\n const path = [target];\n while (target !== root) {\n if (!target.parentNode || target.parentNode.nodeType !== 1 /* Node.ELEMENT_NODE */ && target.parentNode.nodeType !== 11 /* Node.DOCUMENT_FRAGMENT_NODE */)\n throw new Error('Target does not belong to the root subtree');\n target = target.parentNode;\n path.push(target);\n }\n path.reverse();\n return path;\n}\nfunction detectLists(root, shouldConsider, getBox) {\n const lists = new Map();\n const add = (map, element, key) => {\n let list = map.get(key);\n if (!list) {\n list = [];\n map.set(key, list);\n }\n list.push(element);\n };\n const mark = (parent, map, used) => {\n for (let list of map.values()) {\n list = list.filter(item => !used.has(item));\n if (list.length < 2)\n continue;\n let collection = lists.get(parent);\n if (!collection) {\n collection = [];\n lists.set(parent, collection);\n }\n collection.push(list);\n list.forEach(item => used.add(item));\n }\n };\n // hashes list: s, vh, v, h\n const kHashes = 4;\n const visit = (element, produceHashes) => {\n const consider = shouldConsider(element);\n let size = 1;\n let maps;\n if (consider)\n maps = new Array(kHashes).fill(0).map(_ => new Map());\n let structure;\n if (produceHashes)\n structure = [element.nodeName];\n for (let child = element.firstElementChild; child; child = child.nextElementSibling) {\n const childResult = visit(child, consider);\n size += childResult.size;\n if (consider) {\n for (let i = 0; i < childResult.hashes.length; i++) {\n if (childResult.hashes[i])\n add(maps[i], child, childResult.hashes[i]);\n }\n }\n if (structure)\n structure.push(child.nodeName);\n }\n if (consider) {\n const used = new Set();\n maps.forEach(map => mark(element, map, used));\n }\n let hashes;\n if (produceHashes) {\n const box = getBox(element);\n hashes = [];\n hashes.push((structure.length >= 4) || (size >= 10) ? structure.join('') : '');\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.width | 0}`);\n if (size <= 5)\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.width | 0},${box.left | 0}`);\n else\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.width | 0},${box.left | 0},${2 * Math.log(box.height) | 0}`);\n if (size <= 5)\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.top | 0}`);\n else\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.top | 0},${2 * Math.log(box.width) | 0}`);\n }\n return { size, hashes };\n };\n visit(root, false);\n return lists;\n}\nconst defaultOptions = {\n genericTagScore: 10,\n textScore: 1,\n imgAltScore: 2,\n ariaLabelScore: 2,\n detectLists: true,\n avoidShortText: false,\n usePlaceholders: true,\n debug: false,\n};\nfunction parentOrRoot(element) {\n return element.parentNode;\n}\nclass Engine {\n constructor(options = defaultOptions) {\n this._cues = new Map();\n this._metrics = new Map();\n this.options = options;\n }\n query(root, selector, all) {\n const tokens = tokenize(selector);\n if (typeof tokens === 'number')\n throw new Error('Cannot parse selector at position ' + tokens);\n if (!tokens.length)\n throw new Error('Empty selector');\n if (!this._cues.has(root)) {\n const cueMap = new Map();\n const pathCues = this._preprocess(root, [root], Infinity).pathCues;\n for (const [text, cue] of pathCues) {\n cueMap.set(text, {\n type: cue.type,\n score: cue.score,\n elements: cue.elements[0]\n });\n }\n this._cues.set(root, cueMap);\n }\n // Map from the element to the boundary used. We never go outside the boundary when doing '~'.\n let currentStep = new Map();\n currentStep.set(root, root);\n for (const token of tokens) {\n const nextStep = new Map();\n for (let [element, boundary] of currentStep) {\n let next = [];\n if (token.combinator === '^') {\n if (element === boundary) {\n next = [];\n }\n else {\n const parent = parentOrRoot(element);\n next = parent ? [parent] : [];\n }\n }\n else if (token.combinator === '>') {\n boundary = element;\n next = this._matchChildren(element, token, all);\n }\n else if (token.combinator === '') {\n boundary = element;\n next = this._matchSubtree(element, token, all);\n }\n else if (token.combinator === '~') {\n while (true) {\n next = this._matchSubtree(element, token, all);\n if (next.length) {\n // Further '~' / '^' will not go outside of this boundary, which is\n // a container with both the cue and the target elements inside.\n boundary = element;\n break;\n }\n if (element === boundary)\n break;\n element = parentOrRoot(element);\n }\n }\n for (const nextElement of next) {\n if (!nextStep.has(nextElement))\n nextStep.set(nextElement, boundary);\n }\n }\n currentStep = nextStep;\n }\n return Array.from(currentStep.keys()).filter(e => e.nodeType === 1 /* Node.ELEMENT_NODE */);\n }\n create(root, target, type) {\n const path = pathFromRoot(root, target);\n const maxCueCount = type === 'notext' ? 50 : 10;\n const { pathCues, lcaMap } = this._preprocess(root, path, maxCueCount);\n const lists = this.options.detectLists ?\n this._buildLists(root, path) : undefined;\n const queue = path.map(_ => new Map());\n const startStep = {\n token: { combinator: '' },\n element: root,\n depth: 0,\n score: 0,\n totalScore: 0\n };\n for (let stepDepth = -1; stepDepth < path.length; stepDepth++) {\n const stepsMap = stepDepth === -1 ? new Map([[undefined, startStep]]) : queue[stepDepth];\n const ancestorDepth = stepDepth === -1 ? 0 : stepDepth;\n for (const [text, cue] of pathCues) {\n const elements = cue.elements[ancestorDepth];\n for (let index = 0; index < elements.length; index++) {\n const element = elements[index];\n const lca = lcaMap.get(element);\n const lcaDepth = lca.lcaDepth;\n // Always go deeper in the tree.\n if (lcaDepth <= stepDepth)\n continue;\n // 'notext' - do not use elements from the target's subtree.\n if (type === 'notext' && lcaDepth === path.length - 1 && lca.depth > 0)\n continue;\n // 'notext' - do not use target's own text.\n if (type === 'notext' && lcaDepth === path.length - 1 && !lca.depth && cue.type !== 'tag')\n continue;\n const targetAnchor = path[lcaDepth + 1];\n if (lists && lca.anchor && targetAnchor && lca.anchor !== targetAnchor) {\n const oldList = lists.get(lca.anchor);\n // Do not use cues from sibling list items (lca.anchor and targetAnchor).\n if (oldList && oldList === lists.get(targetAnchor))\n continue;\n }\n if (cue.type !== 'tag' && !this._isVisible(element))\n continue;\n const distanceToTarget = path.length - stepDepth;\n // Short text can be used more effectively in a smaller scope.\n let shortTextScore = 0;\n if (this.options.avoidShortText && cue.type === 'text')\n shortTextScore = Math.max(0, distanceToTarget - 2 * (text.length - 2));\n const score = (cue.score + shortTextScore) * (\n // Unique cues are heavily favored.\n 1 * (index + elements.length * 1000) +\n // Larger text is preferred.\n 5 * (cue.type === 'text' ? this._elementMetrics(element).fontMetric : 1) +\n // The closer to the target, the better.\n 1 * lca.depth);\n for (const [anchor, step] of stepsMap) {\n // This ensures uniqueness when resolving the selector.\n if (anchor && (cue.anchorCount.get(anchor) || 0) > index)\n continue;\n let newStep = {\n token: {\n combinator: stepDepth === -1 ? '' : '~',\n text: cue.type === 'text' ? text : undefined,\n css: cue.type === 'text' ? undefined : text,\n index: index || undefined,\n },\n previous: step,\n depth: lca.depth,\n element,\n score,\n totalScore: step.totalScore + score\n };\n let nextStep = queue[lcaDepth].get(lca.anchor);\n if (!nextStep || nextStep.totalScore > newStep.totalScore)\n queue[lcaDepth].set(lca.anchor, newStep);\n // Try going to the ancestor.\n if (newStep.depth) {\n newStep = {\n token: { combinator: '^' },\n previous: newStep,\n depth: 0,\n element: lca.lca,\n score: 2000 * newStep.depth,\n totalScore: newStep.totalScore + 2000 * newStep.depth,\n repeat: newStep.depth\n };\n nextStep = queue[lcaDepth].get(undefined);\n if (!nextStep || nextStep.totalScore > newStep.totalScore)\n queue[lcaDepth].set(undefined, newStep);\n }\n }\n }\n }\n }\n let best;\n for (const [, step] of queue[path.length - 1]) {\n if (!best || step.totalScore < best.totalScore)\n best = step;\n }\n if (!best)\n return '';\n const tokens = new Array(best.depth).fill({ combinator: '^' });\n while (best && best !== startStep) {\n for (let repeat = best.repeat || 1; repeat; repeat--)\n tokens.push(best.token);\n best = best.previous;\n }\n tokens.reverse();\n return this._serialize(tokens);\n }\n _textMetric(text) {\n // Text which looks like a float number or counter is most likely volatile.\n if (/^\\$?[\\d,]+(\\.\\d+|(\\.\\d+)?[kKmMbBgG])?$/.test(text))\n return 12;\n const num = Number(text);\n // Large numbers are likely volatile.\n if (!isNaN(num) && (num >= 32 || num < 0))\n return 6;\n return 1;\n }\n _elementMetrics(element) {\n let metrics = this._metrics.get(element);\n if (!metrics) {\n const style = element.ownerDocument ?\n element.ownerDocument.defaultView.getComputedStyle(element) :\n {};\n const box = element.getBoundingClientRect();\n const fontSize = (parseInt(style.fontSize || '', 10) || 12) / 12; // default 12 px\n const fontWeight = (parseInt(style.fontWeight || '', 10) || 400) / 400; // default normal weight\n let fontMetric = fontSize * (1 + (fontWeight - 1) / 5);\n fontMetric = 1 / Math.exp(fontMetric - 1);\n metrics = { box, style, fontMetric };\n this._metrics.set(element, metrics);\n }\n return metrics;\n }\n _isVisible(element) {\n const metrics = this._elementMetrics(element);\n return metrics.box.width > 1 && metrics.box.height > 1;\n }\n _preprocess(root, path, maxCueCount) {\n const pathCues = new Map();\n const lcaMap = new Map();\n const textScore = this.options.textScore || 1;\n const appendCue = (text, type, score, element, lca, textValue) => {\n let pathCue = pathCues.get(text);\n if (!pathCue) {\n pathCue = { type, score: (textValue ? this._textMetric(textValue) : 1) * score, elements: [], anchorCount: new Map() };\n for (let i = 0; i < path.length; i++)\n pathCue.elements.push([]);\n pathCues.set(text, pathCue);\n }\n for (let index = lca.lcaDepth; index >= 0; index--) {\n const elements = pathCue.elements[index];\n if (elements.length < maxCueCount)\n elements.push(element);\n }\n if (lca.anchor)\n pathCue.anchorCount.set(lca.anchor, 1 + (pathCue.anchorCount.get(lca.anchor) || 0));\n };\n const appendElementCues = (element, lca, detached) => {\n const nodeName = element.nodeName;\n if (!detached && this.options.usePlaceholders && nodeName === 'INPUT') {\n const placeholder = element.getAttribute('placeholder');\n if (placeholder)\n appendCue(JSON.stringify(placeholder), 'text', textScore, element, lca, placeholder);\n }\n if (!detached && nodeName === 'INPUT' && element.getAttribute('type') === 'button') {\n const value = element.getAttribute('value');\n if (value)\n appendCue(JSON.stringify(value), 'text', textScore, element, lca, value);\n }\n if (!nodeName.startsWith('<pseudo') && !nodeName.startsWith('::'))\n appendCue(nodeName, 'tag', this.options.genericTagScore, element, lca, '');\n if (this.options.imgAltScore && nodeName === 'IMG') {\n const alt = element.getAttribute('alt');\n if (alt)\n appendCue(`img[alt=${JSON.stringify(alt)}]`, 'imgAlt', this.options.imgAltScore, element, lca, alt);\n }\n if (this.options.ariaLabelScore) {\n const ariaLabel = element.getAttribute('aira-label');\n if (ariaLabel)\n appendCue(JSON.stringify(`[aria-label=${JSON.stringify(ariaLabel)}]`), 'ariaLabel', this.options.ariaLabelScore, element, lca, ariaLabel);\n }\n };\n const visit = (element, lca, depth) => {\n // Check for elements STYLE, NOSCRIPT, SCRIPT, OPTION and other elements\n // that have |display:none| behavior.\n const detached = !element.offsetParent;\n if (element.nodeType === 1 /* Node.ELEMENT_NODE */)\n appendElementCues(element, lca, detached);\n lcaMap.set(element, lca);\n for (let childNode = element.firstChild; childNode; childNode = childNode.nextSibling) {\n if (element.nodeType === 1 /* Node.ELEMENT_NODE */ && !detached && childNode.nodeType === 3 /* Node.TEXT_NODE */ && childNode.nodeValue) {\n const textValue = childNode.nodeValue.trim();\n if (textValue)\n appendCue(JSON.stringify(textValue), 'text', textScore, element, lca, textValue);\n }\n if (childNode.nodeType !== 1 /* Node.ELEMENT_NODE */)\n continue;\n const childElement = childNode;\n if (childElement.nodeName.startsWith('<pseudo:'))\n continue;\n if (path[depth + 1] === childElement) {\n const childLca = { depth: 0, lca: childElement, lcaDepth: depth + 1, anchor: undefined };\n visit(childElement, childLca, depth + 1);\n }\n else {\n const childLca = { depth: lca.depth + 1, lca: lca.lca, lcaDepth: lca.lcaDepth, anchor: lca.anchor || element };\n visit(childElement, childLca, depth + 1);\n }\n }\n };\n visit(root, { depth: 0, lca: root, lcaDepth: 0, anchor: undefined }, 0);\n return { pathCues: pathCues, lcaMap };\n }\n _filterCues(cues, root) {\n const result = new Map();\n for (const [text, cue] of cues) {\n const filtered = cue.elements.filter(element => root.contains(element));\n if (!filtered.length)\n continue;\n const newCue = { type: cue.type, score: cue.score, elements: filtered };\n result.set(text, newCue);\n }\n return result;\n }\n _buildLists(root, path) {\n const pathSet = new Set(path);\n const map = detectLists(root, e => pathSet.has(e), e => this._elementMetrics(e).box);\n const result = new Map();\n let listNumber = 1;\n for (const collection of map.values()) {\n for (const list of collection) {\n for (const child of list)\n result.set(child, listNumber);\n ++listNumber;\n }\n }\n return result;\n }\n _matchChildren(parent, token, all) {\n const result = [];\n if (token.index !== undefined)\n all = false;\n let index = token.index || 0;\n if (token.css !== undefined) {\n for (let child = parent.firstElementChild; child; child = child.nextElementSibling) {\n if (child.matches(token.css) && (all || !index--)) {\n result.push(child);\n if (!all)\n return result;\n }\n }\n return result;\n }\n if (token.text !== undefined) {\n const cue = this._getCues(parent).get(token.text);\n if (!cue || cue.type !== 'text')\n return [];\n for (const element of cue.elements) {\n if (parentOrRoot(element) === parent && (all || !index--)) {\n result.push(element);\n if (!all)\n return result;\n }\n }\n return result;\n }\n throw new Error('Unsupported token');\n }\n _matchSubtree(root, token, all) {\n const result = [];\n if (token.index !== undefined)\n all = false;\n let index = token.index || 0;\n if (token.css !== undefined) {\n if (root.nodeType === 1 /* Node.ELEMENT_NODE */) {\n const rootElement = root;\n if (rootElement.matches(token.css) && (all || !index--)) {\n result.push(rootElement);\n if (!all)\n return result;\n }\n }\n const queried = root.querySelectorAll(token.css);\n if (all)\n result.push(...Array.from(queried));\n else if (queried.length > index)\n result.push(queried.item(index));\n return result;\n }\n if (token.text !== undefined) {\n const texts = this._getCues(root);\n const cue = texts.get(token.text);\n if (!cue || cue.type !== 'text')\n return result;\n if (all)\n return cue.elements;\n if (index < cue.elements.length)\n result.push(cue.elements[index]);\n return result;\n }\n throw new Error('Unsupported token');\n }\n _getCues(element) {\n if (!this._cues.has(element)) {\n let parent = element;\n while (!this._cues.has(parent))\n parent = parentOrRoot(parent);\n this._cues.set(element, this._filterCues(this._cues.get(parent), element));\n }\n return this._cues.get(element);\n }\n _serialize(tokens) {\n const result = tokens.map(token => (token.combinator === '' ? ' ' : token.combinator) +\n (token.text !== undefined ? token.text : '') +\n (token.css !== undefined ? token.css : '') +\n (token.index !== undefined ? '#' + token.index : '')).join('');\n if (result[0] !== ' ')\n throw new Error('First token is wrong');\n return result.substring(1);\n }\n}\nconst ZSSelectorEngine = {\n name: 'zs',\n create(root, element, type) {\n return new Engine().create(root, element, type || 'default');\n },\n query(root, selector) {\n return new Engine().query(root, selector, false /* all */)[0];\n },\n queryAll(root, selector) {\n return new Engine().query(root, selector, true /* all */);\n }\n};\nZSSelectorEngine.test = () => {\n const elements = Array.from(document.querySelectorAll('*')).slice(1500, 2000);\n console.time('test'); // eslint-disable-line no-console\n const failures = elements.filter((e, index) => {\n const name = e.tagName.toUpperCase();\n if (name === 'SCRIPT' || name === 'STYLE' || name === 'NOSCRIPT' || name === 'META' || name === 'LINK' || name === 'OPTION')\n return false;\n if (index % 100 === 0)\n console.log(`${index} / ${elements.length}`); // eslint-disable-line no-console\n if (e.nodeName.toLowerCase().startsWith('<pseudo:'))\n e = e.parentElement;\n while (e && e.namespaceURI && e.namespaceURI.endsWith('svg') && e.nodeName.toLowerCase() !== 'svg')\n e = e.parentElement;\n try {\n document.documentElement.style.outline = '1px solid red';\n const selector = new Engine().create(document.documentElement, e, 'default');\n document.documentElement.style.outline = '1px solid green';\n const e2 = new Engine().query(document.documentElement, selector, false)[0];\n return e !== e2;\n }\n catch (e) {\n return false;\n }\n });\n console.timeEnd('test'); // eslint-disable-line no-console\n console.log(failures); // eslint-disable-line no-console\n};\nexports.default = ZSSelectorEngine;\n\n\n/***/ })\n\n/******/ })).default";
exports.source = "(/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// define __esModule on exports\n/******/ \t__webpack_require__.r = function(exports) {\n/******/ \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n/******/ \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n/******/ \t\t}\n/******/ \t\tObject.defineProperty(exports, '__esModule', { value: true });\n/******/ \t};\n/******/\n/******/ \t// create a fake namespace object\n/******/ \t// mode & 1: value is a module id, require it\n/******/ \t// mode & 2: merge all properties of value into the ns\n/******/ \t// mode & 4: return value when already ns object\n/******/ \t// mode & 8|1: behave like require\n/******/ \t__webpack_require__.t = function(value, mode) {\n/******/ \t\tif(mode & 1) value = __webpack_require__(value);\n/******/ \t\tif(mode & 8) return value;\n/******/ \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n/******/ \t\tvar ns = Object.create(null);\n/******/ \t\t__webpack_require__.r(ns);\n/******/ \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n/******/ \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n/******/ \t\treturn ns;\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = \"./src/injected/zsSelectorEngine.ts\");\n/******/ })\n/************************************************************************/\n/******/ ({\n\n/***/ \"./src/injected/zsSelectorEngine.ts\":\n/*!******************************************!*\\\n !*** ./src/injected/zsSelectorEngine.ts ***!\n \\******************************************/\n/*! no static exports found */\n/***/ (function(module, exports, __webpack_require__) {\n\n\"use strict\";\n\n/**\n * Copyright (c) Microsoft Corporation.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nObject.defineProperty(exports, \"__esModule\", { value: true });\nfunction tokenize(selector) {\n const tokens = [];\n let pos = 0;\n const skipWhitespace = () => {\n while (pos < selector.length && selector[pos] === ' ')\n pos++;\n };\n while (pos < selector.length) {\n skipWhitespace();\n if (pos === selector.length)\n break;\n if (!tokens.length && '^>~'.includes(selector[pos]))\n return pos;\n const token = { combinator: '' };\n if (selector[pos] === '^') {\n token.combinator = '^';\n tokens.push(token);\n pos++;\n continue;\n }\n if (selector[pos] === '>') {\n token.combinator = '>';\n pos++;\n skipWhitespace();\n if (pos === selector.length)\n return pos;\n }\n else if (selector[pos] === '~') {\n token.combinator = '~';\n pos++;\n skipWhitespace();\n if (pos === selector.length)\n return pos;\n }\n let text = '';\n let end = pos;\n let stringQuote;\n const isText = '`\"\\''.includes(selector[pos]);\n while (end < selector.length) {\n if (stringQuote) {\n if (selector[end] === '\\\\' && end + 1 < selector.length) {\n if (!isText)\n text += selector[end];\n text += selector[end + 1];\n end += 2;\n }\n else if (selector[end] === stringQuote) {\n text += selector[end++];\n stringQuote = undefined;\n if (isText)\n break;\n }\n else {\n text += selector[end++];\n }\n }\n else if (' >~^#'.includes(selector[end])) {\n break;\n }\n else if ('`\"\\''.includes(selector[end])) {\n stringQuote = selector[end];\n text += selector[end++];\n }\n else {\n text += selector[end++];\n }\n }\n if (stringQuote)\n return end;\n if (isText)\n token.text = JSON.stringify(text.substring(1, text.length - 1));\n else\n token.css = text;\n pos = end;\n if (pos < selector.length && selector[pos] === '#') {\n pos++;\n let end = pos;\n while (end < selector.length && selector[end] >= '0' && selector[end] <= '9')\n end++;\n if (end === pos)\n return pos;\n const num = Number(selector.substring(pos, end));\n if (isNaN(num))\n return pos;\n token.index = num;\n pos = end;\n }\n tokens.push(token);\n }\n return tokens;\n}\nfunction pathFromRoot(root, targetElement) {\n let target = targetElement;\n const path = [target];\n while (target !== root) {\n if (!target.parentNode || target.parentNode.nodeType !== 1 /* Node.ELEMENT_NODE */ && target.parentNode.nodeType !== 11 /* Node.DOCUMENT_FRAGMENT_NODE */)\n throw new Error('Target does not belong to the root subtree');\n target = target.parentNode;\n path.push(target);\n }\n path.reverse();\n return path;\n}\nfunction detectLists(root, shouldConsider, getBox) {\n const lists = new Map();\n const add = (map, element, key) => {\n let list = map.get(key);\n if (!list) {\n list = [];\n map.set(key, list);\n }\n list.push(element);\n };\n const mark = (parent, map, used) => {\n for (let list of map.values()) {\n list = list.filter(item => !used.has(item));\n if (list.length < 2)\n continue;\n let collection = lists.get(parent);\n if (!collection) {\n collection = [];\n lists.set(parent, collection);\n }\n collection.push(list);\n list.forEach(item => used.add(item));\n }\n };\n // hashes list: s, vh, v, h\n const kHashes = 4;\n const visit = (element, produceHashes) => {\n const consider = shouldConsider(element);\n let size = 1;\n let maps;\n if (consider)\n maps = new Array(kHashes).fill(0).map(_ => new Map());\n let structure;\n if (produceHashes)\n structure = [element.nodeName];\n for (let child = element.firstElementChild; child; child = child.nextElementSibling) {\n const childResult = visit(child, consider);\n size += childResult.size;\n if (consider) {\n for (let i = 0; i < childResult.hashes.length; i++) {\n if (childResult.hashes[i])\n add(maps[i], child, childResult.hashes[i]);\n }\n }\n if (structure)\n structure.push(child.nodeName);\n }\n if (consider) {\n const used = new Set();\n maps.forEach(map => mark(element, map, used));\n }\n let hashes;\n if (produceHashes) {\n const box = getBox(element);\n hashes = [];\n hashes.push((structure.length >= 4) || (size >= 10) ? structure.join('') : '');\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.width | 0}`);\n if (size <= 5)\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.width | 0},${box.left | 0}`);\n else\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.width | 0},${box.left | 0},${2 * Math.log(box.height) | 0}`);\n if (size <= 5)\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.top | 0}`);\n else\n hashes.push(`${element.nodeName},${(size / 3) | 0},${box.height | 0},${box.top | 0},${2 * Math.log(box.width) | 0}`);\n }\n return { size, hashes };\n };\n visit(root, false);\n return lists;\n}\nconst defaultOptions = {\n genericTagScore: 10,\n textScore: 1,\n imgAltScore: 2,\n ariaLabelScore: 2,\n detectLists: true,\n avoidShortText: false,\n usePlaceholders: true,\n debug: false,\n};\nfunction parentOrRoot(element) {\n return element.parentNode;\n}\nclass Engine {\n constructor(options = defaultOptions) {\n this._cues = new Map();\n this._metrics = new Map();\n this.options = options;\n }\n query(root, selector, all) {\n const tokens = tokenize(selector);\n if (typeof tokens === 'number')\n throw new Error('Cannot parse selector at position ' + tokens);\n if (!tokens.length)\n throw new Error('Empty selector');\n if (!this._cues.has(root)) {\n const cueMap = new Map();\n const pathCues = this._preprocess(root, [root], Infinity).pathCues;\n for (const [text, cue] of pathCues) {\n cueMap.set(text, {\n type: cue.type,\n score: cue.score,\n elements: cue.elements[0]\n });\n }\n this._cues.set(root, cueMap);\n }\n // Map from the element to the boundary used. We never go outside the boundary when doing '~'.\n let currentStep = new Map();\n currentStep.set(root, root);\n for (const token of tokens) {\n const nextStep = new Map();\n for (let [element, boundary] of currentStep) {\n let next = [];\n if (token.combinator === '^') {\n if (element === boundary) {\n next = [];\n }\n else {\n const parent = parentOrRoot(element);\n next = parent ? [parent] : [];\n }\n }\n else if (token.combinator === '>') {\n boundary = element;\n next = this._matchChildren(element, token, all);\n }\n else if (token.combinator === '') {\n boundary = element;\n next = this._matchSubtree(element, token, all);\n }\n else if (token.combinator === '~') {\n while (true) {\n next = this._matchSubtree(element, token, all);\n if (next.length) {\n // Further '~' / '^' will not go outside of this boundary, which is\n // a container with both the cue and the target elements inside.\n boundary = element;\n break;\n }\n if (element === boundary)\n break;\n element = parentOrRoot(element);\n }\n }\n for (const nextElement of next) {\n if (!nextStep.has(nextElement))\n nextStep.set(nextElement, boundary);\n }\n }\n currentStep = nextStep;\n }\n return Array.from(currentStep.keys()).filter(e => e.nodeType === 1 /* Node.ELEMENT_NODE */);\n }\n create(root, target, type) {\n const path = pathFromRoot(root, target);\n const maxCueCount = type === 'notext' ? 50 : 10;\n const { pathCues, lcaMap } = this._preprocess(root, path, maxCueCount);\n const lists = this.options.detectLists ?\n this._buildLists(root, path) : undefined;\n const queue = path.map(_ => new Map());\n const startStep = {\n token: { combinator: '' },\n element: root,\n depth: 0,\n score: 0,\n totalScore: 0\n };\n for (let stepDepth = -1; stepDepth < path.length; stepDepth++) {\n const stepsMap = stepDepth === -1 ? new Map([[undefined, startStep]]) : queue[stepDepth];\n const ancestorDepth = stepDepth === -1 ? 0 : stepDepth;\n for (const [text, cue] of pathCues) {\n const elements = cue.elements[ancestorDepth];\n for (let index = 0; index < elements.length; index++) {\n const element = elements[index];\n const lca = lcaMap.get(element);\n const lcaDepth = lca.lcaDepth;\n // Always go deeper in the tree.\n if (lcaDepth <= stepDepth)\n continue;\n // 'notext' - do not use elements from the target's subtree.\n if (type === 'notext' && lcaDepth === path.length - 1 && lca.depth > 0)\n continue;\n // 'notext' - do not use target's own text.\n if (type === 'notext' && lcaDepth === path.length - 1 && !lca.depth && cue.type !== 'tag')\n continue;\n const targetAnchor = path[lcaDepth + 1];\n if (lists && lca.anchor && targetAnchor && lca.anchor !== targetAnchor) {\n const oldList = lists.get(lca.anchor);\n // Do not use cues from sibling list items (lca.anchor and targetAnchor).\n if (oldList && oldList === lists.get(targetAnchor))\n continue;\n }\n if (cue.type !== 'tag' && !this._isVisible(element))\n continue;\n const distanceToTarget = path.length - stepDepth;\n // Short text can be used more effectively in a smaller scope.\n let shortTextScore = 0;\n if (this.options.avoidShortText && cue.type === 'text')\n shortTextScore = Math.max(0, distanceToTarget - 2 * (text.length - 2));\n const score = (cue.score + shortTextScore) * (\n // Unique cues are heavily favored.\n 1 * (index + elements.length * 1000) +\n // Larger text is preferred.\n 5 * (cue.type === 'text' ? this._elementMetrics(element).fontMetric : 1) +\n // The closer to the target, the better.\n 1 * lca.depth);\n for (const [anchor, step] of stepsMap) {\n // This ensures uniqueness when resolving the selector.\n if (anchor && (cue.anchorCount.get(anchor) || 0) > index)\n continue;\n let newStep = {\n token: {\n combinator: stepDepth === -1 ? '' : '~',\n text: cue.type === 'text' ? text : undefined,\n css: cue.type === 'text' ? undefined : text,\n index: index || undefined,\n },\n previous: step,\n depth: lca.depth,\n element,\n score,\n totalScore: step.totalScore + score\n };\n let nextStep = queue[lcaDepth].get(lca.anchor);\n if (!nextStep || nextStep.totalScore > newStep.totalScore)\n queue[lcaDepth].set(lca.anchor, newStep);\n // Try going to the ancestor.\n if (newStep.depth) {\n newStep = {\n token: { combinator: '^' },\n previous: newStep,\n depth: 0,\n element: lca.lca,\n score: 2000 * newStep.depth,\n totalScore: newStep.totalScore + 2000 * newStep.depth,\n repeat: newStep.depth\n };\n nextStep = queue[lcaDepth].get(undefined);\n if (!nextStep || nextStep.totalScore > newStep.totalScore)\n queue[lcaDepth].set(undefined, newStep);\n }\n }\n }\n }\n }\n let best;\n for (const [, step] of queue[path.length - 1]) {\n if (!best || step.totalScore < best.totalScore)\n best = step;\n }\n if (!best)\n return '';\n const tokens = new Array(best.depth).fill({ combinator: '^' });\n while (best && best !== startStep) {\n for (let repeat = best.repeat || 1; repeat; repeat--)\n tokens.push(best.token);\n best = best.previous;\n }\n tokens.reverse();\n return this._serialize(tokens);\n }\n _textMetric(text) {\n // Text which looks like a float number or counter is most likely volatile.\n if (/^\\$?[\\d,]+(\\.\\d+|(\\.\\d+)?[kKmMbBgG])?$/.test(text))\n return 12;\n const num = Number(text);\n // Large numbers are likely volatile.\n if (!isNaN(num) && (num >= 32 || num < 0))\n return 6;\n return 1;\n }\n _elementMetrics(element) {\n let metrics = this._metrics.get(element);\n if (!metrics) {\n const style = element.ownerDocument ?\n element.ownerDocument.defaultView.getComputedStyle(element) :\n {};\n const box = element.getBoundingClientRect();\n const fontSize = (parseInt(style.fontSize || '', 10) || 12) / 12; // default 12 px\n const fontWeight = (parseInt(style.fontWeight || '', 10) || 400) / 400; // default normal weight\n let fontMetric = fontSize * (1 + (fontWeight - 1) / 5);\n fontMetric = 1 / Math.exp(fontMetric - 1);\n metrics = { box, style, fontMetric };\n this._metrics.set(element, metrics);\n }\n return metrics;\n }\n _isVisible(element) {\n const metrics = this._elementMetrics(element);\n return metrics.box.width > 1 && metrics.box.height > 1;\n }\n _preprocess(root, path, maxCueCount) {\n const pathCues = new Map();\n const lcaMap = new Map();\n const textScore = this.options.textScore || 1;\n const appendCue = (text, type, score, element, lca, textValue) => {\n let pathCue = pathCues.get(text);\n if (!pathCue) {\n pathCue = { type, score: (textValue ? this._textMetric(textValue) : 1) * score, elements: [], anchorCount: new Map() };\n for (let i = 0; i < path.length; i++)\n pathCue.elements.push([]);\n pathCues.set(text, pathCue);\n }\n for (let index = lca.lcaDepth; index >= 0; index--) {\n const elements = pathCue.elements[index];\n if (elements.length < maxCueCount)\n elements.push(element);\n }\n if (lca.anchor)\n pathCue.anchorCount.set(lca.anchor, 1 + (pathCue.anchorCount.get(lca.anchor) || 0));\n };\n const appendElementCues = (element, lca, detached) => {\n const nodeName = element.nodeName;\n if (!detached && this.options.usePlaceholders && nodeName === 'INPUT') {\n const placeholder = element.getAttribute('placeholder');\n if (placeholder)\n appendCue(JSON.stringify(placeholder), 'text', textScore, element, lca, placeholder);\n }\n if (!detached && nodeName === 'INPUT' && element.getAttribute('type') === 'button') {\n const value = element.getAttribute('value');\n if (value)\n appendCue(JSON.stringify(value), 'text', textScore, element, lca, value);\n }\n if (!nodeName.startsWith('<pseudo') && !nodeName.startsWith('::'))\n appendCue(nodeName, 'tag', this.options.genericTagScore, element, lca, '');\n if (this.options.imgAltScore && nodeName === 'IMG') {\n const alt = element.getAttribute('alt');\n if (alt)\n appendCue(`img[alt=${JSON.stringify(alt)}]`, 'imgAlt', this.options.imgAltScore, element, lca, alt);\n }\n if (this.options.ariaLabelScore) {\n const ariaLabel = element.getAttribute('aria-label');\n if (ariaLabel)\n appendCue(JSON.stringify(`[aria-label=${JSON.stringify(ariaLabel)}]`), 'ariaLabel', this.options.ariaLabelScore, element, lca, ariaLabel);\n }\n };\n const visit = (element, lca, depth) => {\n // Check for elements STYLE, NOSCRIPT, SCRIPT, OPTION and other elements\n // that have |display:none| behavior.\n const detached = !element.offsetParent;\n if (element.nodeType === 1 /* Node.ELEMENT_NODE */)\n appendElementCues(element, lca, detached);\n lcaMap.set(element, lca);\n for (let childNode = element.firstChild; childNode; childNode = childNode.nextSibling) {\n if (element.nodeType === 1 /* Node.ELEMENT_NODE */ && !detached && childNode.nodeType === 3 /* Node.TEXT_NODE */ && childNode.nodeValue) {\n const textValue = childNode.nodeValue.trim();\n if (textValue)\n appendCue(JSON.stringify(textValue), 'text', textScore, element, lca, textValue);\n }\n if (childNode.nodeType !== 1 /* Node.ELEMENT_NODE */)\n continue;\n const childElement = childNode;\n if (childElement.nodeName.startsWith('<pseudo:'))\n continue;\n if (path[depth + 1] === childElement) {\n const childLca = { depth: 0, lca: childElement, lcaDepth: depth + 1, anchor: undefined };\n visit(childElement, childLca, depth + 1);\n }\n else {\n const childLca = { depth: lca.depth + 1, lca: lca.lca, lcaDepth: lca.lcaDepth, anchor: lca.anchor || element };\n visit(childElement, childLca, depth + 1);\n }\n }\n };\n visit(root, { depth: 0, lca: root, lcaDepth: 0, anchor: undefined }, 0);\n return { pathCues: pathCues, lcaMap };\n }\n _filterCues(cues, root) {\n const result = new Map();\n for (const [text, cue] of cues) {\n const filtered = cue.elements.filter(element => root.contains(element));\n if (!filtered.length)\n continue;\n const newCue = { type: cue.type, score: cue.score, elements: filtered };\n result.set(text, newCue);\n }\n return result;\n }\n _buildLists(root, path) {\n const pathSet = new Set(path);\n const map = detectLists(root, e => pathSet.has(e), e => this._elementMetrics(e).box);\n const result = new Map();\n let listNumber = 1;\n for (const collection of map.values()) {\n for (const list of collection) {\n for (const child of list)\n result.set(child, listNumber);\n ++listNumber;\n }\n }\n return result;\n }\n _matchChildren(parent, token, all) {\n const result = [];\n if (token.index !== undefined)\n all = false;\n let index = token.index || 0;\n if (token.css !== undefined) {\n for (let child = parent.firstElementChild; child; child = child.nextElementSibling) {\n if (child.matches(token.css) && (all || !index--)) {\n result.push(child);\n if (!all)\n return result;\n }\n }\n return result;\n }\n if (token.text !== undefined) {\n const cue = this._getCues(parent).get(token.text);\n if (!cue || cue.type !== 'text')\n return [];\n for (const element of cue.elements) {\n if (parentOrRoot(element) === parent && (all || !index--)) {\n result.push(element);\n if (!all)\n return result;\n }\n }\n return result;\n }\n throw new Error('Unsupported token');\n }\n _matchSubtree(root, token, all) {\n const result = [];\n if (token.index !== undefined)\n all = false;\n let index = token.index || 0;\n if (token.css !== undefined) {\n if (root.nodeType === 1 /* Node.ELEMENT_NODE */) {\n const rootElement = root;\n if (rootElement.matches(token.css) && (all || !index--)) {\n result.push(rootElement);\n if (!all)\n return result;\n }\n }\n const queried = root.querySelectorAll(token.css);\n if (all)\n result.push(...Array.from(queried));\n else if (queried.length > index)\n result.push(queried.item(index));\n return result;\n }\n if (token.text !== undefined) {\n const texts = this._getCues(root);\n const cue = texts.get(token.text);\n if (!cue || cue.type !== 'text')\n return result;\n if (all)\n return cue.elements;\n if (index < cue.elements.length)\n result.push(cue.elements[index]);\n return result;\n }\n throw new Error('Unsupported token');\n }\n _getCues(element) {\n if (!this._cues.has(element)) {\n let parent = element;\n while (!this._cues.has(parent))\n parent = parentOrRoot(parent);\n this._cues.set(element, this._filterCues(this._cues.get(parent), element));\n }\n return this._cues.get(element);\n }\n _serialize(tokens) {\n const result = tokens.map(token => (token.combinator === '' ? ' ' : token.combinator) +\n (token.text !== undefined ? token.text : '') +\n (token.css !== undefined ? token.css : '') +\n (token.index !== undefined ? '#' + token.index : '')).join('');\n if (result[0] !== ' ')\n throw new Error('First token is wrong');\n return result.substring(1);\n }\n}\nconst ZSSelectorEngine = {\n name: 'zs',\n create(root, element, type) {\n return new Engine().create(root, element, type || 'default');\n },\n query(root, selector) {\n return new Engine().query(root, selector, false /* all */)[0];\n },\n queryAll(root, selector) {\n return new Engine().query(root, selector, true /* all */);\n }\n};\nZSSelectorEngine.test = () => {\n const elements = Array.from(document.querySelectorAll('*')).slice(1500, 2000);\n console.time('test'); // eslint-disable-line no-console\n const failures = elements.filter((e, index) => {\n const name = e.tagName.toUpperCase();\n if (name === 'SCRIPT' || name === 'STYLE' || name === 'NOSCRIPT' || name === 'META' || name === 'LINK' || name === 'OPTION')\n return false;\n if (index % 100 === 0)\n console.log(`${index} / ${elements.length}`); // eslint-disable-line no-console\n if (e.nodeName.toLowerCase().startsWith('<pseudo:'))\n e = e.parentElement;\n while (e && e.namespaceURI && e.namespaceURI.endsWith('svg') && e.nodeName.toLowerCase() !== 'svg')\n e = e.parentElement;\n try {\n document.documentElement.style.outline = '1px solid red';\n const selector = new Engine().create(document.documentElement, e, 'default');\n document.documentElement.style.outline = '1px solid green';\n const e2 = new Engine().query(document.documentElement, selector, false)[0];\n return e !== e2;\n }\n catch (e) {\n return false;\n }\n });\n console.timeEnd('test'); // eslint-disable-line no-console\n console.log(failures); // eslint-disable-line no-console\n};\nexports.default = ZSSelectorEngine;\n\n\n/***/ })\n\n/******/ })).default";
//# sourceMappingURL=zsSelectorEngineSource.js.map

@@ -550,3 +550,3 @@ /******/ (function(modules) { // webpackBootstrap

if (this.options.ariaLabelScore) {
const ariaLabel = element.getAttribute('aira-label');
const ariaLabel = element.getAttribute('aria-label');
if (ariaLabel)

@@ -553,0 +553,0 @@ appendCue(JSON.stringify(`[aria-label=${JSON.stringify(ariaLabel)}]`), 'ariaLabel', this.options.ariaLabelScore, element, lca, ariaLabel);

@@ -454,3 +454,3 @@ "use strict";

if (this.options.ariaLabelScore) {
const ariaLabel = element.getAttribute('aira-label');
const ariaLabel = element.getAttribute('aria-label');
if (ariaLabel)

@@ -457,0 +457,0 @@ appendCue(JSON.stringify(`[aria-label=${JSON.stringify(ariaLabel)}]`), 'ariaLabel', this.options.ariaLabelScore, element, lca, ariaLabel);

@@ -91,5 +91,5 @@ /**

continue(overrides?: {
headers?: {
[key: string]: string;
};
method?: string;
headers?: Headers;
postData?: string;
}): Promise<void>;

@@ -130,6 +130,5 @@ }

continue(overrides: {
url?: string;
method?: string;
headers?: Headers;
postData?: string;
headers?: Headers;
}): Promise<void>;

@@ -136,0 +135,0 @@ }

@@ -32,2 +32,3 @@ /**

readonly rawKeyboard: input.RawKeyboard;
opener(): Promise<Page | null>;
reload(): Promise<void>;

@@ -47,2 +48,3 @@ goBack(): Promise<boolean>;

authenticate(credentials: types.Credentials | null): Promise<void>;
setFileChooserIntercepted(enabled: boolean): Promise<void>;
getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null>;

@@ -117,2 +119,3 @@ canScreenshotOutsideViewport(): boolean;

browserContext(): BrowserContext;
opener(): Promise<Page | null>;
mainFrame(): frames.Frame;

@@ -126,3 +129,2 @@ frames(): frames.Frame[];

}): Promise<dom.ElementHandle<Element> | null>;
_createSelector(name: string, handle: dom.ElementHandle<Element>): Promise<string | undefined>;
evaluateHandle: types.EvaluateHandle;

@@ -198,2 +200,4 @@ $eval: types.$Eval;

_clearWorkers(): void;
on(event: string | symbol, listener: platform.Listener): this;
removeListener(event: string | symbol, listener: platform.Listener): this;
}

@@ -200,0 +204,0 @@ export declare class Worker {

@@ -67,3 +67,3 @@ "use strict";

credentials: null,
hasTouch: null
hasTouch: null,
};

@@ -108,2 +108,5 @@ this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate));

}
async opener() {
return await this._delegate.opener();
}
mainFrame() {

@@ -127,8 +130,2 @@ return this._frameManager.mainFrame();

}
async _createSelector(name, handle) {
const mainContext = await this.mainFrame()._mainContext();
return mainContext.evaluate((injected, target, name) => {
return injected.engines.get(name).create(document.documentElement, target);
}, await mainContext._injected(), handle, name);
}
async $$(selector) {

@@ -388,2 +385,16 @@ return this.mainFrame().$$(selector);

}
on(event, listener) {
if (event === events_1.Events.Page.FileChooser) {
if (!this.listenerCount(event))
this._delegate.setFileChooserIntercepted(true);
}
super.on(event, listener);
return this;
}
removeListener(event, listener) {
super.removeListener(event, listener);
if (event === events_1.Events.Page.FileChooser && !this.listenerCount(event))
this._delegate.setFileChooserIntercepted(false);
return this;
}
}

@@ -390,0 +401,0 @@ exports.Page = Page;

@@ -24,2 +24,3 @@ /**

export declare function promisify(nodeFunction: Function): Function;
export declare type Listener = (...args: any[]) => void;
export declare const EventEmitter: typeof nodeEvents.EventEmitter;

@@ -26,0 +27,0 @@ export declare type EventEmitterType = nodeEvents.EventEmitter;

@@ -54,3 +54,3 @@ "use strict";

}
addListener(event, listener) {
on(event, listener) {
let set = this._listeners.get(event);

@@ -64,4 +64,4 @@ if (!set) {

}
on(event, listener) {
return this.addListener(event, listener);
addListener(event, listener) {
return this.on(event, listener);
}

@@ -73,3 +73,3 @@ once(event, listener) {

};
return this.on(event, wrapped);
return this.addListener(event, wrapped);
}

@@ -76,0 +76,0 @@ removeListener(event, listener) {

@@ -42,2 +42,3 @@ /**

executablePath(): string;
name(): string;
launchBrowserApp(options?: LaunchOptions): Promise<BrowserApp>;

@@ -44,0 +45,0 @@ launch(options?: LaunchOptions): Promise<Browser>;

@@ -28,2 +28,3 @@ /**

constructor(projectRoot: string, preferredRevision: string);
name(): string;
launch(options?: LaunchOptions): Promise<CRBrowser>;

@@ -30,0 +31,0 @@ launchBrowserApp(options?: LaunchOptions): Promise<BrowserApp>;

@@ -40,2 +40,5 @@ "use strict";

}
name() {
return 'chromium';
}
async launch(options) {

@@ -95,5 +98,5 @@ const app = await this.launchBrowserApp(options);

},
onkill: () => {
onkill: (exitCode, signal) => {
if (browserApp)
browserApp.emit(events_1.Events.BrowserApp.Close);
browserApp.emit(events_1.Events.BrowserApp.Close, exitCode, signal);
},

@@ -209,3 +212,3 @@ });

const revisionInfo = browserFetcher.revisionInfo();
const missingText = !revisionInfo.local ? `Chromium revision is not downloaded. Run "npm install" or "yarn install"` : null;
const missingText = !revisionInfo.local ? `Chromium revision is not downloaded. Run "npm install"` : null;
return { executablePath: revisionInfo.executablePath, missingText };

@@ -212,0 +215,0 @@ }

@@ -28,2 +28,3 @@ /**

constructor(projectRoot: string, preferredRevision: string);
name(): string;
launch(options?: LaunchOptions): Promise<FFBrowser>;

@@ -30,0 +31,0 @@ launchBrowserApp(options?: LaunchOptions): Promise<BrowserApp>;

@@ -39,2 +39,5 @@ "use strict";

}
name() {
return 'firefox';
}
async launch(options) {

@@ -95,5 +98,5 @@ const app = await this.launchBrowserApp(options);

},
onkill: () => {
onkill: (exitCode, signal) => {
if (browserApp)
browserApp.emit(events_1.Events.BrowserApp.Close);
browserApp.emit(events_1.Events.BrowserApp.Close, exitCode, signal);
},

@@ -190,3 +193,3 @@ });

const revisionInfo = browserFetcher.revisionInfo();
const missingText = !revisionInfo.local ? `Firefox revision is not downloaded. Run "npm install" or "yarn install"` : null;
const missingText = !revisionInfo.local ? `Firefox revision is not downloaded. Run "npm install"` : null;
return { executablePath: revisionInfo.executablePath, missingText };

@@ -193,0 +196,0 @@ }

@@ -34,3 +34,3 @@ /**

attemptToGracefullyClose: () => Promise<any>;
onkill: () => void;
onkill: (exitCode: number | null, signal: string | null) => void;
};

@@ -37,0 +37,0 @@ declare type LaunchResult = {

@@ -59,6 +59,7 @@ "use strict";

const waitForProcessToClose = new Promise((fulfill, reject) => {
spawnedProcess.once('exit', () => {
spawnedProcess.once('exit', (exitCode, signal) => {
debugLauncher(`[${id}] <process did exit>`);
processClosed = true;
helper_1.helper.removeEventListeners(listeners);
options.onkill(exitCode, signal);
// Cleanup as processes exit.

@@ -73,8 +74,10 @@ if (options.tempDir) {

}
options.onkill();
});
});
const listeners = [helper_1.helper.addEventListener(process, 'exit', killProcess)];
if (options.handleSIGINT)
listeners.push(helper_1.helper.addEventListener(process, 'SIGINT', () => { killProcess(); process.exit(130); }));
if (options.handleSIGINT) {
listeners.push(helper_1.helper.addEventListener(process, 'SIGINT', () => {
gracefullyClose().then(() => process.exit(130));
}));
}
if (options.handleSIGTERM)

@@ -85,16 +88,17 @@ listeners.push(helper_1.helper.addEventListener(process, 'SIGTERM', gracefullyClose));

let gracefullyClosing = false;
return { launchedProcess: spawnedProcess, gracefullyClose };
async function gracefullyClose() {
// We keep listeners until we are done, to handle 'exit' and 'SIGINT' while
// asynchronously closing to prevent zombie processes. This might introduce
// reentrancy to this function.
if (gracefullyClosing)
// reentrancy to this function, for example user sends SIGINT second time.
// In this case, let's forcefully kill the process.
if (gracefullyClosing) {
debugLauncher(`[${id}] <forecefully close>`);
killProcess();
return;
}
gracefullyClosing = true;
debugLauncher(`[${id}] <gracefully close start>`);
options.attemptToGracefullyClose().catch(() => killProcess());
// TODO: forcefully kill the process after some timeout.
await waitForProcessToClose;
debugLauncher(`[${id}] <gracefully close end>`);
helper_1.helper.removeEventListeners(listeners);
}

@@ -124,2 +128,3 @@ // This method has to be sync to be used as 'exit' event handler.

}
return { launchedProcess: spawnedProcess, gracefullyClose };
}

@@ -126,0 +131,0 @@ exports.launchProcess = launchProcess;

@@ -28,2 +28,3 @@ /**

constructor(projectRoot: string, preferredRevision: string);
name(): string;
launch(options?: LaunchOptions): Promise<WKBrowser>;

@@ -30,0 +31,0 @@ launchBrowserApp(options?: LaunchOptions): Promise<BrowserApp>;

@@ -42,2 +42,5 @@ "use strict";

}
name() {
return 'webkit';
}
async launch(options) {

@@ -98,5 +101,5 @@ const app = await this.launchBrowserApp(options);

},
onkill: () => {
onkill: (exitCode, signal) => {
if (browserApp)
browserApp.emit(events_1.Events.BrowserApp.Close);
browserApp.emit(events_1.Events.BrowserApp.Close, exitCode, signal);
},

@@ -181,3 +184,3 @@ });

const revisionInfo = browserFetcher.revisionInfo();
const missingText = !revisionInfo.local ? `WebKit revision is not downloaded. Run "npm install" or "yarn install"` : null;
const missingText = !revisionInfo.local ? `WebKit revision is not downloaded. Run "npm install"` : null;
return { executablePath: revisionInfo.executablePath, missingText };

@@ -184,0 +187,0 @@ }

@@ -26,2 +26,3 @@ "use strict";

const platform = require("../platform");
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15';
class WKBrowser extends platform.EventEmitter {

@@ -58,2 +59,3 @@ constructor(transport) {

const { browserContextId } = await this._browserSession.send('Browser.createContext');
options.userAgent = options.userAgent || DEFAULT_USER_AGENT;
const context = this._createBrowserContext(browserContextId, options);

@@ -92,3 +94,10 @@ if (options.ignoreHTTPSErrors)

});
const pageProxy = new wkPageProxy_1.WKPageProxy(pageProxySession, context);
const pageProxy = new wkPageProxy_1.WKPageProxy(pageProxySession, context, () => {
if (!pageProxyInfo.openerId)
return null;
const opener = this._pageProxies.get(pageProxyInfo.openerId);
if (!opener)
return null;
return opener;
});
this._pageProxies.set(pageProxyId, pageProxy);

@@ -95,0 +104,0 @@ if (pageProxyInfo.openerId) {

@@ -91,13 +91,12 @@ "use strict";

}
send(method, params) {
async send(method, params) {
if (this._disposed)
return Promise.reject(new Error(`Protocol error (${method}): ${this.errorText}`));
throw new Error(`Protocol error (${method}): ${this.errorText}`);
const id = this.connection.nextMessageId();
const messageObj = { id, method, params };
platform.debug('pw:wrapped:' + this.sessionId)('SEND ► ' + JSON.stringify(messageObj, null, 2));
const result = new Promise((resolve, reject) => {
this._rawSend(messageObj);
return new Promise((resolve, reject) => {
this._callbacks.set(id, { resolve, reject, error: new Error(), method });
});
this._rawSend(messageObj);
return result;
}

@@ -104,0 +103,0 @@ isDisposed() {

@@ -18,17 +18,16 @@ /**

import { WKSession } from './wkConnection';
import { Protocol } from './protocol';
import * as js from '../javascript';
export declare const EVALUATION_SCRIPT_URL = "__playwright_evaluation_script__";
export declare class WKExecutionContext implements js.ExecutionContextDelegate {
private _globalObjectId?;
_session: WKSession;
_contextId: number | undefined;
private _globalObjectIdPromise?;
private readonly _session;
readonly _contextId: number | undefined;
private _contextDestroyedCallback;
private _executionContextDestroyedPromise;
_jsonStringifyObjectId: Protocol.Runtime.RemoteObjectId | undefined;
constructor(client: WKSession, contextId: number | undefined);
private readonly _executionContextDestroyedPromise;
constructor(session: WKSession, contextId: number | undefined);
_dispose(): void;
evaluate(context: js.ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any>;
private _evaluateRemoteObject;
private _serializeFunctionAndArguments;
private _contextGlobalObjectId;
private _awaitPromise;
private _returnObjectByValue;

@@ -35,0 +34,0 @@ getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>>;

@@ -26,5 +26,5 @@ "use strict";

class WKExecutionContext {
constructor(client, contextId) {
constructor(session, contextId) {
this._contextDestroyedCallback = () => { };
this._session = client;
this._session = session;
this._contextId = contextId;

@@ -39,2 +39,28 @@ this._executionContextDestroyedPromise = new Promise((resolve, reject) => {

async evaluate(context, returnByValue, pageFunction, ...args) {
try {
let response = await this._evaluateRemoteObject(pageFunction, args);
if (response.result.type === 'object' && response.result.className === 'Promise') {
response = await Promise.race([
this._executionContextDestroyedPromise.then(() => contextDestroyedResult),
this._session.send('Runtime.awaitPromise', {
promiseObjectId: response.result.objectId,
returnByValue: false
})
]);
}
if (response.wasThrown)
throw new Error('Evaluation failed: ' + response.result.description);
if (!returnByValue)
return context._createHandle(response.result);
if (response.result.objectId)
return await this._returnObjectByValue(response.result.objectId);
return wkProtocolHelper_1.valueFromRemoteObject(response.result);
}
catch (error) {
if (wkConnection_1.isSwappedOutError(error) || error.message.includes('Missing injected script for given'))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
}
}
async _evaluateRemoteObject(pageFunction, args) {
if (helper_1.helper.isString(pageFunction)) {

@@ -44,3 +70,3 @@ const contextId = this._contextId;

const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression) ? expression : expression + '\n' + suffix;
return this._session.send('Runtime.evaluate', {
return await this._session.send('Runtime.evaluate', {
expression: expressionWithSourceUrl,

@@ -50,22 +76,24 @@ contextId,

emulateUserGesture: true
}).then(response => {
if (response.result.type === 'object' && response.result.className === 'Promise') {
return Promise.race([
this._executionContextDestroyedPromise.then(() => contextDestroyedResult),
this._awaitPromise(response.result.objectId),
]);
}
return response;
}).then(response => {
if (response.wasThrown)
throw new Error('Evaluation failed: ' + response.result.description);
if (!returnByValue)
return context._createHandle(response.result);
if (response.result.objectId)
return this._returnObjectByValue(response.result.objectId);
return wkProtocolHelper_1.valueFromRemoteObject(response.result);
}).catch(rewriteError);
});
}
if (typeof pageFunction !== 'function')
throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
try {
const callParams = this._serializeFunctionAndArguments(pageFunction, args);
const thisObjectId = await this._contextGlobalObjectId();
return await this._session.send('Runtime.callFunctionOn', {
functionDeclaration: callParams.functionText + '\n' + suffix + '\n',
objectId: thisObjectId,
arguments: callParams.callArguments,
returnByValue: false,
emulateUserGesture: true
});
}
catch (err) {
if (err instanceof TypeError && err.message.startsWith('Converting circular structure to JSON'))
err.message += ' Are you passing a nested JSHandle?';
throw err;
}
}
_serializeFunctionAndArguments(pageFunction, args) {
let functionText = pageFunction.toString();

@@ -108,43 +136,4 @@ try {

}
let thisObjectId;
try {
thisObjectId = await this._contextGlobalObjectId();
}
catch (error) {
if (error.message.includes('Missing injected script for given'))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
}
let callFunctionOnPromise;
try {
callFunctionOnPromise = this._session.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n',
objectId: thisObjectId,
arguments: serializableArgs.map((arg) => this._convertArgument(arg)),
returnByValue: false,
emulateUserGesture: true
});
}
catch (err) {
if (err instanceof TypeError && err.message.startsWith('Converting circular structure to JSON'))
err.message += ' Are you passing a nested JSHandle?';
throw err;
}
return callFunctionOnPromise.then(response => {
if (response.result.type === 'object' && response.result.className === 'Promise') {
return Promise.race([
this._executionContextDestroyedPromise.then(() => contextDestroyedResult),
this._awaitPromise(response.result.objectId),
]);
}
return response;
}).then(response => {
if (response.wasThrown)
throw new Error('Evaluation failed: ' + response.result.description);
if (!returnByValue)
return context._createHandle(response.result);
if (response.result.objectId)
return this._returnObjectByValue(response.result.objectId).catch(() => undefined);
return wkProtocolHelper_1.valueFromRemoteObject(response.result);
}).catch(rewriteError);
const serialized = serializableArgs.map((arg) => this._convertArgument(arg));
return { functionText, callArguments: serialized };
function unserializableToString(arg) {

@@ -184,17 +173,8 @@ if (Object.is(arg, -0))

}
function rewriteError(error) {
if (error.message.includes('Missing injected script for given'))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
}
}
_contextGlobalObjectId() {
if (!this._globalObjectId) {
this._globalObjectId = this._session.send('Runtime.evaluate', {
if (!this._globalObjectIdPromise) {
this._globalObjectIdPromise = this._session.send('Runtime.evaluate', {
expression: 'this',
contextId: this._contextId
}).catch(e => {
if (wkConnection_1.isSwappedOutError(e))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw e;
}).then(response => {

@@ -204,30 +184,22 @@ return response.result.objectId;

}
return this._globalObjectId;
return this._globalObjectIdPromise;
}
_awaitPromise(objectId) {
return this._session.send('Runtime.awaitPromise', {
promiseObjectId: objectId,
returnByValue: false
}).catch(e => {
async _returnObjectByValue(objectId) {
try {
const serializeResponse = await this._session.send('Runtime.callFunctionOn', {
// Serialize object using standard JSON implementation to correctly pass 'undefined'.
functionDeclaration: 'function(){return this}\n' + suffix + '\n',
objectId: objectId,
returnByValue: true
});
if (serializeResponse.wasThrown)
return undefined;
return serializeResponse.result.value;
}
catch (e) {
if (wkConnection_1.isSwappedOutError(e))
return contextDestroyedResult;
throw e;
});
return undefined;
}
}
_returnObjectByValue(objectId) {
return this._session.send('Runtime.callFunctionOn', {
// Serialize object using standard JSON implementation to correctly pass 'undefined'.
functionDeclaration: 'function(){return this}\n' + suffix + '\n',
objectId: objectId,
returnByValue: true
}).catch(e => {
if (wkConnection_1.isSwappedOutError(e))
return contextDestroyedResult;
throw e;
}).then(serializeResponse => {
if (serializeResponse.wasThrown)
throw new Error('Serialization failed: ' + serializeResponse.result.description);
return serializeResponse.result.value;
});
}
async getProperties(handle) {

@@ -234,0 +206,0 @@ const objectId = toRemoteObject(handle).objectId;

@@ -37,7 +37,7 @@ /**

continue(overrides: {
headers?: {
[key: string]: string;
};
method?: string;
headers?: network.Headers;
postData?: string;
}): Promise<void>;
createResponse(responsePayload: Protocol.Network.Response): network.Response;
}

@@ -87,4 +87,6 @@ "use strict";

requestId: this._requestId,
...overrides
}).catch(error => {
method: overrides.method,
headers: overrides.headers,
postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined
}).catch((error) => {
// In certain cases, protocol will return error if the request was already canceled

@@ -91,0 +93,0 @@ // or the page was closed. We should tolerate these errors.

@@ -35,2 +35,3 @@ /**

private readonly _pageProxySession;
private readonly _openerResolver;
private readonly _requestIdToRequest;

@@ -42,3 +43,3 @@ private readonly _workers;

private readonly _bootstrapScripts;
constructor(browserContext: BrowserContext, pageProxySession: WKSession);
constructor(browserContext: BrowserContext, pageProxySession: WKSession, openerResolver: () => Promise<Page | null>);
private _initializePageProxySession;

@@ -48,3 +49,4 @@ private _setSession;

_initializeSession(session: WKSession, resourceTreeHandler: (r: Protocol.Page.getResourceTreeReturnValue) => void): Promise<void>;
onProvisionalLoadStarted(provisionalSession: WKSession): void;
private _initializeSessionMayThrow;
initializeProvisionalPage(provisionalSession: WKSession): Promise<void>;
onProvisionalLoadCommitted(session: WKSession): void;

@@ -60,3 +62,3 @@ onSessionDestroyed(session: WKSession, crashed: boolean): void;

private _handleFrameTree;
_onFrameAttached(frameId: string, parentFrameId: string | null): void;
_onFrameAttached(frameId: string, parentFrameId: string | null): frames.Frame;
private _onFrameNavigated;

@@ -79,2 +81,4 @@ private _onFrameNavigatedWithinDocument;

authenticate(credentials: types.Credentials | null): Promise<void>;
setFileChooserIntercepted(enabled: boolean): Promise<void>;
opener(): Promise<Page | null>;
reload(): Promise<void>;

@@ -81,0 +85,0 @@ goBack(): Promise<boolean>;

@@ -34,3 +34,3 @@ "use strict";

class WKPage {
constructor(browserContext, pageProxySession) {
constructor(browserContext, pageProxySession, openerResolver) {
this._provisionalPage = null;

@@ -41,2 +41,3 @@ this._requestIdToRequest = new Map();

this._pageProxySession = pageProxySession;
this._openerResolver = openerResolver;
this.rawKeyboard = new wkInput_1.RawKeyboardImpl(pageProxySession);

@@ -79,11 +80,23 @@ this.rawMouse = new wkInput_1.RawMouseImpl(pageProxySession);

async _initializeSession(session, resourceTreeHandler) {
const promises = [
await this._initializeSessionMayThrow(session, resourceTreeHandler).catch(e => {
if (session.isDisposed())
return;
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
if (this._session === session)
throw e;
});
}
async _initializeSessionMayThrow(session, resourceTreeHandler) {
const [, frameTree] = await Promise.all([
// Page agent must be enabled before Runtime.
session.send('Page.enable'),
session.send('Page.getResourceTree').then(resourceTreeHandler),
session.send('Page.getResourceTree'),
]);
resourceTreeHandler(frameTree);
const promises = [
// Resource tree should be received before first execution context.
session.send('Runtime.enable'),
session.send('Page.createIsolatedWorld', { name: UTILITY_WORLD_NAME, source: `//# sourceURL=${wkExecutionContext_1.EVALUATION_SCRIPT_URL}` }),
session.send('Page.createUserWorld', { name: UTILITY_WORLD_NAME }).catch(_ => { }),
session.send('Console.enable'),
session.send('Page.setInterceptFileChooserDialog', { enabled: true }),
session.send('Network.enable'),

@@ -113,14 +126,8 @@ this._workers.initializeSession(session)

promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: true }));
await Promise.all(promises).catch(e => {
if (session.isDisposed())
return;
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
if (this._session === session)
throw e;
});
await Promise.all(promises);
}
onProvisionalLoadStarted(provisionalSession) {
initializeProvisionalPage(provisionalSession) {
helper_1.assert(!this._provisionalPage);
this._provisionalPage = new wkProvisionalPage_1.WKProvisionalPage(provisionalSession, this);
return this._provisionalPage.initializationPromise;
}

@@ -213,3 +220,3 @@ onProvisionalLoadCommitted(session) {

_onFrameAttached(frameId, parentFrameId) {
this._page._frameManager.frameAttached(frameId, parentFrameId);
return this._page._frameManager.frameAttached(frameId, parentFrameId);
}

@@ -248,7 +255,7 @@ _onFrameNavigated(framePayload, initial) {

const context = new dom.FrameExecutionContext(delegate, frame);
if (contextPayload.isPageContext)
if (contextPayload.type === 'normal')
frame._contextCreated('main', context);
else if (contextPayload.name === UTILITY_WORLD_NAME)
else if (contextPayload.type === 'user' && contextPayload.name === UTILITY_WORLD_NAME)
frame._contextCreated('utility', context);
if (contextPayload.isPageContext && frame === this._page.mainFrame())
if (contextPayload.type === 'normal' && frame === this._page.mainFrame())
this._mainFrameContextId = contextPayload.id;

@@ -355,2 +362,8 @@ this._contextIdToContext.set(contextPayload.id, context);

}
async setFileChooserIntercepted(enabled) {
await this._session.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => { }); // target can be closed.
}
async opener() {
return await this._openerResolver();
}
async reload() {

@@ -357,0 +370,0 @@ await this._session.send('Page.reload');

@@ -23,2 +23,3 @@ /**

readonly _browserContext: BrowserContext;
private readonly _openerResolver;
private _pagePromise;

@@ -28,5 +29,6 @@ private _wkPage;

private _firstTargetCallback?;
private _pagePausedOnStart;
private readonly _sessions;
private readonly _eventListeners;
constructor(pageProxySession: WKSession, browserContext: BrowserContext);
constructor(pageProxySession: WKSession, browserContext: BrowserContext, openerResolver: () => (WKPageProxy | null));
didClose(): void;

@@ -42,2 +44,3 @@ dispose(): void;

private _onTargetCreated;
private _resumeTarget;
private _onTargetDestroyed;

@@ -44,0 +47,0 @@ private _onDispatchMessageFromTarget;

@@ -24,8 +24,10 @@ "use strict";

class WKPageProxy {
constructor(pageProxySession, browserContext) {
constructor(pageProxySession, browserContext, openerResolver) {
this._pagePromise = null;
this._wkPage = null;
this._pagePausedOnStart = false;
this._sessions = new Map();
this._pageProxySession = pageProxySession;
this._browserContext = browserContext;
this._openerResolver = openerResolver;
this._firstTargetPromise = new Promise(r => this._firstTargetCallback = r);

@@ -38,9 +40,2 @@ this._eventListeners = [

];
// Intercept provisional targets during cross-process navigation.
this._pageProxySession.send('Target.setPauseOnStart', { pauseOnStart: true }).catch(e => {
if (this._pageProxySession.isDisposed())
return;
helper_1.debugError(e);
throw e;
});
}

@@ -89,6 +84,4 @@ didClose() {

onPopupCreated(popupPageProxy) {
const wkPage = this._wkPage;
if (!wkPage || !wkPage._page.listenerCount(events_1.Events.Page.Popup))
return;
popupPageProxy.page().then(page => wkPage._page.emit(events_1.Events.Page.Popup, page));
if (this._wkPage)
popupPageProxy.page().then(page => this._wkPage._page.emit(events_1.Events.Page.Popup, page));
}

@@ -105,4 +98,13 @@ async _initializeWKPage() {

helper_1.assert(session, 'One non-provisional target session must exist');
this._wkPage = new wkPage_1.WKPage(this._browserContext, this._pageProxySession);
this._wkPage = new wkPage_1.WKPage(this._browserContext, this._pageProxySession, async () => {
const pageProxy = this._openerResolver();
if (!pageProxy)
return null;
return await pageProxy.page();
});
await this._wkPage.initialize(session);
if (this._pagePausedOnStart) {
this._resumeTarget(session.sessionId);
this._pagePausedOnStart = false;
}
return this._wkPage._page;

@@ -125,9 +127,26 @@ }

}
if (targetInfo.isProvisional)
if (targetInfo.isProvisional) {
session[isPovisionalSymbol] = true;
if (targetInfo.isProvisional && this._wkPage)
this._wkPage.onProvisionalLoadStarted(session);
if (targetInfo.isPaused)
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(helper_1.debugError);
if (this._wkPage) {
const provisionalPageInitialized = this._wkPage.initializeProvisionalPage(session);
if (targetInfo.isPaused)
provisionalPageInitialized.then(() => this._resumeTarget(targetInfo.targetId));
}
else if (targetInfo.isPaused) {
this._resumeTarget(targetInfo.targetId);
}
}
else if (this._pagePromise) {
helper_1.assert(!this._pagePausedOnStart);
// This is the first time page target is created, will resume
// after finishing intialization.
this._pagePausedOnStart = !!targetInfo.isPaused;
}
else if (targetInfo.isPaused) {
this._resumeTarget(targetInfo.targetId);
}
}
_resumeTarget(targetId) {
this._pageProxySession.send('Target.resume', { targetId }).catch(helper_1.debugError);
}
_onTargetDestroyed(event) {

@@ -134,0 +153,0 @@ const { targetId, crashed } = event;

@@ -23,2 +23,3 @@ /**

private _mainFrameId;
readonly initializationPromise: Promise<void>;
constructor(session: WKSession, page: WKPage);

@@ -25,0 +26,0 @@ dispose(): void;

@@ -41,3 +41,3 @@ "use strict";

];
this._wkPage._initializeSession(session, ({ frameTree }) => this._handleFrameTree(frameTree));
this.initializationPromise = this._wkPage._initializeSession(session, ({ frameTree }) => this._handleFrameTree(frameTree));
}

@@ -44,0 +44,0 @@ dispose() {

@@ -32,3 +32,3 @@ "use strict";

this._sessionListeners = [
helper_1.helper.addEventListener(session, 'Worker.workerCreated', async (event) => {
helper_1.helper.addEventListener(session, 'Worker.workerCreated', (event) => {
const worker = new page_1.Worker(event.url);

@@ -47,14 +47,10 @@ const workerSession = new wkConnection_1.WKSession(session.connection, event.workerId, 'Most likely the worker has been closed.', (message) => {

workerSession.on('Console.messageAdded', event => this._onConsoleMessage(worker, event));
try {
Promise.all([
workerSession.send('Runtime.enable'),
workerSession.send('Console.enable'),
session.send('Worker.initialized', { workerId: event.workerId }).catch(e => {
this._page._removeWorker(event.workerId);
})
]);
}
catch (e) {
Promise.all([
workerSession.send('Runtime.enable'),
workerSession.send('Console.enable'),
session.send('Worker.initialized', { workerId: event.workerId })
]).catch(e => {
// Worker can go as we are initializing it.
}
this._page._removeWorker(event.workerId);
});
}),

@@ -61,0 +57,0 @@ helper_1.helper.addEventListener(session, 'Worker.dispatchMessageFromWorker', (event) => {

{
"name": "playwright-core",
"version": "0.9.24",
"version": "0.10.0",
"description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright",
"engines": {
"node": ">=10.17.0"
"node": ">=10.15.0"
},
"main": "index.js",
"playwright": {
"chromium_revision": "733125",
"firefox_revision": "1018",
"webkit_revision": "1113"
"chromium_revision": "737027",
"firefox_revision": "1021",
"webkit_revision": "1127"
},
"scripts": {
"unit": "node test/test.js",
"funit": "cross-env BROWSER=firefox node test/test.js",
"wunit": "cross-env BROWSER=webkit node test/test.js",
"debug-unit": "node --inspect-brk test/test.js",
"ctest": "cross-env BROWSER=chromium node test/test.js",
"ftest": "cross-env BROWSER=firefox node test/test.js",
"wtest": "cross-env BROWSER=webkit node test/test.js",
"debug-test": "node --inspect-brk test/test.js",
"test-doclint": "node utils/doclint/check_public_api/test/test.js && node utils/doclint/preprocessor/test.js",

@@ -25,5 +25,5 @@ "test": "npm run lint --silent && npm run coverage && npm run test-doclint && node utils/testrunner/test/test.js",

"doc": "node utils/doclint/cli.js",
"coverage": "cross-env COVERAGE=true npm run unit",
"fcoverage": "cross-env COVERAGE=true BROWSER=firefox npm run unit",
"wcoverage": "cross-env COVERAGE=true BROWSER=webkit npm run unit",
"ccoverage": "cross-env COVERAGE=true npm run ctest",
"fcoverage": "cross-env COVERAGE=true BROWSER=firefox node test/test.js",
"wcoverage": "cross-env COVERAGE=true BROWSER=webkit node test/test.js",
"tsc": "tsc -p .",

@@ -30,0 +30,0 @@ "clean": "rimraf lib",

# Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg?style=flat)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-81.0.4032-blue.svg)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-73.0b3-blue.svg)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-13.0.4-blue.svg)](https://webkit.org/) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg)
[![npm version](https://img.shields.io/npm/v/playwright.svg?style=flat)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-81.0.4044-blue.svg)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-73.0b3-blue.svg)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-13.0.4-blue.svg)](https://webkit.org/) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg)

@@ -7,15 +7,14 @@ ###### [API](https://github.com/microsoft/playwright/blob/master/docs/api.md) | [FAQ](#faq) | [Contributing](#contributing)

Playwright is a Node library to automate the [Chromium](https://www.chromium.org/Home), [WebKit](https://webkit.org/) and [Firefox](https://www.mozilla.org/en-US/firefox/new/) browsers with **a single API**. This includes support for the new Microsoft Edge browser, which is based on Chromium.
Playwright is a Node library to automate the [Chromium](https://www.chromium.org/Home), [WebKit](https://webkit.org/) and [Firefox](https://www.mozilla.org/en-US/firefox/new/) browsers with a single API. It enables **cross-browser** web automation that is **ever-green**, **capable**, **reliable** and **fast**.
Playwright is focused on enabling **cross-browser** web automation platform that is **ever-green**, **capable**, **reliable** and **fast**. Our primary goal with Playwright is to improve automated UI testing by eliminating flakiness, improving the speed of execution and offering insights into the browser operation.
| | ver | Linux | macOS | Win |
| ---: | :---: | :---: | :---: | :---: |
| Chromium| 81.0.4032 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium| 81.0.4044 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit | 13.0.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox |73.0b3 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
- Headless is supported for all the browsers on all platforms.
Headless is supported for all browsers on all platforms.
Our primary goal with Playwright is to improve automated UI testing by eliminating flakiness, improving the speed of execution and offering insights into the browser operation.
### Installation

@@ -134,11 +133,11 @@

**Q: Can I use a single API to automate different browsers?**
### Q: Can I use a single API to automate different browsers?
Yes, you can. See [Browser](https://github.com/microsoft/playwright/blob/master/docs/api.md#class-browser) in the API reference for the common set of APIs across Chromium, Firefox and WebKit. A small set of features are specific to browsers, for example see [ChromiumBrowser](https://github.com/microsoft/playwright/blob/master/docs/api.md#class-chromiumbrowser).
**Q: How does Playwright relate to [Puppeteer](https://github.com/puppeteer/puppeteer)?**
### Q: How does Playwright relate to [Puppeteer](https://github.com/puppeteer/puppeteer)?
Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer project is active and is maintained by Google.
We are the same team that built Puppeteer. Puppeteer proved that there is a lot of interest in the new generation of ever-green, capable and reliable automation drivers. With Playwright, we'd like to take it one step further and offer the same functionality for **all** the popular rendering engines. We'd like to see Playwright vendor-neutral and shared governed.
We are the same team that originally built Puppeteer at Google, but has since then moved on. Puppeteer proved that there is a lot of interest in the new generation of ever-green, capable and reliable automation drivers. With Playwright, we'd like to take it one step further and offer the same functionality for **all** the popular rendering engines. We'd like to see Playwright vendor-neutral and shared governed.

@@ -151,3 +150,3 @@ With Playwright, we are making the APIs more testing-friendly as well. We are taking the lessons learned from Puppeteer and incorporate them into the API, for example, user agent / device emulation is set up consistently on the `BrowserContext` level to enable multi-page scenarios, `click` waits for the element to be available and visible by default, there is a way to wait for network and other events, etc.

**Q: What about the [WebDriver](https://www.w3.org/TR/webdriver/)?**
### Q: What about the [WebDriver](https://www.w3.org/TR/webdriver/)?

@@ -162,3 +161,3 @@ We recognize WebDriver as a universal standard for the web automation and testing. At the same time we were excited to see Puppeteer affect the WebDriver agenda, steer it towards the bi-directional communication channel, etc. We hope that Playwright can take it further and pioneer support for numerous PWA features across the browsers as they emerge:

**Q: What browser versions does Playwright use?**
### Q: What browser versions does Playwright use?

@@ -175,4 +174,8 @@ - *Chromium*: Playwright uses upstream versions of Chromium. When we need changes in the browser, they go into the browser directly and then we roll our dependency to that version of Chromium. As of today, we update Chromium as needed or at least once a month. We plan to synchronize our npm release cycle with the Chromium stable channel cadence.

**Q: Is Playwright ready?**
### Q: Does Playwright support new Microsoft Edge?
The new Microsoft Edge browser is based on Chromium, so Playwright supports it.
### Q: Is Playwright ready?
Playwright is ready for your feedback. It respects [semver](https://semver.org/), so please expect some API breakages as we release 1.0. All we can promise is that those breakages are going to be based on your feedback with the sole purpose of making our APIs better.

@@ -179,0 +182,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc