From 6769ff501c8b5d20e9056de57eff5a62a92339d3 Mon Sep 17 00:00:00 2001 From: praschke Date: Sat, 30 Sep 2023 17:17:31 +0100 Subject: create both cross-frame ports in the background on Chrome (currently 117), the port created in the content script with runtime.connect does not properly receive an onDisconnect event when the service worker sleeps. the port created in the background with tabs.connect does receive the event, so create both ports with tabs.connect. fixes #241. --- ext/js/background/backend.js | 102 +++++++++++++++++++---------------------- ext/js/comm/api.js | 4 ++ ext/js/comm/cross-frame-api.js | 31 +++++++------ 3 files changed, 68 insertions(+), 69 deletions(-) (limited to 'ext') diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index dd233abb..3cac2e4d 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -139,7 +139,8 @@ class Backend { ['textHasJapaneseCharacters', {async: false, contentScript: true, handler: this._onApiTextHasJapaneseCharacters.bind(this)}], ['getTermFrequencies', {async: true, contentScript: true, handler: this._onApiGetTermFrequencies.bind(this)}], ['findAnkiNotes', {async: true, contentScript: true, handler: this._onApiFindAnkiNotes.bind(this)}], - ['loadExtensionScripts', {async: true, contentScript: true, handler: this._onApiLoadExtensionScripts.bind(this)}] + ['loadExtensionScripts', {async: true, contentScript: true, handler: this._onApiLoadExtensionScripts.bind(this)}], + ['openCrossFramePort', {async: false, contentScript: true, handler: this._onApiOpenCrossFramePort.bind(this)}] ]); this._messageHandlersWithProgress = new Map([ ]); @@ -189,9 +190,6 @@ class Backend { chrome.tabs.onZoomChange.addListener(onZoomChange); } - const onConnect = this._onWebExtensionEventWrapper(this._onConnect.bind(this)); - chrome.runtime.onConnect.addListener(onConnect); - const onMessage = this._onMessageWrapper.bind(this); chrome.runtime.onMessage.addListener(onMessage); @@ -331,58 +329,6 @@ class Backend { return invokeMessageHandler(messageHandler, params, callback, sender); } - _onConnect(port) { - try { - let details; - try { - details = JSON.parse(port.name); - } catch (e) { - return; - } - if (details.name !== 'background-cross-frame-communication-port') { return; } - - const senderTabId = (port.sender && port.sender.tab ? port.sender.tab.id : null); - if (typeof senderTabId !== 'number') { - throw new Error('Port does not have an associated tab ID'); - } - const senderFrameId = port.sender.frameId; - if (typeof senderFrameId !== 'number') { - throw new Error('Port does not have an associated frame ID'); - } - let {targetTabId, targetFrameId} = details; - if (typeof targetTabId !== 'number') { - targetTabId = senderTabId; - } - - const details2 = { - name: 'cross-frame-communication-port', - sourceTabId: senderTabId, - sourceFrameId: senderFrameId - }; - let forwardPort = chrome.tabs.connect(targetTabId, {frameId: targetFrameId, name: JSON.stringify(details2)}); - - const cleanup = () => { - this._checkLastError(chrome.runtime.lastError); - if (forwardPort !== null) { - forwardPort.disconnect(); - forwardPort = null; - } - if (port !== null) { - port.disconnect(); - port = null; - } - }; - - port.onMessage.addListener((message) => { forwardPort.postMessage(message); }); - forwardPort.onMessage.addListener((message) => { port.postMessage(message); }); - port.onDisconnect.addListener(cleanup); - forwardPort.onDisconnect.addListener(cleanup); - } catch (e) { - port.disconnect(); - log.error(e); - } - } - _onZoomChange({tabId, oldZoomFactor, newZoomFactor}) { this._sendMessageTabIgnoreResponse(tabId, {action: 'Yomichan.zoomChanged', params: {oldZoomFactor, newZoomFactor}}); } @@ -2273,4 +2219,48 @@ class Backend { } return results; } + + _onApiOpenCrossFramePort({targetTabId, targetFrameId}, sender) { + const sourceTabId = (sender && sender.tab ? sender.tab.id : null); + if (typeof sourceTabId !== 'number') { + throw new Error('Port does not have an associated tab ID'); + } + const sourceFrameId = sender.frameId; + if (typeof sourceFrameId !== 'number') { + throw new Error('Port does not have an associated frame ID'); + } + + const sourceDetails = { + name: 'cross-frame-communication-port', + otherTabId: targetTabId, + otherFrameId: targetFrameId + }; + const targetDetails = { + name: 'cross-frame-communication-port', + otherTabId: sourceTabId, + otherFrameId: sourceFrameId + }; + let sourcePort = chrome.tabs.connect(sourceTabId, {frameId: sourceFrameId, name: JSON.stringify(sourceDetails)}); + let targetPort = chrome.tabs.connect(targetTabId, {frameId: targetFrameId, name: JSON.stringify(targetDetails)}); + + const cleanup = () => { + console.log('cross-frame cleanup', targetPort, sourcePort); + this._checkLastError(chrome.runtime.lastError); + if (targetPort !== null) { + targetPort.disconnect(); + targetPort = null; + } + if (sourcePort !== null) { + sourcePort.disconnect(); + sourcePort = null; + } + }; + + sourcePort.onMessage.addListener((message) => { targetPort.postMessage(message); }); + targetPort.onMessage.addListener((message) => { sourcePort.postMessage(message); }); + sourcePort.onDisconnect.addListener(cleanup); + targetPort.onDisconnect.addListener(cleanup); + + return {targetTabId, targetFrameId}; + } } diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index de12bb6c..72d2ba07 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -181,6 +181,10 @@ class API { return this._invoke('loadExtensionScripts', {files}); } + openCrossFramePort(targetTabId, targetFrameId) { + return this._invoke('openCrossFramePort', {targetTabId, targetFrameId}); + } + // Utilities _createActionPort(timeout=5000) { diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js index 7892eb4c..fb2a1718 100644 --- a/ext/js/comm/cross-frame-api.js +++ b/ext/js/comm/cross-frame-api.js @@ -224,10 +224,13 @@ class CrossFrameAPI { this._commPorts = new Map(); this._messageHandlers = new Map(); this._onDisconnectBind = this._onDisconnect.bind(this); + this._tabId = null; + this._frameId = null; } - prepare() { + async prepare() { chrome.runtime.onConnect.addListener(this._onConnect.bind(this)); + ({tabId: this._tabId, frameId: this._frameId} = await yomichan.api.frameInformationGet()); } invoke(targetFrameId, action, params={}) { @@ -235,8 +238,8 @@ class CrossFrameAPI { } async invokeTab(targetTabId, targetFrameId, action, params={}) { - if (typeof targetTabId !== 'number') { targetTabId = null; } - const commPort = this._getOrCreateCommPort(targetTabId, targetFrameId); + if (typeof targetTabId !== 'number') { targetTabId = this._tabId; } + const commPort = await this._getOrCreateCommPort(targetTabId, targetFrameId); return await commPort.invoke(action, params, this._ackTimeout, this._responseTimeout); } @@ -265,8 +268,8 @@ class CrossFrameAPI { } if (details.name !== 'cross-frame-communication-port') { return; } - const otherTabId = details.sourceTabId; - const otherFrameId = details.sourceFrameId; + const otherTabId = details.otherTabId; + const otherFrameId = details.otherFrameId; this._setupCommPort(otherTabId, otherFrameId, port); } catch (e) { port.disconnect(); @@ -297,14 +300,16 @@ class CrossFrameAPI { return this._createCommPort(otherTabId, otherFrameId); } - _createCommPort(otherTabId, otherFrameId) { - const details = { - name: 'background-cross-frame-communication-port', - targetTabId: otherTabId, - targetFrameId: otherFrameId - }; - const port = yomichan.connect(null, {name: JSON.stringify(details)}); - return this._setupCommPort(otherTabId, otherFrameId, port); + async _createCommPort(otherTabId, otherFrameId) { + await yomichan.api.openCrossFramePort(otherTabId, otherFrameId); + + const tabPorts = this._commPorts.get(otherTabId); + if (typeof tabPorts !== 'undefined') { + const commPort = tabPorts.get(otherFrameId); + if (typeof commPort !== 'undefined') { + return commPort; + } + } } _setupCommPort(otherTabId, otherFrameId, port) { -- cgit v1.2.3 From 97a6cedbcd1a6e791b55b612086eb8d30ed1ca1f Mon Sep 17 00:00:00 2001 From: praschke Date: Sun, 1 Oct 2023 09:08:49 +0100 Subject: remove yomichan.connect() it was only used in the cross-frame api. additionally, this comment on triggerExtensionUnloaded is incorrect. --- ext/js/yomichan.js | 18 ------------------ 1 file changed, 18 deletions(-) (limited to 'ext') diff --git a/ext/js/yomichan.js b/ext/js/yomichan.js index 6eea952e..e4fcd4bc 100644 --- a/ext/js/yomichan.js +++ b/ext/js/yomichan.js @@ -172,24 +172,6 @@ class Yomichan extends EventDispatcher { } } - /** - * Runs `chrome.runtime.connect()` with additional exception handling events. - * @param {...*} args The arguments to be passed to `chrome.runtime.connect()`. - * @returns {Port} The resulting port. - * @throws {Error} Errors thrown by `chrome.runtime.connect()` are re-thrown. - */ - connect(...args) { - try { - return chrome.runtime.connect(...args); - } catch (e) { - this.triggerExtensionUnloaded(); - throw e; - } - } - - /** - * Runs chrome.runtime.connect() with additional exception handling events. - */ triggerExtensionUnloaded() { this._isExtensionUnloaded = true; if (this._isTriggeringExtensionUnloaded) { return; } -- cgit v1.2.3 From be46fb6faf8d9403a3b23e29e35cc2982a66732e Mon Sep 17 00:00:00 2001 From: praschke Date: Sun, 1 Oct 2023 09:10:17 +0100 Subject: remove stray debugging comment --- ext/js/background/backend.js | 1 - 1 file changed, 1 deletion(-) (limited to 'ext') diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 3cac2e4d..58387fd4 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -2244,7 +2244,6 @@ class Backend { let targetPort = chrome.tabs.connect(targetTabId, {frameId: targetFrameId, name: JSON.stringify(targetDetails)}); const cleanup = () => { - console.log('cross-frame cleanup', targetPort, sourcePort); this._checkLastError(chrome.runtime.lastError); if (targetPort !== null) { targetPort.disconnect(); -- cgit v1.2.3 From 841d49c7f97678b4873ea198b91419687d137849 Mon Sep 17 00:00:00 2001 From: praschke Date: Sun, 1 Oct 2023 09:47:59 +0100 Subject: wait for backend ready signal to prepare cross-frame api not sure if API messages should be sent in prepare(), but we should probably wait for the ready signal before doing so --- ext/js/yomichan.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ext') diff --git a/ext/js/yomichan.js b/ext/js/yomichan.js index e4fcd4bc..5a55b3ea 100644 --- a/ext/js/yomichan.js +++ b/ext/js/yomichan.js @@ -129,12 +129,12 @@ class Yomichan extends EventDispatcher { if (!isBackground) { this._api = new API(this); - this._crossFrame = new CrossFrameAPI(); - this._crossFrame.prepare(); - this.sendMessage({action: 'requestBackendReadySignal'}); await this._isBackendReadyPromise; + this._crossFrame = new CrossFrameAPI(); + await this._crossFrame.prepare(); + log.on('log', this._onForwardLog.bind(this)); } } -- cgit v1.2.3