Commit cc73bd13 authored by Kevin Marshall's avatar Kevin Marshall Committed by Commit Bot

[Fuchsia] CastRunner cleanup, incl. CastChannel & QueryableData removal

* Removes CastChannel implementation (it's now provided by Agent).
* Removes QueryableData implementation.
* Removes CastChannel from CastRunner integration tests.
* Add ApiBindings e2e test to CastRunner integration tests.
* Cleans up "metrics" from not implemented stubs.
* Cleans up some transitional code and makes the ApiBindings connection
  mandatory.


Bug: 953958
Change-Id: I28a56131f896c46697369711d3c5e7d0aa72efd4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1656110
Commit-Queue: Kevin Marshall <kmarshall@chromium.org>
Reviewed-by: default avatarFabrice de Gans-Riberi <fdegans@chromium.org>
Cr-Commit-Position: refs/heads/master@{#672588}
parent 3b4706e9
......@@ -13,7 +13,6 @@ fidl_library("cast_fidl") {
sources = [
"fidl/cast/api_bindings.fidl",
"fidl/cast/application_config.fidl",
"fidl/cast/cast_channel.fidl",
"fidl/cast/queryable_data.fidl",
]
......@@ -103,7 +102,6 @@ group("gn_all") {
"http:http_service_tests",
"runners:cast_runner",
"runners:cast_runner_browsertests",
"runners:cast_runner_integration_tests",
"runners:web_runner",
"//chromecast/bindings:bindings_manager_fuchsia",
]
......
// Copyright 2019 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.
library chromium.cast;
using fuchsia.web;
[Discoverable]
protocol CastChannel {
/// Receives an opened Cast |channel| from the Cast application.
/// Open() must not be called again until the preceding call has returned.
Open(fuchsia.web.MessagePort channel) -> ();
};
......@@ -34,8 +34,6 @@ source_set("cast_runner_core") {
sources = [
"cast/api_bindings_client.cc",
"cast/api_bindings_client.h",
"cast/cast_channel_bindings.cc",
"cast/cast_channel_bindings.h",
"cast/cast_component.cc",
"cast/cast_component.h",
"cast/cast_platform_bindings_ids.h",
......@@ -43,15 +41,11 @@ source_set("cast_runner_core") {
"cast/cast_runner.h",
"cast/named_message_port_connector.cc",
"cast/named_message_port_connector.h",
"cast/queryable_data_bindings.cc",
"cast/queryable_data_bindings.h",
"cast/touch_input_bindings.cc",
"cast/touch_input_bindings.h",
]
data = [
"cast/cast_channel_bindings.js",
"cast/not_implemented_api_bindings.js",
"cast/queryable_data_bindings.js",
"cast/touch_input_bindings.js",
]
data_deps = [
......@@ -105,8 +99,6 @@ source_set("cast_runner_test_core") {
sources = [
"cast/fake_application_config_manager.cc",
"cast/fake_application_config_manager.h",
"cast/fake_queryable_data.cc",
"cast/fake_queryable_data.h",
"cast/test_api_bindings.cc",
"cast/test_api_bindings.h",
]
......@@ -147,10 +139,8 @@ test("cast_runner_integration_tests") {
test("cast_runner_browsertests") {
sources = [
"cast/api_bindings_client_browsertest.cc",
"cast/cast_channel_bindings_browsertest.cc",
"cast/named_message_port_connector_browsertest.cc",
"cast/not_implemented_api_bindings_browsertest.cc",
"cast/queryable_data_bindings_browsertest.cc",
]
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
data = [
......
......@@ -27,21 +27,10 @@ ApiBindingsClient::ApiBindingsClient(
bindings_service_->GetAll(
fit::bind_member(this, &ApiBindingsClient::OnBindingsReceived));
bindings_service_.set_error_handler([this](zx_status_t status) mutable {
ZX_LOG_IF(ERROR, status != ZX_OK, status)
<< "ApiBindings disconnected before bindings were read.";
if (!bindings_) {
// The Agent disconnected before sending a bindings response,
// so it's possible that the Agent doesn't yet implement the ApiBindings
// service. Populate the bindings with an empty set so initialization may
// continue.
// TODO(crbug.com/953958): Remove this fallback once the Agent implements
// ApiBindings.
LOG(WARNING)
<< "Couldn't receive bindings from Agent, proceeding anyway.";
OnBindingsReceived({});
}
bindings_service_.set_error_handler(
[](zx_status_t status) mutable {
ZX_LOG(FATAL, status)
<< "ApiBindings service disconnected before entries were returned.";
});
}
......
// Copyright 2019 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 "fuchsia/runners/cast/cast_channel_bindings.h"
#include <lib/fit/function.h>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/threading/thread_task_runner_handle.h"
#include "fuchsia/base/mem_buffer_util.h"
#include "fuchsia/runners/cast/cast_platform_bindings_ids.h"
#include "fuchsia/runners/cast/named_message_port_connector.h"
// Unique identifier of the Cast Channel message port, used by the JavaScript
// API to connect to the port.
const char kMessagePortName[] = "cast.__platform__.channel";
CastChannelBindings::CastChannelBindings(
fuchsia::web::Frame* frame,
NamedMessagePortConnector* connector,
chromium::cast::CastChannelPtr channel_consumer)
: frame_(frame),
connector_(connector),
channel_consumer_(std::move(channel_consumer)) {
DCHECK(connector_);
DCHECK(frame_);
channel_consumer_.set_error_handler([](zx_status_t status) mutable {
ZX_LOG(ERROR, status) << "Cast Channel FIDL client disconnected";
});
connector->Register(
kMessagePortName,
base::BindRepeating(&CastChannelBindings::OnMasterPortReceived,
base::Unretained(this)));
base::FilePath assets_path;
CHECK(base::PathService::Get(base::DIR_ASSETS, &assets_path));
frame_->AddBeforeLoadJavaScript(
static_cast<uint64_t>(CastPlatformBindingsId::CAST_CHANNEL), {"*"},
cr_fuchsia::MemBufferFromFile(
base::File(assets_path.AppendASCII(
"fuchsia/runners/cast/cast_channel_bindings.js"),
base::File::FLAG_OPEN | base::File::FLAG_READ)),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
CHECK(result.is_response()) << "JavaScript injection error.";
});
}
CastChannelBindings::~CastChannelBindings() {
connector_->Unregister(kMessagePortName);
}
void CastChannelBindings::OnMasterPortError() {
master_port_.Unbind();
}
void CastChannelBindings::OnMasterPortReceived(
fidl::InterfaceHandle<fuchsia::web::MessagePort> port) {
DCHECK(port);
master_port_ = port.Bind();
master_port_.set_error_handler([this](zx_status_t status) {
ZX_LOG_IF(WARNING, status != ZX_ERR_PEER_CLOSED, status)
<< "Cast Channel master port disconnected.";
OnMasterPortError();
});
master_port_->ReceiveMessage(fit::bind_member(
this, &CastChannelBindings::OnCastChannelMessageReceived));
}
void CastChannelBindings::OnCastChannelMessageReceived(
fuchsia::web::WebMessage message) {
if (!message.has_incoming_transfer() ||
!(message.incoming_transfer().size() == 1) ||
!message.incoming_transfer().at(0).is_message_port()) {
LOG(WARNING) << "Received a CastChannel without a message port.";
OnMasterPortError();
return;
}
SendChannelToConsumer(
message.mutable_incoming_transfer()->at(0).message_port().Bind());
master_port_->ReceiveMessage(fit::bind_member(
this, &CastChannelBindings::OnCastChannelMessageReceived));
}
void CastChannelBindings::SendChannelToConsumer(
fuchsia::web::MessagePortPtr channel) {
if (consumer_ready_for_port_) {
consumer_ready_for_port_ = false;
channel_consumer_->Open(
std::move(channel),
fit::bind_member(this, &CastChannelBindings::OnConsumerReadyForPort));
} else {
connected_channel_queue_.push_front(std::move(channel));
}
}
void CastChannelBindings::OnConsumerReadyForPort() {
DCHECK(!consumer_ready_for_port_);
consumer_ready_for_port_ = true;
if (!connected_channel_queue_.empty()) {
// Deliver the next enqueued channel.
fuchsia::web::MessagePortPtr next_port =
std::move(connected_channel_queue_.back());
SendChannelToConsumer(std::move(next_port));
connected_channel_queue_.pop_back();
}
}
// Copyright 2019 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 FUCHSIA_RUNNERS_CAST_CAST_CHANNEL_BINDINGS_H_
#define FUCHSIA_RUNNERS_CAST_CAST_CHANNEL_BINDINGS_H_
#include <string>
#include "base/callback.h"
#include "base/containers/circular_deque.h"
#include "base/macros.h"
#include "base/strings/string_piece.h"
#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
#include "fuchsia/runners/cast/named_message_port_connector.h"
// Handles the injection of cast.__platform__.channel bindings into pages'
// scripting context, and establishes a bidirectional message pipe over
// which the two communicate.
class CastChannelBindings {
public:
// Attaches CastChannel bindings and port to a |frame|.
// |frame|: The frame to be provided with a CastChannel.
// |connector|: The NamedMessagePortConnector to use for establishing
// transport.
// |channel_consumer|: A FIDL service which receives opened Cast Channels.
// Both |frame| and |connector| must outlive |this|.
CastChannelBindings(fuchsia::web::Frame* frame,
NamedMessagePortConnector* connector,
chromium::cast::CastChannelPtr channel_consumer);
~CastChannelBindings();
private:
// Receives a port used for receiving new Cast Channel ports.
void OnMasterPortReceived(
fidl::InterfaceHandle<fuchsia::web::MessagePort> port);
// Receives a message containing a newly opened Cast Channel from
// |master_port_|.
void OnCastChannelMessageReceived(fuchsia::web::WebMessage message);
// Indicates that |channel_consumer_| is ready to take another port.
void OnConsumerReadyForPort();
// Sends or enqueues a Cast Channel for sending to |channel_consumer_|.
void SendChannelToConsumer(fuchsia::web::MessagePortPtr channel);
// Handles error conditions on |master_port_|.
void OnMasterPortError();
fuchsia::web::Frame* const frame_;
NamedMessagePortConnector* const connector_;
// A queue of channels waiting to be sent the Cast Channel FIDL service.
base::circular_deque<fuchsia::web::MessagePortPtr> connected_channel_queue_;
// A long-lived port, used to receive new Cast Channel ports when they are
// opened. Should be automatically populated by the
// NamedMessagePortConnector whenever |frame| loads a new page.
fuchsia::web::MessagePortPtr master_port_;
fuchsia::mem::Buffer bindings_script_;
// The service which will receive connected Cast Channels.
chromium::cast::CastChannelPtr channel_consumer_;
// If set, indicates that |channel_consumer_| is ready to accept another Cast
// Channel.
bool consumer_ready_for_port_ = true;
DISALLOW_COPY_AND_ASSIGN(CastChannelBindings);
};
#endif // FUCHSIA_RUNNERS_CAST_CAST_CHANNEL_BINDINGS_H_
// Copyright 2019 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.
'use strict';
// Implementation of the cast.__platform__.channel API which uses MessagePort
// IPC to communicate with an actual Cast Channel implementation provided by
// the content embedder. There is at most one channel which may be opened (able
// to send & receive messages) or closed.
// Don't clobber the Cast Channel API if it was previously injected.
if (!cast.__platform__.channel) {
cast.__platform__.channel = new class {
constructor() {
this.master_port_ = cast.__platform__.PortConnector.bind(
'cast.__platform__.channel');
}
// Signals to the peer that the Cast Channel is opened.
// |openHandler|: A callback function which is invoked on channel open with
// a boolean indicating success.
// |messageHandler|: Invoked when a message arrives from the peer.
open(openHandler, messageHandler) {
if (this.current_port_) {
console.error('open() called on an open Cast Channel.');
openHandler(false);
return;
}
if (!messageHandler) {
console.error('Null messageHandler passed to open().');
openHandler(false);
return;
}
// Create the MessageChannel for Cast Channel and distribute its ports.
var channel = new MessageChannel();
this.master_port_.postMessage('', [channel.port1]);
this.current_port_ = channel.port2;
this.current_port_.onmessage = function(message) {
messageHandler(message.data);
};
this.current_port_.onerror = function() {
console.error('Cast Channel was closed unexpectedly by peer.');
return;
};
openHandler(true);
}
// Closes the Cast Channel.
close(closeHandler) {
if (this.current_port_) {
this.current_port_.close();
this.current_port_ = null;
}
}
// Sends a message to the Cast Channel's peer.
send(message) {
if (!this.current_port_) {
console.error('send() called on a closed Cast Channel.');
return;
}
this.current_port_.postMessage(message);
}
// Used to send newly opened Cast Channel ports to C++.
master_port_ = null;
// The current opened Cast Channel.
current_port_ = null;
};
}
// 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 <lib/fidl/cpp/binding.h>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/test/bind_test_util.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_restrictions.h"
#include "fuchsia/base/fit_adapter.h"
#include "fuchsia/base/mem_buffer_util.h"
#include "fuchsia/base/result_receiver.h"
#include "fuchsia/base/test_navigation_listener.h"
#include "fuchsia/engine/test/web_engine_browser_test.h"
#include "fuchsia/runners/cast/cast_channel_bindings.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/url_constants.h"
namespace {
class CastChannelBindingsTest : public cr_fuchsia::WebEngineBrowserTest,
public chromium::cast::CastChannel {
public:
CastChannelBindingsTest()
: receiver_binding_(this),
run_timeout_(TestTimeouts::action_timeout(),
base::MakeExpectedNotRunClosure(FROM_HERE)) {
set_test_server_root(base::FilePath("fuchsia/runners/cast/testdata"));
}
~CastChannelBindingsTest() override = default;
protected:
void SetUpOnMainThread() override {
cr_fuchsia::WebEngineBrowserTest::SetUpOnMainThread();
base::ScopedAllowBlockingForTesting allow_blocking;
frame_ = WebEngineBrowserTest::CreateFrame(&navigation_listener_);
connector_ = std::make_unique<NamedMessagePortConnector>(frame_.get());
}
void Open(fidl::InterfaceHandle<fuchsia::web::MessagePort> channel,
OpenCallback receive_next_channel_cb) override {
connected_channel_ = channel.Bind();
receive_next_channel_cb_ = std::move(receive_next_channel_cb);
if (on_channel_connected_cb_)
std::move(on_channel_connected_cb_).Run();
}
void WaitUntilCastChannelOpened() {
if (!connected_channel_) {
base::RunLoop run_loop;
on_channel_connected_cb_ = run_loop.QuitClosure();
run_loop.Run();
}
receive_next_channel_cb_();
}
void WaitUntilCastChannelClosed() {
if (!connected_channel_)
return;
base::RunLoop run_loop;
connected_channel_.set_error_handler(
[quit_closure = run_loop.QuitClosure()](zx_status_t) {
quit_closure.Run();
});
run_loop.Run();
}
std::string ReadStringFromChannel() {
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> message(
run_loop.QuitClosure());
connected_channel_->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(message.GetReceiveCallback()));
run_loop.Run();
std::string data;
CHECK(cr_fuchsia::StringFromMemBuffer(message->data(), &data));
return data;
}
void CheckLoadUrl(const GURL& url,
fuchsia::web::NavigationController* controller) {
navigate_run_loop_ = std::make_unique<base::RunLoop>();
cr_fuchsia::ResultReceiver<
fuchsia::web::NavigationController_LoadUrl_Result>
result;
controller->LoadUrl(
url.spec(), fuchsia::web::LoadUrlParams(),
cr_fuchsia::CallbackToFitFunction(result.GetReceiveCallback()));
navigation_listener_.RunUntilUrlEquals(url);
EXPECT_TRUE(result->is_response());
}
std::unique_ptr<base::RunLoop> navigate_run_loop_;
fuchsia::web::FramePtr frame_;
std::unique_ptr<NamedMessagePortConnector> connector_;
fidl::Binding<chromium::cast::CastChannel> receiver_binding_;
cr_fuchsia::TestNavigationListener navigation_listener_;
// The connected Cast Channel.
fuchsia::web::MessagePortPtr connected_channel_;
// A pending on-connect callback, to be invoked once a Cast Channel is
// received.
base::OnceClosure on_channel_connected_cb_;
private:
const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
OpenCallback receive_next_channel_cb_;
DISALLOW_COPY_AND_ASSIGN(CastChannelBindingsTest);
};
IN_PROC_BROWSER_TEST_F(CastChannelBindingsTest, CastChannelBufferedInput) {
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url(embedded_test_server()->GetURL("/cast_channel.html"));
frame_->SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel::INFO);
fuchsia::web::NavigationControllerPtr controller;
frame_->GetNavigationController(controller.NewRequest());
CastChannelBindings cast_channel_instance(
frame_.get(), connector_.get(), receiver_binding_.NewBinding().Bind());
// Verify that CastChannelBindings can properly handle message, connect,
// disconnect, and MessagePort disconnection events.
CheckLoadUrl(test_url, controller.get());
connector_->OnPageLoad();
WaitUntilCastChannelOpened();
auto expected_list = {"this", "is", "a", "test"};
for (const std::string& expected : expected_list) {
EXPECT_EQ(ReadStringFromChannel(), expected);
}
}
IN_PROC_BROWSER_TEST_F(CastChannelBindingsTest, CastChannelReconnect) {
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url(embedded_test_server()->GetURL("/cast_channel_reconnect.html"));
GURL empty_url(embedded_test_server()->GetURL("/defaultresponse"));
frame_->SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel::INFO);
fuchsia::web::NavigationControllerPtr controller;
frame_->GetNavigationController(controller.NewRequest());
CastChannelBindings cast_channel_instance(
frame_.get(), connector_.get(), receiver_binding_.NewBinding().Bind());
// Verify that CastChannelBindings can properly handle message, connect,
// disconnect, and MessagePort disconnection events.
// Also verify that the cast channel is used across inter-page navigations.
for (int i = 0; i < 5; ++i) {
CheckLoadUrl(test_url, controller.get());
connector_->OnPageLoad();
WaitUntilCastChannelOpened();
WaitUntilCastChannelClosed();
WaitUntilCastChannelOpened();
EXPECT_EQ("reconnected", ReadStringFromChannel());
// Send a message to the channel.
{
fuchsia::web::WebMessage message;
message.set_data(cr_fuchsia::MemBufferFromString("hello"));
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::MessagePort_PostMessage_Result>
post_result(run_loop.QuitClosure());
connected_channel_->PostMessage(
std::move(message),
cr_fuchsia::CallbackToFitFunction(post_result.GetReceiveCallback()));
run_loop.Run();
EXPECT_FALSE(post_result->is_err());
}
EXPECT_EQ("ack hello", ReadStringFromChannel());
// Navigate away.
CheckLoadUrl(empty_url, controller.get());
}
}
} // namespace
......@@ -100,15 +100,6 @@ void CastComponent::InitializeCastPlatformBindings() {
CHECK(result.is_response()) << "Couldn't inject stub bindings.";
});
cast_channel_ = std::make_unique<CastChannelBindings>(
frame(), &connector_,
agent_manager_->ConnectToAgentService<chromium::cast::CastChannel>(
CastRunner::kAgentComponentUrl));
queryable_data_ = std::make_unique<QueryableDataBindings>(
frame(),
agent_manager_->ConnectToAgentService<chromium::cast::QueryableData>(
CastRunner::kAgentComponentUrl));
touch_input_ = std::make_unique<TouchInputBindings>(touch_input_policy_,
frame(), &connector_);
}
......@@ -11,9 +11,7 @@
#include "base/fuchsia/service_directory.h"
#include "fuchsia/base/agent_manager.h"
#include "fuchsia/runners/cast/api_bindings_client.h"
#include "fuchsia/runners/cast/cast_channel_bindings.h"
#include "fuchsia/runners/cast/named_message_port_connector.h"
#include "fuchsia/runners/cast/queryable_data_bindings.h"
#include "fuchsia/runners/cast/touch_input_bindings.h"
#include "fuchsia/runners/common/web_component.h"
......@@ -52,8 +50,6 @@ class CastComponent : public WebComponent,
bool constructor_active_ = false;
TouchInputPolicy touch_input_policy_;
NamedMessagePortConnector connector_;
std::unique_ptr<CastChannelBindings> cast_channel_;
std::unique_ptr<QueryableDataBindings> queryable_data_;
std::unique_ptr<TouchInputBindings> touch_input_;
std::unique_ptr<ApiBindingsClient> api_bindings_client_;
......
......@@ -154,30 +154,6 @@ if (!cast.__platform__._notImplemented) {
}
if (!cast.__platform__.metrics) {
cast.__platform__.metrics = {};
cast.__platform__.metrics.logBoolToUma =
cast.__platform__._notImplemented(
'metrics.logBoolToUma');
cast.__platform__.metrics.logIntToUma =
cast.__platform__._notImplemented(
'metrics.logIntToUma');
cast.__platform__.metrics.logEventToUma =
cast.__platform__._notImplemented(
'metrics.logEventToUma');
cast.__platform__.metrics.logHistogramValueToUma =
cast.__platform__._notImplemented(
'metrics.logHistogramValueToUma');
cast.__platform__.metrics.setMplVersion =
cast.__platform__._notImplemented('metrics.setMplVersion');
}
if (!cast.__platform__.accessibility) {
cast.__platform__.accessibility = {};
......
......@@ -153,12 +153,6 @@ IN_PROC_BROWSER_TEST_F(StubBindingsTest, ApiCoverage) {
{"display.updateOutputMode", "promise {}"},
{"display.getHdcpVersion", "promise \"0\""},
{"metrics.logBoolToUma"},
{"metrics.logIntToUma"},
{"metrics.logEventToUma"},
{"metrics.logHistogramValueToUma"},
{"metrics.setMplVersion"},
{"accessibility.getAccessibilitySettings",
"promise {\"isColorInversionEnabled\":false,"
"\"isScreenReaderEnabled\":false}"},
......
// Copyright 2019 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 "fuchsia/runners/cast/queryable_data_bindings.h"
#include <memory>
#include <string>
#include <utility>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "fuchsia/base/mem_buffer_util.h"
#include "fuchsia/runners/cast/cast_platform_bindings_ids.h"
namespace {
constexpr char kBindingsPath[] =
FILE_PATH_LITERAL("fuchsia/runners/cast/queryable_data_bindings.js");
// Builds a script snippet which, when executed, will inserts entries into the
// QueryableData JavaScript object. The scripts are inserted in the order
// provided by the iterator.
template <typename Iterator>
base::Optional<fuchsia::mem::Buffer> BuildScriptFromEntries(Iterator begin,
Iterator end) {
// Prepare values for serialization as a JavaScript object literal.
base::DictionaryValue output;
for (auto it = begin; it != end; ++it) {
const chromium::cast::QueryableDataEntry& current = *it;
base::Optional<base::Value> value_parsed =
base::JSONReader::Read(current.json_value);
if (!value_parsed) {
LOG(WARNING) << "Couldn't parse QueryableData item: " << current.key;
continue;
}
output.SetKey(current.key, std::move(*value_parsed));
}
std::string output_json;
if (!base::JSONWriter::Write(output, &output_json)) {
LOG(ERROR)
<< "An unexpected error occurred while serializing QueryableData.";
return base::nullopt;
}
// Check for the |cast| namespace in case we are injecting into a fresh Frame
// which hasn't navigated to a page or received JS bindings.
std::string initialize_values_js = base::StringPrintf(
"if (window.cast)"
" cast.__platform__.__queryPlatformValueStore__.mergeValues(%s);",
output_json.c_str());
output_json.clear();
output_json.shrink_to_fit();
fuchsia::mem::Buffer initialize_values_js_buffer =
cr_fuchsia::MemBufferFromString(initialize_values_js);
initialize_values_js.clear();
initialize_values_js.shrink_to_fit();
return initialize_values_js_buffer;
}
} // namespace
QueryableDataBindings::QueryableDataBindings(
fuchsia::web::Frame* frame,
fidl::InterfaceHandle<chromium::cast::QueryableData> service)
: frame_(frame), service_(service.Bind()) {
DCHECK(frame_);
base::FilePath assets_path;
CHECK(base::PathService::Get(base::DIR_ASSETS, &assets_path));
frame_->AddBeforeLoadJavaScript(
static_cast<uint64_t>(CastPlatformBindingsId::QUERYABLE_DATA), {"*"},
cr_fuchsia::MemBufferFromFile(
base::File(assets_path.AppendASCII(kBindingsPath),
base::File::FLAG_OPEN | base::File::FLAG_READ)),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
CHECK(result.is_response()) << "JavaScript injection error.";
});
service_->GetChangedEntries(
fit::bind_member(this, &QueryableDataBindings::OnEntriesReceived));
}
QueryableDataBindings::~QueryableDataBindings() = default;
bool QueryableDataBindings::QueryableDataEntryLess::operator()(
const chromium::cast::QueryableDataEntry& lhs,
const chromium::cast::QueryableDataEntry& rhs) const {
return lhs.key < rhs.key;
}
void QueryableDataBindings::OnEntriesReceived(
std::vector<chromium::cast::QueryableDataEntry> new_entries) {
// Push changes to the page immediately.
base::Optional<fuchsia::mem::Buffer> update_script =
BuildScriptFromEntries(new_entries.begin(), new_entries.end());
if (!update_script)
return;
frame_->ExecuteJavaScriptNoResult(
{"*"}, std::move(*update_script),
[](fuchsia::web::Frame_ExecuteJavaScriptNoResult_Result result) {
DCHECK(result.is_response()) << "JavaScript injection error.";
});
// Update the cached values by merging in the new entries.
cached_entries_.insert(new_entries.begin(), new_entries.end(),
base::KEEP_LAST_OF_DUPES);
// Update the on-load script with the full list of values.
base::Optional<fuchsia::mem::Buffer> on_load_script =
BuildScriptFromEntries(cached_entries_.begin(), cached_entries_.end());
if (!on_load_script)
return;
frame_->AddBeforeLoadJavaScript(
static_cast<uint64_t>(CastPlatformBindingsId::QUERYABLE_DATA_VALUES),
{"*"}, std::move(*on_load_script),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
CHECK(result.is_response()) << "JavaScript injection error.";
});
// Request more changes from the FIDL service.
service_->GetChangedEntries(
fit::bind_member(this, &QueryableDataBindings::OnEntriesReceived));
}
// Copyright 2019 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 FUCHSIA_RUNNERS_CAST_QUERYABLE_DATA_BINDINGS_H_
#define FUCHSIA_RUNNERS_CAST_QUERYABLE_DATA_BINDINGS_H_
#include <fuchsia/web/cpp/fidl.h>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/macros.h"
#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
// Adds JavaScript functions to a Frame for querying platform values from the
// Agent.
class QueryableDataBindings {
public:
// |frame|: The Frame which will receive the bindings and data.
// Must outlive |this|.
// |service|: The QueryableData service which will supply |this| with data.
// Values from |service| will be read once and injected into
// |frame|. Any changes to |service|'s values will not be
// propagated to the Frame for the lifetime of |this|.
QueryableDataBindings(
fuchsia::web::Frame* frame,
fidl::InterfaceHandle<chromium::cast::QueryableData> service);
~QueryableDataBindings();
private:
// Allows QueryableDataEntry to be stored in a flat_set.
struct QueryableDataEntryLess {
bool operator()(const chromium::cast::QueryableDataEntry& lhs,
const chromium::cast::QueryableDataEntry& rhs) const;
};
// Takes the initial list of QueryableData entries, or a list of updated
// entries, and propagates the information to |frame_|'s script context.
void OnEntriesReceived(
std::vector<chromium::cast::QueryableDataEntry> new_entries);
// The callbacks of any asynchronous calls made to |frame_| should ensure that
// |this| is valid before using it (e.g. via a WeakPtr).
fuchsia::web::Frame* const frame_;
chromium::cast::QueryableDataPtr service_;
base::flat_set<chromium::cast::QueryableDataEntry, QueryableDataEntryLess>
cached_entries_;
DISALLOW_COPY_AND_ASSIGN(QueryableDataBindings);
};
#endif // FUCHSIA_RUNNERS_CAST_QUERYABLE_DATA_BINDINGS_H_
// Copyright 2019 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.
if (!cast)
var cast = new Object;
if (!cast.__platform__)
cast.__platform__ = new Object;
// Don't clobber the QueryableData API if it was previously injected.
if (!cast.__platform__.queryPlatformValue) {
cast.__platform__.__queryPlatformValueStore__ = new class {
mergeValues(values) {
for (var key in values)
this.values_[key] = values[key];
}
getValue(key) {
if (!this.values_.hasOwnProperty(key)) {
console.error('Unknown platformValue: ' + key);
return null;
}
return this.values_[key];
}
values_ = {};
};
cast.__platform__.queryPlatformValue =
cast.__platform__.__queryPlatformValueStore__.getValue.bind(
cast.__platform__.__queryPlatformValueStore__);
}
// Copyright 2019 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 <fuchsia/web/cpp/fidl.h>
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "fuchsia/base/fit_adapter.h"
#include "fuchsia/base/frame_test_util.h"
#include "fuchsia/base/mem_buffer_util.h"
#include "fuchsia/base/result_receiver.h"
#include "fuchsia/base/test_navigation_listener.h"
#include "fuchsia/engine/test/web_engine_browser_test.h"
#include "fuchsia/runners/cast/fake_queryable_data.h"
#include "fuchsia/runners/cast/named_message_port_connector.h"
#include "fuchsia/runners/cast/queryable_data_bindings.h"
namespace {
class QueryableDataBindingsTest : public cr_fuchsia::WebEngineBrowserTest {
public:
QueryableDataBindingsTest()
: queryable_data_service_binding_(&queryable_data_service_) {
set_test_server_root(base::FilePath("fuchsia/runners/cast/testdata"));
}
~QueryableDataBindingsTest() override { connector_->Unregister("testQuery"); }
void SetUpOnMainThread() override {
cr_fuchsia::WebEngineBrowserTest::SetUpOnMainThread();
base::ScopedAllowBlockingForTesting allow_blocking;
frame_ = WebEngineBrowserTest::CreateFrame(&navigation_listener_);
ASSERT_TRUE(embedded_test_server()->Start());
test_url_ = embedded_test_server()->GetURL("/query_platform_value.html");
navigation_listener_.SetBeforeAckHook(base::BindRepeating(
&QueryableDataBindingsTest::OnBeforeAckHook, base::Unretained(this)));
connector_ = std::make_unique<NamedMessagePortConnector>(frame_.get());
connector_->Register(
"testQuery",
base::BindRepeating(&QueryableDataBindingsTest::ReceiveMessagePort,
base::Unretained(this)));
}
// Blocks test execution until the page has indicated that it's processed the
// updates, which we achieve by setting the title to a new value and waiting
// for the resulting navigation event.
void SynchronizeWithPage() {
std::string unique_title =
base::StringPrintf("sync-%d", current_sync_id_++);
frame_->ExecuteJavaScriptNoResult(
{"*"},
cr_fuchsia::MemBufferFromString(
base::StringPrintf("document.title = '%s'", unique_title.c_str())),
[](fuchsia::web::Frame_ExecuteJavaScriptNoResult_Result result) {
ASSERT_TRUE(result.is_response());
});
navigation_listener_.RunUntilUrlAndTitleEquals(test_url_, unique_title);
}
// Communicates with the page to read an entry from its QueryableData store.
std::string CallQueryPlatformValue(base::StringPiece key) {
// Wait until the querying MessagePort is ready to use.
if (!query_port_) {
base::RunLoop run_loop;
on_query_port_received_cb_ = run_loop.QuitClosure();
run_loop.Run();
if (!query_port_)
return "";
}
// Send the request to the page.
fuchsia::web::WebMessage message;
message.set_data(cr_fuchsia::MemBufferFromString(key));
query_port_->PostMessage(
std::move(message),
[](fuchsia::web::MessagePort_PostMessage_Result result) {
ASSERT_TRUE(result.is_response());
});
// Return the response from the page.
base::RunLoop response_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> response(
response_loop.QuitClosure());
query_port_->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(response.GetReceiveCallback()));
response_loop.Run();
if (!response->has_data())
return "";
std::string response_string;
if (!cr_fuchsia::StringFromMemBuffer(response->data(), &response_string))
return "";
return response_string;
}
void ReceiveMessagePort(
fidl::InterfaceHandle<fuchsia::web::MessagePort> port) {
query_port_ = port.Bind();
if (on_query_port_received_cb_)
std::move(on_query_port_received_cb_).Run();
}
protected:
void OnBeforeAckHook(
const fuchsia::web::NavigationState& change,
fuchsia::web::NavigationEventListener::OnNavigationStateChangedCallback
callback) {
if (change.has_is_main_document_loaded() &&
change.is_main_document_loaded())
connector_->OnPageLoad();
callback();
}
fuchsia::web::FramePtr frame_;
GURL test_url_;
std::unique_ptr<NamedMessagePortConnector> connector_;
FakeQueryableData queryable_data_service_;
cr_fuchsia::TestNavigationListener navigation_listener_;
fidl::Binding<chromium::cast::QueryableData> queryable_data_service_binding_;
base::OnceClosure on_query_port_received_cb_;
base::OnceClosure on_navigate_cb_;
fuchsia::web::MessagePortPtr query_port_;
int current_sync_id_ = 0;
private:
DISALLOW_COPY_AND_ASSIGN(QueryableDataBindingsTest);
};
IN_PROC_BROWSER_TEST_F(QueryableDataBindingsTest, VariousTypes) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::DictionaryValue dict_value;
dict_value.SetString("key", "val");
queryable_data_service_.SendChanges({{"string", base::Value("foo")},
{"number", base::Value(123)},
{"null", base::Value()},
{"dict", std::move(dict_value)}});
QueryableDataBindings bindings(
frame_.get(), queryable_data_service_binding_.NewBinding().Bind());
fuchsia::web::NavigationControllerPtr controller;
frame_->GetNavigationController(controller.NewRequest());
frame_->SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel::INFO);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), test_url_.spec()));
navigation_listener_.RunUntilUrlEquals(test_url_);
EXPECT_EQ(CallQueryPlatformValue("string"), "\"foo\"");
EXPECT_EQ(CallQueryPlatformValue("number"), "123");
EXPECT_EQ(CallQueryPlatformValue("null"), "null");
EXPECT_EQ(CallQueryPlatformValue("dict"), "{\"key\":\"val\"}");
}
IN_PROC_BROWSER_TEST_F(QueryableDataBindingsTest, NoValues) {
base::ScopedAllowBlockingForTesting allow_blocking;
QueryableDataBindings bindings(
frame_.get(), queryable_data_service_binding_.NewBinding().Bind());
fuchsia::web::NavigationControllerPtr controller;
frame_->GetNavigationController(controller.NewRequest());
frame_->SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel::INFO);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), test_url_.spec()));
navigation_listener_.RunUntilUrlEquals(test_url_);
EXPECT_EQ(CallQueryPlatformValue("string"), "null");
}
IN_PROC_BROWSER_TEST_F(QueryableDataBindingsTest, AtPageRuntime) {
base::ScopedAllowBlockingForTesting allow_blocking;
queryable_data_service_.SendChanges({{"key1", base::Value(1)},
{"key2", base::Value(2)},
{"key3", base::Value(3)}});
QueryableDataBindings bindings(
frame_.get(), queryable_data_service_binding_.NewBinding().Bind());
fuchsia::web::NavigationControllerPtr controller;
frame_->GetNavigationController(controller.NewRequest());
frame_->SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel::INFO);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), test_url_.spec()));
navigation_listener_.RunUntilUrlEquals(test_url_);
SynchronizeWithPage();
EXPECT_EQ(CallQueryPlatformValue("key1"), "1");
EXPECT_EQ(CallQueryPlatformValue("key2"), "2");
EXPECT_EQ(CallQueryPlatformValue("key3"), "3");
queryable_data_service_.SendChanges(
{{"key1", base::Value(10)}, {"key2", base::Value(20)}});
SynchronizeWithPage();
// Verify that the changes are immediately available.
EXPECT_EQ(CallQueryPlatformValue("key1"), "10");
EXPECT_EQ(CallQueryPlatformValue("key2"), "20");
EXPECT_EQ(CallQueryPlatformValue("key3"), "3");
}
// Sends updates to the Frame before the Frame has created a renderer.
IN_PROC_BROWSER_TEST_F(QueryableDataBindingsTest, AtPageLoad) {
base::ScopedAllowBlockingForTesting allow_blocking;
queryable_data_service_.SendChanges({{"key1", base::Value(1)},
{"key2", base::Value(2)},
{"key3", base::Value(3)}});
queryable_data_service_.SendChanges(
{{"key1", base::Value(10)}, {"key2", base::Value(20)}});
QueryableDataBindings bindings(
frame_.get(), queryable_data_service_binding_.NewBinding().Bind());
fuchsia::web::NavigationControllerPtr controller;
frame_->GetNavigationController(controller.NewRequest());
frame_->SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel::INFO);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), test_url_.spec()));
navigation_listener_.RunUntilUrlEquals(test_url_);
SynchronizeWithPage();
EXPECT_EQ(CallQueryPlatformValue("key1"), "10");
EXPECT_EQ(CallQueryPlatformValue("key2"), "20");
EXPECT_EQ(CallQueryPlatformValue("key3"), "3");
}
} // namespace
<html>
<head><title>castChannel</title></head>
<body>
<script>
var reopened = false;
function openHandler() {
cast.__platform__.channel.send('this');
cast.__platform__.channel.send('is');
cast.__platform__.channel.send('a');
cast.__platform__.channel.send('test');
}
cast.__platform__.channel.open(openHandler, function(ignored) {});
</script>
</body>
</html>
<html>
<head><title>castChannel</title></head>
<body>
<script>
var reopened = false;
// Executes state transitions: open, close, open, send, receive, send.
function openHandler() {
if (!reopened) {
reopened = true;
cast.__platform__.channel.close(function() {});
cast.__platform__.channel.open(openHandler, messageHandler);
} else {
cast.__platform__.channel.send('reconnected');
}
}
function messageHandler(data) {
cast.__platform__.channel.send('ack ' + data);
}
cast.__platform__.channel.open(openHandler, messageHandler);
</script>
</body>
</html>
<html>
<head><title>queryPlatformValue</title></head>
<body>
<script>
// Set up a message port so that native test code can query the live
// state of QueryableData.
var port = cast.__platform__.PortConnector.bind('testQuery');
port.onmessage = function(message) {
port.postMessage(
JSON.stringify(cast.__platform__.queryPlatformValue(message.data)));
}.bind(this);
</script>
</body>
</html>
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