Prepare Cast Shell for InterfaceProvider removal

Upstream Chromium no longer uses an InterfaceProvider to expose browser
interfaces to render frames. Cast Shell still does this for a small
handful of interfaces registered by downstream internal-only logic.

In order to support the transition of the internal repository away from
this mechanism, this CL introduces a bit of redundancy: interfaces
registered with CastWebContents through either of its two registration
mechanisms will now be exposed to *both* InterfaceProvider consumers as
well as BrowserInterfaceBroker.

Once this lands, internal renderer code can simply be rewritten to use
RenderFrame::GetBrowserInterfaceBroker instead of
RenderFrame::GetRemoteInterfaces(), and then upstream Chromium can
completely remove all its gnarly support for a browser-exposed
InterfaceProvider.

Bug: 977637
Change-Id: I21ae1fc31b35b0ca77ab2a00645feafe0ac67a73
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2547054
Reviewed-by: Sean Topping <[email protected]>
Reviewed-by: Daniel Cheng <[email protected]>
Commit-Queue: Ken Rockot <[email protected]>
Cr-Commit-Position: refs/heads/master@{#829858}
diff --git a/chromecast/browser/BUILD.gn b/chromecast/browser/BUILD.gn
index 91bc55d6..0d7cd67 100644
--- a/chromecast/browser/BUILD.gn
+++ b/chromecast/browser/BUILD.gn
@@ -6,6 +6,7 @@
 import("//build/config/ui.gni")
 import("//chromecast/chromecast.gni")
 import("//media/media_options.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
 import("//testing/test.gni")
 import("//tools/grit/grit_rule.gni")
 
@@ -540,6 +541,11 @@
   ]
 }
 
+mojom("test_interfaces") {
+  testonly = true
+  sources = [ "test_interfaces.test-mojom" ]
+}
+
 cast_source_set("browsertests") {
   testonly = true
   sources = [
@@ -555,6 +561,7 @@
   defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
 
   deps = [
+    ":test_interfaces",
     ":test_support",
     "//base",
     "//chromecast:chromecast_buildflags",
@@ -567,6 +574,8 @@
     "//content/test:test_support",
     "//media:test_support",
     "//net:test_support",
+    "//services/service_manager/public/cpp",
+    "//services/service_manager/public/mojom",
   ]
 
   data = [
diff --git a/chromecast/browser/cast_browser_interface_binders.cc b/chromecast/browser/cast_browser_interface_binders.cc
index 5f66833..b33fd59 100644
--- a/chromecast/browser/cast_browser_interface_binders.cc
+++ b/chromecast/browser/cast_browser_interface_binders.cc
@@ -54,6 +54,26 @@
       ::media::mojom::Remotee::Name_, &interface_pipe);
 }
 
+// Some Cast internals still dynamically set up interface binders after
+// frame host initialization. This is used to generically forward incoming
+// interface receivers to those objects until they can be reworked as static
+// registrations below.
+bool HandleGenericReceiver(content::RenderFrameHost* frame_host,
+                           mojo::GenericPendingReceiver& receiver) {
+  auto* web_contents = content::WebContents::FromRenderFrameHost(frame_host);
+  if (!web_contents)
+    return false;
+
+  // Only WebContents created for Cast Webviews will have a CastWebContents
+  // object associated with them. We ignore these requests for any other
+  // WebContents.
+  auto* cast_web_contents = CastWebContents::FromWebContents(web_contents);
+  if (!cast_web_contents || !cast_web_contents->can_bind_interfaces())
+    return false;
+
+  return cast_web_contents->TryBindReceiver(receiver);
+}
+
 }  // namespace
 
 void PopulateCastFrameBinders(
@@ -65,6 +85,9 @@
       base::BindRepeating(&BindApplicationMediaCapabilities));
   binder_map->Add<::media::mojom::Remotee>(
       base::BindRepeating(&BindMediaRemotingRemotee));
+
+  binder_map->SetDefaultBinderDeprecated(
+      base::BindRepeating(&HandleGenericReceiver));
 }
 
 }  // namespace shell
