Convert WidgetMsg_SetActive to FrameWidget mojom

This CL defines frame widget specific methods that will be invoked from
the browser process to activate/deactivate the corresponding
RenderWidget.

Bug: 1071565
Change-Id: I2866536547d9b58fb6bd9559329e3d35b577748d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2188013
Commit-Queue: Abhijeet Kandalkar <abhijeet@igalia.com>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Cr-Commit-Position: refs/heads/master@{#803792}
diff --git a/chrome/browser/chrome_render_widget_host_browsertests.cc b/chrome/browser/chrome_render_widget_host_browsertests.cc
new file mode 100644
index 0000000..f6c04316
--- /dev/null
+++ b/chrome/browser/chrome_render_widget_host_browsertests.cc
@@ -0,0 +1,247 @@
+// 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.
+
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "base/test/test_timeouts.h"
+#include "build/build_config.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/location_bar/location_bar.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/omnibox/browser/omnibox_view.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/focused_node_details.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "url/gurl.h"
+
+class ActiveRenderWidgetHostBrowserTest : public InProcessBrowserTest {
+ public:
+  ActiveRenderWidgetHostBrowserTest() = default;
+  ~ActiveRenderWidgetHostBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    content::IsolateAllSitesForTesting(command_line);
+  }
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    // Add content/test/data for cross_site_iframe_factory.html
+    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ActiveRenderWidgetHostBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(ActiveRenderWidgetHostBrowserTest,
+                       DocumentIsActiveAndFocused) {
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b(c),d)"));
+
+  //  Site A ------------ proxies for B C D
+  //    |--Site B ------- proxies for A C D
+  //    |    +--Site C -- proxies for A B D
+  //    +--Site D ------- proxies for A B C
+  // Where A = http://a.com/
+  //       B = http://b.com/
+  //       C = http://c.com/
+  //       D = http://d.com/
+  ui_test_utils::NavigateToURL(browser(), main_url);
+
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  content::RenderFrameHost* main_frame_a = web_contents->GetMainFrame();
+  content::RenderFrameHost* child_frame_b = ChildFrameAt(main_frame_a, 0);
+  ASSERT_NE(nullptr, child_frame_b);
+  content::RenderFrameHost* child_frame_d = ChildFrameAt(main_frame_a, 1);
+  ASSERT_NE(nullptr, child_frame_d);
+  content::RenderFrameHost* child_frame_c = ChildFrameAt(child_frame_b, 0);
+  ASSERT_NE(nullptr, child_frame_c);
+
+  EXPECT_NE(main_frame_a->GetSiteInstance(), child_frame_b->GetSiteInstance());
+  EXPECT_NE(main_frame_a->GetSiteInstance(), child_frame_d->GetSiteInstance());
+  EXPECT_NE(child_frame_b->GetSiteInstance(), child_frame_c->GetSiteInstance());
+
+  // Helper function to check document.hasFocus() for a given frame.
+  // hasFocus internally calls FocusController::IsDocumentFocused which
+  // return true only iff document is  active and focused.
+  auto document_is_active_and_focused =
+      [](content::RenderFrameHost* rfh) -> bool {
+    bool has_focus = false;
+    EXPECT_TRUE(ExecuteScriptAndExtractBool(
+        rfh, "window.domAutomationController.send(document.hasFocus())",
+        &has_focus));
+    return has_focus;
+  };
+
+  // Helper function to check a property of document.activeElement in the
+  // specified frame.
+  auto verify_active_element_property = [](content::RenderFrameHost* rfh,
+                                           const std::string& property,
+                                           const std::string& expected_value) {
+    std::string script = base::StringPrintf(
+        "document.activeElement.%s.toLowerCase();", property.c_str());
+    EXPECT_EQ(expected_value, EvalJs(rfh, script));
+  };
+
+  // The main_frame_a should have a focus to start with.
+  EXPECT_EQ(main_frame_a, web_contents->GetFocusedFrame());
+  EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_b));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
+  verify_active_element_property(main_frame_a, "tagName", "body");
+
+  // After focusing child_frame_b, document.hasFocus() should return
+  // true for child_frame_b and all its ancestor frames.
+  EXPECT_TRUE(ExecuteScript(child_frame_b, "window.focus();"));
+  EXPECT_EQ(child_frame_b, web_contents->GetFocusedFrame());
+  EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
+  EXPECT_TRUE(document_is_active_and_focused(child_frame_b));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
+  verify_active_element_property(main_frame_a, "tagName", "iframe");
+  verify_active_element_property(main_frame_a, "src",
+                                 child_frame_b->GetLastCommittedURL().spec());
+
+  // After focusing child_frame_c, document.hasFocus() should return
+  // true for child_frame_c and all its ancestor frames.
+  EXPECT_TRUE(ExecuteScript(child_frame_c, "window.focus();"));
+  EXPECT_EQ(child_frame_c, web_contents->GetFocusedFrame());
+  EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
+  EXPECT_TRUE(document_is_active_and_focused(child_frame_b));
+  EXPECT_TRUE(document_is_active_and_focused(child_frame_c));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
+  verify_active_element_property(main_frame_a, "tagName", "iframe");
+  // Check document.activeElement in main_frame_a.  It should still
+  // point to <iframe> for the b.com frame, since Blink computes the
+  // focused iframe element by walking the parent chain of the focused
+  // frame until it hits the current frame.  This logic should still
+  // work with remote frames.
+  verify_active_element_property(main_frame_a, "src",
+                                 child_frame_b->GetLastCommittedURL().spec());
+
+  // After focusing child_frame_d, document.hasFocus() should return
+  // true for child_frame_d and all its ancestor frames.
+  EXPECT_TRUE(ExecuteScript(child_frame_d, "window.focus();"));
+  EXPECT_EQ(child_frame_d, web_contents->GetFocusedFrame());
+  EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_b));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
+  EXPECT_TRUE(document_is_active_and_focused(child_frame_d));
+  verify_active_element_property(main_frame_a, "tagName", "iframe");
+  verify_active_element_property(main_frame_a, "src",
+                                 child_frame_d->GetLastCommittedURL().spec());
+
+  // After focusing main_frame_a, document.hasFocus() should return
+  // true for main_frame_a and since it's a root of tree, all its
+  // descendants should return false. On the renderer side, both the
+  // 'active' and 'focus' states for blink::FocusController will be
+  // true.
+  EXPECT_TRUE(ExecuteScript(main_frame_a, "window.focus();"));
+  EXPECT_EQ(main_frame_a, web_contents->GetFocusedFrame());
+  EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_b));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
+  verify_active_element_property(main_frame_a, "tagName", "body");
+
+  // Focus the URL bar.
+  OmniboxView* omnibox =
+      browser()->window()->GetLocationBar()->GetOmniboxView();
+  // Give the omnibox focus.
+  omnibox->SetFocus(/*is_user_initiated=*/true);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(main_frame_a, web_contents->GetFocusedFrame());
+
+  // `omnibox->SetFocus()` should call blur event on main_frame_a and
+  // deactivate the active render widget, but on Mac calling
+  // `omnibox->SetFocus()` function doesn't invoke
+  // RWHI::SetActive(false). As a result, `blink::FocusController`'s
+  // 'active' state maintains the previous value of false.
+  //
+  // This table sums up `blink::FocusController`'s 'active' and 'focus'
+  // states on different platforms after focusing the omnibox:
+  //
+  // |        | Linux |  Mac  | Windows |
+  // | active | false | true  | false   |
+  // | focus  | false | false | false   |
+  //
+  // Since `document.hasFocus()` only returns true iff the document is
+  // both active and focus, the test still expects
+  // `document.hasFocus()` to be false on all platforms.
+  //
+  // Note that there is no separate API to test active state of the
+  // document. Instead, Mac's active behavior is separately tested in
+  // `ActiveRenderWidgetHostBrowserTest.FocusOmniBox`.
+  EXPECT_FALSE(document_is_active_and_focused(main_frame_a));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_b));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
+  EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
+  // body tag is active by default.
+  verify_active_element_property(main_frame_a, "tagName", "body");
+  verify_active_element_property(child_frame_b, "tagName", "body");
+  verify_active_element_property(child_frame_c, "tagName", "body");
+  verify_active_element_property(child_frame_d, "tagName", "body");
+}
+
+// This test verifies that on Mac, moving the focus from webcontents to Omnibox
+// doesn't change the 'active' state and old value of the active state is
+// retained.
+//
+// FakeFrameWidget has Optional<bool> 'active' state which is
+// uninitialised at the beginning. omnibox->SetFocus() invokes
+// RWHI::SetActive(false) for webcontents and there is a IPC call to
+// renderer which changes 'active' state to false.
+//
+// On Mac, calling omnibox->SetFocus function doesn't invoke
+// RWHI::SetActive(false). Hence there is no IPC call to renderer and
+// 'active' state maintains old value.
+IN_PROC_BROWSER_TEST_F(ActiveRenderWidgetHostBrowserTest, FocusOmniBox) {
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  ui_test_utils::NavigateToURL(browser(), main_url);
+
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  content::RenderFrameHost* main_frame = web_contents->GetMainFrame();
+  EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
+
+  mojo::PendingAssociatedReceiver<blink::mojom::FrameWidget>
+      blink_frame_widget_receiver =
+          content::BindFakeFrameWidgetInterfaces(main_frame);
+  content::FakeFrameWidget fake_frame_widget(
+      std::move(blink_frame_widget_receiver));
+
+  // Main frame is already focused at this point and now focus URL bar.
+  OmniboxView* omnibox =
+      browser()->window()->GetLocationBar()->GetOmniboxView();
+  // Give the omnibox focus.
+  omnibox->SetFocus(/*is_user_initiated=*/true);
+
+  base::RunLoop().RunUntilIdle();
+#if defined(OS_MAC)
+  // On MacOS, calling omnibox->SetFocus function doesn't invoke
+  // RWHI::SetActive. Hence there is no IPC call to renderer and
+  // FakeFrameWidget's 'active' state remains uninitialised.
+  EXPECT_EQ(fake_frame_widget.GetActive(), base::nullopt);
+#else
+  EXPECT_EQ(fake_frame_widget.GetActive(), false);
+#endif
+}
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 499156cc..12e4faf 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -6000,6 +6000,7 @@
       "../browser/autofill/autofill_uitest.cc",
       "../browser/autofill/autofill_uitest.h",
       "../browser/browser_keyevents_browsertest.cc",
