910e62b5创建于 1月15日历史提交
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/public/browser/back_forward_cache.h"

#include <string>
#include <string_view>

#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/back_forward_cache/back_forward_cache_disable.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api/messaging/message_service.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
#include "third_party/blink/public/mojom/navigation/renderer_eviction_reason.mojom-shared.h"

namespace extensions {

using testing::Values;
using ContextType = extensions::browser_test_util::ContextType;

class ExtensionBackForwardCacheBrowserTest
    : public ExtensionBrowserTest,
      public ::testing::WithParamInterface<ContextType> {
 public:
  ExtensionBackForwardCacheBrowserTest() : ExtensionBrowserTest(GetParam()) {
    feature_list_.InitWithFeaturesAndParameters(
        content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(
            {{features::kBackForwardCache, {}}}),
        content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    ExtensionBrowserTest::SetUpOnMainThread();
  }

  content::RenderFrameHost* current_main_frame_host() {
    return web_contents()->GetPrimaryMainFrame();
  }

  void RunChromeRuntimeConnectTest() {
    const Extension* extension =
        LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                          .AppendASCII("content_script"));
    ASSERT_TRUE(extension);

    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

    // 1) Navigate to A.
    content::RenderFrameHostWrapper render_frame_host_a(
        ui_test_utils::NavigateToURL(browser(), url_a));
    std::u16string expected_title = u"connected";
    content::TitleWatcher title_watcher(
        browser()->tab_strip_model()->GetActiveWebContents(), expected_title);

    std::string action = base::StringPrintf(
        R"HTML(
        var p = chrome.runtime.connect('%s');
        p.onMessage.addListener((m) => {document.title = m; });
      )HTML",
        extension->id().c_str());
    EXPECT_TRUE(ExecJs(render_frame_host_a.get(), action));

    // 2) Wait for the message port to be connected.
    EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());

    // Expect that a channel is open.
    EXPECT_EQ(1u, MessageService::Get(profile())->GetChannelCountForTest());

    // 3) Navigate to B.
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

    // Expect that `render_frame_host_a` is cached.
    EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kInBackForwardCache);

    // The channel should close.
    EXPECT_EQ(0u, MessageService::Get(profile())->GetChannelCountForTest());

    // 4) Go back to A.
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    web_contents->GetController().GoBack();
    EXPECT_TRUE(WaitForLoadStop(web_contents));
  }

  void ExpectTitleChangeSuccess(const Extension& extension, const char* title) {
    const std::string script = base::StringPrintf(R"(
          chrome.tabs.executeScript({
            code: "document.title='%s'"
          });
        )",
                                                  title);
    ExecuteScriptInBackgroundPageNoWait(extension.id(), script);

    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    std::u16string title16(base::UTF8ToUTF16(title));
    content::TitleWatcher title_watcher(web_contents, title16);
    EXPECT_EQ(title16, title_watcher.WaitAndGetTitle());
  }

  void ExpectTitleChangeFail(const Extension& extension) {
    static constexpr char kScript[] =
        R"(
          chrome.tabs.executeScript({code: "document.title='fail'"},
            () => {
              if (chrome.runtime.lastError) {
                chrome.test.sendScriptResult(
                  chrome.runtime.lastError.message);
              } else {
                chrome.test.sendScriptResult("Unexpected success");
              }
            });
        )";
    EXPECT_EQ(manifest_errors::kCannotAccessPage,
              ExecuteScriptInBackgroundPage(extension.id(), kScript));

    std::u16string title;
    ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &title));
    EXPECT_NE(u"fail", title);
  }

  content::WebContents* web_contents() const {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

 protected:
  base::HistogramTester histogram_tester_;

 private:
  base::test::ScopedFeatureList feature_list_;
};

// These tests use chrome.tabs.executeScript, so the SW versions of the tests
// must still be run with MV2. See crbug.com/332328868.
INSTANTIATE_TEST_SUITE_P(EventPage,
                         ExtensionBackForwardCacheBrowserTest,
                         Values(ContextType::kEventPage));
INSTANTIATE_TEST_SUITE_P(ServiceWorker,
                         ExtensionBackForwardCacheBrowserTest,
                         Values(ContextType::kServiceWorkerMV2));

IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest, ScriptAllowed) {
  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                                .AppendASCII("content_script")));

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  // 2) Navigate to B.
  content::RenderFrameHostWrapper render_frame_host_b(
      ui_test_utils::NavigateToURL(browser(), url_b));

  // Ensure that `render_frame_host_a` is in the cache.
  EXPECT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_NE(render_frame_host_a.get(), render_frame_host_b.get());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}

IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest, CSSAllowed) {
  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                                .AppendASCII("content_css")));

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  // 2) Navigate to B.
  content::RenderFrameHostWrapper render_frame_host_b(
      ui_test_utils::NavigateToURL(browser(), url_b));

  // Ensure that `render_frame_host_a` is in the cache.
  EXPECT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_NE(render_frame_host_a.get(), render_frame_host_b.get());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}

IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       UnloadExtensionFlushCache) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // Load the extension so we can unload it later.
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_css"));
  ASSERT_TRUE(extension);

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  // 2) Navigate to B.
  content::RenderFrameHostWrapper render_frame_host_b(
      ui_test_utils::NavigateToURL(browser(), url_b));

  // Ensure that `render_frame_host_a` is in the cache.
  EXPECT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_NE(render_frame_host_a.get(), render_frame_host_b.get());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // Now unload the extension after something is in the cache.
  UnloadExtension(extension->id());

  // Expect that `render_frame_host_a` is destroyed as it should be cleared from
  // the cache.
  EXPECT_TRUE(render_frame_host_a.WaitUntilRenderFrameDeleted());
}

IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       LoadExtensionFlushCache) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  // 2) Navigate to B.
  content::RenderFrameHostWrapper render_frame_host_b(
      ui_test_utils::NavigateToURL(browser(), url_b));

  // Ensure that `render_frame_host_a` is in the cache.
  EXPECT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_NE(render_frame_host_a.get(), render_frame_host_b.get());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // Now load the extension after something is in the cache.
  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                                .AppendASCII("content_css")));

  // Expect that `render_frame_host_a` is destroyed as it should be cleared from
  // the cache.
  EXPECT_TRUE(render_frame_host_a.WaitUntilRenderFrameDeleted());
}

// Test if the chrome.runtime.connect API is called, the page is prevented from
// entering bfcache.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ChromeRuntimeConnectUsage) {
  RunChromeRuntimeConnectTest();
}

// Test that we correctly clear the bfcache disable reasons on a same-origin
// cross document navigation for a document with an active channel, allowing
// the frame to be bfcached subsequently.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ChromeRuntimeConnectUsageInIframeWithIframeNavigation) {
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a = embedded_test_server()->GetURL("a.com", "/iframe.html");
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper primary_render_frame_host(
      ui_test_utils::NavigateToURL(browser(), url_a));
  std::u16string expected_title = u"connected";
  content::TitleWatcher title_watcher(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);

  content::RenderFrameHost* child =
      ChildFrameAt(primary_render_frame_host.get(), 0);

  std::string action = base::StringPrintf(
      R"HTML(
        var p = chrome.runtime.connect('%s');
        p.onMessage.addListener((m) => {window.top.document.title = m; });
      )HTML",
      extension->id().c_str());
  ASSERT_TRUE(ExecJs(child, action));

  // 2) Wait for the message port to be connected.
  EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());

  // Expect that a channel is open.
  EXPECT_EQ(1u, MessageService::Get(profile())->GetChannelCountForTest());

  // 3) Navigate the iframe.
  GURL iframe_url = embedded_test_server()->GetURL("a.com", "/title2.html");
  EXPECT_TRUE(NavigateToURLFromRenderer(child, iframe_url));

  EXPECT_EQ(0u, MessageService::Get(profile())->GetChannelCountForTest());

  // 4) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  // 5) Expect that A is in the back forward cache.
  EXPECT_FALSE(primary_render_frame_host.IsDestroyed());
  EXPECT_EQ(primary_render_frame_host->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}