diff --git a/chromecast/browser/cast_web_contents.h b/chromecast/browser/cast_web_contents.h
index 2cf04fa0..b7fa8fb 100644
--- a/chromecast/browser/cast_web_contents.h
+++ b/chromecast/browser/cast_web_contents.h
@@ -16,6 +16,7 @@
 #include "base/strings/string16.h"
 #include "chromecast/common/mojom/feature_manager.mojom.h"
 #include "content/public/common/media_playback_renderer_type.mojom.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/public/common/messaging/web_message_port.h"
@@ -380,6 +381,10 @@
   // when it is ready.
   virtual service_manager::BinderRegistry* binder_registry() = 0;
 
+  // Asks the CastWebContents to bind an interface receiver using either its
+  // registry or any registered InterfaceProvider.
+  virtual bool TryBindReceiver(mojo::GenericPendingReceiver& receiver) = 0;
+
   // Used for owner to pass its |InterfaceProvider| pointers to CastWebContents.
   // It is owner's responsibility to make sure each |InterfaceProvider| pointer
   // has distinct mojo interface set.
diff --git a/chromecast/browser/cast_web_contents_browsertest.cc b/chromecast/browser/cast_web_contents_browsertest.cc
index 454478a..0e4e7bc 100644
--- a/chromecast/browser/cast_web_contents_browsertest.cc
+++ b/chromecast/browser/cast_web_contents_browsertest.cc
@@ -19,11 +19,13 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "chromecast/base/chromecast_switches.h"
 #include "chromecast/base/metrics/cast_metrics_helper.h"
 #include "chromecast/browser/cast_browser_context.h"
 #include "chromecast/browser/cast_browser_process.h"
 #include "chromecast/browser/cast_web_contents_impl.h"
+#include "chromecast/browser/test_interfaces.test-mojom.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
@@ -33,11 +35,16 @@
 #include "content/public/test/browser_test_base.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/url_loader_interceptor.h"
-#include "mojo/public/cpp/bindings/connector.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "net/http/http_status_code.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -239,8 +246,9 @@
     SetUpCommandLine(base::CommandLine::ForCurrentProcess());
     BrowserTestBase::SetUp();
   }
-  void SetUpCommandLine(base::CommandLine* command_line) final {
+  void SetUpCommandLine(base::CommandLine* command_line) override {
     command_line->AppendSwitchASCII(switches::kTestType, "browser");
+    command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "MojoJS");
   }
   void PreRunTestOnMainThread() override {
     // Pump startup related events.
@@ -955,6 +963,147 @@
   run_loop2.Run();
 }
 
