diff --git a/docs/api/message-port-main.md b/docs/api/message-port-main.md index 2b1d528ab1..bbd8d1d8b9 100644 --- a/docs/api/message-port-main.md +++ b/docs/api/message-port-main.md @@ -45,5 +45,9 @@ Returns: Emitted when a MessagePortMain object receives a message. +#### Event: 'close' + +Emitted when the remote end of a MessagePortMain object becomes disconnected. + [`MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort [Channel Messaging API]: https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API diff --git a/patches/chromium/.patches b/patches/chromium/.patches index 5c0e6c3b7f..a2a6154879 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -91,3 +91,4 @@ feat_enable_offscreen_rendering_with_viz_compositor.patch delay_lock_the_protocol_scheme_registry.patch gpu_notify_when_dxdiag_request_fails.patch feat_allow_embedders_to_add_observers_on_created_hunspell.patch +feat_add_onclose_to_messageport.patch diff --git a/patches/chromium/feat_add_onclose_to_messageport.patch b/patches/chromium/feat_add_onclose_to_messageport.patch new file mode 100644 index 0000000000..2ef64ad511 --- /dev/null +++ b/patches/chromium/feat_add_onclose_to_messageport.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jeremy Apthorp +Date: Wed, 4 Mar 2020 11:18:03 -0800 +Subject: feat: add onclose to MessagePort + +This adds the 'onclose' event to MessagePort. This is +[proposed](https://github.com/w3ctag/design-reviews/issues/269#issuecomment-407584290) +in w3c and has been discussed for years without conclusion. I'd like to +get this standardised, but in lieu of that, this makes MessagePort a +whole bunch more useful! + +diff --git a/third_party/blink/renderer/core/messaging/message_port.cc b/third_party/blink/renderer/core/messaging/message_port.cc +index e2ddbafc9ea2836a302da481702e2922949ffe78..7da7071862e384bce65ecb52996b5287891a33f9 100644 +--- a/third_party/blink/renderer/core/messaging/message_port.cc ++++ b/third_party/blink/renderer/core/messaging/message_port.cc +@@ -156,6 +156,7 @@ void MessagePort::close() { + Entangle(mojo::MessagePipe().handle0); + } + closed_ = true; ++ DispatchEvent(*Event::Create(event_type_names::kClose)); + } + + void MessagePort::Entangle(mojo::ScopedMessagePipeHandle handle) { +diff --git a/third_party/blink/renderer/core/messaging/message_port.h b/third_party/blink/renderer/core/messaging/message_port.h +index 2a08335398b30671a61aee0f1ebe060222a4f1ff..874aecb9c038f19cc03641a19ce51cf2f958d80c 100644 +--- a/third_party/blink/renderer/core/messaging/message_port.h ++++ b/third_party/blink/renderer/core/messaging/message_port.h +@@ -119,6 +119,13 @@ class CORE_EXPORT MessagePort : public EventTargetWithInlineData, + return GetAttributeEventListener(event_type_names::kMessageerror); + } + ++ void setOnclose(EventListener* listener) { ++ SetAttributeEventListener(event_type_names::kClose, listener); ++ } ++ EventListener* onclose() { ++ return GetAttributeEventListener(event_type_names::kClose); ++ } ++ + // A port starts out its life entangled, and remains entangled until it is + // closed or is cloned. + bool IsEntangled() const { return !closed_ && !IsNeutered(); } +diff --git a/third_party/blink/renderer/core/messaging/message_port.idl b/third_party/blink/renderer/core/messaging/message_port.idl +index 6fab27fcdf1c333739b6ffe88b3cc4eed3301ee4..3f1f181d9b8a66997136f870f55c97c08294b6eb 100644 +--- a/third_party/blink/renderer/core/messaging/message_port.idl ++++ b/third_party/blink/renderer/core/messaging/message_port.idl +@@ -40,4 +40,5 @@ + // event handlers + attribute EventHandler onmessage; + attribute EventHandler onmessageerror; ++ attribute EventHandler onclose; + }; diff --git a/shell/browser/api/message_port.cc b/shell/browser/api/message_port.cc index 04242e182b..d1f50e8bb9 100644 --- a/shell/browser/api/message_port.cc +++ b/shell/browser/api/message_port.cc @@ -103,6 +103,12 @@ void MessagePort::Close() { closed_ = true; if (!HasPendingActivity()) Unpin(); + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + v8::Local self; + if (GetWrapper(isolate).ToLocal(&self)) + gin_helper::EmitEvent(isolate, self, "close"); } void MessagePort::Entangle(mojo::ScopedMessagePipeHandle handle) { @@ -196,6 +202,7 @@ void MessagePort::Pin() { if (!pinned_.IsEmpty()) return; v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); v8::Local self; if (GetWrapper(isolate).ToLocal(&self)) { pinned_.Reset(isolate, self); diff --git a/spec-main/api-ipc-spec.ts b/spec-main/api-ipc-spec.ts index 70ae38edba..5fa43d73e2 100644 --- a/spec-main/api-ipc-spec.ts +++ b/spec-main/api-ipc-spec.ts @@ -278,6 +278,57 @@ describe('ipc module', () => { expect(data).to.equal('a message') }) + describe('close event', () => { + describe('in renderer', () => { + it('is emitted when the main process closes its end of the port', async () => { + const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }) + w.loadURL('about:blank') + await w.webContents.executeJavaScript(`(${function () { + const { ipcRenderer } = require('electron') + ipcRenderer.on('port', (e) => { + const [port] = e.ports + port.start(); + (port as any).onclose = () => { + ipcRenderer.send('closed') + } + }) + }})()`) + const { port1, port2 } = new MessageChannelMain() + w.webContents.postMessage('port', null, [port2]) + port1.close() + await emittedOnce(ipcMain, 'closed') + }) + + it('is emitted when the other end of a port is garbage-collected', async () => { + const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }) + w.loadURL('about:blank') + await w.webContents.executeJavaScript(`(${async function () { + const { port2 } = new MessageChannel() + await new Promise(resolve => { + port2.start(); + (port2 as any).onclose = resolve + process.electronBinding('v8_util').requestGarbageCollectionForTesting() + }) + }})()`) + }) + + it('is emitted when the other end of a port is sent to nowhere', async () => { + const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }) + w.loadURL('about:blank') + ipcMain.once('do-a-gc', () => v8Util.requestGarbageCollectionForTesting()) + await w.webContents.executeJavaScript(`(${async function () { + const { port1, port2 } = new MessageChannel() + await new Promise(resolve => { + port2.start(); + (port2 as any).onclose = resolve + require('electron').ipcRenderer.postMessage('nobody-listening', null, [port1]) + require('electron').ipcRenderer.send('do-a-gc') + }) + }})()`) + }) + }) + }) + describe('MessageChannelMain', () => { it('can be created', () => { const { port1, port2 } = new MessageChannelMain()