From dac33e696145ad3c2cfe076a7fadc82c05732102 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 18 Jul 2020 14:15:36 -0400 Subject: [PATCH] Extension unload indication fix (#662) * Remove unused function * Rename field * Change extensionUnloaded trigger function * Update how extension unloaded content is shown * Ignore certain errors caused by extension unload * Add _showExtensionUnloaded function * Wrap internals of updateOptions * Suppress errors caued by extension unload * Make the frontend trigger the popup's extensionUnloaded event --- ext/fg/js/float.js | 25 +++++++++++-- ext/fg/js/frontend.js | 76 ++++++++++++++++++++++++++++------------ ext/fg/js/popup-proxy.js | 14 ++++++-- ext/fg/js/popup.js | 32 ++++++++++------- ext/mixed/js/display.js | 15 ++++---- ext/mixed/js/yomichan.js | 12 +++---- 6 files changed, 119 insertions(+), 55 deletions(-) diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 61d42fb3..690d1b9e 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -31,7 +31,7 @@ class DisplayFloat extends Display { this._nestedPopupsPrepared = false; this._ownerFrameId = null; this._frameEndpoint = new FrameEndpoint(); - this._windowMessageHandlers = new Map([ + this._messageHandlers = new Map([ ['configure', {async: true, handler: this._onMessageConfigure.bind(this)}], ['setOptionsContext', {async: false, handler: this._onMessageSetOptionsContext.bind(this)}], ['setContent', {async: false, handler: this._onMessageSetContent.bind(this)}], @@ -39,6 +39,9 @@ class DisplayFloat extends Display { ['setCustomCss', {async: false, handler: this._onMessageSetCustomCss.bind(this)}], ['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}] ]); + this._windowMessageHandlers = new Map([ + ['extensionUnloaded', {async: false, handler: this._onMessageExtensionUnloaded.bind(this)}] + ]); this.registerActions([ ['copy-host-selection', () => this._copySelection()] @@ -54,6 +57,7 @@ class DisplayFloat extends Display { api.crossFrame.registerHandlers([ ['popupMessage', {async: 'dynamic', handler: this._onMessage.bind(this)}] ]); + window.addEventListener('message', this._onWindowMessage.bind(this), false); this._frameEndpoint.signal(); } @@ -107,7 +111,7 @@ class DisplayFloat extends Display { } const {action, params} = data.data; - const handlerInfo = this._windowMessageHandlers.get(action); + const handlerInfo = this._messageHandlers.get(action); if (typeof handlerInfo === 'undefined') { throw new Error(`Invalid action: ${action}`); } @@ -117,6 +121,18 @@ class DisplayFloat extends Display { return {async, result}; } + _onWindowMessage(e) { + const data = e.data; + if (!this._frameEndpoint.authenticate(data)) { return; } + + const {action, params} = data.data; + const messageHandler = this._windowMessageHandlers.get(action); + if (typeof messageHandler === 'undefined') { return; } + + const callback = () => {}; // NOP + yomichan.invokeMessageHandler(messageHandler, params, callback); + } + async _onMessageConfigure({frameId, ownerFrameId, popupId, optionsContext, childrenSupported, scale}) { this._ownerFrameId = ownerFrameId; this.setOptionsContext(optionsContext); @@ -152,6 +168,11 @@ class DisplayFloat extends Display { this._setContentScale(scale); } + _onMessageExtensionUnloaded() { + if (yomichan.isExtensionUnloaded) { return; } + yomichan.triggerExtensionUnloaded(); + } + // Private _copySelection() { diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index aa03d4b5..bd64f1ac 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -147,26 +147,13 @@ 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(); - - const ignoreNodes = ['.scan-disable', '.scan-disable *']; - if (!this._options.scanning.enableOnPopupExpressions) { - ignoreNodes.push('.source-text', '.source-text *'); - } - this._textScanner.ignoreNodes = ignoreNodes.join(','); - - this._updateContentScale(); - - const textSourceCurrent = this._textScanner.getCurrentTextSource(); - const causeCurrent = this._textScanner.causeCurrent; - if (textSourceCurrent !== null && causeCurrent !== null) { - await this._search(textSourceCurrent, causeCurrent); + try { + await this._updateOptionsInternal(); + } catch (e) { + if (!yomichan.isExtensionUnloaded) { + throw e; + } + this._showExtensionUnloaded(null); } } @@ -243,6 +230,30 @@ class Frontend { await this.updateOptions(); } + async _updateOptionsInternal() { + const optionsContext = await this.getOptionsContext(); + this._options = await api.optionsGet(optionsContext); + + await this._updatePopup(); + + this._textScanner.setOptions(this._options); + this._updateTextScannerEnabled(); + + const ignoreNodes = ['.scan-disable', '.scan-disable *']; + if (!this._options.scanning.enableOnPopupExpressions) { + ignoreNodes.push('.source-text', '.source-text *'); + } + this._textScanner.ignoreNodes = ignoreNodes.join(','); + + this._updateContentScale(); + + const textSourceCurrent = this._textScanner.getCurrentTextSource(); + const causeCurrent = this._textScanner.causeCurrent; + if (textSourceCurrent !== null && causeCurrent !== null) { + await this._search(textSourceCurrent, causeCurrent); + } + } + async _updatePopup() { const showIframePopupsInRootFrame = this._options.general.showIframePopupsInRootFrame; const isIframe = !this._useProxyPopup && (window !== window.parent); @@ -328,8 +339,15 @@ class Frontend { return this._popup === null || this._popup.isProxy() ? [] : [this._popup.getContainer()]; } - _ignorePoint(x, y) { - return this._popup !== null && this._popup.containsPoint(x, y); + async _ignorePoint(x, y) { + try { + return this._popup !== null && await this._popup.containsPoint(x, y); + } catch (e) { + if (!yomichan.isExtensionUnloaded) { + throw e; + } + return false; + } } async _search(textSource, cause) { @@ -352,7 +370,7 @@ class Frontend { } catch (e) { if (yomichan.isExtensionUnloaded) { if (textSource !== null && this._options.scanning.modifier !== 'none') { - this._showPopupContent(textSource, await this.getOptionsContext(), 'extensionUnloaded'); + this._showExtensionUnloaded(textSource); } } else { yomichan.logError(e); @@ -392,6 +410,14 @@ class Frontend { return {definitions, type: 'kanji'}; } + async _showExtensionUnloaded(textSource) { + if (textSource === null) { + textSource = this._textScanner.getCurrentTextSource(); + if (textSource === null) { return; } + } + this._showPopupContent(textSource, await this.getOptionsContext()); + } + _showContent(textSource, focus, definitions, type, optionsContext) { const {url} = optionsContext; const sentenceExtent = this._options.anki.sentenceExt; @@ -414,6 +440,10 @@ class Frontend { details, context ); + this._lastShowPromise.catch((error) => { + if (yomichan.isExtensionUnloaded) { return; } + yomichan.logError(error); + }); return this._lastShowPromise; } diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 352c5b34..09c184db 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -69,7 +69,11 @@ class PopupProxy extends EventDispatcher { } async isVisible() { - return await this._invoke('isVisible', {id: this._id}); + try { + return await this._invoke('isVisible', {id: this._id}); + } catch (e) { + return false; + } } setVisibleOverride(visible) { @@ -98,8 +102,12 @@ class PopupProxy extends EventDispatcher { this._invoke('setCustomCss', {id: this._id, css}); } - clearAutoPlayTimer() { - this._invoke('clearAutoPlayTimer', {id: this._id}); + async clearAutoPlayTimer() { + try { + await this._invoke('clearAutoPlayTimer', {id: this._id}); + } catch (e) { + // NOP + } } setContentScale(scale) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 4f2de4f2..35e66044 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -83,6 +83,7 @@ class Popup { this._frame.addEventListener('mousedown', (e) => e.stopPropagation()); this._frame.addEventListener('scroll', (e) => e.stopPropagation()); this._frame.addEventListener('load', this._onFrameLoad.bind(this)); + yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); } isProxy() { @@ -149,8 +150,12 @@ class Popup { this._invokeApi('setCustomCss', {css}); } - clearAutoPlayTimer() { - this._invokeApi('clearAutoPlayTimer'); + async clearAutoPlayTimer() { + try { + await this._invokeApi('clearAutoPlayTimer'); + } catch (e) { + // NOP + } } setContentScale(scale) { @@ -447,6 +452,18 @@ class Popup { return await api.crossFrame.invoke(this._frameClient.frameId, 'popupMessage', message); } + _invokeWindowApi(action, params={}) { + const contentWindow = this._frame.contentWindow; + if (this._frameClient === null || !this._frameClient.isConnected() || contentWindow === null) { return; } + + const message = this._frameClient.createMessage({action, params}); + contentWindow.postMessage(message, this._targetOrigin); + } + + _onExtensionUnloaded() { + this._invokeWindowApi('extensionUnloaded'); + } + _getFrameParentElement() { const defaultParent = document.body; const fullscreenElement = DOM.getFullscreenElement(); @@ -636,15 +653,4 @@ class Popup { bottom: window.innerHeight }; } - - static isFrameAboutBlank(frame) { - try { - const contentDocument = frame.contentDocument; - if (contentDocument === null) { return false; } - const url = contentDocument.location.href; - return /^about:blank(?:[#?]|$)/.test(url); - } catch (e) { - return false; - } - } } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 7dc63e65..bf3e3eae 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -97,14 +97,12 @@ class Display { this._setInteractive(true); await yomichan.ready(); await this._displayGenerator.prepare(); + yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); } onError(error) { - if (yomichan.isExtensionUnloaded) { - this.setContent('extensionUnloaded'); - } else { - yomichan.logError(error); - } + if (yomichan.isExtensionUnloaded) { return; } + yomichan.logError(error); } onEscape() { @@ -176,9 +174,6 @@ class Display { case 'kanji': await this._setContentKanji(details.definitions, details.context, token); break; - case 'extensionUnloaded': - this._setContentExtensionUnloaded(); - break; } } catch (e) { this.onError(e); @@ -236,6 +231,10 @@ class Display { // Private + _onExtensionUnloaded() { + this._setContentExtensionUnloaded(); + } + _onSourceTermView(e) { e.preventDefault(); this._sourceTermView(); diff --git a/ext/mixed/js/yomichan.js b/ext/mixed/js/yomichan.js index 7fffbaa6..33870658 100644 --- a/ext/mixed/js/yomichan.js +++ b/ext/mixed/js/yomichan.js @@ -196,7 +196,7 @@ const yomichan = (() => { try { return chrome.runtime.sendMessage(...args); } catch (e) { - this._onExtensionUnloaded(e); + this.triggerExtensionUnloaded(); throw e; } } @@ -205,7 +205,7 @@ const yomichan = (() => { try { return chrome.runtime.connect(...args); } catch (e) { - this._onExtensionUnloaded(e); + this.triggerExtensionUnloaded(); throw e; } } @@ -247,13 +247,13 @@ const yomichan = (() => { } } - // Private - - _onExtensionUnloaded(error) { + triggerExtensionUnloaded() { this._isExtensionUnloaded = true; - this.trigger('extensionUnloaded', {error}); + this.trigger('extensionUnloaded'); } + // Private + _getUrl() { return (typeof window === 'object' && window !== null ? window.location.href : ''); }