// Copyright 2012 The Chromium Authors
// 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/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/public/test/url_loader_monitor.h"
#include "net/base/features.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/resource_request.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using testing::HasSubstr;
using testing::ContainsRegex;

namespace {
const char kTestHtml[] = "/viewsource/test.html";
const char kTestNavigationHtml[] = "/viewsource/navigation.html";
const char kTestMedia[] = "/media/pink_noise_140ms.wav";
}

class ViewSourceTest : public InProcessBrowserTest {
 public:
  ViewSourceTest() {
    feature_list_.InitAndDisableFeature(features::kHttpsUpgrades);
  }

  ViewSourceTest(const ViewSourceTest&) = delete;
  ViewSourceTest& operator=(const ViewSourceTest&) = delete;

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

  base::test::ScopedFeatureList* feature_list() { return &feature_list_; }

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

class ViewSourcePermissionsPolicyTest : public ViewSourceTest {
 public:
  ViewSourcePermissionsPolicyTest() : ViewSourceTest() {}

  ViewSourcePermissionsPolicyTest(const ViewSourcePermissionsPolicyTest&) =
      delete;
  ViewSourcePermissionsPolicyTest& operator=(
      const ViewSourcePermissionsPolicyTest&) = delete;

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

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
  }
};

// This test renders a page in view-source and then checks to see if the title
// set in the html was set successfully (it shouldn't because we rendered the
// page in view source).
// Flaky; see http://crbug.com/41319283.
IN_PROC_BROWSER_TEST_F(ViewSourceTest, DoesBrowserRenderInViewSource) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // First we navigate to our view-source test page.
  GURL url(content::kViewSourceScheme + std::string(":") +
           embedded_test_server()->GetURL(kTestHtml).spec());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  // Check that the title didn't get set.  It should not be there (because we
  // are in view-source mode).
  EXPECT_NE(u"foo",
            browser()->tab_strip_model()->GetActiveWebContents()->GetTitle());
}

// This test renders a page normally and then renders the same page in
// view-source mode. This is done since we had a problem at one point during
// implementation of the view-source: prefix being consumed (removed from the
// URL) if the URL was not changed (apart from adding the view-source prefix)
IN_PROC_BROWSER_TEST_F(ViewSourceTest, DoesBrowserConsumeViewSourcePrefix) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // First we navigate to google.html.
  GURL url(embedded_test_server()->GetURL(kTestHtml));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  // Then we navigate to the same url but with the "view-source:" prefix.
  GURL url_viewsource(content::kViewSourceScheme + std::string(":") +
                      url.spec());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_viewsource));

  // The URL should still be prefixed with "view-source:".
  EXPECT_EQ(url_viewsource.spec(),
            browser()->tab_strip_model()->GetActiveWebContents()->
                GetURL().spec());
}

// Make sure that when looking at the actual page, we can select "View Source"
// from the menu.
IN_PROC_BROWSER_TEST_F(ViewSourceTest, ViewSourceInMenuEnabledOnANormalPage) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url(embedded_test_server()->GetURL(kTestHtml));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  EXPECT_TRUE(chrome::CanViewSource(browser()));
}

// For page that is media content, make sure that we cannot select "View Source"
// See http://crbug.com/40573724
IN_PROC_BROWSER_TEST_F(ViewSourceTest, ViewSourceInMenuDisabledOnAMediaPage) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url(embedded_test_server()->GetURL(kTestMedia));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  const char* mime_type = browser()->tab_strip_model()->GetActiveWebContents()->
      GetContentsMimeType().c_str();

  EXPECT_STREQ("audio/wav", mime_type);
  EXPECT_FALSE(chrome::CanViewSource(browser()));
}

// Make sure that when looking at the page source, we can't select "View Source"
// from the menu.
IN_PROC_BROWSER_TEST_F(ViewSourceTest,
                       ViewSourceInMenuDisabledWhileViewingSource) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_viewsource(content::kViewSourceScheme + std::string(":") +
                      embedded_test_server()->GetURL(kTestHtml).spec());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_viewsource));

  EXPECT_FALSE(chrome::CanViewSource(browser()));
}

