Bug 231167 - MessagePort messages sent in iframe unload event not received
Summary: MessagePort messages sent in iframe unload event not received
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Frames (show other bugs)
Version: Safari 15
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Chris Dumez
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2021-10-04 05:16 PDT by Robert Knight
Modified: 2021-10-13 10:20 PDT (History)
7 users (show)

See Also:


Attachments
iframe unload notification test case (1.24 KB, text/html)
2021-10-04 05:16 PDT, Robert Knight
no flags Details
WIP Patch (811 bytes, patch)
2021-10-12 11:28 PDT, Chris Dumez
no flags Details | Formatted Diff | Diff
Patch (6.30 KB, patch)
2021-10-13 08:35 PDT, Chris Dumez
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Robert Knight 2021-10-04 05:16:21 PDT
Created attachment 440054 [details]
iframe unload notification test case

In the attached test case an iframe sends a notification to its parent frame via a) a MessagePort and b) `window.parent.postMessage` when it is unloaded. In Firefox and Chrome the messages sent via both methods are received in the parent frame. In Safari only the `window.parent.postMessage` events are received. The message sent via the MessagePort is not.

Steps to reproduce:

1. Load attached test case and click "Remove iframe" button

Console output in Chrome:

```
iframe received "pagehide" event
iframe received "visibilitychange" event
iframe received "unload" event
MessagePort in parent frame received message {trigger: 'pagehide'}
Parent frame received "message" event {trigger: 'pagehide'}
Parent frame received "message" event {trigger: 'visibilitychange'}
Parent frame received "message" event {trigger: 'unload'}
MessagePort in parent frame received message {trigger: 'visibilitychange'}
MessagePort in parent frame received message {trigger: 'unload'}
```

Firefox has the same output but with a slightly different order.

Console output in Safari 15:

