From dac33e696145ad3c2cfe076a7fadc82c05732102 Mon Sep 17 00:00:00 2001
From: toasted-nutbread <toasted-nutbread@users.noreply.github.com>
Date: Sat, 18 Jul 2020 14:15:36 -0400
Subject: 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 +++++++++++---------
 4 files changed, 106 insertions(+), 41 deletions(-)

(limited to 'ext/fg/js')

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;
-        }
-    }
 }
-- 
cgit v1.2.3