[Fuchsia] Support embedder-provided UserAgent product & version.
Bug: 969252
Change-Id: Ia54129af9dbcb727aab1d3289be997a31143e004
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1649186
Commit-Queue: Wez <wez@chromium.org>
Reviewed-by: Kevin Marshall <kmarshall@chromium.org>
Reviewed-by: Thiemo Nagel <tnagel@chromium.org>
Reviewed-by: Fabrice de Gans-Riberi <fdegans@chromium.org>
Auto-Submit: Wez <wez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#672700}
diff --git a/fuchsia/base/frame_test_util.cc b/fuchsia/base/frame_test_util.cc
index 463c19b..9bd182e72 100644
--- a/fuchsia/base/frame_test_util.cc
+++ b/fuchsia/base/frame_test_util.cc
@@ -4,8 +4,10 @@
#include "fuchsia/base/frame_test_util.h"
+#include "base/json/json_reader.h"
#include "base/run_loop.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"
@@ -14,16 +16,36 @@
bool LoadUrlAndExpectResponse(
fuchsia::web::NavigationController* navigation_controller,
fuchsia::web::LoadUrlParams load_url_params,
- std::string url) {
+ base::StringPiece url) {
DCHECK(navigation_controller);
base::RunLoop run_loop;
ResultReceiver<fuchsia::web::NavigationController_LoadUrl_Result> result(
run_loop.QuitClosure());
navigation_controller->LoadUrl(
- url, std::move(load_url_params),
+ url.as_string(), std::move(load_url_params),
CallbackToFitFunction(result.GetReceiveCallback()));
run_loop.Run();
return result->is_response();
}
+base::Optional<base::Value> ExecuteJavaScript(fuchsia::web::Frame* frame,
+ base::StringPiece script) {
+ base::RunLoop run_loop;
+ ResultReceiver<fuchsia::web::Frame_ExecuteJavaScript_Result> result(
+ run_loop.QuitClosure());
+ frame->ExecuteJavaScript({"*"}, MemBufferFromString(script),
+ CallbackToFitFunction(result.GetReceiveCallback()));
+ run_loop.Run();
+
+ if (!result.has_value() || !result->is_response())
+ return {};
+
+ std::string result_json;
+ if (!StringFromMemBuffer(result->response().result, &result_json)) {
+ return {};
+ }
+
+ return base::JSONReader::Read(result_json);
+}
+
} // namespace cr_fuchsia
diff --git a/fuchsia/base/frame_test_util.h b/fuchsia/base/frame_test_util.h
index d4340f7..b4165bb9 100644
--- a/fuchsia/base/frame_test_util.h
+++ b/fuchsia/base/frame_test_util.h
@@ -7,6 +7,10 @@
#include <fuchsia/web/cpp/fidl.h>
+#include "base/optional.h"
+#include "base/strings/string_piece.h"
+#include "base/values.h"
+
namespace cr_fuchsia {
// Uses |navigation_controller| to load |url| with |load_url_params|. Returns
@@ -15,7 +19,12 @@
bool LoadUrlAndExpectResponse(
fuchsia::web::NavigationController* navigation_controller,
fuchsia::web::LoadUrlParams load_url_params,
- std::string url);
+ base::StringPiece url);
+
+// Executes |script| in the context of |frame|'s top-level document.
+// Returns an un-set |base::Optional<>| on failure.
+base::Optional<base::Value> ExecuteJavaScript(fuchsia::web::Frame* frame,
+ base::StringPiece script);
} // namespace cr_fuchsia
diff --git a/fuchsia/engine/BUILD.gn b/fuchsia/engine/BUILD.gn
index e450b4f..165a179f 100644
--- a/fuchsia/engine/BUILD.gn
+++ b/fuchsia/engine/BUILD.gn
@@ -246,6 +246,7 @@
"test_debug_listener.cc",
"test_debug_listener.h",
"web_engine_debug_integration_test.cc",
+ "web_engine_integration_test.cc",
]
data = [
"test/data",
diff --git a/fuchsia/engine/browser/web_engine_content_browser_client.cc b/fuchsia/engine/browser/web_engine_content_browser_client.cc
index 8fe5c17..f6621b5 100644
--- a/fuchsia/engine/browser/web_engine_content_browser_client.cc
+++ b/fuchsia/engine/browser/web_engine_content_browser_client.cc
@@ -12,6 +12,7 @@
#include "fuchsia/engine/browser/web_engine_browser_context.h"
#include "fuchsia/engine/browser/web_engine_browser_main_parts.h"
#include "fuchsia/engine/browser/web_engine_devtools_manager_delegate.h"
+#include "fuchsia/engine/common.h"
WebEngineContentBrowserClient::WebEngineContentBrowserClient(
fidl::InterfaceRequest<fuchsia::web::Context> request)
@@ -43,8 +44,14 @@
}
std::string WebEngineContentBrowserClient::GetUserAgent() {
- return content::BuildUserAgentFromProduct(
- version_info::GetProductNameAndVersionForUserAgent());
+ std::string user_agent = content::BuildUserAgentFromProduct(GetProduct());
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ kUserAgentProductAndVersion)) {
+ user_agent +=
+ " " + base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
+ kUserAgentProductAndVersion);
+ }
+ return user_agent;
}
void WebEngineContentBrowserClient::OverrideWebkitPrefs(
diff --git a/fuchsia/engine/common.cc b/fuchsia/engine/common.cc
index 61927a9..22db116 100644
--- a/fuchsia/engine/common.cc
+++ b/fuchsia/engine/common.cc
@@ -6,3 +6,4 @@
constexpr char kIncognitoSwitch[] = "incognito";
constexpr char kRemoteDebuggerHandles[] = "remote-debugger-handles";
+constexpr char kUserAgentProductAndVersion[] = "user-agent-product";
diff --git a/fuchsia/engine/common.h b/fuchsia/engine/common.h
index b6f2672..f0664e0 100644
--- a/fuchsia/engine/common.h
+++ b/fuchsia/engine/common.h
@@ -20,6 +20,9 @@
// a comma-separated list of remote debugger handle IDs as an argument.
WEB_ENGINE_EXPORT extern const char kRemoteDebuggerHandles[];
+// Switch passed to Context process to customize the UserAgent string.
+WEB_ENGINE_EXPORT extern const char kUserAgentProductAndVersion[];
+
// Handle ID for the Context interface request passed from ContextProvider to
// Context process.
constexpr uint32_t kContextRequestHandleId = PA_HND(PA_USER0, 0);
diff --git a/fuchsia/engine/context_provider_impl.cc b/fuchsia/engine/context_provider_impl.cc
index b55dd2e..5d50626 100644
--- a/fuchsia/engine/context_provider_impl.cc
+++ b/fuchsia/engine/context_provider_impl.cc
@@ -31,6 +31,7 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "fuchsia/engine/common.h"
+#include "net/http/http_util.h"
#include "services/service_manager/sandbox/fuchsia/sandbox_policy_fuchsia.h"
namespace {
@@ -183,6 +184,31 @@
launch_command.AppendSwitchASCII("--use-gl", "stub");
}
+ // Validate embedder-supplied product, and optional version, and pass it to
+ // the Context to include in the UserAgent.
+ if (params.has_user_agent_product()) {
+ if (!net::HttpUtil::IsToken(params.user_agent_product())) {
+ DLOG(ERROR) << "Invalid embedder product.";
+ context_request.Close(ZX_ERR_INVALID_ARGS);
+ return;
+ }
+ std::string product_tag(params.user_agent_product());
+ if (params.has_user_agent_version()) {
+ if (!net::HttpUtil::IsToken(params.user_agent_version())) {
+ DLOG(ERROR) << "Invalid embedder version.";
+ context_request.Close(ZX_ERR_INVALID_ARGS);
+ return;
+ }
+ product_tag += "/" + params.user_agent_version();
+ }
+ launch_command.AppendSwitchNative(kUserAgentProductAndVersion,
+ std::move(product_tag));
+ } else if (params.has_user_agent_version()) {
+ DLOG(ERROR) << "Embedder version without product.";
+ context_request.Close(ZX_ERR_INVALID_ARGS);
+ return;
+ }
+
if (launch_for_test_)
launch_for_test_.Run(launch_command, launch_options);
else
diff --git a/fuchsia/engine/integration_tests_sandbox_policy b/fuchsia/engine/integration_tests_sandbox_policy
index d5649d3..a2e5ae20 100644
--- a/fuchsia/engine/integration_tests_sandbox_policy
+++ b/fuchsia/engine/integration_tests_sandbox_policy
@@ -5,6 +5,7 @@
"system-temp" ],
"services": [
"fuchsia.device.NameProvider",
+ "fuchsia.fonts.Provider",
"fuchsia.logger.LogSink",
"fuchsia.net.SocketProvider",
"fuchsia.netstack.Netstack",
diff --git a/fuchsia/engine/web_engine_integration_test.cc b/fuchsia/engine/web_engine_integration_test.cc
new file mode 100644
index 0000000..08b302d
--- /dev/null
+++ b/fuchsia/engine/web_engine_integration_test.cc
@@ -0,0 +1,189 @@
+// 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 <lib/fidl/cpp/binding.h>
+
+#include "base/fuchsia/file_utils.h"
+#include "base/fuchsia/service_directory_client.h"
+#include "base/macros.h"
+#include "base/test/scoped_task_environment.h"
+#include "fuchsia/base/frame_test_util.h"
+#include "fuchsia/base/test_navigation_listener.h"
+#include "net/http/http_request_headers.h"
+#include "net/test/embedded_test_server/default_handlers.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr char kValidUserAgentProduct[] = "TestProduct";
+constexpr char kValidUserAgentVersion[] = "dev.12345";
+constexpr char kValidUserAgentProductAndVersion[] = "TestProduct/dev.12345";
+constexpr char kInvalidUserAgentProduct[] = "Test/Product";
+constexpr char kInvalidUserAgentVersion[] = "dev/12345";
+
+} // namespace
+
+class WebEngineIntegrationTest : public testing::Test {
+ public:
+ WebEngineIntegrationTest()
+ : task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
+ ~WebEngineIntegrationTest() override = default;
+
+ void SetUp() override {
+ web_context_provider_ =
+ base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
+ ->ConnectToService<fuchsia::web::ContextProvider>();
+ web_context_provider_.set_error_handler(
+ [](zx_status_t status) { ADD_FAILURE(); });
+
+ net::test_server::RegisterDefaultHandlers(&embedded_test_server_);
+ ASSERT_TRUE(embedded_test_server_.Start());
+ }
+
+ fuchsia::web::CreateContextParams DefaultContextParams() const {
+ fuchsia::web::CreateContextParams create_params;
+ auto directory = base::fuchsia::OpenDirectory(
+ base::FilePath(base::fuchsia::kServiceDirectoryPath));
+ EXPECT_TRUE(directory.is_valid());
+ create_params.set_service_directory(std::move(directory));
+ return create_params;
+ }
+
+ void CreateContextAndFrame(fuchsia::web::CreateContextParams params) {
+ web_context_provider_->Create(std::move(params), context_.NewRequest());
+ context_.set_error_handler([](zx_status_t status) { ADD_FAILURE(); });
+
+ context_->CreateFrame(frame_.NewRequest());
+ frame_.set_error_handler([](zx_status_t status) { ADD_FAILURE(); });
+
+ frame_->GetNavigationController(navigation_controller_.NewRequest());
+ navigation_controller_.set_error_handler(
+ [](zx_status_t status) { ADD_FAILURE(); });
+ }
+
+ void CreateContextAndExpectError(fuchsia::web::CreateContextParams params,
+ zx_status_t expected_error) {
+ web_context_provider_->Create(std::move(params), context_.NewRequest());
+ base::RunLoop run_loop;
+ context_.set_error_handler([&run_loop, expected_error](zx_status_t status) {
+ EXPECT_EQ(status, expected_error);
+ run_loop.Quit();
+ });
+ run_loop.Run();
+ }
+
+ void CreateContextAndFrameAndLoadUrl(fuchsia::web::CreateContextParams params,
+ const GURL& url) {
+ CreateContextAndFrame(std::move(params));
+
+ // Attach a navigation listener, to monitor the state of the Frame.
+ cr_fuchsia::TestNavigationListener listener;
+ fidl::Binding<fuchsia::web::NavigationEventListener> listener_binding(
+ &listener);
+ frame_->SetNavigationEventListener(listener_binding.NewBinding());
+
+ // Navigate the Frame to |url| and wait for it to complete loading.
+ fuchsia::web::LoadUrlParams load_url_params;
+ ASSERT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
+ navigation_controller_.get(), std::move(load_url_params), url.spec()));
+
+ // Wait for the URL to finish loading.
+ listener.RunUntilUrlEquals(url);
+ }
+
+ std::string ExecuteJavaScriptWithStringResult(base::StringPiece script) {
+ base::Optional<base::Value> value =
+ cr_fuchsia::ExecuteJavaScript(frame_.get(), script);
+ return value ? value->GetString() : std::string();
+ }
+
+ protected:
+ const base::test::ScopedTaskEnvironment task_environment_;
+
+ fuchsia::web::ContextProviderPtr web_context_provider_;
+
+ net::EmbeddedTestServer embedded_test_server_;
+
+ fuchsia::web::ContextPtr context_;
+ fuchsia::web::FramePtr frame_;
+ fuchsia::web::NavigationControllerPtr navigation_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebEngineIntegrationTest);
+};
+
+TEST_F(WebEngineIntegrationTest, ValidUserAgent) {
+ const std::string kEchoHeaderPath =
+ std::string("/echoheader?") + net::HttpRequestHeaders::kUserAgent;
+ const GURL kEchoUserAgentUrl(embedded_test_server_.GetURL(kEchoHeaderPath));
+
+ {
+ // Create a Context with just an embedder product specified.
+ fuchsia::web::CreateContextParams create_params = DefaultContextParams();
+ create_params.set_user_agent_product(kValidUserAgentProduct);
+ CreateContextAndFrameAndLoadUrl(std::move(create_params),
+ kEchoUserAgentUrl);
+
+ // Query & verify that the header echoed into the document body contains
+ // the product tag.
+ std::string result =
+ ExecuteJavaScriptWithStringResult("document.body.innerText;");
+ EXPECT_TRUE(result.find(kValidUserAgentProduct) != std::string::npos);
+
+ // Query & verify that the navigator.userAgent contains the product tag.
+ result = ExecuteJavaScriptWithStringResult("navigator.userAgent;");
+ EXPECT_TRUE(result.find(kValidUserAgentProduct) != std::string::npos);
+ }
+
+ {
+ // Create a Context with both product and version specified.
+ fuchsia::web::CreateContextParams create_params = DefaultContextParams();
+ create_params.set_user_agent_product(kValidUserAgentProduct);
+ create_params.set_user_agent_version(kValidUserAgentVersion);
+ CreateContextAndFrameAndLoadUrl(std::move(create_params),
+ kEchoUserAgentUrl);
+
+ // Query & verify that the header echoed into the document body contains
+ // both product & version.
+ std::string result =
+ ExecuteJavaScriptWithStringResult("document.body.innerText;");
+ EXPECT_TRUE(result.find(kValidUserAgentProductAndVersion) !=
+ std::string::npos);
+
+ // Query & verify that the navigator.userAgent contains product & version.
+ result = ExecuteJavaScriptWithStringResult("navigator.userAgent;");
+ EXPECT_TRUE(result.find(kValidUserAgentProductAndVersion) !=
+ std::string::npos);
+ }
+}
+
+TEST_F(WebEngineIntegrationTest, InvalidUserAgent) {
+ const std::string kEchoHeaderPath =
+ std::string("/echoheader?") + net::HttpRequestHeaders::kUserAgent;
+ const GURL kEchoUserAgentUrl(embedded_test_server_.GetURL(kEchoHeaderPath));
+
+ {
+ // Try to create a Context with an invalid embedder product tag.
+ fuchsia::web::CreateContextParams create_params = DefaultContextParams();
+ create_params.set_user_agent_product(kInvalidUserAgentProduct);
+ CreateContextAndExpectError(std::move(create_params), ZX_ERR_INVALID_ARGS);
+ }
+
+ {
+ // Try to create a Context with an embedder version but no product.
+ fuchsia::web::CreateContextParams create_params = DefaultContextParams();
+ create_params.set_user_agent_version(kValidUserAgentVersion);
+ CreateContextAndExpectError(std::move(create_params), ZX_ERR_INVALID_ARGS);
+ }
+
+ {
+ // Try to create a Context with valid product tag, but invalid version.
+ fuchsia::web::CreateContextParams create_params = DefaultContextParams();
+ create_params.set_user_agent_product(kValidUserAgentProduct);
+ create_params.set_user_agent_version(kInvalidUserAgentVersion);
+ CreateContextAndExpectError(std::move(create_params), ZX_ERR_INVALID_ARGS);
+ }
+}
diff --git a/fuchsia/runners/cast/cast_runner_integration_test.cc b/fuchsia/runners/cast/cast_runner_integration_test.cc
index 3f60906..ed8e8f2c 100644
--- a/fuchsia/runners/cast/cast_runner_integration_test.cc
+++ b/fuchsia/runners/cast/cast_runner_integration_test.cc
@@ -18,6 +18,7 @@
#include "fuchsia/base/agent_impl.h"
#include "fuchsia/base/fake_component_context.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"
@@ -366,27 +367,11 @@
navigation_listener.RunUntilUrlEquals(echo_app_url);
// Check the header was properly set.
- {
- base::RunLoop run_loop;
- web_component->frame()->ExecuteJavaScript(
- {echo_app_url.GetOrigin().spec()},
- cr_fuchsia::MemBufferFromString("document.body.innerText"),
- [&](fuchsia::web::Frame_ExecuteJavaScript_Result result) {
- ASSERT_TRUE(result.is_response());
- std::string result_json;
- ASSERT_TRUE(cr_fuchsia::StringFromMemBuffer(result.response().result,
- &result_json));
- EXPECT_EQ(result_json, "\"Value\"");
- run_loop.Quit();
- });
- run_loop.Run();
- }
-
- // Shutdown the component and wait for the teardown of its state.
- base::RunLoop run_loop;
- component_state_->set_on_delete(run_loop.QuitClosure());
- component_controller.Unbind();
- run_loop.Run();
+ base::Optional<base::Value> result = cr_fuchsia::ExecuteJavaScript(
+ web_component->frame(), "document.body.innerText");
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result->is_string());
+ EXPECT_EQ(result->GetString(), "Value");
}
} // namespace castrunner