Skip to content

Commit 05e3920

Browse files
authored
Merge pull request #2 from Probely/manifest-3
New version 0.0.3. Added support for manifest V3
2 parents 80bb7e0 + 401d185 commit 05e3920

49 files changed

Lines changed: 10175 additions & 11498 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.babelrc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
{
22
"presets": [
3-
// "@babel/preset-env",
4-
// "@babel/preset-react"
5-
"react-app"
3+
// "@babel/preset-env"
4+
"@babel/preset-react"
5+
// "react-app"
66
],
77
"plugins": [
88
// "@babel/plugin-proposal-class-properties",
9-
"react-hot-loader/babel"
109
]
1110
}

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
"globals": {
44
"chrome": "readonly"
55
}
6-
}
6+
}

aux/finder_testpage.js

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// License: MIT
2+
// Author: Anton Medvedev <anton@medv.io>
3+
// Source: https://github.com/antonmedv/finder
4+
let config;
5+
let rootDocument;
6+
function finder(input, options) {
7+
if (input.nodeType !== Node.ELEMENT_NODE) {
8+
throw new Error(`Can't generate CSS selector for non-element node type.`);
9+
}
10+
if ('html' === input.tagName.toLowerCase()) {
11+
return 'html';
12+
}
13+
const defaults = {
14+
root: document.body,
15+
idName: (name) => true,
16+
className: (name) => true,
17+
tagName: (name) => true,
18+
attr: (name, value) => false,
19+
seedMinLength: 1,
20+
optimizedMinLength: 2,
21+
threshold: 1000,
22+
maxNumberOfTries: 10000,
23+
};
24+
config = { ...defaults, ...options };
25+
rootDocument = findRootDocument(config.root, defaults);
26+
let path = bottomUpSearch(input, 'all', () =>
27+
bottomUpSearch(input, 'two', () =>
28+
bottomUpSearch(input, 'one', () => bottomUpSearch(input, 'none'))
29+
)
30+
);
31+
if (path) {
32+
const optimized = sort(optimize(path, input));
33+
if (optimized.length > 0) {
34+
path = optimized[0];
35+
}
36+
return selector(path);
37+
} else {
38+
throw new Error(`Selector was not found.`);
39+
}
40+
}
41+
function findRootDocument(rootNode, defaults) {
42+
if (rootNode.nodeType === Node.DOCUMENT_NODE) {
43+
return rootNode;
44+
}
45+
if (rootNode === defaults.root) {
46+
return rootNode.ownerDocument;
47+
}
48+
return rootNode;
49+
}
50+
function bottomUpSearch(input, limit, fallback) {
51+
let path = null;
52+
let stack = [];
53+
let current = input;
54+
let i = 0;
55+
while (current) {
56+
let level = maybe(id(current)) ||
57+
maybe(...attr(current)) ||
58+
maybe(...classNames(current)) ||
59+
maybe(tagName(current)) || [any()];
60+
const nth = index(current);
61+
if (limit == 'all') {
62+
if (nth) {
63+
level = level.concat(
64+
level.filter(dispensableNth).map((node) => nthChild(node, nth))
65+
);
66+
}
67+
} else if (limit == 'two') {
68+
level = level.slice(0, 1);
69+
if (nth) {
70+
level = level.concat(
71+
level.filter(dispensableNth).map((node) => nthChild(node, nth))
72+
);
73+
}
74+
} else if (limit == 'one') {
75+
const [node] = (level = level.slice(0, 1));
76+
if (nth && dispensableNth(node)) {
77+
level = [nthChild(node, nth)];
78+
}
79+
} else if (limit == 'none') {
80+
level = [any()];
81+
if (nth) {
82+
level = [nthChild(level[0], nth)];
83+
}
84+
}
85+
for (let node of level) {
86+
node.level = i;
87+
}
88+
stack.push(level);
89+
if (stack.length >= config.seedMinLength) {
90+
path = findUniquePath(stack, fallback);
91+
if (path) {
92+
break;
93+
}
94+
}
95+
current = current.parentElement;
96+
i++;
97+
}
98+
if (!path) {
99+
path = findUniquePath(stack, fallback);
100+
}
101+
if (!path && fallback) {
102+
return fallback();
103+
}
104+
return path;
105+
}
106+
function findUniquePath(stack, fallback) {
107+
const paths = sort(combinations(stack));
108+
if (paths.length > config.threshold) {
109+
return fallback ? fallback() : null;
110+
}
111+
for (let candidate of paths) {
112+
if (unique(candidate)) {
113+
return candidate;
114+
}
115+
}
116+
return null;
117+
}
118+
function selector(path) {
119+
let node = path[0];
120+
let query = node.name;
121+
for (let i = 1; i < path.length; i++) {
122+
const level = path[i].level || 0;
123+
if (node.level === level - 1) {
124+
query = `${path[i].name} > ${query}`;
125+
} else {
126+
query = `${path[i].name} ${query}`;
127+
}
128+
node = path[i];
129+
}
130+
return query;
131+
}
132+
function penalty(path) {
133+
return path.map((node) => node.penalty).reduce((acc, i) => acc + i, 0);
134+
}
135+
function unique(path) {
136+
const css = selector(path);
137+
switch (rootDocument.querySelectorAll(css).length) {
138+
case 0:
139+
throw new Error(`Can't select any node with this selector: ${css}`);
140+
case 1:
141+
return true;
142+
default:
143+
return false;
144+
}
145+
}
146+
function id(input) {
147+
const elementId = input.getAttribute('id');
148+
if (elementId && config.idName(elementId)) {
149+
return {
150+
name: '#' + CSS.escape(elementId),
151+
penalty: 0,
152+
};
153+
}
154+
return null;
155+
}
156+
function attr(input) {
157+
const attrs = Array.from(input.attributes).filter((attr) =>
158+
config.attr(attr.name, attr.value)
159+
);
160+
return attrs.map((attr) => ({
161+
name: `[${CSS.escape(attr.name)}="${CSS.escape(attr.value)}"]`,
162+
penalty: 0.5,
163+
}));
164+
}
165+
function classNames(input) {
166+
const names = Array.from(input.classList).filter(config.className);
167+
return names.map((name) => ({
168+
name: '.' + CSS.escape(name),
169+
penalty: 1,
170+
}));
171+
}
172+
function tagName(input) {
173+
const name = input.tagName.toLowerCase();
174+
if (config.tagName(name)) {
175+
return {
176+
name,
177+
penalty: 2,
178+
};
179+
}
180+
return null;
181+
}
182+
function any() {
183+
return {
184+
name: '*',
185+
penalty: 3,
186+
};
187+
}
188+
function index(input) {
189+
const parent = input.parentNode;
190+
if (!parent) {
191+
return null;
192+
}
193+
let child = parent.firstChild;
194+
if (!child) {
195+
return null;
196+
}
197+
let i = 0;
198+
while (child) {
199+
if (child.nodeType === Node.ELEMENT_NODE) {
200+
i++;
201+
}
202+
if (child === input) {
203+
break;
204+
}
205+
child = child.nextSibling;
206+
}
207+
return i;
208+
}
209+
function nthChild(node, i) {
210+
return {
211+
name: node.name + `:nth-child(${i})`,
212+
penalty: node.penalty + 1,
213+
};
214+
}
215+
function dispensableNth(node) {
216+
return node.name !== 'html' && !node.name.startsWith('#');
217+
}
218+
function maybe(...level) {
219+
const list = level.filter(notEmpty);
220+
if (list.length > 0) {
221+
return list;
222+
}
223+
return null;
224+
}
225+
function notEmpty(value) {
226+
return value !== null && value !== undefined;
227+
}
228+
function* combinations(stack, path = []) {
229+
if (stack.length > 0) {
230+
for (let node of stack[0]) {
231+
yield* combinations(stack.slice(1, stack.length), path.concat(node));
232+
}
233+
} else {
234+
yield path;
235+
}
236+
}
237+
function sort(paths) {
238+
return [...paths].sort((a, b) => penalty(a) - penalty(b));
239+
}
240+
function* optimize(
241+
path,
242+
input,
243+
scope = {
244+
counter: 0,
245+
visited: new Map(),
246+
}
247+
) {
248+
if (path.length > 2 && path.length > config.optimizedMinLength) {
249+
for (let i = 1; i < path.length - 1; i++) {
250+
if (scope.counter > config.maxNumberOfTries) {
251+
return; // Okay At least I tried!
252+
}
253+
scope.counter += 1;
254+
const newPath = [...path];
255+
newPath.splice(i, 1);
256+
const newPathKey = selector(newPath);
257+
if (scope.visited.has(newPathKey)) {
258+
return;
259+
}
260+
if (unique(newPath) && same(newPath, input)) {
261+
yield newPath;
262+
scope.visited.set(newPathKey, true);
263+
yield* optimize(newPath, input, scope);
264+
}
265+
}
266+
}
267+
}
268+
function same(path, input) {
269+
return rootDocument.querySelector(selector(path)) === input;
270+
}

