content-ui.js 22 KB

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