20beddae创建于 2024年10月29日历史提交
// 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 "content/browser/renderer_host/policy_container_host.h"

#include "base/command_line.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/mojom/referrer_policy.mojom-shared.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

using ::testing::ByRef;
using ::testing::Eq;
using ::testing::IsNull;
using ::testing::Pointee;

namespace {
class PolicyContainerHostBrowserTest : public content::ContentBrowserTest {
 protected:
  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

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

  RenderFrameHostImpl* current_frame_host() {
    return web_contents()->GetPrimaryMainFrame();
  }
};
}  // namespace

IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       ReferrerPolicyFromHeader) {
  using ReferrerPolicy = network::mojom::ReferrerPolicy;
  const struct {
    const char* headers;
    ReferrerPolicy expected_referrer;
  } kTestCases[] = {
      {"", ReferrerPolicy::kDefault},
      {"Referrer-Policy: no-referrer", ReferrerPolicy::kNever},
      {"Referrer-Policy: no-referrer-when-downgrade",
       ReferrerPolicy::kNoReferrerWhenDowngrade},
      {"Referrer-Policy: origin", ReferrerPolicy::kOrigin},
      {"Referrer-Policy: origin-when-cross-origin",
       ReferrerPolicy::kOriginWhenCrossOrigin},
      {"Referrer-Policy: same-origin", ReferrerPolicy::kSameOrigin},
      {"Referrer-Policy: strict-origin", ReferrerPolicy::kStrictOrigin},
      {"Referrer-Policy: strict-origin-when-cross-origin",
       ReferrerPolicy::kStrictOriginWhenCrossOrigin},
      {"Referrer-Policy: unsafe-url", ReferrerPolicy::kAlways},
  };
  for (const auto& test_case : kTestCases) {
    GURL url = embedded_test_server()->GetURL(
        "a.com", "/set-header?" + std::string(test_case.headers));
    ASSERT_TRUE(NavigateToURL(shell(), url));
    EXPECT_EQ(test_case.expected_referrer,
              current_frame_host()->policy_container_host()->referrer_policy());
  }
}

IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       ReferrerPolicyMetaUpdates) {
  GURL page = embedded_test_server()->GetURL("a.com", "/empty.html");
  ASSERT_TRUE(NavigateToURL(shell(), page));
  EXPECT_EQ(network::mojom::ReferrerPolicy::kDefault,
            current_frame_host()->policy_container_host()->referrer_policy());
  ASSERT_TRUE(ExecJs(current_frame_host(),
                     "var meta = document.createElement('meta');"
                     "meta.name = 'referrer';"
                     "meta.content = 'no-referrer';"
                     "document.head.appendChild(meta);"));
  EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
            current_frame_host()->policy_container_host()->referrer_policy());
}

IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest, CopiedFromPopupOpener) {
  GURL policies_a_url = embedded_test_server()->GetURL(
      "a.com", "/set-header?Referrer-Policy: no-referrer");
  GURL policies_b_url = embedded_test_server()->GetURL(
      "a.com",
      "/set-header?"
      "Referrer-Policy: origin&"
      "Content-Security-Policy: img-src 'self'; style-src 'self'");
  ASSERT_TRUE(NavigateToURL(shell(), policies_b_url));

  PolicyContainerPolicies main_document_policies =
      current_frame_host()->policy_container_host()->policies().Clone();

  {
    // Open a popup. It stays on the initial empty document. The
    // PolicyContainerHost's policies must have been inherited from the opener.
    ShellAddedObserver shell_observer;
    ASSERT_TRUE(ExecJs(current_frame_host(), "window.open();"));
    WebContentsImpl* popup_webcontents = static_cast<WebContentsImpl*>(
        shell_observer.GetShell()->web_contents());
    RenderFrameHostImpl* popup_frame =
        popup_webcontents->GetPrimaryFrameTree().root()->current_frame_host();
    EXPECT_EQ(network::mojom::ReferrerPolicy::kOrigin,
              popup_frame->policy_container_host()->referrer_policy());
    EXPECT_EQ(main_document_policies,
              popup_frame->policy_container_host()->policies());
  }
  {
    // Open a popup that navigates to another document, the referrer policy
    // of the current frame (referrer-policy: origin) should be applied, which
    // induces on the following document the |document.referrer| value of
    // |origin_referrer_page.GetWithEmptyPath()| as tested below.
    // The document loaded in the popup should have
    // referrer-policy: no-referrer and no CSPs from the response headers.
    ShellAddedObserver shell_observer;
    ASSERT_TRUE(ExecJs(current_frame_host(),
                       JsReplace("window.open($1);", policies_a_url)));
    WebContentsImpl* popup_webcontents = static_cast<WebContentsImpl*>(
        shell_observer.GetShell()->web_contents());
    WaitForLoadStop(popup_webcontents);
    RenderFrameHostImpl* popup_frame =
        popup_webcontents->GetPrimaryFrameTree().root()->current_frame_host();
    EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
              popup_frame->policy_container_host()->referrer_policy());
    EXPECT_EQ(0u, popup_frame->policy_container_host()
                      ->policies()
                      .content_security_policies.size());
    EXPECT_EQ(policies_b_url.GetWithEmptyPath(),
              EvalJs(popup_frame, "document.referrer;"));
  }
}

IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest, CopiedFromParent) {
  GURL policies_a_url = embedded_test_server()->GetURL(
      "a.com", "/set-header?Referrer-Policy: no-referrer");
  GURL policies_b_url = embedded_test_server()->GetURL(
      "a.com",
      "/set-header?"
      "Referrer-Policy: origin&"
      "Content-Security-Policy: img-src 'self'; style-src 'self'");
  ASSERT_TRUE(NavigateToURL(shell(), policies_b_url));

  PolicyContainerPolicies main_document_policies =
      current_frame_host()->policy_container_host()->policies().Clone();

  std::string create_srcdoc_iframe_script(
      "var iframe = document.createElement('iframe');"
      "iframe.srcdoc = '<p>hello world!</p>';"
      "document.body.appendChild(iframe);");
  {
    // Add an iframe and verify its policies.
    ASSERT_TRUE(ExecJs(current_frame_host(), create_srcdoc_iframe_script));
    ASSERT_EQ(1U, current_frame_host()->child_count());
    WaitForLoadStop(web_contents());
    FrameTreeNode* iframe_node =
        current_frame_host()->child_at(current_frame_host()->child_count() - 1);
    EXPECT_TRUE(iframe_node->current_url().IsAboutSrcdoc());
    EXPECT_EQ(network::mojom::ReferrerPolicy::kOrigin,
              iframe_node->current_frame_host()
                  ->policy_container_host()
                  ->referrer_policy());
    EXPECT_EQ(
        main_document_policies,
        iframe_node->current_frame_host()->policy_container_host()->policies());

    // Navigate the document and verify the policies are updated and the
    // initiator referrer policy (referrer-policy: origin) is used which induces
    // on the following document the |document.referrer| value of
    // |origin_referrer_page.GetWithEmptyPath()| as tested below.
    ASSERT_TRUE(
        ExecJs(iframe_node->current_frame_host(),
               JsReplace("document.location.href = $1", policies_a_url)));
    WaitForLoadStop(web_contents());
    EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
              iframe_node->current_frame_host()
                  ->policy_container_host()
                  ->referrer_policy());
    EXPECT_EQ(0u, iframe_node->current_frame_host()
                      ->policy_container_host()
                      ->policies()
                      .content_security_policies.size());
    EXPECT_EQ(policies_b_url.GetWithEmptyPath(),
              EvalJs(iframe_node->current_frame_host(), "document.referrer;"));
  }

  // Taint the RFH Policy container with a value that will not be the same in
  // the renderer (kOrigin). This is not possible in a normal situation, but
  // could occur if the renderer was compromised.
  // This will enable the following test, which verifies that the copied policy
  // comes from the browser.
  static_cast<blink::mojom::PolicyContainerHost*>(
      current_frame_host()->policy_container_host())
      ->SetReferrerPolicy(network::mojom::ReferrerPolicy::kSameOrigin);
  // Repeat the previous test with the tainted policy container:
  {
    // Add an iframe and verify its Policy Container value.
    ASSERT_TRUE(ExecJs(current_frame_host(), create_srcdoc_iframe_script));
    // Check that the iframe was properly added.
    ASSERT_EQ(2U, current_frame_host()->child_count());
    WaitForLoadStop(web_contents());
    FrameTreeNode* iframe_node =
        current_frame_host()->child_at(current_frame_host()->child_count() - 1);
    EXPECT_TRUE(iframe_node->current_url().IsAboutSrcdoc());
    EXPECT_EQ(network::mojom::ReferrerPolicy::kSameOrigin,
              iframe_node->current_frame_host()
                  ->policy_container_host()
                  ->referrer_policy());

    ASSERT_TRUE(
        ExecJs(iframe_node->current_frame_host(),
               JsReplace("document.location.href = $1", policies_a_url)));
    WaitForLoadStop(web_contents());
    // The referrer policy is the one of the newly loaded document:
    // no_referrer_page.
    EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
              iframe_node->current_frame_host()
                  ->policy_container_host()
                  ->referrer_policy());
    // The referrer is determined using the policy container inherited when the
    // frame was created and navigated to the srcdoc. The tainted value, within
    // the browser process, was inherited during the navigation, and is expected
    // to be referrer-policy: same-origin, leading to the full url being used as
    // a referrer.
    EXPECT_EQ(policies_b_url,
              EvalJs(iframe_node->current_frame_host(), "document.referrer;"));
  }
}

IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       CopiedFromParentCreatedBySibling) {
  GURL origin_referrer_page = embedded_test_server()->GetURL(
      "a.com",
      "/set-header?"
      "Referrer-Policy: origin&"
      "Content-Security-Policy: style-src-attr 'none'");
  GURL same_origin_referrer_page = embedded_test_server()->GetURL(
      "a.com", "/set-header?Referrer-Policy: same-origin");
  GURL no_referrer_page = embedded_test_server()->GetURL(
      "a.com", "/set-header?Referrer-Policy: no-referrer");
  ASSERT_TRUE(NavigateToURL(shell(), origin_referrer_page));

  PolicyContainerPolicies main_document_policies =
      current_frame_host()->policy_container_host()->policies().Clone();

  {
    // Add an iframe and verify its referrer policy.
    ASSERT_TRUE(
        ExecJs(current_frame_host(),
               JsReplace("var iframe = document.createElement('iframe');"
                         "iframe.src = $1;"
                         "iframe.name = 'first';"
                         "document.body.appendChild(iframe);",
                         same_origin_referrer_page)));
    ASSERT_EQ(1U, current_frame_host()->child_count());
    WaitForLoadStop(web_contents());
    FrameTreeNode* first_iframe_node =
        current_frame_host()->child_at(current_frame_host()->child_count() - 1);
    EXPECT_EQ(network::mojom::ReferrerPolicy::kSameOrigin,
              first_iframe_node->current_frame_host()
                  ->policy_container_host()
                  ->referrer_policy());

    // From the iframe, create a sibling.
    ASSERT_TRUE(ExecJs(first_iframe_node->current_frame_host(),
                       "var iframe = document.createElement('iframe');"
                       "iframe.srcdoc = 'hello world';"
                       "iframe.name = 'second';"
                       "parent.document.body.appendChild(iframe);"));
    ASSERT_EQ(2U, current_frame_host()->child_count());
    FrameTreeNode* second_iframe_node =
        current_frame_host()->child_at(current_frame_host()->child_count() - 1);
    // The policies should be inherited from the creator of the iframe, which is
    // the node to which the iframe is attached to, i.e. the parent, here the
    // main document, which has referrer-policy: origin.
    EXPECT_EQ(network::mojom::ReferrerPolicy::kOrigin,
              second_iframe_node->current_frame_host()
                  ->policy_container_host()
                  ->referrer_policy())
        << "Sibling policy container inherited from parent.";
    EXPECT_EQ(main_document_policies, second_iframe_node->current_frame_host()
                                          ->policy_container_host()
                                          ->policies());

    // The second iframe navigates its sibling, the first iframe, the inherited
    // referrer policy of second (from the main frame) is applied.
    // The document loaded in first is a local scheme, and as such inherits from
    // the initiator, the second iframe.
    ASSERT_TRUE(ExecJs(second_iframe_node->current_frame_host(),
                       "window.open('about:blank', 'first');"));
    WaitForLoadStop(web_contents());
    EXPECT_EQ(network::mojom::ReferrerPolicy::kOrigin,
              first_iframe_node->current_frame_host()
                  ->policy_container_host()
                  ->referrer_policy());
    EXPECT_EQ(main_document_policies, first_iframe_node->current_frame_host()
                                          ->policy_container_host()
                                          ->policies());
    EXPECT_EQ(
        origin_referrer_page.GetWithEmptyPath(),
        EvalJs(first_iframe_node->current_frame_host(), "document.referrer;"))
        << "Referrer obtained from applying same-origin referrer policy.";

    // Navigate the second iframe, verify the policies are updated from
    // the response. The document.referrer comes from the application of the
    // inherited referrer policy of the main frame
    // (referrer-policy: same-origin).
    ASSERT_TRUE(
        ExecJs(second_iframe_node->current_frame_host(),
               JsReplace("document.location.href = $1", no_referrer_page)));
    WaitForLoadStop(web_contents());
    EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
              second_iframe_node->current_frame_host()
                  ->policy_container_host()
                  ->referrer_policy());
    EXPECT_EQ(0u, second_iframe_node->current_frame_host()
                      ->policy_container_host()
                      ->policies()
                      .content_security_policies.size());
    EXPECT_EQ(
        origin_referrer_page.GetWithEmptyPath(),
        EvalJs(second_iframe_node->current_frame_host(), "document.referrer;"))
        << "Referrer obtained from applying same-origin referrer policy.";
  }
}

IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest, HistoryForMainFrame) {
  NavigationControllerImpl& controller = web_contents()->GetController();
  GURL policies_a_url(embedded_test_server()->GetURL(
      "/set-header?"
      "Referrer-Policy: no-referrer&"
      "Content-Security-Policy: img-src 'none'"));
  GURL policies_b_url(embedded_test_server()->GetURL(
      "/set-header?"
      "Referrer-Policy: unsafe-url&"
      "Content-Security-Policy: style-src 'none'"));

  // Navigate to a page setting policies_a.
  ASSERT_TRUE(NavigateToURL(shell(), policies_a_url));
  ASSERT_EQ(1, controller.GetEntryCount());
  EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
            current_frame_host()->policy_container_host()->referrer_policy());
  PolicyContainerPolicies policies_a =
      current_frame_host()->policy_container_host()->policies().Clone();

  // Now navigate to a local scheme.
  ASSERT_TRUE(ExecJs(current_frame_host(), "window.location = 'about:blank'"));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  ASSERT_EQ(policies_a,
            current_frame_host()->policy_container_host()->policies());

  ASSERT_EQ(2, controller.GetEntryCount());
  NavigationEntryImpl* entry2 = controller.GetEntryAtIndex(1);

  // Check that RendererDidNavigateToNewEntry stored the correct policy
  // container in the FrameNavigationEntry.
  EXPECT_THAT(entry2->root_node()->frame_entry->policy_container_policies(),
              Pointee(Eq(ByRef(policies_a))));

  // Same document navigation.
  ASSERT_TRUE(ExecJs(current_frame_host(), "window.location.href = '#top'"));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  EXPECT_EQ(policies_a,
            current_frame_host()->policy_container_host()->policies());

  ASSERT_EQ(3, controller.GetEntryCount());
  NavigationEntryImpl* entry3 = controller.GetEntryAtIndex(2);
  EXPECT_THAT(entry3->root_node()->frame_entry->policy_container_policies(),
              Pointee(Eq(ByRef(policies_a))));

  // Navigate to a third page.
  ASSERT_TRUE(NavigateToURL(shell(), policies_b_url));
  ASSERT_EQ(4, controller.GetEntryCount());
  ASSERT_EQ(network::mojom::ReferrerPolicy::kAlways,
            current_frame_host()->policy_container_host()->referrer_policy());

  // Go back to "about:blank#top"
  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // The correct policies should be restored from history.
  EXPECT_EQ(policies_a,
            current_frame_host()->policy_container_host()->policies());

  // The function RendererDidNavigateToExistingEntry should not have changed
  // anything.
  EXPECT_THAT(entry3->root_node()->frame_entry->policy_container_policies(),
              Pointee(Eq(ByRef(policies_a))));

  // Go back to "about:blank".
  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // The correct policies should be restored from history.
  EXPECT_EQ(policies_a,
            current_frame_host()->policy_container_host()->policies());

  // The function RendererDidNavigateToExistingEntry should not have changed
  // anything.
  EXPECT_THAT(entry2->root_node()->frame_entry->policy_container_policies(),
              Pointee(Eq(ByRef(policies_a))));

  // Same URL navigation, which gets converted to a reload.
  ASSERT_TRUE(NavigateFrameToURL(current_frame_host()->frame_tree_node(),
                                 GURL("about:blank")));
  EXPECT_EQ(policies_a,
            current_frame_host()->policy_container_host()->policies());

  ASSERT_EQ(4, controller.GetEntryCount());

  // Check that after RendererDidNavigateToExistingEntry the policy container in
  // the FrameNavigationEntry is still correct.
  EXPECT_THAT(entry2->root_node()->frame_entry->policy_container_policies(),
              Pointee(Eq(ByRef(policies_a))));
}