// Test that the page can enter BFCache with an active channel created from the
// iframe.
IN_PROC_BROWSER_TEST_P(
    ExtensionBackForwardCacheBrowserTest,
    ChromeRuntimeConnectUsageInIframeWithoutIframeNavigation) {
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a = embedded_test_server()->GetURL("a.com", "/iframe.html");
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper primary_render_frame_host(
      ui_test_utils::NavigateToURL(browser(), url_a));
  std::u16string expected_title = u"connected";
  content::TitleWatcher title_watcher(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);

  content::RenderFrameHost* child =
      ChildFrameAt(primary_render_frame_host.get(), 0);

  std::string action = base::StringPrintf(
      R"JS(
        var p = chrome.runtime.connect('%s');
        p.onMessage.addListener((m) => {window.top.document.title = m; });
      )JS",
      extension->id().c_str());
  ASSERT_TRUE(ExecJs(child, action));

  // 2) Wait for the message port to be connected.
  EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());

  // Expect that a channel is open.
  EXPECT_EQ(1u, MessageService::Get(profile())->GetChannelCountForTest());

  // 3) Navigate to B, and the channel is closed.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
  EXPECT_EQ(0u, MessageService::Get(profile())->GetChannelCountForTest());

  // 4) Expect that A is in the back forward cache.
  EXPECT_FALSE(primary_render_frame_host.IsDestroyed());
  EXPECT_EQ(primary_render_frame_host->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}

// Test that the page can enter BFCache with an active channel that's created
// from the extension background with two receivers from different frames.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ChromeTabsConnectWithMultipleReceivers) {
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script_all_frames"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/iframe.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper primary_render_frame_host(
      ui_test_utils::NavigateToURL(browser(), url_a));

  // 2) Create channel from the extension background.
  static constexpr char kScript[] =
      R"JS(
      var p;
      var countConnected = 0;
      chrome.tabs.query({}, (t) => {
        p = chrome.tabs.connect(t[0].id);
        p.onMessage.addListener(
         (m) => {
          if (m == 'connected') {
            countConnected++;
            if (countConnected == 2) {
              chrome.test.sendScriptResult('connected twice');
            }
          }
        });
      });
    )JS";

  // The background should receives two "connected" messages from different
  // frames.
  EXPECT_EQ("connected twice",
            ExecuteScriptInBackgroundPage(extension->id(), kScript));
  // Even though there are two ports from the receiver end, there is still one
  // channel.
  EXPECT_EQ(1u, MessageService::Get(profile())->GetChannelCountForTest());

  // 3) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  // 4) Expect that A is in the back forward cache.
  EXPECT_EQ(primary_render_frame_host->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  EXPECT_EQ(0u, MessageService::Get(profile())->GetChannelCountForTest());
}

// Test if the chrome.runtime.sendMessage API is called, the page is allowed
// to enter the bfcache.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ChromeRuntimeSendMessageUsage) {
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  std::u16string expected_title = u"sent";
  content::TitleWatcher title_watcher(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);

  static constexpr char kAction[] =
      R"HTML(
        chrome.runtime.sendMessage('%s', 'some message',
          () => { document.title = 'sent'});
      )HTML";
  EXPECT_TRUE(ExecJs(render_frame_host_a.get(),
                     base::StringPrintf(kAction, extension->id().c_str())));

  // 2) Wait until the sendMessage has completed.
  EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());

  // Expect that no channel is open.
  EXPECT_EQ(0u, MessageService::Get(profile())->GetChannelCountForTest());

  // 3) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  // 4) Expect that A is in the back forward cache.
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 5) Ensure that the runtime.onConnect listener in the restored page still
  // works.
  static constexpr char kScript[] =
      R"HTML(
      var p;
      chrome.tabs.query({}, (t) => {
        p = chrome.tabs.connect(t[0].id);
        p.onMessage.addListener(
         (m) => {chrome.test.sendScriptResult(m)}
        );
      });
    )HTML";
  EXPECT_EQ("connected",
            ExecuteScriptInBackgroundPage(extension->id(), kScript));
}