+      "../browser/chrome_render_widget_host_browsertests.cc",
       "../browser/devtools/devtools_sanity_interactive_browsertest.cc",
       "../browser/extensions/api/extension_action/browser_action_interactive_test.cc",
       "../browser/extensions/api/extension_action/page_action_interactive_test.cc",
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index ae13996e..3d48c475 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -1231,7 +1231,9 @@
 }
 
 void RenderWidgetHostImpl::SetActive(bool active) {
-  Send(new WidgetMsg_SetActive(routing_id_, active));
+  const bool is_frame_widget = owner_delegate_;
+  if (is_frame_widget)
+    blink_frame_widget_->SetActive(active);
 }
 
 void RenderWidgetHostImpl::LostMouseLock() {
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc b/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc
index 93acb8a5..dd608faa 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc
@@ -22,6 +22,9 @@
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/shell/browser/shell.h"
+#include "content/shell/common/shell_switches.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
 #include "ui/events/event_utils.h"
 
 namespace content {
@@ -299,4 +302,68 @@
   ASSERT_TRUE(ExecuteScript(wc, "noop();"));
 }
 
+class RenderWidgetHostViewAuraActiveWidgetTest : public ContentBrowserTest {
+ public:
+  RenderWidgetHostViewAuraActiveWidgetTest() = default;
+  ~RenderWidgetHostViewAuraActiveWidgetTest() override = default;
+
+  // Helper function to check |isActivated| for a given frame.
+  bool FrameIsActivated(content::RenderFrameHost* rfh) {
+    bool active = false;
+    EXPECT_TRUE(ExecuteScriptAndExtractBool(
+        rfh,
+        "window.domAutomationController.send(window.internals.isActivated())",
+        &active));
+    return active;
+  }
+
+ protected:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    ContentBrowserTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(switches::kExposeInternalsForTesting);
+  }
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    // Add content/test/data for cross_site_iframe_factory.html
+    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAuraActiveWidgetTest);
+};
+
+// In this test, toggling the value of 'active' state changes the
+// active state of frame on the renderer side.
+// SimulateActiveStateForWidget toggles the 'active' state of widget
+// over IPC.
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraActiveWidgetTest,
+                       FocusIsInactive) {
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+  content::WebContents* web_contents = shell()->web_contents();
+
+  // The main_frame_a should have a focus to start with.
+  // On renderer side, blink::FocusController's both 'active' and
+  //'focus' states are set to true.
+  content::RenderFrameHost* main_frame = web_contents->GetMainFrame();
+  EXPECT_TRUE(FrameIsActivated(main_frame));
+
+  // After changing the 'active' state of main_frame to false
+  // blink::FocusController's 'active' set to false.
+  content::SimulateActiveStateForWidget(main_frame, false);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(FrameIsActivated(main_frame));
+
+  // After changing the 'active' state of main_frame to true
+  // blink::FocusController's 'active' set to true.
+  content::SimulateActiveStateForWidget(main_frame, true);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(FrameIsActivated(main_frame));
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index f793384..7a18d871 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -551,6 +551,17 @@
     delegates_.push_back(std::make_unique<MockRenderWidgetHostDelegate>());
     parent_host_ = MockRenderWidgetHostImpl::Create(
         delegates_.back().get(), *agent_scheduling_group_host_, routing_id);
+    mojo::AssociatedRemote<blink::mojom::FrameWidgetHost>
+        parent_frame_widget_host;
+    auto parent_frame_widget_host_receiver =
+        parent_frame_widget_host
+            .BindNewEndpointAndPassDedicatedReceiverForTesting();
+    mojo::AssociatedRemote<blink::mojom::FrameWidget> parent_frame_widget;
+    auto parent_frame_widget_receiver =
+        parent_frame_widget.BindNewEndpointAndPassDedicatedReceiverForTesting();
+    parent_host_->BindFrameWidgetInterfaces(
+        std::move(parent_frame_widget_host_receiver),
+        parent_frame_widget.Unbind());
     delegates_.back()->set_widget_host(parent_host_);
     delegates_.back()->set_frame_tree(GetFrameTree());
     parent_view_ = new RenderWidgetHostViewAura(parent_host_);
@@ -560,6 +571,14 @@
                                           gfx::Rect());
     view_ = CreateView();
     widget_host_ = static_cast<MockRenderWidgetHostImpl*>(view_->host());