// Tests that reload initiated by the script on the view-source page leaves
// the page in view-source mode.
// Times out on Mac, Windows, ChromeOS Linux: crbug.com/40955430
IN_PROC_BROWSER_TEST_F(ViewSourceTest, DISABLED_TestViewSourceReload) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_viewsource(content::kViewSourceScheme + std::string(":") +
                      embedded_test_server()->GetURL(kTestHtml).spec());

  content::LoadStopObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_viewsource));
  observer.Wait();

  ASSERT_TRUE(content::ExecJs(browser()->tab_strip_model()->GetWebContentsAt(0),
                              "window.location.reload();"));

  content::LoadStopObserver observer2(
      browser()->tab_strip_model()->GetWebContentsAt(0));
  observer2.Wait();
  ASSERT_TRUE(browser()->tab_strip_model()->GetWebContentsAt(0)->
                  GetController().GetActiveEntry()->IsViewSourceMode());
}

// This test ensures that view-source session history navigations work
// correctly when switching processes. See https://crbug.com/40440793.
IN_PROC_BROWSER_TEST_F(ViewSourceTest,
                       ViewSourceCrossProcessAndBack) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url_viewsource(content::kViewSourceScheme + std::string(":") +
                      embedded_test_server()->GetURL(kTestHtml).spec());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_viewsource));
  EXPECT_FALSE(chrome::CanViewSource(browser()));
  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  // Open another tab to the same origin, so the process is kept alive while
  // the original tab is navigated cross-process. This is required for the
  // original bug to reproduce.
  {
    GURL url = embedded_test_server()->GetURL("a.com", "/title1.html");
    ui_test_utils::UrlLoadObserver load_complete(url);
    EXPECT_TRUE(
        content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
                        "window.open('" + url.spec() + "');"));
    load_complete.Wait();
    EXPECT_EQ(2, browser()->tab_strip_model()->count());
  }

  // Switch back to the first tab and navigate it cross-process.
  browser()->tab_strip_model()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("b.com", "/title1.html")));
  EXPECT_TRUE(chrome::CanViewSource(browser()));

  // Navigate back in session history to ensure view-source mode is still
  // active.
  {
    ui_test_utils::UrlLoadObserver load_complete(url_viewsource);
    chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
    load_complete.Wait();
  }

  // Check whether the page is in view-source mode or not by checking if an
  // expected element on the page exists or not. In view-source mode it
  // should not be found.
  EXPECT_EQ(true, content::EvalJs(
                      browser()->tab_strip_model()->GetActiveWebContents(),
                      "document.getElementById('bar') === null;"));
  EXPECT_FALSE(chrome::CanViewSource(browser()));
}