namespace {

bool EqualsExceptCOOPAndTopNavigation(const PolicyContainerPolicies& lhs,
                                      const PolicyContainerPolicies& rhs) {
  PolicyContainerPolicies rhs_modulo_coop = rhs.Clone();
  rhs_modulo_coop.cross_origin_opener_policy = lhs.cross_origin_opener_policy;
  rhs_modulo_coop.can_navigate_top_without_user_gesture =
      lhs.can_navigate_top_without_user_gesture;

  return lhs == rhs_modulo_coop;
}

}  // namespace

IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest, HistoryForChildFrame) {
  NavigationControllerImpl& controller = web_contents()->GetController();
  GURL policies_a_url(embedded_test_server()->GetURL(
      "/set-header?"
      "Referrer-Policy: unsafe-url&"
      "Content-Security-Policy: style-src 'none'"));
  GURL strict_origin_when_cross_origin_referrer_url(
      embedded_test_server()->GetURL(
          "/set-header?Referrer-Policy: strict-origin-when-cross-origin"));

  GURL main_url(embedded_test_server()->GetURL("/page_with_blank_iframe.html"));
  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_TRUE(NavigateToURL(shell(), main_url));

  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  ASSERT_NE(nullptr, child);
  ASSERT_EQ(1, controller.GetEntryCount());

  // The child has default policies (same as the parent).
  EXPECT_EQ(
      network::mojom::ReferrerPolicy::kDefault,
      child->current_frame_host()->policy_container_host()->referrer_policy());
  EXPECT_EQ(0u, child->current_frame_host()
                    ->policy_container_host()
                    ->policies()
                    .content_security_policies.size());
  NavigationEntryImpl* entry1 = controller.GetEntryAtIndex(0);
  EXPECT_THAT(
      entry1->GetFrameEntry(child)->policy_container_policies(),
      Pointee(Eq(ByRef(
          child->current_frame_host()->policy_container_host()->policies()))));

  // Change policies of the main frame.
  ASSERT_TRUE(ExecJs(current_frame_host(),
                     "var meta = document.createElement('meta');"
                     "meta.name = 'referrer';"
                     "meta.content = 'same-origin';"
                     "document.head.appendChild(meta);"
                     "var meta2 = document.createElement('meta');"
                     "meta2.httpEquiv = 'content-security-policy';"
                     "meta2.content = 'img-src';"
                     "document.head.appendChild(meta2);"));
  EXPECT_EQ(network::mojom::ReferrerPolicy::kSameOrigin,
            current_frame_host()->policy_container_host()->referrer_policy());
  EXPECT_EQ(1u, current_frame_host()
                    ->policy_container_host()
                    ->policies()
                    .content_security_policies.size());
  PolicyContainerPolicies main_frame_new_policies =
      current_frame_host()->policy_container_host()->policies().Clone();

  // 1) Navigate the child frame to a local scheme url.
  ASSERT_TRUE(ExecJs(current_frame_host(),
                     "window.open('data:text/html,Hello', 'test_iframe');"));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // The new document inherits from the navigation initiator, except for COOP.
  EXPECT_TRUE(EqualsExceptCOOPAndTopNavigation(
      main_frame_new_policies,
      child->current_frame_host()->policy_container_host()->policies()));

  // The new page replaces the initial about:blank page in the subframe, so no
  // new navigation entry is created.
  ASSERT_EQ(1, controller.GetEntryCount());

  // The policy container of the FrameNavigationEntry should have been
  // updated. Test that the function RendererDidNavigateAutoSubframe updates the
  // FrameNavigationEntry properly.
  EXPECT_THAT(
      entry1->GetFrameEntry(child)->policy_container_policies(),
      Pointee(Eq(ByRef(
          child->current_frame_host()->policy_container_host()->policies()))));

  // 2) Same document navigation.
  ASSERT_TRUE(
      ExecJs(child->current_frame_host(), "window.location.href = '#top';"));
  ASSERT_TRUE(WaitForLoadStop(web_contents()));

  // The policies have not changed.
  EXPECT_TRUE(EqualsExceptCOOPAndTopNavigation(
      main_frame_new_policies,
      child->current_frame_host()->policy_container_host()->policies()));
  ASSERT_EQ(2, controller.GetEntryCount());
  NavigationEntryImpl* entry2 = controller.GetEntryAtIndex(1);
  EXPECT_THAT(
      entry1->GetFrameEntry(child)->policy_container_policies(),
      Pointee(Eq(ByRef(
          child->current_frame_host()->policy_container_host()->policies()))));

  // 3) Navigate the child frame to a network scheme url.
  ASSERT_TRUE(NavigateFrameToURL(child, policies_a_url));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  ASSERT_EQ(3, controller.GetEntryCount());
  EXPECT_EQ(
      network::mojom::ReferrerPolicy::kAlways,
      child->current_frame_host()->policy_container_host()->referrer_policy());
  PolicyContainerPolicies policies_a =
      child->current_frame_host()->policy_container_host()->policies().Clone();

  // 4) Navigate the child frame to another local scheme url.
  ASSERT_TRUE(ExecJs(child->current_frame_host(),
                     "window.location = 'data:text/html,Hello2';"));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // The new document inherits from the navigation initiator.
  EXPECT_TRUE(EqualsExceptCOOPAndTopNavigation(
      policies_a,
      child->current_frame_host()->policy_container_host()->policies()));

  // Now test that the function RendererDidNavigateNewSubframe properly stored
  // the policy container in the FrameNavigationEntry.
  ASSERT_EQ(4, controller.GetEntryCount());
  NavigationEntryImpl* entry4 = controller.GetEntryAtIndex(3);
  EXPECT_THAT(
      entry4->GetFrameEntry(child)->policy_container_policies(),
      Pointee(Eq(ByRef(
          child->current_frame_host()->policy_container_host()->policies()))));

  // 5) Navigate the child frame to another network scheme url.
  ASSERT_TRUE(
      NavigateFrameToURL(child, strict_origin_when_cross_origin_referrer_url));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  ASSERT_EQ(5, controller.GetEntryCount());
  EXPECT_EQ(
      network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin,
      child->current_frame_host()->policy_container_host()->referrer_policy());
  EXPECT_EQ(0u, child->current_frame_host()
                    ->policy_container_host()
                    ->policies()
                    .content_security_policies.size());

  // 6) Navigate the main frame cross-document to destroy the subframes.
  GURL foo_url(embedded_test_server()->GetURL(
      "foo.com", "/navigation_controller/simple_page_1.html"));
  ASSERT_TRUE(NavigateToURL(shell(), foo_url));

  // 7) Navigate all the way back and check that we properly reload the policy
  // container from history.
  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  child = web_contents()->GetPrimaryFrameTree().root()->child_at(0);
  ASSERT_NE(nullptr, child);

  // The correct referrer policy should be restored from history.
  EXPECT_TRUE(EqualsExceptCOOPAndTopNavigation(
      policies_a,
      child->current_frame_host()->policy_container_host()->policies()));

  // The frame entry should not have changed.
  EXPECT_THAT(
      entry4->GetFrameEntry(child)->policy_container_policies(),
      Pointee(Eq(ByRef(
          child->current_frame_host()->policy_container_host()->policies()))));

  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // The correct referrer policy should be restored from history.
  EXPECT_TRUE(EqualsExceptCOOPAndTopNavigation(
      main_frame_new_policies,
      child->current_frame_host()->policy_container_host()->policies()));

  // The frame entry should not have changed.
  EXPECT_THAT(
      entry2->GetFrameEntry(child)->policy_container_policies(),
      Pointee(Eq(ByRef(
          child->current_frame_host()->policy_container_host()->policies()))));

  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // The correct referrer policy should be restored from history.
  EXPECT_TRUE(EqualsExceptCOOPAndTopNavigation(
      main_frame_new_policies,
      child->current_frame_host()->policy_container_host()->policies()));

  // The frame entry should not have changed.
  EXPECT_THAT(
      entry1->GetFrameEntry(child)->policy_container_policies(),
      Pointee(Eq(ByRef(
          child->current_frame_host()->policy_container_host()->policies()))));
}