+    mojo::AssociatedRemote<blink::mojom::FrameWidgetHost> frame_widget_host;
+    auto frame_widget_host_receiver =
+        frame_widget_host.BindNewEndpointAndPassDedicatedReceiverForTesting();
+    mojo::AssociatedRemote<blink::mojom::FrameWidget> frame_widget;
+    auto frame_widget_receiver =
+        frame_widget.BindNewEndpointAndPassDedicatedReceiverForTesting();
+    widget_host_->BindFrameWidgetInterfaces(
+        std::move(frame_widget_host_receiver), frame_widget.Unbind());
     // Set the mouse_wheel_phase_handler_ timer timeout to 100ms.
     view_->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
         base::TimeDelta::FromMilliseconds(100));
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
index 1af999b..5c4f7ee 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -364,6 +364,15 @@
                              routing_id,
                              /*hidden=*/false,
                              std::make_unique<FrameTokenMessageQueue>()) {
+    mojo::AssociatedRemote<blink::mojom::FrameWidgetHost> frame_widget_host;
+    auto frame_widget_host_receiver =
+        frame_widget_host.BindNewEndpointAndPassDedicatedReceiverForTesting();
+    mojo::AssociatedRemote<blink::mojom::FrameWidget> frame_widget;
+    auto frame_widget_receiver =
+        frame_widget.BindNewEndpointAndPassDedicatedReceiverForTesting();
+    BindFrameWidgetInterfaces(std::move(frame_widget_host_receiver),
+                              frame_widget.Unbind());
+
     set_renderer_initialized(true);
     lastWheelEventLatencyInfo = ui::LatencyInfo();
 
@@ -489,6 +498,14 @@
     host_ = base::WrapUnique(MockRenderWidgetHostImpl::Create(
         &delegate_, *agent_scheduling_group_host_,
         process_host_->GetNextRoutingID()));
+    mojo::AssociatedRemote<blink::mojom::FrameWidgetHost> frame_widget_host;
+    auto frame_widget_host_receiver =
+        frame_widget_host.BindNewEndpointAndPassDedicatedReceiverForTesting();
+    mojo::AssociatedRemote<blink::mojom::FrameWidget> frame_widget;
+    auto frame_widget_receiver =
+        frame_widget.BindNewEndpointAndPassDedicatedReceiverForTesting();
+    host_->BindFrameWidgetInterfaces(std::move(frame_widget_host_receiver),
+                                     frame_widget.Unbind());
     host_->set_owner_delegate(&mock_owner_delegate_);
     rwhv_mac_ = new RenderWidgetHostViewMac(host_.get());
     rwhv_cocoa_.reset([rwhv_mac_->GetInProcessNSView() retain]);
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 2074bc1..7ace153 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -682,6 +682,31 @@
   return "";
 }
 