// Test if the chrome.runtime.connect is called then disconnected, the page is
// allowed to enter the bfcache.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ChromeRuntimeConnectDisconnect) {
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));
  std::u16string expected_title = u"connected";
  auto title_watcher = std::make_unique<content::TitleWatcher>(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);

  std::string action = base::StringPrintf(
      R"HTML(
        var p = chrome.runtime.connect('%s');
        p.onMessage.addListener((m) => {document.title = m; });
      )HTML",
      extension->id().c_str());
  EXPECT_TRUE(ExecJs(render_frame_host_a.get(), action));

  // 2) Wait for the message port to be connected.
  EXPECT_EQ(expected_title, title_watcher->WaitAndGetTitle());

  expected_title = u"disconnect";
  title_watcher = std::make_unique<content::TitleWatcher>(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
  EXPECT_TRUE(ExecJs(render_frame_host_a.get(),
                     R"HTML(
        p.onDisconnect.addListener((m) => {document.title = 'disconnect';});
        p.postMessage('disconnect');
      )HTML"));

  EXPECT_EQ(expected_title, title_watcher->WaitAndGetTitle());

  // Expect that the channel is closed.
  EXPECT_EQ(0u, MessageService::Get(profile())->GetChannelCountForTest());

  // 3) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  // 4) Expect that A is in the back forward cache.
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}

// Test if the chrome.tabs.connect is called and then the page is navigated,
// the page is allowed to enter the bfcache, but if the extension tries to send
// it a message the page will be evicted.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ChromeTabsConnect) {
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));
  std::u16string expected_title = u"connected";

  static constexpr char kScript[] =
      R"HTML(
      chrome.tabs.query({}, (t) => {
        p = chrome.tabs.connect(t[0].id);
        // Save a "global" reference to the port so it can be used by the test
        // later.
        port = p;
        p.onMessage.addListener(
         (m) => {chrome.test.sendScriptResult(m)}
        );
      });
    )HTML";
  EXPECT_EQ("connected",
            ExecuteScriptInBackgroundPage(extension->id(), kScript));

  // Expect that a channel is open.
  EXPECT_EQ(1u, MessageService::Get(profile())->GetChannelCountForTest());

  // 3) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  // Expect that `render_frame_host_a` is cached, and the channel is closed.
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  EXPECT_EQ(0u, MessageService::Get(profile())->GetChannelCountForTest());
}

// Test that after caching and restoring a page, long-lived ports still work.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ChromeTabsConnectChannelWorksAfterRestore) {
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));
  std::u16string expected_title_connected = u"connected";
  content::TitleWatcher title_watcher_connected(
      browser()->tab_strip_model()->GetActiveWebContents(),
      expected_title_connected);

  EXPECT_EQ(MessageService::Get(profile())->GetChannelCountForTest(), 0u);

  std::string action = base::StringPrintf(
      R"HTML(
        var p = chrome.runtime.connect('%s');
        p.onMessage.addListener((m) => {
          document.title = m;
        });
      )HTML",
      extension->id().c_str());
  ASSERT_TRUE(ExecJs(render_frame_host_a.get(), action));

  // 2) Wait for the message port to be connected.
  EXPECT_EQ(expected_title_connected,
            title_watcher_connected.WaitAndGetTitle());

  EXPECT_EQ(MessageService::Get(profile())->GetChannelCountForTest(), 1u);

  // 3) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
  EXPECT_EQ(MessageService::Get(profile())->GetChannelCountForTest(), 0u);

  // Expect that `render_frame_host_a` is cached.
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 4) Navigate back to A.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  web_contents->GetController().GoBack();
  ASSERT_TRUE(WaitForLoadStop(web_contents));

  // Verify that `render_frame_host_a` is the active frame again.
  EXPECT_TRUE(render_frame_host_a->GetLifecycleState() ==
              content::RenderFrameHost::LifecycleState::kActive);
}

// Test if the chrome.tabs.connect is called then disconnected, the page is
// allowed to enter the bfcache.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ChromeTabsConnectDisconnect) {
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));
  std::u16string expected_title = u"connected";

  static constexpr char kScript[] =
      R"HTML(
      var p;
      chrome.tabs.query({}, (t) => {
        p = chrome.tabs.connect(t[0].id);
        p.onMessage.addListener(
         (m) => {chrome.test.sendScriptResult(m)}
        );
      });
    )HTML";
  EXPECT_EQ("connected",
            ExecuteScriptInBackgroundPage(extension->id(), kScript));

  static constexpr char kDisconnectScript[] =
      R"HTML(
      p.postMessage('disconnect');
      p.onDisconnect.addListener(() => {
        chrome.test.sendScriptResult('disconnect')
      });
    )HTML";
  EXPECT_EQ("disconnect",
            ExecuteScriptInBackgroundPage(extension->id(), kDisconnectScript));

  // 3) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  // 4) Expect that A is in the back forward cache.
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}

