From 8110de514edaae5c685282668826bf14db7db557 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 27 Sep 2019 23:14:21 -0400 Subject: [PATCH 1/4] Change skipped node types to use a switch statement --- ext/fg/js/source.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js index 4642de50..2e73be06 100644 --- a/ext/fg/js/source.js +++ b/ext/fg/js/source.js @@ -87,9 +87,11 @@ class TextSourceRange { return false; } - const skip = ['RT', 'SCRIPT', 'STYLE']; - if (skip.includes(node.nodeName.toUpperCase())) { - return false; + switch (node.nodeName.toUpperCase()) { + case 'RT': + case 'SCRIPT': + case 'STYLE': + return false; } const style = window.getComputedStyle(node); From 928d7aecd5018ed71f0409fdad95f7c1b2a768bb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 27 Sep 2019 23:14:52 -0400 Subject: [PATCH 2/4] Directly return rather than use a temporary variable --- ext/fg/js/source.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js index 2e73be06..d8b1977b 100644 --- a/ext/fg/js/source.js +++ b/ext/fg/js/source.js @@ -95,12 +95,10 @@ class TextSourceRange { } const style = window.getComputedStyle(node); - const hidden = + return !( style.visibility === 'hidden' || style.display === 'none' || - parseFloat(style.fontSize) === 0; - - return !hidden; + parseFloat(style.fontSize) === 0); } static seekForward(node, offset, length) { From a5f393fa2c95476c5b2af5714b03a50b92a2e109 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 27 Sep 2019 23:37:10 -0400 Subject: [PATCH 3/4] Fix incorrect check --- ext/fg/js/source.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js index d8b1977b..af2119e8 100644 --- a/ext/fg/js/source.js +++ b/ext/fg/js/source.js @@ -234,7 +234,7 @@ class TextSourceRange { if (next !== null) { break; } next = node.parentNode; - if (node === null) { break; } + if (next === null) { break; } node = next; } From 03c52625a98c1f932a19d8377d82b388fa23c78c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 28 Sep 2019 10:07:52 -0400 Subject: [PATCH 4/4] Refactor seekForward and seekBackward --- ext/fg/js/source.js | 172 +++++++++++++++++++++++++++----------------- 1 file changed, 105 insertions(+), 67 deletions(-) diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js index af2119e8..ee4f58e2 100644 --- a/ext/fg/js/source.js +++ b/ext/fg/js/source.js @@ -83,10 +83,6 @@ class TextSourceRange { } static shouldEnter(node) { - if (node.nodeType !== 1) { - return false; - } - switch (node.nodeName.toUpperCase()) { case 'RT': case 'SCRIPT': @@ -101,102 +97,128 @@ class TextSourceRange { parseFloat(style.fontSize) === 0); } + static getRubyElement(node) { + node = TextSourceRange.getParentElement(node); + if (node !== null && node.nodeName.toUpperCase() === "RT") { + node = node.parentNode; + return (node !== null && node.nodeName.toUpperCase() === "RUBY") ? node : null; + } + return null; + } + static seekForward(node, offset, length) { const state = {node, offset, remainder: length, content: ''}; - if (!TextSourceRange.seekForwardHelper(node, state)) { - return state; + const TEXT_NODE = Node.TEXT_NODE; + const ELEMENT_NODE = Node.ELEMENT_NODE; + let resetOffset = false; + + const ruby = TextSourceRange.getRubyElement(node); + if (ruby !== null) { + node = ruby; + resetOffset = true; } - for (let current = node; current !== null; current = current.parentElement) { - for (let sibling = current.nextSibling; sibling !== null; sibling = sibling.nextSibling) { - if (!TextSourceRange.seekForwardHelper(sibling, state)) { - return state; + while (node !== null) { + let visitChildren = true; + const nodeType = node.nodeType; + + if (nodeType === TEXT_NODE) { + state.node = node; + if (TextSourceRange.seekForwardTextNode(state, resetOffset)) { + break; } + resetOffset = true; + } else if (nodeType === ELEMENT_NODE) { + visitChildren = TextSourceRange.shouldEnter(node); } + + node = TextSourceRange.getNextNode(node, visitChildren); } return state; } - static seekForwardHelper(node, state) { - if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { - const offset = state.node === node ? state.offset : 0; + static seekForwardTextNode(state, resetOffset) { + const nodeValue = state.node.nodeValue; + const nodeValueLength = nodeValue.length; + let content = state.content; + let offset = resetOffset ? 0 : state.offset; + let remainder = state.remainder; + let result = false; - let consumed = 0; - let stripped = 0; - while (state.remainder - consumed > 0) { - const currentChar = node.nodeValue[offset + consumed + stripped]; - if (!currentChar) { - break; - } else if (currentChar.match(IGNORE_TEXT_PATTERN)) { - stripped++; - } else { - consumed++; - state.content += currentChar; - } - } - - state.node = node; - state.offset = offset + consumed + stripped; - state.remainder -= consumed; - } else if (TextSourceRange.shouldEnter(node)) { - for (let i = 0; i < node.childNodes.length; ++i) { - if (!TextSourceRange.seekForwardHelper(node.childNodes[i], state)) { + for (; offset < nodeValueLength; ++offset) { + const c = nodeValue[offset]; + if (!IGNORE_TEXT_PATTERN.test(c)) { + content += c; + if (--remainder <= 0) { + result = true; + ++offset; break; } } } - return state.remainder > 0; + state.offset = offset; + state.content = content; + state.remainder = remainder; + return result; } static seekBackward(node, offset, length) { const state = {node, offset, remainder: length, content: ''}; - if (!TextSourceRange.seekBackwardHelper(node, state)) { - return state; + const TEXT_NODE = Node.TEXT_NODE; + const ELEMENT_NODE = Node.ELEMENT_NODE; + let resetOffset = false; + + const ruby = TextSourceRange.getRubyElement(node); + if (ruby !== null) { + node = ruby; + resetOffset = true; } - for (let current = node; current !== null; current = current.parentElement) { - for (let sibling = current.previousSibling; sibling !== null; sibling = sibling.previousSibling) { - if (!TextSourceRange.seekBackwardHelper(sibling, state)) { - return state; + while (node !== null) { + let visitChildren = true; + const nodeType = node.nodeType; + + if (nodeType === TEXT_NODE) { + state.node = node; + if (TextSourceRange.seekBackwardTextNode(state, resetOffset)) { + break; } + resetOffset = true; + } else if (nodeType === ELEMENT_NODE) { + visitChildren = TextSourceRange.shouldEnter(node); } + + node = TextSourceRange.getPreviousNode(node, visitChildren); } return state; } - static seekBackwardHelper(node, state) { - if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { - const offset = state.node === node ? state.offset : node.length; + static seekBackwardTextNode(state, resetOffset) { + const nodeValue = state.node.nodeValue; + let content = state.content; + let offset = resetOffset ? nodeValue.length : state.offset; + let remainder = state.remainder; + let result = false; - let consumed = 0; - let stripped = 0; - while (state.remainder - consumed > 0) { - const currentChar = node.nodeValue[offset - 1 - consumed - stripped]; // negative indices are undefined in JS - if (!currentChar) { - break; - } else if (currentChar.match(IGNORE_TEXT_PATTERN)) { - stripped++; - } else { - consumed++; - state.content = currentChar + state.content; - } - } - - state.node = node; - state.offset = offset - consumed - stripped; - state.remainder -= consumed; - } else if (TextSourceRange.shouldEnter(node)) { - for (let i = node.childNodes.length - 1; i >= 0; --i) { - if (!TextSourceRange.seekBackwardHelper(node.childNodes[i], state)) { + for (; offset > 0; --offset) { + const c = nodeValue[offset - 1]; + if (!IGNORE_TEXT_PATTERN.test(c)) { + content = c + content; + if (--remainder <= 0) { + result = true; + --offset; break; } } } - return state.remainder > 0; + state.offset = offset; + state.content = content; + state.remainder = remainder; + return result; } static getParentElement(node) { @@ -219,15 +241,15 @@ class TextSourceRange { static getNodesInRange(range) { const end = range.endContainer; const nodes = []; - for (let node = range.startContainer; node !== null; node = TextSourceRange.getNextNode(node)) { + for (let node = range.startContainer; node !== null; node = TextSourceRange.getNextNode(node, true)) { nodes.push(node); if (node === end) { break; } } return nodes; } - static getNextNode(node) { - let next = node.firstChild; + static getNextNode(node, visitChildren) { + let next = visitChildren ? node.firstChild : null; if (next === null) { while (true) { next = node.nextSibling; @@ -242,6 +264,22 @@ class TextSourceRange { return next; } + static getPreviousNode(node, visitChildren) { + let next = visitChildren ? node.lastChild : null; + if (next === null) { + while (true) { + next = node.previousSibling; + if (next !== null) { break; } + + next = node.parentNode; + if (next === null) { break; } + + node = next; + } + } + return next; + } + static anyNodeMatchesSelector(nodeList, selector) { for (const node of nodeList) { if (TextSourceRange.nodeMatchesSelector(node, selector)) {