+mojo::PendingAssociatedReceiver<blink::mojom::FrameWidget>
+BindFakeFrameWidgetInterfaces(RenderFrameHost* frame) {
+  RenderWidgetHostImpl* render_widget_host_impl =
+      static_cast<RenderFrameHostImpl*>(frame)->GetRenderWidgetHost();
+
+  mojo::AssociatedRemote<blink::mojom::FrameWidgetHost> blink_frame_widget_host;
+  auto blink_frame_widget_host_receiver =
+      blink_frame_widget_host
+          .BindNewEndpointAndPassDedicatedReceiverForTesting();
+
+  mojo::AssociatedRemote<blink::mojom::FrameWidget> blink_frame_widget;
+  auto blink_frame_widget_receiver =
+      blink_frame_widget.BindNewEndpointAndPassDedicatedReceiverForTesting();
+
+  render_widget_host_impl->BindFrameWidgetInterfaces(
+      std::move(blink_frame_widget_host_receiver), blink_frame_widget.Unbind());
+
+  return blink_frame_widget_receiver;
+}
+
+void SimulateActiveStateForWidget(RenderFrameHost* frame, bool active) {
+  static_cast<RenderFrameHostImpl*>(frame)->GetRenderWidgetHost()->SetActive(
+      active);
+}
+
 void WaitForLoadStopWithoutSuccessCheck(WebContents* web_contents) {
   // In many cases, the load may have finished before we get here.  Only wait if
   // the tab still has a pending navigation.
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index 48cbc55..0ece6986 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -39,6 +39,7 @@
 #include "content/public/common/isolated_world_ids.h"
 #include "content/public/common/page_type.h"
 #include "content/public/common/untrustworthy_context_menu_params.h"
+#include "content/public/test/fake_frame_widget.h"
 #include "ipc/message_filter.h"
 #include "net/base/load_flags.h"
 #include "services/network/public/mojom/network_service.mojom.h"
@@ -350,6 +351,15 @@
 std::string ReferrerPolicyToString(
     network::mojom::ReferrerPolicy referrer_policy);
 
+// For testing, bind FakeFrameWidget to a RenderWidgetHost associated
+// with a given RenderFrameHost
+mojo::PendingAssociatedReceiver<blink::mojom::FrameWidget>
+BindFakeFrameWidgetInterfaces(RenderFrameHost* frame);
+
+// Set |active| state for a RenderWidgetHost associated with a given
+// RenderFrameHost
+void SimulateActiveStateForWidget(RenderFrameHost* frame, bool active);
+
 // Holds down modifier keys for the duration of its lifetime and releases them
 // upon destruction. This allows simulating multiple input events without
 // simulating modifier key releases in between.
diff --git a/content/public/test/fake_frame_widget.cc b/content/public/test/fake_frame_widget.cc
index b41a5d2d..969f89a 100644
--- a/content/public/test/fake_frame_widget.cc
+++ b/content/public/test/fake_frame_widget.cc
@@ -27,4 +27,12 @@
 }
 #endif
 
+base::Optional<bool> FakeFrameWidget::GetActive() const {
+  return active_;
+}
+
+void FakeFrameWidget::SetActive(bool active) {
+  active_ = active;
+}
+
 }  // namespace content
diff --git a/content/public/test/fake_frame_widget.h b/content/public/test/fake_frame_widget.h
index 369d8fac..9576585 100644
--- a/content/public/test/fake_frame_widget.h
+++ b/content/public/test/fake_frame_widget.h
@@ -29,6 +29,7 @@
   void operator=(const FakeFrameWidget&) = delete;
 
   base::i18n::TextDirection GetTextDirection() const;
+  base::Optional<bool> GetActive() const;
 
  private:
   void DragTargetDragOver(const gfx::PointF& point_in_viewport,
@@ -48,6 +49,7 @@
   void DragSourceSystemDragEnded() override {}
   void SetBackgroundOpaque(bool value) override {}
   void SetTextDirection(base::i18n::TextDirection direction) override;
+  void SetActive(bool active) override;
   void SetInheritedEffectiveTouchActionForSubFrame(
       const cc::TouchAction touch_action) override {}
   void UpdateRenderThrottlingStatusForSubFrame(
@@ -70,6 +72,7 @@
   mojo::AssociatedReceiver<blink::mojom::FrameWidget> receiver_;
   base::i18n::TextDirection text_direction_ =
       base::i18n::TextDirection::UNKNOWN_DIRECTION;
+  base::Optional<bool> active_;
 };
 
 }  // namespace content
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 17989ea..5b8818db 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -356,7 +356,6 @@
     IPC_MESSAGE_HANDLER(WidgetMsg_Close, OnClose)
     IPC_MESSAGE_HANDLER(WidgetMsg_WasHidden, OnWasHidden)
     IPC_MESSAGE_HANDLER(WidgetMsg_WasShown, OnWasShown)
-    IPC_MESSAGE_HANDLER(WidgetMsg_SetActive, OnSetActive)
     IPC_MESSAGE_HANDLER(WidgetMsg_SetBounds_ACK, OnRequestSetBoundsAck)
     IPC_MESSAGE_HANDLER(WidgetMsg_SetViewportIntersection,
                         OnSetViewportIntersection)
@@ -535,7 +534,7 @@
   return GetFrameSinkId();
 }
 
-void RenderWidget::OnSetActive(bool active) {
+void RenderWidget::SetActive(bool active) {
   if (delegate())
     delegate()->SetActiveForWidget(active);
 }
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index b49d995..376a2920 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -308,7 +308,7 @@
   viz::FrameSinkId GetFrameSinkIdAtPoint(const gfx::PointF& point,
                                          gfx::PointF* local_point);
 
-  void OnSetActive(bool active);
+  void SetActive(bool active);
 
   void UseSynchronousResizeModeForTesting(bool enable);
   void SetDeviceScaleFactorForTesting(float factor);
diff --git a/content/shell/renderer/web_test/test_runner.cc b/content/shell/renderer/web_test/test_runner.cc
index cbce82c..24204f19 100644
--- a/content/shell/renderer/web_test/test_runner.cc
+++ b/content/shell/renderer/web_test/test_runner.cc
@@ -3116,7 +3116,7 @@
     // This path simulates losing focus on the window, without moving it to
     // another window.
     if (widget->GetWebWidget()->HasFocus()) {
-      widget->OnSetActive(false);
+      widget->SetActive(false);
       widget->GetWebWidget()->SetFocus(false);
     }
     return;
@@ -3127,7 +3127,7 @@
     if (other_main_frame != main_frame) {
       RenderWidget* other_widget = other_main_frame->GetLocalRootRenderWidget();
       if (other_widget->GetWebWidget()->HasFocus()) {
-        other_widget->OnSetActive(false);
+        other_widget->SetActive(false);
         other_widget->GetWebWidget()->SetFocus(false);
       }
     }
@@ -3135,7 +3135,7 @@
 
   if (!widget->GetWebWidget()->HasFocus()) {
     widget->GetWebWidget()->SetFocus(true);
-    widget->OnSetActive(true);
+    widget->SetActive(true);
   }
 }
 
diff --git a/third_party/blink/public/mojom/page/widget.mojom b/third_party/blink/public/mojom/page/widget.mojom
index de9bc02..0774f572 100644
--- a/third_party/blink/public/mojom/page/widget.mojom
+++ b/third_party/blink/public/mojom/page/widget.mojom
@@ -61,6 +61,13 @@
   // Changes the text direction of the currently selected input field (if any).
   SetTextDirection(mojo_base.mojom.TextDirection direction);
 
+  // Activate/deactivate the Widget.
+  // Focused window is the window that receives keyboard input.
+  // The focused window is always or is always contained by the Active window.
+  // Active window is the one that contains the focused element.
+  // https://www.chromium.org/developers/design-documents/aura/focus-and-activation
+  SetActive(bool active);
+
   // Only valid for sub frame local roots.
   //
   // Sets the inherited effective touch action on an out-of-process iframe.
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
index 2206bdf4..d7014ea6 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
@@ -376,6 +376,10 @@
   widget_base_->BindWidgetCompositor(std::move(receiver));
 }
 
+void WebFrameWidgetBase::SetActive(bool active) {
+  View()->SetIsActive(active);
+}
+
 void WebFrameWidgetBase::CancelDrag() {
   // It's possible for this to be called while we're not doing a drag if
   // it's from a previous page that got unloaded.
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_base.h b/third_party/blink/renderer/core/frame/web_frame_widget_base.h
index fac24ce..eaf1f69 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_base.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_base.h
@@ -381,6 +381,7 @@
                          WebDragOperation) override;
   void DragSourceSystemDragEnded() override;
   void SetBackgroundOpaque(bool opaque) override;
+  void SetActive(bool active) override;
   // For both mainframe and childframe change the text direction of the
   // currently selected input field (if any).
   void SetTextDirection(base::i18n::TextDirection direction) override;
diff --git a/third_party/blink/renderer/core/testing/internals.cc b/third_party/blink/renderer/core/testing/internals.cc
index 0272f901..3fdb30f0 100644
--- a/third_party/blink/renderer/core/testing/internals.cc
+++ b/third_party/blink/renderer/core/testing/internals.cc
@@ -3164,6 +3164,13 @@
       .GetInterestedElement();
 }
 
+bool Internals::isActivated() {
+  if (!GetFrame())
+    return false;
+
+  return GetFrame()->GetPage()->GetFocusController().IsActive();
+}
+
 bool Internals::isInCanvasFontCache(Document* document,
                                     const String& font_string) {
   return document->GetCanvasFontCache()->IsInCache(font_string);
diff --git a/third_party/blink/renderer/core/testing/internals.h b/third_party/blink/renderer/core/testing/internals.h
index f759f13..c0dcdd3 100644
--- a/third_party/blink/renderer/core/testing/internals.h
+++ b/third_party/blink/renderer/core/testing/internals.h
@@ -500,6 +500,10 @@
 
   Element* interestedElement();
 
+  // Check if frame associated with current internals object is
+  // active or not.
+  bool isActivated();
+
   bool isInCanvasFontCache(Document*, const String&);
   unsigned canvasFontCacheMaxFonts();
 
diff --git a/third_party/blink/renderer/core/testing/internals.idl b/third_party/blink/renderer/core/testing/internals.idl
index c57453969..87e5ae0 100644
--- a/third_party/blink/renderer/core/testing/internals.idl
+++ b/third_party/blink/renderer/core/testing/internals.idl
@@ -321,6 +321,10 @@
     // attribute returns the currently interested element on the page.
     readonly attribute Element? interestedElement;
 
+    // Returns true if page associated with current internals object is
+    // active.
+    boolean isActivated();
+
     boolean isInCanvasFontCache(Document document, DOMString fontString);
     unsigned long canvasFontCacheMaxFonts();
 
diff --git a/third_party/blink/web_tests/fast/dom/Window/window-active-state.html b/third_party/blink/web_tests/fast/dom/Window/window-active-state.html
new file mode 100644
index 0000000..223735d
--- /dev/null
+++ b/third_party/blink/web_tests/fast/dom/Window/window-active-state.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<title>Check active state of windows</title>
+<script src="../../../resources/user-gesture-utils.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+  var popupWin = window.open('about:blank');
+  async_test((t) => {
+    window.addEventListener("blur", t.step_func(() => {
+      // Give a time to `popupWin` for activation.
+      setTimeout(() => {
+        assert_true(popupWin.internals.isActivated(), "Popup window should be activated");
+        assert_false(window.internals.isActivated(), "Parent window should be deactivated");
+        t.done();
+      }, 0);
+    }));
+
+    window.addEventListener("load", t.step_func(() => {
+      assert_true(window.internals.isActivated(), "Parent window should be activated");
+      focusWithUserGesture(popupWin);
+    }));
+  }, "This test passes if no crash");
+</script>