// Test that the extension background receives `disconnect` event if the
// channel is closed after the page enters BFCache.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ExtensionBackgroundOnDisconnectEvent) {
  const Extension* extension = LoadExtension(
      test_data_dir_.AppendASCII("back_forward_cache")
          .AppendASCII("content_script_with_background_disconnect_listener"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper rfh(
      ui_test_utils::NavigateToURL(browser(), url_a));
  std::u16string expected_title = u"connected";
  content::TitleWatcher title_watcher(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
  std::string connectScript = base::StringPrintf(
      R"JS(
        var p = chrome.runtime.connect('%s');
        p.onMessage.addListener((m) => {document.title = m; });
      )JS",
      extension->id().c_str());
  ASSERT_TRUE(ExecJs(rfh.get(), connectScript));

  // 2) Wait for the message port to be connected.
  ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());

  // Expect that a channel is open.
  EXPECT_EQ(1u, MessageService::Get(profile())->GetChannelCountForTest());

  // 3) Navigate to B, and the channel is closed.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
  EXPECT_EQ(0u, MessageService::Get(profile())->GetChannelCountForTest());

  // 4) Expect that A is in the back forward cache.
  ASSERT_FALSE(rfh.IsDestroyed());
  EXPECT_EQ(rfh->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 5) Expect that the `disconnect` event is dispatched to the background.
  constexpr char kCheckDisconnectCountScript[] =
      R"JS(chrome.test.sendScriptResult(disconnectCount))JS";
  EXPECT_EQ(1, ExecuteScriptInBackgroundPage(extension->id(),
                                             kCheckDisconnectCountScript));
}