// Tests that view-source mode of b.com subframe won't commit in an a.com (main
// frame) process.  This is a regression test for https://crbug.com/40542846.
IN_PROC_BROWSER_TEST_F(ViewSourceTest, CrossSiteSubframe) {
  // Navigate to a page with a cross-site frame.
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL main_url(
      embedded_test_server()->GetURL("a.com", "/iframe_cross_site.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  // Grab the original frames.
  content::WebContents* original_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::RenderFrameHost* original_main_frame =
      original_contents->GetPrimaryMainFrame();
  content::RenderFrameHost* original_child_frame =
      ChildFrameAt(original_main_frame, 0);
  ASSERT_TRUE(original_child_frame);

  // Do a sanity check that in this particular test page the main frame and the
  // subframe are cross-site.
  EXPECT_NE(
      original_main_frame->GetLastCommittedURL().DeprecatedGetOriginAsURL(),
      original_child_frame->GetLastCommittedURL().DeprecatedGetOriginAsURL());
  if (content::AreAllSitesIsolatedForTesting()) {
    EXPECT_NE(original_main_frame->GetSiteInstance(),
              original_child_frame->GetSiteInstance());
    EXPECT_NE(original_main_frame->GetProcess()->GetDeprecatedID(),
              original_child_frame->GetProcess()->GetDeprecatedID());
  }

  // Open view-source mode tab for the subframe.  This tries to mimic the
  // behavior of RenderViewContextMenu::ExecuteCommand when it handles
  // IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE.
  content::WebContentsAddedObserver view_source_contents_observer;
  original_child_frame->ViewSource();

  // Grab the view-source frame and wait for load stop.
  content::WebContents* view_source_contents =
      view_source_contents_observer.GetWebContents();
  content::RenderFrameHost* view_source_frame =
      view_source_contents->GetPrimaryMainFrame();
  EXPECT_TRUE(WaitForLoadStop(view_source_contents));

  // Verify that the last committed URL is the same in the original and the
  // view-source frames.
  EXPECT_EQ(original_child_frame->GetLastCommittedURL(),
            view_source_frame->GetLastCommittedURL());

  // Verify that the original main frame and the view-source subframe are in a
  // different process (e.g. if the main frame was malicious and the subframe
  // was an isolated origin, then the malicious frame shouldn't be able to see
  // the contents of the isolated document).  See https://crbug.com/40542846.
  EXPECT_NE(original_main_frame->GetSiteInstance(),
            view_source_frame->GetSiteInstance());

  // Verify that the original subframe and the view-source subframe are in a
  // different process - see https://crbug.com/40509794.
  EXPECT_NE(original_child_frame->GetSiteInstance(),
            view_source_frame->GetSiteInstance());

  // Verify the contents of the view-source tab (should match title1.html).
  std::string view_source_extraction_script = R"(
      output = "";
      document.querySelectorAll(".line-content").forEach(function(elem) {
          output += elem.innerText;
      });
      output; )";
  EXPECT_EQ(
      "<html><head></head><body>This page has no title.</body></html>",
      content::EvalJs(view_source_contents, view_source_extraction_script));

  // Verify the title is derived from the subframe URL.
  GURL original_url = original_child_frame->GetLastCommittedURL();
  std::string title = base::UTF16ToUTF8(view_source_contents->GetTitle());
  EXPECT_THAT(title, HasSubstr(content::kViewSourceScheme));
  EXPECT_THAT(title, HasSubstr(original_url.GetHost()));
  EXPECT_THAT(title, HasSubstr(original_url.GetPort()));
  EXPECT_THAT(title, HasSubstr(original_url.GetPath()));
}

// Tests that "View Source" works fine for pages shown via HTTP POST.
// This is a regression test for https://crbug.com/40432314.
IN_PROC_BROWSER_TEST_F(ViewSourceTest, HttpPostInMainframe) {
  // Navigate to a page with a form.
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL form_url(embedded_test_server()->GetURL(
      "a.com", "/form_that_posts_to_echoall.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), form_url));
  content::WebContents* original_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::RenderFrameHost* original_main_frame =
      original_contents->GetPrimaryMainFrame();

  // Submit the form and verify that we arrived at the expected location.
  content::TestNavigationObserver form_post_observer(original_contents, 1);
  EXPECT_TRUE(
      ExecJs(original_main_frame, "document.getElementById('form').submit();"));
  form_post_observer.Wait();
  GURL target_url(embedded_test_server()->GetURL("a.com", "/echoall"));

  content::RenderFrameHost* current_main_frame =
      original_contents->GetPrimaryMainFrame();
  if (content::CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
    // When ProactivelySwapBrowsingInstance or RenderDocument is enabled on
    // same-site main frame navigations, the form submission above will result
    // in a change of RFH.
    EXPECT_NE(current_main_frame, original_main_frame);
  } else {
    EXPECT_EQ(current_main_frame, original_main_frame);
  }
  EXPECT_EQ(target_url, current_main_frame->GetLastCommittedURL());

  // Extract the response nonce.
  std::string response_nonce_extraction_script =
      "document.getElementById('response-nonce').innerText;";
  std::string response_nonce =
      EvalJs(current_main_frame, response_nonce_extraction_script)
          .ExtractString();

  // Open view-source mode tab for the main frame.  This tries to mimic the
  // behavior of RenderViewContextMenu::ExecuteCommand when it handles
  // IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE.
  content::WebContentsAddedObserver view_source_contents_observer;
  current_main_frame->ViewSource();
  content::WebContents* view_source_contents =
      view_source_contents_observer.GetWebContents();
  EXPECT_TRUE(WaitForLoadStop(view_source_contents));

  // Verify contents of the view-source tab.  In particular:
  // 1) the sources should contain the POST data
  // 2) the sources should contain the original response-nonce
  //    (i.e. no new network request should be made - the data should be
  //    retrieved from the cache)
  std::string view_source_extraction_script = R"(
      output = "";
      document.querySelectorAll(".line-content").forEach(function(elem) {
          output += elem.innerText;
      });
      output; )";
  std::string source_text =
      content::EvalJs(view_source_contents, view_source_extraction_script)
          .ExtractString();
  EXPECT_THAT(source_text,
              HasSubstr("<h1>Request Body:</h1><pre>text=value</pre>"));
  EXPECT_THAT(source_text,
              HasSubstr("<pre id='request-headers'>POST /echoall HTTP"));
  EXPECT_THAT(source_text,
              ContainsRegex("Request Headers:.*Referer: " + form_url.spec()));
  EXPECT_THAT(
      source_text,
      ContainsRegex(
          "Request Headers:.*Content-Type: application/x-www-form-urlencoded"));
  EXPECT_THAT(source_text, HasSubstr("<h1>Response nonce:</h1>"
                                     "<pre id='response-nonce'>" +
                                     response_nonce + "</pre>"));
  EXPECT_THAT(source_text,
              HasSubstr("<title>EmbeddedTestServer - EchoAll</title>"));

  // Verify that the original contents and the view-source contents are in a
  // different process - see https://crbug.com/40509794.
  EXPECT_NE(current_main_frame->GetSiteInstance(),
            view_source_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Verify the title of view-source is derived from the URL (not from the title
  // of the original contents).
  std::string title = base::UTF16ToUTF8(view_source_contents->GetTitle());
  EXPECT_EQ("EmbeddedTestServer - EchoAll",
            base::UTF16ToUTF8(original_contents->GetTitle()));
  EXPECT_THAT(title, Not(HasSubstr("EmbeddedTestServer - EchoAll")));
  GURL original_url = current_main_frame->GetLastCommittedURL();
  EXPECT_THAT(title, HasSubstr(content::kViewSourceScheme));
  EXPECT_THAT(title, HasSubstr(original_url.GetHost()));
  EXPECT_THAT(title, HasSubstr(original_url.GetPort()));
  EXPECT_THAT(title, HasSubstr(original_url.GetPath()));
}

// Test the case where ViewSource() is called on a top-level RenderFrameHost
// that has never had a commit, so has an empty IsolationInfo. For ViewSource()
// to do anything, the NavigationController for the tab must have a
// LastCommittedEntry(). This sounds like a contradiction of requirements, but
// can happen when a tab is cloned, and possibly other cases as well, like
// session restore.
//
// The main concern here is that the source RenderFrameHost has an empty
// IsolationInfo, and accessing it would DCHECK, so this path should mint a new
// one.
IN_PROC_BROWSER_TEST_F(ViewSourceTest,
                       ViewSourceWithRenderFrameHostWithoutCommit) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Navigate to a URL, it doesn't matter which, just need the tab to have a
  // committed entry other than about:blank or the NTP.
  GURL url(embedded_test_server()->GetURL(kTestHtml));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  // Duplicate the tab. The newly created tab should be active.
  chrome::DuplicateTab(browser());
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());

  // Check preconditions.
  EXPECT_TRUE(browser()
                  ->tab_strip_model()
                  ->GetActiveWebContents()
                  ->GetController()
                  .GetLastCommittedEntry());
  EXPECT_EQ(GURL(), browser()
                        ->tab_strip_model()
                        ->GetActiveWebContents()
                        ->GetPrimaryMainFrame()
                        ->GetLastCommittedURL());

  // Open a view source tab, and watch for its main network request.
  content::URLLoaderMonitor loader_monitor({url});
  content::WebContentsAddedObserver view_source_contents_observer;
  browser()
      ->tab_strip_model()
      ->GetActiveWebContents()
      ->GetPrimaryMainFrame()
      ->ViewSource();
  content::WebContents* view_source_contents =
      view_source_contents_observer.GetWebContents();
  EXPECT_TRUE(WaitForLoadStop(view_source_contents));
  GURL view_source_url(content::kViewSourceScheme + std::string(":") +
                       url.spec());
  EXPECT_EQ(view_source_url, view_source_contents->GetLastCommittedURL());
  // Make sure that the navigation type reported is "back_forward" on the
  // duplicated tab.
  EXPECT_EQ(
      "back_forward",
      content::EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
                      "performance.getEntriesByType('navigation')[0].type"));

  // Verify the request for the view-source tab had the correct IsolationInfo.
  std::optional<network::ResourceRequest> request =
      loader_monitor.GetRequestInfo(url);
  ASSERT_TRUE(request);
  ASSERT_TRUE(request->trusted_params);
  url::Origin origin = url::Origin::Create(url);
  EXPECT_TRUE(request->trusted_params->isolation_info.IsEqualForTesting(
      net::IsolationInfo::Create(net::IsolationInfo::RequestType::kMainFrame,
                                 origin, origin,
                                 net::SiteForCookies::FromOrigin(origin))));
}

class ViewSourceWithSplitCacheTest
    : public ViewSourceTest,
      public ::testing::WithParamInterface<bool> {
 public:
  void SetUp() override {
    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;

    bool split_cache_by_network_isolation_key = GetParam();
    if (split_cache_by_network_isolation_key) {
      enabled_features.push_back(
          net::features::kSplitCacheByNetworkIsolationKey);
    } else {
      disabled_features.push_back(
          net::features::kSplitCacheByNetworkIsolationKey);
    }
    disabled_features.push_back(features::kHttpsUpgrades);
    feature_list()->Reset();
    feature_list()->InitWithFeatures(enabled_features, disabled_features);

    ViewSourceTest::SetUp();
  }
};

// Tests that "View Source" works fine for *subframes* shown via HTTP POST.
// This is a regression test for https://crbug.com/41349757.
IN_PROC_BROWSER_TEST_P(ViewSourceWithSplitCacheTest, HttpPostInSubframe) {
  // Navigate to a page with multiple frames.
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL main_url(
      embedded_test_server()->GetURL("a.com", "/iframe_cross_site.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  content::WebContents* original_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  // Navigate a child frame to a document with a form.
  GURL form_url(embedded_test_server()->GetURL(
      "b.com", "/form_that_posts_to_echoall.html"));
  EXPECT_TRUE(
      content::NavigateIframeToURL(original_contents, "frame1", form_url));
  content::RenderFrameHost* original_child_frame =
      ChildFrameAt(original_contents, 0);
  ASSERT_TRUE(original_child_frame);

  // Submit the form and verify that we arrived at the expected location.
  content::TestNavigationObserver form_post_observer(original_contents, 1);
  EXPECT_TRUE(ExecJs(original_child_frame,
                     "document.getElementById('form').submit();"));
  form_post_observer.Wait();
  original_child_frame = ChildFrameAt(original_contents, 0);

  GURL target_url(embedded_test_server()->GetURL("b.com", "/echoall"));
  EXPECT_EQ(target_url, original_child_frame->GetLastCommittedURL());

  // Extract the response nonce.
  std::string response_nonce_extraction_script =
      "document.getElementById('response-nonce').innerText;";
  std::string response_nonce =
      EvalJs(original_child_frame, response_nonce_extraction_script)
          .ExtractString();

  // Open view-source mode tab for the subframe.  This tries to mimic the
  // behavior of RenderViewContextMenu::ExecuteCommand when it handles
  // IDC_CONTENT_CONTEXT_VIEWFRAMESOURCE.
  content::WebContentsAddedObserver view_source_contents_observer;
  original_child_frame->ViewSource();
  content::WebContents* view_source_contents =
      view_source_contents_observer.GetWebContents();
  EXPECT_TRUE(WaitForLoadStop(view_source_contents));

  // Verify contents of the view-source tab.  In particular:
  // 1) the sources should contain the POST data
  // 2) the sources should contain the original response-nonce
  //    (i.e. no new network request should be made - the data should be
  //    retrieved from the cache)
  std::string view_source_extraction_script = R"(
      output = "";
      document.querySelectorAll(".line-content").forEach(function(elem) {
          output += elem.innerText;
      });
      output; )";
  std::string source_text =
      content::EvalJs(view_source_contents, view_source_extraction_script)
          .ExtractString();
  EXPECT_THAT(source_text,
              HasSubstr("<h1>Request Body:</h1><pre>text=value</pre>"));
  EXPECT_THAT(source_text,
              HasSubstr("<pre id='request-headers'>POST /echoall HTTP"));
  EXPECT_THAT(source_text,
              ContainsRegex("Request Headers:.*Referer: " + form_url.spec()));
  EXPECT_THAT(
      source_text,
      ContainsRegex(
          "Request Headers:.*Content-Type: application/x-www-form-urlencoded"));
  EXPECT_THAT(source_text, HasSubstr("<h1>Response nonce:</h1>"
                                     "<pre id='response-nonce'>" +
                                     response_nonce + "</pre>"));
  EXPECT_THAT(source_text,
              HasSubstr("<title>EmbeddedTestServer - EchoAll</title>"));

  // Verify that view-source opens in a new process -
  // https://crbug.com/40509794.
  EXPECT_NE(original_child_frame->GetSiteInstance(),
            view_source_contents->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_NE(original_contents->GetSiteInstance(),
            view_source_contents->GetPrimaryMainFrame()->GetSiteInstance());

  // Verify the title is derived from the URL.
  GURL original_url = original_child_frame->GetLastCommittedURL();
  std::string title = base::UTF16ToUTF8(view_source_contents->GetTitle());
  EXPECT_THAT(title, HasSubstr(content::kViewSourceScheme));
  EXPECT_THAT(title, HasSubstr(original_url.GetHost()));
  EXPECT_THAT(title, HasSubstr(original_url.GetPort()));
  EXPECT_THAT(title, HasSubstr(original_url.GetPath()));
}

INSTANTIATE_TEST_SUITE_P(
    All,
    ViewSourceWithSplitCacheTest,
    testing::Bool());

using ViewSourceWithSplitCacheEnabledTest = ViewSourceWithSplitCacheTest;

// Tests that the network isolation key for the view-source request is reused
// in the back-navigation request to the view-source page.
//
// The test runs the following steps:
// 1. Navigate to page a.com/title1.html
// 2. Create a cross-site subframe b.com/title1.html
// 3. View-source the subframe
// 4. Navigate the view-source page to a c.com/title1.html
// 5. Navigate back to the view-source page
//
// In the end, the test checks whether the back navigation request resource
// exists in the cache. |exists_in_cache == true| implies the top_frame_origin
// of the network isolation key is a.com (reused).
IN_PROC_BROWSER_TEST_P(ViewSourceWithSplitCacheEnabledTest,
                       NetworkIsolationKeyReusedForBackNavigation) {
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());

  // 1. Navigate to page a.com/title1.html
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  content::WebContents* original_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  std::string subframe_url =
      GURL(embedded_test_server()->GetURL("b.com", "/title1.html")).spec();
  {
    // 2. Create a cross-site subframe b.com/title1.html
    std::string create_frame_script = base::StringPrintf(
        "let frame = document.createElement('iframe');"
        "frame.src = '%s';"
        "document.body.appendChild(frame);",
        subframe_url.c_str());
    content::TestNavigationObserver navigation_observer(original_contents);
    original_contents->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
        base::ASCIIToUTF16(create_frame_script), base::NullCallback(),
        content::ISOLATED_WORLD_ID_GLOBAL);
    navigation_observer.Wait();
  }

  // 3. View-source the subframe
  content::WebContentsAddedObserver view_source_contents_observer;
  ChildFrameAt(original_contents, 0)->ViewSource();
  content::WebContents* view_source_contents =
      view_source_contents_observer.GetWebContents();
  EXPECT_TRUE(WaitForLoadStop(view_source_contents));
  // This test expects us to re-load a page after a back navigation (and reuse
  // the network isolation key while doing so), which won't happen when the
  // page is restored from the back forward cache. We are disabling caching for
  // |view_source_contents| to make sure it will not be put into the back
  // forward cache.
  view_source_contents->GetController().GetBackForwardCache().DisableForTesting(
      content::BackForwardCache::TEST_REQUIRES_NO_CACHING);

  // 4. Navigate the view-source page to a c.com/title1.html
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      GURL(embedded_test_server()->GetURL("c.com", "/title1.html"))));

  base::RunLoop cache_status_waiter;
  content::URLLoaderInterceptor interceptor(
      base::BindLambdaForTesting(
          [&](content::URLLoaderInterceptor::RequestParams* params) {
            return false;
          }),
      base::BindLambdaForTesting(
          [&](const GURL& request_url,
              const network::URLLoaderCompletionStatus& status) {
            if (request_url == subframe_url) {
              EXPECT_TRUE(status.exists_in_cache);
              cache_status_waiter.Quit();
            }
          }),
      {});

  {
    // 5. Navigate back to the view-source page
    content::WebContents* new_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    content::TestNavigationObserver navigation_observer(new_contents);
    chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
    navigation_observer.Wait();
  }

  cache_status_waiter.Run();
}

INSTANTIATE_TEST_SUITE_P(
    All,
    ViewSourceWithSplitCacheEnabledTest,
    ::testing::Values(true));

// Verify that links clicked from view-source do not send a Referer header.
// See https://crbug.com/41383952.
IN_PROC_BROWSER_TEST_F(ViewSourceTest, NavigationOmitsReferrer) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url(content::kViewSourceScheme + std::string(":") +
           embedded_test_server()->GetURL(kTestNavigationHtml).spec());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  // Click the first link in the view-source markup.
  content::WebContentsAddedObserver nav_observer;
  EXPECT_TRUE(
      content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
                      "document.getElementsByTagName('A')[0].click();"));
  content::WebContents* new_contents = nav_observer.GetWebContents();
  EXPECT_TRUE(WaitForLoadStop(new_contents));

  // Validate that no referrer was sent.
  EXPECT_EQ("None", EvalJs(new_contents, "document.body.innerText;"));
}

// Verify that JavaScript URIs are sanitized to about:blank.
IN_PROC_BROWSER_TEST_F(ViewSourceTest, JavaScriptURISanitized) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url(content::kViewSourceScheme + std::string(":") +
           embedded_test_server()->GetURL(kTestNavigationHtml).spec());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  // Get the href of the second link in the view-source markup.
  std::string link_href_extraction_script =
      "document.getElementsByTagName('A')[1].href;";

  EXPECT_EQ(
      "about:blank",
      content::EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
                      link_href_extraction_script));
}

