Commit 45af854a authored by Kevin Marshall's avatar Kevin Marshall Committed by Commit Bot

Fuchsia: Add support for Window.postMessage() and MessagePort.

Adds a channel for sending messages to web content. Messages can include
MessagePorts to allow for bidirectional message exchange between the
FIDL client and web content.

Promotes Blink CloneableMessage and TransferableMessage struct traits
to "public" so that the messages can be serialized and deserialized
by code outside Blink.

Bug: 893236
Change-Id: If4e23a65e9d35e1ed2ce80de95e9f2212c55aecf
Reviewed-on: https://chromium-review.googlesource.com/c/1318839Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Reviewed-by: default avatarMarijn Kruisselbrink <mek@chromium.org>
Reviewed-by: default avatarWez <wez@chromium.org>
Commit-Queue: Kevin Marshall <kmarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#608515}
parent c6b23f1e
......@@ -4,6 +4,9 @@
#include "content/public/browser/message_port_provider.h"
#include <utility>
#include "build/build_config.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
......@@ -21,11 +24,12 @@ using blink::MessagePortChannel;
namespace content {
namespace {
void PostMessageToFrameInternal(WebContents* web_contents,
const base::string16& source_origin,
const base::string16& target_origin,
const base::string16& data,
std::vector<MessagePortChannel> channels) {
void PostMessageToFrameInternal(
WebContents* web_contents,
const base::string16& source_origin,
const base::Optional<base::string16>& target_origin,
const base::string16& data,
std::vector<MessagePortChannel> channels) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
FrameMsg_PostMessage_Params params;
......@@ -36,7 +40,8 @@ void PostMessageToFrameInternal(WebContents* web_contents,
params.message->data.ports = std::move(channels);
params.source_routing_id = MSG_ROUTING_NONE;
params.source_origin = source_origin;
params.target_origin = target_origin;
if (target_origin)
params.target_origin = *target_origin;
RenderFrameHost* rfh = web_contents->GetMainFrame();
rfh->Send(new FrameMsg_PostMessageEvent(rfh->GetRoutingID(), params));
......@@ -80,4 +85,22 @@ void MessagePortProvider::PostMessageToFrame(
}
#endif
} // namespace content
#if defined(OS_FUCHSIA)
// static
void MessagePortProvider::PostMessageToFrame(
WebContents* web_contents,
const base::string16& source_origin,
const base::Optional<base::string16>& target_origin,
const base::string16& data,
std::vector<mojo::ScopedMessagePipeHandle> channels) {
std::vector<MessagePortChannel> channels_wrapped;
for (mojo::ScopedMessagePipeHandle& handle : channels) {
channels_wrapped.emplace_back(std::move(handle));
}
PostMessageToFrameInternal(web_contents, source_origin, target_origin, data,
channels_wrapped);
}
#endif
} // namespace content
......@@ -9,13 +9,19 @@
#include "base/macros.h"
#include "base/memory/singleton.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "build/build_config.h"
#include "content/common/content_export.h"
#if defined(OS_ANDROID)
#include "base/android/scoped_java_ref.h"
#endif
#if defined(OS_FUCHSIA)
#include "third_party/blink/public/common/messaging/message_port_channel.h"
#endif
namespace content {
class WebContents;
......@@ -41,7 +47,17 @@ class CONTENT_EXPORT MessagePortProvider {
const base::android::JavaParamRef<jstring>& target_origin,
const base::android::JavaParamRef<jstring>& data,
const base::android::JavaParamRef<jobjectArray>& ports);
#endif
#endif // OS_ANDROID
#if defined(OS_FUCHSIA)
// If |target_origin| is unset, then no origin scoping is applied.
static void PostMessageToFrame(
WebContents* web_contents,
const base::string16& source_origin,
const base::Optional<base::string16>& target_origin,
const base::string16& data,
std::vector<mojo::ScopedMessagePipeHandle> channels);
#endif // OS_FUCHSIA
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(MessagePortProvider);
......
......@@ -42,12 +42,10 @@ jumbo_source_set("common") {
"manifest/manifest_icon_selector.cc",
"messaging/cloneable_message.cc",
"messaging/cloneable_message_struct_traits.cc",
"messaging/cloneable_message_struct_traits.h",
"messaging/message_port_channel.cc",
"messaging/string_message_codec.cc",
"messaging/transferable_message.cc",
"messaging/transferable_message_struct_traits.cc",
"messaging/transferable_message_struct_traits.h",
"mime_util/mime_util.cc",
"notifications/notification_resources.cc",
"notifications/notification_struct_traits.cc",
......
......@@ -5,8 +5,7 @@
mojom = "//third_party/blink/public/mojom/messaging/cloneable_message.mojom"
public_headers =
[ "//third_party/blink/public/common/messaging/cloneable_message.h" ]
traits_headers =
[ "//third_party/blink/common/messaging/cloneable_message_struct_traits.h" ]
traits_headers = [ "//third_party/blink/public/common/messaging/cloneable_message_struct_traits.h" ]
type_mappings =
[ "blink.mojom.CloneableMessage=::blink::CloneableMessage[move_only]" ]
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/common/messaging/cloneable_message_struct_traits.h"
#include "third_party/blink/public/common/messaging/cloneable_message_struct_traits.h"
#include "base/containers/span.h"
#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
......
......@@ -104,7 +104,7 @@ std::vector<uint8_t> EncodeStringMessage(const base::string16& data) {
return buffer;
}
bool DecodeStringMessage(const std::vector<uint8_t>& encoded_data,
bool DecodeStringMessage(base::span<const uint8_t> encoded_data,
base::string16* result) {
const uint8_t* ptr = encoded_data.data();
const uint8_t* end = ptr + encoded_data.size();
......
......@@ -7,9 +7,7 @@ public_headers = [
"//third_party/blink/public/common/messaging/transferable_message.h",
"//third_party/blink/public/common/messaging/message_port_channel.h",
]
traits_headers = [
"//third_party/blink/common/messaging/transferable_message_struct_traits.h",
]
traits_headers = [ "//third_party/blink/public/common/messaging/transferable_message_struct_traits.h" ]
deps = [
"//skia/public/interfaces",
......
......@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/common/messaging/transferable_message_struct_traits.h"
#include "third_party/blink/public/common/messaging/transferable_message_struct_traits.h"
#include "base/containers/span.h"
#include "third_party/blink/common/messaging/cloneable_message_struct_traits.h"
#include "third_party/blink/public/common/messaging/cloneable_message_struct_traits.h"
namespace mojo {
......
......@@ -62,9 +62,11 @@ source_set("headers") {
"manifest/manifest_icon_selector.h",
"manifest/web_display_mode.h",
"messaging/cloneable_message.h",
"messaging/cloneable_message_struct_traits.h",
"messaging/message_port_channel.h",
"messaging/string_message_codec.h",
"messaging/transferable_message.h",
"messaging/transferable_message_struct_traits.h",
"mime_util/mime_util.h",
"notifications/notification_resources.h",
"notifications/notification_struct_traits.h",
......
......@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_COMMON_MESSAGING_CLONEABLE_MESSAGE_STRUCT_TRAITS_H_
#define THIRD_PARTY_BLINK_COMMON_MESSAGING_CLONEABLE_MESSAGE_STRUCT_TRAITS_H_
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_MESSAGING_CLONEABLE_MESSAGE_STRUCT_TRAITS_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_MESSAGING_CLONEABLE_MESSAGE_STRUCT_TRAITS_H_
#include "mojo/public/cpp/base/big_buffer.h"
#include "mojo/public/cpp/base/unguessable_token_mojom_traits.h"
......@@ -49,4 +49,4 @@ struct BLINK_COMMON_EXPORT
} // namespace mojo
#endif // THIRD_PARTY_BLINK_COMMON_MESSAGING_CLONEABLE_MESSAGE_STRUCT_TRAITS_H_
#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_MESSAGING_CLONEABLE_MESSAGE_STRUCT_TRAITS_H_
......@@ -6,6 +6,7 @@
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_MESSAGING_STRING_MESSAGE_CODEC_H_
#include <vector>
#include "base/containers/span.h"
#include "base/strings/string16.h"
#include "third_party/blink/public/common/common_export.h"
......@@ -25,7 +26,7 @@ BLINK_COMMON_EXPORT std::vector<uint8_t> EncodeStringMessage(
const base::string16& data);
BLINK_COMMON_EXPORT bool DecodeStringMessage(
const std::vector<uint8_t>& encoded_data,
base::span<const uint8_t> encoded_data,
base::string16* result);
} // namespace blink
......
......@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_COMMON_MESSAGING_TRANSFERABLE_MESSAGE_STRUCT_TRAITS_H_
#define THIRD_PARTY_BLINK_COMMON_MESSAGING_TRANSFERABLE_MESSAGE_STRUCT_TRAITS_H_
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_MESSAGING_TRANSFERABLE_MESSAGE_STRUCT_TRAITS_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_MESSAGING_TRANSFERABLE_MESSAGE_STRUCT_TRAITS_H_
#include "skia/public/interfaces/bitmap_skbitmap_struct_traits.h"
#include "third_party/blink/common/messaging/cloneable_message_struct_traits.h"
#include "third_party/blink/public/common/messaging/cloneable_message_struct_traits.h"
#include "third_party/blink/public/common/messaging/transferable_message.h"
#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
......@@ -51,4 +51,4 @@ struct BLINK_COMMON_EXPORT
} // namespace mojo
#endif // THIRD_PARTY_BLINK_COMMON_MESSAGING_TRANSFERABLE_MESSAGE_STRUCT_TRAITS_H_
#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_MESSAGING_TRANSFERABLE_MESSAGE_STRUCT_TRAITS_H_
......@@ -134,7 +134,9 @@ component("service_lib") {
"//content/public/child",
"//content/public/common",
"//content/public/renderer",
"//mojo/public/cpp/bindings",
"//services/network/public/cpp",
"//third_party/blink/public/common",
"//ui/aura",
"//ui/display",
"//ui/ozone",
......@@ -159,6 +161,10 @@ component("service_lib") {
"browser/context_impl.h",
"browser/frame_impl.cc",
"browser/frame_impl.h",
"browser/message_port_impl.cc",
"browser/message_port_impl.h",
"browser/vmo_util.cc",
"browser/vmo_util.h",
"browser/webrunner_browser_context.cc",
"browser/webrunner_browser_context.h",
"browser/webrunner_browser_main.cc",
......
include_rules = [
"+content/public/browser",
"+content/public/test",
"+mojo/public/cpp/bindings",
"+mojo/public/cpp/system",
"+third_party/blink/public/common/associated_interfaces",
"+third_party/blink/public/common/messaging",
"+third_party/blink/public/mojom/messaging",
"+ui/aura",
"+ui/display",
"+ui/ozone/public",
......
......@@ -13,6 +13,7 @@
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/message_port_provider.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
......@@ -24,6 +25,8 @@
#include "ui/wm/core/base_focus_rules.h"
#include "url/gurl.h"
#include "webrunner/browser/context_impl.h"
#include "webrunner/browser/message_port_impl.h"
#include "webrunner/browser/vmo_util.h"
namespace webrunner {
......@@ -339,19 +342,12 @@ void FrameImpl::ExecuteJavaScript(fidl::VectorPtr<::fidl::StringPtr> origins,
return;
}
std::string script_utf8;
script_utf8.reserve(script.size);
zx_status_t status = script.vmo.read(&script_utf8.front(), 0, script.size);
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_read";
base::string16 script_utf16;
if (!base::UTF8ToUTF16(&script_utf8.front(), script.size, &script_utf16)) {
if (!ReadUTF8FromVMOAsUTF16(script, &script_utf16)) {
DLOG(WARNING) << "Ignored non-UTF8 script passed to ExecuteJavaScript().";
callback(false);
return;
}
script_utf8.clear();
script_utf8.shrink_to_fit();
std::vector<std::string> origins_strings;
for (const auto& origin : *origins)
......@@ -474,4 +470,49 @@ FrameImpl::OriginScopedScript::OriginScopedScript(
FrameImpl::OriginScopedScript::~OriginScopedScript() = default;
void FrameImpl::PostMessage(chromium::web::WebMessage message,
fidl::StringPtr target_origin,
PostMessageCallback callback) {
constexpr char kWildcardOrigin[] = "*";
if (target_origin->empty()) {
callback(false);
return;
}
base::Optional<base::string16> target_origin_utf16;
if (*target_origin != kWildcardOrigin)
target_origin_utf16 = base::UTF8ToUTF16(*target_origin);
base::string16 data_utf16;
if (!ReadUTF8FromVMOAsUTF16(message.data, &data_utf16)) {
DLOG(WARNING) << "PostMessage() rejected non-UTF8 |message.data|.";
callback(false);
return;
}
// Include outgoing MessagePorts in the message.
std::vector<mojo::ScopedMessagePipeHandle> message_ports;
if (message.outgoing_transfer) {
if (!message.outgoing_transfer->is_message_port()) {
DLOG(WARNING) << "|outgoing_transfer| is not a MessagePort.";
callback(false);
return;
}
mojo::ScopedMessagePipeHandle port = MessagePortImpl::FromFidl(
std::move(message.outgoing_transfer->message_port()));
if (!port) {
callback(false);
return;
}
message_ports.push_back(std::move(port));
}
content::MessagePortProvider::PostMessageToFrame(
web_contents_.get(), base::string16(), target_origin_utf16,
std::move(data_utf16), std::move(message_ports));
callback(true);
}
} // namespace webrunner
......@@ -79,6 +79,9 @@ class FrameImpl : public chromium::web::Frame,
fuchsia::mem::Buffer script,
chromium::web::ExecuteMode mode,
ExecuteJavaScriptCallback callback) override;
void PostMessage(chromium::web::WebMessage message,
fidl::StringPtr targetOrigin,
PostMessageCallback callback) override;
private:
FRIEND_TEST_ALL_PREFIXES(FrameImplTest, DelayedNavigationEventAck);
......
......@@ -733,4 +733,227 @@ IN_PROC_BROWSER_TEST_F(FrameImplTest, Stop) {
context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
}
fuchsia::mem::Buffer MemBufferFromString(const std::string& data) {
fuchsia::mem::Buffer buffer;
zx_status_t status =
zx::vmo::create(data.size(), ZX_VMO_NON_RESIZABLE, &buffer.vmo);
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
status = buffer.vmo.write(data.data(), 0, data.size());
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_write";
buffer.size = data.size();
return buffer;
}
// Reads a UTF-8 string from |buffer|.
std::string ReadFromBufferOrDie(const fuchsia::mem::Buffer& buffer) {
std::string output;
output.resize(buffer.size);
zx_status_t status = buffer.vmo.read(&output[0], 0, buffer.size);
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_read";
return output;
}
// Stores an asynchronously generated value for later retrieval.
template <typename T>
class AsyncValueReceiver {
public:
explicit AsyncValueReceiver(
base::RepeatingClosure on_capture = base::DoNothing())
: on_capture_(std::move(on_capture)) {}
~AsyncValueReceiver() = default;
fit::function<void(T)> GetReceiveClosure() {
return [this](T value) {
captured_ = std::move(value);
on_capture_.Run();
};
}
T& captured() { return captured_; }
private:
T captured_;
base::RepeatingClosure on_capture_;
DISALLOW_COPY_AND_ASSIGN(AsyncValueReceiver<T>);
};
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessage) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(
embedded_test_server()->GetURL("/window_post_message.html"));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(post_message_url.spec(), "postmessage", controller.get());
chromium::web::WebMessage message;
message.data = MemBufferFromString(kPage1Path);
AsyncValueReceiver<bool> post_result;
frame->PostMessage(std::move(message), post_message_url.GetOrigin().spec(),
post_result.GetReceiveClosure());
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(
testing::AllOf(Field(&NavigationDetails::title, kPage1Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
CheckRunWithTimeout(&run_loop);
EXPECT_TRUE(post_result.captured());
frame.Unbind();
}
// Send a MessagePort to the content, then perform bidirectional messaging
// through the port.
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessagePassMessagePort) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
chromium::web::MessagePortPtr message_port;
chromium::web::WebMessage msg;
{
msg.outgoing_transfer =
std::make_unique<chromium::web::OutgoingTransferable>();
msg.outgoing_transfer->set_message_port(message_port.NewRequest());
msg.data = MemBufferFromString("hi");
AsyncValueReceiver<bool> post_result;
frame->PostMessage(std::move(msg), post_message_url.GetOrigin().spec(),
post_result.GetReceiveClosure());
base::RunLoop run_loop;
AsyncValueReceiver<chromium::web::WebMessage> receiver(
run_loop.QuitClosure());
message_port->ReceiveMessage(receiver.GetReceiveClosure());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("got_port", ReadFromBufferOrDie(receiver.captured().data));
}
{
msg.data = MemBufferFromString("ping");
AsyncValueReceiver<bool> post_result;
message_port->PostMessage(std::move(msg), post_result.GetReceiveClosure());
base::RunLoop run_loop;
AsyncValueReceiver<chromium::web::WebMessage> receiver(
run_loop.QuitClosure());
message_port->ReceiveMessage(receiver.GetReceiveClosure());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("ack ping", ReadFromBufferOrDie(receiver.captured().data));
EXPECT_TRUE(post_result.captured());
}
frame.Unbind();
}
// Send a MessagePort to the content, then perform bidirectional messaging
// over its channel.
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageMessagePortDisconnected) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
chromium::web::MessagePortPtr message_port;
chromium::web::WebMessage msg;
{
msg.outgoing_transfer =
std::make_unique<chromium::web::OutgoingTransferable>();
msg.outgoing_transfer->set_message_port(message_port.NewRequest());
msg.data = MemBufferFromString("hi");
AsyncValueReceiver<bool> post_result;
frame->PostMessage(std::move(msg), post_message_url.GetOrigin().spec(),
post_result.GetReceiveClosure());
base::RunLoop run_loop;
AsyncValueReceiver<chromium::web::WebMessage> receiver(
run_loop.QuitClosure());
message_port->ReceiveMessage(receiver.GetReceiveClosure());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("got_port", ReadFromBufferOrDie(receiver.captured().data));
EXPECT_TRUE(post_result.captured());
}
// Navigating off-page should tear down the Mojo channel, thereby causing the
// MessagePortImpl to self-destruct and tear down its FIDL channel.
{
base::RunLoop run_loop;
message_port.set_error_handler([&run_loop]() { run_loop.Quit(); });
controller->LoadUrl(url::kAboutBlankURL, nullptr);
CheckRunWithTimeout(&run_loop);
}
frame.Unbind();
}
// Send a MessagePort to the content, and through that channel, receive a
// different MessagePort that was created by the content. Verify the second
// channel's liveness by sending a ping to it.
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageUseContentProvidedPort) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
chromium::web::MessagePortPtr incoming_message_port;
chromium::web::WebMessage msg;
{
chromium::web::MessagePortPtr message_port;
msg.outgoing_transfer =
std::make_unique<chromium::web::OutgoingTransferable>();
msg.outgoing_transfer->set_message_port(message_port.NewRequest());
msg.data = MemBufferFromString("hi");
AsyncValueReceiver<bool> post_result;
frame->PostMessage(std::move(msg), "*", post_result.GetReceiveClosure());
base::RunLoop run_loop;
AsyncValueReceiver<chromium::web::WebMessage> receiver(
run_loop.QuitClosure());
message_port->ReceiveMessage(receiver.GetReceiveClosure());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("got_port", ReadFromBufferOrDie(receiver.captured().data));
incoming_message_port =
receiver.captured().incoming_transfer->message_port().Bind();
EXPECT_TRUE(post_result.captured());
}
{
AsyncValueReceiver<bool> post_result;
msg.data = MemBufferFromString("ping");
incoming_message_port->PostMessage(std::move(msg),
post_result.GetReceiveClosure());
base::RunLoop run_loop;
AsyncValueReceiver<chromium::web::WebMessage> receiver(
run_loop.QuitClosure());
incoming_message_port->ReceiveMessage(receiver.GetReceiveClosure());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("ack ping", ReadFromBufferOrDie(receiver.captured().data));
EXPECT_TRUE(post_result.captured());
}
frame.Unbind();
}
// TODO BEFORE SUBMITTING(kmarshall): bad origin tests, multiple buffered
// messages, perhaps a proof-of-concept test with injected bindings *and*
// message i/o.
} // namespace webrunner
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webrunner/browser/message_port_impl.h"
#include <stdint.h>
#include <lib/fit/function.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/macros.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "third_party/blink/public/common/messaging/message_port_channel.h"
#include "third_party/blink/public/common/messaging/string_message_codec.h"
#include "third_party/blink/public/common/messaging/transferable_message_struct_traits.h"
#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
#include "webrunner/browser/vmo_util.h"
#include "webrunner/fidl/chromium/web/cpp/fidl.h"
namespace webrunner {
namespace {
// Converts a message posted to a JS MessagePort to a WebMessage.
// Returns an unset Optional<> if the message could not be converted.
base::Optional<chromium::web::WebMessage> FromMojoMessage(
mojo::Message message) {
chromium::web::WebMessage converted;
blink::TransferableMessage transferable_message;
if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
std::move(message), &transferable_message))
return {};
if (!transferable_message.ports.empty()) {
if (transferable_message.ports.size() != 1u) {
// TODO(crbug.com/893236): support >1 transferable when fidlc cycle
// detection is fixed (FIDL-354).
LOG(ERROR) << "FIXME: Request to transfer >1 MessagePort was ignored.";
return {};
}
converted.incoming_transfer =
std::make_unique<chromium::web::IncomingTransferable>();
converted.incoming_transfer->set_message_port(MessagePortImpl::FromMojo(
transferable_message.ports[0].ReleaseHandle()));
}
base::string16 data_utf16;
if (!blink::DecodeStringMessage(transferable_message.encoded_message,
&data_utf16)) {
return {};
}
std::string data_utf8;
if (!base::UTF16ToUTF8(data_utf16.data(), data_utf16.size(), &data_utf8))
return {};
base::STLClearObject(&data_utf16);
converted.data.size = data_utf8.size();
zx_status_t status = zx::vmo::create(data_utf8.size(), ZX_VMO_NON_RESIZABLE,
&converted.data.vmo);
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_vmo_create";
return {};
}
status = converted.data.vmo.write(data_utf8.data(), 0, data_utf8.length());
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_vmo_write";
return {};
}
return converted;
}
// Converts a FIDL chromium::web::WebMessage into a MessagePipe message, for
// sending over Mojo.
//
// Returns a null mojo::Message if |message| was invalid.
mojo::Message FromFidlMessage(chromium::web::WebMessage message) {
base::string16 data_utf16;
if (!ReadUTF8FromVMOAsUTF16(message.data, &data_utf16))
return mojo::Message();
// TODO(crbug.com/893236): support >1 transferable when fidlc cycle detection
// is fixed (FIDL-354).
blink::TransferableMessage transfer_message;
if (message.outgoing_transfer) {
transfer_message.ports.emplace_back(MessagePortImpl::FromFidl(
std::move(message.outgoing_transfer->message_port())));
}
transfer_message.owned_encoded_message =
blink::EncodeStringMessage(data_utf16);
transfer_message.encoded_message = transfer_message.owned_encoded_message;
return blink::mojom::TransferableMessage::SerializeAsMessage(
&transfer_message);
}
} // namespace
MessagePortImpl::MessagePortImpl(mojo::ScopedMessagePipeHandle mojo_port)
: binding_(this) {
connector_ = std::make_unique<mojo::Connector>(
std::move(mojo_port), mojo::Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
connector_->set_incoming_receiver(this);
connector_->set_connection_error_handler(
base::BindOnce(&MessagePortImpl::OnDisconnected, base::Unretained(this)));
binding_.set_error_handler([this](zx_status_t status) {
if (status != ZX_OK)
ZX_DLOG(INFO, status) << "Disconnected";
OnDisconnected();
});
}
MessagePortImpl::~MessagePortImpl() {}
void MessagePortImpl::OnDisconnected() {
// |connector_| and |binding_| are implicitly unbound.
delete this;
}
void MessagePortImpl::PostMessage(chromium::web::WebMessage message,
PostMessageCallback callback) {
mojo::Message mojo_message = FromFidlMessage(std::move(message));
if (mojo_message.IsNull()) {
callback(false);
delete this;
return;
}
CHECK(connector_->Accept(&mojo_message));
}
void MessagePortImpl::ReceiveMessage(ReceiveMessageCallback callback) {
pending_client_read_cb_ = std::move(callback);
MaybeDeliverToClient();
}
void MessagePortImpl::MaybeDeliverToClient() {
// Do nothing if the client hasn't requested a read, or if there's nothing to
// read.
if (!pending_client_read_cb_ || message_queue_.empty())
return;
std::move(pending_client_read_cb_)(std::move(message_queue_.front()));
message_queue_.pop_front();
}
bool MessagePortImpl::Accept(mojo::Message* message) {
base::Optional<chromium::web::WebMessage> message_converted =
FromMojoMessage(std::move(*message));
if (!message_converted) {
DLOG(ERROR) << "Couldn't decode MessageChannel from Mojo pipe.";
return false;
}
message_queue_.push_back(std::move(message_converted.value()));
MaybeDeliverToClient();
return true;
}
// static
mojo::ScopedMessagePipeHandle MessagePortImpl::FromFidl(
fidl::InterfaceRequest<chromium::web::MessagePort> port) {
mojo::ScopedMessagePipeHandle client_port;
mojo::ScopedMessagePipeHandle content_port;
mojo::CreateMessagePipe(0, &content_port, &client_port);
MessagePortImpl* port_impl = new MessagePortImpl(std::move(client_port));
port_impl->binding_.Bind(std::move(port));
return content_port;
}
// static
fidl::InterfaceHandle<chromium::web::MessagePort> MessagePortImpl::FromMojo(
mojo::ScopedMessagePipeHandle port) {
MessagePortImpl* created_port = new MessagePortImpl(std::move(port));
return created_port->binding_.NewBinding();
}
} // namespace webrunner
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef WEBRUNNER_BROWSER_MESSAGE_PORT_IMPL_H_
#define WEBRUNNER_BROWSER_MESSAGE_PORT_IMPL_H_
#include <lib/fidl/cpp/binding.h>
#include <deque>
#include <memory>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "mojo/public/cpp/bindings/connector.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "webrunner/fidl/chromium/web/cpp/fidl.h"
namespace webrunner {
// Defines the implementation of a MessagePort which routes messages from
// FIDL clients to web content, or vice versa. Every MessagePortImpl has a FIDL
// port and a Mojo port.
//
// MessagePortImpl instances are self-managed; they destroy themselves when
// the connection is terminated from either the Mojo or FIDL side.
class MessagePortImpl : public chromium::web::MessagePort,
public mojo::MessageReceiver {
public:
// Creates a connected MessagePort from a FIDL MessagePort request and
// returns a handle to its peer Mojo pipe.
static mojo::ScopedMessagePipeHandle FromFidl(
fidl::InterfaceRequest<chromium::web::MessagePort> client_fidl_port);
// Creates a connected MessagePort from a transferred Mojo MessagePort and
// returns a handle to its FIDL interface peer.
static fidl::InterfaceHandle<chromium::web::MessagePort> FromMojo(
mojo::ScopedMessagePipeHandle port);
protected:
friend class base::DeleteHelper<MessagePortImpl>;
explicit MessagePortImpl(mojo::ScopedMessagePipeHandle mojo_port);
// Non-public to ensure that only this object may destroy itself.
~MessagePortImpl() override;
fidl::Binding<chromium::web::MessagePort> binding_;
private:
// chromium::web::MessagePortImpl implementation.
void PostMessage(chromium::web::WebMessage message,
PostMessageCallback callback) override;
void ReceiveMessage(ReceiveMessageCallback callback) override;
// Called when the connection to Blink or FIDL is terminated.
void OnDisconnected();
// Sends the next message enqueued in |message_queue_| to the client,
// if the client has requested a message.
void MaybeDeliverToClient();
// mojo::MessageReceiver implementation.
bool Accept(mojo::Message* message) override;
std::deque<chromium::web::WebMessage> message_queue_;
ReceiveMessageCallback pending_client_read_cb_;
std::unique_ptr<mojo::Connector> connector_;
DISALLOW_COPY_AND_ASSIGN(MessagePortImpl);
};
} // namespace webrunner
#endif // WEBRUNNER_BROWSER_MESSAGE_PORT_IMPL_H_
<html>
<head><title>messageport</title></head>
<body>
<script>
// Use onMessage handler with a null |sourcePort|.
window.addEventListener('message', onMessage.bind(null, null));
// Function for exercising bidirectional communication between JS content and
// For messages received on the window:
// * acknowledge receipt of MessagePort by sending "got_port <msg>" and
// including a MessagePort created in JS.
// For messages received on a MessagePort provided by the client:
// * acknowledge the message sent by the client by sending "ack <msg>" on
// the port used to received the message.
// * if a message port is provided, acknowledge receipt of MessagePort by
// sending "got_port <msg>" on that port, and include a MessagePort
// created in JS.
function onMessage(sourcePort, event) {
// Ack all messages received on message ports.
if (sourcePort)
sourcePort.postMessage('ack ' + event.data);
// If the peer provided a MessagePort, acknowledge that we got it by
// sending "got_port" on it, along with a MessagePort that we create on the
// JS side.
if (event.ports && event.ports.length > 0) {
var chan = new MessageChannel();
chan.port1.onmessage = onMessage.bind(null, chan.port1);
event.ports[0].onmessage = onMessage.bind(null, event.ports[0]);
event.ports[0].postMessage('got_port', [chan.port2]);
}
}
</script>
</body>
</html>
<html>
<head><title>postmessage</title></head>
<body>
<script>
window.addEventListener('message', function(event) {
window.location.href = event.data
});
</script>
</body>
</html>
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webrunner/browser/vmo_util.h"
#include <string>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/stl_util.h"
namespace webrunner {
bool ReadUTF8FromVMOAsUTF16(const fuchsia::mem::Buffer& buffer,
base::string16* output) {
std::string vmo_data;
vmo_data.resize(buffer.size);
zx_status_t status =
buffer.vmo.read(base::data(vmo_data), 0, vmo_data.size());
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_vmo_read";
return false;
}
return base::UTF8ToUTF16(&vmo_data.front(), vmo_data.size(), output);
}
} // namespace webrunner
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef WEBRUNNER_BROWSER_VMO_UTIL_H_
#define WEBRUNNER_BROWSER_VMO_UTIL_H_
#include <fuchsia/mem/cpp/fidl.h>
#include <lib/zx/channel.h>
#include "base/strings/utf_string_conversions.h"
namespace webrunner {
// Reads the contents of |buffer|, encoded in UTF-8, to a UTF-16 string.
// Returns |false| if |buffer| is not valid UTF-8.
bool ReadUTF8FromVMOAsUTF16(const fuchsia::mem::Buffer& buffer,
base::string16* output);
} // namespace webrunner
#endif // WEBRUNNER_BROWSER_VMO_UTIL_H_
......@@ -69,6 +69,17 @@ interface Frame {
vector<string> origins, fuchsia.mem.Buffer script, ExecuteMode mode) ->
(bool success);
// Posts a message to the frame's onMessage handler.
//
// |targetOrigin| restricts message delivery to the specified origin.
// If |targetOrigin| is "*", then the message will be sent to the document
// regardless of its origin.
// See html.spec.whatwg.org/multipage/web-messaging.html sect. 9.4.3
// for more details on how the target origin policy is applied.
//
// Returns |false| if |message| is invalid or |targetOrigin| is missing.
5: PostMessage(WebMessage message, string targetOrigin) -> (bool success);
// Sets the observer for handling page navigation events.
//
// |observer|: The observer to use. Unregisters any existing observers
......@@ -80,3 +91,40 @@ interface Frame {
// and console.error().
101: SetJavaScriptLogLevel(LogLevel level);
};
struct WebMessage {
// The message payload, encoded as an UTF-8 string.
fuchsia.mem.Buffer data;
// List of objects transferred into the MessagePort from the FIDL client.
// TODO(crbug.com/893236): make this a vector when FIDL-354 is fixed.
IncomingTransferable? incoming_transfer;
// List of objects transferred out of the MessagePort to the FIDL client.
OutgoingTransferable? outgoing_transfer;
};
union OutgoingTransferable {
request<MessagePort> message_port;
};
union IncomingTransferable {
MessagePort message_port;
};
// Represents one end of an HTML5 MessageChannel. Can be used to send
// and exchange Messages with the peered MessagePort in the Frame's script
// context. The port is destroyed when either end of the MessagePort channel
// is torn down.
interface MessagePort {
// Sends a WebMessage to the peer.
1: PostMessage(WebMessage message) -> (bool success);
// Asynchronously reads the next message from the channel.
// The client should invoke the callback when it is ready to process
// another message.
// Unreceived messages are buffered on the sender's side and bounded
// by its available resources.
2: ReceiveMessage() -> (WebMessage message);
};
......@@ -9,6 +9,7 @@
#include <zircon/processargs.h>
#include <functional>
#include <string>
#include <utility>
#include <vector>
......@@ -29,6 +30,7 @@
#include "base/test/multiprocess_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
#include "webrunner/fidl/chromium/web/cpp/fidl_test_base.h"
#include "webrunner/service/common.h"
namespace webrunner {
......@@ -40,7 +42,7 @@ constexpr char kUrl[] = "chrome://:emorhc";
constexpr char kTitle[] = "Palindrome";
// A fake Frame implementation that manages its own lifetime.
class FakeFrame : public chromium::web::Frame {
class FakeFrame : public chromium::web::testing::Frame_TestBase {
public:
explicit FakeFrame(fidl::InterfaceRequest<chromium::web::Frame> request)
: binding_(this, std::move(request)) {
......@@ -63,28 +65,11 @@ class FakeFrame : public chromium::web::Frame {
std::move(on_set_observer_callback_).Run();
}
void CreateView(
fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> view_owner,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> services) override {
// chromium::web::testing::Frame_TestBase implementation.
void NotImplemented_(const std::string& name) override {
NOTREACHED() << name;
}
void CreateView2(
zx::eventpair view_token,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services)
override {}
void GetNavigationController(
fidl::InterfaceRequest<chromium::web::NavigationController> controller)
override {}
void ExecuteJavaScript(fidl::VectorPtr<::fidl::StringPtr> origins,
fuchsia::mem::Buffer script,
chromium::web::ExecuteMode mode,
ExecuteJavaScriptCallback callback) override {}
void SetJavaScriptLogLevel(chromium::web::LogLevel) override {}
private:
fidl::Binding<chromium::web::Frame> binding_;
chromium::web::NavigationEventObserverPtr observer_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment