[go: nahoru, domu]

[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