content-ui.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  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, globalThis, prompt, getComputedStyle, addEventListener, removeEventListener, requestAnimationFrame, setTimeout, getSelection, Node */
  24. const singlefile = globalThis.singlefile;
  25. const SELECTED_CONTENT_ATTRIBUTE_NAME = singlefile.helper.SELECTED_CONTENT_ATTRIBUTE_NAME;
  26. const MASK_TAGNAME = "singlefile-mask";
  27. const MASK_CONTENT_CLASSNAME = "singlefile-mask-content";
  28. const PROGRESSBAR_CLASSNAME = "singlefile-progress-bar";
  29. const PROGRESSBAR_CONTENT_CLASSNAME = "singlefile-progress-bar-content";
  30. const SELECTION_ZONE_TAGNAME = "single-file-selection-zone";
  31. const LOGS_WINDOW_TAGNAME = "singlefile-logs-window";
  32. const LOGS_CLASSNAME = "singlefile-logs";
  33. const LOGS_LINE_CLASSNAME = "singlefile-logs-line";
  34. const LOGS_LINE_TEXT_ELEMENT_CLASSNAME = "singlefile-logs-line-text";
  35. const LOGS_LINE_STATUS_ELEMENT_CLASSNAME = "singlefile-logs-line-icon";
  36. const SINGLE_FILE_UI_ELEMENT_CLASS = singlefile.helper.SINGLE_FILE_UI_ELEMENT_CLASS;
  37. const SELECT_PX_THRESHOLD = 8;
  38. const LOG_PANEL_DEFERRED_IMAGES_MESSAGE = browser.i18n.getMessage("logPanelDeferredImages");
  39. const LOG_PANEL_FRAME_CONTENTS_MESSAGE = browser.i18n.getMessage("logPanelFrameContents");
  40. const LOG_PANEL_STEP_MESSAGE = browser.i18n.getMessage("logPanelStep");
  41. const LOG_PANEL_WIDTH = browser.i18n.getMessage("logPanelWidth");
  42. const CSS_PROPERTIES = new Set(Array.from(getComputedStyle(document.documentElement)));
  43. let selectedAreaElement, logsWindowElement;
  44. createLogsWindowElement();
  45. export {
  46. getSelectedLinks,
  47. markSelection,
  48. unmarkSelection,
  49. promptMessage as prompt,
  50. onStartPage,
  51. onEndPage,
  52. onLoadResource,
  53. onLoadingDeferResources,
  54. onLoadDeferResources,
  55. onLoadingFrames,
  56. onLoadFrames,
  57. onStartStage,
  58. onEndStage,
  59. onPageLoading,
  60. onLoadPage,
  61. onStartStageTask,
  62. onEndStageTask
  63. };
  64. function promptMessage(message, defaultValue) {
  65. return prompt(message, defaultValue);
  66. }
  67. function onStartPage(options) {
  68. let maskElement = document.querySelector(MASK_TAGNAME);
  69. if (!maskElement) {
  70. if (options.logsEnabled) {
  71. document.body.appendChild(logsWindowElement);
  72. }
  73. if (options.shadowEnabled) {
  74. const maskElement = createMaskElement();
  75. if (options.progressBarEnabled) {
  76. createProgressBarElement(maskElement);
  77. }
  78. }
  79. }
  80. }
  81. function onEndPage() {
  82. const maskElement = document.querySelector(MASK_TAGNAME);
  83. if (maskElement) {
  84. maskElement.remove();
  85. }
  86. logsWindowElement.remove();
  87. clearLogs();
  88. }
  89. function onLoadResource(index, maxIndex, options) {
  90. if (options.shadowEnabled && options.progressBarEnabled) {
  91. updateProgressBar(index, maxIndex);
  92. }
  93. }
  94. function onLoadingDeferResources(options) {
  95. updateLog("load-deferred-images", LOG_PANEL_DEFERRED_IMAGES_MESSAGE, "…", options);
  96. }
  97. function onLoadDeferResources(options) {
  98. updateLog("load-deferred-images", LOG_PANEL_DEFERRED_IMAGES_MESSAGE, "✓", options);
  99. }
  100. function onLoadingFrames(options) {
  101. updateLog("load-frames", LOG_PANEL_FRAME_CONTENTS_MESSAGE, "…", options);
  102. }
  103. function onLoadFrames(options) {
  104. updateLog("load-frames", LOG_PANEL_FRAME_CONTENTS_MESSAGE, "✓", options);
  105. }
  106. function onStartStage(step, options) {
  107. updateLog("step-" + step, `${LOG_PANEL_STEP_MESSAGE} ${step + 1} / 3`, "…", options);
  108. }
  109. function onEndStage(step, options) {
  110. updateLog("step-" + step, `${LOG_PANEL_STEP_MESSAGE} ${step + 1} / 3`, "✓", options);
  111. }
  112. function onPageLoading() { }
  113. function onLoadPage() { }
  114. function onStartStageTask() { }
  115. function onEndStageTask() { }
  116. function getSelectedLinks() {
  117. let selectionFound;
  118. const links = [];
  119. const selection = getSelection();
  120. for (let indexRange = 0; indexRange < selection.rangeCount; indexRange++) {
  121. let range = selection.getRangeAt(indexRange);
  122. if (range && range.commonAncestorContainer) {
  123. const treeWalker = document.createTreeWalker(range.commonAncestorContainer);
  124. let rangeSelectionFound = false;
  125. let finished = false;
  126. while (!finished) {
  127. if (rangeSelectionFound || treeWalker.currentNode == range.startContainer || treeWalker.currentNode == range.endContainer) {
  128. rangeSelectionFound = true;
  129. if (range.startContainer != range.endContainer || range.startOffset != range.endOffset) {
  130. selectionFound = true;
  131. if (treeWalker.currentNode.tagName == "A" && treeWalker.currentNode.href) {
  132. links.push(treeWalker.currentNode.href);
  133. }
  134. }
  135. }
  136. if (treeWalker.currentNode == range.endContainer) {
  137. finished = true;
  138. } else {
  139. treeWalker.nextNode();
  140. }
  141. }
  142. if (selectionFound && treeWalker.currentNode == range.endContainer && treeWalker.currentNode.querySelectorAll) {
  143. treeWalker.currentNode.querySelectorAll("*").forEach(descendantElement => {
  144. if (descendantElement.tagName == "A" && descendantElement.href) {
  145. links.push(treeWalker.currentNode.href);
  146. }
  147. });
  148. }
  149. }
  150. }
  151. return Array.from(new Set(links));
  152. }
  153. async function markSelection(optionallySelected) {
  154. let selectionFound = markSelectedContent();
  155. if (selectionFound || optionallySelected) {
  156. return selectionFound;
  157. } else {
  158. selectionFound = await selectArea();
  159. if (selectionFound) {
  160. return markSelectedContent();
  161. }
  162. }
  163. }
  164. function markSelectedContent() {
  165. const selection = getSelection();
  166. let selectionFound;
  167. for (let indexRange = 0; indexRange < selection.rangeCount; indexRange++) {
  168. let range = selection.getRangeAt(indexRange);
  169. if (range && range.commonAncestorContainer) {
  170. const treeWalker = document.createTreeWalker(range.commonAncestorContainer);
  171. let rangeSelectionFound = false;
  172. let finished = false;
  173. while (!finished) {
  174. if (rangeSelectionFound || treeWalker.currentNode == range.startContainer || treeWalker.currentNode == range.endContainer) {
  175. rangeSelectionFound = true;
  176. if (range.startContainer != range.endContainer || range.startOffset != range.endOffset) {
  177. selectionFound = true;
  178. markSelectedNode(treeWalker.currentNode);
  179. }
  180. }
  181. if (selectionFound && treeWalker.currentNode == range.startContainer) {
  182. markSelectedParents(treeWalker.currentNode);
  183. }
  184. if (treeWalker.currentNode == range.endContainer) {
  185. finished = true;
  186. } else {
  187. treeWalker.nextNode();
  188. }
  189. }
  190. if (selectionFound && treeWalker.currentNode == range.endContainer && treeWalker.currentNode.querySelectorAll) {
  191. treeWalker.currentNode.querySelectorAll("*").forEach(descendantElement => markSelectedNode(descendantElement));
  192. }
  193. }
  194. }
  195. return selectionFound;
  196. }
  197. function markSelectedNode(node) {
  198. const element = node.nodeType == Node.ELEMENT_NODE ? node : node.parentElement;
  199. element.setAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME, "");
  200. }
  201. function markSelectedParents(node) {
  202. if (node.parentElement) {
  203. markSelectedNode(node);
  204. markSelectedParents(node.parentElement);
  205. }
  206. }
  207. function unmarkSelection() {
  208. document.querySelectorAll("[" + SELECTED_CONTENT_ATTRIBUTE_NAME + "]").forEach(selectedContent => selectedContent.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME));
  209. }
  210. function selectArea() {
  211. return new Promise(resolve => {
  212. let selectedRanges = [];
  213. addEventListener("mousemove", mousemoveListener, true);
  214. addEventListener("click", clickListener, true);
  215. addEventListener("keyup", keypressListener, true);
  216. document.addEventListener("contextmenu", contextmenuListener, true);
  217. getSelection().removeAllRanges();
  218. function contextmenuListener(event) {
  219. selectedRanges = [];
  220. select();
  221. event.preventDefault();
  222. }
  223. function mousemoveListener(event) {
  224. const targetElement = getTarget(event);
  225. if (targetElement) {
  226. selectedAreaElement = targetElement;
  227. moveAreaSelector(targetElement);
  228. }
  229. }
  230. function clickListener(event) {
  231. event.preventDefault();
  232. event.stopPropagation();
  233. if (event.button == 0) {
  234. select(selectedAreaElement, event.ctrlKey);
  235. } else {
  236. cancel();
  237. }
  238. }
  239. function keypressListener(event) {
  240. if (event.key == "Escape") {
  241. cancel();
  242. }
  243. }
  244. function cancel() {
  245. if (selectedRanges.length) {
  246. getSelection().removeAllRanges();
  247. }
  248. selectedRanges = [];
  249. cleanupAndResolve();
  250. }
  251. function select(selectedElement, multiSelect) {
  252. if (selectedElement) {
  253. if (!multiSelect) {
  254. restoreSelectedRanges();
  255. }
  256. const range = document.createRange();
  257. range.selectNodeContents(selectedElement);
  258. cleanupSelectionRanges();
  259. getSelection().addRange(range);
  260. saveSelectedRanges();
  261. if (!multiSelect) {
  262. cleanupAndResolve();
  263. }
  264. } else {
  265. cleanupAndResolve();
  266. }
  267. }
  268. function cleanupSelectionRanges() {
  269. const selection = getSelection();
  270. for (let indexRange = selection.rangeCount - 1; indexRange >= 0; indexRange--) {
  271. const range = selection.getRangeAt(indexRange);
  272. if (range.startOffset == range.endOffset) {
  273. selection.removeRange(range);
  274. indexRange--;
  275. }
  276. }
  277. }
  278. function cleanupAndResolve() {
  279. getAreaSelector().remove();
  280. removeEventListener("mousemove", mousemoveListener, true);
  281. removeEventListener("click", clickListener, true);
  282. removeEventListener("keyup", keypressListener, true);
  283. selectedAreaElement = null;
  284. resolve(Boolean(selectedRanges.length));
  285. setTimeout(() => document.removeEventListener("contextmenu", contextmenuListener, true), 0);
  286. }
  287. function restoreSelectedRanges() {
  288. getSelection().removeAllRanges();
  289. selectedRanges.forEach(range => getSelection().addRange(range));
  290. }
  291. function saveSelectedRanges() {
  292. selectedRanges = [];
  293. for (let indexRange = 0; indexRange < getSelection().rangeCount; indexRange++) {
  294. const range = getSelection().getRangeAt(indexRange);
  295. selectedRanges.push(range);
  296. }
  297. }
  298. });
  299. }
  300. function getTarget(event) {
  301. let newTarget, target = event.target, boundingRect = target.getBoundingClientRect();
  302. newTarget = determineTargetElement("floor", target, event.clientX - boundingRect.left, getMatchedParents(target, "left"));
  303. if (newTarget == target) {
  304. newTarget = determineTargetElement("ceil", target, boundingRect.left + boundingRect.width - event.clientX, getMatchedParents(target, "right"));
  305. }
  306. if (newTarget == target) {
  307. newTarget = determineTargetElement("floor", target, event.clientY - boundingRect.top, getMatchedParents(target, "top"));
  308. }
  309. if (newTarget == target) {
  310. newTarget = determineTargetElement("ceil", target, boundingRect.top + boundingRect.height - event.clientY, getMatchedParents(target, "bottom"));
  311. }
  312. target = newTarget;
  313. while (target && target.clientWidth <= SELECT_PX_THRESHOLD && target.clientHeight <= SELECT_PX_THRESHOLD) {
  314. target = target.parentElement;
  315. }
  316. return target;
  317. }
  318. function moveAreaSelector(target) {
  319. requestAnimationFrame(() => {
  320. const selectorElement = getAreaSelector();
  321. const boundingRect = target.getBoundingClientRect();
  322. const scrollingElement = document.scrollingElement || document.documentElement;
  323. selectorElement.style.setProperty("top", (scrollingElement.scrollTop + boundingRect.top - 10) + "px");
  324. selectorElement.style.setProperty("left", (scrollingElement.scrollLeft + boundingRect.left - 10) + "px");
  325. selectorElement.style.setProperty("width", (boundingRect.width + 20) + "px");
  326. selectorElement.style.setProperty("height", (boundingRect.height + 20) + "px");
  327. });
  328. }
  329. function getAreaSelector() {
  330. let selectorElement = document.querySelector(SELECTION_ZONE_TAGNAME);
  331. if (!selectorElement) {
  332. selectorElement = createElement(SELECTION_ZONE_TAGNAME, document.body);
  333. selectorElement.style.setProperty("box-sizing", "border-box", "important");
  334. selectorElement.style.setProperty("background-color", "#3ea9d7", "important");
  335. selectorElement.style.setProperty("border", "10px solid #0b4892", "important");
  336. selectorElement.style.setProperty("border-radius", "2px", "important");
  337. selectorElement.style.setProperty("opacity", ".25", "important");
  338. selectorElement.style.setProperty("pointer-events", "none", "important");
  339. selectorElement.style.setProperty("position", "absolute", "important");
  340. selectorElement.style.setProperty("transition", "all 100ms", "important");
  341. selectorElement.style.setProperty("cursor", "pointer", "important");
  342. selectorElement.style.setProperty("z-index", "2147483647", "important");
  343. selectorElement.style.removeProperty("border-inline-end");
  344. selectorElement.style.removeProperty("border-inline-start");
  345. selectorElement.style.removeProperty("inline-size");
  346. selectorElement.style.removeProperty("block-size");
  347. selectorElement.style.removeProperty("inset-block-start");
  348. selectorElement.style.removeProperty("inset-inline-end");
  349. selectorElement.style.removeProperty("inset-block-end");
  350. selectorElement.style.removeProperty("inset-inline-start");
  351. }
  352. return selectorElement;
  353. }
  354. function createMaskElement() {
  355. try {
  356. let maskElement = document.querySelector(MASK_TAGNAME);
  357. if (!maskElement) {
  358. maskElement = createElement(MASK_TAGNAME, document.body);
  359. const shadowRoot = maskElement.attachShadow({ mode: "open" });
  360. const styleElement = document.createElement("style");
  361. styleElement.textContent = `
  362. @keyframes single-file-progress {
  363. 0% {
  364. left: -50px;
  365. }
  366. 100% {
  367. left: 0;
  368. }
  369. }
  370. .${PROGRESSBAR_CLASSNAME} {
  371. position: fixed;
  372. top: 0;
  373. left: 0;
  374. width: 0;
  375. height: 8px;
  376. z-index: 2147483646;
  377. opacity: .5;
  378. overflow: hidden;
  379. transition: width 200ms ease-in-out;
  380. }
  381. .${PROGRESSBAR_CONTENT_CLASSNAME} {
  382. position: absolute;
  383. left: 0;
  384. animation: single-file-progress 3s linear infinite reverse;
  385. background:
  386. white
  387. linear-gradient(-45deg, rgba(0, 0, 0, 0.075) 25%,
  388. transparent 25%,
  389. transparent 50%,
  390. rgba(0, 0, 0, 0.075) 50%,
  391. rgba(0, 0, 0, 0.075) 75%,
  392. transparent 75%, transparent)
  393. repeat scroll 0% 0% / 50px 50px padding-box border-box;
  394. width: calc(100% + 50px);
  395. height: 100%;
  396. }
  397. .${MASK_CONTENT_CLASSNAME} {
  398. position: fixed;
  399. top: 0;
  400. left: 0;
  401. width: 100%;
  402. height: 100%;
  403. z-index: 2147483646;
  404. opacity: 0;
  405. background-color: black;
  406. transition: opacity 250ms;
  407. }
  408. `;
  409. shadowRoot.appendChild(styleElement);
  410. let maskElementContent = document.createElement("div");
  411. maskElementContent.classList.add(MASK_CONTENT_CLASSNAME);
  412. shadowRoot.appendChild(maskElementContent);
  413. maskElement.offsetWidth;
  414. maskElementContent.style.setProperty("opacity", .3);
  415. maskElement.offsetWidth;
  416. }
  417. return maskElement;
  418. } catch (error) {
  419. // ignored
  420. }
  421. }
  422. function createProgressBarElement(maskElement) {
  423. try {
  424. let progressBarElement = maskElement.shadowRoot.querySelector("." + PROGRESSBAR_CLASSNAME);
  425. if (!progressBarElement) {
  426. let progressBarContent = document.createElement("div");
  427. progressBarContent.classList.add(PROGRESSBAR_CLASSNAME);
  428. maskElement.shadowRoot.appendChild(progressBarContent);
  429. const progressBarContentElement = document.createElement("div");
  430. progressBarContentElement.classList.add(PROGRESSBAR_CONTENT_CLASSNAME);
  431. progressBarContent.appendChild(progressBarContentElement);
  432. }
  433. } catch (error) {
  434. // ignored
  435. }
  436. }
  437. function createLogsWindowElement() {
  438. try {
  439. logsWindowElement = document.querySelector(LOGS_WINDOW_TAGNAME);
  440. if (!logsWindowElement) {
  441. logsWindowElement = createElement(LOGS_WINDOW_TAGNAME);
  442. const shadowRoot = logsWindowElement.attachShadow({ mode: "open" });
  443. const styleElement = document.createElement("style");
  444. styleElement.textContent = `
  445. @keyframes single-file-pulse {
  446. 0% {
  447. opacity: .25;
  448. }
  449. 100% {
  450. opacity: 1;
  451. }
  452. }
  453. .${LOGS_CLASSNAME} {
  454. position: fixed;
  455. bottom: 24px;
  456. left: 8px;
  457. z-index: 2147483647;
  458. opacity: 0.9;
  459. padding: 4px;
  460. background-color: white;
  461. min-width: ${LOG_PANEL_WIDTH}px;
  462. min-height: 16px;
  463. transition: height 100ms;
  464. }
  465. .${LOGS_LINE_CLASSNAME} {
  466. display: flex;
  467. justify-content: space-between;
  468. padding: 2px;
  469. font-family: arial, sans-serif;
  470. color: black;
  471. background-color: white;
  472. }
  473. .${LOGS_LINE_TEXT_ELEMENT_CLASSNAME} {
  474. font-size: 13px;
  475. opacity: 1;
  476. transition: opacity 200ms;
  477. }
  478. .${LOGS_LINE_STATUS_ELEMENT_CLASSNAME} {
  479. font-size: 11px;
  480. min-width: 15px;
  481. text-align: center;
  482. position: relative;
  483. top: 1px;
  484. }
  485. `;
  486. shadowRoot.appendChild(styleElement);
  487. const logsContentElement = document.createElement("div");
  488. logsContentElement.classList.add(LOGS_CLASSNAME);
  489. shadowRoot.appendChild(logsContentElement);
  490. }
  491. } catch (error) {
  492. // ignored
  493. }
  494. }
  495. function updateLog(id, textContent, textStatus, options) {
  496. try {
  497. if (options.logsEnabled) {
  498. const logsContentElement = logsWindowElement.shadowRoot.querySelector("." + LOGS_CLASSNAME);
  499. let lineElement = logsContentElement.querySelector("[data-id='" + id + "']");
  500. if (!lineElement) {
  501. lineElement = document.createElement("div");
  502. lineElement.classList.add(LOGS_LINE_CLASSNAME);
  503. logsContentElement.appendChild(lineElement);
  504. lineElement.setAttribute("data-id", id);
  505. const textElement = document.createElement("div");
  506. textElement.classList.add(LOGS_LINE_TEXT_ELEMENT_CLASSNAME);
  507. lineElement.appendChild(textElement);
  508. textElement.textContent = textContent;
  509. const statusElement = document.createElement("div");
  510. statusElement.classList.add(LOGS_LINE_STATUS_ELEMENT_CLASSNAME);
  511. lineElement.appendChild(statusElement);
  512. }
  513. updateLogLine(lineElement, textContent, textStatus);
  514. }
  515. } catch (error) {
  516. // ignored
  517. }
  518. }
  519. function updateLogLine(lineElement, textContent, textStatus) {
  520. const textElement = lineElement.childNodes[0];
  521. const statusElement = lineElement.childNodes[1];
  522. textElement.textContent = textContent;
  523. statusElement.style.setProperty("color", textStatus == "✓" ? "#055000" : "black");
  524. if (textStatus == "✓") {
  525. textElement.style.setProperty("opacity", ".5");
  526. statusElement.style.setProperty("opacity", ".5");
  527. statusElement.style.setProperty("animation", "none");
  528. } else {
  529. statusElement.style.setProperty("animation", "1s ease-in-out 0s infinite alternate none running single-file-pulse");
  530. }
  531. statusElement.textContent = textStatus;
  532. }
  533. function updateProgressBar(index, maxIndex) {
  534. try {
  535. const maskElement = document.querySelector(MASK_TAGNAME);
  536. if (maskElement) {
  537. const progressBarElement = maskElement.shadowRoot.querySelector("." + PROGRESSBAR_CLASSNAME);
  538. if (progressBarElement && maxIndex) {
  539. const width = Math.floor((index / maxIndex) * 100) + "%";
  540. if (progressBarElement.style.getPropertyValue("width") != width) {
  541. progressBarElement.style.setProperty("width", width);
  542. progressBarElement.offsetWidth;
  543. }
  544. }
  545. }
  546. } catch (error) {
  547. // ignored
  548. }
  549. }
  550. function clearLogs() {
  551. createLogsWindowElement();
  552. }
  553. function getMatchedParents(target, property) {
  554. let element = target, matchedParent, parents = [];
  555. do {
  556. const boundingRect = element.getBoundingClientRect();
  557. if (element.parentElement) {
  558. const parentBoundingRect = element.parentElement.getBoundingClientRect();
  559. matchedParent = Math.abs(parentBoundingRect[property] - boundingRect[property]) <= SELECT_PX_THRESHOLD;
  560. if (matchedParent) {
  561. if (element.parentElement.clientWidth > SELECT_PX_THRESHOLD && element.parentElement.clientHeight > SELECT_PX_THRESHOLD &&
  562. ((element.parentElement.clientWidth - element.clientWidth > SELECT_PX_THRESHOLD) || (element.parentElement.clientHeight - element.clientHeight > SELECT_PX_THRESHOLD))) {
  563. parents.push(element.parentElement);
  564. }
  565. element = element.parentElement;
  566. }
  567. } else {
  568. matchedParent = false;
  569. }
  570. } while (matchedParent && element);
  571. return parents;
  572. }
  573. function determineTargetElement(roundingMethod, target, widthDistance, parents) {
  574. if (Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) <= parents.length) {
  575. target = parents[parents.length - Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) - 1];
  576. }
  577. return target;
  578. }
  579. function createElement(tagName, parentElement) {
  580. const element = document.createElement(tagName);
  581. element.className = SINGLE_FILE_UI_ELEMENT_CLASS;
  582. if (parentElement) {
  583. parentElement.appendChild(element);
  584. }
  585. CSS_PROPERTIES.forEach(property => element.style.setProperty(property, "initial", "important"));
  586. return element;
  587. }