diff options
author | Alex Yatskov <alex@foosoft.net> | 2019-09-23 17:35:36 -0700 |
---|---|---|
committer | Alex Yatskov <alex@foosoft.net> | 2019-09-23 17:35:36 -0700 |
commit | f4b6527ed6ed1f0f4f5a63b94766b20f3b90e6ec (patch) | |
tree | 0d2f733c13597dd4067d3dc01e6da27f96bfe81b /ext/fg/js | |
parent | cfc6363a01ee00e89866c54709006d6f55d093de (diff) | |
parent | f5afe590ad0730a695614b32032b7ea70b46c7b0 (diff) |
Merge branch 'master' into testing
Diffstat (limited to 'ext/fg/js')
-rw-r--r-- | ext/fg/js/api.js | 24 | ||||
-rw-r--r-- | ext/fg/js/document.js | 21 | ||||
-rw-r--r-- | ext/fg/js/float.js | 11 | ||||
-rw-r--r-- | ext/fg/js/frontend-api-sender.js | 14 | ||||
-rw-r--r-- | ext/fg/js/frontend.js | 126 | ||||
-rw-r--r-- | ext/fg/js/popup-nested.js | 7 | ||||
-rw-r--r-- | ext/fg/js/popup-proxy-host.js | 24 | ||||
-rw-r--r-- | ext/fg/js/popup-proxy.js | 17 | ||||
-rw-r--r-- | ext/fg/js/popup.js | 16 | ||||
-rw-r--r-- | ext/fg/js/source.js | 4 |
10 files changed, 154 insertions, 110 deletions
diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js index 6bcb0dbb..d0ac649a 100644 --- a/ext/fg/js/api.js +++ b/ext/fg/js/api.js @@ -17,28 +17,24 @@ */ -function apiOptionsSet(options) { - return utilInvoke('optionsSet', {options}); +function apiOptionsGet(optionsContext) { + return utilInvoke('optionsGet', {optionsContext}); } -function apiOptionsGet() { - return utilInvoke('optionsGet'); +function apiTermsFind(text, optionsContext) { + return utilInvoke('termsFind', {text, optionsContext}); } -function apiTermsFind(text) { - return utilInvoke('termsFind', {text}); +function apiKanjiFind(text, optionsContext) { + return utilInvoke('kanjiFind', {text, optionsContext}); } -function apiKanjiFind(text) { - return utilInvoke('kanjiFind', {text}); +function apiDefinitionAdd(definition, mode, context, optionsContext) { + return utilInvoke('definitionAdd', {definition, mode, context, optionsContext}); } -function apiDefinitionAdd(definition, mode, context) { - return utilInvoke('definitionAdd', {definition, mode, context}); -} - -function apiDefinitionsAddable(definitions, modes) { - return utilInvoke('definitionsAddable', {definitions, modes}).catch(() => null); +function apiDefinitionsAddable(definitions, modes, optionsContext) { + return utilInvoke('definitionsAddable', {definitions, modes, optionsContext}).catch(() => null); } function apiNoteView(noteId) { diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js index 60b1b9bd..94a68e6c 100644 --- a/ext/fg/js/document.js +++ b/ext/fg/js/document.js @@ -89,13 +89,23 @@ function docImposterCreate(element, isTextarea) { return [imposter, container]; } -function docRangeFromPoint({x, y}, options) { - const elements = document.elementsFromPoint(x, y); +function docElementsFromPoint(x, y, all) { + if (all) { + return document.elementsFromPoint(x, y); + } + + const e = document.elementFromPoint(x, y); + return e !== null ? [e] : []; +} + +function docRangeFromPoint(x, y, options) { + const deepDomScan = options.scanning.deepDomScan; + const elements = docElementsFromPoint(x, y, deepDomScan); let imposter = null; let imposterContainer = null; if (elements.length > 0) { const element = elements[0]; - switch (element.nodeName) { + switch (element.nodeName.toUpperCase()) { case 'IMG': case 'BUTTON': return new TextSourceElement(element); @@ -108,7 +118,7 @@ function docRangeFromPoint({x, y}, options) { } } - const range = caretRangeFromPointExt(x, y, options.scanning.deepDomScan ? elements : []); + const range = caretRangeFromPointExt(x, y, deepDomScan ? elements : []); if (range !== null) { if (imposter !== null) { docSetImposterStyle(imposterContainer.style, 'z-index', '-2147483646'); @@ -257,6 +267,9 @@ const caretRangeFromPoint = (() => { // Firefox return (x, y) => { const position = document.caretPositionFromPoint(x, y); + if (position === null) { + return null; + } const node = position.offsetNode; if (node === null) { return null; diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 3c521714..fd7986b8 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -23,6 +23,11 @@ class DisplayFloat extends Display { this.autoPlayAudioTimer = null; this.styleNode = null; + this.optionsContext = { + depth: 0, + url: window.location.href + }; + this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); $(window).on('message', utilAsync(this.onMessage.bind(this))); @@ -74,8 +79,10 @@ class DisplayFloat extends Display { } }, - popupNestedInitialize: ({id, depth, parentFrameId}) => { - popupNestedInitialize(id, depth, parentFrameId); + popupNestedInitialize: ({id, depth, parentFrameId, url}) => { + this.optionsContext.depth = depth; + this.optionsContext.url = url; + popupNestedInitialize(id, depth, parentFrameId, url); } }; diff --git a/ext/fg/js/frontend-api-sender.js b/ext/fg/js/frontend-api-sender.js index a1cb02c4..2e037e62 100644 --- a/ext/fg/js/frontend-api-sender.js +++ b/ext/fg/js/frontend-api-sender.js @@ -26,9 +26,7 @@ class FrontendApiSender { this.disconnected = false; this.nextId = 0; - this.port = chrome.runtime.connect(null, {name: 'backend-api-forwarder'}); - this.port.onDisconnect.addListener(this.onDisconnect.bind(this)); - this.port.onMessage.addListener(this.onMessage.bind(this)); + this.port = null; } invoke(action, params, target) { @@ -36,6 +34,10 @@ class FrontendApiSender { return Promise.reject('Disconnected'); } + if (this.port === null) { + this.createPort(); + } + const id = `${this.nextId}`; ++this.nextId; @@ -48,6 +50,12 @@ class FrontendApiSender { }); } + createPort() { + this.port = chrome.runtime.connect(null, {name: 'backend-api-forwarder'}); + this.port.onDisconnect.addListener(this.onDisconnect.bind(this)); + this.port.onMessage.addListener(this.onMessage.bind(this)); + } + onMessage({type, id, data, senderId}) { if (senderId !== this.senderId) { return; } switch (type) { diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 079a7ef2..167e82c0 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -21,13 +21,16 @@ class Frontend { constructor(popup, ignoreNodes) { this.popup = popup; this.popupTimer = null; - this.mouseDownLeft = false; - this.mouseDownMiddle = false; this.textSourceLast = null; this.pendingLookup = false; this.options = null; this.ignoreNodes = (Array.isArray(ignoreNodes) && ignoreNodes.length > 0 ? ignoreNodes.join(',') : null); + this.optionsContext = { + depth: popup.depth, + url: popup.url + }; + this.primaryTouchIdentifier = null; this.contextMenuChecking = false; this.contextMenuPrevent = false; @@ -40,9 +43,9 @@ class Frontend { static create() { const initializationData = window.frontendInitializationData; const isNested = (initializationData !== null && typeof initializationData === 'object'); - const {id, parentFrameId, ignoreNodes} = isNested ? initializationData : {}; + const {id, depth, parentFrameId, ignoreNodes, url} = isNested ? initializationData : {}; - const popup = isNested ? new PopupProxy(id, parentFrameId) : PopupProxyHost.instance.createPopup(null); + const popup = isNested ? new PopupProxy(depth + 1, id, parentFrameId, url) : PopupProxyHost.instance.createPopup(null); const frontend = new Frontend(popup, ignoreNodes); frontend.prepare(); return frontend; @@ -50,14 +53,13 @@ class Frontend { async prepare() { try { - this.options = await apiOptionsGet(); + this.options = await apiOptionsGet(this.getOptionsContext()); window.addEventListener('message', this.onFrameMessage.bind(this)); window.addEventListener('mousedown', this.onMouseDown.bind(this)); window.addEventListener('mousemove', this.onMouseMove.bind(this)); window.addEventListener('mouseover', this.onMouseOver.bind(this)); window.addEventListener('mouseout', this.onMouseOut.bind(this)); - window.addEventListener('mouseup', this.onMouseUp.bind(this)); window.addEventListener('resize', this.onResize.bind(this)); if (this.options.scanning.touchInputEnabled) { @@ -76,7 +78,7 @@ class Frontend { } onMouseOver(e) { - if (e.target === this.popup.container && this.popupTimer) { + if (e.target === this.popup.container && this.popupTimer !== null) { this.popupTimerClear(); } } @@ -84,38 +86,32 @@ class Frontend { onMouseMove(e) { this.popupTimerClear(); - if (!this.options.general.enable) { - return; - } - - if (this.mouseDownLeft) { - return; - } - - if (this.pendingLookup) { + if ( + this.pendingLookup || + !this.options.general.enable || + (e.buttons & 0x1) !== 0x0 // Left mouse button + ) { return; } - const mouseScan = this.mouseDownMiddle && this.options.scanning.middleMouse; - const keyScan = - this.options.scanning.modifier === 'alt' && e.altKey || - this.options.scanning.modifier === 'ctrl' && e.ctrlKey || - this.options.scanning.modifier === 'shift' && e.shiftKey || - this.options.scanning.modifier === 'none'; - - if (!keyScan && !mouseScan) { + const scanningOptions = this.options.scanning; + const scanningModifier = scanningOptions.modifier; + if (!( + Frontend.isScanningModifierPressed(scanningModifier, e) || + (scanningOptions.middleMouse && (e.buttons & 0x4) !== 0x0) // Middle mouse button + )) { return; } const search = async () => { try { - await this.searchAt({x: e.clientX, y: e.clientY}, 'mouse'); + await this.searchAt(e.clientX, e.clientY, 'mouse'); } catch (e) { this.onError(e); } }; - if (this.options.scanning.modifier === 'none') { + if (scanningModifier === 'none') { this.popupTimerSet(search); } else { search(); @@ -131,23 +127,8 @@ class Frontend { return false; } - this.mousePosLast = {x: e.clientX, y: e.clientY}; this.popupTimerClear(); this.searchClear(); - - if (e.which === 1) { - this.mouseDownLeft = true; - } else if (e.which === 2) { - this.mouseDownMiddle = true; - } - } - - onMouseUp(e) { - if (e.which === 1) { - this.mouseDownLeft = false; - } else if (e.which === 2) { - this.mouseDownMiddle = false; - } } onMouseOut(e) { @@ -239,8 +220,8 @@ class Frontend { } } - onAfterSearch(newRange, type, searched, success) { - if (type === 'mouse') { + onAfterSearch(newRange, cause, searched, success) { + if (cause === 'mouse') { return; } @@ -250,7 +231,7 @@ class Frontend { return; } - if (type === 'touchStart' && newRange !== null) { + if (cause === 'touchStart' && newRange !== null) { this.scrollPrevent = true; } @@ -261,11 +242,8 @@ class Frontend { onBgMessage({action, params}, sender, callback) { const handlers = { - optionsSet: options => { - this.options = options; - if (!this.options.enable) { - this.searchClear(); - } + optionsUpdate: () => { + this.updateOptions(); }, popupSetVisible: ({visible}) => { @@ -284,24 +262,35 @@ class Frontend { console.log(error); } + async updateOptions() { + this.options = await apiOptionsGet(this.getOptionsContext()); + if (!this.options.enable) { + this.searchClear(); + } + } + popupTimerSet(callback) { - this.popupTimerClear(); - this.popupTimer = window.setTimeout(callback, this.options.scanning.delay); + const delay = this.options.scanning.delay; + if (delay > 0) { + this.popupTimer = window.setTimeout(callback, delay); + } else { + Promise.resolve().then(callback); + } } popupTimerClear() { - if (this.popupTimer) { + if (this.popupTimer !== null) { window.clearTimeout(this.popupTimer); this.popupTimer = null; } } - async searchAt(point, type) { - if (this.pendingLookup || await this.popup.containsPoint(point)) { + async searchAt(x, y, cause) { + if (this.pendingLookup || await this.popup.containsPoint(x, y)) { return; } - const textSource = docRangeFromPoint(point, this.options); + const textSource = docRangeFromPoint(x, y, this.options); let hideResults = textSource === null; let searched = false; let success = false; @@ -310,7 +299,7 @@ class Frontend { if (!hideResults && (!this.textSourceLast || !this.textSourceLast.equals(textSource))) { searched = true; this.pendingLookup = true; - const focus = (type === 'mouse'); + const focus = (cause === 'mouse'); hideResults = !await this.searchTerms(textSource, focus) && !await this.searchKanji(textSource, focus); success = true; } @@ -335,7 +324,7 @@ class Frontend { } this.pendingLookup = false; - this.onAfterSearch(this.textSourceLast, type, searched, success); + this.onAfterSearch(this.textSourceLast, cause, searched, success); } } @@ -347,7 +336,7 @@ class Frontend { return; } - const {definitions, length} = await apiTermsFind(searchText); + const {definitions, length} = await apiTermsFind(searchText, this.getOptionsContext()); if (definitions.length === 0) { return false; } @@ -380,7 +369,7 @@ class Frontend { return; } - const definitions = await apiKanjiFind(searchText); + const definitions = await apiKanjiFind(searchText, this.getOptionsContext()); if (definitions.length === 0) { return false; } @@ -477,7 +466,7 @@ class Frontend { this.clickPrevent = value; } - searchFromTouch(x, y, type) { + searchFromTouch(x, y, cause) { this.popupTimerClear(); if (!this.options.general.enable || this.pendingLookup) { @@ -486,7 +475,7 @@ class Frontend { const search = async () => { try { - await this.searchAt({x, y}, type); + await this.searchAt(x, y, cause); } catch (e) { this.onError(e); } @@ -523,6 +512,21 @@ class Frontend { textSource.setEndOffset(length); } } + + getOptionsContext() { + this.optionsContext.url = this.popup.url; + return this.optionsContext; + } + + static isScanningModifierPressed(scanningModifier, mouseEvent) { + switch (scanningModifier) { + case 'alt': return mouseEvent.altKey; + case 'ctrl': return mouseEvent.ctrlKey; + case 'shift': return mouseEvent.shiftKey; + case 'none': return true; + default: return false; + } + } } window.yomichan_frontend = Frontend.create(); diff --git a/ext/fg/js/popup-nested.js b/ext/fg/js/popup-nested.js index e0376bb2..b36de2ec 100644 --- a/ext/fg/js/popup-nested.js +++ b/ext/fg/js/popup-nested.js @@ -19,13 +19,14 @@ let popupNestedInitialized = false; -async function popupNestedInitialize(id, depth, parentFrameId) { +async function popupNestedInitialize(id, depth, parentFrameId, url) { if (popupNestedInitialized) { return; } popupNestedInitialized = true; - const options = await apiOptionsGet(); + const optionsContext = {depth, url}; + const options = await apiOptionsGet(optionsContext); const popupNestingMaxDepth = options.scanning.popupNestingMaxDepth; if (!(typeof popupNestingMaxDepth === 'number' && typeof depth === 'number' && depth < popupNestingMaxDepth)) { @@ -34,7 +35,7 @@ async function popupNestedInitialize(id, depth, parentFrameId) { const ignoreNodes = options.scanning.enableOnPopupExpressions ? [] : [ '.expression', '.expression *' ]; - window.frontendInitializationData = {id, depth, parentFrameId, ignoreNodes}; + window.frontendInitializationData = {id, depth, parentFrameId, ignoreNodes, url}; const scriptSrcs = [ '/fg/js/frontend-api-sender.js', diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index fa61aeb4..396f7556 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -42,9 +42,9 @@ class PopupProxyHost { showOrphaned: ({id, elementRect, options}) => this.show(id, elementRect, options), hide: ({id}) => this.hide(id), setVisible: ({id, visible}) => this.setVisible(id, visible), - containsPoint: ({id, point}) => this.containsPoint(id, point), - termsShow: ({id, elementRect, definitions, options, context}) => this.termsShow(id, elementRect, definitions, options, context), - kanjiShow: ({id, elementRect, definitions, options, context}) => this.kanjiShow(id, elementRect, definitions, options, context), + containsPoint: ({id, x, y}) => this.containsPoint(id, x, y), + termsShow: ({id, elementRect, writingMode, definitions, options, context}) => this.termsShow(id, elementRect, writingMode, definitions, options, context), + kanjiShow: ({id, elementRect, writingMode, definitions, options, context}) => this.kanjiShow(id, elementRect, writingMode, definitions, options, context), clearAutoPlayTimer: ({id}) => this.clearAutoPlayTimer(id) }); } @@ -108,27 +108,33 @@ class PopupProxyHost { return popup.setVisible(visible); } - async containsPoint(id, point) { + async containsPoint(id, x, y) { const popup = this.getPopup(id); - return await popup.containsPoint(point); + return await popup.containsPoint(x, y); } - async termsShow(id, elementRect, definitions, options, context) { + async termsShow(id, elementRect, writingMode, definitions, options, context) { const popup = this.getPopup(id); elementRect = this.jsonRectToDOMRect(popup, elementRect); - return await popup.termsShow(elementRect, definitions, options, context); + if (!PopupProxyHost.popupCanShow(popup)) { return false; } + return await popup.termsShow(elementRect, writingMode, definitions, options, context); } - async kanjiShow(id, elementRect, definitions, options, context) { + async kanjiShow(id, elementRect, writingMode, definitions, options, context) { const popup = this.getPopup(id); elementRect = this.jsonRectToDOMRect(popup, elementRect); - return await popup.kanjiShow(elementRect, definitions, options, context); + if (!PopupProxyHost.popupCanShow(popup)) { return false; } + return await popup.kanjiShow(elementRect, writingMode, definitions, options, context); } async clearAutoPlayTimer(id) { const popup = this.getPopup(id); return popup.clearAutoPlayTimer(); } + + static popupCanShow(popup) { + return popup.parent === null || popup.parent.isVisible(); + } } PopupProxyHost.instance = PopupProxyHost.create(); diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index f6295079..235e1730 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -18,14 +18,15 @@ class PopupProxy { - constructor(parentId, parentFrameId) { + constructor(depth, parentId, parentFrameId, url) { this.parentId = parentId; this.parentFrameId = parentFrameId; this.id = null; this.idPromise = null; this.parent = null; this.child = null; - this.depth = 0; + this.depth = depth; + this.url = url; this.container = null; @@ -69,23 +70,23 @@ class PopupProxy { return await this.invokeHostApi('setVisible', {id, visible}); } - async containsPoint(point) { + async containsPoint(x, y) { if (this.id === null) { return false; } - return await this.invokeHostApi('containsPoint', {id: this.id, point}); + return await this.invokeHostApi('containsPoint', {id: this.id, x, y}); } - async termsShow(elementRect, definitions, options, context) { + async termsShow(elementRect, writingMode, definitions, options, context) { const id = await this.getPopupId(); elementRect = PopupProxy.DOMRectToJson(elementRect); - return await this.invokeHostApi('termsShow', {id, elementRect, definitions, options, context}); + return await this.invokeHostApi('termsShow', {id, elementRect, writingMode, definitions, options, context}); } - async kanjiShow(elementRect, definitions, options, context) { + async kanjiShow(elementRect, writingMode, definitions, options, context) { const id = await this.getPopupId(); elementRect = PopupProxy.DOMRectToJson(elementRect); - return await this.invokeHostApi('kanjiShow', {id, elementRect, definitions, options, context}); + return await this.invokeHostApi('kanjiShow', {id, elementRect, writingMode, definitions, options, context}); } async clearAutoPlayTimer() { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 1b15977b..08965084 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -59,7 +59,8 @@ class Popup { this.invokeApi('popupNestedInitialize', { id: this.id, depth: this.depth, - parentFrameId + parentFrameId, + url: this.url }); this.invokeApi('setOptions', { general: { @@ -239,9 +240,12 @@ class Popup { } focusParent() { - if (this.parent && this.parent.container) { + if (this.parent !== null) { // Chrome doesn't like focusing iframe without contentWindow. - this.parent.container.contentWindow.focus(); + const contentWindow = this.parent.container.contentWindow; + if (contentWindow !== null) { + contentWindow.focus(); + } } else { // Firefox doesn't like focusing window without first blurring the iframe. // this.container.contentWindow.blur() doesn't work on Firefox for some reason. @@ -251,7 +255,7 @@ class Popup { } } - async containsPoint({x, y}) { + async containsPoint(x, y) { for (let popup = this; popup !== null && popup.isVisible(); popup = popup.child) { const rect = popup.container.getBoundingClientRect(); if (x >= rect.left && y >= rect.top && x < rect.right && y < rect.bottom) { @@ -308,4 +312,8 @@ class Popup { parent.appendChild(this.container); } } + + get url() { + return window.location.href; + } } diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js index 18a1a976..4642de50 100644 --- a/ext/fg/js/source.js +++ b/ext/fg/js/source.js @@ -88,7 +88,7 @@ class TextSourceRange { } const skip = ['RT', 'SCRIPT', 'STYLE']; - if (skip.includes(node.nodeName)) { + if (skip.includes(node.nodeName.toUpperCase())) { return false; } @@ -285,7 +285,7 @@ class TextSourceElement { } setEndOffset(length) { - switch (this.element.nodeName) { + switch (this.element.nodeName.toUpperCase()) { case 'BUTTON': this.content = this.element.innerHTML; break; |