// Tests sending a message to all frames does not send it to back-forward
// cached frames.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       MessageSentToAllFramesDoesNotSendToBackForwardCache) {
  const Extension* extension = extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("background_page"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title2.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  // 2) Navigate to B.
  content::RenderFrameHostWrapper render_frame_host_b(
      ui_test_utils::NavigateToURL(browser(), url_b));

  // Ensure that `render_frame_host_a` is in the cache.
  ASSERT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  std::u16string expected_title = u"foo";
  auto title_watcher = std::make_unique<content::TitleWatcher>(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);

  static constexpr char kScript[] =
      R"HTML(
      chrome.tabs.executeScript({allFrames: true, code: "document.title='foo'"})
    )HTML";
  ASSERT_TRUE(ExecuteScriptInBackgroundPageNoWait(extension->id(), kScript));

  EXPECT_EQ(expected_title, title_watcher->WaitAndGetTitle());

  // `render_frame_host_a` should still be in the cache.
  ASSERT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // Expect the original title when going back to A.
  expected_title = u"Title Of Awesomeness";
  title_watcher = std::make_unique<content::TitleWatcher>(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
  // Go back to A.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  web_contents->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents));

  EXPECT_EQ(expected_title, title_watcher->WaitAndGetTitle());

  // `render_frame_host_b` should still be in the cache.
  ASSERT_FALSE(render_frame_host_b.IsDestroyed());
  EXPECT_EQ(render_frame_host_b->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // Now go forward to B, and expect that it is what was set before it
  // went into the back forward cache.
  expected_title = u"foo";
  title_watcher = std::make_unique<content::TitleWatcher>(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
  web_contents->GetController().GoForward();
  EXPECT_TRUE(WaitForLoadStop(web_contents));

  EXPECT_EQ(expected_title, title_watcher->WaitAndGetTitle());
}

// Tests sending a message to specific frame that is in the back forward cache
// fails.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       MessageSentToCachedIdFails) {
  const Extension* extension = extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("background_page"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/iframe_blank.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  content::RenderFrameHostWrapper iframe(
      ChildFrameAt(render_frame_host_a.get(), 0));
  ASSERT_TRUE(iframe.get());

  // Cache the iframe's frame tree node id to send it a message later.
  content::FrameTreeNodeId iframe_frame_tree_node_id =
      iframe->GetFrameTreeNodeId();

  // 2) Navigate to B.
  content::RenderFrameHostWrapper render_frame_host_b(
      ui_test_utils::NavigateToURL(browser(), url_b));

  // Ensure that `render_frame_host_a` is in the cache.
  EXPECT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_NE(render_frame_host_a.get(), render_frame_host_b.get());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  std::u16string expected_title = u"foo";
  auto title_watcher = std::make_unique<content::TitleWatcher>(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);

  static constexpr char kScript[] =
      R"HTML(
        chrome.tabs.executeScript({frameId: %d,
                                   code: "document.title='foo'",
                                   matchAboutBlank: true
                                  }, (e) => {
          chrome.test.sendScriptResult(chrome.runtime.lastError ? 'false'
        : 'true')});
      )HTML";
  EXPECT_EQ("false", ExecuteScriptInBackgroundPage(
                         extension->id(),
                         base::StringPrintf(
                             kScript, iframe_frame_tree_node_id.value())));
  // Go back to A.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  web_contents->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents));

  // Re-execute the script.
  EXPECT_EQ("true", ExecuteScriptInBackgroundPage(
                        extension->id(),
                        base::StringPrintf(kScript,
                                           iframe_frame_tree_node_id.value())));
}

// Test that running extensions message dispatching via a ScriptContext::ForEach
// for back forward cached pages causes eviction of that RenderFrameHost.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       StorageCallbackEvicts) {
  const Extension* extension = extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script_storage"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html"));

  // 1) Navigate to A and wait until the extension's content script has
  // executed.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  // 2) Navigate to B. Ensure that |render_frame_host_a| is in back/forward
  // cache.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  // Validate that the eviction due to JavaScript execution has not happened.
  EXPECT_EQ(0, histogram_tester_.GetBucketCount(
                   "BackForwardCache.Eviction.Renderer",
                   blink::mojom::RendererEvictionReason::kJavaScriptExecution));

  // 3) Navigate back to A and make sure that the callback is called after
  // restore.
  ASSERT_TRUE(HistoryGoBack(web_contents()));
  // Check that the page was cached.
  ASSERT_EQ(render_frame_host_a.get(), web_contents()->GetPrimaryMainFrame());

  // Wait for the content script to run.
  content::DOMMessageQueue dom_message_queue(web_contents());
  std::string dom_message;
  ASSERT_TRUE(dom_message_queue.WaitForMessage(&dom_message));
  ASSERT_EQ("\"event handler ran\"", dom_message);

  // Verify that the callback was called.
  EXPECT_EQ("called", EvalJs(render_frame_host_a.get(),
                             "document.getElementById('callback').value;"));
}

// Test that ensures the origin restriction declared on the extension
// manifest.json is properly respected even when BFCache is involved.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest, TabsOrigin) {
  scoped_refptr<const Extension> extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("correct_origin"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  ExpectTitleChangeSuccess(*extension, "first nav");

  // 2) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  // Ensure that `render_frame_host_a` is in the cache.
  EXPECT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  ExpectTitleChangeFail(*extension);

  // 3) Go back to A.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  web_contents->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents));

  std::u16string title;
  ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &title));
  ASSERT_EQ(title, u"first nav");
  ExpectTitleChangeSuccess(*extension, "restore nav");
}

// Test that ensures the content scripts only execute once on a back/forward
// cached page.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ContentScriptsRunOnlyOnce) {
  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                                .AppendASCII("content_script_stages")));

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  std::u16string expected_title = u"document_idle";
  content::TitleWatcher title_watcher(
      browser()->tab_strip_model()->GetActiveWebContents(), expected_title);

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));
  EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());

  // Verify that the content scripts have been run (the 'stage' element
  // is created by the content script running at 'document_start" and
  // populated whenever the content script run at 'document_start',
  // 'document_end', or 'document_idle').
  EXPECT_EQ("document_start/document_end/document_idle/page_show/",
            EvalJs(render_frame_host_a.get(),
                   "document.getElementById('stage').value;"));

  // 2) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  // Ensure that `render_frame_host_a` is in the cache.
  EXPECT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // 3) Go back to A.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  web_contents->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents));

  // Verify that the content scripts have not run again and that the
  // 'stage' element has the appended a page_hide/page_show to its list.
  EXPECT_EQ(
      "document_start/document_end/document_idle/page_show/page_hide/"
      "page_show/",
      EvalJs(render_frame_host_a.get(),
             "document.getElementById('stage').value;"));
}