```
iframe received "pagehide" event
iframe received "visibilitychange" event
iframe received "unload" event
Parent frame received "message" event – {trigger: "pagehide"}
Parent frame received "message" event – {trigger: "visibilitychange"}
Parent frame received "message" event – {trigger: "unload"}
```
Comment 1 Robert Knight 2021-10-04 07:27:31 PDT
In case it is relevant, the results seem to be the same when the parent and child frames are cross-origin.
Comment 2 Radar WebKit Bug Importer 2021-10-11 05:17:17 PDT
<rdar://problem/84095357>
Comment 3 Chris Dumez 2021-10-12 11:17:23 PDT
MessagePort::close() gets called *before* the calls to MessagePort::postMessage(), likely as a result of MessagePort::stop() since MessagePort is an ActiveDOMObject. As a result, the calls to postMessage() are ignored.
Comment 4 Chris Dumez 2021-10-12 11:20:01 PDT
(In reply to Chris Dumez from comment #3)
> MessagePort::close() gets called *before* the calls to
> MessagePort::postMessage(), likely as a result of MessagePort::stop() since
> MessagePort is an ActiveDOMObject. As a result, the calls to postMessage()
> are ignored.

1   0x62998b0c4 WebCore::MessagePort::close()
2   0x629990165 WebCore::MessagePort::stop()
3   0x629a3a13a auto WebCore::ScriptExecutionContext::stopActiveDOMObjects()::$_4::operator()<WebCore::ActiveDOMObject>(WebCore::ActiveDOMObject&) const
4   0x629a3a0d1 WTF::Detail::CallableWrapper<WebCore::ScriptExecutionContext::stopActiveDOMObjects()::$_4, WebCore::ScriptExecutionContext::ShouldContinue, WebCore::ActiveDOMObject&>::call(WebCore::ActiveDOMObject&)
5   0x629a23c37 WTF::Function<WebCore::ScriptExecutionContext::ShouldContinue (WebCore::ActiveDOMObject&)>::operator()(WebCore::ActiveDOMObject&) const
6   0x629a23aaa WebCore::ScriptExecutionContext::forEachActiveDOMObject(WTF::Function<WebCore::ScriptExecutionContext::ShouldContinue (WebCore::ActiveDOMObject&)> const&) const
7   0x629a23fa4 WebCore::ScriptExecutionContext::stopActiveDOMObjects()
8   0x629833765 WebCore::Document::stopActiveDOMObjects()
9   0x62a3f64a0 WebCore::FrameLoader::frameDetached()
10  0x629d1d337 WebCore::HTMLFrameOwnerElement::disconnectContentFrame()
11  0x6297ebce8 WebCore::disconnectSubframes(WebCore::ContainerNode&, WebCore::SubframeDisconnectPolicy)
12  0x6297e7112 WebCore::disconnectSubframesIfNeeded(WebCore::ContainerNode&, WebCore::SubframeDisconnectPolicy)
13  0x6297e72a5 WebCore::ContainerNode::removeNodeWithScriptAssertion(WebCore::Node&, WebCore::ContainerNode::ChildChange::Source)
14  0x6297e3862 WebCore::ContainerNode::removeChild(WebCore::Node&)
15  0x6299bf8b6 WebCore::Node::remove()
16  0x62734baf6 WebCore::jsElementPrototypeFunction_removeBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSElement*)::'lambda'()::operator()() const
Comment 5 Chris Dumez 2021-10-12 11:23:36 PDT
(In reply to Chris Dumez from comment #4)
> (In reply to Chris Dumez from comment #3)
> > MessagePort::close() gets called *before* the calls to
> > MessagePort::postMessage(), likely as a result of MessagePort::stop() since
> > MessagePort is an ActiveDOMObject. As a result, the calls to postMessage()
> > are ignored.
> 
> 1   0x62998b0c4 WebCore::MessagePort::close()
> 2   0x629990165 WebCore::MessagePort::stop()
> 3   0x629a3a13a auto
> WebCore::ScriptExecutionContext::stopActiveDOMObjects()::$_4::
> operator()<WebCore::ActiveDOMObject>(WebCore::ActiveDOMObject&) const
> 4   0x629a3a0d1
> WTF::Detail::CallableWrapper<WebCore::ScriptExecutionContext::
> stopActiveDOMObjects()::$_4,
> WebCore::ScriptExecutionContext::ShouldContinue,
> WebCore::ActiveDOMObject&>::call(WebCore::ActiveDOMObject&)
> 5   0x629a23c37
> WTF::Function<WebCore::ScriptExecutionContext::ShouldContinue
> (WebCore::ActiveDOMObject&)>::operator()(WebCore::ActiveDOMObject&) const
> 6   0x629a23aaa
> WebCore::ScriptExecutionContext::forEachActiveDOMObject(WTF::
> Function<WebCore::ScriptExecutionContext::ShouldContinue
> (WebCore::ActiveDOMObject&)> const&) const
> 7   0x629a23fa4 WebCore::ScriptExecutionContext::stopActiveDOMObjects()
> 8   0x629833765 WebCore::Document::stopActiveDOMObjects()
> 9   0x62a3f64a0 WebCore::FrameLoader::frameDetached()
> 10  0x629d1d337 WebCore::HTMLFrameOwnerElement::disconnectContentFrame()
> 11  0x6297ebce8 WebCore::disconnectSubframes(WebCore::ContainerNode&,
> WebCore::SubframeDisconnectPolicy)
> 12  0x6297e7112
> WebCore::disconnectSubframesIfNeeded(WebCore::ContainerNode&,
> WebCore::SubframeDisconnectPolicy)
> 13  0x6297e72a5
> WebCore::ContainerNode::removeNodeWithScriptAssertion(WebCore::Node&,
> WebCore::ContainerNode::ChildChange::Source)
> 14  0x6297e3862 WebCore::ContainerNode::removeChild(WebCore::Node&)
> 15  0x6299bf8b6 WebCore::Node::remove()
> 16  0x62734baf6
> WebCore::jsElementPrototypeFunction_removeBody(JSC::JSGlobalObject*,
> JSC::CallFrame*, WebCore::JSElement*)::'lambda'()::operator()() const

And the call trace from postMessage() is:
1   0x58198b2d2 WebCore::MessagePort::postMessage(JSC::JSGlobalObject&, JSC::JSValue, WebCore::StructuredSerializeOptions&&)
2   0x57f9264c9 WebCore::jsMessagePortPrototypeFunction_postMessage2Body(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMessagePort*)::'lambda'()::operator()() const
3   0x57f9263c1 JSC::JSValue WebCore::toJS<WebCore::IDLUndefined, WebCore::jsMessagePortPrototypeFunction_postMessage2Body(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMessagePort*)::'lambda'()>(JSC::JSGlobalObject&, JSC::ThrowScope&, WebCore::jsMessagePortPrototypeFunction_postMessage2Body(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMessagePort*)::'lambda'()&&)
4   0x57f925efc WebCore::jsMessagePortPrototypeFunction_postMessage2Body(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMessagePort*)
5   0x57f9257ab WebCore::jsMessagePortPrototypeFunction_postMessageOverloadDispatcher(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMessagePort*)
6   0x57f92568d long long WebCore::IDLOperation<WebCore::JSMessagePort>::call<&(WebCore::jsMessagePortPrototypeFunction_postMessageOverloadDispatcher(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSMessagePort*)), (WebCore::CastedThisErrorBehavior)0>(JSC::JSGlobalObject&, JSC::CallFrame&, char const*)
7   0x57f925374 WebCore::jsMessagePortPrototypeFunction_postMessage(JSC::JSGlobalObject*, JSC::CallFrame*)
8   0x3df9658011d8
9   0x56d11f990 llint_entry
10  0x56d0fbda0 vmEntryToJavaScript
11  0x56dfc81b5 JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*)
12  0x56dfc8973 JSC::Interpreter::executeCall(JSC::JSGlobalObject*, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&)
13  0x56e38079a JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&)
14  0x56e38087d JSC::call(JSC::JSGlobalObject*, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&)
15  0x56e380b3d JSC::profiledCall(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&)
16  0x5811a464c WebCore::JSExecState::profiledCall(JSC::JSGlobalObject*, JSC::ProfilingReason, JSC::JSValue, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&)
17  0x5811c3cb9 WebCore::JSEventListener::handleEvent(WebCore::ScriptExecutionContext&, WebCore::Event&)
18  0x58194e2fe WebCore::EventTarget::innerInvokeEventListeners(WebCore::Event&, WTF::Vector<WTF::RefPtr<WebCore::RegisteredEventListener, WTF::RawPtrTraits<WebCore::RegisteredEventListener>, WTF::DefaultRefDerefTraits<WebCore::RegisteredEventListener> >, 1ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>, WebCore::EventTarget::EventInvokePhase)
19  0x58194dd86 WebCore::EventTarget::fireEventListeners(WebCore::Event&, WebCore::EventTarget::EventInvokePhase)
20  0x58257d28f WebCore::DOMWindow::dispatchEvent(WebCore::Event&, WebCore::EventTarget*)
21  0x5823e297b WebCore::FrameLoader::dispatchUnloadEvents(WebCore::UnloadEventPolicy)
22  0x5823e270a WebCore::FrameLoader::stopLoading(WebCore::UnloadEventPolicy)
23  0x5823e2f59 WebCore::FrameLoader::closeURL()
24  0x5823f5c79 WebCore::FrameLoader::detachFromParent()
25  0x5823f64a9 WebCore::FrameLoader::frameDetached()
26  0x581d1d337 WebCore::HTMLFrameOwnerElement::disconnectContentFrame()
Comment 6 Chris Dumez 2021-10-12 11:28:42 PDT
Created attachment 440964 [details]
WIP Patch
Comment 7 Chris Dumez 2021-10-13 08:35:00 PDT
Created attachment 441083 [details]
Patch
Comment 8 EWS 2021-10-13 10:20:28 PDT
Committed r284101 (242929@main): <https://commits.webkit.org/242929@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 441083 [details].