910e62b5创建于 1月15日历史提交
// Copyright 2020 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/test/scoped_feature_list.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/devtools/protocol/audits.h"
#include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/devtools_agent_host_client.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.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/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"

namespace content {

class DevToolsIssueStorageBrowserTest : public DevToolsProtocolTest {
 public:
  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    SetupCrossSiteRedirector(embedded_test_server());
  }

 protected:
  WebContentsImpl* web_contents() {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }

  RenderFrameHostImpl* main_frame_host() {
    return web_contents()->GetPrimaryFrameTree().GetMainFrame();
  }

  void WaitForDummyIssueNotification() {
    base::Value::Dict notification =
        WaitForNotification("Audits.issueAdded", true);
    EXPECT_EQ(*notification.FindStringByDottedPath("issue.code"),
              protocol::Audits::InspectorIssueCodeEnum::CookieIssue);
  }
};

namespace {

void ReportDummyIssue(RenderFrameHostImpl* rfh) {
  auto issueDetails = protocol::Audits::InspectorIssueDetails::Create();
  auto inspector_issue =
      protocol::Audits::InspectorIssue::Create()
          .SetCode(protocol::Audits::InspectorIssueCodeEnum::CookieIssue)
          .SetDetails(issueDetails.Build())
          .Build();
  devtools_instrumentation::ReportBrowserInitiatedIssue(
      rfh, std::move(inspector_issue));
}

}  // namespace

IN_PROC_BROWSER_TEST_F(DevToolsIssueStorageBrowserTest,
                       DevToolsReceivesBrowserIssues) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
  // Report an empty SameSite cookie issue.
  ReportDummyIssue(main_frame_host());
  Attach();
  SendCommandSync("Audits.enable");
  // Verify we have received the SameSite issue.
  WaitForDummyIssueNotification();
}

IN_PROC_BROWSER_TEST_F(DevToolsIssueStorageBrowserTest,
                       DevToolsReceivesBrowserIssuesWhileAttached) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
  Attach();
  SendCommandSync("Audits.enable");
  // Report an empty SameSite cookie issue.
  ReportDummyIssue(main_frame_host());
  // Verify we have received the SameSite issue.
  WaitForDummyIssueNotification();
}

IN_PROC_BROWSER_TEST_F(DevToolsIssueStorageBrowserTest,
                       DeleteSubframeWithIssue) {
  // 1) Navigate to a page with an OOP iframe.
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL test_url =
      embedded_test_server()->GetURL("/devtools/page-with-oopif.html");
  EXPECT_TRUE(NavigateToURL(shell(), test_url));

  // 2) Report an empty SameSite cookie issue in the iframe.
  RenderFrameHostImpl* main_frame = main_frame_host();
  EXPECT_EQ(main_frame->child_count(), static_cast<unsigned>(1));
  RenderFrameHostImpl* iframe = main_frame->child_at(0)->current_frame_host();
  EXPECT_FALSE(iframe->is_main_frame());

  ReportDummyIssue(iframe);

  // 3) Delete the iframe from the page. This should cause the issue to be
  // re-assigned
  //    to the root frame.
  main_frame->RemoveChild(iframe->frame_tree_node());

  // 4) Open DevTools and enable Audits domain.
  Attach();
  SendCommandSync("Audits.enable");

  // 5) Verify we have received the SameSite issue on the main target.
  WaitForDummyIssueNotification();
}

IN_PROC_BROWSER_TEST_F(DevToolsIssueStorageBrowserTest,
                       MainFrameNavigationClearsIssues) {
  // 1) Navigate to about:blank.
  EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));

  // 2) Report an empty SameSite cookie issue.
  ReportDummyIssue(main_frame_host());

  // 3) Navigate to /devtools/navigation.html
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
  EXPECT_TRUE(NavigateToURL(shell(), test_url));

  // 4) Open DevTools and enable Audits domain.
  Attach();
  SendCommandSync("Audits.enable");

  // 5) Verify that we haven't received any notifications.
  ASSERT_FALSE(HasExistingNotification());
}