// This test verifies that 'view-source' documents are not affected by vertical
// scroll (see https://crbug.com/40599616).
IN_PROC_BROWSER_TEST_F(ViewSourcePermissionsPolicyTest,
                       ViewSourceNotAffectedByHeaderPolicy) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const std::string k_verify_feature = R"(
      var all_features = document.featurePolicy.allowedFeatures();
      var vs = all_features.find((f) => f === 'vertical-scroll');
      console.log(vs);
      "" + vs;)";
  // Sanity-check: 'vertical-scroll' is disabled in the actual page (set by the
  // mock headers).
  GURL url(embedded_test_server()->GetURL(kTestHtml));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  EXPECT_EQ("undefined",
            EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
                   k_verify_feature));
  // Ensure the policy is enabled in the view-source version.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      GURL(content::kViewSourceScheme + std::string(":") + url.spec())));
  EXPECT_EQ("vertical-scroll",
            EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
                   k_verify_feature));
}

namespace {

class ViewSourcePrerenderTest : public ViewSourceTest {
 protected:
  content::test::PrerenderTestHelper& prerender_test_helper() {
    return prerender_test_helper_;
  }

  content::WebContents* target() const { return target_; }
  void set_target(content::WebContents* target) { target_ = target; }

  void SetUp() override {
    prerender_test_helper().RegisterServerRequestMonitor(
        embedded_test_server());
    ViewSourceTest::SetUp();
  }

