From c7bbfcf5d71d1519d3c575be4faa7e063ae31a11 Mon Sep 17 00:00:00 2001 From: "YUKI \"Piro\" Hiroshi" Date: Sun, 18 Dec 2022 18:21:23 +0900 Subject: [PATCH] Allow to detect URI-like text across boundaries between inline texts and "display:inline-flex" boxes --- webextensions/content_scripts/content.js | 38 ++++++++++++++++--- webextensions/content_scripts/range.js | 16 ++++++-- webextensions/manifest.json | 3 ++ .../deactivate-boundary-inline-elements.css | 16 ++++++++ 4 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 webextensions/resources/deactivate-boundary-inline-elements.css diff --git a/webextensions/content_scripts/content.js b/webextensions/content_scripts/content.js index f8b3834..b8e03c8 100644 --- a/webextensions/content_scripts/content.js +++ b/webextensions/content_scripts/content.js @@ -7,6 +7,8 @@ gLogContext = 'content'; +const INLINE_BOUNDARY_NODE = 'textlink-boundary-inline-node'; + let gTryingAction = false; let gLastActionResult = null; let gMatchAllProgress = 0; @@ -20,10 +22,17 @@ async function onDblClick(event) { gTryingAction = true; gLastActionResult = null; const textFieldSelection = isInputField(event.target); + for (const node of data.boundaryInlineNodes) { + node.classList.add(INLINE_BOUNDARY_NODE); + } gLastActionResult = await browser.runtime.sendMessage({ ...data, + boundaryInlineNodes: [], // don't send raw DOM nodes type: kCOMMAND_TRY_ACTION }); + for (const node of data.boundaryInlineNodes) { + node.classList.remove(INLINE_BOUNDARY_NODE); + } if (textFieldSelection && gLastActionResult && gLastActionResult.range) @@ -53,10 +62,17 @@ async function onKeyDown(event) { gTryingAction = true; gLastActionResult = null; const textFieldSelection = isInputField(event.target); + for (const node of data.boundaryInlineNodes) { + node.classList.add(INLINE_BOUNDARY_NODE); + } gLastActionResult = await browser.runtime.sendMessage({ ...data, + boundaryInlineNodes: [], // don't send raw DOM nodes type: kCOMMAND_TRY_ACTION }); + for (const node of data.boundaryInlineNodes) { + node.classList.remove(INLINE_BOUNDARY_NODE); + } if (textFieldSelection && gLastActionResult && gLastActionResult.range) @@ -200,21 +216,29 @@ async function findURIRanges(options = {}) { } const selectionRanges = []; + const boundaryInlineNodes = []; for (let i = 0, maxi = selection.rangeCount; i < maxi; i++) { const selectionRange = selection.getRangeAt(i); const selectionText = rangeToText(selectionRange); const precedings = getPrecedingRanges(selectionRange); const followings = getFollowingRanges(selectionRange); const rangeData = getRangeData(selectionRange); - rangeData.text = selectionText; - rangeData.expandedText = `${precedings.texts.join('')}${selectionText}${followings.texts.join('')}`; + rangeData.text = selectionText.text; + rangeData.expandedText = `${precedings.texts.join('')}${selectionText.text}${followings.texts.join('')}`; selectionRanges.push(rangeData); + boundaryInlineNodes.push(...selectionText.boundaryInlineNodes, ...precedings.boundaryInlineNodes, ...followings.boundaryInlineNodes); + } + for (const node of boundaryInlineNodes) { + node.classList.add(INLINE_BOUNDARY_NODE); } const ranges = await browser.runtime.sendMessage({ type: kCOMMAND_FIND_URI_RANGES, base: location.href, ranges: selectionRanges }); + for (const node of boundaryInlineNodes) { + node.classList.remove(INLINE_BOUNDARY_NODE); + } gFindingURIRanges = false; return ranges; } @@ -226,17 +250,20 @@ function getSelectionEventData(event) { if (!textFieldSelection && selection.rangeCount != 1) return null; - let text, cursor; + let text, cursor, boundaryInlineNodes; if (textFieldSelection) { cursor = getFieldRangeData(event.target); text = cursor.text; + boundaryInlineNodes = []; } else { const selectionRange = selection.getRangeAt(0); + const selectionText = rangeToText(selectionRange); const precedings = getPrecedingRanges(selectionRange); const followings = getFollowingRanges(selectionRange); - text = `${precedings.texts.join('')}${rangeToText(selectionRange)}${followings.texts.join('')}`; + text = `${precedings.texts.join('')}${selectionText.text}${followings.texts.join('')}`; cursor = getRangeData(selectionRange); + boundaryInlineNodes = [...selectionText.boundaryInlineNodes, ...precedings.boundaryInlineNodes, ...followings.boundaryInlineNodes]; } const data = { @@ -248,7 +275,8 @@ function getSelectionEventData(event) { metaKey: event.metaKey, shiftKey: event.shiftKey, inEditable: textFieldSelection || isEditableNode(event.target) - } + }, + boundaryInlineNodes, }; if (event.type == 'dblclick' && diff --git a/webextensions/content_scripts/range.js b/webextensions/content_scripts/range.js index 4de6ceb..9669f49 100644 --- a/webextensions/content_scripts/range.js +++ b/webextensions/content_scripts/range.js @@ -21,6 +21,7 @@ function rangeToText(range) { result += text; } + const boundaryInlineNodes = []; while (walker.nextNode()) { const node = walker.currentNode; const position = range.endContainer.compareDocumentPosition(node); @@ -36,9 +37,14 @@ function rangeToText(range) { } const { text, state } = nodeToText(node); result += text; + if (state == STATE_CONTINUE_VISUALLY) + boundaryInlineNodes.push(node.offsetParent); } - return result; // .replace(/\n\s*|\s*\n/g, '\n'); + return { + text: result, // .replace(/\n\s*|\s*\n/g, '\n') + boundaryInlineNodes, + }; } function nodeToText(node) { @@ -76,6 +82,7 @@ function nodeToText(node) { function getPrecedingRanges(sourceRange) { const texts = []; const ranges = []; + const boundaryInlineNodes = []; const range = document.createRange(); range.setStart(sourceRange.startContainer, sourceRange.startOffset); range.setEnd(sourceRange.startContainer, sourceRange.startOffset); @@ -106,6 +113,7 @@ function getPrecedingRanges(sourceRange) { case STATE_CONTINUE_VISUALLY: texts.unshift(text); ranges.unshift(range.cloneRange()); + boundaryInlineNodes.unshift(walker.currentNode.offsetParent); text = partialText; range.collapse(true); continue; @@ -116,12 +124,13 @@ function getPrecedingRanges(sourceRange) { } texts.unshift(text); ranges.unshift(range); - return { texts, ranges }; + return { texts, ranges, boundaryInlineNodes }; } function getFollowingRanges(sourceRange) { const texts = []; const ranges = []; + const boundaryInlineNodes = []; const range = document.createRange(); range.setStart(sourceRange.endContainer, sourceRange.endOffset); range.setEnd(sourceRange.endContainer, sourceRange.endOffset); @@ -152,6 +161,7 @@ function getFollowingRanges(sourceRange) { case STATE_CONTINUE_VISUALLY: texts.push(text); ranges.push(range.cloneRange()); + boundaryInlineNodes.push(walker.currentNode.offsetParent); text = partialText; range.collapse(false); continue; @@ -162,7 +172,7 @@ function getFollowingRanges(sourceRange) { } texts.push(text); ranges.push(range); - return { texts, ranges }; + return { texts, ranges, boundaryInlineNodes }; } function createVisibleTextNodeWalker() { diff --git a/webextensions/manifest.json b/webextensions/manifest.json index decfea4..2bbe024 100644 --- a/webextensions/manifest.json +++ b/webextensions/manifest.json @@ -34,6 +34,9 @@ "content_scripts/xpath.js", "content_scripts/range.js", "content_scripts/content.js" + ], + "css": [ + "resources/deactivate-boundary-inline-elements.css" ] } ], diff --git a/webextensions/resources/deactivate-boundary-inline-elements.css b/webextensions/resources/deactivate-boundary-inline-elements.css new file mode 100644 index 0000000..aca021f --- /dev/null +++ b/webextensions/resources/deactivate-boundary-inline-elements.css @@ -0,0 +1,16 @@ +/* +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +/* + An elements with "display:inline-flex" is visually rendered as a regular + inline box, but it is actually treated as a non-inline box internally. + The API "browser.find.find()" (and Firefox's native in-page find feature also) + fails to find a term across the boundary. Thus we need to change its "display" + temporary while finding URI-like texts in webpages. +*/ +.textlink-boundary-inline-node:not(#never#match#to#any#element) { + display: inline !important; +}