playwright-core
Advanced tools
Comparing version 0.11.1-next.1582931884276 to 0.11.1-next.1582935649965
@@ -251,3 +251,3 @@ "use strict"; | ||
async addInitScript(script, ...args) { | ||
const source = await helper_1.helper.evaluationScript(script, ...args); | ||
const source = await helper_1.helper.evaluationScript(script, args); | ||
this._evaluateOnNewDocumentSources.push(source); | ||
@@ -254,0 +254,0 @@ for (const page of this._existingPages()) |
@@ -66,5 +66,8 @@ "use strict"; | ||
if (!this._injectedPromise) { | ||
const custom = []; | ||
for (const [name, source] of selectors._engines) | ||
custom.push(`{ name: '${name}', engine: (${source}) }`); | ||
const source = ` | ||
new (${injectedSource.source})([ | ||
${selectors._sources.join(',\n')} | ||
${custom.join(',\n')} | ||
]) | ||
@@ -71,0 +74,0 @@ `; |
@@ -301,3 +301,3 @@ "use strict"; | ||
async addInitScript(script, ...args) { | ||
const source = await helper_1.helper.evaluationScript(script, ...args); | ||
const source = await helper_1.helper.evaluationScript(script, args); | ||
this._evaluateOnNewDocumentSources.push(source); | ||
@@ -304,0 +304,0 @@ await this._browser._connection.send('Browser.addScriptToEvaluateOnNewDocument', { browserContextId: this._browserContextId || undefined, script: source }); |
@@ -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/injected.ts\");\n/******/ })\n/************************************************************************/\n/******/ ({\n\n/***/ \"./src/injected/cssSelectorEngine.ts\":\n/*!*******************************************!*\\\n !*** ./src/injected/cssSelectorEngine.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 });\nexports.CSSEngine = {\n name: 'css',\n create(root, targetElement) {\n const tokens = [];\n function uniqueCSSSelector(prefix) {\n const path = tokens.slice();\n if (prefix)\n path.unshift(prefix);\n const selector = path.join(' > ');\n const nodes = Array.from(root.querySelectorAll(selector));\n return nodes[0] === targetElement ? selector : undefined;\n }\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n const nodeName = element.nodeName.toLowerCase();\n // Element ID is the strongest signal, use it.\n let bestTokenForLevel = '';\n if (element.id) {\n const token = /^[a-zA-Z][a-zA-Z0-9\\-\\_]+$/.test(element.id) ? '#' + element.id : `[id=\"${element.id}\"]`;\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n bestTokenForLevel = token;\n }\n const parent = element.parentElement;\n // Combine class names until unique.\n const classes = Array.from(element.classList);\n for (let i = 0; i < classes.length; ++i) {\n const token = '.' + classes.slice(0, i + 1).join('.');\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n // Even if not unique, does this subset of classes uniquely identify node as a child?\n if (!bestTokenForLevel && parent) {\n const sameClassSiblings = parent.querySelectorAll(token);\n if (sameClassSiblings.length === 1)\n bestTokenForLevel = token;\n }\n }\n // Ordinal is the weakest signal.\n if (parent) {\n const siblings = Array.from(parent.children);\n const sameTagSiblings = siblings.filter(sibling => (sibling).nodeName.toLowerCase() === nodeName);\n const token = sameTagSiblings.length === 1 ? nodeName : `${nodeName}:nth-child(${1 + siblings.indexOf(element)})`;\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n if (!bestTokenForLevel)\n bestTokenForLevel = token;\n }\n else if (!bestTokenForLevel) {\n bestTokenForLevel = nodeName;\n }\n tokens.unshift(bestTokenForLevel);\n }\n return uniqueCSSSelector();\n },\n query(root, selector) {\n return root.querySelector(selector) || undefined;\n },\n queryAll(root, selector) {\n return Array.from(root.querySelectorAll(selector));\n }\n};\n\n\n/***/ }),\n\n/***/ \"./src/injected/injected.ts\":\n/*!**********************************!*\\\n !*** ./src/injected/injected.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 });\nconst utils_1 = __webpack_require__(/*! ./utils */ \"./src/injected/utils.ts\");\nconst cssSelectorEngine_1 = __webpack_require__(/*! ./cssSelectorEngine */ \"./src/injected/cssSelectorEngine.ts\");\nconst xpathSelectorEngine_1 = __webpack_require__(/*! ./xpathSelectorEngine */ \"./src/injected/xpathSelectorEngine.ts\");\nconst textSelectorEngine_1 = __webpack_require__(/*! ./textSelectorEngine */ \"./src/injected/textSelectorEngine.ts\");\nfunction createAttributeEngine(attribute) {\n const engine = {\n name: attribute,\n create(root, target) {\n const value = target.getAttribute(attribute);\n if (!value)\n return;\n if (root.querySelector(`[${attribute}=${value}]`) === target)\n return value;\n },\n query(root, selector) {\n return root.querySelector(`[${attribute}=${selector}]`) || undefined;\n },\n queryAll(root, selector) {\n return Array.from(root.querySelectorAll(`[${attribute}=${selector}]`));\n }\n };\n return engine;\n}\nclass Injected {\n constructor(customEngines) {\n const defaultEngines = [\n cssSelectorEngine_1.CSSEngine,\n xpathSelectorEngine_1.XPathEngine,\n textSelectorEngine_1.TextEngine,\n createAttributeEngine('id'),\n createAttributeEngine('data-testid'),\n createAttributeEngine('data-test-id'),\n createAttributeEngine('data-test'),\n ];\n this.utils = new utils_1.Utils();\n this.engines = new Map();\n for (const engine of [...defaultEngines, ...customEngines])\n this.engines.set(engine.name, engine);\n }\n querySelector(selector, root) {\n const parsed = this._parseSelector(selector);\n if (!root['querySelector'])\n throw new Error('Node is not queryable.');\n let element = root;\n for (const { engine, selector } of parsed) {\n const next = engine.query(element.shadowRoot || element, selector);\n if (!next)\n return;\n element = next;\n }\n return element;\n }\n querySelectorAll(selector, root) {\n const parsed = this._parseSelector(selector);\n if (!root['querySelectorAll'])\n throw new Error('Node is not queryable.');\n let set = new Set([root]);\n for (const { engine, selector } of parsed) {\n const newSet = new Set();\n for (const prev of set) {\n for (const next of engine.queryAll(prev.shadowRoot || prev, selector)) {\n if (newSet.has(next))\n continue;\n newSet.add(next);\n }\n }\n set = newSet;\n }\n return Array.from(set);\n }\n _parseSelector(selector) {\n let index = 0;\n let quote;\n let start = 0;\n const result = [];\n const append = () => {\n const part = selector.substring(start, index).trim();\n const eqIndex = part.indexOf('=');\n let name;\n let body;\n if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-]+$/)) {\n name = part.substring(0, eqIndex).trim();\n body = part.substring(eqIndex + 1);\n }\n else if (part.startsWith('\"')) {\n name = 'text';\n body = part;\n }\n else if (/^\\(*\\/\\//.test(part)) {\n // If selector starts with '//' or '//' prefixed with multiple opening\n // parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817\n name = 'xpath';\n body = part;\n }\n else {\n name = 'css';\n body = part;\n }\n const engine = this.engines.get(name.toLowerCase());\n if (!engine)\n throw new Error(`Unknown engine ${name} while parsing selector ${selector}`);\n result.push({ engine, selector: body });\n };\n while (index < selector.length) {\n const c = selector[index];\n if (c === '\\\\' && index + 1 < selector.length) {\n index += 2;\n }\n else if (c === quote) {\n quote = undefined;\n index++;\n }\n else if (!quote && c === '>' && selector[index + 1] === '>') {\n append();\n index += 2;\n start = index;\n }\n else {\n index++;\n }\n }\n append();\n return result;\n }\n isVisible(element) {\n if (!element.ownerDocument || !element.ownerDocument.defaultView)\n return true;\n const style = element.ownerDocument.defaultView.getComputedStyle(element);\n if (!style || style.visibility === 'hidden')\n return false;\n const rect = element.getBoundingClientRect();\n return !!(rect.top || rect.bottom || rect.width || rect.height);\n }\n _pollMutation(selector, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n return Promise.resolve(success);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const observer = new MutationObserver(() => {\n if (timedOut) {\n observer.disconnect();\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success) {\n observer.disconnect();\n fulfill(success);\n }\n });\n observer.observe(document, {\n childList: true,\n subtree: true,\n attributes: true\n });\n return result;\n }\n _pollRaf(selector, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const onRaf = () => {\n if (timedOut) {\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n fulfill(success);\n else\n requestAnimationFrame(onRaf);\n };\n onRaf();\n return result;\n }\n _pollInterval(selector, pollInterval, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const onTimeout = () => {\n if (timedOut) {\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n fulfill(success);\n else\n setTimeout(onTimeout, pollInterval);\n };\n onTimeout();\n return result;\n }\n poll(polling, selector, timeout, predicate) {\n if (polling === 'raf')\n return this._pollRaf(selector, predicate, timeout);\n if (polling === 'mutation')\n return this._pollMutation(selector, predicate, timeout);\n return this._pollInterval(selector, polling, predicate, timeout);\n }\n getElementBorderWidth(node) {\n if (node.nodeType !== Node.ELEMENT_NODE || !node.ownerDocument || !node.ownerDocument.defaultView)\n return { left: 0, top: 0 };\n const style = node.ownerDocument.defaultView.getComputedStyle(node);\n return { left: parseInt(style.borderLeftWidth || '', 10), top: parseInt(style.borderTopWidth || '', 10) };\n }\n selectOptions(node, optionsToSelect) {\n if (node.nodeName.toLowerCase() !== 'select')\n throw new Error('Element is not a <select> element.');\n const element = node;\n const options = Array.from(element.options);\n element.value = undefined;\n for (let index = 0; index < options.length; index++) {\n const option = options[index];\n option.selected = optionsToSelect.some(optionToSelect => {\n if (optionToSelect instanceof Node)\n return option === optionToSelect;\n let matches = true;\n if (optionToSelect.value !== undefined)\n matches = matches && optionToSelect.value === option.value;\n if (optionToSelect.label !== undefined)\n matches = matches && optionToSelect.label === option.label;\n if (optionToSelect.index !== undefined)\n matches = matches && optionToSelect.index === index;\n return matches;\n });\n if (option.selected && !element.multiple)\n break;\n }\n element.dispatchEvent(new Event('input', { 'bubbles': true }));\n element.dispatchEvent(new Event('change', { 'bubbles': true }));\n return options.filter(option => option.selected).map(option => option.value);\n }\n fill(node, value) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n return 'Node is not of type HTMLElement';\n const element = node;\n if (!element.isConnected)\n return 'Element is not attached to the DOM';\n if (!element.ownerDocument || !element.ownerDocument.defaultView)\n return 'Element does not belong to a window';\n const style = element.ownerDocument.defaultView.getComputedStyle(element);\n if (!style || style.visibility === 'hidden')\n return 'Element is hidden';\n if (!element.offsetParent && element.tagName !== 'BODY')\n return 'Element is not visible';\n if (element.nodeName.toLowerCase() === 'input') {\n const input = element;\n const type = input.getAttribute('type') || '';\n const kTextInputTypes = new Set(['', 'email', 'number', 'password', 'search', 'tel', 'text', 'url']);\n if (!kTextInputTypes.has(type.toLowerCase()))\n return 'Cannot fill input of type \"' + type + '\".';\n if (type.toLowerCase() === 'number') {\n value = value.trim();\n if (!value || isNaN(Number(value)))\n return 'Cannot type text into input[type=number].';\n }\n if (input.disabled)\n return 'Cannot fill a disabled input.';\n if (input.readOnly)\n return 'Cannot fill a readonly input.';\n input.select();\n input.focus();\n }\n else if (element.nodeName.toLowerCase() === 'textarea') {\n const textarea = element;\n if (textarea.disabled)\n return 'Cannot fill a disabled textarea.';\n if (textarea.readOnly)\n return 'Cannot fill a readonly textarea.';\n textarea.selectionStart = 0;\n textarea.selectionEnd = textarea.value.length;\n textarea.focus();\n }\n else if (element.isContentEditable) {\n const range = element.ownerDocument.createRange();\n range.selectNodeContents(element);\n const selection = element.ownerDocument.defaultView.getSelection();\n if (!selection)\n return 'Element belongs to invisible iframe.';\n selection.removeAllRanges();\n selection.addRange(range);\n element.focus();\n }\n else {\n return 'Element is not an <input>, <textarea> or [contenteditable] element.';\n }\n return false;\n }\n isCheckboxChecked(node) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n throw new Error('Not a checkbox or radio button');\n let element = node;\n if (element.getAttribute('role') === 'checkbox')\n return element.getAttribute('aria-checked') === 'true';\n if (element.nodeName === 'LABEL') {\n const forId = element.getAttribute('for');\n if (forId && element.ownerDocument)\n element = element.ownerDocument.querySelector(`input[id=\"${forId}\"]`) || undefined;\n else\n element = element.querySelector('input[type=checkbox],input[type=radio]') || undefined;\n }\n if (element && element.nodeName === 'INPUT') {\n const type = element.getAttribute('type');\n if (type && (type.toLowerCase() === 'checkbox' || type.toLowerCase() === 'radio'))\n return element.checked;\n }\n throw new Error('Not a checkbox');\n }\n waitForStablePosition(node, timeout) {\n if (!node.isConnected)\n throw new Error('Element is not attached to the DOM');\n const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;\n if (!element)\n throw new Error('Element is not attached to the DOM');\n let lastRect;\n let counter = 0;\n return this.poll('raf', undefined, timeout, () => {\n // First raf happens in the same animation frame as evaluation, so it does not produce\n // any client rect difference compared to synchronous call. We skip the synchronous call\n // and only force layout during actual rafs as a small optimisation.\n if (++counter === 1)\n return false;\n const clientRect = element.getBoundingClientRect();\n const rect = { x: clientRect.top, y: clientRect.left, width: clientRect.width, height: clientRect.height };\n const isStable = lastRect && rect.x === lastRect.x && rect.y === lastRect.y && rect.width === lastRect.width && rect.height === lastRect.height;\n lastRect = rect;\n return isStable;\n });\n }\n waitForHitTargetAt(node, timeout, point) {\n const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;\n if (!element)\n throw new Error('Element is not attached to the DOM');\n return this.poll('raf', undefined, timeout, () => {\n let hitElement = this.utils.deepElementFromPoint(document, point.x, point.y);\n while (hitElement && hitElement !== element)\n hitElement = this.utils.parentElementOrShadowHost(hitElement);\n return hitElement === element;\n });\n }\n}\nexports.default = Injected;\n\n\n/***/ }),\n\n/***/ \"./src/injected/textSelectorEngine.ts\":\n/*!********************************************!*\\\n !*** ./src/injected/textSelectorEngine.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 });\nexports.TextEngine = {\n name: 'text',\n create(root, targetElement, type) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n for (let child = targetElement.firstChild; child; child = child.nextSibling) {\n if (child.nodeType === 3 /* Node.TEXT_NODE */) {\n const text = child.nodeValue;\n if (!text)\n continue;\n if (text.match(/^\\s*[a-zA-Z0-9]+\\s*$/) && exports.TextEngine.query(root, text.trim()) === targetElement)\n return text.trim();\n if (exports.TextEngine.query(root, JSON.stringify(text)) === targetElement)\n return JSON.stringify(text);\n }\n }\n },\n query(root, selector) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n const matcher = createMatcher(selector);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const node = walker.currentNode;\n const element = node.parentElement;\n const text = node.nodeValue;\n if (element && text && matcher(text))\n return element;\n }\n },\n queryAll(root, selector) {\n const result = [];\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return result;\n const matcher = createMatcher(selector);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const node = walker.currentNode;\n const element = node.parentElement;\n const text = node.nodeValue;\n if (element && text && matcher(text))\n result.push(element);\n }\n return result;\n }\n};\nfunction createMatcher(selector) {\n if (selector[0] === '\"' && selector[selector.length - 1] === '\"') {\n const parsed = JSON.parse(selector);\n return text => text === parsed;\n }\n if (selector[0] === '/' && selector.lastIndexOf('/') > 0) {\n const lastSlash = selector.lastIndexOf('/');\n const re = new RegExp(selector.substring(1, lastSlash), selector.substring(lastSlash + 1));\n return text => re.test(text);\n }\n selector = selector.trim().toLowerCase();\n return text => text.trim().toLowerCase() === selector;\n}\n\n\n/***/ }),\n\n/***/ \"./src/injected/utils.ts\":\n/*!*******************************!*\\\n !*** ./src/injected/utils.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 });\nclass Utils {\n parentElementOrShadowHost(element) {\n if (element.parentElement)\n return element.parentElement;\n if (!element.parentNode)\n return;\n if (element.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE && element.parentNode.host)\n return element.parentNode.host;\n }\n deepElementFromPoint(document, x, y) {\n let container = document;\n let element;\n while (container) {\n const innerElement = container.elementFromPoint(x, y);\n if (!innerElement || element === innerElement)\n break;\n element = innerElement;\n container = element.shadowRoot;\n }\n return element;\n }\n}\nexports.Utils = Utils;\n\n\n/***/ }),\n\n/***/ \"./src/injected/xpathSelectorEngine.ts\":\n/*!*********************************************!*\\\n !*** ./src/injected/xpathSelectorEngine.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 });\nconst maxTextLength = 80;\nconst minMeaningfulSelectorLegth = 100;\nexports.XPathEngine = {\n name: 'xpath',\n create(root, targetElement, type) {\n const maybeDocument = root instanceof Document ? root : root.ownerDocument;\n if (!maybeDocument)\n return;\n const document = maybeDocument;\n const xpathCache = new Map();\n if (type === 'notext')\n return createNoText(root, targetElement);\n const tokens = [];\n function evaluateXPath(expression) {\n let nodes = xpathCache.get(expression);\n if (!nodes) {\n nodes = [];\n try {\n const result = document.evaluate(expression, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = result.iterateNext(); node; node = result.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n nodes.push(node);\n }\n }\n catch (e) {\n }\n xpathCache.set(expression, nodes);\n }\n return nodes;\n }\n function uniqueXPathSelector(prefix) {\n const path = tokens.slice();\n if (prefix)\n path.unshift(prefix);\n let selector = '//' + path.join('/');\n while (selector.includes('///'))\n selector = selector.replace('///', '//');\n if (selector.endsWith('/'))\n selector = selector.substring(0, selector.length - 1);\n const nodes = evaluateXPath(selector);\n if (nodes[nodes.length - 1] === targetElement)\n return selector;\n // If we are looking at a small set of elements with long selector, fall back to ordinal.\n if (nodes.length < 5 && selector.length > minMeaningfulSelectorLegth) {\n const index = nodes.indexOf(targetElement);\n if (index !== -1)\n return `(${selector})[${index + 1}]`;\n }\n return undefined;\n }\n function escapeAndCap(text) {\n text = text.substring(0, maxTextLength);\n // XPath 1.0 does not support quote escaping.\n // 1. If there are no single quotes - use them.\n if (text.indexOf(`'`) === -1)\n return `'${text}'`;\n // 2. If there are no double quotes - use them to enclose text.\n if (text.indexOf(`\"`) === -1)\n return `\"${text}\"`;\n // 3. Otherwise, use popular |concat| trick.\n const Q = `'`;\n return `concat(${text.split(Q).map(token => Q + token + Q).join(`, \"'\", `)})`;\n }\n const defaultAttributes = new Set(['title', 'aria-label', 'disabled', 'role']);\n const importantAttributes = new Map([\n ['form', ['action']],\n ['img', ['alt']],\n ['input', ['placeholder', 'type', 'name', 'value']],\n ]);\n let usedTextConditions = false;\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n const nodeName = element.nodeName.toLowerCase();\n const tag = nodeName === 'svg' ? '*' : nodeName;\n const tagConditions = [];\n if (nodeName === 'svg')\n tagConditions.push('local-name()=\"svg\"');\n const attrConditions = [];\n const importantAttrs = [...defaultAttributes, ...(importantAttributes.get(tag) || [])];\n for (const attr of importantAttrs) {\n const value = element.getAttribute(attr);\n if (value && value.length < maxTextLength)\n attrConditions.push(`normalize-space(@${attr})=${escapeAndCap(value)}`);\n else if (value)\n attrConditions.push(`starts-with(normalize-space(@${attr}), ${escapeAndCap(value)})`);\n }\n const text = document.evaluate('normalize-space(.)', element).stringValue;\n const textConditions = [];\n if (tag !== 'select' && text.length && !usedTextConditions) {\n if (text.length < maxTextLength)\n textConditions.push(`normalize-space(.)=${escapeAndCap(text)}`);\n else\n textConditions.push(`starts-with(normalize-space(.), ${escapeAndCap(text)})`);\n usedTextConditions = true;\n }\n // Always retain the last tag.\n const conditions = [...tagConditions, ...textConditions, ...attrConditions];\n const token = conditions.length ? `${tag}[${conditions.join(' and ')}]` : (tokens.length ? '' : tag);\n const selector = uniqueXPathSelector(token);\n if (selector)\n return selector;\n // Ordinal is the weakest signal.\n const parent = element.parentElement;\n let tagWithOrdinal = tag;\n if (parent) {\n const siblings = Array.from(parent.children);\n const sameTagSiblings = siblings.filter(sibling => (sibling).nodeName.toLowerCase() === nodeName);\n if (sameTagSiblings.length > 1)\n tagWithOrdinal += `[${1 + siblings.indexOf(element)}]`;\n }\n // Do not include text into this token, only tag / attributes.\n // Topmost node will get all the text.\n const nonTextConditions = [...tagConditions, ...attrConditions];\n const levelToken = nonTextConditions.length ? `${tagWithOrdinal}[${nonTextConditions.join(' and ')}]` : tokens.length ? '' : tagWithOrdinal;\n tokens.unshift(levelToken);\n }\n return uniqueXPathSelector();\n },\n query(root, selector) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n const it = document.evaluate(selector, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = it.iterateNext(); node; node = it.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n return node;\n }\n },\n queryAll(root, selector) {\n const result = [];\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return result;\n const it = document.evaluate(selector, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = it.iterateNext(); node; node = it.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n result.push(node);\n }\n return result;\n }\n};\nfunction createNoText(root, targetElement) {\n const steps = [];\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n if (element.getAttribute('id')) {\n steps.unshift(`//*[@id=\"${element.getAttribute('id')}\"]`);\n return steps.join('/');\n }\n const siblings = element.parentElement ? Array.from(element.parentElement.children) : [];\n const similarElements = siblings.filter(sibling => element.nodeName === sibling.nodeName);\n const index = similarElements.length === 1 ? 0 : similarElements.indexOf(element) + 1;\n steps.unshift(index ? `${element.nodeName}[${index}]` : element.nodeName);\n }\n return '/' + steps.join('/');\n}\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/injected.ts\");\n/******/ })\n/************************************************************************/\n/******/ ({\n\n/***/ \"./src/injected/cssSelectorEngine.ts\":\n/*!*******************************************!*\\\n !*** ./src/injected/cssSelectorEngine.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 });\nexports.CSSEngine = {\n create(root, targetElement) {\n const tokens = [];\n function uniqueCSSSelector(prefix) {\n const path = tokens.slice();\n if (prefix)\n path.unshift(prefix);\n const selector = path.join(' > ');\n const nodes = Array.from(root.querySelectorAll(selector));\n return nodes[0] === targetElement ? selector : undefined;\n }\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n const nodeName = element.nodeName.toLowerCase();\n // Element ID is the strongest signal, use it.\n let bestTokenForLevel = '';\n if (element.id) {\n const token = /^[a-zA-Z][a-zA-Z0-9\\-\\_]+$/.test(element.id) ? '#' + element.id : `[id=\"${element.id}\"]`;\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n bestTokenForLevel = token;\n }\n const parent = element.parentElement;\n // Combine class names until unique.\n const classes = Array.from(element.classList);\n for (let i = 0; i < classes.length; ++i) {\n const token = '.' + classes.slice(0, i + 1).join('.');\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n // Even if not unique, does this subset of classes uniquely identify node as a child?\n if (!bestTokenForLevel && parent) {\n const sameClassSiblings = parent.querySelectorAll(token);\n if (sameClassSiblings.length === 1)\n bestTokenForLevel = token;\n }\n }\n // Ordinal is the weakest signal.\n if (parent) {\n const siblings = Array.from(parent.children);\n const sameTagSiblings = siblings.filter(sibling => (sibling).nodeName.toLowerCase() === nodeName);\n const token = sameTagSiblings.length === 1 ? nodeName : `${nodeName}:nth-child(${1 + siblings.indexOf(element)})`;\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n if (!bestTokenForLevel)\n bestTokenForLevel = token;\n }\n else if (!bestTokenForLevel) {\n bestTokenForLevel = nodeName;\n }\n tokens.unshift(bestTokenForLevel);\n }\n return uniqueCSSSelector();\n },\n query(root, selector) {\n return root.querySelector(selector) || undefined;\n },\n queryAll(root, selector) {\n return Array.from(root.querySelectorAll(selector));\n }\n};\n\n\n/***/ }),\n\n/***/ \"./src/injected/injected.ts\":\n/*!**********************************!*\\\n !*** ./src/injected/injected.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 });\nconst utils_1 = __webpack_require__(/*! ./utils */ \"./src/injected/utils.ts\");\nconst cssSelectorEngine_1 = __webpack_require__(/*! ./cssSelectorEngine */ \"./src/injected/cssSelectorEngine.ts\");\nconst xpathSelectorEngine_1 = __webpack_require__(/*! ./xpathSelectorEngine */ \"./src/injected/xpathSelectorEngine.ts\");\nconst textSelectorEngine_1 = __webpack_require__(/*! ./textSelectorEngine */ \"./src/injected/textSelectorEngine.ts\");\nfunction createAttributeEngine(attribute) {\n const engine = {\n create(root, target) {\n const value = target.getAttribute(attribute);\n if (!value)\n return;\n if (root.querySelector(`[${attribute}=${value}]`) === target)\n return value;\n },\n query(root, selector) {\n return root.querySelector(`[${attribute}=${selector}]`) || undefined;\n },\n queryAll(root, selector) {\n return Array.from(root.querySelectorAll(`[${attribute}=${selector}]`));\n }\n };\n return engine;\n}\nclass Injected {\n constructor(customEngines) {\n this.utils = new utils_1.Utils();\n this.engines = new Map();\n // Note: keep predefined names in sync with Selectors class.\n this.engines.set('css', cssSelectorEngine_1.CSSEngine);\n this.engines.set('xpath', xpathSelectorEngine_1.XPathEngine);\n this.engines.set('text', textSelectorEngine_1.TextEngine);\n this.engines.set('id', createAttributeEngine('id'));\n this.engines.set('data-testid', createAttributeEngine('data-testid'));\n this.engines.set('data-test-id', createAttributeEngine('data-test-id'));\n this.engines.set('data-test', createAttributeEngine('data-test'));\n for (const { name, engine } of customEngines)\n this.engines.set(name, engine);\n }\n querySelector(selector, root) {\n const parsed = this._parseSelector(selector);\n if (!root['querySelector'])\n throw new Error('Node is not queryable.');\n let element = root;\n for (const { engine, selector } of parsed) {\n const next = engine.query(element.shadowRoot || element, selector);\n if (!next)\n return;\n element = next;\n }\n return element;\n }\n querySelectorAll(selector, root) {\n const parsed = this._parseSelector(selector);\n if (!root['querySelectorAll'])\n throw new Error('Node is not queryable.');\n let set = new Set([root]);\n for (const { engine, selector } of parsed) {\n const newSet = new Set();\n for (const prev of set) {\n for (const next of engine.queryAll(prev.shadowRoot || prev, selector)) {\n if (newSet.has(next))\n continue;\n newSet.add(next);\n }\n }\n set = newSet;\n }\n return Array.from(set);\n }\n _parseSelector(selector) {\n let index = 0;\n let quote;\n let start = 0;\n const result = [];\n const append = () => {\n const part = selector.substring(start, index).trim();\n const eqIndex = part.indexOf('=');\n let name;\n let body;\n if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-]+$/)) {\n name = part.substring(0, eqIndex).trim();\n body = part.substring(eqIndex + 1);\n }\n else if (part.startsWith('\"')) {\n name = 'text';\n body = part;\n }\n else if (/^\\(*\\/\\//.test(part)) {\n // If selector starts with '//' or '//' prefixed with multiple opening\n // parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817\n name = 'xpath';\n body = part;\n }\n else {\n name = 'css';\n body = part;\n }\n const engine = this.engines.get(name.toLowerCase());\n if (!engine)\n throw new Error(`Unknown engine ${name} while parsing selector ${selector}`);\n result.push({ engine, selector: body });\n };\n while (index < selector.length) {\n const c = selector[index];\n if (c === '\\\\' && index + 1 < selector.length) {\n index += 2;\n }\n else if (c === quote) {\n quote = undefined;\n index++;\n }\n else if (!quote && c === '>' && selector[index + 1] === '>') {\n append();\n index += 2;\n start = index;\n }\n else {\n index++;\n }\n }\n append();\n return result;\n }\n isVisible(element) {\n if (!element.ownerDocument || !element.ownerDocument.defaultView)\n return true;\n const style = element.ownerDocument.defaultView.getComputedStyle(element);\n if (!style || style.visibility === 'hidden')\n return false;\n const rect = element.getBoundingClientRect();\n return !!(rect.top || rect.bottom || rect.width || rect.height);\n }\n _pollMutation(selector, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n return Promise.resolve(success);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const observer = new MutationObserver(() => {\n if (timedOut) {\n observer.disconnect();\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success) {\n observer.disconnect();\n fulfill(success);\n }\n });\n observer.observe(document, {\n childList: true,\n subtree: true,\n attributes: true\n });\n return result;\n }\n _pollRaf(selector, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const onRaf = () => {\n if (timedOut) {\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n fulfill(success);\n else\n requestAnimationFrame(onRaf);\n };\n onRaf();\n return result;\n }\n _pollInterval(selector, pollInterval, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const onTimeout = () => {\n if (timedOut) {\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n fulfill(success);\n else\n setTimeout(onTimeout, pollInterval);\n };\n onTimeout();\n return result;\n }\n poll(polling, selector, timeout, predicate) {\n if (polling === 'raf')\n return this._pollRaf(selector, predicate, timeout);\n if (polling === 'mutation')\n return this._pollMutation(selector, predicate, timeout);\n return this._pollInterval(selector, polling, predicate, timeout);\n }\n getElementBorderWidth(node) {\n if (node.nodeType !== Node.ELEMENT_NODE || !node.ownerDocument || !node.ownerDocument.defaultView)\n return { left: 0, top: 0 };\n const style = node.ownerDocument.defaultView.getComputedStyle(node);\n return { left: parseInt(style.borderLeftWidth || '', 10), top: parseInt(style.borderTopWidth || '', 10) };\n }\n selectOptions(node, optionsToSelect) {\n if (node.nodeName.toLowerCase() !== 'select')\n throw new Error('Element is not a <select> element.');\n const element = node;\n const options = Array.from(element.options);\n element.value = undefined;\n for (let index = 0; index < options.length; index++) {\n const option = options[index];\n option.selected = optionsToSelect.some(optionToSelect => {\n if (optionToSelect instanceof Node)\n return option === optionToSelect;\n let matches = true;\n if (optionToSelect.value !== undefined)\n matches = matches && optionToSelect.value === option.value;\n if (optionToSelect.label !== undefined)\n matches = matches && optionToSelect.label === option.label;\n if (optionToSelect.index !== undefined)\n matches = matches && optionToSelect.index === index;\n return matches;\n });\n if (option.selected && !element.multiple)\n break;\n }\n element.dispatchEvent(new Event('input', { 'bubbles': true }));\n element.dispatchEvent(new Event('change', { 'bubbles': true }));\n return options.filter(option => option.selected).map(option => option.value);\n }\n fill(node, value) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n return 'Node is not of type HTMLElement';\n const element = node;\n if (!element.isConnected)\n return 'Element is not attached to the DOM';\n if (!element.ownerDocument || !element.ownerDocument.defaultView)\n return 'Element does not belong to a window';\n const style = element.ownerDocument.defaultView.getComputedStyle(element);\n if (!style || style.visibility === 'hidden')\n return 'Element is hidden';\n if (!element.offsetParent && element.tagName !== 'BODY')\n return 'Element is not visible';\n if (element.nodeName.toLowerCase() === 'input') {\n const input = element;\n const type = input.getAttribute('type') || '';\n const kTextInputTypes = new Set(['', 'email', 'number', 'password', 'search', 'tel', 'text', 'url']);\n if (!kTextInputTypes.has(type.toLowerCase()))\n return 'Cannot fill input of type \"' + type + '\".';\n if (type.toLowerCase() === 'number') {\n value = value.trim();\n if (!value || isNaN(Number(value)))\n return 'Cannot type text into input[type=number].';\n }\n if (input.disabled)\n return 'Cannot fill a disabled input.';\n if (input.readOnly)\n return 'Cannot fill a readonly input.';\n input.select();\n input.focus();\n }\n else if (element.nodeName.toLowerCase() === 'textarea') {\n const textarea = element;\n if (textarea.disabled)\n return 'Cannot fill a disabled textarea.';\n if (textarea.readOnly)\n return 'Cannot fill a readonly textarea.';\n textarea.selectionStart = 0;\n textarea.selectionEnd = textarea.value.length;\n textarea.focus();\n }\n else if (element.isContentEditable) {\n const range = element.ownerDocument.createRange();\n range.selectNodeContents(element);\n const selection = element.ownerDocument.defaultView.getSelection();\n if (!selection)\n return 'Element belongs to invisible iframe.';\n selection.removeAllRanges();\n selection.addRange(range);\n element.focus();\n }\n else {\n return 'Element is not an <input>, <textarea> or [contenteditable] element.';\n }\n return false;\n }\n isCheckboxChecked(node) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n throw new Error('Not a checkbox or radio button');\n let element = node;\n if (element.getAttribute('role') === 'checkbox')\n return element.getAttribute('aria-checked') === 'true';\n if (element.nodeName === 'LABEL') {\n const forId = element.getAttribute('for');\n if (forId && element.ownerDocument)\n element = element.ownerDocument.querySelector(`input[id=\"${forId}\"]`) || undefined;\n else\n element = element.querySelector('input[type=checkbox],input[type=radio]') || undefined;\n }\n if (element && element.nodeName === 'INPUT') {\n const type = element.getAttribute('type');\n if (type && (type.toLowerCase() === 'checkbox' || type.toLowerCase() === 'radio'))\n return element.checked;\n }\n throw new Error('Not a checkbox');\n }\n waitForStablePosition(node, timeout) {\n if (!node.isConnected)\n throw new Error('Element is not attached to the DOM');\n const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;\n if (!element)\n throw new Error('Element is not attached to the DOM');\n let lastRect;\n let counter = 0;\n return this.poll('raf', undefined, timeout, () => {\n // First raf happens in the same animation frame as evaluation, so it does not produce\n // any client rect difference compared to synchronous call. We skip the synchronous call\n // and only force layout during actual rafs as a small optimisation.\n if (++counter === 1)\n return false;\n const clientRect = element.getBoundingClientRect();\n const rect = { x: clientRect.top, y: clientRect.left, width: clientRect.width, height: clientRect.height };\n const isStable = lastRect && rect.x === lastRect.x && rect.y === lastRect.y && rect.width === lastRect.width && rect.height === lastRect.height;\n lastRect = rect;\n return isStable;\n });\n }\n waitForHitTargetAt(node, timeout, point) {\n const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;\n if (!element)\n throw new Error('Element is not attached to the DOM');\n return this.poll('raf', undefined, timeout, () => {\n let hitElement = this.utils.deepElementFromPoint(document, point.x, point.y);\n while (hitElement && hitElement !== element)\n hitElement = this.utils.parentElementOrShadowHost(hitElement);\n return hitElement === element;\n });\n }\n}\nexports.default = Injected;\n\n\n/***/ }),\n\n/***/ \"./src/injected/textSelectorEngine.ts\":\n/*!********************************************!*\\\n !*** ./src/injected/textSelectorEngine.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 });\nexports.TextEngine = {\n create(root, targetElement, type) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n for (let child = targetElement.firstChild; child; child = child.nextSibling) {\n if (child.nodeType === 3 /* Node.TEXT_NODE */) {\n const text = child.nodeValue;\n if (!text)\n continue;\n if (text.match(/^\\s*[a-zA-Z0-9]+\\s*$/) && exports.TextEngine.query(root, text.trim()) === targetElement)\n return text.trim();\n if (exports.TextEngine.query(root, JSON.stringify(text)) === targetElement)\n return JSON.stringify(text);\n }\n }\n },\n query(root, selector) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n const matcher = createMatcher(selector);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const node = walker.currentNode;\n const element = node.parentElement;\n const text = node.nodeValue;\n if (element && text && matcher(text))\n return element;\n }\n },\n queryAll(root, selector) {\n const result = [];\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return result;\n const matcher = createMatcher(selector);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const node = walker.currentNode;\n const element = node.parentElement;\n const text = node.nodeValue;\n if (element && text && matcher(text))\n result.push(element);\n }\n return result;\n }\n};\nfunction createMatcher(selector) {\n if (selector[0] === '\"' && selector[selector.length - 1] === '\"') {\n const parsed = JSON.parse(selector);\n return text => text === parsed;\n }\n if (selector[0] === '/' && selector.lastIndexOf('/') > 0) {\n const lastSlash = selector.lastIndexOf('/');\n const re = new RegExp(selector.substring(1, lastSlash), selector.substring(lastSlash + 1));\n return text => re.test(text);\n }\n selector = selector.trim().toLowerCase();\n return text => text.trim().toLowerCase() === selector;\n}\n\n\n/***/ }),\n\n/***/ \"./src/injected/utils.ts\":\n/*!*******************************!*\\\n !*** ./src/injected/utils.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 });\nclass Utils {\n parentElementOrShadowHost(element) {\n if (element.parentElement)\n return element.parentElement;\n if (!element.parentNode)\n return;\n if (element.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE && element.parentNode.host)\n return element.parentNode.host;\n }\n deepElementFromPoint(document, x, y) {\n let container = document;\n let element;\n while (container) {\n const innerElement = container.elementFromPoint(x, y);\n if (!innerElement || element === innerElement)\n break;\n element = innerElement;\n container = element.shadowRoot;\n }\n return element;\n }\n}\nexports.Utils = Utils;\n\n\n/***/ }),\n\n/***/ \"./src/injected/xpathSelectorEngine.ts\":\n/*!*********************************************!*\\\n !*** ./src/injected/xpathSelectorEngine.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 });\nconst maxTextLength = 80;\nconst minMeaningfulSelectorLegth = 100;\nexports.XPathEngine = {\n create(root, targetElement, type) {\n const maybeDocument = root instanceof Document ? root : root.ownerDocument;\n if (!maybeDocument)\n return;\n const document = maybeDocument;\n const xpathCache = new Map();\n if (type === 'notext')\n return createNoText(root, targetElement);\n const tokens = [];\n function evaluateXPath(expression) {\n let nodes = xpathCache.get(expression);\n if (!nodes) {\n nodes = [];\n try {\n const result = document.evaluate(expression, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = result.iterateNext(); node; node = result.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n nodes.push(node);\n }\n }\n catch (e) {\n }\n xpathCache.set(expression, nodes);\n }\n return nodes;\n }\n function uniqueXPathSelector(prefix) {\n const path = tokens.slice();\n if (prefix)\n path.unshift(prefix);\n let selector = '//' + path.join('/');\n while (selector.includes('///'))\n selector = selector.replace('///', '//');\n if (selector.endsWith('/'))\n selector = selector.substring(0, selector.length - 1);\n const nodes = evaluateXPath(selector);\n if (nodes[nodes.length - 1] === targetElement)\n return selector;\n // If we are looking at a small set of elements with long selector, fall back to ordinal.\n if (nodes.length < 5 && selector.length > minMeaningfulSelectorLegth) {\n const index = nodes.indexOf(targetElement);\n if (index !== -1)\n return `(${selector})[${index + 1}]`;\n }\n return undefined;\n }\n function escapeAndCap(text) {\n text = text.substring(0, maxTextLength);\n // XPath 1.0 does not support quote escaping.\n // 1. If there are no single quotes - use them.\n if (text.indexOf(`'`) === -1)\n return `'${text}'`;\n // 2. If there are no double quotes - use them to enclose text.\n if (text.indexOf(`\"`) === -1)\n return `\"${text}\"`;\n // 3. Otherwise, use popular |concat| trick.\n const Q = `'`;\n return `concat(${text.split(Q).map(token => Q + token + Q).join(`, \"'\", `)})`;\n }\n const defaultAttributes = new Set(['title', 'aria-label', 'disabled', 'role']);\n const importantAttributes = new Map([\n ['form', ['action']],\n ['img', ['alt']],\n ['input', ['placeholder', 'type', 'name', 'value']],\n ]);\n let usedTextConditions = false;\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n const nodeName = element.nodeName.toLowerCase();\n const tag = nodeName === 'svg' ? '*' : nodeName;\n const tagConditions = [];\n if (nodeName === 'svg')\n tagConditions.push('local-name()=\"svg\"');\n const attrConditions = [];\n const importantAttrs = [...defaultAttributes, ...(importantAttributes.get(tag) || [])];\n for (const attr of importantAttrs) {\n const value = element.getAttribute(attr);\n if (value && value.length < maxTextLength)\n attrConditions.push(`normalize-space(@${attr})=${escapeAndCap(value)}`);\n else if (value)\n attrConditions.push(`starts-with(normalize-space(@${attr}), ${escapeAndCap(value)})`);\n }\n const text = document.evaluate('normalize-space(.)', element).stringValue;\n const textConditions = [];\n if (tag !== 'select' && text.length && !usedTextConditions) {\n if (text.length < maxTextLength)\n textConditions.push(`normalize-space(.)=${escapeAndCap(text)}`);\n else\n textConditions.push(`starts-with(normalize-space(.), ${escapeAndCap(text)})`);\n usedTextConditions = true;\n }\n // Always retain the last tag.\n const conditions = [...tagConditions, ...textConditions, ...attrConditions];\n const token = conditions.length ? `${tag}[${conditions.join(' and ')}]` : (tokens.length ? '' : tag);\n const selector = uniqueXPathSelector(token);\n if (selector)\n return selector;\n // Ordinal is the weakest signal.\n const parent = element.parentElement;\n let tagWithOrdinal = tag;\n if (parent) {\n const siblings = Array.from(parent.children);\n const sameTagSiblings = siblings.filter(sibling => (sibling).nodeName.toLowerCase() === nodeName);\n if (sameTagSiblings.length > 1)\n tagWithOrdinal += `[${1 + siblings.indexOf(element)}]`;\n }\n // Do not include text into this token, only tag / attributes.\n // Topmost node will get all the text.\n const nonTextConditions = [...tagConditions, ...attrConditions];\n const levelToken = nonTextConditions.length ? `${tagWithOrdinal}[${nonTextConditions.join(' and ')}]` : tokens.length ? '' : tagWithOrdinal;\n tokens.unshift(levelToken);\n }\n return uniqueXPathSelector();\n },\n query(root, selector) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n const it = document.evaluate(selector, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = it.iterateNext(); node; node = it.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n return node;\n }\n },\n queryAll(root, selector) {\n const result = [];\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return result;\n const it = document.evaluate(selector, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = it.iterateNext(); node; node = it.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n result.push(node);\n }\n return result;\n }\n};\nfunction createNoText(root, targetElement) {\n const steps = [];\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n if (element.getAttribute('id')) {\n steps.unshift(`//*[@id=\"${element.getAttribute('id')}\"]`);\n return steps.join('/');\n }\n const siblings = element.parentElement ? Array.from(element.parentElement.children) : [];\n const similarElements = siblings.filter(sibling => element.nodeName === sibling.nodeName);\n const index = similarElements.length === 1 ? 0 : similarElements.indexOf(element) + 1;\n steps.unshift(index ? `${element.nodeName}[${index}]` : element.nodeName);\n }\n return '/' + steps.join('/');\n}\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/injected.ts\");\n/******/ })\n/************************************************************************/\n/******/ ({\n\n/***/ \"./src/injected/cssSelectorEngine.ts\":\n/*!*******************************************!*\\\n !*** ./src/injected/cssSelectorEngine.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 });\nexports.CSSEngine = {\n name: 'css',\n create(root, targetElement) {\n const tokens = [];\n function uniqueCSSSelector(prefix) {\n const path = tokens.slice();\n if (prefix)\n path.unshift(prefix);\n const selector = path.join(' > ');\n const nodes = Array.from(root.querySelectorAll(selector));\n return nodes[0] === targetElement ? selector : undefined;\n }\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n const nodeName = element.nodeName.toLowerCase();\n // Element ID is the strongest signal, use it.\n let bestTokenForLevel = '';\n if (element.id) {\n const token = /^[a-zA-Z][a-zA-Z0-9\\-\\_]+$/.test(element.id) ? '#' + element.id : `[id=\"${element.id}\"]`;\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n bestTokenForLevel = token;\n }\n const parent = element.parentElement;\n // Combine class names until unique.\n const classes = Array.from(element.classList);\n for (let i = 0; i < classes.length; ++i) {\n const token = '.' + classes.slice(0, i + 1).join('.');\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n // Even if not unique, does this subset of classes uniquely identify node as a child?\n if (!bestTokenForLevel && parent) {\n const sameClassSiblings = parent.querySelectorAll(token);\n if (sameClassSiblings.length === 1)\n bestTokenForLevel = token;\n }\n }\n // Ordinal is the weakest signal.\n if (parent) {\n const siblings = Array.from(parent.children);\n const sameTagSiblings = siblings.filter(sibling => (sibling).nodeName.toLowerCase() === nodeName);\n const token = sameTagSiblings.length === 1 ? nodeName : `${nodeName}:nth-child(${1 + siblings.indexOf(element)})`;\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n if (!bestTokenForLevel)\n bestTokenForLevel = token;\n }\n else if (!bestTokenForLevel) {\n bestTokenForLevel = nodeName;\n }\n tokens.unshift(bestTokenForLevel);\n }\n return uniqueCSSSelector();\n },\n query(root, selector) {\n return root.querySelector(selector) || undefined;\n },\n queryAll(root, selector) {\n return Array.from(root.querySelectorAll(selector));\n }\n};\n\n\n/***/ }),\n\n/***/ \"./src/injected/injected.ts\":\n/*!**********************************!*\\\n !*** ./src/injected/injected.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 });\nconst utils_1 = __webpack_require__(/*! ./utils */ \"./src/injected/utils.ts\");\nconst cssSelectorEngine_1 = __webpack_require__(/*! ./cssSelectorEngine */ \"./src/injected/cssSelectorEngine.ts\");\nconst xpathSelectorEngine_1 = __webpack_require__(/*! ./xpathSelectorEngine */ \"./src/injected/xpathSelectorEngine.ts\");\nconst textSelectorEngine_1 = __webpack_require__(/*! ./textSelectorEngine */ \"./src/injected/textSelectorEngine.ts\");\nfunction createAttributeEngine(attribute) {\n const engine = {\n name: attribute,\n create(root, target) {\n const value = target.getAttribute(attribute);\n if (!value)\n return;\n if (root.querySelector(`[${attribute}=${value}]`) === target)\n return value;\n },\n query(root, selector) {\n return root.querySelector(`[${attribute}=${selector}]`) || undefined;\n },\n queryAll(root, selector) {\n return Array.from(root.querySelectorAll(`[${attribute}=${selector}]`));\n }\n };\n return engine;\n}\nclass Injected {\n constructor(customEngines) {\n const defaultEngines = [\n cssSelectorEngine_1.CSSEngine,\n xpathSelectorEngine_1.XPathEngine,\n textSelectorEngine_1.TextEngine,\n createAttributeEngine('id'),\n createAttributeEngine('data-testid'),\n createAttributeEngine('data-test-id'),\n createAttributeEngine('data-test'),\n ];\n this.utils = new utils_1.Utils();\n this.engines = new Map();\n for (const engine of [...defaultEngines, ...customEngines])\n this.engines.set(engine.name, engine);\n }\n querySelector(selector, root) {\n const parsed = this._parseSelector(selector);\n if (!root['querySelector'])\n throw new Error('Node is not queryable.');\n let element = root;\n for (const { engine, selector } of parsed) {\n const next = engine.query(element.shadowRoot || element, selector);\n if (!next)\n return;\n element = next;\n }\n return element;\n }\n querySelectorAll(selector, root) {\n const parsed = this._parseSelector(selector);\n if (!root['querySelectorAll'])\n throw new Error('Node is not queryable.');\n let set = new Set([root]);\n for (const { engine, selector } of parsed) {\n const newSet = new Set();\n for (const prev of set) {\n for (const next of engine.queryAll(prev.shadowRoot || prev, selector)) {\n if (newSet.has(next))\n continue;\n newSet.add(next);\n }\n }\n set = newSet;\n }\n return Array.from(set);\n }\n _parseSelector(selector) {\n let index = 0;\n let quote;\n let start = 0;\n const result = [];\n const append = () => {\n const part = selector.substring(start, index).trim();\n const eqIndex = part.indexOf('=');\n let name;\n let body;\n if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-]+$/)) {\n name = part.substring(0, eqIndex).trim();\n body = part.substring(eqIndex + 1);\n }\n else if (part.startsWith('\"')) {\n name = 'text';\n body = part;\n }\n else if (/^\\(*\\/\\//.test(part)) {\n // If selector starts with '//' or '//' prefixed with multiple opening\n // parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817\n name = 'xpath';\n body = part;\n }\n else {\n name = 'css';\n body = part;\n }\n const engine = this.engines.get(name.toLowerCase());\n if (!engine)\n throw new Error(`Unknown engine ${name} while parsing selector ${selector}`);\n result.push({ engine, selector: body });\n };\n while (index < selector.length) {\n const c = selector[index];\n if (c === '\\\\' && index + 1 < selector.length) {\n index += 2;\n }\n else if (c === quote) {\n quote = undefined;\n index++;\n }\n else if (!quote && c === '>' && selector[index + 1] === '>') {\n append();\n index += 2;\n start = index;\n }\n else {\n index++;\n }\n }\n append();\n return result;\n }\n isVisible(element) {\n if (!element.ownerDocument || !element.ownerDocument.defaultView)\n return true;\n const style = element.ownerDocument.defaultView.getComputedStyle(element);\n if (!style || style.visibility === 'hidden')\n return false;\n const rect = element.getBoundingClientRect();\n return !!(rect.top || rect.bottom || rect.width || rect.height);\n }\n _pollMutation(selector, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n return Promise.resolve(success);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const observer = new MutationObserver(() => {\n if (timedOut) {\n observer.disconnect();\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success) {\n observer.disconnect();\n fulfill(success);\n }\n });\n observer.observe(document, {\n childList: true,\n subtree: true,\n attributes: true\n });\n return result;\n }\n _pollRaf(selector, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const onRaf = () => {\n if (timedOut) {\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n fulfill(success);\n else\n requestAnimationFrame(onRaf);\n };\n onRaf();\n return result;\n }\n _pollInterval(selector, pollInterval, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const onTimeout = () => {\n if (timedOut) {\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n fulfill(success);\n else\n setTimeout(onTimeout, pollInterval);\n };\n onTimeout();\n return result;\n }\n poll(polling, selector, timeout, predicate) {\n if (polling === 'raf')\n return this._pollRaf(selector, predicate, timeout);\n if (polling === 'mutation')\n return this._pollMutation(selector, predicate, timeout);\n return this._pollInterval(selector, polling, predicate, timeout);\n }\n getElementBorderWidth(node) {\n if (node.nodeType !== Node.ELEMENT_NODE || !node.ownerDocument || !node.ownerDocument.defaultView)\n return { left: 0, top: 0 };\n const style = node.ownerDocument.defaultView.getComputedStyle(node);\n return { left: parseInt(style.borderLeftWidth || '', 10), top: parseInt(style.borderTopWidth || '', 10) };\n }\n selectOptions(node, optionsToSelect) {\n if (node.nodeName.toLowerCase() !== 'select')\n throw new Error('Element is not a <select> element.');\n const element = node;\n const options = Array.from(element.options);\n element.value = undefined;\n for (let index = 0; index < options.length; index++) {\n const option = options[index];\n option.selected = optionsToSelect.some(optionToSelect => {\n if (optionToSelect instanceof Node)\n return option === optionToSelect;\n let matches = true;\n if (optionToSelect.value !== undefined)\n matches = matches && optionToSelect.value === option.value;\n if (optionToSelect.label !== undefined)\n matches = matches && optionToSelect.label === option.label;\n if (optionToSelect.index !== undefined)\n matches = matches && optionToSelect.index === index;\n return matches;\n });\n if (option.selected && !element.multiple)\n break;\n }\n element.dispatchEvent(new Event('input', { 'bubbles': true }));\n element.dispatchEvent(new Event('change', { 'bubbles': true }));\n return options.filter(option => option.selected).map(option => option.value);\n }\n fill(node, value) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n return 'Node is not of type HTMLElement';\n const element = node;\n if (!element.isConnected)\n return 'Element is not attached to the DOM';\n if (!element.ownerDocument || !element.ownerDocument.defaultView)\n return 'Element does not belong to a window';\n const style = element.ownerDocument.defaultView.getComputedStyle(element);\n if (!style || style.visibility === 'hidden')\n return 'Element is hidden';\n if (!element.offsetParent && element.tagName !== 'BODY')\n return 'Element is not visible';\n if (element.nodeName.toLowerCase() === 'input') {\n const input = element;\n const type = input.getAttribute('type') || '';\n const kTextInputTypes = new Set(['', 'email', 'number', 'password', 'search', 'tel', 'text', 'url']);\n if (!kTextInputTypes.has(type.toLowerCase()))\n return 'Cannot fill input of type \"' + type + '\".';\n if (type.toLowerCase() === 'number') {\n value = value.trim();\n if (!value || isNaN(Number(value)))\n return 'Cannot type text into input[type=number].';\n }\n if (input.disabled)\n return 'Cannot fill a disabled input.';\n if (input.readOnly)\n return 'Cannot fill a readonly input.';\n input.select();\n input.focus();\n }\n else if (element.nodeName.toLowerCase() === 'textarea') {\n const textarea = element;\n if (textarea.disabled)\n return 'Cannot fill a disabled textarea.';\n if (textarea.readOnly)\n return 'Cannot fill a readonly textarea.';\n textarea.selectionStart = 0;\n textarea.selectionEnd = textarea.value.length;\n textarea.focus();\n }\n else if (element.isContentEditable) {\n const range = element.ownerDocument.createRange();\n range.selectNodeContents(element);\n const selection = element.ownerDocument.defaultView.getSelection();\n if (!selection)\n return 'Element belongs to invisible iframe.';\n selection.removeAllRanges();\n selection.addRange(range);\n element.focus();\n }\n else {\n return 'Element is not an <input>, <textarea> or [contenteditable] element.';\n }\n return false;\n }\n isCheckboxChecked(node) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n throw new Error('Not a checkbox or radio button');\n let element = node;\n if (element.getAttribute('role') === 'checkbox')\n return element.getAttribute('aria-checked') === 'true';\n if (element.nodeName === 'LABEL') {\n const forId = element.getAttribute('for');\n if (forId && element.ownerDocument)\n element = element.ownerDocument.querySelector(`input[id=\"${forId}\"]`) || undefined;\n else\n element = element.querySelector('input[type=checkbox],input[type=radio]') || undefined;\n }\n if (element && element.nodeName === 'INPUT') {\n const type = element.getAttribute('type');\n if (type && (type.toLowerCase() === 'checkbox' || type.toLowerCase() === 'radio'))\n return element.checked;\n }\n throw new Error('Not a checkbox');\n }\n waitForStablePosition(node, timeout) {\n if (!node.isConnected)\n throw new Error('Element is not attached to the DOM');\n const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;\n if (!element)\n throw new Error('Element is not attached to the DOM');\n let lastRect;\n let counter = 0;\n return this.poll('raf', undefined, timeout, () => {\n // First raf happens in the same animation frame as evaluation, so it does not produce\n // any client rect difference compared to synchronous call. We skip the synchronous call\n // and only force layout during actual rafs as a small optimisation.\n if (++counter === 1)\n return false;\n const clientRect = element.getBoundingClientRect();\n const rect = { x: clientRect.top, y: clientRect.left, width: clientRect.width, height: clientRect.height };\n const isStable = lastRect && rect.x === lastRect.x && rect.y === lastRect.y && rect.width === lastRect.width && rect.height === lastRect.height;\n lastRect = rect;\n return isStable;\n });\n }\n waitForHitTargetAt(node, timeout, point) {\n const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;\n if (!element)\n throw new Error('Element is not attached to the DOM');\n return this.poll('raf', undefined, timeout, () => {\n let hitElement = this.utils.deepElementFromPoint(document, point.x, point.y);\n while (hitElement && hitElement !== element)\n hitElement = this.utils.parentElementOrShadowHost(hitElement);\n return hitElement === element;\n });\n }\n}\nexports.default = Injected;\n\n\n/***/ }),\n\n/***/ \"./src/injected/textSelectorEngine.ts\":\n/*!********************************************!*\\\n !*** ./src/injected/textSelectorEngine.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 });\nexports.TextEngine = {\n name: 'text',\n create(root, targetElement, type) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n for (let child = targetElement.firstChild; child; child = child.nextSibling) {\n if (child.nodeType === 3 /* Node.TEXT_NODE */) {\n const text = child.nodeValue;\n if (!text)\n continue;\n if (text.match(/^\\s*[a-zA-Z0-9]+\\s*$/) && exports.TextEngine.query(root, text.trim()) === targetElement)\n return text.trim();\n if (exports.TextEngine.query(root, JSON.stringify(text)) === targetElement)\n return JSON.stringify(text);\n }\n }\n },\n query(root, selector) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n const matcher = createMatcher(selector);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const node = walker.currentNode;\n const element = node.parentElement;\n const text = node.nodeValue;\n if (element && text && matcher(text))\n return element;\n }\n },\n queryAll(root, selector) {\n const result = [];\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return result;\n const matcher = createMatcher(selector);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const node = walker.currentNode;\n const element = node.parentElement;\n const text = node.nodeValue;\n if (element && text && matcher(text))\n result.push(element);\n }\n return result;\n }\n};\nfunction createMatcher(selector) {\n if (selector[0] === '\"' && selector[selector.length - 1] === '\"') {\n const parsed = JSON.parse(selector);\n return text => text === parsed;\n }\n if (selector[0] === '/' && selector.lastIndexOf('/') > 0) {\n const lastSlash = selector.lastIndexOf('/');\n const re = new RegExp(selector.substring(1, lastSlash), selector.substring(lastSlash + 1));\n return text => re.test(text);\n }\n selector = selector.trim().toLowerCase();\n return text => text.trim().toLowerCase() === selector;\n}\n\n\n/***/ }),\n\n/***/ \"./src/injected/utils.ts\":\n/*!*******************************!*\\\n !*** ./src/injected/utils.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 });\nclass Utils {\n parentElementOrShadowHost(element) {\n if (element.parentElement)\n return element.parentElement;\n if (!element.parentNode)\n return;\n if (element.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE && element.parentNode.host)\n return element.parentNode.host;\n }\n deepElementFromPoint(document, x, y) {\n let container = document;\n let element;\n while (container) {\n const innerElement = container.elementFromPoint(x, y);\n if (!innerElement || element === innerElement)\n break;\n element = innerElement;\n container = element.shadowRoot;\n }\n return element;\n }\n}\nexports.Utils = Utils;\n\n\n/***/ }),\n\n/***/ \"./src/injected/xpathSelectorEngine.ts\":\n/*!*********************************************!*\\\n !*** ./src/injected/xpathSelectorEngine.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 });\nconst maxTextLength = 80;\nconst minMeaningfulSelectorLegth = 100;\nexports.XPathEngine = {\n name: 'xpath',\n create(root, targetElement, type) {\n const maybeDocument = root instanceof Document ? root : root.ownerDocument;\n if (!maybeDocument)\n return;\n const document = maybeDocument;\n const xpathCache = new Map();\n if (type === 'notext')\n return createNoText(root, targetElement);\n const tokens = [];\n function evaluateXPath(expression) {\n let nodes = xpathCache.get(expression);\n if (!nodes) {\n nodes = [];\n try {\n const result = document.evaluate(expression, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = result.iterateNext(); node; node = result.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n nodes.push(node);\n }\n }\n catch (e) {\n }\n xpathCache.set(expression, nodes);\n }\n return nodes;\n }\n function uniqueXPathSelector(prefix) {\n const path = tokens.slice();\n if (prefix)\n path.unshift(prefix);\n let selector = '//' + path.join('/');\n while (selector.includes('///'))\n selector = selector.replace('///', '//');\n if (selector.endsWith('/'))\n selector = selector.substring(0, selector.length - 1);\n const nodes = evaluateXPath(selector);\n if (nodes[nodes.length - 1] === targetElement)\n return selector;\n // If we are looking at a small set of elements with long selector, fall back to ordinal.\n if (nodes.length < 5 && selector.length > minMeaningfulSelectorLegth) {\n const index = nodes.indexOf(targetElement);\n if (index !== -1)\n return `(${selector})[${index + 1}]`;\n }\n return undefined;\n }\n function escapeAndCap(text) {\n text = text.substring(0, maxTextLength);\n // XPath 1.0 does not support quote escaping.\n // 1. If there are no single quotes - use them.\n if (text.indexOf(`'`) === -1)\n return `'${text}'`;\n // 2. If there are no double quotes - use them to enclose text.\n if (text.indexOf(`\"`) === -1)\n return `\"${text}\"`;\n // 3. Otherwise, use popular |concat| trick.\n const Q = `'`;\n return `concat(${text.split(Q).map(token => Q + token + Q).join(`, \"'\", `)})`;\n }\n const defaultAttributes = new Set(['title', 'aria-label', 'disabled', 'role']);\n const importantAttributes = new Map([\n ['form', ['action']],\n ['img', ['alt']],\n ['input', ['placeholder', 'type', 'name', 'value']],\n ]);\n let usedTextConditions = false;\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n const nodeName = element.nodeName.toLowerCase();\n const tag = nodeName === 'svg' ? '*' : nodeName;\n const tagConditions = [];\n if (nodeName === 'svg')\n tagConditions.push('local-name()=\"svg\"');\n const attrConditions = [];\n const importantAttrs = [...defaultAttributes, ...(importantAttributes.get(tag) || [])];\n for (const attr of importantAttrs) {\n const value = element.getAttribute(attr);\n if (value && value.length < maxTextLength)\n attrConditions.push(`normalize-space(@${attr})=${escapeAndCap(value)}`);\n else if (value)\n attrConditions.push(`starts-with(normalize-space(@${attr}), ${escapeAndCap(value)})`);\n }\n const text = document.evaluate('normalize-space(.)', element).stringValue;\n const textConditions = [];\n if (tag !== 'select' && text.length && !usedTextConditions) {\n if (text.length < maxTextLength)\n textConditions.push(`normalize-space(.)=${escapeAndCap(text)}`);\n else\n textConditions.push(`starts-with(normalize-space(.), ${escapeAndCap(text)})`);\n usedTextConditions = true;\n }\n // Always retain the last tag.\n const conditions = [...tagConditions, ...textConditions, ...attrConditions];\n const token = conditions.length ? `${tag}[${conditions.join(' and ')}]` : (tokens.length ? '' : tag);\n const selector = uniqueXPathSelector(token);\n if (selector)\n return selector;\n // Ordinal is the weakest signal.\n const parent = element.parentElement;\n let tagWithOrdinal = tag;\n if (parent) {\n const siblings = Array.from(parent.children);\n const sameTagSiblings = siblings.filter(sibling => (sibling).nodeName.toLowerCase() === nodeName);\n if (sameTagSiblings.length > 1)\n tagWithOrdinal += `[${1 + siblings.indexOf(element)}]`;\n }\n // Do not include text into this token, only tag / attributes.\n // Topmost node will get all the text.\n const nonTextConditions = [...tagConditions, ...attrConditions];\n const levelToken = nonTextConditions.length ? `${tagWithOrdinal}[${nonTextConditions.join(' and ')}]` : tokens.length ? '' : tagWithOrdinal;\n tokens.unshift(levelToken);\n }\n return uniqueXPathSelector();\n },\n query(root, selector) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n const it = document.evaluate(selector, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = it.iterateNext(); node; node = it.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n return node;\n }\n },\n queryAll(root, selector) {\n const result = [];\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return result;\n const it = document.evaluate(selector, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = it.iterateNext(); node; node = it.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n result.push(node);\n }\n return result;\n }\n};\nfunction createNoText(root, targetElement) {\n const steps = [];\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n if (element.getAttribute('id')) {\n steps.unshift(`//*[@id=\"${element.getAttribute('id')}\"]`);\n return steps.join('/');\n }\n const siblings = element.parentElement ? Array.from(element.parentElement.children) : [];\n const similarElements = siblings.filter(sibling => element.nodeName === sibling.nodeName);\n const index = similarElements.length === 1 ? 0 : similarElements.indexOf(element) + 1;\n steps.unshift(index ? `${element.nodeName}[${index}]` : element.nodeName);\n }\n return '/' + steps.join('/');\n}\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/injected.ts\");\n/******/ })\n/************************************************************************/\n/******/ ({\n\n/***/ \"./src/injected/cssSelectorEngine.ts\":\n/*!*******************************************!*\\\n !*** ./src/injected/cssSelectorEngine.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 });\nexports.CSSEngine = {\n create(root, targetElement) {\n const tokens = [];\n function uniqueCSSSelector(prefix) {\n const path = tokens.slice();\n if (prefix)\n path.unshift(prefix);\n const selector = path.join(' > ');\n const nodes = Array.from(root.querySelectorAll(selector));\n return nodes[0] === targetElement ? selector : undefined;\n }\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n const nodeName = element.nodeName.toLowerCase();\n // Element ID is the strongest signal, use it.\n let bestTokenForLevel = '';\n if (element.id) {\n const token = /^[a-zA-Z][a-zA-Z0-9\\-\\_]+$/.test(element.id) ? '#' + element.id : `[id=\"${element.id}\"]`;\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n bestTokenForLevel = token;\n }\n const parent = element.parentElement;\n // Combine class names until unique.\n const classes = Array.from(element.classList);\n for (let i = 0; i < classes.length; ++i) {\n const token = '.' + classes.slice(0, i + 1).join('.');\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n // Even if not unique, does this subset of classes uniquely identify node as a child?\n if (!bestTokenForLevel && parent) {\n const sameClassSiblings = parent.querySelectorAll(token);\n if (sameClassSiblings.length === 1)\n bestTokenForLevel = token;\n }\n }\n // Ordinal is the weakest signal.\n if (parent) {\n const siblings = Array.from(parent.children);\n const sameTagSiblings = siblings.filter(sibling => (sibling).nodeName.toLowerCase() === nodeName);\n const token = sameTagSiblings.length === 1 ? nodeName : `${nodeName}:nth-child(${1 + siblings.indexOf(element)})`;\n const selector = uniqueCSSSelector(token);\n if (selector)\n return selector;\n if (!bestTokenForLevel)\n bestTokenForLevel = token;\n }\n else if (!bestTokenForLevel) {\n bestTokenForLevel = nodeName;\n }\n tokens.unshift(bestTokenForLevel);\n }\n return uniqueCSSSelector();\n },\n query(root, selector) {\n return root.querySelector(selector) || undefined;\n },\n queryAll(root, selector) {\n return Array.from(root.querySelectorAll(selector));\n }\n};\n\n\n/***/ }),\n\n/***/ \"./src/injected/injected.ts\":\n/*!**********************************!*\\\n !*** ./src/injected/injected.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 });\nconst utils_1 = __webpack_require__(/*! ./utils */ \"./src/injected/utils.ts\");\nconst cssSelectorEngine_1 = __webpack_require__(/*! ./cssSelectorEngine */ \"./src/injected/cssSelectorEngine.ts\");\nconst xpathSelectorEngine_1 = __webpack_require__(/*! ./xpathSelectorEngine */ \"./src/injected/xpathSelectorEngine.ts\");\nconst textSelectorEngine_1 = __webpack_require__(/*! ./textSelectorEngine */ \"./src/injected/textSelectorEngine.ts\");\nfunction createAttributeEngine(attribute) {\n const engine = {\n create(root, target) {\n const value = target.getAttribute(attribute);\n if (!value)\n return;\n if (root.querySelector(`[${attribute}=${value}]`) === target)\n return value;\n },\n query(root, selector) {\n return root.querySelector(`[${attribute}=${selector}]`) || undefined;\n },\n queryAll(root, selector) {\n return Array.from(root.querySelectorAll(`[${attribute}=${selector}]`));\n }\n };\n return engine;\n}\nclass Injected {\n constructor(customEngines) {\n this.utils = new utils_1.Utils();\n this.engines = new Map();\n // Note: keep predefined names in sync with Selectors class.\n this.engines.set('css', cssSelectorEngine_1.CSSEngine);\n this.engines.set('xpath', xpathSelectorEngine_1.XPathEngine);\n this.engines.set('text', textSelectorEngine_1.TextEngine);\n this.engines.set('id', createAttributeEngine('id'));\n this.engines.set('data-testid', createAttributeEngine('data-testid'));\n this.engines.set('data-test-id', createAttributeEngine('data-test-id'));\n this.engines.set('data-test', createAttributeEngine('data-test'));\n for (const { name, engine } of customEngines)\n this.engines.set(name, engine);\n }\n querySelector(selector, root) {\n const parsed = this._parseSelector(selector);\n if (!root['querySelector'])\n throw new Error('Node is not queryable.');\n let element = root;\n for (const { engine, selector } of parsed) {\n const next = engine.query(element.shadowRoot || element, selector);\n if (!next)\n return;\n element = next;\n }\n return element;\n }\n querySelectorAll(selector, root) {\n const parsed = this._parseSelector(selector);\n if (!root['querySelectorAll'])\n throw new Error('Node is not queryable.');\n let set = new Set([root]);\n for (const { engine, selector } of parsed) {\n const newSet = new Set();\n for (const prev of set) {\n for (const next of engine.queryAll(prev.shadowRoot || prev, selector)) {\n if (newSet.has(next))\n continue;\n newSet.add(next);\n }\n }\n set = newSet;\n }\n return Array.from(set);\n }\n _parseSelector(selector) {\n let index = 0;\n let quote;\n let start = 0;\n const result = [];\n const append = () => {\n const part = selector.substring(start, index).trim();\n const eqIndex = part.indexOf('=');\n let name;\n let body;\n if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-]+$/)) {\n name = part.substring(0, eqIndex).trim();\n body = part.substring(eqIndex + 1);\n }\n else if (part.startsWith('\"')) {\n name = 'text';\n body = part;\n }\n else if (/^\\(*\\/\\//.test(part)) {\n // If selector starts with '//' or '//' prefixed with multiple opening\n // parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817\n name = 'xpath';\n body = part;\n }\n else {\n name = 'css';\n body = part;\n }\n const engine = this.engines.get(name.toLowerCase());\n if (!engine)\n throw new Error(`Unknown engine ${name} while parsing selector ${selector}`);\n result.push({ engine, selector: body });\n };\n while (index < selector.length) {\n const c = selector[index];\n if (c === '\\\\' && index + 1 < selector.length) {\n index += 2;\n }\n else if (c === quote) {\n quote = undefined;\n index++;\n }\n else if (!quote && c === '>' && selector[index + 1] === '>') {\n append();\n index += 2;\n start = index;\n }\n else {\n index++;\n }\n }\n append();\n return result;\n }\n isVisible(element) {\n if (!element.ownerDocument || !element.ownerDocument.defaultView)\n return true;\n const style = element.ownerDocument.defaultView.getComputedStyle(element);\n if (!style || style.visibility === 'hidden')\n return false;\n const rect = element.getBoundingClientRect();\n return !!(rect.top || rect.bottom || rect.width || rect.height);\n }\n _pollMutation(selector, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n return Promise.resolve(success);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const observer = new MutationObserver(() => {\n if (timedOut) {\n observer.disconnect();\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success) {\n observer.disconnect();\n fulfill(success);\n }\n });\n observer.observe(document, {\n childList: true,\n subtree: true,\n attributes: true\n });\n return result;\n }\n _pollRaf(selector, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const onRaf = () => {\n if (timedOut) {\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n fulfill(success);\n else\n requestAnimationFrame(onRaf);\n };\n onRaf();\n return result;\n }\n _pollInterval(selector, pollInterval, predicate, timeout) {\n let timedOut = false;\n if (timeout)\n setTimeout(() => timedOut = true, timeout);\n let fulfill;\n const result = new Promise(x => fulfill = x);\n const onTimeout = () => {\n if (timedOut) {\n fulfill();\n return;\n }\n const element = selector === undefined ? undefined : this.querySelector(selector, document);\n const success = predicate(element);\n if (success)\n fulfill(success);\n else\n setTimeout(onTimeout, pollInterval);\n };\n onTimeout();\n return result;\n }\n poll(polling, selector, timeout, predicate) {\n if (polling === 'raf')\n return this._pollRaf(selector, predicate, timeout);\n if (polling === 'mutation')\n return this._pollMutation(selector, predicate, timeout);\n return this._pollInterval(selector, polling, predicate, timeout);\n }\n getElementBorderWidth(node) {\n if (node.nodeType !== Node.ELEMENT_NODE || !node.ownerDocument || !node.ownerDocument.defaultView)\n return { left: 0, top: 0 };\n const style = node.ownerDocument.defaultView.getComputedStyle(node);\n return { left: parseInt(style.borderLeftWidth || '', 10), top: parseInt(style.borderTopWidth || '', 10) };\n }\n selectOptions(node, optionsToSelect) {\n if (node.nodeName.toLowerCase() !== 'select')\n throw new Error('Element is not a <select> element.');\n const element = node;\n const options = Array.from(element.options);\n element.value = undefined;\n for (let index = 0; index < options.length; index++) {\n const option = options[index];\n option.selected = optionsToSelect.some(optionToSelect => {\n if (optionToSelect instanceof Node)\n return option === optionToSelect;\n let matches = true;\n if (optionToSelect.value !== undefined)\n matches = matches && optionToSelect.value === option.value;\n if (optionToSelect.label !== undefined)\n matches = matches && optionToSelect.label === option.label;\n if (optionToSelect.index !== undefined)\n matches = matches && optionToSelect.index === index;\n return matches;\n });\n if (option.selected && !element.multiple)\n break;\n }\n element.dispatchEvent(new Event('input', { 'bubbles': true }));\n element.dispatchEvent(new Event('change', { 'bubbles': true }));\n return options.filter(option => option.selected).map(option => option.value);\n }\n fill(node, value) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n return 'Node is not of type HTMLElement';\n const element = node;\n if (!element.isConnected)\n return 'Element is not attached to the DOM';\n if (!element.ownerDocument || !element.ownerDocument.defaultView)\n return 'Element does not belong to a window';\n const style = element.ownerDocument.defaultView.getComputedStyle(element);\n if (!style || style.visibility === 'hidden')\n return 'Element is hidden';\n if (!element.offsetParent && element.tagName !== 'BODY')\n return 'Element is not visible';\n if (element.nodeName.toLowerCase() === 'input') {\n const input = element;\n const type = input.getAttribute('type') || '';\n const kTextInputTypes = new Set(['', 'email', 'number', 'password', 'search', 'tel', 'text', 'url']);\n if (!kTextInputTypes.has(type.toLowerCase()))\n return 'Cannot fill input of type \"' + type + '\".';\n if (type.toLowerCase() === 'number') {\n value = value.trim();\n if (!value || isNaN(Number(value)))\n return 'Cannot type text into input[type=number].';\n }\n if (input.disabled)\n return 'Cannot fill a disabled input.';\n if (input.readOnly)\n return 'Cannot fill a readonly input.';\n input.select();\n input.focus();\n }\n else if (element.nodeName.toLowerCase() === 'textarea') {\n const textarea = element;\n if (textarea.disabled)\n return 'Cannot fill a disabled textarea.';\n if (textarea.readOnly)\n return 'Cannot fill a readonly textarea.';\n textarea.selectionStart = 0;\n textarea.selectionEnd = textarea.value.length;\n textarea.focus();\n }\n else if (element.isContentEditable) {\n const range = element.ownerDocument.createRange();\n range.selectNodeContents(element);\n const selection = element.ownerDocument.defaultView.getSelection();\n if (!selection)\n return 'Element belongs to invisible iframe.';\n selection.removeAllRanges();\n selection.addRange(range);\n element.focus();\n }\n else {\n return 'Element is not an <input>, <textarea> or [contenteditable] element.';\n }\n return false;\n }\n isCheckboxChecked(node) {\n if (node.nodeType !== Node.ELEMENT_NODE)\n throw new Error('Not a checkbox or radio button');\n let element = node;\n if (element.getAttribute('role') === 'checkbox')\n return element.getAttribute('aria-checked') === 'true';\n if (element.nodeName === 'LABEL') {\n const forId = element.getAttribute('for');\n if (forId && element.ownerDocument)\n element = element.ownerDocument.querySelector(`input[id=\"${forId}\"]`) || undefined;\n else\n element = element.querySelector('input[type=checkbox],input[type=radio]') || undefined;\n }\n if (element && element.nodeName === 'INPUT') {\n const type = element.getAttribute('type');\n if (type && (type.toLowerCase() === 'checkbox' || type.toLowerCase() === 'radio'))\n return element.checked;\n }\n throw new Error('Not a checkbox');\n }\n waitForStablePosition(node, timeout) {\n if (!node.isConnected)\n throw new Error('Element is not attached to the DOM');\n const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;\n if (!element)\n throw new Error('Element is not attached to the DOM');\n let lastRect;\n let counter = 0;\n return this.poll('raf', undefined, timeout, () => {\n // First raf happens in the same animation frame as evaluation, so it does not produce\n // any client rect difference compared to synchronous call. We skip the synchronous call\n // and only force layout during actual rafs as a small optimisation.\n if (++counter === 1)\n return false;\n const clientRect = element.getBoundingClientRect();\n const rect = { x: clientRect.top, y: clientRect.left, width: clientRect.width, height: clientRect.height };\n const isStable = lastRect && rect.x === lastRect.x && rect.y === lastRect.y && rect.width === lastRect.width && rect.height === lastRect.height;\n lastRect = rect;\n return isStable;\n });\n }\n waitForHitTargetAt(node, timeout, point) {\n const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;\n if (!element)\n throw new Error('Element is not attached to the DOM');\n return this.poll('raf', undefined, timeout, () => {\n let hitElement = this.utils.deepElementFromPoint(document, point.x, point.y);\n while (hitElement && hitElement !== element)\n hitElement = this.utils.parentElementOrShadowHost(hitElement);\n return hitElement === element;\n });\n }\n}\nexports.default = Injected;\n\n\n/***/ }),\n\n/***/ \"./src/injected/textSelectorEngine.ts\":\n/*!********************************************!*\\\n !*** ./src/injected/textSelectorEngine.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 });\nexports.TextEngine = {\n create(root, targetElement, type) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n for (let child = targetElement.firstChild; child; child = child.nextSibling) {\n if (child.nodeType === 3 /* Node.TEXT_NODE */) {\n const text = child.nodeValue;\n if (!text)\n continue;\n if (text.match(/^\\s*[a-zA-Z0-9]+\\s*$/) && exports.TextEngine.query(root, text.trim()) === targetElement)\n return text.trim();\n if (exports.TextEngine.query(root, JSON.stringify(text)) === targetElement)\n return JSON.stringify(text);\n }\n }\n },\n query(root, selector) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n const matcher = createMatcher(selector);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const node = walker.currentNode;\n const element = node.parentElement;\n const text = node.nodeValue;\n if (element && text && matcher(text))\n return element;\n }\n },\n queryAll(root, selector) {\n const result = [];\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return result;\n const matcher = createMatcher(selector);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const node = walker.currentNode;\n const element = node.parentElement;\n const text = node.nodeValue;\n if (element && text && matcher(text))\n result.push(element);\n }\n return result;\n }\n};\nfunction createMatcher(selector) {\n if (selector[0] === '\"' && selector[selector.length - 1] === '\"') {\n const parsed = JSON.parse(selector);\n return text => text === parsed;\n }\n if (selector[0] === '/' && selector.lastIndexOf('/') > 0) {\n const lastSlash = selector.lastIndexOf('/');\n const re = new RegExp(selector.substring(1, lastSlash), selector.substring(lastSlash + 1));\n return text => re.test(text);\n }\n selector = selector.trim().toLowerCase();\n return text => text.trim().toLowerCase() === selector;\n}\n\n\n/***/ }),\n\n/***/ \"./src/injected/utils.ts\":\n/*!*******************************!*\\\n !*** ./src/injected/utils.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 });\nclass Utils {\n parentElementOrShadowHost(element) {\n if (element.parentElement)\n return element.parentElement;\n if (!element.parentNode)\n return;\n if (element.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE && element.parentNode.host)\n return element.parentNode.host;\n }\n deepElementFromPoint(document, x, y) {\n let container = document;\n let element;\n while (container) {\n const innerElement = container.elementFromPoint(x, y);\n if (!innerElement || element === innerElement)\n break;\n element = innerElement;\n container = element.shadowRoot;\n }\n return element;\n }\n}\nexports.Utils = Utils;\n\n\n/***/ }),\n\n/***/ \"./src/injected/xpathSelectorEngine.ts\":\n/*!*********************************************!*\\\n !*** ./src/injected/xpathSelectorEngine.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 });\nconst maxTextLength = 80;\nconst minMeaningfulSelectorLegth = 100;\nexports.XPathEngine = {\n create(root, targetElement, type) {\n const maybeDocument = root instanceof Document ? root : root.ownerDocument;\n if (!maybeDocument)\n return;\n const document = maybeDocument;\n const xpathCache = new Map();\n if (type === 'notext')\n return createNoText(root, targetElement);\n const tokens = [];\n function evaluateXPath(expression) {\n let nodes = xpathCache.get(expression);\n if (!nodes) {\n nodes = [];\n try {\n const result = document.evaluate(expression, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = result.iterateNext(); node; node = result.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n nodes.push(node);\n }\n }\n catch (e) {\n }\n xpathCache.set(expression, nodes);\n }\n return nodes;\n }\n function uniqueXPathSelector(prefix) {\n const path = tokens.slice();\n if (prefix)\n path.unshift(prefix);\n let selector = '//' + path.join('/');\n while (selector.includes('///'))\n selector = selector.replace('///', '//');\n if (selector.endsWith('/'))\n selector = selector.substring(0, selector.length - 1);\n const nodes = evaluateXPath(selector);\n if (nodes[nodes.length - 1] === targetElement)\n return selector;\n // If we are looking at a small set of elements with long selector, fall back to ordinal.\n if (nodes.length < 5 && selector.length > minMeaningfulSelectorLegth) {\n const index = nodes.indexOf(targetElement);\n if (index !== -1)\n return `(${selector})[${index + 1}]`;\n }\n return undefined;\n }\n function escapeAndCap(text) {\n text = text.substring(0, maxTextLength);\n // XPath 1.0 does not support quote escaping.\n // 1. If there are no single quotes - use them.\n if (text.indexOf(`'`) === -1)\n return `'${text}'`;\n // 2. If there are no double quotes - use them to enclose text.\n if (text.indexOf(`\"`) === -1)\n return `\"${text}\"`;\n // 3. Otherwise, use popular |concat| trick.\n const Q = `'`;\n return `concat(${text.split(Q).map(token => Q + token + Q).join(`, \"'\", `)})`;\n }\n const defaultAttributes = new Set(['title', 'aria-label', 'disabled', 'role']);\n const importantAttributes = new Map([\n ['form', ['action']],\n ['img', ['alt']],\n ['input', ['placeholder', 'type', 'name', 'value']],\n ]);\n let usedTextConditions = false;\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n const nodeName = element.nodeName.toLowerCase();\n const tag = nodeName === 'svg' ? '*' : nodeName;\n const tagConditions = [];\n if (nodeName === 'svg')\n tagConditions.push('local-name()=\"svg\"');\n const attrConditions = [];\n const importantAttrs = [...defaultAttributes, ...(importantAttributes.get(tag) || [])];\n for (const attr of importantAttrs) {\n const value = element.getAttribute(attr);\n if (value && value.length < maxTextLength)\n attrConditions.push(`normalize-space(@${attr})=${escapeAndCap(value)}`);\n else if (value)\n attrConditions.push(`starts-with(normalize-space(@${attr}), ${escapeAndCap(value)})`);\n }\n const text = document.evaluate('normalize-space(.)', element).stringValue;\n const textConditions = [];\n if (tag !== 'select' && text.length && !usedTextConditions) {\n if (text.length < maxTextLength)\n textConditions.push(`normalize-space(.)=${escapeAndCap(text)}`);\n else\n textConditions.push(`starts-with(normalize-space(.), ${escapeAndCap(text)})`);\n usedTextConditions = true;\n }\n // Always retain the last tag.\n const conditions = [...tagConditions, ...textConditions, ...attrConditions];\n const token = conditions.length ? `${tag}[${conditions.join(' and ')}]` : (tokens.length ? '' : tag);\n const selector = uniqueXPathSelector(token);\n if (selector)\n return selector;\n // Ordinal is the weakest signal.\n const parent = element.parentElement;\n let tagWithOrdinal = tag;\n if (parent) {\n const siblings = Array.from(parent.children);\n const sameTagSiblings = siblings.filter(sibling => (sibling).nodeName.toLowerCase() === nodeName);\n if (sameTagSiblings.length > 1)\n tagWithOrdinal += `[${1 + siblings.indexOf(element)}]`;\n }\n // Do not include text into this token, only tag / attributes.\n // Topmost node will get all the text.\n const nonTextConditions = [...tagConditions, ...attrConditions];\n const levelToken = nonTextConditions.length ? `${tagWithOrdinal}[${nonTextConditions.join(' and ')}]` : tokens.length ? '' : tagWithOrdinal;\n tokens.unshift(levelToken);\n }\n return uniqueXPathSelector();\n },\n query(root, selector) {\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return;\n const it = document.evaluate(selector, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = it.iterateNext(); node; node = it.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n return node;\n }\n },\n queryAll(root, selector) {\n const result = [];\n const document = root instanceof Document ? root : root.ownerDocument;\n if (!document)\n return result;\n const it = document.evaluate(selector, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n for (let node = it.iterateNext(); node; node = it.iterateNext()) {\n if (node.nodeType === Node.ELEMENT_NODE)\n result.push(node);\n }\n return result;\n }\n};\nfunction createNoText(root, targetElement) {\n const steps = [];\n for (let element = targetElement; element && element !== root; element = element.parentElement) {\n if (element.getAttribute('id')) {\n steps.unshift(`//*[@id=\"${element.getAttribute('id')}\"]`);\n return steps.join('/');\n }\n const siblings = element.parentElement ? Array.from(element.parentElement.children) : [];\n const similarElements = siblings.filter(sibling => element.nodeName === sibling.nodeName);\n const index = similarElements.length === 1 ? 0 : similarElements.indexOf(element) + 1;\n steps.unshift(index ? `${element.nodeName}[${index}]` : element.nodeName);\n }\n return '/' + steps.join('/');\n}\n\n\n/***/ })\n\n/******/ })).default"; | ||
//# sourceMappingURL=injectedSource.js.map |
@@ -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('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"; | ||
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 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('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"; | ||
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 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 |
@@ -30,3 +30,3 @@ /** | ||
content?: string; | ||
}, ...args: any[]): Promise<string>; | ||
}, args?: any[], addSourceUrl?: boolean): Promise<string>; | ||
static installApiHooks(className: string, classType: any): void; | ||
@@ -33,0 +33,0 @@ static addEventListener(emitter: platform.EventEmitterType, eventName: (string | symbol), handler: (...args: any[]) => void): RegisteredListener; |
@@ -35,3 +35,3 @@ "use strict"; | ||
} | ||
static async evaluationScript(fun, ...args) { | ||
static async evaluationScript(fun, args = [], addSourceUrl = true) { | ||
if (!exports.helper.isString(fun) && typeof fun !== 'function') { | ||
@@ -43,3 +43,4 @@ if (fun.content !== undefined) { | ||
let contents = await platform.readFileAsync(fun.path, 'utf8'); | ||
contents += '//# sourceURL=' + fun.path.replace(/\n/g, ''); | ||
if (addSourceUrl) | ||
contents += '//# sourceURL=' + fun.path.replace(/\n/g, ''); | ||
fun = contents; | ||
@@ -46,0 +47,0 @@ } |
@@ -19,3 +19,2 @@ "use strict"; | ||
exports.CSSEngine = { | ||
name: 'css', | ||
create(root, targetElement) { | ||
@@ -22,0 +21,0 @@ const tokens = []; |
@@ -695,3 +695,2 @@ /******/ (function(modules) { // webpackBootstrap | ||
const ZSSelectorEngine = { | ||
name: 'zs', | ||
create(root, element, type) { | ||
@@ -698,0 +697,0 @@ return new Engine().create(root, element, type || 'default'); |
@@ -23,3 +23,6 @@ /** | ||
readonly engines: Map<string, SelectorEngine>; | ||
constructor(customEngines: SelectorEngine[]); | ||
constructor(customEngines: { | ||
name: string; | ||
engine: SelectorEngine; | ||
}[]); | ||
querySelector(selector: string, root: Node): Element | undefined; | ||
@@ -26,0 +29,0 @@ querySelectorAll(selector: string, root: Node): Element[]; |
@@ -24,3 +24,2 @@ "use strict"; | ||
const engine = { | ||
name: attribute, | ||
create(root, target) { | ||
@@ -44,15 +43,14 @@ const value = target.getAttribute(attribute); | ||
constructor(customEngines) { | ||
const defaultEngines = [ | ||
cssSelectorEngine_1.CSSEngine, | ||
xpathSelectorEngine_1.XPathEngine, | ||
textSelectorEngine_1.TextEngine, | ||
createAttributeEngine('id'), | ||
createAttributeEngine('data-testid'), | ||
createAttributeEngine('data-test-id'), | ||
createAttributeEngine('data-test'), | ||
]; | ||
this.utils = new utils_1.Utils(); | ||
this.engines = new Map(); | ||
for (const engine of [...defaultEngines, ...customEngines]) | ||
this.engines.set(engine.name, engine); | ||
// Note: keep predefined names in sync with Selectors class. | ||
this.engines.set('css', cssSelectorEngine_1.CSSEngine); | ||
this.engines.set('xpath', xpathSelectorEngine_1.XPathEngine); | ||
this.engines.set('text', textSelectorEngine_1.TextEngine); | ||
this.engines.set('id', createAttributeEngine('id')); | ||
this.engines.set('data-testid', createAttributeEngine('data-testid')); | ||
this.engines.set('data-test-id', createAttributeEngine('data-test-id')); | ||
this.engines.set('data-test', createAttributeEngine('data-test')); | ||
for (const { name, engine } of customEngines) | ||
this.engines.set(name, engine); | ||
} | ||
@@ -59,0 +57,0 @@ querySelector(selector, root) { |
@@ -115,3 +115,2 @@ /******/ (function(modules) { // webpackBootstrap | ||
exports.CSSEngine = { | ||
name: 'css', | ||
create(root, targetElement) { | ||
@@ -213,3 +212,2 @@ const tokens = []; | ||
const engine = { | ||
name: attribute, | ||
create(root, target) { | ||
@@ -233,15 +231,14 @@ const value = target.getAttribute(attribute); | ||
constructor(customEngines) { | ||
const defaultEngines = [ | ||
cssSelectorEngine_1.CSSEngine, | ||
xpathSelectorEngine_1.XPathEngine, | ||
textSelectorEngine_1.TextEngine, | ||
createAttributeEngine('id'), | ||
createAttributeEngine('data-testid'), | ||
createAttributeEngine('data-test-id'), | ||
createAttributeEngine('data-test'), | ||
]; | ||
this.utils = new utils_1.Utils(); | ||
this.engines = new Map(); | ||
for (const engine of [...defaultEngines, ...customEngines]) | ||
this.engines.set(engine.name, engine); | ||
// Note: keep predefined names in sync with Selectors class. | ||
this.engines.set('css', cssSelectorEngine_1.CSSEngine); | ||
this.engines.set('xpath', xpathSelectorEngine_1.XPathEngine); | ||
this.engines.set('text', textSelectorEngine_1.TextEngine); | ||
this.engines.set('id', createAttributeEngine('id')); | ||
this.engines.set('data-testid', createAttributeEngine('data-testid')); | ||
this.engines.set('data-test-id', createAttributeEngine('data-test-id')); | ||
this.engines.set('data-test', createAttributeEngine('data-test')); | ||
for (const { name, engine } of customEngines) | ||
this.engines.set(name, engine); | ||
} | ||
@@ -594,3 +591,2 @@ querySelector(selector, root) { | ||
exports.TextEngine = { | ||
name: 'text', | ||
create(root, targetElement, type) { | ||
@@ -740,3 +736,2 @@ const document = root instanceof Document ? root : root.ownerDocument; | ||
exports.XPathEngine = { | ||
name: 'xpath', | ||
create(root, targetElement, type) { | ||
@@ -743,0 +738,0 @@ const maybeDocument = root instanceof Document ? root : root.ownerDocument; |
@@ -19,3 +19,2 @@ /** | ||
export interface SelectorEngine { | ||
name: string; | ||
create(root: SelectorRoot, target: Element, type?: SelectorType): string | undefined; | ||
@@ -22,0 +21,0 @@ query(root: SelectorRoot, selector: string): Element | undefined; |
@@ -19,3 +19,2 @@ "use strict"; | ||
exports.TextEngine = { | ||
name: 'text', | ||
create(root, targetElement, type) { | ||
@@ -22,0 +21,0 @@ const document = root instanceof Document ? root : root.ownerDocument; |
@@ -21,3 +21,2 @@ "use strict"; | ||
exports.XPathEngine = { | ||
name: 'xpath', | ||
create(root, targetElement, type) { | ||
@@ -24,0 +23,0 @@ const maybeDocument = root instanceof Document ? root : root.ownerDocument; |
@@ -599,3 +599,2 @@ "use strict"; | ||
const ZSSelectorEngine = { | ||
name: 'zs', | ||
create(root, element, type) { | ||
@@ -602,0 +601,0 @@ return new Engine().create(root, element, type || 'default'); |
@@ -290,3 +290,3 @@ "use strict"; | ||
async addInitScript(script, ...args) { | ||
await this._delegate.evaluateOnNewDocument(await helper_1.helper.evaluationScript(script, ...args)); | ||
await this._delegate.evaluateOnNewDocument(await helper_1.helper.evaluationScript(script, args)); | ||
} | ||
@@ -293,0 +293,0 @@ async setCacheEnabled(enabled = true) { |
@@ -18,8 +18,11 @@ /** | ||
export declare class Selectors { | ||
readonly _sources: string[]; | ||
readonly _engines: Map<string, string>; | ||
_generation: number; | ||
static _instance(): Selectors; | ||
constructor(); | ||
register(engineFunction: string | Function, ...args: any[]): Promise<void>; | ||
register(name: string, script: string | Function | { | ||
path?: string; | ||
content?: string; | ||
}): Promise<void>; | ||
_createSelector(name: string, handle: dom.ElementHandle<Element>): Promise<string | undefined>; | ||
} |
@@ -23,3 +23,3 @@ "use strict"; | ||
this._generation = 0; | ||
this._sources = []; | ||
this._engines = new Map(); | ||
} | ||
@@ -31,5 +31,12 @@ static _instance() { | ||
} | ||
async register(engineFunction, ...args) { | ||
const source = helper_1.helper.evaluationString(engineFunction, ...args); | ||
this._sources.push(source); | ||
async register(name, script) { | ||
if (!name.match(/^[a-zA-Z_0-9-]+$/)) | ||
throw new Error('Selector engine name may only contain [a-zA-Z0-9_] characters'); | ||
// Note: keep in sync with Injected class, and also keep 'zs' for future. | ||
if (['css', 'xpath', 'text', 'id', 'zs', 'data-testid', 'data-test-id', 'data-test'].includes(name)) | ||
throw new Error(`"${name}" is a predefined selector engine`); | ||
const source = await helper_1.helper.evaluationScript(script, [], false); | ||
if (this._engines.has(name)) | ||
throw new Error(`"${name}" selector engine has been already registered`); | ||
this._engines.set(name, source); | ||
++this._generation; | ||
@@ -36,0 +43,0 @@ } |
@@ -231,3 +231,3 @@ "use strict"; | ||
async addInitScript(script, ...args) { | ||
const source = await helper_1.helper.evaluationScript(script, ...args); | ||
const source = await helper_1.helper.evaluationScript(script, args); | ||
this._evaluateOnNewDocumentSources.push(source); | ||
@@ -234,0 +234,0 @@ for (const page of this._existingPages()) |
{ | ||
"name": "playwright-core", | ||
"version": "0.11.1-next.1582931884276", | ||
"version": "0.11.1-next.1582935649965", | ||
"description": "A high-level API to automate web browsers", | ||
@@ -5,0 +5,0 @@ "repository": "github:Microsoft/playwright", |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2420964
56981