Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

window.postMessage with OffscreenCanvas is broken with isolated world message listener #16003

Merged

Conversation

cdumez
Copy link
Contributor

@cdumez cdumez commented Jul 21, 2023

acece69

window.postMessage with OffscreenCanvas is broken with isolated world message listener
https://bugs.webkit.org/show_bug.cgi?id=259362
rdar://112618195

Reviewed by Darin Adler.

When constructing a MessageEvent, we would deserialize the `data` SerializedScriptValue
and cache the resulting JSValue. When accessing MessageEvent.data from the main world,
we would return the cached JSValue and everything would work fine.
However, upon accessing MessageEvent.data from a non-main world, the cached JSValue
would not be usable and we would deserialize the original SerializedScriptValue again.

The issue is that a SerializedScriptValue is not meant to be deserialized several times.
This is because the deserialization "consumes" certain internal objects. For examples,
OffscreenCanvas are stored as DetachedOffscreenCanvas internally and consumed upon
deserialization to construct OffscreenCanvas objects again.

To address the issue, this patch makes several changes:
1. MessageEvent::create() now stores the deserialized JSValue inside the MessageEvent
   object instead of the SerializedScriptValue. As a result, when accessing
   MessageEvent.data from the main world, we'll just return the internal JSValue.
   When accessing MessageEvent.data from a non-main world, cachedPropertyValue() will
   detect that the internal JSValue is no compatible with this world and call
   cloneAcrossWorlds() on the internal JSValue to generate one suitable for the non-main
   world. Internally, cloneAcrossWorlds() creates a SerializedScriptValue from the JSValue
   and then deserializes that SerializedScriptValue in the target world.
2. As currently implemented, cloneAcrossWorlds() would drop transferrable objects such
   as OffscreenCanvas and MessagePort. To address the issue, we now introduce a new
   CloneAcrossWorlds SerializationContext. When in this context, SerializedScriptValue
   serialization will store OffscreenCanvas/MessagePort in the JSValue inside internal
   vectors and merely serialize indexes inside those vectors. Upon deserialization, we
   deserialize the index and lookup the OffscreenCanvas/MessagePort from the internal
   vector. Then, we call toJS() on the implementation object to get a JS wrapper for the
   target world.

* Source/WebCore/bindings/js/JSDOMWrapper.cpp:
(WebCore::cloneAcrossWorlds):
* Source/WebCore/bindings/js/SerializedScriptValue.cpp:
(WebCore::isTypeExposedToGlobalObject):
(WebCore::CloneSerializer::serialize):
(WebCore::CloneSerializer::CloneSerializer):
(WebCore::CloneSerializer::dumpOffscreenCanvas):
(WebCore::CloneSerializer::dumpIfTerminal):
(WebCore::CloneDeserializer::deserialize):
(WebCore::CloneDeserializer::CloneDeserializer):
(WebCore::CloneDeserializer::readInMemoryOffscreenCanvas):
(WebCore::CloneDeserializer::readTerminal):
(WebCore::SerializedScriptValue::SerializedScriptValue):
(WebCore::SerializedScriptValue::create):
(WebCore::SerializedScriptValue::deserialize):
* Source/WebCore/bindings/js/SerializedScriptValue.h:
* Source/WebCore/dom/MessageEvent.cpp:
(WebCore::MessageEvent::create):
* Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/UserContentWorld.mm:
(-[UserContentWorldMessageHandler userContentController:didReceiveScriptMessage:]):
(TEST):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/postMessage-various-types.html: Added.

Canonical link: https://commits.webkit.org/266465@main

c12afab

Misc iOS, tvOS & watchOS macOS Linux Windows
❌ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ✅ 🛠 wincairo
✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug ✅ 🧪 wpe-wk2
✅ 🧪 webkitperl ✅ 🧪 ios-wk2 ✅ 🧪 api-mac ✅ 🛠 gtk
✅ 🧪 ios-wk2-wpt ✅ 🧪 mac-wk1 ✅ 🧪 gtk-wk2
✅ 🧪 api-ios ✅ 🧪 mac-wk2 ✅ 🧪 api-gtk
✅ 🛠 tv ✅ 🧪 mac-AS-debug-wk2
✅ 🛠 tv-sim
✅ 🛠 🧪 merge ✅ 🛠 watch
✅ 🛠 watch-sim

@cdumez cdumez self-assigned this Jul 21, 2023
@cdumez cdumez added the WebKit API For issues and bugs in the Web Kit public embedding APIs label Jul 21, 2023
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jul 22, 2023
@cdumez cdumez removed the merging-blocked Applied to prevent a change from being merged label Jul 31, 2023
@cdumez cdumez marked this pull request as ready for review July 31, 2023 15:11
@cdumez cdumez requested a review from rniwa as a code owner July 31, 2023 15:11
Copy link
Member

@darinadler darinadler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you learn anything from this that applies to other uses of JSValueInWrappedObject? We should do whatever we can to make it easier to use correctly.