// Test for https://crbug.com/364773822.
IN_PROC_BROWSER_TEST_F(
    PolicyContainerHostBrowserTest,
    PoliciesAreNotReloadedFromHistoryIfNavigationEncounteredError) {
  NavigationControllerImpl& controller = web_contents()->GetController();
  GURL main_url(embedded_test_server()->GetURL("/page_with_blank_iframe.html"));

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
  ASSERT_TRUE(NavigateToURL(shell(), main_url));

  ASSERT_EQ(1U, root->child_count());
  FrameTreeNode* child = root->child_at(0);
  ASSERT_NE(nullptr, child);

  GURL child_frame_url(
      embedded_test_server()->GetURL("/page_with_srcdoc_iframe_and_csp.html"));
  ASSERT_TRUE(NavigateFrameToURL(child, child_frame_url));

  ASSERT_EQ(1, controller.GetEntryCount());

  PolicyContainerPolicies child_frame_policies =
      child->current_frame_host()->policy_container_host()->policies().Clone();
  EXPECT_EQ(1u, child_frame_policies.content_security_policies.size());
  NavigationEntryImpl* entry1 = controller.GetEntryAtIndex(0);
  EXPECT_THAT(
      entry1->GetFrameEntry(child)->policy_container_policies(),
      Pointee(Eq(ByRef(
          child->current_frame_host()->policy_container_host()->policies()))));

  // The srcdoc document inherits from the parent, except for COOP.
  ASSERT_EQ(1U, child->child_count());
  FrameTreeNode* grandchild = child->child_at(0);
  ASSERT_NE(nullptr, grandchild);
  EXPECT_TRUE(EqualsExceptCOOPAndTopNavigation(
      child_frame_policies,
      grandchild->current_frame_host()->policy_container_host()->policies()));

  ASSERT_EQ(1, controller.GetEntryCount());

  GURL about_blank("about:blank#1");
  ASSERT_TRUE(NavigateFrameToURL(grandchild, about_blank));

  // Add a sandbox to the child iframe and navigate away and back to recompute
  // the sandbox flag, so that it gets reloaded with an opaque origin.
  ASSERT_TRUE(ExecJs(current_frame_host(), R"(
    document.getElementById('test_iframe').sandbox = 'allow-scripts';
  )"));
  ASSERT_TRUE(NavigateFrameToURL(child, about_blank));
  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  EXPECT_TRUE(child->current_origin().opaque());

  // Trying to navigate the grandchild back to about:srcdoc is disallowed by
  // https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_request.cc;l=6957-6967;drc=580a3da6e0ea94caa1f127e4455fbdbd05625065.
  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  EXPECT_TRUE(child->child_at(0)->current_frame_host()->IsErrorDocument());

  // The error page should be loaded without inheriting the parent policies.
  EXPECT_FALSE(EqualsExceptCOOPAndTopNavigation(child_frame_policies,
                                                child->child_at(0)
                                                    ->current_frame_host()
                                                    ->policy_container_host()
                                                    ->policies()));

  // The policy container of the FrameNavigationEntry should have been cleared
  // so that the error page's policies are not used on later successful loads.
  EXPECT_THAT(
      entry1->GetFrameEntry(child->child_at(0))->policy_container_policies(),
      IsNull());

  controller.GoForward();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  controller.GoForward();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // Remove the sandbox flag so that the srcdoc frame can successfully load
  // again.
  ASSERT_TRUE(ExecJs(current_frame_host(), R"(
    document.getElementById('test_iframe').removeAttribute('sandbox');
  )"));

  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  controller.GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // The srcdoc document inherits again from the parent, except for COOP.
  EXPECT_TRUE(EqualsExceptCOOPAndTopNavigation(child_frame_policies,
                                               child->child_at(0)
                                                   ->current_frame_host()
                                                   ->policy_container_host()
                                                   ->policies()));
}

// Check that the FrameNavigationEntry for the initial empty document is
// correctly populated, both for main frames and for subframes.
IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       HistoryForInitialEmptyDocument) {
  GURL policies_a_url =
      embedded_test_server()->GetURL("a.com",
                                     "/set-header?"
                                     "Referrer-Policy: origin&"
                                     "Content-Security-Policy: img-src 'none'");
  ASSERT_TRUE(NavigateToURL(shell(), policies_a_url));
  PolicyContainerPolicies policies_a =
      current_frame_host()->policy_container_host()->policies().Clone();

  {
    // Open a subframe
    ASSERT_TRUE(ExecJs(current_frame_host(),
                       "var frame = document.createElement('iframe');"
                       "frame.name = 'test_iframe';"
                       "document.body.appendChild(frame);"));

    FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
    ASSERT_EQ(1U, root->child_count());
    FrameTreeNode* child = root->child_at(0);
    ASSERT_NE(nullptr, child);
    // The child inherits from the parent.
    EXPECT_EQ(policies_a,
              child->current_frame_host()->policy_container_host()->policies());

    // The right policy is stored in the FrameNavigationEntry.
    NavigationControllerImpl& controller = web_contents()->GetController();
    ASSERT_EQ(1, controller.GetEntryCount());
    NavigationEntryImpl* entry1 = controller.GetEntryAtIndex(0);
    EXPECT_THAT(entry1->GetFrameEntry(child)->policy_container_policies(),
                Pointee(Eq(ByRef(policies_a))));
  }

  {
    // Open a popup.
    ShellAddedObserver shell_observer;
    ASSERT_TRUE(ExecJs(current_frame_host(), "window.open('about:blank');"));
    WebContentsImpl* popup_webcontents = static_cast<WebContentsImpl*>(
        shell_observer.GetShell()->web_contents());
    RenderFrameHostImpl* popup_frame =
        popup_webcontents->GetPrimaryFrameTree().root()->current_frame_host();

    // The popup inherits from the creator.
    EXPECT_EQ(policies_a, popup_frame->policy_container_host()->policies());

    // The right policy is stored in the FrameNavigationEntry.
    NavigationEntryImpl* entry1 =
        popup_webcontents->GetController().GetEntryAtIndex(0);
    EXPECT_THAT(entry1->root_node()->frame_entry->policy_container_policies(),
                Pointee(Eq(ByRef(policies_a))));
  }
}

// This test ensures that the document policies what we store in the
// FrameNavigationEntry are a snapshot of the document policies at
// CommitNavigation time, and do not include updates triggered by Blink.
IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       BlinkModificationsDoNotAffectPolicyContainer) {
  NavigationControllerImpl& controller = web_contents()->GetController();

  GURL always_referrer_url(embedded_test_server()->GetURL(
      "/set-header?Referrer-Policy: unsafe-url"));
  ASSERT_TRUE(NavigateToURL(shell(), always_referrer_url));

  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();

  // Create child frame
  ASSERT_TRUE(ExecJs(current_frame_host(), R"(
    let frame = document.createElement('iframe');
    let content = '<head><meta name="referrer" content="no-referrer"></head>';
    frame.src = 'data:text/html,' + content;
    document.body.appendChild(frame);
  )"));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  FrameTreeNode* child = root->child_at(0);
  ASSERT_NE(nullptr, child);

  // Blink should have parsed Referrer-Policy from the meta tag and the
  // information should have propagated to the PolicyContainerHost.
  ASSERT_EQ(
      network::mojom::ReferrerPolicy::kNever,
      child->current_frame_host()->policy_container_host()->referrer_policy());
  ASSERT_EQ(1, controller.GetEntryCount());
  NavigationEntryImpl* entry1 = controller.GetEntryAtIndex(0);

  // Policies should be stored in the FrameNavigationEntry before any
  // modification coming from blink.
  ASSERT_EQ(network::mojom::ReferrerPolicy::kAlways,
            entry1->GetFrameEntry(child)
                ->policy_container_policies()
                ->referrer_policy);
}

// This is a regression test. A document navigates a remote subframe away from
// about:blank. The new FrameNavigationEntry used to wrongly inherit a
// policies from the about:blank document.
IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       PoliciesNotInheritedForRemoteNonLocalScheme) {
  NavigationControllerImpl& controller = web_contents()->GetController();

  // Create a main frame with referrer policy 'unsafe-url'.
  GURL always_referrer_url(embedded_test_server()->GetURL(
      "/set-header?Referrer-Policy: unsafe-url"));
  ASSERT_TRUE(NavigateToURL(shell(), always_referrer_url));

  // Create a cross-origin child frame with referrer policy 'no-referrer'.
  GURL cross_origin_no_referrer_url(embedded_test_server()->GetURL(
      "b.com", "/set-header?Referrer-Policy: no-referrer"));
  EXPECT_TRUE(
      ExecJs(current_frame_host(), JsReplace(R"(
    child_frame = document.createElement('iframe');
    child_frame.src = $1;
    document.body.appendChild(child_frame);
  )",
                                             cross_origin_no_referrer_url)));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  FrameTreeNode* child = current_frame_host()->child_at(0);
  EXPECT_NE(nullptr, child);

  // Navigate the child frame to "about:blank", but keep it in a remote frame
  // w.r.t. the main frame.
  EXPECT_TRUE(ExecJs(child->current_frame_host(), R"(
    location.href = "about:blank";
  )"));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  EXPECT_EQ(
      network::mojom::ReferrerPolicy::kNever,
      child->current_frame_host()->policy_container_host()->referrer_policy());
  EXPECT_EQ(2, controller.GetEntryCount());
  NavigationEntryImpl* entry1 = controller.GetEntryAtIndex(1);

  EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
            entry1->GetFrameEntry(child)
                ->policy_container_policies()
                ->referrer_policy);

  // Now navigate the child from the main frame to another empty url.
  GURL empty_url(embedded_test_server()->GetURL("c.com", "/empty.html"));
  EXPECT_TRUE(ExecJs(current_frame_host(), JsReplace(R"(
    child_frame.src = $1;
  )",
                                                     empty_url)));
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // The child should have default referrer policy.
  EXPECT_EQ(
      network::mojom::ReferrerPolicy::kDefault,
      child->current_frame_host()->policy_container_host()->referrer_policy());
}

// The following tests shows the behavior of the policy container during the
// early commit following a crashed frame: If an embedder pauses the navigation
// that causing the early commit, they can then execute javascript in the
// committed frame and blink's PolicyContainer would not be connected to
// browser's PolicyContainerHost. This has limited impact in practice.
IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       CheckRendererPolicyContainerAccessesAfterCrash) {
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html"));
  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());

  std::string add_referrer_script = R"(
    new Promise(async resolve => {
      // Make sure the DOM is ready before modifying <header>.
      if (document.readyState !== "complete")
        await new Promise(r => addEventListener("DOMContentLoaded", r));

      // Add <meta name="referrer" content=$1>
      let meta = document.createElement("meta");
      meta.name = "referrer";
      meta.content= $1;
      document.head.append(meta);
      resolve(true);
    })
  )";

  // Navigate to A.
  EXPECT_TRUE(NavigateToURL(shell(), url_a));

  // The referrer policy is initially default.
  EXPECT_EQ(network::mojom::ReferrerPolicy::kDefault,
            current_frame_host()->policy_container_host()->referrer_policy());

  // Deliver a new referrer policy in the renderer with
  // <meta name="referrer" content="none">.
  EXPECT_EQ(true, EvalJs(current_frame_host(),
                         content::JsReplace(add_referrer_script, "none")));
  // The policy is propagated to the browser by policy container.
  EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
            current_frame_host()->policy_container_host()->referrer_policy());

  // Crash the renderer.
  RenderProcessHost* renderer_process = current_frame_host()->GetProcess();
  RenderProcessHostWatcher crash_observer(
      renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  renderer_process->Shutdown(0);
  crash_observer.Wait();

  // Start navigation to B, but don't commit yet.
  TestNavigationManager manager(web_contents(), url_b);
  shell()->LoadURL(url_b);
  EXPECT_TRUE(manager.WaitForRequestStart());

  NavigationRequest* navigation_request =
      NavigationRequest::From(manager.GetNavigationHandle());
  if (ShouldSkipEarlyCommitPendingForCrashedFrame()) {
    EXPECT_EQ(navigation_request->GetAssociatedRFHType(),
              NavigationRequest::AssociatedRenderFrameHostType::SPECULATIVE);
  } else {
    // Policy container is properly initialized in the early committed
    // RenderFrameHost.
    EXPECT_TRUE(current_frame_host()->policy_container_host());
    EXPECT_EQ(navigation_request->GetAssociatedRFHType(),
              NavigationRequest::AssociatedRenderFrameHostType::CURRENT);

    // The policy is copied from the previous RFH following the crash.
    EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
              current_frame_host()->policy_container_host()->referrer_policy());

    // Deliver a new referrer policy in the renderer with
    // <meta name="referrer" content="origin">. Using "origin" to differ from
    // previous "none" and "default".

    EXPECT_EQ(true, EvalJs(current_frame_host(),
                           content::JsReplace(add_referrer_script, "origin")));
    // The previous referrer policy is not propagated to the browser since
    // blink's policy container is not linked with browser's policy container
    // host. Never (a.k.a. "None") is copied from the previous RFH.
    EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
              current_frame_host()->policy_container_host()->referrer_policy());
  }

  // Let the navigation finish.
  ASSERT_TRUE(manager.WaitForNavigationFinished());

  EXPECT_EQ(url_b,
            web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());

  // The referrer policy is initialized to default during the navigation (no
  // referrer-policy header in the response).
  EXPECT_EQ(network::mojom::ReferrerPolicy::kDefault,
            current_frame_host()->policy_container_host()->referrer_policy());

  // Deliver a new referrer policy in the renderer with
  // <meta name="referrer" content="strict-origin">. Using "strict-origin" to
  // differ again from the previous uses.
  EXPECT_EQ(true,
            EvalJs(current_frame_host(),
                   content::JsReplace(add_referrer_script, "strict-origin")));
  // This time renderer's policy container properly propagates the referrer
  // policy to the browser.
  EXPECT_EQ(network::mojom::ReferrerPolicy::kStrictOrigin,
            current_frame_host()->policy_container_host()->referrer_policy());
}