 private:
  content::test::PrerenderTestHelper prerender_test_helper_{
      base::BindRepeating(&ViewSourcePrerenderTest::target,
                          base::Unretained(this))};

  // The WebContents which is expected to request prerendering.
  raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged> target_ = nullptr;
};

// A frame in a prerendered page should be able to have its source viewed, like
// any other. There is currently no UI for this, but in principle it should
// work.
IN_PROC_BROWSER_TEST_F(ViewSourcePrerenderTest, ViewSourceForPrerender) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL referrer_url = embedded_test_server()->GetURL("/title1.html");
  GURL prerender_url = embedded_test_server()->GetURL("/title2.html");
  content::RenderFrameHost* referrer_frame =
      ui_test_utils::NavigateToURL(browser(), referrer_url);
  set_target(content::WebContents::FromRenderFrameHost(referrer_frame));

  prerender_test_helper().AddPrerender(prerender_url);
  content::PrerenderHostId host_id =
      prerender_test_helper().GetHostForUrl(prerender_url);
  content::RenderFrameHost* prerender_frame =
      prerender_test_helper().GetPrerenderedMainFrameHost(host_id);
  EXPECT_TRUE(prerender_frame);

  content::WebContentsAddedObserver view_source_contents_observer;
  prerender_frame->ViewSource();
  content::WebContents* view_source_contents =
      view_source_contents_observer.GetWebContents();
  EXPECT_TRUE(WaitForLoadStop(view_source_contents));
  EXPECT_EQ(view_source_contents->GetLastCommittedURL(),
            GURL(base::StrCat(
                {content::kViewSourceScheme, ":", prerender_url.spec()})));
  EXPECT_THAT(base::UTF16ToUTF8(view_source_contents->GetTitle()),
              HasSubstr(content::kViewSourceScheme));
}

}  // namespace