#endif
Vector<RefPtr<MessagePort>>& inMemoryMessagePorts,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly inconsistent indenting here. When we end up with these large packages of parameters, I wonder if we might need some kind of structure instead of a parameter list, anyway. Should be fine for now, but #if and parameter lists don’t combined well.

Comment on lines +3902 to +3908
uint32_t index;
bool indexSuccessfullyRead = read(index);
if (!indexSuccessfullyRead || index >= m_inMemoryOffscreenCanvases.size()) {
fail();
return JSValue();
}
return getJSValue(m_inMemoryOffscreenCanvases[index].get());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like to explore things in future to make this code less repetitive.

Source/WebCore/dom/MessageEvent.cpp Show resolved Hide resolved
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jul 31, 2023
@cdumez cdumez removed the merging-blocked Applied to prevent a change from being merged label Aug 1, 2023
@cdumez cdumez force-pushed the OffscreenCanvas_isolatedWorld branch from d4cb260 to c12afab Compare August 1, 2023 02:03
@cdumez cdumez added the merge-queue Applied to send a pull request to merge-queue label Aug 1, 2023
… message listener

https://bugs.webkit.org/show_bug.cgi?id=259362
rdar://112618195

Reviewed by Darin Adler.

When constructing a MessageEvent, we would deserialize the `data` SerializedScriptValue
and cache the resulting JSValue. When accessing MessageEvent.data from the main world,
we would return the cached JSValue and everything would work fine.
However, upon accessing MessageEvent.data from a non-main world, the cached JSValue
would not be usable and we would deserialize the original SerializedScriptValue again.

The issue is that a SerializedScriptValue is not meant to be deserialized several times.
This is because the deserialization "consumes" certain internal objects. For examples,
OffscreenCanvas are stored as DetachedOffscreenCanvas internally and consumed upon
deserialization to construct OffscreenCanvas objects again.

To address the issue, this patch makes several changes:
1. MessageEvent::create() now stores the deserialized JSValue inside the MessageEvent
   object instead of the SerializedScriptValue. As a result, when accessing
   MessageEvent.data from the main world, we'll just return the internal JSValue.
   When accessing MessageEvent.data from a non-main world, cachedPropertyValue() will
   detect that the internal JSValue is no compatible with this world and call
   cloneAcrossWorlds() on the internal JSValue to generate one suitable for the non-main
   world. Internally, cloneAcrossWorlds() creates a SerializedScriptValue from the JSValue
   and then deserializes that SerializedScriptValue in the target world.
2. As currently implemented, cloneAcrossWorlds() would drop transferrable objects such
   as OffscreenCanvas and MessagePort. To address the issue, we now introduce a new
   CloneAcrossWorlds SerializationContext. When in this context, SerializedScriptValue
   serialization will store OffscreenCanvas/MessagePort in the JSValue inside internal
   vectors and merely serialize indexes inside those vectors. Upon deserialization, we
   deserialize the index and lookup the OffscreenCanvas/MessagePort from the internal
   vector. Then, we call toJS() on the implementation object to get a JS wrapper for the
   target world.

* Source/WebCore/bindings/js/JSDOMWrapper.cpp:
(WebCore::cloneAcrossWorlds):
* Source/WebCore/bindings/js/SerializedScriptValue.cpp:
(WebCore::isTypeExposedToGlobalObject):
(WebCore::CloneSerializer::serialize):
(WebCore::CloneSerializer::CloneSerializer):
(WebCore::CloneSerializer::dumpOffscreenCanvas):
(WebCore::CloneSerializer::dumpIfTerminal):
(WebCore::CloneDeserializer::deserialize):
(WebCore::CloneDeserializer::CloneDeserializer):
(WebCore::CloneDeserializer::readInMemoryOffscreenCanvas):
(WebCore::CloneDeserializer::readTerminal):
(WebCore::SerializedScriptValue::SerializedScriptValue):
(WebCore::SerializedScriptValue::create):
(WebCore::SerializedScriptValue::deserialize):
* Source/WebCore/bindings/js/SerializedScriptValue.h:
* Source/WebCore/dom/MessageEvent.cpp:
(WebCore::MessageEvent::create):
* Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/UserContentWorld.mm:
(-[UserContentWorldMessageHandler userContentController:didReceiveScriptMessage:]):
(TEST):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/postMessage-various-types.html: Added.

Canonical link: https://commits.webkit.org/266465@main
@webkit-commit-queue
Copy link
Collaborator

Committed 266465@main (acece69): https://commits.webkit.org/266465@main

Reviewed commits have been landed. Closing PR #16003 and removing active labels.

@webkit-commit-queue webkit-commit-queue merged commit acece69 into WebKit:main Aug 1, 2023
@webkit-commit-queue webkit-commit-queue removed the merge-queue Applied to send a pull request to merge-queue label Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
WebKit API For issues and bugs in the Web Kit public embedding APIs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants