diff --git a/qutebrowser/javascript/webengine_caret.js b/qutebrowser/javascript/webengine_caret.js index 8390174a5..e6f71d44f 100644 --- a/qutebrowser/javascript/webengine_caret.js +++ b/qutebrowser/javascript/webengine_caret.js @@ -1,1075 +1,6 @@ -"use strict"; - -window._qutebrowser.caret = (function() { - const axs = {}; - - axs.dom = {}; - - axs.color = {}; - - axs.utils = {}; - - axs.dom.parentElement = function(a) { - if (!a) { - return null; - } - a = axs.dom.composedParentNode(a); - if (!a) { - return null; - } - switch(a.nodeType) { - case Node.ELEMENT_NODE: - return a; - default: - return axs.dom.parentElement(a); - } - }; - - axs.dom.shadowHost = function(a) { - return "host" in a ? a.host : null; - }; - - axs.dom.composedParentNode = function(a) { - if (!a) { - return null; - } - if (a.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { - return axs.dom.shadowHost(a); - } - var b = a.parentNode; - if (!b) { - return null; - } - if (b.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { - return axs.dom.shadowHost(b); - } - if (!b.shadowRoot) { - return b; - } - a = a.getDestinationInsertionPoints(); - return 0 < a.length ? axs.dom.composedParentNode(a[a.length - 1]) : null; - }; - - axs.color.Color = function(a, b, c, d) { - this.red = a; - this.green = b; - this.blue = c; - this.alpha = d; - }; - - axs.color.parseColor = function(a) { - if ("transparent" === a) { - return new axs.color.Color(0, 0, 0, 0); - } - var b = a.match(/^rgb\((\d+), (\d+), (\d+)\)$/); - if (b) { - a = parseInt(b[1], 10); - var c = parseInt(b[2], 10), d = parseInt(b[3], 10); - return new axs.color.Color(a, c, d, 1); - } - return (b = a.match(/^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/)) ? (a = parseInt(b[1], 10), c = parseInt(b[2], 10), d = parseInt(b[3], 10), b = parseFloat(b[4]), new axs.color.Color(a, c, d, b)) : null; - }; - - axs.color.flattenColors = function(a, b) { - var c = a.alpha; - return new axs.color.Color((1 - c) * b.red + c * a.red, (1 - c) * b.green + c * a.green, (1 - c) * b.blue + c * a.blue, a.alpha + b.alpha * (1 - a.alpha)); - }; - - axs.utils.getParentBgColor = function(a) { - var b = a; - a = []; - for (var c = null;b = axs.dom.parentElement(b);) { - var d = window.getComputedStyle(b, null); - if (d) { - var e = axs.color.parseColor(d.backgroundColor); - if (e && (1 > d.opacity && (e.alpha *= d.opacity), 0 != e.alpha && (a.push(e), 1 == e.alpha))) { - c = !0; - break; - } - } - } - c || a.push(new axs.color.Color(255, 255, 255, 1)); - for (b = a.pop();a.length;) { - c = a.pop(), b = axs.color.flattenColors(c, b); - } - return b; - }; - - axs.utils.getFgColor = function(a, b, c) { - var d = axs.color.parseColor(a.color); - if (!d) { - return null; - } - 1 > d.alpha && (d = axs.color.flattenColors(d, c)); - 1 > a.opacity && (b = axs.utils.getParentBgColor(b), d.alpha *= a.opacity, d = axs.color.flattenColors(d, b)); - return d; - }; - - axs.utils.getBgColor = function(a, b) { - var c = axs.color.parseColor(a.backgroundColor); - if (!c) { - return null; - } - 1 > a.opacity && (c.alpha *= a.opacity); - if (1 > c.alpha) { - var d = axs.utils.getParentBgColor(b); - if (null == d) { - return null; - } - c = axs.color.flattenColors(c, d); - } - return c; - }; - - axs.color.colorChannelToString = function(a) { - a = Math.round(a); - return 15 >= a ? "0" + a.toString(16) : a.toString(16); - }; - - axs.color.colorToString = function(a) { - return 1 == a.alpha ? "#" + axs.color.colorChannelToString(a.red) + axs.color.colorChannelToString(a.green) + axs.color.colorChannelToString(a.blue) : "rgba(" + [a.red, a.green, a.blue, a.alpha].join() + ")"; - }; - - var Cursor = function(node, index, text) { - this.node = node; - this.index = index; - this.text = text; - }; - - /** - * @return {Cursor} A new cursor pointing to the same location. - */ - Cursor.prototype.clone = function() { - return new Cursor(this.node, this.index, this.text); - }; - - /** - * Modify this cursor to point to the location that another cursor points to. - * @param {Cursor} otherCursor The cursor to copy from. - */ - Cursor.prototype.copyFrom = function(otherCursor) { - this.node = otherCursor.node; - this.index = otherCursor.index; - this.text = otherCursor.text; - }; - - var TraverseUtil = function() {}; - - TraverseUtil.getNodeText = function(node) { - if (node.constructor == Text) { - return node.data; - } else { - return ''; - } - }; - - /** - * Return true if a node should be treated as a leaf node, because - * its children are properties of the object that shouldn't be traversed. - * - * TODO(dmazzoni): replace this with a predicate that detects nodes with - * ARIA roles and other objects that have their own description. - * For now we just detect a couple of common cases. - * - * @param {Node} node A DOM node. - * @return {boolean} True if the node should be treated as a leaf node. - */ - TraverseUtil.treatAsLeafNode = function(node) { - return node.childNodes.length == 0 || - node.nodeName == 'SELECT' || - node.nodeName == 'OBJECT'; - }; - - /** - * Return true only if a single character is whitespace. - * From https://developer.mozilla.org/en/Whitespace_in_the_DOM, - * whitespace is defined as one of the characters - * "\t" TAB \u0009 - * "\n" LF \u000A - * "\r" CR \u000D - * " " SPC \u0020. - * - * @param {string} c A string containing a single character. - * @return {boolean} True if the character is whitespace, otherwise false. - */ - TraverseUtil.isWhitespace = function(c) { - return (c == ' ' || c == '\n' || c == '\r' || c == '\t'); - }; - - /** - * Use the computed CSS style to figure out if this DOM node is currently - * visible. - * @param {Node} node A HTML DOM node. - * @return {boolean} Whether or not the html node is visible. - */ - TraverseUtil.isVisible = function(node) { - if (!node.style) - return true; - var style = window.getComputedStyle(/** @type {Element} */(node), null); - return (!!style && style.display != 'none' && style.visibility != 'hidden'); - }; - - /** - * Use the class name to figure out if this DOM node should be traversed. - * @param {Node} node A HTML DOM node. - * @return {boolean} Whether or not the html node should be traversed. - */ - TraverseUtil.isSkipped = function(node) { - if (node.constructor == Text) - node = node.parentElement; - if (node.className == 'CaretBrowsing_Caret' || - node.className == 'CaretBrowsing_AnimateCaret') { - return true; - } - return false; - }; - - /** - * Moves the cursor forwards until it has crossed exactly one character. - * @param {Cursor} cursor The cursor location where the search should start. - * On exit, the cursor will be immediately to the right of the - * character returned. - * @param {Array} nodesCrossed Any HTML nodes crossed between the - * initial and final cursor position will be pushed onto this array. - * @return {?string} The character found, or null if the bottom of the - * document has been reached. - */ - TraverseUtil.forwardsChar = function(cursor, nodesCrossed) { - while (true) { - // Move down until we get to a leaf node. - var childNode = null; - if (!TraverseUtil.treatAsLeafNode(cursor.node)) { - for (var i = cursor.index; i < cursor.node.childNodes.length; i++) { - var node = cursor.node.childNodes[i]; - if (TraverseUtil.isSkipped(node)) { - nodesCrossed.push(node); - continue; - } - if (TraverseUtil.isVisible(node)) { - childNode = node; - break; - } - } - } - if (childNode) { - cursor.node = childNode; - cursor.index = 0; - cursor.text = TraverseUtil.getNodeText(cursor.node); - if (cursor.node.constructor != Text) { - nodesCrossed.push(cursor.node); - } - continue; - } - - // Return the next character from this leaf node. - if (cursor.index < cursor.text.length) - return cursor.text[cursor.index++]; - - // Move to the next sibling, going up the tree as necessary. - while (cursor.node != null) { - // Try to move to the next sibling. - var siblingNode = null; - for (var node = cursor.node.nextSibling; - node != null; - node = node.nextSibling) { - if (TraverseUtil.isSkipped(node)) { - nodesCrossed.push(node); - continue; - } - if (TraverseUtil.isVisible(node)) { - siblingNode = node; - break; - } - } - if (siblingNode) { - cursor.node = siblingNode; - cursor.text = TraverseUtil.getNodeText(siblingNode); - cursor.index = 0; - - if (cursor.node.constructor != Text) { - nodesCrossed.push(cursor.node); - } - - break; - } - - // Otherwise, move to the parent. - if (cursor.node.parentNode && - cursor.node.parentNode.constructor != HTMLBodyElement) { - cursor.node = cursor.node.parentNode; - cursor.text = null; - cursor.index = 0; - } else { - return null; - } - } - } - }; - - /** - * Finds the next character, starting from endCursor. Upon exit, startCursor - * and endCursor will surround the next character. If skipWhitespace is - * true, will skip until a real character is found. Otherwise, it will - * attempt to select all of the whitespace between the initial position - * of endCursor and the next non-whitespace character. - * @param {Cursor} startCursor On exit, points to the position before - * the char. - * @param {Cursor} endCursor The position to start searching for the next - * char. On exit, will point to the position past the char. - * @param {Array} nodesCrossed Any HTML nodes crossed between the - * initial and final cursor position will be pushed onto this array. - * @param {boolean} skipWhitespace If true, will keep scanning until a - * non-whitespace character is found. - * @return {?string} The next char, or null if the bottom of the - * document has been reached. - */ - TraverseUtil.getNextChar = function( - startCursor, endCursor, nodesCrossed, skipWhitespace) { - - // Save the starting position and get the first character. - startCursor.copyFrom(endCursor); - var c = TraverseUtil.forwardsChar(endCursor, nodesCrossed); - if (c == null) - return null; - - // Keep track of whether the first character was whitespace. - var initialWhitespace = TraverseUtil.isWhitespace(c); - - // Keep scanning until we find a non-whitespace or non-skipped character. - while ((TraverseUtil.isWhitespace(c)) || - (TraverseUtil.isSkipped(endCursor.node))) { - c = TraverseUtil.forwardsChar(endCursor, nodesCrossed); - if (c == null) - return null; - } - if (skipWhitespace || !initialWhitespace) { - // If skipWhitepace is true, or if the first character we encountered - // was not whitespace, return that non-whitespace character. - startCursor.copyFrom(endCursor); - startCursor.index--; - return c; - } - else { - for (var i = 0; i < nodesCrossed.length; i++) { - if (TraverseUtil.isSkipped(nodesCrossed[i])) { - // We need to make sure that startCursor and endCursor aren't - // surrounding a skippable node. - endCursor.index--; - startCursor.copyFrom(endCursor); - startCursor.index--; - return ' '; - } - } - // Otherwise, return all of the whitespace before that last character. - endCursor.index--; - return ' '; - } - }; - - var CaretBrowsing = function() {}; - - /** - * Is caret browsing enabled? - * @type {boolean} - */ - CaretBrowsing.isEnabled = false; - - /** - * Keep it enabled even when flipped off (for the options page)? - * @type {boolean} - */ - CaretBrowsing.forceEnabled = false; - - /** - * What to do when the caret appears? - * @type {string} - */ - CaretBrowsing.onEnable; - - /** - * What to do when the caret jumps? - * @type {string} - */ - CaretBrowsing.onJump; - - /** - * Is this window / iframe focused? We won't show the caret if not, - * especially so that carets aren't shown in two iframes of the same - * tab. - * @type {boolean} - */ - CaretBrowsing.isWindowFocused = false; - - /** - * Is the caret actually visible? This is true only if isEnabled and - * isWindowFocused are both true. - * @type {boolean} - */ - CaretBrowsing.isCaretVisible = false; - - /** - * The actual caret element, an absolute-positioned flashing line. - * @type {Element} - */ - CaretBrowsing.caretElement; - - /** - * The x-position of the caret, in absolute pixels. - * @type {number} - */ - CaretBrowsing.caretX = 0; - - /** - * The y-position of the caret, in absolute pixels. - * @type {number} - */ - CaretBrowsing.caretY = 0; - - /** - * The width of the caret in pixels. - * @type {number} - */ - CaretBrowsing.caretWidth = 0; - - /** - * The height of the caret in pixels. - * @type {number} - */ - CaretBrowsing.caretHeight = 0; - - /** - * The foregroundc color. - * @type {string} - */ - CaretBrowsing.caretForeground = '#000'; - - /** - * The backgroundc color. - * @type {string} - */ - CaretBrowsing.caretBackground = '#fff'; - - /** - * Is the selection collapsed, i.e. are the start and end locations - * the same? If so, our blinking caret image is shown; otherwise - * the Chrome selection is shown. - * @type {boolean} - */ - CaretBrowsing.isSelectionCollapsed = false; - - /** - * The id returned by window.setInterval for our blink function, so - * we can cancel it when caret browsing is disabled. - * @type {number?} - */ - CaretBrowsing.blinkFunctionId = null; - - /** - * The desired x-coordinate to match when moving the caret up and down. - * To match the behavior as documented in Mozilla's caret browsing spec - * (http://www.mozilla.org/access/keyboard/proposal), we keep track of the - * initial x position when the user starts moving the caret up and down, - * so that the x position doesn't drift as you move throughout lines, but - * stays as close as possible to the initial position. This is reset when - * moving left or right or clicking. - * @type {number?} - */ - CaretBrowsing.targetX = null; - - /** - * A flag that flips on or off as the caret blinks. - * @type {boolean} - */ - CaretBrowsing.blinkFlag = true; - - /** - * Check if a node is a control that normally allows the user to interact - * with it using arrow keys. We won't override the arrow keys when such a - * control has focus, the user must press Escape to do caret browsing outside - * that control. - * @param {Node} node A node to check. - * @return {boolean} True if this node is a control that the user can - * interact with using arrow keys. - */ - CaretBrowsing.isControlThatNeedsArrowKeys = function(node) { - if (!node) { - return false; - } - - if (node == document.body || node != document.activeElement) { - return false; - } - - if (node.constructor == HTMLSelectElement) { - return true; - } - - if (node.constructor == HTMLInputElement) { - switch (node.type) { - case 'email': - case 'number': - case 'password': - case 'search': - case 'text': - case 'tel': - case 'url': - case '': - return true; // All of these are text boxes. - case 'datetime': - case 'datetime-local': - case 'date': - case 'month': - case 'radio': - case 'range': - case 'week': - return true; // These are other input elements that use arrows. - } - } - - // Handle focusable ARIA controls. - if (node.getAttribute && isFocusable(node)) { - var role = node.getAttribute('role'); - switch (role) { - case 'combobox': - case 'grid': - case 'gridcell': - case 'listbox': - case 'menu': - case 'menubar': - case 'menuitem': - case 'menuitemcheckbox': - case 'menuitemradio': - case 'option': - case 'radiogroup': - case 'scrollbar': - case 'slider': - case 'spinbutton': - case 'tab': - case 'tablist': - case 'textbox': - case 'tree': - case 'treegrid': - case 'treeitem': - return true; - } - } - - return false; - }; - - CaretBrowsing.injectCaretStyles = function() { - var style = '.CaretBrowsing_Caret {' + - ' position: absolute;' + - ' z-index: 2147483647;' + - ' min-height: 10px;' + - ' background-color: #000;' + - '}' + - '.CaretBrowsing_AnimateCaret {' + - ' position: absolute;' + - ' z-index: 2147483647;' + - ' min-height: 10px;' + - '}' + - '.CaretBrowsing_FlashVert {' + - ' position: absolute;' + - ' z-index: 2147483647;' + - ' background: linear-gradient(' + - ' 270deg,' + - ' rgba(128, 128, 255, 0) 0%,' + - ' rgba(128, 128, 255, 0.3) 45%,' + - ' rgba(128, 128, 255, 0.8) 50%,' + - ' rgba(128, 128, 255, 0.3) 65%,' + - ' rgba(128, 128, 255, 0) 100%);' + - '}'; - var node = document.createElement('style'); - node.innerHTML = style; - document.body.appendChild(node); - } - - CaretBrowsing.setInitialCursor = function() { - var sel = window.getSelection(); - if (sel.rangeCount > 0) { - return; - } - - positionCaret(); - CaretBrowsing.injectCaretStyles(); - CaretBrowsing.toggle(); - CaretBrowsing.initiated = true; - CaretBrowsing.selectionEnabled = false; - }; - - /** - * Try to set the window's selection to be between the given start and end - * cursors, and return whether or not it was successful. - * @param {Cursor} start The start position. - * @param {Cursor} end The end position. - * @return {boolean} True if the selection was successfully set. - */ - CaretBrowsing.setAndValidateSelection = function(start, end) { - var sel = window.getSelection(); - sel.setBaseAndExtent(start.node, start.index, end.node, end.index); - - if (sel.rangeCount != 1) { - return false; - } - - return (sel.anchorNode == start.node && - sel.anchorOffset == start.index && - sel.focusNode == end.node && - sel.focusOffset == end.index); - }; - - - CaretBrowsing.setFocusToNode = function(node) { - while (node && node != document.body) { - if (isFocusable(node) && node.constructor != HTMLIFrameElement) { - node.focus(); - if (node.constructor == HTMLInputElement && node.select) { - node.select(); - } - return true; - } - node = node.parentNode; - } - - return false; - }; - - /** - * Set the caret element's normal style, i.e. not when animating. - */ - CaretBrowsing.setCaretElementNormalStyle = function() { - var element = CaretBrowsing.caretElement; - element.className = 'CaretBrowsing_Caret'; - element.style.opacity = CaretBrowsing.isSelectionCollapsed ? '1.0' : '0.0'; - element.style.left = CaretBrowsing.caretX + 'px'; - element.style.top = CaretBrowsing.caretY + 'px'; - element.style.width = CaretBrowsing.caretWidth + 'px'; - element.style.height = CaretBrowsing.caretHeight + 'px'; - element.style.color = CaretBrowsing.caretForeground; - }; - - /** - * Animate the caret element into the normal style. - */ - CaretBrowsing.animateCaretElement = function() { - var element = CaretBrowsing.caretElement; - element.style.left = (CaretBrowsing.caretX - 50) + 'px'; - element.style.top = (CaretBrowsing.caretY - 100) + 'px'; - element.style.width = (CaretBrowsing.caretWidth + 100) + 'px'; - element.style.height = (CaretBrowsing.caretHeight + 200) + 'px'; - element.className = 'CaretBrowsing_AnimateCaret'; - - // Start the animation. The setTimeout is so that the old values will get - // applied first, so we can animate to the new values. - window.setTimeout(function() { - if (!CaretBrowsing.caretElement) { - return; - } - CaretBrowsing.setCaretElementNormalStyle(); - element.style['transition'] = 'all 0.8s ease-in'; - function listener() { - element.removeEventListener( - 'transitionend', listener, false); - element.style['transition'] = 'none'; - } - element.addEventListener( - 'transitionend', listener, false); - }, 0); - }; - - /** - * Quick flash and then show the normal caret style. - */ - CaretBrowsing.flashCaretElement = function() { - var x = CaretBrowsing.caretX; - var y = CaretBrowsing.caretY; - var height = CaretBrowsing.caretHeight; - - var vert = document.createElement('div'); - vert.className = 'CaretBrowsing_FlashVert'; - vert.style.left = (x - 6) + 'px'; - vert.style.top = (y - 100) + 'px'; - vert.style.width = '11px'; - vert.style.height = (200) + 'px'; - document.body.appendChild(vert); - - window.setTimeout(function() { - document.body.removeChild(vert); - if (CaretBrowsing.caretElement) { - CaretBrowsing.setCaretElementNormalStyle(); - } - }, 250); - }; - - /** - * Create the caret element. This assumes that caretX, caretY, - * caretWidth, and caretHeight have all been set. The caret is - * animated in so the user can find it when it first appears. - */ - CaretBrowsing.createCaretElement = function() { - var element = document.createElement('div'); - element.className = 'CaretBrowsing_Caret'; - document.body.appendChild(element); - CaretBrowsing.caretElement = element; - - if (CaretBrowsing.onEnable == 'anim') { - CaretBrowsing.animateCaretElement(); - } else if (CaretBrowsing.onEnable == 'flash') { - CaretBrowsing.flashCaretElement(); - } else { - CaretBrowsing.setCaretElementNormalStyle(); - } - }; - - /** - * Recreate the caret element, triggering any intro animation. - */ - CaretBrowsing.recreateCaretElement = function() { - if (CaretBrowsing.caretElement) { - window.clearInterval(CaretBrowsing.blinkFunctionId); - CaretBrowsing.caretElement.parentElement.removeChild( - CaretBrowsing.caretElement); - CaretBrowsing.caretElement = null; - CaretBrowsing.updateIsCaretVisible(); - } - }; - - /** - * Get the rectangle for a cursor position. This is tricky because - * you can't get the bounding rectangle of an empty range, so this function - * computes the rect by trying a range including one character earlier or - * later than the cursor position. - * @param {Cursor} cursor A single cursor position. - * @return {{left: number, top: number, width: number, height: number}} - * The bounding rectangle of the cursor. - */ - CaretBrowsing.getCursorRect = function(cursor) { - var node = cursor.node; - var index = cursor.index; - var rect = { - left: 0, - top: 0, - width: 1, - height: 0 - }; - if (node.constructor == Text) { - var left = index; - var right = index; - var max = node.data.length; - var newRange = document.createRange(); - while (left > 0 || right < max) { - if (left > 0) { - left--; - newRange.setStart(node, left); - newRange.setEnd(node, index); - var rangeRect = newRange.getBoundingClientRect(); - if (rangeRect && rangeRect.width && rangeRect.height) { - rect.left = rangeRect.right; - rect.top = rangeRect.top; - rect.height = rangeRect.height; - break; - } - } - if (right < max) { - right++; - newRange.setStart(node, index); - newRange.setEnd(node, right); - var rangeRect = newRange.getBoundingClientRect(); - if (rangeRect && rangeRect.width && rangeRect.height) { - rect.left = rangeRect.left; - rect.top = rangeRect.top; - rect.height = rangeRect.height; - break; - } - } - } - } else { - rect.height = node.offsetHeight; - while (node !== null) { - rect.left += node.offsetLeft; - rect.top += node.offsetTop; - node = node.offsetParent; - } - } - rect.left += window.pageXOffset; - rect.top += window.pageYOffset; - return rect; - }; - - /** - * Compute the new location of the caret or selection and update - * the element as needed. - * @param {boolean} scrollToSelection If true, will also scroll the page - * to the caret / selection location. - */ - CaretBrowsing.updateCaretOrSelection = function(scrollToSelection) { - var previousX = CaretBrowsing.caretX; - var previousY = CaretBrowsing.caretY; - - var sel = window.getSelection(); - if (sel.rangeCount == 0) { - if (CaretBrowsing.caretElement) { - CaretBrowsing.isSelectionCollapsed = false; - CaretBrowsing.caretElement.style.opacity = '0.0'; - } - return; - } - - var range = sel.getRangeAt(0); - if (!range) { - if (CaretBrowsing.caretElement) { - CaretBrowsing.isSelectionCollapsed = false; - CaretBrowsing.caretElement.style.opacity = '0.0'; - } - return; - } - - if (CaretBrowsing.isControlThatNeedsArrowKeys(document.activeElement)) { - var node = document.activeElement; - CaretBrowsing.caretWidth = node.offsetWidth; - CaretBrowsing.caretHeight = node.offsetHeight; - CaretBrowsing.caretX = 0; - CaretBrowsing.caretY = 0; - while (node.offsetParent) { - CaretBrowsing.caretX += node.offsetLeft; - CaretBrowsing.caretY += node.offsetTop; - node = node.offsetParent; - } - CaretBrowsing.isSelectionCollapsed = false; - } else if (range.startOffset != range.endOffset || - range.startContainer != range.endContainer) { - var rect = range.getBoundingClientRect(); - if (!rect) { - return; - } - CaretBrowsing.caretX = rect.left + window.pageXOffset; - CaretBrowsing.caretY = rect.top + window.pageYOffset; - CaretBrowsing.caretWidth = rect.width; - CaretBrowsing.caretHeight = rect.height; - CaretBrowsing.isSelectionCollapsed = false; - } else { - var rect = CaretBrowsing.getCursorRect( - new Cursor(range.startContainer, - range.startOffset, - TraverseUtil.getNodeText(range.startContainer))); - CaretBrowsing.caretX = rect.left; - CaretBrowsing.caretY = rect.top; - CaretBrowsing.caretWidth = rect.width; - CaretBrowsing.caretHeight = rect.height; - CaretBrowsing.isSelectionCollapsed = true; - } - - if (!CaretBrowsing.caretElement) { - CaretBrowsing.createCaretElement(); - } else { - var element = CaretBrowsing.caretElement; - if (CaretBrowsing.isSelectionCollapsed) { - element.style.opacity = '1.0'; - element.style.left = CaretBrowsing.caretX + 'px'; - element.style.top = CaretBrowsing.caretY + 'px'; - element.style.width = CaretBrowsing.caretWidth + 'px'; - element.style.height = CaretBrowsing.caretHeight + 'px'; - } else { - element.style.opacity = '0.0'; - } - } - - var elem = range.startContainer; - if (elem.constructor == Text) - elem = elem.parentElement; - var style = window.getComputedStyle(elem); - var bg = axs.utils.getBgColor(style, elem); - var fg = axs.utils.getFgColor(style, elem, bg); - CaretBrowsing.caretBackground = axs.color.colorToString(bg); - CaretBrowsing.caretForeground = axs.color.colorToString(fg); - - if (scrollToSelection) { - // Scroll just to the "focus" position of the selection, - // the part the user is manipulating. - var rect = CaretBrowsing.getCursorRect( - new Cursor(sel.focusNode, sel.focusOffset, - TraverseUtil.getNodeText(sel.focusNode))); - - var yscroll = window.pageYOffset; - var pageHeight = window.innerHeight; - var caretY = rect.top; - var caretHeight = Math.min(rect.height, 30); - if (yscroll + pageHeight < caretY + caretHeight) { - window.scroll(0, (caretY + caretHeight - pageHeight + 100)); - } else if (caretY < yscroll) { - window.scroll(0, (caretY - 100)); - } - } - - if (Math.abs(previousX - CaretBrowsing.caretX) > 500 || - Math.abs(previousY - CaretBrowsing.caretY) > 100) { - if (CaretBrowsing.onJump == 'anim') { - CaretBrowsing.animateCaretElement(); - } else if (CaretBrowsing.onJump == 'flash') { - CaretBrowsing.flashCaretElement(); - } - } - }; - - CaretBrowsing.move = function(direction, granularity) { - window - .getSelection() - .modify( - CaretBrowsing.selectionEnabled ? 'extend' : 'move', - direction, - granularity); - - window.setTimeout(function() { - CaretBrowsing.updateCaretOrSelection(true); - }, 0); - } - - CaretBrowsing.moveToBlock = function(paragraph, boundary) { - window - .getSelection() - .modify( - CaretBrowsing.selectionEnabled ? 'extend' : 'move', - paragraph, - 'paragraph'); - - window - .getSelection() - .modify( - CaretBrowsing.selectionEnabled ? 'extend' : 'move', - boundary, - 'paragraphboundary'); - - window.setTimeout(function() { - CaretBrowsing.updateCaretOrSelection(true); - }, 0); - - } - - /** - * Toggle whether caret browsing is enabled or not. - */ - CaretBrowsing.toggle = function() { - if (CaretBrowsing.forceEnabled) { - CaretBrowsing.recreateCaretElement(); - return; - } - - CaretBrowsing.isEnabled = !CaretBrowsing.isEnabled; - var obj = {}; - obj['enabled'] = CaretBrowsing.isEnabled; - CaretBrowsing.updateIsCaretVisible(); - }; - - /** - * Event handler, called when the mouse is clicked. Chrome already - * sets the selection when the mouse is clicked, all we need to do is - * update our cursor. - * @param {Event} evt The DOM event. - * @return {boolean} True if the default action should be performed. - */ - CaretBrowsing.onClick = function(evt) { - if (!CaretBrowsing.isEnabled) { - return true; - } - window.setTimeout(function() { - CaretBrowsing.targetX = null; - CaretBrowsing.updateCaretOrSelection(false); - }, 0); - return true; - }; - - /** - * Called at a regular interval. Blink the cursor by changing its visibility. - */ - CaretBrowsing.caretBlinkFunction = function() { - if (CaretBrowsing.caretElement) { - if (CaretBrowsing.blinkFlag) { - CaretBrowsing.caretElement.style.backgroundColor = - CaretBrowsing.caretForeground; - CaretBrowsing.blinkFlag = false; - } else { - CaretBrowsing.caretElement.style.backgroundColor = - CaretBrowsing.caretBackground; - CaretBrowsing.blinkFlag = true; - } - } - }; - - /** - * Update whether or not the caret is visible, based on whether caret browsing - * is enabled and whether this window / iframe has focus. - */ - CaretBrowsing.updateIsCaretVisible = function() { - CaretBrowsing.isCaretVisible = - (CaretBrowsing.isEnabled && CaretBrowsing.isWindowFocused); - if (CaretBrowsing.isCaretVisible && !CaretBrowsing.caretElement) { - CaretBrowsing.setInitialCursor(); - CaretBrowsing.updateCaretOrSelection(true); - if (CaretBrowsing.caretElement) { - CaretBrowsing.blinkFunctionId = window.setInterval( - CaretBrowsing.caretBlinkFunction, 500); - } - } else if (!CaretBrowsing.isCaretVisible && - CaretBrowsing.caretElement) { - window.clearInterval(CaretBrowsing.blinkFunctionId); - if (CaretBrowsing.caretElement) { - CaretBrowsing.isSelectionCollapsed = false; - CaretBrowsing.caretElement.parentElement.removeChild( - CaretBrowsing.caretElement); - CaretBrowsing.caretElement = null; - } - } - }; - - /** - * Called when the prefs get updated. - */ - /** - * Called when this window / iframe gains focus. - */ - CaretBrowsing.onWindowFocus = function() { - CaretBrowsing.isWindowFocused = true; - CaretBrowsing.updateIsCaretVisible(); - }; - - /** - * Called when this window / iframe loses focus. - */ - CaretBrowsing.onWindowBlur = function() { - CaretBrowsing.isWindowFocused = false; - CaretBrowsing.updateIsCaretVisible(); - }; - - /** - * Initializes caret browsing by adding event listeners and extension - * message listeners. - */ - CaretBrowsing.init = function() { - CaretBrowsing.isWindowFocused = document.hasFocus(); - - document.addEventListener('click', CaretBrowsing.onClick, false); - window.addEventListener('focus', CaretBrowsing.onWindowFocus, false); - window.addEventListener('blur', CaretBrowsing.onWindowBlur, false); - }; - - window.setTimeout(function() { - - // Make sure the script only loads once. - if (!window['caretBrowsingLoaded']) { - window['caretBrowsingLoaded'] = true; - CaretBrowsing.init(); - - if (document.body.getAttribute('caretbrowsing') == 'on') { - CaretBrowsing.forceEnabled = true; - CaretBrowsing.isEnabled = true; - CaretBrowsing.updateIsCaretVisible(); - } - - } - - }, 0); +"use strict"; // eslint-disable-line max-lines +window._qutebrowser.caret = (function() { // eslint-disable-line max-statements,max-len function isElementInViewport(node) { // eslint-disable-line complexity let i; let boundingRect = (node.getClientRects()[0] || @@ -1138,19 +69,20 @@ window._qutebrowser.caret = (function() { } } if (el !== undefined) { - var start = new Cursor(el, 0, ''); - var end = new Cursor(el, 0, ''); - var nodesCrossed = []; - var result = TraverseUtil.getNextChar(start, end, nodesCrossed, true); - if (result == null) { + const start = new Cursor(el, 0, ""); // eslint-disable-line no-use-before-define,max-len + const end = new Cursor(el, 0, ""); // eslint-disable-line no-use-before-define,max-len + const nodesCrossed = []; + const result = TraverseUtil.getNextChar( // eslint-disable-line no-use-before-define,max-len + start, end, nodesCrossed, true); + if (result === null) { return; } - CaretBrowsing.setAndValidateSelection(start, start); + CaretBrowsing.setAndValidateSelection(start, start); // eslint-disable-line no-use-before-define,max-len } } function isFocusable(targetNode) { - if (!targetNode || typeof(targetNode.tabIndex) != 'number') { + if (!targetNode || typeof (targetNode.tabIndex) !== "number") { return false; } @@ -1159,104 +91,977 @@ window._qutebrowser.caret = (function() { } if (targetNode.hasAttribute && - targetNode.hasAttribute('tabindex') && - targetNode.getAttribute('tabindex') == '-1') { + targetNode.hasAttribute("tabindex") && + targetNode.getAttribute("tabindex") === "-1") { return true; } return false; } + const axs = {}; + + axs.dom = {}; + + axs.color = {}; + + axs.utils = {}; + + axs.dom.parentElement = function(node) { + if (!node) { + return null; + } + const composedNode = axs.dom.composedParentNode(node); + if (!composedNode) { + return null; + } + switch (composedNode.nodeType) { + case Node.ELEMENT_NODE: + return composedNode; + default: + return axs.dom.parentElement(composedNode); + } + }; + + axs.dom.shadowHost = function(node) { + if ("host" in node) { + return node.host; + } + return null; + }; + + axs.dom.composedParentNode = function(node) { + if (!node) { + return null; + } + if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + return axs.dom.shadowHost(node); + } + const parentNode = node.parentNode; + if (!parentNode) { + return null; + } + if (parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + return axs.dom.shadowHost(parentNode); + } + if (!parentNode.shadowRoot) { + return parentNode; + } + const points = node.getDestinationInsertionPoints(); + if (points.length > 0) { + return axs.dom.composedParentNode(points[points.length - 1]); + } + return null; + }; + + axs.color.Color = function(red, green, blue, alpha) { // eslint-disable-line max-params,max-len + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + }; + + axs.color.parseColor = function(colorText) { + if (colorText === "transparent") { + return new axs.color.Color(0, 0, 0, 0); + } + let match = colorText.match(/^rgb\((\d+), (\d+), (\d+)\)$/); + if (match) { + const blue = parseInt(match[3], 10); + const green = parseInt(match[2], 10); + const red = parseInt(match[1], 10); + return new axs.color.Color(red, green, blue, 1); + } + match = colorText.match(/^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/); + if (match) { + const red = parseInt(match[1], 10); + const green = parseInt(match[2], 10); + const blue = parseInt(match[3], 10); + const alpha = parseFloat(match[4]); + return new axs.color.Color(red, green, blue, alpha); + } + return null; + }; + + axs.color.flattenColors = function(color1, color2) { + const colorAlpha = color1.alpha; + return new axs.color.Color( + ((1 - colorAlpha) * color2.red) + (colorAlpha * color1.red), + ((1 - colorAlpha) * color2.green) + (colorAlpha * color1.green), + ((1 - colorAlpha) * color2.blue) + (colorAlpha * color2.blue), + color1.alpha + (color2.alpha * (1 - color1.alpha))); + }; + + axs.utils.getParentBgColor = function(_el) { + let el = _el; + let el2 = el; + let iter = null; + el = []; + for (iter = null; (el2 = axs.dom.parentElement(el2));) { + const style = window.getComputedStyle(el2, null); + if (style) { + const color = axs.color.parseColor(style.backgroundColor); + if (color && + (style.opacity < 1 && + (color.alpha *= style.opacity), + color.alpha !== 0 && + (el.push(color), color.alpha === 1))) { + iter = !0; + break; + } + } + } + if (!iter) { + el.push(new axs.color.Color(255, 255, 255, 1)); + } + for (el2 = el.pop(); el.length;) { + iter = el.pop(); + el2 = axs.color.flattenColors(iter, el2); + } + return el2; + }; + + axs.utils.getFgColor = function(el, el2, color) { + let color2 = axs.color.parseColor(el.color); + if (!color2) { + return null; + } + if (color2.alpha < 1) { + color2 = axs.color.flattenColors(color2, color); + } + if (el.opacity < 1) { + const el3 = axs.utils.getParentBgColor(el2); + color2.alpha *= el.opacity; + color2 = axs.color.flattenColors(color2, el3); + } + return color2; + }; + + axs.utils.getBgColor = function(el, elParent) { + let color = axs.color.parseColor(el.backgroundColor); + if (!color) { + return null; + } + if (el.opacity < 1) { + color.alpha *= el.opacity; + } + if (color.alpha < 1) { + const bgColor = axs.utils.getParentBgColor(elParent); + if (bgColor === null) { + return null; + } + color = axs.color.flattenColors(color, bgColor); + } + return color; + }; + + axs.color.colorChannelToString = function(_color) { + const color = Math.round(_color); + if (color < 15) { + return `0${color.toString(16)}`; + } + return color.toString(16); + }; + + axs.color.colorToString = function(color) { + if (color.alpha === 1) { + const red = axs.color.colorChannelToString(color.red); + const green = axs.color.colorChannelToString(color.green); + const blue = axs.color.colorChannelToString(color.blue); + return `#${red}${green}${blue}`; + } + const arr = [color.red, color.green, color.blue, color.alpha].join(); + return `rgba(${arr})`; + }; + + const Cursor = function(node, index, text) { // eslint-disable-line func-style,max-len + this.node = node; + this.index = index; + this.text = text; + }; + + Cursor.prototype.clone = function() { + return new Cursor(this.node, this.index, this.text); + }; + + Cursor.prototype.copyFrom = function(otherCursor) { + this.node = otherCursor.node; + this.index = otherCursor.index; + this.text = otherCursor.text; + }; + + const TraverseUtil = {}; + + TraverseUtil.getNodeText = function(node) { + if (node.constructor === Text) { + return node.data; + } + return ""; + }; + + TraverseUtil.treatAsLeafNode = function(node) { + return node.childNodes.length === 0 || + node.nodeName === "SELECT" || + node.nodeName === "OBJECT"; + }; + + TraverseUtil.isWhitespace = function(ch) { + return (ch === " " || ch === "\n" || ch === "\r" || ch === "\t"); + }; + + TraverseUtil.isVisible = function(node) { + if (!node.style) { + return true; + } + const style = window.getComputedStyle(node, null); + return (Boolean(style) && + style.display !== "none" && + style.visibility !== "hidden"); + }; + + TraverseUtil.isSkipped = function(_node) { + let node = _node; + if (node.constructor === Text) { + node = node.parentElement; + } + if (node.className === "CaretBrowsing_Caret" || + node.className === "CaretBrowsing_AnimateCaret") { + return true; + } + return false; + }; + + TraverseUtil.forwardsChar = function(cursor, nodesCrossed) { // eslint-disable-line max-statements,max-len + for (;;) { + let childNode = null; + if (!TraverseUtil.treatAsLeafNode(cursor.node)) { + for (let i = cursor.index; + i < cursor.node.childNodes.length; + i++) { + const node = cursor.node.childNodes[i]; + if (TraverseUtil.isSkipped(node)) { + nodesCrossed.push(node); + } else if (TraverseUtil.isVisible(node)) { + childNode = node; + break; + } + } + } + if (childNode) { + cursor.node = childNode; + cursor.index = 0; + cursor.text = TraverseUtil.getNodeText(cursor.node); + if (cursor.node.constructor !== Text) { + nodesCrossed.push(cursor.node); + } + } else { + if (cursor.index < cursor.text.length) { + return cursor.text[cursor.index++]; + } + + while (cursor.node !== null) { + let siblingNode = null; + for (let node = cursor.node.nextSibling; + node !== null; + node = node.nextSibling) { + if (TraverseUtil.isSkipped(node)) { + nodesCrossed.push(node); + } else if (TraverseUtil.isVisible(node)) { + siblingNode = node; + break; + } + } + if (siblingNode) { + cursor.node = siblingNode; + cursor.text = TraverseUtil.getNodeText(siblingNode); + cursor.index = 0; + + if (cursor.node.constructor !== Text) { + nodesCrossed.push(cursor.node); + } + + break; + } + + const parentNode = cursor.node.parentNode; + if (parentNode && + parentNode.constructor !== HTMLBodyElement) { + cursor.node = cursor.node.parentNode; + cursor.text = null; + cursor.index = 0; + } else { + return null; + } + } + } + } + }; + + TraverseUtil.getNextChar = function( // eslint-disable-line max-params + startCursor, endCursor, nodesCrossed, skipWhitespace) { + startCursor.copyFrom(endCursor); + let fChar = TraverseUtil.forwardsChar(endCursor, nodesCrossed); + if (fChar === null) { + return null; + } + + const initialWhitespace = TraverseUtil.isWhitespace(fChar); + + while ((TraverseUtil.isWhitespace(fChar)) || + (TraverseUtil.isSkipped(endCursor.node))) { + fChar = TraverseUtil.forwardsChar(endCursor, nodesCrossed); + if (fChar === null) { + return null; + } + } + if (skipWhitespace || !initialWhitespace) { + startCursor.copyFrom(endCursor); + startCursor.index--; + return fChar; + } + + for (let i = 0; i < nodesCrossed.length; i++) { + if (TraverseUtil.isSkipped(nodesCrossed[i])) { + endCursor.index--; + startCursor.copyFrom(endCursor); + startCursor.index--; + return " "; + } + } + endCursor.index--; + return " "; + }; + + const CaretBrowsing = {}; + + CaretBrowsing.isEnabled = false; + + CaretBrowsing.forceEnabled = false; + + CaretBrowsing.onEnable = undefined; + + CaretBrowsing.onJump = undefined; + + CaretBrowsing.isWindowFocused = false; + + CaretBrowsing.isCaretVisible = false; + + CaretBrowsing.caretElement = undefined; + + CaretBrowsing.caretX = 0; + + CaretBrowsing.caretY = 0; + + CaretBrowsing.caretWidth = 0; + + CaretBrowsing.caretHeight = 0; + + CaretBrowsing.caretForeground = "#000"; + + CaretBrowsing.caretBackground = "#fff"; + + CaretBrowsing.isSelectionCollapsed = false; + + CaretBrowsing.blinkFunctionId = null; + + CaretBrowsing.targetX = null; + + CaretBrowsing.blinkFlag = true; + + CaretBrowsing.isControlThatNeedsArrowKeys = function(node) { // eslint-disable-line complexity,max-len + if (!node) { + return false; + } + + if (node === document.body || node !== document.activeElement) { + return false; + } + + if (node.constructor === HTMLSelectElement) { + return true; + } + + if (node.constructor === HTMLInputElement) { + switch (node.type) { // eslint-disable-line default-case + case "email": + case "number": + case "password": + case "search": + case "text": + case "tel": + case "url": + case "": + return true; + case "datetime": + case "datetime-local": + case "date": + case "month": + case "radio": + case "range": + case "week": + return true; + } + } + + if (node.getAttribute && isFocusable(node)) { + const role = node.getAttribute("role"); + switch (role) { // eslint-disable-line default-case + case "combobox": + case "grid": + case "gridcell": + case "listbox": + case "menu": + case "menubar": + case "menuitem": + case "menuitemcheckbox": + case "menuitemradio": + case "option": + case "radiogroup": + case "scrollbar": + case "slider": + case "spinbutton": + case "tab": + case "tablist": + case "textbox": + case "tree": + case "treegrid": + case "treeitem": + return true; + } + } + + return false; + }; + + CaretBrowsing.injectCaretStyles = function() { + const style = ".CaretBrowsing_Caret {" + + " position: absolute;" + + " z-index: 2147483647;" + + " min-height: 10px;" + + " background-color: #000;" + + "}" + + ".CaretBrowsing_AnimateCaret {" + + " position: absolute;" + + " z-index: 2147483647;" + + " min-height: 10px;" + + "}" + + ".CaretBrowsing_FlashVert {" + + " position: absolute;" + + " z-index: 2147483647;" + + " background: linear-gradient(" + + " 270deg," + + " rgba(128, 128, 255, 0) 0%," + + " rgba(128, 128, 255, 0.3) 45%," + + " rgba(128, 128, 255, 0.8) 50%," + + " rgba(128, 128, 255, 0.3) 65%," + + " rgba(128, 128, 255, 0) 100%);" + + "}"; + const node = document.createElement("style"); + node.innerHTML = style; + document.body.appendChild(node); + }; + + CaretBrowsing.setInitialCursor = function() { + const sel = window.getSelection(); + if (sel.rangeCount > 0) { + return; + } + + positionCaret(); + CaretBrowsing.injectCaretStyles(); + CaretBrowsing.toggle(); + CaretBrowsing.initiated = true; + CaretBrowsing.selectionEnabled = false; + }; + + CaretBrowsing.setAndValidateSelection = function(start, end) { + const sel = window.getSelection(); + sel.setBaseAndExtent(start.node, start.index, end.node, end.index); + + if (sel.rangeCount !== 1) { + return false; + } + + return (sel.anchorNode === start.node && + sel.anchorOffset === start.index && + sel.focusNode === end.node && + sel.focusOffset === end.index); + }; + + CaretBrowsing.setFocusToNode = function(nodeArg) { + let node = nodeArg; + while (node && node !== document.body) { + if (isFocusable(node) && node.constructor !== HTMLIFrameElement) { + node.focus(); + if (node.constructor === HTMLInputElement && node.select) { + node.select(); + } + return true; + } + node = node.parentNode; + } + + return false; + }; + + CaretBrowsing.setCaretElementNormalStyle = function() { + const element = CaretBrowsing.caretElement; + element.className = "CaretBrowsing_Caret"; + if (CaretBrowsing.isSelectionCollapsed) { + element.style.opacity = "1.0"; + } else { + element.style.opacity = "0.0"; + } + element.style.left = `${CaretBrowsing.caretX}px`; + element.style.top = `${CaretBrowsing.caretY}px`; + element.style.width = `${CaretBrowsing.caretWidth}px`; + element.style.height = `${CaretBrowsing.caretHeight}px`; + element.style.color = CaretBrowsing.caretForeground; + }; + + CaretBrowsing.animateCaretElement = function() { + const element = CaretBrowsing.caretElement; + element.style.left = `${CaretBrowsing.caretX - 50}px`; + element.style.top = `${CaretBrowsing.caretY - 100}px`; + element.style.width = `${CaretBrowsing.caretWidth + 100}px`; + element.style.height = `${CaretBrowsing.caretHeight + 200}px`; + element.className = "CaretBrowsing_AnimateCaret"; + + window.setTimeout(() => { + if (!CaretBrowsing.caretElement) { + return; + } + CaretBrowsing.setCaretElementNormalStyle(); + element.style.transition = "all 0.8s ease-in"; + function listener() { + element.removeEventListener( + "transitionend", listener, false); + element.style.transition = "none"; + } + element.addEventListener( + "transitionend", listener, false); + }, 0); + }; + + CaretBrowsing.flashCaretElement = function() { + const x = CaretBrowsing.caretX; + const y = CaretBrowsing.caretY; + + const vert = document.createElement("div"); + vert.className = "CaretBrowsing_FlashVert"; + vert.style.left = `${x - 6}px`; + vert.style.top = `${y - 100}px`; + vert.style.width = "11px"; + vert.style.height = `${200}px`; + document.body.appendChild(vert); + + window.setTimeout(() => { + document.body.removeChild(vert); + if (CaretBrowsing.caretElement) { + CaretBrowsing.setCaretElementNormalStyle(); + } + }, 250); + }; + + CaretBrowsing.createCaretElement = function() { + const element = document.createElement("div"); + element.className = "CaretBrowsing_Caret"; + document.body.appendChild(element); + CaretBrowsing.caretElement = element; + + if (CaretBrowsing.onEnable === "anim") { + CaretBrowsing.animateCaretElement(); + } else if (CaretBrowsing.onEnable === "flash") { + CaretBrowsing.flashCaretElement(); + } else { + CaretBrowsing.setCaretElementNormalStyle(); + } + }; + + CaretBrowsing.recreateCaretElement = function() { + if (CaretBrowsing.caretElement) { + window.clearInterval(CaretBrowsing.blinkFunctionId); + CaretBrowsing.caretElement.parentElement.removeChild( + CaretBrowsing.caretElement); + CaretBrowsing.caretElement = null; + CaretBrowsing.updateIsCaretVisible(); + } + }; + + CaretBrowsing.getCursorRect = function(cursor) { // eslint-disable-line max-statements,max-len + let node = cursor.node; + const index = cursor.index; + const rect = { + "left": 0, + "top": 0, + "width": 1, + "height": 0, + }; + if (node.constructor === Text) { + let left = index; + let right = index; + const max = node.data.length; + const newRange = document.createRange(); + while (left > 0 || right < max) { + if (left > 0) { + left--; + newRange.setStart(node, left); + newRange.setEnd(node, index); + const rangeRect = newRange.getBoundingClientRect(); + if (rangeRect && rangeRect.width && rangeRect.height) { + rect.left = rangeRect.right; + rect.top = rangeRect.top; + rect.height = rangeRect.height; + break; + } + } + if (right < max) { + right++; + newRange.setStart(node, index); + newRange.setEnd(node, right); + const rangeRect = newRange.getBoundingClientRect(); + if (rangeRect && rangeRect.width && rangeRect.height) { + rect.left = rangeRect.left; + rect.top = rangeRect.top; + rect.height = rangeRect.height; + break; + } + } + } + } else { + rect.height = node.offsetHeight; + while (node !== null) { + rect.left += node.offsetLeft; + rect.top += node.offsetTop; + node = node.offsetParent; + } + } + rect.left += window.pageXOffset; + rect.top += window.pageYOffset; + return rect; + }; + + CaretBrowsing.updateCaretOrSelection = + function(scrollToSelection) { // eslint-disable-line max-statements + const previousX = CaretBrowsing.caretX; + const previousY = CaretBrowsing.caretY; + + const sel = window.getSelection(); + if (sel.rangeCount === 0) { + if (CaretBrowsing.caretElement) { + CaretBrowsing.isSelectionCollapsed = false; + CaretBrowsing.caretElement.style.opacity = "0.0"; + } + return; + } + + const range = sel.getRangeAt(0); + if (!range) { + if (CaretBrowsing.caretElement) { + CaretBrowsing.isSelectionCollapsed = false; + CaretBrowsing.caretElement.style.opacity = "0.0"; + } + return; + } + + if (CaretBrowsing.isControlThatNeedsArrowKeys( + document.activeElement)) { + let node = document.activeElement; + CaretBrowsing.caretWidth = node.offsetWidth; + CaretBrowsing.caretHeight = node.offsetHeight; + CaretBrowsing.caretX = 0; + CaretBrowsing.caretY = 0; + while (node.offsetParent) { + CaretBrowsing.caretX += node.offsetLeft; + CaretBrowsing.caretY += node.offsetTop; + node = node.offsetParent; + } + CaretBrowsing.isSelectionCollapsed = false; + } else if (range.startOffset !== range.endOffset || + range.startContainer !== range.endContainer) { + const rect = range.getBoundingClientRect(); + if (!rect) { + return; + } + CaretBrowsing.caretX = rect.left + window.pageXOffset; + CaretBrowsing.caretY = rect.top + window.pageYOffset; + CaretBrowsing.caretWidth = rect.width; + CaretBrowsing.caretHeight = rect.height; + CaretBrowsing.isSelectionCollapsed = false; + } else { + const rect = CaretBrowsing.getCursorRect( + new Cursor(range.startContainer, + range.startOffset, + TraverseUtil.getNodeText(range.startContainer))); + CaretBrowsing.caretX = rect.left; + CaretBrowsing.caretY = rect.top; + CaretBrowsing.caretWidth = rect.width; + CaretBrowsing.caretHeight = rect.height; + CaretBrowsing.isSelectionCollapsed = true; + } + + if (CaretBrowsing.caretElement) { + const element = CaretBrowsing.caretElement; + if (CaretBrowsing.isSelectionCollapsed) { + element.style.opacity = "1.0"; + element.style.left = `${CaretBrowsing.caretX}px`; + element.style.top = `${CaretBrowsing.caretY}px`; + element.style.width = `${CaretBrowsing.caretWidth}px`; + element.style.height = `${CaretBrowsing.caretHeight}px`; + } else { + element.style.opacity = "0.0"; + } + } else { + CaretBrowsing.createCaretElement(); + } + + let elem = range.startContainer; + if (elem.constructor === Text) { + elem = elem.parentElement; + } + const style = window.getComputedStyle(elem); + const bg = axs.utils.getBgColor(style, elem); + const fg = axs.utils.getFgColor(style, elem, bg); + CaretBrowsing.caretBackground = axs.color.colorToString(bg); + CaretBrowsing.caretForeground = axs.color.colorToString(fg); + + if (scrollToSelection) { + const rect = CaretBrowsing.getCursorRect( + new Cursor(sel.focusNode, sel.focusOffset, + TraverseUtil.getNodeText(sel.focusNode))); + + const yscroll = window.pageYOffset; + const pageHeight = window.innerHeight; + const caretY = rect.top; + const caretHeight = Math.min(rect.height, 30); + if (yscroll + pageHeight < caretY + caretHeight) { + window.scroll(0, (caretY + caretHeight - pageHeight + 100)); + } else if (caretY < yscroll) { + window.scroll(0, (caretY - 100)); + } + } + + if (Math.abs(previousX - CaretBrowsing.caretX) > 500 || + Math.abs(previousY - CaretBrowsing.caretY) > 100) { + if (CaretBrowsing.onJump === "anim") { + CaretBrowsing.animateCaretElement(); + } else if (CaretBrowsing.onJump === "flash") { + CaretBrowsing.flashCaretElement(); + } + } + }; + + CaretBrowsing.move = function(direction, granularity) { + let action = "move"; + if (CaretBrowsing.selectionEnabled) { + action = "extend"; + } + window. + getSelection(). + modify(action, direction, granularity); + + window.setTimeout(() => { + CaretBrowsing.updateCaretOrSelection(true); + }, 0); + }; + + CaretBrowsing.moveToBlock = function(paragraph, boundary) { + let action = "move"; + if (CaretBrowsing.selectionEnabled) { + action = "extend"; + } + window. + getSelection(). + modify(action, paragraph, "paragraph"); + + window. + getSelection(). + modify(action, boundary, "paragraphboundary"); + + window.setTimeout(() => { + CaretBrowsing.updateCaretOrSelection(true); + }, 0); + }; + + CaretBrowsing.toggle = function() { + if (CaretBrowsing.forceEnabled) { + CaretBrowsing.recreateCaretElement(); + return; + } + + CaretBrowsing.isEnabled = !CaretBrowsing.isEnabled; + const obj = {}; + obj.enabled = CaretBrowsing.isEnabled; + CaretBrowsing.updateIsCaretVisible(); + }; + + CaretBrowsing.onClick = function() { + if (!CaretBrowsing.isEnabled) { + return true; + } + window.setTimeout(() => { + CaretBrowsing.targetX = null; + CaretBrowsing.updateCaretOrSelection(false); + }, 0); + return true; + }; + + CaretBrowsing.caretBlinkFunction = function() { + if (CaretBrowsing.caretElement) { + if (CaretBrowsing.blinkFlag) { + CaretBrowsing.caretElement.style.backgroundColor = + CaretBrowsing.caretForeground; + CaretBrowsing.blinkFlag = false; + } else { + CaretBrowsing.caretElement.style.backgroundColor = + CaretBrowsing.caretBackground; + CaretBrowsing.blinkFlag = true; + } + } + }; + + CaretBrowsing.updateIsCaretVisible = function() { + CaretBrowsing.isCaretVisible = + (CaretBrowsing.isEnabled && CaretBrowsing.isWindowFocused); + if (CaretBrowsing.isCaretVisible && !CaretBrowsing.caretElement) { + CaretBrowsing.setInitialCursor(); + CaretBrowsing.updateCaretOrSelection(true); + if (CaretBrowsing.caretElement) { + CaretBrowsing.blinkFunctionId = window.setInterval( + CaretBrowsing.caretBlinkFunction, 500); + } + } else if (!CaretBrowsing.isCaretVisible && + CaretBrowsing.caretElement) { + window.clearInterval(CaretBrowsing.blinkFunctionId); + if (CaretBrowsing.caretElement) { + CaretBrowsing.isSelectionCollapsed = false; + CaretBrowsing.caretElement.parentElement.removeChild( + CaretBrowsing.caretElement); + CaretBrowsing.caretElement = null; + } + } + }; + + CaretBrowsing.onWindowFocus = function() { + CaretBrowsing.isWindowFocused = true; + CaretBrowsing.updateIsCaretVisible(); + }; + + CaretBrowsing.onWindowBlur = function() { + CaretBrowsing.isWindowFocused = false; + CaretBrowsing.updateIsCaretVisible(); + }; + + CaretBrowsing.init = function() { + CaretBrowsing.isWindowFocused = document.hasFocus(); + + document.addEventListener("click", CaretBrowsing.onClick, false); + window.addEventListener("focus", CaretBrowsing.onWindowFocus, false); + window.addEventListener("blur", CaretBrowsing.onWindowBlur, false); + }; + + window.setTimeout(() => { + if (!window.caretBrowsingLoaded) { + window.caretBrowsingLoaded = true; + CaretBrowsing.init(); + + if (document.body.getAttribute("caretbrowsing") === "on") { + CaretBrowsing.forceEnabled = true; + CaretBrowsing.isEnabled = true; + CaretBrowsing.updateIsCaretVisible(); + } + } + }, 0); + const funcs = {}; funcs.setInitialCursor = () => { if (!CaretBrowsing.initiated) { CaretBrowsing.setInitialCursor(); - } else { - if (!window.getSelection().toString()) { - positionCaret(); - } - CaretBrowsing.toggle(); + return; } - } + + if (!window.getSelection().toString()) { + positionCaret(); + } + CaretBrowsing.toggle(); + }; funcs.toggle = () => { CaretBrowsing.toggle(); - } + }; funcs.moveRight = () => { - CaretBrowsing.move('right', 'character'); - } + CaretBrowsing.move("right", "character"); + }; funcs.moveLeft = () => { - CaretBrowsing.move('left', 'character'); - } + CaretBrowsing.move("left", "character"); + }; funcs.moveDown = () => { - CaretBrowsing.move('forward', 'line'); - } + CaretBrowsing.move("forward", "line"); + }; funcs.moveUp = () => { - CaretBrowsing.move('backward', 'line'); - } + CaretBrowsing.move("backward", "line"); + }; funcs.moveToEndOfWord = () => { funcs.moveToNextWord(); funcs.moveLeft(); - } + }; funcs.moveToNextWord = () => { - CaretBrowsing.move('forward', 'word'); + CaretBrowsing.move("forward", "word"); funcs.moveRight(); - } + }; funcs.moveToPreviousWord = () => { - CaretBrowsing.move('backward', 'word'); - } + CaretBrowsing.move("backward", "word"); + }; funcs.moveToStartOfLine = () => { - CaretBrowsing.move('left', 'lineboundary'); - } + CaretBrowsing.move("left", "lineboundary"); + }; funcs.moveToEndOfLine = () => { - CaretBrowsing.move('right', 'lineboundary'); - } + CaretBrowsing.move("right", "lineboundary"); + }; funcs.moveToStartOfNextBlock = () => { - CaretBrowsing.moveToBlock('forward', 'backward'); - } + CaretBrowsing.moveToBlock("forward", "backward"); + }; funcs.moveToStartOfPrevBlock = () => { - CaretBrowsing.moveToBlock('backward', 'backward'); - } + CaretBrowsing.moveToBlock("backward", "backward"); + }; funcs.moveToEndOfNextBlock = () => { - CaretBrowsing.moveToBlock('forward', 'forward'); - } + CaretBrowsing.moveToBlock("forward", "forward"); + }; funcs.moveToEndOfPrevBlock = () => { - CaretBrowsing.moveToBlock('backward', 'forward'); - } + CaretBrowsing.moveToBlock("backward", "forward"); + }; funcs.moveToStartOfDocument = () => { - CaretBrowsing.move('backward', 'documentboundary'); - } + CaretBrowsing.move("backward", "documentboundary"); + }; funcs.moveToEndOfDocument = () => { - CaretBrowsing.move('forward', 'documentboundary'); - } + CaretBrowsing.move("forward", "documentboundary"); + }; funcs.dropSelection = () => { window.getSelection().removeAllRanges(); - } + }; - funcs.getSelection = () => { - return window.getSelection().toString(); - } + funcs.getSelection = () => window.getSelection().toString(); funcs.toggleSelection = () => { CaretBrowsing.selectionEnabled = !CaretBrowsing.selectionEnabled; - } + }; return funcs; })();