+// Helper for the test below. This exposes two interfaces, TestAdder and
+// TestDoubler. TestAdder is exposed only through a binder (see MakeAdderBinder)
+// which the test will register in the CastWebContents' binder_registry().
+// TestDoubler is exposed only through an InterfaceProvider, registered with the
+// CastWebContents using RegisterInterfaceProvider.
+class TestInterfaceProvider : public service_manager::mojom::InterfaceProvider,
+                              public mojom::TestAdder,
+                              public mojom::TestDoubler {
+ public:
+  TestInterfaceProvider()
+      : provider_(receiver_.BindNewPipeAndPassRemote(),
+                  base::SequencedTaskRunnerHandle::Get()) {}
+  ~TestInterfaceProvider() override = default;
+
+  size_t num_adders() const { return adders_.size(); }
+  size_t num_doublers() const { return doublers_.size(); }
+
+  service_manager::InterfaceProvider* interface_provider() {
+    return &provider_;
+  }
+
+  base::RepeatingCallback<void(mojo::PendingReceiver<mojom::TestAdder>)>
+  MakeAdderBinder() {
+    return base::BindLambdaForTesting(
+        [this](mojo::PendingReceiver<mojom::TestAdder> receiver) {
+          adders_.Add(this, std::move(receiver));
+          OnRequestHandled();
+        });
+  }
+
+  // Waits for some number of new interface binding requests to be dispatched
+  // and then invokes `callback`.
+  void WaitForRequests(size_t n, base::OnceClosure callback) {
+    wait_callback_ = std::move(callback);
+    num_requests_to_wait_for_ = n;
+  }
+
+  // service_manager::mojom::InterfaceProvider:
+  void GetInterface(const std::string& interface_name,
+                    mojo::ScopedMessagePipeHandle interface_pipe) override {
+    if (interface_name == mojom::TestDoubler::Name_) {
+      doublers_.Add(this, mojo::PendingReceiver<mojom::TestDoubler>(
+                              std::move(interface_pipe)));
+      OnRequestHandled();
+    }
+  }
+
+  // mojom::TestAdder:
+  void Add(int32_t a, int32_t b, AddCallback callback) override {
+    std::move(callback).Run(a + b);
+  }
+
+  // mojom::TestDouble:
+  void Double(int32_t x, DoubleCallback callback) override {
+    std::move(callback).Run(x * 2);
+  }
+
+ private:
+  void OnRequestHandled() {
+    if (num_requests_to_wait_for_ == 0)
+      return;
+    DCHECK(wait_callback_);
+    if (--num_requests_to_wait_for_ == 0)
+      std::move(wait_callback_).Run();
+  }
+
+  mojo::Receiver<service_manager::mojom::InterfaceProvider> receiver_{this};
+  service_manager::InterfaceProvider provider_;
+  mojo::ReceiverSet<mojom::TestAdder> adders_;
+  mojo::ReceiverSet<mojom::TestDoubler> doublers_;
+  size_t num_requests_to_wait_for_ = 0;
+  base::OnceClosure wait_callback_;
+};
+
+IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, InterfaceBinding) {
+  // This test verifies that interfaces registered with the CastWebContents --
+  // either via its binder_registry() or its RegisterInterfaceProvider() API --
+  // are reachable from render frames using either the deprecated
+  // InterfaceProvider API (which results in an OnInterfaceRequestFromFrame call
+  // on the WebContents) or the newer BrowserInterfaceBroker API which is used
+  // in most other places (including from Mojo JS).
+  TestInterfaceProvider provider;
+  cast_web_contents_->binder_registry()->AddInterface(
+      provider.MakeAdderBinder());
+  cast_web_contents_->RegisterInterfaceProvider(
+      CastWebContents::InterfaceSet{mojom::TestDoubler::Name_},
+      provider.interface_provider());
+
+  // First verify that both interfaces are reachable using the deprecated
+  // WebContents path, which is triggered only by renderer-side use of
+  // RenderFrame::GetRemoteInterfaces(). Since poking renderer state in browser
+  // tests is challenging, we simply simulate the resulting WebContentsObbserver
+  // calls here instead and verify end-to-end connection for each interface.
+  content::RenderFrameHost* main_frame =
+      cast_web_contents_->web_contents()->GetMainFrame();
+  mojo::Remote<mojom::TestAdder> adder;
+  mojo::ScopedMessagePipeHandle adder_receiver_pipe =
+      adder.BindNewPipeAndPassReceiver().PassPipe();
+  cast_web_contents_->OnInterfaceRequestFromFrame(
+      main_frame, mojom::TestAdder::Name_, &adder_receiver_pipe);
+  mojo::Remote<mojom::TestDoubler> doubler;
+  mojo::ScopedMessagePipeHandle doubler_receiver_pipe =
+      doubler.BindNewPipeAndPassReceiver().PassPipe();
+  cast_web_contents_->OnInterfaceRequestFromFrame(
+      main_frame, mojom::TestDoubler::Name_, &doubler_receiver_pipe);
+
+  base::RunLoop add_loop;
+  adder->Add(37, 5, base::BindLambdaForTesting([&](int32_t result) {
+               EXPECT_EQ(42, result);
+               add_loop.Quit();
+             }));
+  add_loop.Run();
+
+  base::RunLoop double_loop;
+  doubler->Double(21, base::BindLambdaForTesting([&](int32_t result) {
+                    EXPECT_EQ(42, result);
+                    double_loop.Quit();
+                  }));
+  double_loop.Run();
+
+  EXPECT_EQ(1u, provider.num_adders());
+  EXPECT_EQ(1u, provider.num_doublers());
+
+  // Now verify that the same interfaces are also reachable at the same binders
+  // when going through the newer BrowserInterfaceBroker path. For simplicity
+  // the test JS here does not have access to bindings and so does not make
+  // calls on the interfaces. It is however totally sufficient for us to verify
+  // that the page's requests result in new receivers being bound inside
+  // TestInterfaceProvider.
+  base::RunLoop loop;
+  provider.WaitForRequests(2, loop.QuitClosure());
+  embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
+  StartTestServer();
+  const GURL kUrl{embedded_test_server()->GetURL("/interface_binding.html")};
+  cast_web_contents_->LoadUrl(kUrl);
+  loop.Run();
+
+  EXPECT_EQ(2u, provider.num_adders());
+  EXPECT_EQ(2u, provider.num_doublers());
+}
+
 }  // namespace chromecast
 
 #endif  // CHROMECAST_BROWSER_CAST_WEB_CONTENTS_BROWSERTEST_H_