// Test that an activeTab permission temporarily granted to an extension for a
// page does not revive when the BFCache entry is restored.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheBrowserTest,
                       ActiveTabPermissionRevoked) {
  scoped_refptr<const Extension> extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("active_tab"));
  ASSERT_TRUE(extension);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  // Grant the activeTab permission.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  ExtensionActionRunner::GetForWebContents(web_contents)
      ->RunAction(extension.get(), /* grant_tab_permissions=*/true);

  ExpectTitleChangeSuccess(*extension, "changed_title");

  // 2) Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  // Ensure that `render_frame_host_a` is in the cache.
  EXPECT_FALSE(render_frame_host_a.IsDestroyed());
  EXPECT_EQ(render_frame_host_a->GetLifecycleState(),
            content::RenderFrameHost::LifecycleState::kInBackForwardCache);

  // Extension should no longer be able to change title, since the permission
  // should be revoked with a cross-site navigation.
  ExpectTitleChangeFail(*extension);

  // 3) Go back to A.
  web_contents->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents));

  // Extension should no longer be able to change title, since the permission
  // should not revive with BFCache navigation to a.com.
  ExpectTitleChangeFail(*extension);
}

class ExtensionBackForwardCacheWithPrerenderBrowserTest
    : public ExtensionBackForwardCacheBrowserTest {
 public:
  ExtensionBackForwardCacheWithPrerenderBrowserTest()
      : prerender_helper_(base::BindRepeating(
            &ExtensionBackForwardCacheBrowserTest::web_contents,
            base::Unretained(this))) {}

  void SetUp() override {
    prerender_helper_.RegisterServerRequestMonitor(embedded_test_server());
    ExtensionBackForwardCacheBrowserTest::SetUp();
  }

  content::test::PrerenderTestHelper& prerender_helper() {
    return prerender_helper_;
  }

 private:
  content::test::PrerenderTestHelper prerender_helper_;
};

INSTANTIATE_TEST_SUITE_P(EventPage,
                         ExtensionBackForwardCacheWithPrerenderBrowserTest,
                         Values(ContextType::kEventPage));
INSTANTIATE_TEST_SUITE_P(ServiceWorker,
                         ExtensionBackForwardCacheWithPrerenderBrowserTest,
                         Values(ContextType::kServiceWorker));

// Test the extension message port created during prerendering won't be closed
// after the prerendered page is activated.
IN_PROC_BROWSER_TEST_P(ExtensionBackForwardCacheWithPrerenderBrowserTest,
                       PortIsStillOpenAfterPrerenderAndActivate) {
  // This extension will automatically create a port from the content script.
  // It's only registers on title2.html, the prerendered page from this test.
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("back_forward_cache")
                        .AppendASCII("content_script_auto_connect"));
  ASSERT_TRUE(extension);
  ASSERT_TRUE(embedded_test_server()->Start());
  base::HistogramTester histogram_tester;
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1) Navigate to A.
  content::RenderFrameHostWrapper render_frame_host_a(
      ui_test_utils::NavigateToURL(browser(), url_a));

  // 2) Start a prerender.
  GURL prerender_url = embedded_test_server()->GetURL("a.com", "/title2.html");
  prerender_helper().AddPrerender(prerender_url);

  // 3) Activate.
  content::TestActivationManager activation_manager(web_contents(),
                                                    prerender_url);
  ASSERT_TRUE(
      content::ExecJs(web_contents()->GetPrimaryMainFrame(),
                      content::JsReplace("location = $1", prerender_url)));
  activation_manager.WaitForNavigationFinished();
  EXPECT_TRUE(activation_manager.was_activated());

  histogram_tester.ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      /* PrerenderFinalStatus::kActivated */ 0, 1);

  // The channel associated to the prerendered page should be open.
  EXPECT_EQ(1u, MessageService::Get(profile())->GetChannelCountForTest());
}

}  // namespace extensions