aux/iframe.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!doctype html>
2+
<html>
3+
4+
<body>
5+
<div>
6+
<h1>Inside iframe</h1>
7+
<div><button type="button" id="button_inside_iframe">Click me</button></div>
8+
</div>
9+
</body>
10+
11+
</html>

aux/secondpage.html

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
5+
<meta charset="utf-8">
6+
<title>...</title>
7+
<style type="text/css">
8+
body {
9+
display: flex;
10+
flex: 1;
11+
flex-direction: column;
12+
}
13+
.container {
14+
display: flex;
15+
flex: 1;
16+
flex-direction: row;
17+
flex-wrap: wrap;
18+
}
19+
.item_test_container {
20+
display: flex;
21+
/* flex: 1; */
22+
flex-direction: column;
23+
align-items: center;
24+
vertical-align: middle;
25+
border: 1px solid blue;
26+
width: 300px;
27+
text-align: center;
28+
}
29+
input, button, select {
30+
margin: 20px;
31+
}
32+
#mouse_over_elm {
33+
height: 100%;
34+
width: 100%;
35+
border: 1px solid red;
36+
}
37+
</style>
38+
</head>
39+
<body>
40+
<h1>Get elements test page - second page</h1>
41+
<div class="container">
42+
<p><a href="./testpage.html">Go back to the main page</a></p>
43+
</div>
44+
</body>
45+
</html>

0 commit comments

Comments
 (0)