diff --git a/chromecast/browser/cast_web_contents_impl.cc b/chromecast/browser/cast_web_contents_impl.cc
index c962ebf..f327e2ec 100644
--- a/chromecast/browser/cast_web_contents_impl.cc
+++ b/chromecast/browser/cast_web_contents_impl.cc
@@ -397,6 +397,31 @@
   return &binder_registry_;
 }
 
+bool CastWebContentsImpl::TryBindReceiver(
+    mojo::GenericPendingReceiver& receiver) {
+  const std::string interface_name = *receiver.interface_name();
+  mojo::ScopedMessagePipeHandle interface_pipe = receiver.PassPipe();
+  if (binder_registry_.TryBindInterface(interface_name, &interface_pipe)) {
+    return true;
+  }
+
+  for (auto& entry : interface_providers_map_) {
+    auto const& interface_set = entry.first;
+    // Interface is provided by this InterfaceProvider.
+    if (interface_set.find(interface_name) != interface_set.end()) {
+      auto* interface_provider = entry.second;
+      interface_provider->GetInterfaceByName(interface_name,
+                                             std::move(interface_pipe));
+      return true;
+    }
+  }
+
+  // Unsuccessful, so give the caller its receiver back.
+  receiver =
+      mojo::GenericPendingReceiver(interface_name, std::move(interface_pipe));
+  return false;
+}
+
 void CastWebContentsImpl::RegisterInterfaceProvider(
     const InterfaceSet& interface_set,
     service_manager::InterfaceProvider* interface_provider) {
@@ -504,18 +529,12 @@
   if (!can_bind_interfaces()) {
     return;
   }
-  if (binder_registry_.TryBindInterface(interface_name, interface_pipe)) {
-    return;
-  }
-  for (auto& entry : interface_providers_map_) {
-    auto const& interface_set = entry.first;
-    // Interface is provided by this InterfaceProvider.
-    if (interface_set.find(interface_name) != interface_set.end()) {
-      auto* interface_provider = entry.second;
-      interface_provider->GetInterfaceByName(interface_name,
-                                             std::move(*interface_pipe));
-      break;
-    }
+
+  mojo::GenericPendingReceiver receiver(interface_name,
+                                        std::move(*interface_pipe));
+  if (!TryBindReceiver(receiver)) {
+    // If binding was unsuccessful, give the caller its pipe back.
+    *interface_pipe = receiver.PassPipe();
   }
 }
 
diff --git a/chromecast/browser/cast_web_contents_impl.h b/chromecast/browser/cast_web_contents_impl.h
index 535c99df..9a319763 100644
--- a/chromecast/browser/cast_web_contents_impl.h
+++ b/chromecast/browser/cast_web_contents_impl.h
@@ -63,6 +63,7 @@
       const InterfaceSet& interface_set,
       service_manager::InterfaceProvider* interface_provider) override;
   service_manager::BinderRegistry* binder_registry() override;
+  bool TryBindReceiver(mojo::GenericPendingReceiver& receiver) override;
   void BlockMediaLoading(bool blocked) override;
   void BlockMediaStarting(bool blocked) override;
   void EnableBackgroundVideoPlayback(bool enabled) override;