IN_PROC_BROWSER_TEST_F(DevToolsIssueStorageBrowserTest,
                       IssueForSkippableNavigationEntry_MainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL non_skippable_url(
      embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), non_skippable_url));

  GURL skippable_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
  EXPECT_TRUE(NavigateToURL(shell(), skippable_url));

  // Navigate to a new document from the renderer without a user gesture.
  GURL redirected_url(embedded_test_server()->GetURL("c.com", "/title3.html"));
  EXPECT_TRUE(
      NavigateToURLFromRendererWithoutUserGesture(shell(), redirected_url));

  // Open DevTools and enable Audits domain.
  Attach();
  SendCommandSync("Audits.enable");

  // Verify that we have received the notification for issue.
  base::Value::Dict notification =
      WaitForNotification("Audits.issueAdded", true);

  EXPECT_EQ(*notification.FindStringByDottedPath("issue.code"),
            protocol::Audits::InspectorIssueCodeEnum::GenericIssue);

  EXPECT_EQ(*notification.FindStringByDottedPath(
                "issue.details.genericIssueDetails.errorType"),
            protocol::Audits::GenericIssueErrorTypeEnum::
                NavigationEntryMarkedSkippable);

  EXPECT_THAT(*notification.FindStringByDottedPath(
                  "issue.details.genericIssueDetails.request.url"),
              testing::HasSubstr("/title2.html"));
}

IN_PROC_BROWSER_TEST_F(DevToolsIssueStorageBrowserTest,
                       IssueForSkippableNavigationEntry_SubFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL page_with_iframe_url =
      embedded_test_server()->GetURL("/devtools/page-with-oopif.html");
  EXPECT_TRUE(NavigateToURL(shell(), page_with_iframe_url));

  RenderFrameHostImpl* main_frame = main_frame_host();

  // This navigation is renderer-initiated, so the previous entry
  // ('/devtools/page-with-oopif.html' which has 'title1.html' in iframe) should
  // be marked skippable. An issue should be reported for this.
  GURL next_url(embedded_test_server()->GetURL("bar.com", "/title2.html"));

  EXPECT_TRUE(NavigateToURLFromRendererWithoutUserGesture(
      main_frame->child_at(0)->current_frame_host(), next_url));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  RenderFrameHost* child_frame = main_frame->child_at(0)->current_frame_host();
  EXPECT_TRUE(child_frame);

  // Verify that both main and child frame point to their designated url
  EXPECT_EQ(child_frame->GetLastCommittedURL(), next_url);
  EXPECT_EQ(main_frame->GetLastCommittedURL(), page_with_iframe_url);

  // Open DevTools in the child frame and enable Audits domain.
  AttachToFrameTreeHost(child_frame);
  SendCommandSync("Audits.enable");

  // Verify that we have received the notification for the issue.
  // The issue should be about the navigation to 'title1.html' in the iframe
  // being skippable.
  base::Value::Dict notification =
      WaitForNotification("Audits.issueAdded", true);

  EXPECT_EQ(*notification.FindStringByDottedPath("issue.code"),
            protocol::Audits::InspectorIssueCodeEnum::GenericIssue);

  EXPECT_EQ(*notification.FindStringByDottedPath(
                "issue.details.genericIssueDetails.errorType"),
            protocol::Audits::GenericIssueErrorTypeEnum::
                NavigationEntryMarkedSkippable);

  EXPECT_THAT(*notification.FindStringByDottedPath(
                  "issue.details.genericIssueDetails.request.url"),
              testing::HasSubstr("/title1.html"));
}

