Facebook
From Bitty Madrill, 3 Years ago, written in Plain Text.
Embed
Download Paste or View Raw
Hits: 140
  1. /*
  2.  * Copyright (C) 2016-2017  Alex Yatskov <[email protected]>
  3.  * Author: Alex Yatskov <[email protected]>
  4.  *
  5.  * This program is free software: you can redistribute it and/or modify
  6.  * it under the terms of the GNU General Public License as published by
  7.  * the Free Software Foundation, either version 3 of the License, or
  8.  * (at your option) any later version.
  9.  *
  10.  * This program is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  * GNU General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License
  16.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17.  */
  18.  
  19.  
  20. const REGEX_TRANSPARENT_COLOR = /rgba\s*\([^\)]*,\s*0(?:\.0+)?\s*\)/;
  21.  
  22. function docSetImposterStyle(style, propertyName, value) {
  23.         style.setProperty(propertyName, value, 'important');
  24. }
  25.  
  26. function docImposterCreate(element, isTextarea) {
  27.         const elementStyle = window.getComputedStyle(element);
  28.         const elementRect = element.getBoundingClientRect();
  29.         const documentRect = document.documentElement.getBoundingClientRect();
  30.         const left = elementRect.left - documentRect.left;
  31.         const top = elementRect.top - documentRect.top;
  32.  
  33.         // Container
  34.         const container = document.createElement('div');
  35.         const containerStyle = container.style;
  36.         docSetImposterStyle(containerStyle, 'all', 'initial');
  37.         docSetImposterStyle(containerStyle, 'position', 'absolute');
  38.         docSetImposterStyle(containerStyle, 'left', '0');
  39.         docSetImposterStyle(containerStyle, 'top', '0');
  40.         docSetImposterStyle(containerStyle, 'width', `${documentRect.width}px`);
  41.         docSetImposterStyle(containerStyle, 'height', `${documentRect.height}px`);
  42.         docSetImposterStyle(containerStyle, 'overflow', 'hidden');
  43.         docSetImposterStyle(containerStyle, 'opacity', '0');
  44.  
  45.         docSetImposterStyle(containerStyle, 'pointer-events', 'none');
  46.         docSetImposterStyle(containerStyle, 'z-index', '2147483646');
  47.  
  48.         // Imposter
  49.         const imposter = document.createElement('div');
  50.         const imposterStyle = imposter.style;
  51.  
  52.         imposter.innerText = element.value;
  53.  
  54.         for (let i = 0, ii = elementStyle.length; i < ii; ++i) {
  55.                 const property = elementStyle[i];
  56.                 docSetImposterStyle(imposterStyle, property, elementStyle.getPropertyValue(property));
  57.         }
  58.         docSetImposterStyle(imposterStyle, 'position', 'absolute');
  59.         docSetImposterStyle(imposterStyle, 'top', `${top}px`);
  60.         docSetImposterStyle(imposterStyle, 'left', `${left}px`);
  61.         docSetImposterStyle(imposterStyle, 'margin', '0');
  62.         docSetImposterStyle(imposterStyle, 'pointer-events', 'auto');
  63.  
  64.         if (isTextarea) {
  65.                 if (elementStyle.overflow === 'visible') {
  66.                         docSetImposterStyle(imposterStyle, 'overflow', 'auto');
  67.                 }
  68.         } else {
  69.                 docSetImposterStyle(imposterStyle, 'overflow', 'hidden');
  70.                 docSetImposterStyle(imposterStyle, 'white-space', 'nowrap');
  71.                 docSetImposterStyle(imposterStyle, 'line-height', elementStyle.height);
  72.         }
  73.  
  74.         container.appendChild(imposter);
  75.         document.body.appendChild(container);
  76.  
  77.         // Adjust size
  78.         const imposterRect = imposter.getBoundingClientRect();
  79.         if (imposterRect.width !== elementRect.width || imposterRect.height !== elementRect.height) {
  80.                 const width = parseFloat(elementStyle.width) + (elementRect.width - imposterRect.width);
  81.                 const height = parseFloat(elementStyle.height) + (elementRect.height - imposterRect.height);
  82.                 docSetImposterStyle(imposterStyle, 'width', `${width}px`);
  83.                 docSetImposterStyle(imposterStyle, 'height', `${height}px`);
  84.         }
  85.  
  86.         imposter.scrollTop = element.scrollTop;
  87.         imposter.scrollLeft = element.scrollLeft;
  88.  
  89.         return [imposter, container];
  90. }
  91.  
  92. function docRangeFromPoint({x, y}, options) {
  93.         const elements = document.elementsFromPoint(x, y);
  94.         let imposter = null;
  95.         let imposterContainer = null;
  96.         if (elements.length > 0) {
  97.                 const element = elements[0];
  98.                 switch (element.nodeName) {
  99.                         case 'IMG':
  100.                         case 'BUTTON':
  101.                                 return new TextSourceElement(element);
  102.                         case 'INPUT':
  103.                                 [imposter, imposterContainer] = docImposterCreate(element, false);
  104.                                 break;
  105.                         case 'TEXTAREA':
  106.                                 [imposter, imposterContainer] = docImposterCreate(element, true);
  107.                                 break;
  108.                 }
  109.         }
  110.  
  111.         const range = caretRangeFromPointExt(x, y, options.scanning.deepDomScan ? elements : []);
  112.         if (range !== null) {
  113.                 if (imposter !== null) {
  114.                         docSetImposterStyle(imposterContainer.style, 'z-index', '-2147483646');
  115.                         docSetImposterStyle(imposter.style, 'pointer-events', 'none');
  116.                 }
  117.                 return new TextSourceRange(range, '', imposterContainer);
  118.         } else {
  119.                 if (imposterContainer !== null) {
  120.                         imposterContainer.parentNode.removeChild(imposterContainer);
  121.                 }
  122.                 return null;
  123.         }
  124. }
  125.  
  126. function docSentenceExtract(source, extent) {
  127.         //const quotesFwd = {'「': '」', '『': '』', "'": "'", '"': '"'};
  128.         //const quotesBwd = {'」': '「', '』': '『', "'": "'", '"': '"'};
  129.         const quotesFwd = {};
  130.         const quotesBwd = {};
  131.         const terminators = '。..??!!';
  132.  
  133.         const sourceLocal = source.clone();
  134.         const position = sourceLocal.setStartOffset(extent);
  135.         sourceLocal.setEndOffset(position + extent);
  136.         const content = sourceLocal.text();
  137.  
  138.         let quoteStack = [];
  139.         let noStart = 4;
  140.         let noEnd   = 2;
  141.  
  142.         let startPos = 0;
  143.         for (let i = position; i >= startPos; --i) {
  144.                 const c = content[i];
  145.  
  146.                 if (c === '\n') {
  147.                         if(noStart == 0) {
  148.                                 startPos = i + 1;
  149.                                 break;
  150.                         } else {
  151.                                 //noStart--;
  152.                         }
  153.                 }
  154.  
  155.                 if (quoteStack.length === 0 && (terminators.includes(c) || c in quotesFwd)) {
  156.                         if(noStart == 0) {
  157.                                 startPos = i + 1;
  158.                                 break;
  159.                         }
  160.                         else {
  161.                                 noStart--;
  162.                         }
  163.                 }
  164.  
  165.                 if (quoteStack.length > 0 && c === quoteStack[0]) {
  166.                         quoteStack.pop();
  167.                 } else if (c in quotesBwd) {
  168.                         quoteStack.unshift(quotesBwd[c]);
  169.                 }
  170.         }
  171.  
  172.         quoteStack = [];
  173.  
  174.         let endPos = content.length;
  175.         for (let i = position; i <= endPos; ++i) {
  176.                 const c = content[i];
  177.  
  178.                 if (c === '\n') {
  179.                         if (noEnd == 0) {
  180.                                 endPos = i + 1;
  181.                                 break;
  182.                         } else {
  183.                                 //noEnd--;
  184.                         }
  185.                 }
  186.  
  187.                 if (quoteStack.length === 0) {
  188.                         if (terminators.includes(c)) {
  189.                                 if(noEnd == 0) {
  190.                                         endPos = i + 1;
  191.                                         break;
  192.                                 } else {
  193.                                         noEnd--;
  194.                                 }
  195.                         }
  196.                         else if (c in quotesBwd) {
  197.                                 if(noEnd == 0){
  198.                                         endPos = i;
  199.                                         break;
  200.                                 } else {
  201.                                         noEnd--;
  202.                                 }
  203.                         }
  204.                 }
  205.  
  206.                 if (quoteStack.length > 0 && c === quoteStack[0]) {
  207.                         quoteStack.pop();
  208.                 } else if (c in quotesFwd) {
  209.                         quoteStack.unshift(quotesFwd[c]);
  210.                 }
  211.         }
  212.  
  213.         const text = content.substring(startPos, endPos);
  214.         const padding = text.length - text.replace(/^\s+/, '').length;
  215.  
  216.         return {
  217.                 text: text.trim(),
  218.                 offset: position - startPos - padding
  219.         };
  220. }
  221.  
  222. function isPointInRange(x, y, range) {
  223.         // Require a text node to start
  224.         if (range.startContainer.nodeType !== Node.TEXT_NODE) {
  225.                 return false;
  226.         }
  227.  
  228.         // Scan forward
  229.         const nodePre = range.endContainer;
  230.         const offsetPre = range.endOffset;
  231.         try {
  232.                 const {node, offset, content} = TextSourceRange.seekForward(range.endContainer, range.endOffset, 1);
  233.                 range.setEnd(node, offset);
  234.  
  235.                 if (!isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects())) {
  236.                         return true;
  237.                 }
  238.         } finally {
  239.                 range.setEnd(nodePre, offsetPre);
  240.         }
  241.  
  242.         // Scan backward
  243.         const {node, offset, content} = TextSourceRange.seekBackward(range.startContainer, range.startOffset, 1);
  244.         range.setStart(node, offset);
  245.  
  246.         if (!isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects())) {
  247.                 // This purposefully leaves the starting offset as modified and sets the range length to 0.
  248.                 range.setEnd(node, offset);
  249.                 return true;
  250.         }
  251.  
  252.         // No match
  253.         return false;
  254. }
  255.  
  256. function isWhitespace(string) {
  257.         return string.trim().length === 0;
  258. }
  259.  
  260. function isPointInAnyRect(x, y, rects) {
  261.         for (const rect of rects) {
  262.                 if (isPointInRect(x, y, rect)) {
  263.                         return true;
  264.                 }
  265.         }
  266.         return false;
  267. }
  268.  
  269. function isPointInRect(x, y, rect) {
  270.         return (
  271.                 x >= rect.left && x < rect.right &&
  272.                 y >= rect.top && y < rect.bottom);
  273. }
  274.  
  275. const caretRangeFromPoint = (() => {
  276.         if (typeof document.caretRangeFromPoint === 'function') {
  277.                 // Chrome, Edge
  278.                 return (x, y) => document.caretRangeFromPoint(x, y);
  279.         }
  280.  
  281.         if (typeof document.caretPositionFromPoint === 'function') {
  282.                 // Firefox
  283.                 return (x, y) => {
  284.                         const position = document.caretPositionFromPoint(x, y);
  285.                         const node = position.offsetNode;
  286.                         if (node === null) {
  287.                                 return null;
  288.                         }
  289.  
  290.                         const range = document.createRange();
  291.                         const offset = (node.nodeType === Node.TEXT_NODE ? position.offset : 0);
  292.                         range.setStart(node, offset);
  293.                         range.setEnd(node, offset);
  294.                         return range;
  295.                 };
  296.         }
  297.  
  298.         // No support
  299.         return () => null;
  300. })();
  301.  
  302. function caretRangeFromPointExt(x, y, elements) {
  303.         const modifications = [];
  304.         try {
  305.                 let i = 0;
  306.                 let startContinerPre = null;
  307.                 while (true) {
  308.                         const range = caretRangeFromPoint(x, y);
  309.                         if (range === null) {
  310.                                 return null;
  311.                         }
  312.  
  313.                         const startContainer = range.startContainer;
  314.                         if (startContinerPre !== startContainer) {
  315.                                 if (isPointInRange(x, y, range)) {
  316.                                         return range;
  317.                                 }
  318.                                 startContinerPre = startContainer;
  319.                         }
  320.  
  321.                         i = disableTransparentElement(elements, i, modifications);
  322.                         if (i < 0) {
  323.                                 return null;
  324.                         }
  325.                 }
  326.         } finally {
  327.                 if (modifications.length > 0) {
  328.                         restoreElementStyleModifications(modifications);
  329.                 }
  330.         }
  331. }
  332.  
  333. function disableTransparentElement(elements, i, modifications) {
  334.         while (true) {
  335.                 if (i >= elements.length) {
  336.                         return -1;
  337.                 }
  338.  
  339.                 const element = elements[i++];
  340.                 if (isElementTransparent(element)) {
  341.                         const style = element.hasAttribute('style') ? element.getAttribute('style') : null;
  342.                         modifications.push({element, style});
  343.                         element.style.pointerEvents = 'none';
  344.                         return i;
  345.                 }
  346.         }
  347. }
  348.  
  349. function restoreElementStyleModifications(modifications) {
  350.         for (const {element, style} of modifications) {
  351.                 if (style === null) {
  352.                         element.removeAttribute('style');
  353.                 } else {
  354.                         element.setAttribute('style', style);
  355.                 }
  356.         }
  357. }
  358.  
  359. function isElementTransparent(element) {
  360.         if (
  361.                 element === document.body ||
  362.                 element === document.documentElement
  363.         ) {
  364.                 return false;
  365.         }
  366.         const style = window.getComputedStyle(element);
  367.         return (
  368.                 parseFloat(style.opacity) < 0 ||
  369.                 style.visibility === 'hidden' ||
  370.                 (style.backgroundImage === 'none' && isColorTransparent(style.backgroundColor))
  371.         );
  372. }
  373.  
  374. function isColorTransparent(cssColor) {
  375.         return REGEX_TRANSPARENT_COLOR.test(cssColor);
  376. }
  377.