diff --git a/chromecast/browser/test/data/interface_binding.html b/chromecast/browser/test/data/interface_binding.html
new file mode 100644
index 0000000..70d2c48
--- /dev/null
+++ b/chromecast/browser/test/data/interface_binding.html
@@ -0,0 +1,17 @@
+<html>
+    <head><title>Binding some Mojo interfaces</title></head>
+    <body>
+        <script>
+          function bindInterface(name) {
+            const {handle0, handle1} = Mojo.createMessagePipe();
+            Mojo.bindInterface(name, handle0);
+          }
+
+          // The browser test which uses this page will succeed once these
+          // bindInterface requests reach browser-side binders registered by
+          // the test.
+          bindInterface('chromecast.mojom.TestAdder');
+          bindInterface('chromecast.mojom.TestDoubler');
+        </script>
+    </body>
+</html>
diff --git a/chromecast/browser/test/mock_cast_web_view.cc b/chromecast/browser/test/mock_cast_web_view.cc
index 6af9c9d8..fa9bf9b 100644
--- a/chromecast/browser/test/mock_cast_web_view.cc
+++ b/chromecast/browser/test/mock_cast_web_view.cc
@@ -13,6 +13,10 @@
   return &registry_;
 }
 
+bool MockCastWebContents::TryBindReceiver(mojo::GenericPendingReceiver&) {
+  return false;
+}
+
 MockCastWebView::MockCastWebView() {
   mock_cast_web_contents_ = std::make_unique<MockCastWebContents>();
 }
diff --git a/chromecast/browser/test/mock_cast_web_view.h b/chromecast/browser/test/mock_cast_web_view.h
index 22c366c1..5393bb5 100644
--- a/chromecast/browser/test/mock_cast_web_view.h
+++ b/chromecast/browser/test/mock_cast_web_view.h
@@ -64,6 +64,7 @@
   MOCK_METHOD(bool, can_bind_interfaces, (), (override));
 
   service_manager::BinderRegistry* binder_registry() override;
+  bool TryBindReceiver(mojo::GenericPendingReceiver&) override;
 
  private:
   service_manager::BinderRegistry registry_;
diff --git a/chromecast/browser/test_interfaces.test-mojom b/chromecast/browser/test_interfaces.test-mojom
new file mode 100644
index 0000000..f5ca2a76
--- /dev/null
+++ b/chromecast/browser/test_interfaces.test-mojom
@@ -0,0 +1,13 @@
+// Copyright 2020 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.
+
+module chromecast.mojom;
+
+interface TestAdder {
+  Add(int32 a, int32 b) => (int32 result);
+};
+
+interface TestDoubler {
+  Double(int32 x) => (int32 result);
+};
diff --git a/mojo/public/cpp/bindings/binder_map.h b/mojo/public/cpp/bindings/binder_map.h
index 35bd8fc..09f67b6 100644
--- a/mojo/public/cpp/bindings/binder_map.h
+++ b/mojo/public/cpp/bindings/binder_map.h
@@ -100,12 +100,24 @@
                   "ContextType is void.");
     auto it = binders_.find(*receiver->interface_name());
     if (it == binders_.end())
-      return false;
+      return default_binder_ && default_binder_.Run(context, *receiver);
 
     it->second->BindInterface(std::move(context), receiver->PassPipe());
     return true;
   }
 
+  // DO NOT USE. This sets a generic default handler for any receiver that
+  // doesn't match a registered binder. It's a transitional API to help migrate
+  // some older code to BinderMap. Reliance on this mechanism makes security
+  // auditing more difficult. Note that this intentionally only supports use
+  // with a non-void ContextType, since that's the only existing use case.
+  using DefaultBinder =
+      base::RepeatingCallback<bool(ContextValueType context,
+                                   mojo::GenericPendingReceiver&)>;
+  void SetDefaultBinderDeprecated(DefaultBinder binder) {
+    default_binder_ = std::move(binder);
+  }
+
  private:
   using IsVoidContext = std::is_same<ContextType, void>;
 
@@ -113,6 +125,7 @@
       std::string,
       std::unique_ptr<internal::GenericCallbackBinderWithContext<ContextType>>>
       binders_;
+  DefaultBinder default_binder_;
 };
 
 // Common alias for BinderMapWithContext that has no context. Binders added to