class DevToolsIssueStorageWithBackForwardCacheBrowserTest
    : public DevToolsIssueStorageBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Enable BackForwardCache, omitting this feature results in a crash.
    feature_list_.InitWithFeaturesAndParameters(
        GetDefaultEnabledBackForwardCacheFeaturesForTesting(),
        GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(DevToolsIssueStorageWithBackForwardCacheBrowserTest,
                       BackForwardCacheGoBack) {
  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.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* rfh_a = main_frame_host();
  RenderFrameDeletedObserver rfh_a_deleted(rfh_a);

  // 2) Report an empty SameSite cookie issue.
  ReportDummyIssue(rfh_a);

  // 3) Navigate to B.
  //    The previous test verifies that the issue storage is cleared at
  //    this point.
  EXPECT_TRUE(NavigateToURL(shell(), url_b));
  EXPECT_TRUE(rfh_a->IsInBackForwardCache());

  // 4) Go back to A and expect that it is restored from the back-forward cache.
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_FALSE(rfh_a_deleted.deleted());
  EXPECT_EQ(main_frame_host(), rfh_a);

  // 5) Open DevTools and enable Audits domain.
  Attach();
  SendCommandSync("Audits.enable");

  // 6) Verify we have received the SameSite issue on the main target.
  WaitForDummyIssueNotification();
}

class DevToolsIssueStorageWithPrerenderBrowserTest
    : public DevToolsIssueStorageBrowserTest {
 public:
  DevToolsIssueStorageWithPrerenderBrowserTest()
      : prerender_test_helper_(base::BindRepeating(
            &DevToolsIssueStorageWithPrerenderBrowserTest::GetWebContents,
            base::Unretained(this))) {}

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

  test::PrerenderTestHelper& prerender_test_helper() {
    return prerender_test_helper_;
  }

 private:
  WebContents* GetWebContents() { return web_contents(); }
  test::PrerenderTestHelper prerender_test_helper_;
};

IN_PROC_BROWSER_TEST_F(DevToolsIssueStorageWithPrerenderBrowserTest,
                       IssueWhilePrerendering) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL main_url(embedded_test_server()->GetURL("/empty.html"));
  GURL prerender_url(embedded_test_server()->GetURL("/title1.html"));

  // 1) Navigate to |main_url|.
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // 2) Prerender |prerender_url|.
  FrameTreeNodeId host_id = prerender_test_helper().AddPrerender(prerender_url);
  RenderFrameHostImpl* prerender_rfh = static_cast<RenderFrameHostImpl*>(
      prerender_test_helper().GetPrerenderedMainFrameHost(host_id));

  // 3) Report an empty SameSite cookie issue in prerendering page.
  ReportDummyIssue(prerender_rfh);

  // 4) Activate prerendering page.
  prerender_test_helper().NavigatePrimaryPage(prerender_url);

  // 5) Open DevTools and enable Audits domain.
  Attach();
  SendCommandSync("Audits.enable");

  // 6) Verify we have received the SameSite issue on the main target.
  WaitForDummyIssueNotification();
}

class DevToolsIssueStorageFencedFrameTest
    : public DevToolsIssueStorageBrowserTest {
 public:
  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_helper_;
  }

 protected:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(DevToolsIssueStorageFencedFrameTest,
                       DeleteFencedFrameWithIssue) {
  // 1) Navigate to a page.
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL test_url = embedded_test_server()->GetURL("/title1.html");
  EXPECT_TRUE(NavigateToURL(shell(), test_url));

  // 2) Create a fenced frame.
  GURL fenced_frame_url =
      embedded_test_server()->GetURL("/fenced_frames/title1.html");
  content::RenderFrameHostImpl* fenced_frame_rfh =
      static_cast<RenderFrameHostImpl*>(
          fenced_frame_test_helper().CreateFencedFrame(
              web_contents()->GetPrimaryMainFrame(), fenced_frame_url));
  EXPECT_NE(nullptr, fenced_frame_rfh);

  // 3) Report an empty SameSite cookie issue in the fenced frame.
  ReportDummyIssue(fenced_frame_rfh);

  // 4) Delete the fenced frame from the page. This should cause the issue to be
  // re-assigned to the primary root frame.
  EXPECT_TRUE(ExecJs(shell()->web_contents(),
                     "document.querySelector('fencedframe').remove()"));

  // 5) Open DevTools and enable Audits domain.
  Attach();
  SendCommandSync("Audits.enable");

  // 6) Verify we have received the SameSite issue on the main target.
  WaitForDummyIssueNotification();
}

}  // namespace content