// Test that chrome error pages are loaded with a default policy container and
// don't inherit policies.
IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest, FailedNavigation) {
  // Perform a navigation setting referrer policy.
  GURL url = embedded_test_server()->GetURL(
      "/set-header?Referrer-Policy: no-referrer");
  EXPECT_TRUE(NavigateToURL(shell(), url));
  EXPECT_EQ(network::mojom::ReferrerPolicy::kNever,
            current_frame_host()->policy_container_host()->referrer_policy());

  // Now open a popup with an unreachable url.
  GURL error_url = embedded_test_server()->GetURL("/close-socket");
  ShellAddedObserver shell_observer;
  EXPECT_TRUE(
      ExecJs(current_frame_host(), JsReplace("window.open($1)", error_url)));
  WebContentsImpl* popup_webcontents =
      static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents());
  WaitForLoadStop(popup_webcontents);

  NavigationEntry* entry =
      popup_webcontents->GetController().GetLastCommittedEntry();
  EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType());
  RenderFrameHostImpl* popup_frame =
      popup_webcontents->GetPrimaryFrameTree().root()->current_frame_host();
  EXPECT_EQ(network::mojom::ReferrerPolicy::kDefault,
            popup_frame->policy_container_host()->referrer_policy());
}

IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       ContentSecurityPoliciesFromHeader) {
  GURL url = embedded_test_server()->GetURL(
      "a.com", "/set-header?Content-Security-Policy: img-src 'none'");
  ASSERT_TRUE(NavigateToURL(shell(), url));
  ASSERT_EQ(1u, current_frame_host()
                    ->policy_container_host()
                    ->policies()
                    .content_security_policies.size());
  auto& csp = current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .content_security_policies[0];
  EXPECT_EQ("img-src 'none'", csp->header->header_value);
  EXPECT_TRUE(csp->directives.find(network::mojom::CSPDirectiveName::ImgSrc) !=
              csp->directives.end());
  EXPECT_EQ(0u, csp->directives.at(network::mojom::CSPDirectiveName::ImgSrc)
                    ->sources.size());
}

IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       ContentSecurityPolicyFromMeta) {
  GURL page = embedded_test_server()->GetURL("a.com", "/empty.html");
  ASSERT_TRUE(NavigateToURL(shell(), page));
  EXPECT_EQ(0u, current_frame_host()
                    ->policy_container_host()
                    ->policies()
                    .content_security_policies.size());
  ASSERT_TRUE(ExecJs(current_frame_host(),
                     "var meta = document.createElement('meta');"
                     "meta.httpEquiv = 'content-security-policy';"
                     "meta.content = \"img-src 'none'\";"
                     "document.head.appendChild(meta);"));
  ASSERT_EQ(1u, current_frame_host()
                    ->policy_container_host()
                    ->policies()
                    .content_security_policies.size());
  auto& csp = current_frame_host()
                  ->policy_container_host()
                  ->policies()
                  .content_security_policies[0];
  EXPECT_EQ("img-src 'none'", csp->header->header_value);
  EXPECT_TRUE(csp->directives.find(network::mojom::CSPDirectiveName::ImgSrc) !=
              csp->directives.end());
  EXPECT_EQ(0u, csp->directives.at(network::mojom::CSPDirectiveName::ImgSrc)
                    ->sources.size());
}

// Regression test for https://crbug.com/1196372. This test passes if the
// renderer does not crash.
IN_PROC_BROWSER_TEST_F(PolicyContainerHostBrowserTest,
                       PolicyContainerOnClonedDocumentNoCrash) {
  GURL page = embedded_test_server()->GetURL("a.com", "/empty.html");
  GURL img_url = embedded_test_server()->GetURL("a.com", "/blank.jpg");
  ASSERT_TRUE(NavigateToURL(shell(), page));

  // Create an empty iframe and clone its document. Then execute a javascript
  // URL inside the iframe. This will create a new ExecutionContext, but with
  // the same PolicyContainer. However, the clone we created is still around and
  // still has an ExecutionContext, which has not PolicyContainer anymore. The
  // following code used to trigger a nullptr dereference, while it should not.
  ASSERT_TRUE(ExecJs(current_frame_host(), JsReplace(R"(
      new Promise((resolve, reject) => {
        let iframe = document.createElement('iframe');
        document.body.appendChild(iframe);
        let d = iframe.contentDocument.cloneNode(true);
        iframe.src =
            'javascript:"<script>top.postMessage(\'ready\',\'*\');</script>"';
        function addStyleSheet() {
          let css = 'html { background: url($1); }';
          let style = d.createElement('style');
          d.head.appendChild(style);
          style.type = 'text/css';
          style.appendChild(d.createTextNode(css));
        };
        window.addEventListener('message', e => {
          if (e.source !== iframe.contentWindow) return;
          if (e.data !== 'ready') return;
          addStyleSheet();
          resolve();
        });
      });
  )",
                                                     img_url)));
}

}  // namespace content