content-ui-main.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. /*
  2. * Copyright 2010-2020 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * The code in this file is free software: you can redistribute it and/or
  8. * modify it under the terms of the GNU Affero General Public License
  9. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  10. * of the License, or (at your option) any later version.
  11. *
  12. * The code in this file is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  15. * General Public License for more details.
  16. *
  17. * As additional permission under GNU AGPL version 3 section 7, you may
  18. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  19. * AGPL normally required by section 4, provided you include this license
  20. * notice and a URL through which recipients can access the Corresponding
  21. * Source.
  22. */
  23. /* global browser, document, prompt, getComputedStyle, addEventListener, removeEventListener, requestAnimationFrame, setTimeout, getSelection, Node */
  24. this.singlefile.extension.ui.content.main = this.singlefile.extension.ui.content.main || (() => {
  25. const SELECTED_CONTENT_ATTRIBUTE_NAME = this.singlefile.lib.helper.SELECTED_CONTENT_ATTRIBUTE_NAME;
  26. const MASK_TAGNAME = "singlefile-mask";
  27. const PROGRESS_BAR_TAGNAME = "singlefile-progress-bar";
  28. const PROGRESS_CURSOR_TAGNAME = "singlefile-progress-cursor";
  29. const SELECTION_ZONE_TAGNAME = "single-file-selection-zone";
  30. const LOGS_WINDOW_TAGNAME = "singlefile-logs-window";
  31. const LOGS_LINE_TAGNAME = "singlefile-logs-line";
  32. const LOGS_LINE_ELEMENT_TAGNAME = "singlefile-logs-element";
  33. const SINGLE_FILE_UI_ELEMENT_CLASS = "single-file-ui-element";
  34. const SELECT_PX_THRESHOLD = 8;
  35. const LOG_PANEL_DEFERRED_IMAGES_MESSAGE = browser.i18n.getMessage("logPanelDeferredImages");
  36. const LOG_PANEL_FRAME_CONTENTS_MESSAGE = browser.i18n.getMessage("logPanelFrameContents");
  37. const LOG_PANEL_STEP_MESSAGE = browser.i18n.getMessage("logPanelStep");
  38. const LOG_PANEL_WIDTH = browser.i18n.getMessage("logPanelWidth");
  39. let selectedAreaElement;
  40. let logsWindowElement = createLogsWindowElement();
  41. const allProperties = new Set();
  42. Array.from(getComputedStyle(document.body)).forEach(property => allProperties.add(property));
  43. return {
  44. getSelectedLinks,
  45. markSelection,
  46. unmarkSelection,
  47. prompt(message, defaultValue) {
  48. return prompt(message, defaultValue);
  49. },
  50. onStartPage(options) {
  51. let maskElement = document.querySelector(MASK_TAGNAME);
  52. if (!maskElement) {
  53. if (options.logsEnabled) {
  54. setLogsWindowStyle();
  55. document.body.appendChild(logsWindowElement);
  56. }
  57. if (options.shadowEnabled) {
  58. const maskElement = createMaskElement();
  59. if (options.progressBarEnabled) {
  60. createProgressBarElement(maskElement);
  61. }
  62. maskElement.offsetWidth;
  63. maskElement.style.setProperty("background-color", "black", "important");
  64. maskElement.style.setProperty("opacity", .3, "important");
  65. }
  66. }
  67. },
  68. onEndPage() {
  69. const maskElement = document.querySelector(MASK_TAGNAME);
  70. if (maskElement) {
  71. maskElement.remove();
  72. }
  73. logsWindowElement.remove();
  74. clearLogs();
  75. },
  76. onLoadResource(index, maxIndex, options) {
  77. if (options.shadowEnabled && options.progressBarEnabled) {
  78. const progressBarElement = document.querySelector(PROGRESS_BAR_TAGNAME);
  79. if (progressBarElement && maxIndex) {
  80. const width = Math.floor((index / maxIndex) * 100) + "%";
  81. if (progressBarElement.style.getPropertyValue("width") != width) {
  82. requestAnimationFrame(() => progressBarElement.style.setProperty("width", Math.floor((index / maxIndex) * 100) + "%", "important"));
  83. }
  84. }
  85. }
  86. },
  87. onLoadingDeferResources(options) {
  88. updateLog("load-deferred-images", LOG_PANEL_DEFERRED_IMAGES_MESSAGE, "…", options);
  89. },
  90. onLoadDeferResources(options) {
  91. updateLog("load-deferred-images", LOG_PANEL_DEFERRED_IMAGES_MESSAGE, "✓", options);
  92. },
  93. onLoadingFrames(options) {
  94. updateLog("load-frames", LOG_PANEL_FRAME_CONTENTS_MESSAGE, "…", options);
  95. },
  96. onLoadFrames(options) {
  97. updateLog("load-frames", LOG_PANEL_FRAME_CONTENTS_MESSAGE, "✓", options);
  98. },
  99. onStartStage(step, options) {
  100. updateLog("step-" + step, `${LOG_PANEL_STEP_MESSAGE} ${step + 1} / 3`, "…", options);
  101. },
  102. onEndStage(step, options) {
  103. updateLog("step-" + step, `${LOG_PANEL_STEP_MESSAGE} ${step + 1} / 3`, "✓", options);
  104. },
  105. onPageLoading() { },
  106. onLoadPage() { },
  107. onStartStageTask() { },
  108. onEndStageTask() { }
  109. };
  110. function getSelectedLinks() {
  111. let selectionFound;
  112. const links = [];
  113. const selection = getSelection();
  114. for (let indexRange = 0; indexRange < selection.rangeCount; indexRange++) {
  115. let range = selection.getRangeAt(indexRange);
  116. if (range && range.commonAncestorContainer) {
  117. const treeWalker = document.createTreeWalker(range.commonAncestorContainer);
  118. let rangeSelectionFound = false;
  119. let finished = false;
  120. while (!finished) {
  121. if (rangeSelectionFound || treeWalker.currentNode == range.startContainer || treeWalker.currentNode == range.endContainer) {
  122. rangeSelectionFound = true;
  123. if (range.startContainer != range.endContainer || range.startOffset != range.endOffset) {
  124. selectionFound = true;
  125. if (treeWalker.currentNode.tagName == "A" && treeWalker.currentNode.href) {
  126. links.push(treeWalker.currentNode.href);
  127. }
  128. }
  129. }
  130. if (treeWalker.currentNode == range.endContainer) {
  131. finished = true;
  132. } else {
  133. treeWalker.nextNode();
  134. }
  135. }
  136. if (selectionFound && treeWalker.currentNode == range.endContainer && treeWalker.currentNode.querySelectorAll) {
  137. treeWalker.currentNode.querySelectorAll("*").forEach(descendantElement => {
  138. if (descendantElement.tagName == "A" && descendantElement.href) {
  139. links.push(treeWalker.currentNode.href);
  140. }
  141. });
  142. }
  143. }
  144. }
  145. return Array.from(new Set(links));
  146. }
  147. async function markSelection(optionallySelected) {
  148. let selectionFound = markSelectedContent();
  149. if (selectionFound || optionallySelected) {
  150. return selectionFound;
  151. } else {
  152. selectionFound = await selectArea();
  153. if (selectionFound) {
  154. return markSelectedContent();
  155. }
  156. }
  157. }
  158. function markSelectedContent() {
  159. const selection = getSelection();
  160. let selectionFound;
  161. for (let indexRange = 0; indexRange < selection.rangeCount; indexRange++) {
  162. let range = selection.getRangeAt(indexRange);
  163. if (range && range.commonAncestorContainer) {
  164. const treeWalker = document.createTreeWalker(range.commonAncestorContainer);
  165. let rangeSelectionFound = false;
  166. let finished = false;
  167. while (!finished) {
  168. if (rangeSelectionFound || treeWalker.currentNode == range.startContainer || treeWalker.currentNode == range.endContainer) {
  169. rangeSelectionFound = true;
  170. if (range.startContainer != range.endContainer || range.startOffset != range.endOffset) {
  171. selectionFound = true;
  172. markSelectedNode(treeWalker.currentNode);
  173. }
  174. }
  175. if (selectionFound && treeWalker.currentNode == range.startContainer) {
  176. markSelectedParents(treeWalker.currentNode);
  177. }
  178. if (treeWalker.currentNode == range.endContainer) {
  179. finished = true;
  180. } else {
  181. treeWalker.nextNode();
  182. }
  183. }
  184. if (selectionFound && treeWalker.currentNode == range.endContainer && treeWalker.currentNode.querySelectorAll) {
  185. treeWalker.currentNode.querySelectorAll("*").forEach(descendantElement => markSelectedNode(descendantElement));
  186. }
  187. }
  188. }
  189. return selectionFound;
  190. }
  191. function markSelectedNode(node) {
  192. const element = node.nodeType == Node.ELEMENT_NODE ? node : node.parentElement;
  193. element.setAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME, "");
  194. }
  195. function markSelectedParents(node) {
  196. if (node.parentElement) {
  197. markSelectedNode(node);
  198. markSelectedParents(node.parentElement);
  199. }
  200. }
  201. function unmarkSelection() {
  202. document.querySelectorAll("[" + SELECTED_CONTENT_ATTRIBUTE_NAME + "]").forEach(selectedContent => selectedContent.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME));
  203. }
  204. function selectArea() {
  205. return new Promise(resolve => {
  206. let selectedRanges = [];
  207. addEventListener("mousemove", mousemoveListener, true);
  208. addEventListener("click", clickListener, true);
  209. addEventListener("keyup", keypressListener, true);
  210. document.addEventListener("contextmenu", contextmenuListener, true);
  211. getSelection().removeAllRanges();
  212. function contextmenuListener(event) {
  213. selectedRanges = [];
  214. select();
  215. event.preventDefault();
  216. }
  217. function mousemoveListener(event) {
  218. const targetElement = getTarget(event);
  219. if (targetElement) {
  220. selectedAreaElement = targetElement;
  221. moveAreaSelector(targetElement);
  222. }
  223. }
  224. function clickListener(event) {
  225. event.preventDefault();
  226. event.stopPropagation();
  227. if (event.button == 0) {
  228. select(selectedAreaElement, event.ctrlKey);
  229. } else {
  230. cancel();
  231. }
  232. }
  233. function keypressListener(event) {
  234. if (event.key == "Escape") {
  235. cancel();
  236. }
  237. }
  238. function cancel() {
  239. if (selectedRanges.length) {
  240. getSelection().removeAllRanges();
  241. }
  242. selectedRanges = [];
  243. cleanupAndResolve();
  244. }
  245. function select(selectedElement, multiSelect) {
  246. if (selectedElement) {
  247. if (!multiSelect) {
  248. restoreSelectedRanges();
  249. }
  250. const range = document.createRange();
  251. range.selectNodeContents(selectedElement);
  252. cleanupSelectionRanges();
  253. getSelection().addRange(range);
  254. saveSelectedRanges();
  255. if (!multiSelect) {
  256. cleanupAndResolve();
  257. }
  258. } else {
  259. cleanupAndResolve();
  260. }
  261. }
  262. function cleanupSelectionRanges() {
  263. const selection = getSelection();
  264. for (let indexRange = selection.rangeCount - 1; indexRange >= 0; indexRange--) {
  265. const range = selection.getRangeAt(indexRange);
  266. if (range.startOffset == range.endOffset) {
  267. selection.removeRange(range);
  268. indexRange--;
  269. }
  270. }
  271. }
  272. function cleanupAndResolve() {
  273. getAreaSelector().remove();
  274. removeEventListener("mousemove", mousemoveListener, true);
  275. removeEventListener("click", clickListener, true);
  276. removeEventListener("keyup", keypressListener, true);
  277. selectedAreaElement = null;
  278. resolve(Boolean(selectedRanges.length));
  279. setTimeout(() => document.removeEventListener("contextmenu", contextmenuListener, true), 0);
  280. }
  281. function restoreSelectedRanges() {
  282. getSelection().removeAllRanges();
  283. selectedRanges.forEach(range => getSelection().addRange(range));
  284. }
  285. function saveSelectedRanges() {
  286. selectedRanges = [];
  287. for (let indexRange = 0; indexRange < getSelection().rangeCount; indexRange++) {
  288. const range = getSelection().getRangeAt(indexRange);
  289. selectedRanges.push(range);
  290. }
  291. }
  292. });
  293. }
  294. function getTarget(event) {
  295. let newTarget, target = event.target, boundingRect = target.getBoundingClientRect();
  296. newTarget = determineTargetElement("floor", target, event.clientX - boundingRect.left, getMatchedParents(target, "left"));
  297. if (newTarget == target) {
  298. newTarget = determineTargetElement("ceil", target, boundingRect.left + boundingRect.width - event.clientX, getMatchedParents(target, "right"));
  299. }
  300. if (newTarget == target) {
  301. newTarget = determineTargetElement("floor", target, event.clientY - boundingRect.top, getMatchedParents(target, "top"));
  302. }
  303. if (newTarget == target) {
  304. newTarget = determineTargetElement("ceil", target, boundingRect.top + boundingRect.height - event.clientY, getMatchedParents(target, "bottom"));
  305. }
  306. target = newTarget;
  307. while (target && target.clientWidth <= SELECT_PX_THRESHOLD && target.clientHeight <= SELECT_PX_THRESHOLD) {
  308. target = target.parentElement;
  309. }
  310. return target;
  311. }
  312. function moveAreaSelector(target) {
  313. requestAnimationFrame(() => {
  314. const selectorElement = getAreaSelector();
  315. const boundingRect = target.getBoundingClientRect();
  316. const scrollingElement = document.scrollingElement || document.documentElement;
  317. selectorElement.style.setProperty("top", (scrollingElement.scrollTop + boundingRect.top - 10) + "px");
  318. selectorElement.style.setProperty("left", (scrollingElement.scrollLeft + boundingRect.left - 10) + "px");
  319. selectorElement.style.setProperty("width", (boundingRect.width + 20) + "px");
  320. selectorElement.style.setProperty("height", (boundingRect.height + 20) + "px");
  321. });
  322. }
  323. function getAreaSelector() {
  324. let selectorElement = document.querySelector(SELECTION_ZONE_TAGNAME);
  325. if (!selectorElement) {
  326. selectorElement = createElement(SELECTION_ZONE_TAGNAME, document.body);
  327. selectorElement.style.setProperty("box-sizing", "border-box", "important");
  328. selectorElement.style.setProperty("background-color", "#3ea9d7", "important");
  329. selectorElement.style.setProperty("border", "10px solid #0b4892", "important");
  330. selectorElement.style.setProperty("border-radius", "2px", "important");
  331. selectorElement.style.setProperty("opacity", ".25", "important");
  332. selectorElement.style.setProperty("pointer-events", "none", "important");
  333. selectorElement.style.setProperty("position", "absolute", "important");
  334. selectorElement.style.setProperty("transition", "all 100ms", "important");
  335. selectorElement.style.setProperty("cursor", "pointer", "important");
  336. selectorElement.style.setProperty("z-index", "2147483647", "important");
  337. selectorElement.style.removeProperty("border-inline-end");
  338. selectorElement.style.removeProperty("border-inline-start");
  339. selectorElement.style.removeProperty("inline-size");
  340. selectorElement.style.removeProperty("block-size");
  341. selectorElement.style.removeProperty("inset-block-start");
  342. selectorElement.style.removeProperty("inset-inline-end");
  343. selectorElement.style.removeProperty("inset-block-end");
  344. selectorElement.style.removeProperty("inset-inline-start");
  345. }
  346. return selectorElement;
  347. }
  348. function createMaskElement() {
  349. let maskElement = document.querySelector(MASK_TAGNAME);
  350. if (!maskElement) {
  351. maskElement = createElement(MASK_TAGNAME);
  352. maskElement.style.setProperty("opacity", 0, "important");
  353. maskElement.style.setProperty("background-color", "transparent", "important");
  354. maskElement.style.setProperty("position", "fixed", "important");
  355. maskElement.style.setProperty("top", "0", "important");
  356. maskElement.style.setProperty("left", "0", "important");
  357. maskElement.style.setProperty("width", "100%", "important");
  358. maskElement.style.setProperty("height", "100%", "important");
  359. maskElement.style.setProperty("z-index", 2147483646, "important");
  360. maskElement.style.setProperty("transition", "opacity 250ms", "important");
  361. document.body.appendChild(maskElement);
  362. maskElement.offsetWidth;
  363. }
  364. return maskElement;
  365. }
  366. function createProgressBarElement(maskElement) {
  367. let progressBarElementContainer = document.querySelector(PROGRESS_BAR_TAGNAME);
  368. if (!progressBarElementContainer) {
  369. progressBarElementContainer = createElement(PROGRESS_BAR_TAGNAME, maskElement);
  370. const styleElement = document.createElement("style");
  371. styleElement.textContent = "@keyframes single-file-progress { 0% { left: -50px } 100% { left: 0 }";
  372. maskElement.appendChild(styleElement);
  373. progressBarElementContainer.style.setProperty("position", "fixed", "important");
  374. progressBarElementContainer.style.setProperty("top", "0", "important");
  375. progressBarElementContainer.style.setProperty("left", "0", "important");
  376. progressBarElementContainer.style.setProperty("width", "0", "important");
  377. progressBarElementContainer.style.setProperty("height", "8px", "important");
  378. progressBarElementContainer.style.setProperty("overflow", "hidden", "important");
  379. progressBarElementContainer.style.setProperty("transition", "width 200ms", "important");
  380. progressBarElementContainer.style.setProperty("will-change", "width", "important");
  381. const progressBarElement = createElement(PROGRESS_CURSOR_TAGNAME, progressBarElementContainer);
  382. progressBarElement.style.setProperty("position", "absolute", "important");
  383. progressBarElement.style.setProperty("left", "0");
  384. progressBarElement.style.setProperty("animation", "single-file-progress 5s linear infinite reverse", "important");
  385. progressBarElement.style.setProperty("background", "white linear-gradient(-45deg, rgba(0, 0, 0, 0.1) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.1) 50%, rgba(0, 0, 0, 0.1) 75%, transparent 75%, transparent) repeat scroll 0% 0% / 50px 50px padding-box border-box", "important");
  386. progressBarElement.style.setProperty("width", "calc(100% + 50px)", "important");
  387. progressBarElement.style.setProperty("height", "100%", "important");
  388. progressBarElement.style.setProperty("inset-inline-start", "auto");
  389. }
  390. return progressBarElementContainer;
  391. }
  392. function createLogsWindowElement() {
  393. let logsWindowElement = document.querySelector(LOGS_WINDOW_TAGNAME);
  394. if (!logsWindowElement) {
  395. logsWindowElement = document.createElement(LOGS_WINDOW_TAGNAME);
  396. logsWindowElement.className = SINGLE_FILE_UI_ELEMENT_CLASS;
  397. }
  398. const styleElement = document.createElement("style");
  399. logsWindowElement.appendChild(styleElement);
  400. styleElement.textContent = "@keyframes single-file-pulse { 0% { opacity: .5 } 100% { opacity: 1 }";
  401. return logsWindowElement;
  402. }
  403. function setLogsWindowStyle() {
  404. initStyle(logsWindowElement);
  405. logsWindowElement.style.setProperty("opacity", "0.9", "important");
  406. logsWindowElement.style.setProperty("padding", "4px", "important");
  407. logsWindowElement.style.setProperty("position", "fixed", "important");
  408. logsWindowElement.style.setProperty("bottom", "24px", "important");
  409. logsWindowElement.style.setProperty("left", "8px", "important");
  410. logsWindowElement.style.setProperty("z-index", 2147483647, "important");
  411. logsWindowElement.style.setProperty("background-color", "white", "important");
  412. logsWindowElement.style.setProperty("min-width", LOG_PANEL_WIDTH + "px", "important");
  413. logsWindowElement.style.setProperty("min-height", "16px", "important");
  414. logsWindowElement.style.setProperty("transition", "height 100ms", "important");
  415. logsWindowElement.style.setProperty("will-change", "height", "important");
  416. logsWindowElement.style.setProperty("inset-block", "auto");
  417. }
  418. function updateLog(id, textContent, textStatus, options) {
  419. if (options.logsEnabled) {
  420. let lineElement = logsWindowElement.querySelector("[data-id='" + id + "']");
  421. if (!lineElement) {
  422. lineElement = createElement(LOGS_LINE_TAGNAME, logsWindowElement);
  423. lineElement.setAttribute("data-id", id);
  424. lineElement.style.setProperty("display", "flex", "important");
  425. lineElement.style.setProperty("justify-content", "space-between", "important");
  426. lineElement.style.setProperty("padding", "2px", "important");
  427. const textElement = createElement(LOGS_LINE_ELEMENT_TAGNAME, lineElement);
  428. textElement.style.setProperty("font-size", "13px", "important");
  429. textElement.style.setProperty("font-family", "arial, sans-serif", "important");
  430. textElement.style.setProperty("color", "black", "important");
  431. textElement.style.setProperty("background-color", "white", "important");
  432. textElement.style.setProperty("opacity", "1", "important");
  433. textElement.style.setProperty("transition", "opacity 200ms", "important");
  434. textElement.textContent = textContent;
  435. const statusElement = createElement(LOGS_LINE_ELEMENT_TAGNAME, lineElement);
  436. statusElement.style.setProperty("font-size", "11px", "important");
  437. statusElement.style.setProperty("font-family", "arial, sans-serif", "important");
  438. statusElement.style.setProperty("color", "black", "important");
  439. statusElement.style.setProperty("background-color", "white", "important");
  440. statusElement.style.setProperty("min-width", "15px", "important");
  441. statusElement.style.setProperty("text-align", "center", "important");
  442. statusElement.style.setProperty("position", "relative", "important");
  443. statusElement.style.setProperty("top", "1px", "important");
  444. statusElement.style.setProperty("will-change", "opacity", "important");
  445. }
  446. updateLogLine(lineElement, textContent, textStatus);
  447. }
  448. }
  449. function updateLogLine(lineElement, textContent, textStatus) {
  450. const textElement = lineElement.childNodes[0];
  451. const statusElement = lineElement.childNodes[1];
  452. textElement.textContent = textContent;
  453. statusElement.style.setProperty("color", textStatus == "✓" ? "#055000" : "black", "important");
  454. if (textStatus == "✓") {
  455. textElement.style.setProperty("opacity", ".5", "important");
  456. statusElement.style.setProperty("animation", "none", "important");
  457. } else {
  458. statusElement.style.setProperty("opacity", ".5", "important");
  459. statusElement.style.setProperty("animation", "single-file-pulse 1s linear infinite alternate", "important");
  460. }
  461. statusElement.textContent = textStatus;
  462. }
  463. function clearLogs() {
  464. logsWindowElement = createLogsWindowElement();
  465. }
  466. function getMatchedParents(target, property) {
  467. let element = target, matchedParent, parents = [];
  468. do {
  469. const boundingRect = element.getBoundingClientRect();
  470. if (element.parentElement) {
  471. const parentBoundingRect = element.parentElement.getBoundingClientRect();
  472. matchedParent = Math.abs(parentBoundingRect[property] - boundingRect[property]) <= SELECT_PX_THRESHOLD;
  473. if (matchedParent) {
  474. if (element.parentElement.clientWidth > SELECT_PX_THRESHOLD && element.parentElement.clientHeight > SELECT_PX_THRESHOLD &&
  475. ((element.parentElement.clientWidth - element.clientWidth > SELECT_PX_THRESHOLD) || (element.parentElement.clientHeight - element.clientHeight > SELECT_PX_THRESHOLD))) {
  476. parents.push(element.parentElement);
  477. }
  478. element = element.parentElement;
  479. }
  480. } else {
  481. matchedParent = false;
  482. }
  483. } while (matchedParent && element);
  484. return parents;
  485. }
  486. function determineTargetElement(roundingMethod, target, widthDistance, parents) {
  487. if (Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) <= parents.length) {
  488. target = parents[parents.length - Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) - 1];
  489. }
  490. return target;
  491. }
  492. function createElement(tagName, parentElement) {
  493. const element = document.createElement(tagName);
  494. element.className = SINGLE_FILE_UI_ELEMENT_CLASS;
  495. if (parentElement) {
  496. parentElement.appendChild(element);
  497. }
  498. initStyle(element);
  499. return element;
  500. }
  501. function initStyle(element) {
  502. allProperties.forEach(property => element.style.setProperty(property, "initial", "important"));
  503. }
  504. })();