From c61a87b152b91bdebe01eefdbc3fa00670a3071d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 24 May 2020 13:30:40 -0400 Subject: API refactor (#532) * Convert api.js into a class instance * Use new api.* functions * Fix missing binds * Group functions with progress callbacks together * Change style * Fix API override not working --- ext/bg/js/search-query-parser.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'ext/bg/js/search-query-parser.js') diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index e1e37d1c..addfc686 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -18,9 +18,7 @@ /* global * QueryParserGenerator * TextScanner - * apiModifySettings - * apiTermsFind - * apiTextParse + * api * docSentenceExtract */ @@ -59,7 +57,7 @@ class QueryParser { this._setPreview(text); - this._parseResults = await apiTextParse(text, this._getOptionsContext()); + this._parseResults = await api.textParse(text, this._getOptionsContext()); this._refreshSelectedParser(); this._renderParserSelect(); @@ -80,7 +78,7 @@ class QueryParser { const searchText = this._textScanner.getTextSourceContent(textSource, this._options.scanning.length); if (searchText.length === 0) { return null; } - const {definitions, length} = await apiTermsFind(searchText, {}, this._getOptionsContext()); + const {definitions, length} = await api.termsFind(searchText, {}, this._getOptionsContext()); if (definitions.length === 0) { return null; } const sentence = docSentenceExtract(textSource, this._options.anki.sentenceExt); @@ -99,7 +97,7 @@ class QueryParser { _onParserChange(e) { const value = e.target.value; - apiModifySettings([{ + api.modifySettings([{ action: 'set', path: 'parsing.selectedParser', value, @@ -112,7 +110,7 @@ class QueryParser { if (this._parseResults.length > 0) { if (!this._getParseResult()) { const value = this._parseResults[0].id; - apiModifySettings([{ + api.modifySettings([{ action: 'set', path: 'parsing.selectedParser', value, -- cgit v1.2.3 From e23504613f8526b90a497512c086ed48e66cde95 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 21 Jun 2020 16:07:51 -0400 Subject: Use DOMTextScanner (#536) * Use DOMTextScanner instead of TextSourceRange.seek* * Move getNodesInRange to dom.js * Move anyNodeMatchesSelector to dom.js * Remove unused functions * Update tests * Add layoutAwareScan option * Use layoutAwareScan for source and sentence scanning * Remove unused IGNORE_TEXT_PATTERN --- ext/bg/data/options-schema.json | 7 +- ext/bg/js/options.js | 3 +- ext/bg/js/search-query-parser.js | 8 +- ext/bg/search.html | 1 + ext/bg/settings-popup-preview.html | 1 + ext/bg/settings.html | 4 + ext/fg/float.html | 1 + ext/fg/js/document.js | 11 +- ext/fg/js/frontend.js | 14 ++- ext/fg/js/source.js | 224 ++----------------------------------- ext/manifest.json | 1 + ext/mixed/js/display.js | 11 +- ext/mixed/js/dom.js | 38 +++++++ ext/mixed/js/text-scanner.js | 11 +- test/test-document.js | 14 ++- 15 files changed, 102 insertions(+), 247 deletions(-) (limited to 'ext/bg/js/search-query-parser.js') diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index 0379fa75..5885e036 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -321,7 +321,8 @@ "enablePopupSearch", "enableOnPopupExpressions", "enableOnSearchPage", - "enableSearchTags" + "enableSearchTags", + "layoutAwareScan" ], "properties": { "middleMouse": { @@ -383,6 +384,10 @@ "enableSearchTags": { "type": "boolean", "default": false + }, + "layoutAwareScan": { + "type": "boolean", + "default": false } } }, diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 97368a0b..170e4799 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -203,7 +203,8 @@ function profileOptionsCreateDefaults() { enablePopupSearch: false, enableOnPopupExpressions: false, enableOnSearchPage: true, - enableSearchTags: false + enableSearchTags: false, + layoutAwareScan: false }, translation: { diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index addfc686..97e98b40 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -75,15 +75,17 @@ class QueryParser { async _search(textSource, cause) { if (textSource === null) { return null; } - const searchText = this._textScanner.getTextSourceContent(textSource, this._options.scanning.length); + const {length: scanLength, layoutAwareScan} = this._options.scanning; + const searchText = this._textScanner.getTextSourceContent(textSource, scanLength, layoutAwareScan); if (searchText.length === 0) { return null; } const {definitions, length} = await api.termsFind(searchText, {}, this._getOptionsContext()); if (definitions.length === 0) { return null; } - const sentence = docSentenceExtract(textSource, this._options.anki.sentenceExt); + const sentenceExtent = this._options.anki.sentenceExt; + const sentence = docSentenceExtract(textSource, sentenceExtent, layoutAwareScan); - textSource.setEndOffset(length); + textSource.setEndOffset(length, layoutAwareScan); this._setContent('terms', {definitions, context: { focus: false, diff --git a/ext/bg/search.html b/ext/bg/search.html index de08cdae..4a28dd88 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -79,6 +79,7 @@ + diff --git a/ext/bg/settings-popup-preview.html b/ext/bg/settings-popup-preview.html index fe92f24f..5eecd005 100644 --- a/ext/bg/settings-popup-preview.html +++ b/ext/bg/settings-popup-preview.html @@ -126,6 +126,7 @@ + diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 118a13b9..77b61aef 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -400,6 +400,10 @@ +
+ +
+
diff --git a/ext/fg/float.html b/ext/fg/float.html index 17dbcc6d..3e41cde5 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -46,6 +46,7 @@ + diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js index d639bc86..c288502c 100644 --- a/ext/fg/js/document.js +++ b/ext/fg/js/document.js @@ -17,6 +17,7 @@ /* global * DOM + * DOMTextScanner * TextSourceElement * TextSourceRange */ @@ -152,14 +153,14 @@ function docRangeFromPoint(x, y, deepDomScan) { } } -function docSentenceExtract(source, extent) { +function docSentenceExtract(source, extent, layoutAwareScan) { const quotesFwd = {'「': '」', '『': '』', "'": "'", '"': '"'}; const quotesBwd = {'」': '「', '』': '『', "'": "'", '"': '"'}; const terminators = '…。..??!!'; const sourceLocal = source.clone(); - const position = sourceLocal.setStartOffset(extent); - sourceLocal.setEndOffset(extent * 2 - position, true); + const position = sourceLocal.setStartOffset(extent, layoutAwareScan); + sourceLocal.setEndOffset(extent * 2 - position, layoutAwareScan, true); const content = sourceLocal.text(); let quoteStack = []; @@ -232,7 +233,7 @@ function isPointInRange(x, y, range) { const nodePre = range.endContainer; const offsetPre = range.endOffset; try { - const {node, offset, content} = TextSourceRange.seekForward(range.endContainer, range.endOffset, 1); + const {node, offset, content} = new DOMTextScanner(range.endContainer, range.endOffset, true, false).seek(1); range.setEnd(node, offset); if (!isWhitespace(content) && DOM.isPointInAnyRect(x, y, range.getClientRects())) { @@ -243,7 +244,7 @@ function isPointInRange(x, y, range) { } // Scan backward - const {node, offset, content} = TextSourceRange.seekBackward(range.startContainer, range.startOffset, 1); + const {node, offset, content} = new DOMTextScanner(range.startContainer, range.startOffset, true, false).seek(-1); range.setStart(node, offset); if (!isWhitespace(content) && DOM.isPointInAnyRect(x, y, range.getClientRects())) { diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 70bd8a48..ab455c09 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -258,32 +258,36 @@ class Frontend { } async _findTerms(textSource, optionsContext) { - const searchText = this._textScanner.getTextSourceContent(textSource, this._options.scanning.length); + const {length: scanLength, layoutAwareScan} = this._options.scanning; + const searchText = this._textScanner.getTextSourceContent(textSource, scanLength, layoutAwareScan); if (searchText.length === 0) { return null; } const {definitions, length} = await api.termsFind(searchText, {}, optionsContext); if (definitions.length === 0) { return null; } - textSource.setEndOffset(length); + textSource.setEndOffset(length, layoutAwareScan); return {definitions, type: 'terms'}; } async _findKanji(textSource, optionsContext) { - const searchText = this._textScanner.getTextSourceContent(textSource, 1); + const layoutAwareScan = this._options.scanning.layoutAwareScan; + const searchText = this._textScanner.getTextSourceContent(textSource, 1, layoutAwareScan); if (searchText.length === 0) { return null; } const definitions = await api.kanjiFind(searchText, optionsContext); if (definitions.length === 0) { return null; } - textSource.setEndOffset(1); + textSource.setEndOffset(1, layoutAwareScan); return {definitions, type: 'kanji'}; } _showContent(textSource, focus, definitions, type, optionsContext) { const {url} = optionsContext; - const sentence = docSentenceExtract(textSource, this._options.anki.sentenceExt); + const sentenceExtent = this._options.anki.sentenceExt; + const layoutAwareScan = this._options.scanning.layoutAwareScan; + const sentence = docSentenceExtract(textSource, sentenceExtent, layoutAwareScan); this._showPopupContent( textSource, optionsContext, diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js index fa4706f2..38810f07 100644 --- a/ext/fg/js/source.js +++ b/ext/fg/js/source.js @@ -15,9 +15,9 @@ * along with this program. If not, see . */ -// \u200c (Zero-width non-joiner) appears on Google Docs from Chrome 76 onwards -const IGNORE_TEXT_PATTERN = /\u200c/; - +/* global + * DOMTextScanner + */ /* * TextSourceRange @@ -46,19 +46,19 @@ class TextSourceRange { return this.content; } - setEndOffset(length, fromEnd=false) { + setEndOffset(length, layoutAwareScan, fromEnd=false) { const state = ( fromEnd ? - TextSourceRange.seekForward(this.range.endContainer, this.range.endOffset, length) : - TextSourceRange.seekForward(this.range.startContainer, this.range.startOffset, length) + new DOMTextScanner(this.range.endContainer, this.range.endOffset, !layoutAwareScan, layoutAwareScan).seek(length) : + new DOMTextScanner(this.range.startContainer, this.range.startOffset, !layoutAwareScan, layoutAwareScan).seek(length) ); this.range.setEnd(state.node, state.offset); this.content = (fromEnd ? this.content + state.content : state.content); return length - state.remainder; } - setStartOffset(length) { - const state = TextSourceRange.seekBackward(this.range.startContainer, this.range.startOffset, length); + setStartOffset(length, layoutAwareScan) { + const state = new DOMTextScanner(this.range.startContainer, this.range.startOffset, !layoutAwareScan, layoutAwareScan).seek(-length); this.range.setStart(state.node, state.offset); this.rangeStartOffset = this.range.startOffset; this.content = state.content + this.content; @@ -110,154 +110,6 @@ class TextSourceRange { } } - static shouldEnter(node) { - switch (node.nodeName.toUpperCase()) { - case 'RT': - case 'SCRIPT': - case 'STYLE': - return false; - } - - const style = window.getComputedStyle(node); - return !( - style.visibility === 'hidden' || - style.display === 'none' || - 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 (length <= 0) { - 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; - } - - 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 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; - - for (; offset < nodeValueLength; ++offset) { - const c = nodeValue[offset]; - if (!IGNORE_TEXT_PATTERN.test(c)) { - content += c; - if (--remainder <= 0) { - result = true; - ++offset; - break; - } - } - } - - state.offset = offset; - state.content = content; - state.remainder = remainder; - return result; - } - - static seekBackward(node, offset, length) { - const state = {node, offset, remainder: length, content: ''}; - if (length <= 0) { - 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; - } - - 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 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; - - 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; - } - } - } - - state.offset = offset; - state.content = content; - state.remainder = remainder; - return result; - } - static getParentElement(node) { while (node !== null && node.nodeType !== Node.ELEMENT_NODE) { node = node.parentNode; @@ -290,66 +142,6 @@ class TextSourceRange { return writingMode; } } - - static getNodesInRange(range) { - const end = range.endContainer; - const nodes = []; - for (let node = range.startContainer; node !== null; node = TextSourceRange.getNextNode(node, true)) { - nodes.push(node); - if (node === end) { break; } - } - return nodes; - } - - static getNextNode(node, visitChildren) { - let next = visitChildren ? node.firstChild : null; - if (next === null) { - while (true) { - next = node.nextSibling; - if (next !== null) { break; } - - next = node.parentNode; - if (next === null) { break; } - - node = next; - } - } - 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)) { - return true; - } - } - return false; - } - - static nodeMatchesSelector(node, selector) { - for (; node !== null; node = node.parentNode) { - if (node.nodeType === Node.ELEMENT_NODE) { - return node.matches(selector); - } - } - return false; - } } diff --git a/ext/manifest.json b/ext/manifest.json index 75334675..4d4f0c06 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -42,6 +42,7 @@ "mixed/js/dynamic-loader.js", "mixed/js/text-scanner.js", "fg/js/document.js", + "fg/js/dom-text-scanner.js", "fg/js/popup.js", "fg/js/source.js", "fg/js/popup-factory.js", diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 90fd1037..1d699706 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -236,7 +236,9 @@ class Display { const {textSource, definitions} = termLookupResults; const scannedElement = e.target; - const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); + const sentenceExtent = this.options.anki.sentenceExt; + const layoutAwareScan = this.options.scanning.layoutAwareScan; + const sentence = docSentenceExtract(textSource, sentenceExtent, layoutAwareScan); this.context.update({ index: this.entryIndexFind(scannedElement), @@ -273,21 +275,22 @@ class Display { try { e.preventDefault(); - const textSource = docRangeFromPoint(e.clientX, e.clientY, this.options.scanning.deepDomScan); + const {length: scanLength, deepDomScan: deepScan, layoutAwareScan} = this.options.scanning; + const textSource = docRangeFromPoint(e.clientX, e.clientY, deepScan); if (textSource === null) { return false; } let definitions, length; try { - textSource.setEndOffset(this.options.scanning.length); + textSource.setEndOffset(scanLength, layoutAwareScan); ({definitions, length} = await api.termsFind(textSource.text(), {}, this.getOptionsContext())); if (definitions.length === 0) { return false; } - textSource.setEndOffset(length); + textSource.setEndOffset(length, layoutAwareScan); } finally { textSource.cleanup(); } diff --git a/ext/mixed/js/dom.js b/ext/mixed/js/dom.js index 0e8f4462..05764443 100644 --- a/ext/mixed/js/dom.js +++ b/ext/mixed/js/dom.js @@ -86,4 +86,42 @@ class DOM { null ); } + + static getNodesInRange(range) { + const end = range.endContainer; + const nodes = []; + for (let node = range.startContainer; node !== null; node = DOM.getNextNode(node)) { + nodes.push(node); + if (node === end) { break; } + } + return nodes; + } + + static getNextNode(node) { + let next = node.firstChild; + if (next === null) { + while (true) { + next = node.nextSibling; + if (next !== null) { break; } + + next = node.parentNode; + if (next === null) { break; } + + node = next; + } + } + return next; + } + + static anyNodeMatchesSelector(nodes, selector) { + const ELEMENT_NODE = Node.ELEMENT_NODE; + for (let node of nodes) { + for (; node !== null; node = node.parentNode) { + if (node.nodeType !== ELEMENT_NODE) { continue; } + if (node.matches(selector)) { return true; } + break; + } + } + return false; + } } diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index b8688b08..fb275452 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -17,7 +17,6 @@ /* global * DOM - * TextSourceRange * docRangeFromPoint */ @@ -119,20 +118,20 @@ class TextScanner extends EventDispatcher { } } - getTextSourceContent(textSource, length) { + getTextSourceContent(textSource, length, layoutAwareScan) { const clonedTextSource = textSource.clone(); - clonedTextSource.setEndOffset(length); + clonedTextSource.setEndOffset(length, layoutAwareScan); if (this._ignoreNodes !== null && clonedTextSource.range) { length = clonedTextSource.text().length; while (clonedTextSource.range && length > 0) { - const nodes = TextSourceRange.getNodesInRange(clonedTextSource.range); - if (!TextSourceRange.anyNodeMatchesSelector(nodes, this._ignoreNodes)) { + const nodes = DOM.getNodesInRange(clonedTextSource.range); + if (!DOM.anyNodeMatchesSelector(nodes, this._ignoreNodes)) { break; } --length; - clonedTextSource.setEndOffset(length); + clonedTextSource.setEndOffset(length, layoutAwareScan); } } diff --git a/test/test-document.js b/test/test-document.js index 0d9026db..ba7acc49 100644 --- a/test/test-document.js +++ b/test/test-document.js @@ -94,10 +94,12 @@ async function testDocument1() { const vm = new VM({document, window, Range, Node}); vm.execute([ 'mixed/js/dom.js', + 'fg/js/dom-text-scanner.js', 'fg/js/source.js', 'fg/js/document.js' ]); - const [TextSourceRange, TextSourceElement, docRangeFromPoint, docSentenceExtract] = vm.get([ + const [DOMTextScanner, TextSourceRange, TextSourceElement, docRangeFromPoint, docSentenceExtract] = vm.get([ + 'DOMTextScanner', 'TextSourceRange', 'TextSourceElement', 'docRangeFromPoint', @@ -106,7 +108,7 @@ async function testDocument1() { try { await testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}); - await testTextSourceRangeSeekFunctions(dom, {TextSourceRange}); + await testTextSourceRangeSeekFunctions(dom, {DOMTextScanner}); } finally { window.close(); } @@ -179,7 +181,7 @@ async function testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSen if (source === null) { continue; } // Test docSentenceExtract - const sentenceActual = docSentenceExtract(source, sentenceExtent).text; + const sentenceActual = docSentenceExtract(source, sentenceExtent, false).text; assert.strictEqual(sentenceActual, sentence); // Clean @@ -187,7 +189,7 @@ async function testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSen } } -async function testTextSourceRangeSeekFunctions(dom, {TextSourceRange}) { +async function testTextSourceRangeSeekFunctions(dom, {DOMTextScanner}) { const document = dom.window.document; for (const testElement of document.querySelectorAll('.test[data-test-type=text-source-range-seek]')) { @@ -220,8 +222,8 @@ async function testTextSourceRangeSeekFunctions(dom, {TextSourceRange}) { const {node, offset, content} = ( seekDirection === 'forward' ? - TextSourceRange.seekForward(seekNode, seekOffset, seekLength) : - TextSourceRange.seekBackward(seekNode, seekOffset, seekLength) + new DOMTextScanner(seekNode, seekOffset, true, false).seek(seekLength) : + new DOMTextScanner(seekNode, seekOffset, true, false).seek(-seekLength) ); assert.strictEqual(node, expectedResultNode); -- cgit v1.2.3 From f2991fb9ee8e83738b726eb558af992f4bb5d9dc Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 21 Jun 2020 16:14:05 -0400 Subject: Frontend initialization refactor (#610) * Create member functions for ignoreElements and ignorePoint * Create addFullscreenChangeEventListener utility * Move popup creation management into Frontend * Move getUrl implementation * Remove old setup * Remove try/catch block * Error wrap * Add prepare call to TextScanner * Update depth when popup changes * Refactor how Frontend gets PopupFactory and frameId * Update popup preview to work * Update popup preview frame to use the frontend's popup * Update how nested popups are set up * Error wrap * Update how popups are set up on the search page * Error wrap * Error unwrap * Add missing prepare * Remove use of frontendInitializationData * Catch and log errors --- ext/bg/js/search-main.js | 44 +---- ext/bg/js/search-query-parser.js | 1 + ext/bg/js/search.js | 120 +++++++++---- ext/bg/js/settings/popup-preview-frame-main.js | 17 +- ext/bg/js/settings/popup-preview-frame.js | 46 +++-- ext/bg/settings-popup-preview.html | 1 + ext/fg/js/content-script-main.js | 152 ++-------------- ext/fg/js/float-main.js | 43 +---- ext/fg/js/float.js | 65 ++++++- ext/fg/js/frontend.js | 231 ++++++++++++++++++++----- ext/fg/js/popup-factory.js | 7 +- ext/fg/js/popup-proxy.js | 4 - ext/fg/js/popup.js | 11 +- ext/mixed/js/dom.js | 18 ++ ext/mixed/js/text-scanner.js | 8 +- 15 files changed, 437 insertions(+), 331 deletions(-) (limited to 'ext/bg/js/search-query-parser.js') diff --git a/ext/bg/js/search-main.js b/ext/bg/js/search-main.js index f18d6d88..13bd8767 100644 --- a/ext/bg/js/search-main.js +++ b/ext/bg/js/search-main.js @@ -18,42 +18,16 @@ /* global * DisplaySearch * api - * dynamicLoader */ -async function injectSearchFrontend() { - await dynamicLoader.loadScripts([ - '/mixed/js/text-scanner.js', - '/fg/js/frame-offset-forwarder.js', - '/fg/js/popup.js', - '/fg/js/popup-factory.js', - '/fg/js/frontend.js', - '/fg/js/content-script-main.js' - ]); -} - (async () => { - api.forwardLogsToBackend(); - await yomichan.prepare(); - - const displaySearch = new DisplaySearch(); - await displaySearch.prepare(); - - let optionsApplied = false; - - const applyOptions = async () => { - const optionsContext = {depth: 0, url: window.location.href}; - const options = await api.optionsGet(optionsContext); - if (!options.scanning.enableOnSearchPage || optionsApplied) { return; } - - optionsApplied = true; - yomichan.off('optionsUpdated', applyOptions); - - window.frontendInitializationData = {depth: 1, proxy: false, isSearchPage: true}; - await injectSearchFrontend(); - }; - - yomichan.on('optionsUpdated', applyOptions); - - await applyOptions(); + try { + api.forwardLogsToBackend(); + await yomichan.prepare(); + + const displaySearch = new DisplaySearch(); + await displaySearch.prepare(); + } catch (e) { + yomichan.logError(e); + } })(); diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 97e98b40..86524b66 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -42,6 +42,7 @@ class QueryParser { async prepare() { await this._queryParserGenerator.prepare(); + this._textScanner.prepare(); this._queryParser.addEventListener('click', this._onClick.bind(this)); } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 08c02624..88be335f 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -19,8 +19,11 @@ * ClipboardMonitor * DOM * Display + * Frontend + * PopupFactory * QueryParser * api + * dynamicLoader * wanakana */ @@ -73,51 +76,49 @@ class DisplaySearch extends Display { } async prepare() { - try { - await super.prepare(); - await this.updateOptions(); - yomichan.on('optionsUpdated', () => this.updateOptions()); - await this.queryParser.prepare(); + await super.prepare(); + await this.updateOptions(); + yomichan.on('optionsUpdated', () => this.updateOptions()); + await this.queryParser.prepare(); + + const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); + + document.documentElement.dataset.searchMode = mode; - const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); + if (this.options.general.enableWanakana === true) { + this.wanakanaEnable.checked = true; + wanakana.bind(this.query); + } else { + this.wanakanaEnable.checked = false; + } - document.documentElement.dataset.searchMode = mode; + this.setQuery(query); + this.onSearchQueryUpdated(this.query.value, false); - if (this.options.general.enableWanakana === true) { - this.wanakanaEnable.checked = true; - wanakana.bind(this.query); + if (mode !== 'popup') { + if (this.options.general.enableClipboardMonitor === true) { + this.clipboardMonitorEnable.checked = true; + this.clipboardMonitor.start(); } else { - this.wanakanaEnable.checked = false; + this.clipboardMonitorEnable.checked = false; } + this.clipboardMonitorEnable.addEventListener('change', this.onClipboardMonitorEnableChange.bind(this)); + } - this.setQuery(query); - this.onSearchQueryUpdated(this.query.value, false); - - if (mode !== 'popup') { - if (this.options.general.enableClipboardMonitor === true) { - this.clipboardMonitorEnable.checked = true; - this.clipboardMonitor.start(); - } else { - this.clipboardMonitorEnable.checked = false; - } - this.clipboardMonitorEnable.addEventListener('change', this.onClipboardMonitorEnableChange.bind(this)); - } + chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); - chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); + this.search.addEventListener('click', this.onSearch.bind(this), false); + this.query.addEventListener('input', this.onSearchInput.bind(this), false); + this.wanakanaEnable.addEventListener('change', this.onWanakanaEnableChange.bind(this)); + window.addEventListener('popstate', this.onPopState.bind(this)); + window.addEventListener('copy', this.onCopy.bind(this)); + this.clipboardMonitor.on('change', this.onExternalSearchUpdate.bind(this)); - this.search.addEventListener('click', this.onSearch.bind(this), false); - this.query.addEventListener('input', this.onSearchInput.bind(this), false); - this.wanakanaEnable.addEventListener('change', this.onWanakanaEnableChange.bind(this)); - window.addEventListener('popstate', this.onPopState.bind(this)); - window.addEventListener('copy', this.onCopy.bind(this)); - this.clipboardMonitor.on('change', this.onExternalSearchUpdate.bind(this)); + this.updateSearchButton(); - this.updateSearchButton(); + await this._prepareNestedPopups(); - this._isPrepared = true; - } catch (e) { - this.onError(e); - } + this._isPrepared = true; } onError(error) { @@ -401,4 +402,53 @@ class DisplaySearch extends Display { document.title = `${text} - Yomichan Search`; } } + + async _prepareNestedPopups() { + let complete = false; + + const onOptionsUpdated = async () => { + const optionsContext = this.getOptionsContext(); + const options = await api.optionsGet(optionsContext); + if (!options.scanning.enableOnSearchPage || complete) { return; } + + complete = true; + yomichan.off('optionsUpdated', onOptionsUpdated); + + try { + await this._setupNestedPopups(); + } catch (e) { + yomichan.logError(e); + } + }; + + yomichan.on('optionsUpdated', onOptionsUpdated); + + await onOptionsUpdated(); + } + + async _setupNestedPopups() { + await dynamicLoader.loadScripts([ + '/mixed/js/text-scanner.js', + '/fg/js/frame-offset-forwarder.js', + '/fg/js/popup.js', + '/fg/js/popup-factory.js', + '/fg/js/frontend.js' + ]); + + const {frameId} = await api.frameInformationGet(); + + const popupFactory = new PopupFactory(frameId); + await popupFactory.prepare(); + + const frontend = new Frontend( + frameId, + popupFactory, + { + depth: 1, + proxy: false, + isSearchPage: true + } + ); + await frontend.prepare(); + } } diff --git a/ext/bg/js/settings/popup-preview-frame-main.js b/ext/bg/js/settings/popup-preview-frame-main.js index 7c4e2eb9..4c6096ec 100644 --- a/ext/bg/js/settings/popup-preview-frame-main.js +++ b/ext/bg/js/settings/popup-preview-frame-main.js @@ -16,12 +16,23 @@ */ /* global + * PopupFactory * PopupPreviewFrame * api */ (async () => { - api.forwardLogsToBackend(); - const preview = new PopupPreviewFrame(); - await preview.prepare(); + try { + api.forwardLogsToBackend(); + + const {frameId} = await api.frameInformationGet(); + + const popupFactory = new PopupFactory(frameId); + await popupFactory.prepare(); + + const preview = new PopupPreviewFrame(frameId, popupFactory); + await preview.prepare(); + } catch (e) { + yomichan.logError(e); + } })(); diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 21fee7ee..98630503 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -18,17 +18,17 @@ /* global * Frontend * Popup - * PopupFactory * TextSourceRange * api */ class PopupPreviewFrame { - constructor() { + constructor(frameId, popupFactory) { + this._frameId = frameId; + this._popupFactory = popupFactory; this._frontend = null; this._frontendGetOptionsContextOld = null; this._apiOptionsGetOld = null; - this._popup = null; this._popupSetCustomOuterCssOld = null; this._popupShown = false; this._themeChangeTimeout = null; @@ -55,24 +55,25 @@ class PopupPreviewFrame { api.optionsGet = this._apiOptionsGet.bind(this); // Overwrite frontend - const {frameId} = await api.frameInformationGet(); - - const popupFactory = new PopupFactory(frameId); - await popupFactory.prepare(); - - this._popup = popupFactory.getOrCreatePopup(); - this._popup.setChildrenSupported(false); - - this._popupSetCustomOuterCssOld = this._popup.setCustomOuterCss.bind(this._popup); - this._popup.setCustomOuterCss = this._popupSetCustomOuterCss.bind(this); - - this._frontend = new Frontend(this._popup); + this._frontend = new Frontend( + this._frameId, + this._popupFactory, + { + allowRootFramePopupProxy: false + } + ); this._frontendGetOptionsContextOld = this._frontend.getOptionsContext.bind(this._frontend); this._frontend.getOptionsContext = this._getOptionsContext.bind(this); await this._frontend.prepare(); this._frontend.setDisabledOverride(true); this._frontend.canClearSelection = false; + const popup = this._frontend.popup; + popup.setChildrenSupported(false); + + this._popupSetCustomOuterCssOld = popup.setCustomOuterCss.bind(popup); + popup.setCustomOuterCss = this._popupSetCustomOuterCss.bind(this); + // Update search this._updateSearch(); } @@ -132,7 +133,9 @@ class PopupPreviewFrame { } this._themeChangeTimeout = setTimeout(() => { this._themeChangeTimeout = null; - this._popup.updateTheme(); + const popup = this._frontend.popup; + if (popup === null) { return; } + popup.updateTheme(); }, 300); } @@ -154,12 +157,16 @@ class PopupPreviewFrame { _setCustomCss({css}) { if (this._frontend === null) { return; } - this._popup.setCustomCss(css); + const popup = this._frontend.popup; + if (popup === null) { return; } + popup.setCustomCss(css); } _setCustomOuterCss({css}) { if (this._frontend === null) { return; } - this._popup.setCustomOuterCss(css, false); + const popup = this._frontend.popup; + if (popup === null) { return; } + popup.setCustomOuterCss(css, false); } async _updateOptionsContext({optionsContext}) { @@ -188,7 +195,8 @@ class PopupPreviewFrame { this._textSource = source; await this._frontend.showContentCompleted(); - if (this._popup.isVisibleSync()) { + const popup = this._frontend.popup; + if (popup !== null && popup.isVisibleSync()) { this._popupShown = true; } diff --git a/ext/bg/settings-popup-preview.html b/ext/bg/settings-popup-preview.html index 5eecd005..75bf06c8 100644 --- a/ext/bg/settings-popup-preview.html +++ b/ext/bg/settings-popup-preview.html @@ -131,6 +131,7 @@ + diff --git a/ext/fg/js/content-script-main.js b/ext/fg/js/content-script-main.js index c31cde3f..c4aa1bca 100644 --- a/ext/fg/js/content-script-main.js +++ b/ext/fg/js/content-script-main.js @@ -16,147 +16,31 @@ */ /* global - * DOM - * FrameOffsetForwarder * Frontend * PopupFactory - * PopupProxy * api */ -async function createPopupFactory() { - const {frameId} = await api.frameInformationGet(); - if (typeof frameId !== 'number') { - const error = new Error('Failed to get frameId'); - yomichan.logError(error); - throw error; - } - - const popupFactory = new PopupFactory(frameId); - await popupFactory.prepare(); - return popupFactory; -} - -async function createIframePopupProxy(frameOffsetForwarder, setDisabled) { - const rootPopupInformationPromise = yomichan.getTemporaryListenerResult( - chrome.runtime.onMessage, - ({action, params}, {resolve}) => { - if (action === 'rootPopupInformation') { - resolve(params); - } - } - ); - api.broadcastTab('rootPopupRequestInformationBroadcast'); - const {popupId, frameId: parentFrameId} = await rootPopupInformationPromise; - - const popup = new PopupProxy(popupId, 0, null, parentFrameId, frameOffsetForwarder); - popup.on('offsetNotFound', setDisabled); - await popup.prepare(); - - return popup; -} - -async function getOrCreatePopup(depth, popupFactory) { - return popupFactory.getOrCreatePopup(null, null, depth); -} - -async function createPopupProxy(depth, id, parentFrameId) { - const popup = new PopupProxy(null, depth + 1, id, parentFrameId); - await popup.prepare(); - - return popup; -} - (async () => { - api.forwardLogsToBackend(); - await yomichan.prepare(); - - const data = window.frontendInitializationData || {}; - const {id, depth=0, parentFrameId, url=window.location.href, proxy=false, isSearchPage=false} = data; - - const isIframe = !proxy && (window !== window.parent); - - const popups = { - iframe: null, - proxy: null, - normal: null - }; + try { + api.forwardLogsToBackend(); + await yomichan.prepare(); - let frontend = null; - let frontendPreparePromise = null; - let frameOffsetForwarder = null; - let popupFactoryPromise = null; - - let iframePopupsInRootFrameAvailable = true; - - const disableIframePopupsInRootFrame = () => { - iframePopupsInRootFrameAvailable = false; - applyOptions(); - }; - - let urlUpdatedAt = 0; - let popupProxyUrlCached = url; - const getPopupProxyUrl = async () => { - const now = Date.now(); - if (popups.proxy !== null && now - urlUpdatedAt > 500) { - popupProxyUrlCached = await popups.proxy.getUrl(); - urlUpdatedAt = now; + const {frameId} = await api.frameInformationGet(); + if (typeof frameId !== 'number') { + throw new Error('Failed to get frameId'); } - return popupProxyUrlCached; - }; - - const applyOptions = async () => { - const optionsContext = { - depth: isSearchPage ? 0 : depth, - url: proxy ? await getPopupProxyUrl() : window.location.href - }; - const options = await api.optionsGet(optionsContext); - if (!proxy && frameOffsetForwarder === null) { - frameOffsetForwarder = new FrameOffsetForwarder(); - frameOffsetForwarder.prepare(); - } - - let popup; - if (isIframe && options.general.showIframePopupsInRootFrame && DOM.getFullscreenElement() === null && iframePopupsInRootFrameAvailable) { - popup = popups.iframe || await createIframePopupProxy(frameOffsetForwarder, disableIframePopupsInRootFrame); - popups.iframe = popup; - } else if (proxy) { - popup = popups.proxy || await createPopupProxy(depth, id, parentFrameId); - popups.proxy = popup; - } else { - popup = popups.normal; - if (!popup) { - if (popupFactoryPromise === null) { - popupFactoryPromise = createPopupFactory(); - } - const popupFactory = await popupFactoryPromise; - const popupNormal = await getOrCreatePopup(depth, popupFactory); - popups.normal = popupNormal; - popup = popupNormal; - } - } - - if (frontend === null) { - const getUrl = proxy ? getPopupProxyUrl : null; - frontend = new Frontend(popup, getUrl); - frontendPreparePromise = frontend.prepare(); - await frontendPreparePromise; - } else { - await frontendPreparePromise; - if (isSearchPage) { - const disabled = !options.scanning.enableOnSearchPage; - frontend.setDisabledOverride(disabled); - } - - if (isIframe) { - await frontend.setPopup(popup); - } - } - }; - - yomichan.on('optionsUpdated', applyOptions); - window.addEventListener('fullscreenchange', applyOptions, false); - - await applyOptions(); + const popupFactory = new PopupFactory(frameId); + await popupFactory.prepare(); + + const frontend = new Frontend( + frameId, + popupFactory, + {} + ); + await frontend.prepare(); + } catch (e) { + yomichan.logError(e); + } })(); diff --git a/ext/fg/js/float-main.js b/ext/fg/js/float-main.js index 2ec334c8..3bedfe58 100644 --- a/ext/fg/js/float-main.js +++ b/ext/fg/js/float-main.js @@ -18,42 +18,15 @@ /* global * DisplayFloat * api - * dynamicLoader */ -async function injectPopupNested() { - await dynamicLoader.loadScripts([ - '/mixed/js/text-scanner.js', - '/fg/js/popup.js', - '/fg/js/popup-proxy.js', - '/fg/js/frontend.js', - '/fg/js/content-script-main.js' - ]); -} - -async function popupNestedInitialize(id, depth, parentFrameId, url) { - let optionsApplied = false; - - const applyOptions = async () => { - const optionsContext = {depth, url}; - const options = await api.optionsGet(optionsContext); - const maxPopupDepthExceeded = !(typeof depth === 'number' && depth < options.scanning.popupNestingMaxDepth); - if (maxPopupDepthExceeded || optionsApplied) { return; } - - optionsApplied = true; - yomichan.off('optionsUpdated', applyOptions); - - window.frontendInitializationData = {id, depth, parentFrameId, url, proxy: true}; - await injectPopupNested(); - }; - - yomichan.on('optionsUpdated', applyOptions); - - await applyOptions(); -} - (async () => { - api.forwardLogsToBackend(); - const display = new DisplayFloat(); - await display.prepare(); + try { + api.forwardLogsToBackend(); + + const display = new DisplayFloat(); + await display.prepare(); + } catch (e) { + yomichan.logError(e); + } })(); diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 12d27a9f..199990e5 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -17,8 +17,10 @@ /* global * Display + * Frontend + * PopupFactory * api - * popupNestedInitialize + * dynamicLoader */ class DisplayFloat extends Display { @@ -30,7 +32,7 @@ class DisplayFloat extends Display { this._token = null; this._orphaned = false; - this._initializedNestedPopups = false; + this._nestedPopupsPrepared = false; this._onKeyDownHandlers = new Map([ ['C', (e) => { @@ -183,10 +185,10 @@ class DisplayFloat extends Display { await this.updateOptions(); - if (childrenSupported && !this._initializedNestedPopups) { + if (childrenSupported && !this._nestedPopupsPrepared) { const {depth, url} = optionsContext; - popupNestedInitialize(popupId, depth, frameId, url); - this._initializedNestedPopups = true; + this._prepareNestedPopups(popupId, depth, frameId, url); + this._nestedPopupsPrepared = true; } this.setContentScale(scale); @@ -201,4 +203,57 @@ class DisplayFloat extends Display { this._secret === message.secret ); } + + async _prepareNestedPopups(id, depth, parentFrameId, url) { + let complete = false; + + const onOptionsUpdated = async () => { + const optionsContext = this.optionsContext; + const options = await api.optionsGet(optionsContext); + const maxPopupDepthExceeded = !(typeof depth === 'number' && depth < options.scanning.popupNestingMaxDepth); + if (maxPopupDepthExceeded || complete) { return; } + + complete = true; + yomichan.off('optionsUpdated', onOptionsUpdated); + + try { + await this._setupNestedPopups(id, depth, parentFrameId, url); + } catch (e) { + yomichan.logError(e); + } + }; + + yomichan.on('optionsUpdated', onOptionsUpdated); + + await onOptionsUpdated(); + } + + async _setupNestedPopups(id, depth, parentFrameId, url) { + await dynamicLoader.loadScripts([ + '/mixed/js/text-scanner.js', + '/fg/js/popup.js', + '/fg/js/popup-proxy.js', + '/fg/js/popup-factory.js', + '/fg/js/frame-offset-forwarder.js', + '/fg/js/frontend.js' + ]); + + const {frameId} = await api.frameInformationGet(); + + const popupFactory = new PopupFactory(frameId); + await popupFactory.prepare(); + + const frontend = new Frontend( + frameId, + popupFactory, + { + id, + depth, + parentFrameId, + url, + proxy: true + } + ); + await frontend.prepare(); + } } diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index ab455c09..71e53b03 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -16,16 +16,18 @@ */ /* global + * DOM + * FrameOffsetForwarder + * PopupProxy * TextScanner * api * docSentenceExtract */ class Frontend { - constructor(popup, getUrl=null) { + constructor(frameId, popupFactory, frontendInitializationData) { this._id = yomichan.generateId(16); - this._popup = popup; - this._getUrl = getUrl; + this._popup = null; this._disabledOverride = false; this._options = null; this._pageZoomFactor = 1.0; @@ -37,11 +39,31 @@ class Frontend { this._optionsUpdatePending = false; this._textScanner = new TextScanner({ node: window, - ignoreElements: () => this._popup.isProxy() ? [] : [this._popup.getFrame()], - ignorePoint: (x, y) => this._popup.containsPoint(x, y), + ignoreElements: this._ignoreElements.bind(this), + ignorePoint: this._ignorePoint.bind(this), search: this._search.bind(this) }); + const { + depth=0, + id: proxyPopupId, + parentFrameId, + proxy: useProxyPopup=false, + isSearchPage=false, + allowRootFramePopupProxy=true + } = frontendInitializationData; + this._proxyPopupId = proxyPopupId; + this._parentFrameId = parentFrameId; + this._useProxyPopup = useProxyPopup; + this._isSearchPage = isSearchPage; + this._depth = depth; + this._frameId = frameId; + this._frameOffsetForwarder = new FrameOffsetForwarder(); + this._popupFactory = popupFactory; + this._allowRootFramePopupProxy = allowRootFramePopupProxy; + this._popupCache = new Map(); + this._updatePopupToken = null; + this._windowMessageHandlers = new Map([ ['popupClose', this._onMessagePopupClose.bind(this)], ['selectionCopy', this._onMessageSelectionCopy.bind()] @@ -62,43 +84,46 @@ class Frontend { this._textScanner.canClearSelection = value; } + get popup() { + return this._popup; + } + async prepare() { + this._frameOffsetForwarder.prepare(); + + await this.updateOptions(); try { - await this.updateOptions(); - try { - const {zoomFactor} = await api.getZoom(); - this._pageZoomFactor = zoomFactor; - } catch (e) { - // Ignore exceptions which may occur due to being on an unsupported page (e.g. about:blank) - } + const {zoomFactor} = await api.getZoom(); + this._pageZoomFactor = zoomFactor; + } catch (e) { + // Ignore exceptions which may occur due to being on an unsupported page (e.g. about:blank) + } - window.addEventListener('resize', this._onResize.bind(this), false); + this._textScanner.prepare(); - const visualViewport = window.visualViewport; - if (visualViewport !== null && typeof visualViewport === 'object') { - window.visualViewport.addEventListener('scroll', this._onVisualViewportScroll.bind(this)); - window.visualViewport.addEventListener('resize', this._onVisualViewportResize.bind(this)); - } + window.addEventListener('resize', this._onResize.bind(this), false); + DOM.addFullscreenChangeEventListener(this._updatePopup.bind(this)); - yomichan.on('orphaned', this._onOrphaned.bind(this)); - yomichan.on('optionsUpdated', this.updateOptions.bind(this)); - yomichan.on('zoomChanged', this._onZoomChanged.bind(this)); - chrome.runtime.onMessage.addListener(this._onRuntimeMessage.bind(this)); + const visualViewport = window.visualViewport; + if (visualViewport !== null && typeof visualViewport === 'object') { + window.visualViewport.addEventListener('scroll', this._onVisualViewportScroll.bind(this)); + window.visualViewport.addEventListener('resize', this._onVisualViewportResize.bind(this)); + } - this._textScanner.on('clearSelection', this._onClearSelection.bind(this)); - this._textScanner.on('activeModifiersChanged', this._onActiveModifiersChanged.bind(this)); + yomichan.on('orphaned', this._onOrphaned.bind(this)); + yomichan.on('optionsUpdated', this.updateOptions.bind(this)); + yomichan.on('zoomChanged', this._onZoomChanged.bind(this)); + chrome.runtime.onMessage.addListener(this._onRuntimeMessage.bind(this)); - this._updateContentScale(); - this._broadcastRootPopupInformation(); - } catch (e) { - yomichan.logError(e); - } - } + this._textScanner.on('clearSelection', this._onClearSelection.bind(this)); + this._textScanner.on('activeModifiersChanged', this._onActiveModifiersChanged.bind(this)); - async setPopup(popup) { - this._textScanner.clearSelection(true); - this._popup = popup; - await popup.setOptionsContext(await this.getOptionsContext(), this._id); + api.crossFrame.registerHandlers([ + ['getUrl', {async: false, handler: this._onApiGetUrl.bind(this)}] + ]); + + this._updateContentScale(); + this._broadcastRootPopupInformation(); } setDisabledOverride(disabled) { @@ -112,8 +137,16 @@ class Frontend { } async getOptionsContext() { - const url = this._getUrl !== null ? await this._getUrl() : window.location.href; - const depth = this._popup.depth; + let url = window.location.href; + if (this._useProxyPopup) { + try { + url = await api.crossFrame.invoke(this._parentFrameId, 'getUrl', {}); + } catch (e) { + // NOP + } + } + + const depth = this._depth; const modifierKeys = [...this._activeModifiers]; return {depth, url, modifierKeys}; } @@ -121,6 +154,9 @@ class Frontend { async updateOptions() { const optionsContext = await this.getOptionsContext(); this._options = await api.optionsGet(optionsContext); + + await this._updatePopup(); + this._textScanner.setOptions(this._options); this._updateTextScannerEnabled(); @@ -130,8 +166,6 @@ class Frontend { } this._textScanner.ignoreNodes = ignoreNodes.join(','); - await this._popup.setOptionsContext(optionsContext, this._id); - this._updateContentScale(); const textSourceCurrent = this._textScanner.getCurrentTextSource(); @@ -167,6 +201,12 @@ class Frontend { this._broadcastDocumentInformation(uniqueId); } + // API message handlers + + _onApiGetUrl() { + return window.location.href; + } + // Private _onResize() { @@ -223,6 +263,95 @@ class Frontend { await this.updateOptions(); } + async _updatePopup() { + const showIframePopupsInRootFrame = this._options.general.showIframePopupsInRootFrame; + const isIframe = !this._useProxyPopup && (window !== window.parent); + + let popupPromise; + if ( + isIframe && + showIframePopupsInRootFrame && + DOM.getFullscreenElement() === null && + this._allowRootFramePopupProxy + ) { + popupPromise = this._popupCache.get('iframe'); + if (typeof popupPromise === 'undefined') { + popupPromise = this._getIframeProxyPopup(); + this._popupCache.set('iframe', popupPromise); + } + } else if (this._useProxyPopup) { + popupPromise = this._popupCache.get('proxy'); + if (typeof popupPromise === 'undefined') { + popupPromise = this._getProxyPopup(); + this._popupCache.set('proxy', popupPromise); + } + } else { + popupPromise = this._popupCache.get('default'); + if (typeof popupPromise === 'undefined') { + popupPromise = this._getDefaultPopup(); + this._popupCache.set('default', popupPromise); + } + } + + // The token below is used as a unique identifier to ensure that a new _updatePopup call + // hasn't been started during the await. + const token = {}; + this._updatePopupToken = token; + const popup = await popupPromise; + const optionsContext = await this.getOptionsContext(); + if (this._updatePopupToken !== token) { return; } + await popup.setOptionsContext(optionsContext, this._id); + if (this._updatePopupToken !== token) { return; } + + if (this._isSearchPage) { + this.setDisabledOverride(!this._options.scanning.enableOnSearchPage); + } + + this._textScanner.clearSelection(true); + this._popup = popup; + this._depth = popup.depth; + } + + async _getDefaultPopup() { + return this._popupFactory.getOrCreatePopup(null, null, this._depth); + } + + async _getProxyPopup() { + const popup = new PopupProxy(null, this._depth + 1, this._proxyPopupId, this._parentFrameId); + await popup.prepare(); + return popup; + } + + async _getIframeProxyPopup() { + const rootPopupInformationPromise = yomichan.getTemporaryListenerResult( + chrome.runtime.onMessage, + ({action, params}, {resolve}) => { + if (action === 'rootPopupInformation') { + resolve(params); + } + } + ); + api.broadcastTab('rootPopupRequestInformationBroadcast'); + const {popupId, frameId: parentFrameId} = await rootPopupInformationPromise; + + const popup = new PopupProxy(popupId, 0, null, parentFrameId, this._frameOffsetForwarder); + popup.on('offsetNotFound', () => { + this._allowRootFramePopupProxy = false; + this._updatePopup(); + }); + await popup.prepare(); + + return popup; + } + + _ignoreElements() { + return this._popup === null || this._popup.isProxy() ? [] : [this._popup.getFrame()]; + } + + _ignorePoint(x, y) { + return this._popup !== null && this._popup.containsPoint(x, y); + } + async _search(textSource, cause) { await this._updatePendingOptions(); @@ -318,7 +447,7 @@ class Frontend { _updateTextScannerEnabled() { const enabled = ( this._options.general.enable && - this._popup.depth <= this._options.scanning.popupNestingMaxDepth && + this._depth <= this._options.scanning.popupNestingMaxDepth && !this._disabledOverride ); this._enabledEventListeners.removeAllEventListeners(); @@ -342,27 +471,41 @@ class Frontend { if (contentScale === this._contentScale) { return; } this._contentScale = contentScale; - this._popup.setContentScale(this._contentScale); + if (this._popup !== null) { + this._popup.setContentScale(this._contentScale); + } this._updatePopupPosition(); } async _updatePopupPosition() { const textSource = this._textScanner.getCurrentTextSource(); - if (textSource !== null && await this._popup.isVisible()) { + if ( + textSource !== null && + this._popup !== null && + await this._popup.isVisible() + ) { this._showPopupContent(textSource, await this.getOptionsContext()); } } _broadcastRootPopupInformation() { - if (!this._popup.isProxy() && this._popup.depth === 0 && this._popup.frameId === 0) { - api.broadcastTab('rootPopupInformation', {popupId: this._popup.id, frameId: this._popup.frameId}); + if ( + this._popup !== null && + !this._popup.isProxy() && + this._depth === 0 && + this._frameId === 0 + ) { + api.broadcastTab('rootPopupInformation', { + popupId: this._popup.id, + frameId: this._frameId + }); } } _broadcastDocumentInformation(uniqueId) { api.broadcastTab('documentInformationBroadcast', { uniqueId, - frameId: this._popup.frameId, + frameId: this._frameId, title: document.title }); } diff --git a/ext/fg/js/popup-factory.js b/ext/fg/js/popup-factory.js index b5997253..4edda91f 100644 --- a/ext/fg/js/popup-factory.js +++ b/ext/fg/js/popup-factory.js @@ -39,8 +39,7 @@ class PopupFactory { ['showContent', {async: true, handler: this._onApiShowContent.bind(this)}], ['setCustomCss', {async: false, handler: this._onApiSetCustomCss.bind(this)}], ['clearAutoPlayTimer', {async: false, handler: this._onApiClearAutoPlayTimer.bind(this)}], - ['setContentScale', {async: false, handler: this._onApiSetContentScale.bind(this)}], - ['getUrl', {async: false, handler: this._onApiGetUrl.bind(this)}] + ['setContentScale', {async: false, handler: this._onApiSetContentScale.bind(this)}] ]); } @@ -147,10 +146,6 @@ class PopupFactory { return popup.setContentScale(scale); } - _onApiGetUrl() { - return window.location.href; - } - // Private functions _getPopup(id) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 14ddcafb..a6602eae 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -104,10 +104,6 @@ class PopupProxy extends EventDispatcher { this._invoke('setContentScale', {id: this._id, scale}); } - async getUrl() { - return await this._invoke('getUrl', {}); - } - // Private _invoke(action, params={}) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index af24989f..4394a965 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -415,16 +415,7 @@ class Popup { return; } - const fullscreenEvents = [ - 'fullscreenchange', - 'MSFullscreenChange', - 'mozfullscreenchange', - 'webkitfullscreenchange' - ]; - const onFullscreenChanged = this._onFullscreenChanged.bind(this); - for (const eventName of fullscreenEvents) { - this._fullscreenEventListeners.addEventListener(document, eventName, onFullscreenChanged, false); - } + DOM.addFullscreenChangeEventListener(this._onFullscreenChanged.bind(this), this._fullscreenEventListeners); } _onFullscreenChanged() { diff --git a/ext/mixed/js/dom.js b/ext/mixed/js/dom.js index 05764443..59fea9f6 100644 --- a/ext/mixed/js/dom.js +++ b/ext/mixed/js/dom.js @@ -77,6 +77,24 @@ class DOM { return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : ''); } + static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection=null) { + const target = document; + const options = false; + const fullscreenEventNames = [ + 'fullscreenchange', + 'MSFullscreenChange', + 'mozfullscreenchange', + 'webkitfullscreenchange' + ]; + for (const eventName of fullscreenEventNames) { + if (eventListenerCollection === null) { + target.addEventListener(eventName, onFullscreenChanged, options); + } else { + eventListenerCollection.addEventListener(target, eventName, onFullscreenChanged, options); + } + } + } + static getFullscreenElement() { return ( document.fullscreenElement || diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index fb275452..7c705fc8 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -28,6 +28,7 @@ class TextScanner extends EventDispatcher { this._ignorePoint = ignorePoint; this._search = search; + this._isPrepared = false; this._ignoreNodes = null; this._causeCurrent = null; @@ -69,10 +70,15 @@ class TextScanner extends EventDispatcher { return this._causeCurrent; } + prepare() { + this._isPrepared = true; + this.setEnabled(this._enabled); + } + setEnabled(enabled) { this._eventListeners.removeAllEventListeners(); this._enabled = enabled; - if (this._enabled) { + if (this._enabled && this._isPrepared) { this._hookEvents(); } else { this.clearSelection(true); -- cgit v1.2.3