From 57ff3574b5a70005d7a08a59a19b1dfffe7f28c3 Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Fri, 13 Aug 2021 21:37:47 +0800 Subject: [PATCH] fix(tracker): put vendor lib inside src for the cjs compilation --- tracker/tracker/package-lock.json | 2 +- tracker/tracker/package.json | 2 +- tracker/tracker/src/main/modules/mouse.ts | 2 +- .../tracker/src/main/vendors/finder/LICENSE | 21 + .../tracker/src/main/vendors/finder/README.md | 132 ++++++ .../src/main/vendors/finder/finder.d.ts | 12 + .../tracker/src/main/vendors/finder/finder.js | 339 ++++++++++++++ .../tracker/src/main/vendors/finder/finder.ts | 414 ++++++++++++++++++ .../src/main/vendors/finder/package.json | 78 ++++ 9 files changed, 999 insertions(+), 3 deletions(-) create mode 100644 tracker/tracker/src/main/vendors/finder/LICENSE create mode 100644 tracker/tracker/src/main/vendors/finder/README.md create mode 100644 tracker/tracker/src/main/vendors/finder/finder.d.ts create mode 100644 tracker/tracker/src/main/vendors/finder/finder.js create mode 100644 tracker/tracker/src/main/vendors/finder/finder.ts create mode 100644 tracker/tracker/src/main/vendors/finder/package.json diff --git a/tracker/tracker/package-lock.json b/tracker/tracker/package-lock.json index 4af49d03f..62bc7c1b6 100644 --- a/tracker/tracker/package-lock.json +++ b/tracker/tracker/package-lock.json @@ -1,6 +1,6 @@ { "name": "@openreplay/tracker", - "version": "3.1.0", + "version": "3.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 822107f58..eb760e700 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "3.2.0", + "version": "3.2.1", "keywords": [ "logging", "replay" diff --git a/tracker/tracker/src/main/modules/mouse.ts b/tracker/tracker/src/main/modules/mouse.ts index 73e3672b9..cae32142f 100644 --- a/tracker/tracker/src/main/modules/mouse.ts +++ b/tracker/tracker/src/main/modules/mouse.ts @@ -1,4 +1,4 @@ -import { finder } from '@medv/finder'; +import { finder } from '../vendors/finder/finder'; import { normSpaces, hasOpenreplayAttribute, getLabelAttribute } from '../utils'; import App from '../app'; import { MouseMove, MouseClick } from '../../messages'; diff --git a/tracker/tracker/src/main/vendors/finder/LICENSE b/tracker/tracker/src/main/vendors/finder/LICENSE new file mode 100644 index 000000000..1c1d11619 --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018–2020 Anton Medvedev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tracker/tracker/src/main/vendors/finder/README.md b/tracker/tracker/src/main/vendors/finder/README.md new file mode 100644 index 000000000..391bfee9a --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/README.md @@ -0,0 +1,132 @@ +![finder](https://medv.io/assets/finder.png) + +# finder + +[![npm](https://img.shields.io/npm/v/@medv/finder?color=grightgreen)](https://www.npmjs.com/package/@medv/finder) +[![Build status](https://img.shields.io/travis/antonmedv/finder)](https://travis-ci.org/antonmedv/finder) +[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@medv/finder?label=size)](https://bundlephobia.com/result?p=@medv/finder) + +> CSS Selector Generator + +## Features + +* Generates **shortest** selectors +* **Unique** selectors per page +* Stable and **robust** selectors +* **2.1 kB** gzip and minify size + +## Install + +```bash +npm install @medv/finder +``` + +Finder can be used via modules: + +```html + +``` + +## Usage + +```js +import {finder} from '@medv/finder' + +document.addEventListener('click', event => { + const selector = finder(event.target) + console.log(selector) +}) +``` + +## Example + +Example of generated selector: + +```css +.blog > article:nth-child(3) .add-comment +``` + +## Configuration + +`finder` takes configuration object as second parameters. Here is example of all params with default values: + +```js +const selector = finder(event.target, { + root: document.body, + className: (name) => true, + tagName: (name) => true, + attr: (name, value) => false, + seedMinLength: 1, + optimizedMinLength: 2, + threshold: 1000, + maxNumberOfTries: 10_000, +}) +``` + +#### `root: Element` + +Root of search, defaults to `document.body`. + +#### `idName: (name: string) => boolean` + +Check if this ID can be used. For example you can restrict using framework specific IDs: + +```js +const selector = finder(event.target, { + idName: name => !name.startsWith('ember') +}) +``` + +#### `className: (name: string) => boolean` + +Check if this class name can be used. For example you can restrict using _is-*_ class names: + +```js +const selector = finder(event.target, { + className: name => !name.startsWith('is-') +}) +``` + +#### `tagName: (name: string) => boolean` + +Check if tag name can be used, same as `className`. + +#### `attr: (name: string, value: string) => boolean` + +Check if attr name can be used. + +#### `seedMinLength: number` + +Minimum length of levels in fining selector. Starts from `1`. +For more robust selectors give this param value around 4-5 depending on depth of you DOM tree. +If `finder` hits `root` this param is ignored. + +#### `optimizedMinLength: number` + +Minimum length for optimising selector. Starts from `2`. +For example selector `body > div > div > p` can be optimized to `body p`. + +#### `threshold: number` + +Max number of selectors to check before falling into `nth-child` usage. +Checking for uniqueness of selector is very costs operation, if you have DOM tree depth of 5, with 5 classes on each level, +that gives you more than 3k selectors to check. +`finder` uses two step approach so it's reaching this threshold in some cases twice. +Default `1000` is good enough in most cases. + +#### `maxNumberOfTries: number` + +Max number of tries when we do the optimization. It is a trade-off between optimization and efficiency. +Default `10_000` is good enough in most cases. + +### Google Chrome Extension + +![Chrome Extension](https://user-images.githubusercontent.com/141232/36737287-4a999d84-1c0d-11e8-8a14-43bcf9baf7ca.png) + +Generate the unique selectors in your browser by using [Chrome Extension](https://chrome.google.com/webstore/detail/get-unique-css-selector/lkfaghhbdebclkklgjhhonadomejckai) + +## License + +[MIT](LICENSE) diff --git a/tracker/tracker/src/main/vendors/finder/finder.d.ts b/tracker/tracker/src/main/vendors/finder/finder.d.ts new file mode 100644 index 000000000..aaff849fb --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/finder.d.ts @@ -0,0 +1,12 @@ +export declare type Options = { + root: Element; + idName: (name: string) => boolean; + className: (name: string) => boolean; + tagName: (name: string) => boolean; + attr: (name: string, value: string) => boolean; + seedMinLength: number; + optimizedMinLength: number; + threshold: number; + maxNumberOfTries: number; +}; +export declare function finder(input: Element, options?: Partial): string; diff --git a/tracker/tracker/src/main/vendors/finder/finder.js b/tracker/tracker/src/main/vendors/finder/finder.js new file mode 100644 index 000000000..0e5eab2d7 --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/finder.js @@ -0,0 +1,339 @@ +var Limit; +(function (Limit) { + Limit[Limit["All"] = 0] = "All"; + Limit[Limit["Two"] = 1] = "Two"; + Limit[Limit["One"] = 2] = "One"; +})(Limit || (Limit = {})); +let config; +let rootDocument; +export function finder(input, options) { + if (input.nodeType !== Node.ELEMENT_NODE) { + throw new Error(`Can't generate CSS selector for non-element node type.`); + } + if ("html" === input.tagName.toLowerCase()) { + return "html"; + } + const defaults = { + root: document.body, + idName: (name) => true, + className: (name) => true, + tagName: (name) => true, + attr: (name, value) => false, + seedMinLength: 1, + optimizedMinLength: 2, + threshold: 1000, + maxNumberOfTries: 10000, + }; + config = Object.assign(Object.assign({}, defaults), options); + rootDocument = findRootDocument(config.root, defaults); + let path = bottomUpSearch(input, Limit.All, () => bottomUpSearch(input, Limit.Two, () => bottomUpSearch(input, Limit.One))); + if (path) { + const optimized = sort(optimize(path, input)); + if (optimized.length > 0) { + path = optimized[0]; + } + return selector(path); + } + else { + throw new Error(`Selector was not found.`); + } +} +function findRootDocument(rootNode, defaults) { + if (rootNode.nodeType === Node.DOCUMENT_NODE) { + return rootNode; + } + if (rootNode === defaults.root) { + return rootNode.ownerDocument; + } + return rootNode; +} +function bottomUpSearch(input, limit, fallback) { + let path = null; + let stack = []; + let current = input; + let i = 0; + while (current && current !== config.root.parentElement) { + let level = maybe(id(current)) || maybe(...attr(current)) || maybe(...classNames(current)) || maybe(tagName(current)) || [any()]; + const nth = index(current); + if (limit === Limit.All) { + if (nth) { + level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))); + } + } + else if (limit === Limit.Two) { + level = level.slice(0, 1); + if (nth) { + level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))); + } + } + else if (limit === Limit.One) { + const [node] = level = level.slice(0, 1); + if (nth && dispensableNth(node)) { + level = [nthChild(node, nth)]; + } + } + for (let node of level) { + node.level = i; + } + stack.push(level); + if (stack.length >= config.seedMinLength) { + path = findUniquePath(stack, fallback); + if (path) { + break; + } + } + current = current.parentElement; + i++; + } + if (!path) { + path = findUniquePath(stack, fallback); + } + return path; +} +function findUniquePath(stack, fallback) { + const paths = sort(combinations(stack)); + if (paths.length > config.threshold) { + return fallback ? fallback() : null; + } + for (let candidate of paths) { + if (unique(candidate)) { + return candidate; + } + } + return null; +} +function selector(path) { + let node = path[0]; + let query = node.name; + for (let i = 1; i < path.length; i++) { + const level = path[i].level || 0; + if (node.level === level - 1) { + query = `${path[i].name} > ${query}`; + } + else { + query = `${path[i].name} ${query}`; + } + node = path[i]; + } + return query; +} +function penalty(path) { + return path.map(node => node.penalty).reduce((acc, i) => acc + i, 0); +} +function unique(path) { + switch (rootDocument.querySelectorAll(selector(path)).length) { + case 0: + throw new Error(`Can't select any node with this selector: ${selector(path)}`); + case 1: + return true; + default: + return false; + } +} +function id(input) { + const elementId = input.getAttribute("id"); + if (elementId && config.idName(elementId)) { + return { + name: "#" + cssesc(elementId, { isIdentifier: true }), + penalty: 0, + }; + } + return null; +} +function attr(input) { + const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value)); + return attrs.map((attr) => ({ + name: "[" + cssesc(attr.name, { isIdentifier: true }) + "=\"" + cssesc(attr.value) + "\"]", + penalty: 0.5 + })); +} +function classNames(input) { + const names = Array.from(input.classList) + .filter(config.className); + return names.map((name) => ({ + name: "." + cssesc(name, { isIdentifier: true }), + penalty: 1 + })); +} +function tagName(input) { + const name = input.tagName.toLowerCase(); + if (config.tagName(name)) { + return { + name, + penalty: 2 + }; + } + return null; +} +function any() { + return { + name: "*", + penalty: 3 + }; +} +function index(input) { + const parent = input.parentNode; + if (!parent) { + return null; + } + let child = parent.firstChild; + if (!child) { + return null; + } + let i = 0; + while (child) { + if (child.nodeType === Node.ELEMENT_NODE) { + i++; + } + if (child === input) { + break; + } + child = child.nextSibling; + } + return i; +} +function nthChild(node, i) { + return { + name: node.name + `:nth-child(${i})`, + penalty: node.penalty + 1 + }; +} +function dispensableNth(node) { + return node.name !== "html" && !node.name.startsWith("#"); +} +function maybe(...level) { + const list = level.filter(notEmpty); + if (list.length > 0) { + return list; + } + return null; +} +function notEmpty(value) { + return value !== null && value !== undefined; +} +function* combinations(stack, path = []) { + if (stack.length > 0) { + for (let node of stack[0]) { + yield* combinations(stack.slice(1, stack.length), path.concat(node)); + } + } + else { + yield path; + } +} +function sort(paths) { + return Array.from(paths).sort((a, b) => penalty(a) - penalty(b)); +} +function* optimize(path, input, scope = { + counter: 0, + visited: new Map() +}) { + if (path.length > 2 && path.length > config.optimizedMinLength) { + for (let i = 1; i < path.length - 1; i++) { + if (scope.counter > config.maxNumberOfTries) { + return; // Okay At least I tried! + } + scope.counter += 1; + const newPath = [...path]; + newPath.splice(i, 1); + const newPathKey = selector(newPath); + if (scope.visited.has(newPathKey)) { + return; + } + if (unique(newPath) && same(newPath, input)) { + yield newPath; + scope.visited.set(newPathKey, true); + yield* optimize(newPath, input, scope); + } + } + } +} +function same(path, input) { + return rootDocument.querySelector(selector(path)) === input; +} +const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/; +const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/; +const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g; +const defaultOptions = { + "escapeEverything": false, + "isIdentifier": false, + "quotes": "single", + "wrap": false +}; +function cssesc(string, opt = {}) { + const options = Object.assign(Object.assign({}, defaultOptions), opt); + if (options.quotes != "single" && options.quotes != "double") { + options.quotes = "single"; + } + const quote = options.quotes == "double" ? "\"" : "'"; + const isIdentifier = options.isIdentifier; + const firstChar = string.charAt(0); + let output = ""; + let counter = 0; + const length = string.length; + while (counter < length) { + const character = string.charAt(counter++); + let codePoint = character.charCodeAt(0); + let value = void 0; + // If it’s not a printable ASCII character… + if (codePoint < 0x20 || codePoint > 0x7E) { + if (codePoint >= 0xD800 && codePoint <= 0xDBFF && counter < length) { + // It’s a high surrogate, and there is a next character. + const extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { + // next character is low surrogate + codePoint = ((codePoint & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000; + } + else { + // It’s an unmatched surrogate; only append this code unit, in case + // the next code unit is the high surrogate of a surrogate pair. + counter--; + } + } + value = "\\" + codePoint.toString(16).toUpperCase() + " "; + } + else { + if (options.escapeEverything) { + if (regexAnySingleEscape.test(character)) { + value = "\\" + character; + } + else { + value = "\\" + codePoint.toString(16).toUpperCase() + " "; + } + } + else if (/[\t\n\f\r\x0B]/.test(character)) { + value = "\\" + codePoint.toString(16).toUpperCase() + " "; + } + else if (character == "\\" || !isIdentifier && (character == "\"" && quote == character || character == "'" && quote == character) || isIdentifier && regexSingleEscape.test(character)) { + value = "\\" + character; + } + else { + value = character; + } + } + output += value; + } + if (isIdentifier) { + if (/^-[-\d]/.test(output)) { + output = "\\-" + output.slice(1); + } + else if (/\d/.test(firstChar)) { + output = "\\3" + firstChar + " " + output.slice(1); + } + } + // Remove spaces after `\HEX` escapes that are not followed by a hex digit, + // since they’re redundant. Note that this is only possible if the escape + // sequence isn’t preceded by an odd number of backslashes. + output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) { + if ($1 && $1.length % 2) { + // It’s not safe to remove the space, so don’t. + return $0; + } + // Strip the space. + return ($1 || "") + $2; + }); + if (!isIdentifier && options.wrap) { + return quote + output + quote; + } + return output; +} diff --git a/tracker/tracker/src/main/vendors/finder/finder.ts b/tracker/tracker/src/main/vendors/finder/finder.ts new file mode 100644 index 000000000..bb2621d75 --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/finder.ts @@ -0,0 +1,414 @@ +type Node = { + name: string + penalty: number + level?: number +} + +type Path = Node[] + +enum Limit { + All, + Two, + One, +} + +export type Options = { + root: Element + idName: (name: string) => boolean + className: (name: string) => boolean + tagName: (name: string) => boolean + attr: (name: string, value: string) => boolean + seedMinLength: number + optimizedMinLength: number + threshold: number + maxNumberOfTries: number +} + +let config: Options + +let rootDocument: Document | Element +export function finder(input: Element, options?: Partial) { + if (input.nodeType !== Node.ELEMENT_NODE) { + throw new Error(`Can't generate CSS selector for non-element node type.`) + } + + if ("html" === input.tagName.toLowerCase()) { + return "html" + } + + const defaults: Options = { + root: document.body, + idName: (name: string) => true, + className: (name: string) => true, + tagName: (name: string) => true, + attr: (name: string, value: string) => false, + seedMinLength: 1, + optimizedMinLength: 2, + threshold: 1000, + maxNumberOfTries: 10000, + } + + config = {...defaults, ...options} + + rootDocument = findRootDocument(config.root, defaults) + + let path = + bottomUpSearch(input, Limit.All, () => + bottomUpSearch(input, Limit.Two, () => + bottomUpSearch(input, Limit.One))) + + if (path) { + const optimized = sort(optimize(path, input)) + + if (optimized.length > 0) { + path = optimized[0] + } + + return selector(path) + } else { + throw new Error(`Selector was not found.`) + } +} + +function findRootDocument(rootNode: Element | Document, defaults: Options) { + if (rootNode.nodeType === Node.DOCUMENT_NODE) { + return rootNode + } + if (rootNode === defaults.root) { + return rootNode.ownerDocument as Document + } + return rootNode +} + +function bottomUpSearch(input: Element, limit: Limit, fallback?: () => Path | null): Path | null { + let path: Path | null = null + let stack: Node[][] = [] + let current: Element | null = input + let i = 0 + + while (current && current !== config.root.parentElement) { + let level: Node[] = maybe(id(current)) || maybe(...attr(current)) || maybe(...classNames(current)) || maybe(tagName(current)) || [any()] + + const nth = index(current) + + if (limit === Limit.All) { + if (nth) { + level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))) + } + } else if (limit === Limit.Two) { + level = level.slice(0, 1) + + if (nth) { + level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))) + } + } else if (limit === Limit.One) { + const [node] = level = level.slice(0, 1) + + if (nth && dispensableNth(node)) { + level = [nthChild(node, nth)] + } + } + + for (let node of level) { + node.level = i + } + + stack.push(level) + + if (stack.length >= config.seedMinLength) { + path = findUniquePath(stack, fallback) + if (path) { + break + } + } + + current = current.parentElement + i++ + } + + if (!path) { + path = findUniquePath(stack, fallback) + } + + return path +} + +function findUniquePath(stack: Node[][], fallback?: () => Path | null): Path | null { + const paths = sort(combinations(stack)) + + if (paths.length > config.threshold) { + return fallback ? fallback() : null + } + + for (let candidate of paths) { + if (unique(candidate)) { + return candidate + } + } + + return null +} + +function selector(path: Path): string { + let node = path[0] + let query = node.name + for (let i = 1; i < path.length; i++) { + const level = path[i].level || 0 + + if (node.level === level - 1) { + query = `${path[i].name} > ${query}` + } else { + query = `${path[i].name} ${query}` + } + + node = path[i] + } + return query +} + +function penalty(path: Path): number { + return path.map(node => node.penalty).reduce((acc, i) => acc + i, 0) +} + +function unique(path: Path) { + switch (rootDocument.querySelectorAll(selector(path)).length) { + case 0: + throw new Error(`Can't select any node with this selector: ${selector(path)}`) + case 1: + return true + default: + return false + } +} + +function id(input: Element): Node | null { + const elementId = input.getAttribute("id") + if (elementId && config.idName(elementId)) { + return { + name: "#" + cssesc(elementId, {isIdentifier: true}), + penalty: 0, + } + } + return null +} + +function attr(input: Element): Node[] { + const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value)) + + return attrs.map((attr): Node => ({ + name: "[" + cssesc(attr.name, {isIdentifier: true}) + "=\"" + cssesc(attr.value) + "\"]", + penalty: 0.5 + })) +} + +function classNames(input: Element): Node[] { + const names = Array.from(input.classList) + .filter(config.className) + + return names.map((name): Node => ({ + name: "." + cssesc(name, {isIdentifier: true}), + penalty: 1 + })) +} + +function tagName(input: Element): Node | null { + const name = input.tagName.toLowerCase() + if (config.tagName(name)) { + return { + name, + penalty: 2 + } + } + return null +} + +function any(): Node { + return { + name: "*", + penalty: 3 + } +} + +function index(input: Element): number | null { + const parent = input.parentNode + if (!parent) { + return null + } + + let child = parent.firstChild + if (!child) { + return null + } + + let i = 0 + while (child) { + if (child.nodeType === Node.ELEMENT_NODE) { + i++ + } + + if (child === input) { + break + } + + child = child.nextSibling + } + + return i +} + +function nthChild(node: Node, i: number): Node { + return { + name: node.name + `:nth-child(${i})`, + penalty: node.penalty + 1 + } +} + +function dispensableNth(node: Node) { + return node.name !== "html" && !node.name.startsWith("#") +} + +function maybe(...level: (Node | null)[]): Node[] | null { + const list = level.filter(notEmpty) + if (list.length > 0) { + return list + } + return null +} + +function notEmpty(value: T | null | undefined): value is T { + return value !== null && value !== undefined +} + +function* combinations(stack: Node[][], path: Node[] = []): Generator { + if (stack.length > 0) { + for (let node of stack[0]) { + yield* combinations(stack.slice(1, stack.length), path.concat(node)) + } + } else { + yield path + } +} + +function sort(paths: Iterable): Path[] { + return Array.from(paths).sort((a, b) => penalty(a) - penalty(b)) +} + +type Scope = { + counter: number + visited: Map +} + +function* optimize(path: Path, input: Element, scope: Scope = { + counter: 0, + visited: new Map() +}): Generator { + if (path.length > 2 && path.length > config.optimizedMinLength) { + for (let i = 1; i < path.length - 1; i++) { + if (scope.counter > config.maxNumberOfTries) { + return // Okay At least I tried! + } + scope.counter += 1 + const newPath = [...path] + newPath.splice(i, 1) + const newPathKey = selector(newPath) + if (scope.visited.has(newPathKey)) { + return + } + if (unique(newPath) && same(newPath, input)) { + yield newPath + scope.visited.set(newPathKey, true) + yield* optimize(newPath, input, scope) + } + } + } +} + +function same(path: Path, input: Element) { + return rootDocument.querySelector(selector(path)) === input +} + +const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/ +const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/ +const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g + +const defaultOptions = { + "escapeEverything": false, + "isIdentifier": false, + "quotes": "single", + "wrap": false +} + +function cssesc(string: string, opt: Partial = {}) { + const options = {...defaultOptions, ...opt} + if (options.quotes != "single" && options.quotes != "double") { + options.quotes = "single" + } + const quote = options.quotes == "double" ? "\"" : "'" + const isIdentifier = options.isIdentifier + + const firstChar = string.charAt(0) + let output = "" + let counter = 0 + const length = string.length + while (counter < length) { + const character = string.charAt(counter++) + let codePoint = character.charCodeAt(0) + let value: string | undefined = void 0 + // If it’s not a printable ASCII character… + if (codePoint < 0x20 || codePoint > 0x7E) { + if (codePoint >= 0xD800 && codePoint <= 0xDBFF && counter < length) { + // It’s a high surrogate, and there is a next character. + const extra = string.charCodeAt(counter++) + if ((extra & 0xFC00) == 0xDC00) { + // next character is low surrogate + codePoint = ((codePoint & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000 + } else { + // It’s an unmatched surrogate; only append this code unit, in case + // the next code unit is the high surrogate of a surrogate pair. + counter-- + } + } + value = "\\" + codePoint.toString(16).toUpperCase() + " " + } else { + if (options.escapeEverything) { + if (regexAnySingleEscape.test(character)) { + value = "\\" + character + } else { + value = "\\" + codePoint.toString(16).toUpperCase() + " " + } + } else if (/[\t\n\f\r\x0B]/.test(character)) { + value = "\\" + codePoint.toString(16).toUpperCase() + " " + } else if (character == "\\" || !isIdentifier && (character == "\"" && quote == character || character == "'" && quote == character) || isIdentifier && regexSingleEscape.test(character)) { + value = "\\" + character + } else { + value = character + } + } + output += value + } + + if (isIdentifier) { + if (/^-[-\d]/.test(output)) { + output = "\\-" + output.slice(1) + } else if (/\d/.test(firstChar)) { + output = "\\3" + firstChar + " " + output.slice(1) + } + } + + // Remove spaces after `\HEX` escapes that are not followed by a hex digit, + // since they’re redundant. Note that this is only possible if the escape + // sequence isn’t preceded by an odd number of backslashes. + output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) { + if ($1 && $1.length % 2) { + // It’s not safe to remove the space, so don’t. + return $0 + } + // Strip the space. + return ($1 || "") + $2 + }) + + if (!isIdentifier && options.wrap) { + return quote + output + quote + } + return output +} diff --git a/tracker/tracker/src/main/vendors/finder/package.json b/tracker/tracker/src/main/vendors/finder/package.json new file mode 100644 index 000000000..685be35f4 --- /dev/null +++ b/tracker/tracker/src/main/vendors/finder/package.json @@ -0,0 +1,78 @@ +{ + "_from": "@medv/finder", + "_id": "@medv/finder@2.0.0", + "_inBundle": false, + "_integrity": "sha512-gV4jOsGpiWNDGd8Dw7tod1Fc9Gc7StaOT4oZ/6srHRWtsHU+HYWzmkYsa3Qy/z0e9tY1WpJ9wWdBFGskfbzoug==", + "_location": "/@medv/finder", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "@medv/finder", + "name": "@medv/finder", + "escapedName": "@medv%2ffinder", + "scope": "@medv", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/@medv/finder/-/finder-2.0.0.tgz", + "_shasum": "699b7141393aa815f120b38f54f92ad212225902", + "_spec": "@medv/finder", + "_where": "/Users/shikhu/work/openreplay/tracker/tracker", + "author": { + "name": "Anton Medvedev", + "email": "anton@medv.io" + }, + "ava": { + "require": [ + "esm", + "./test/helpers/setup-browser-env.js" + ] + }, + "bugs": { + "url": "https://github.com/antonmedv/finder/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "CSS Selector Generator", + "devDependencies": { + "ava": "^3.8.2", + "babel-minify": "*", + "browser-env": "^3.3.0", + "esm": "^3.2.25", + "gzip-size-cli": "*", + "release-it": "^13.6.1", + "typescript": "3.9.3" + }, + "files": [ + "*.ts", + "*.js" + ], + "homepage": "https://github.com/antonmedv/finder", + "keywords": [ + "css", + "selector", + "generator" + ], + "license": "MIT", + "main": "finder.js", + "name": "@medv/finder", + "repository": { + "type": "git", + "url": "git+https://github.com/antonmedv/finder.git" + }, + "scripts": { + "prepare": "tsc", + "release": "release-it --access public", + "size": "minify finder.js --sourceType module | gzip-size", + "start": "tsc -w", + "test": "tsc && ava" + }, + "types": "finder.d.ts", + "version": "2.0.0" +}