Ensure frames have the necessary proxies to be discoverable by name.

With OOPIF, when a frame navigates another frame via window.open(url,
name), we must ensure that a proxy for the target named frame exists
in the caller frame's process.  This doesn't actually involve creating
this proxy in all SiteInstances in the current BrowsingInstance.
Although Blink uses a global namespace for frame names, it only
returns the target named frame if the caller is allowed to navigate
it, as decided by Frame::canNavigate (corresponding spec: [1]).
Currently, canNavigate may return true in these cases (some also
involve sandboxing checks):

1. Target is the top frame in caller's frame tree.
2. Target is a descendant of the caller.
3. Caller is same-origin with the target or one of its ancestors.
4. The target is a top-level frame and is the caller's opener.
5. The target is a top-level frame and caller has same origin as
   an ancestor of the target's opener.

If caller and target frames are in different processes, the right
proxy for the target frame is already created in cases 1-4 by a
combination of CreateProxiesForNewRenderFrameHost,
CreateProxiesForChildFrame, and SwapOutOldFrame.  This CL adds support
to handle case 5 (corresponding to [2] in Frame::canNavigate).  Note
that we only have to do this for new frames that have a name, or if a
frame without a name acquires a name later.

[1] Case 4 in https://html.spec.whatwg.org/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name,
and https://html.spec.whatwg.org/#familiar-with that it references

[2] https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/frame/Frame.cpp&sq=package:chromium&l=247
    if (!targetFrame.tree().parent()) {
        ...
	if (canAccessAncestor(origin, targetFrame.client()->opener()))
            return true;

BUG=511474

Review URL: https://codereview.chromium.org/1250473008

Cr-Commit-Position: refs/heads/master@{#340381}
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index c7314b4..9c844cf 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -1276,7 +1276,10 @@
 }
 
 void RenderFrameHostImpl::OnDidChangeName(const std::string& name) {
+  std::string old_name = frame_tree_node()->frame_name();
   frame_tree_node()->SetFrameName(name);
+  if (old_name.empty() && !name.empty())
+    frame_tree_node_->render_manager()->CreateProxiesForNewNamedFrame();
   delegate_->DidChangeName(this, name);
 }
 
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index 7efd7df..f0f688b7 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -1521,6 +1521,37 @@
   }
 }
 
+void RenderFrameHostManager::CreateProxiesForNewNamedFrame() {
+  // TODO(alexmos): use SiteIsolationPolicy::AreCrossProcessFramesPossible once
+  // https://codereview.chromium.org/1208143002/ lands.
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kSitePerProcess))
+    return;
+
+  DCHECK(!frame_tree_node_->frame_name().empty());
+
+  // If this is a top-level frame, create proxies for this node in the
+  // SiteInstances of its opener's ancestors, which are allowed to discover
+  // this frame by name (see https://crbug.com/511474 and part 4 of
+  // https://html.spec.whatwg.org/#the-rules-for-choosing-a-browsing-context-
+  // given-a-browsing-context-name).
+  FrameTreeNode* opener = frame_tree_node_->opener();
+  if (!opener || !frame_tree_node_->IsMainFrame())
+    return;
+  SiteInstance* current_instance = render_frame_host_->GetSiteInstance();
+
+  // Start from opener's parent.  There's no need to create a proxy in the
+  // opener's SiteInstance, since new windows are always first opened in the
+  // same SiteInstance as their opener, and if the new window navigates
+  // cross-site, that proxy would be created as part of swapping out.
+  for (FrameTreeNode* ancestor = opener->parent(); ancestor;
+       ancestor = ancestor->parent()) {
+    RenderFrameHostImpl* ancestor_rfh = ancestor->current_frame_host();
+    if (ancestor_rfh->GetSiteInstance() != current_instance)
+      CreateRenderFrameProxy(ancestor_rfh->GetSiteInstance());
+  }
+}
+
 scoped_ptr<RenderFrameHostImpl> RenderFrameHostManager::CreateRenderFrameHost(
     SiteInstance* site_instance,
     int view_routing_id,
diff --git a/content/browser/frame_host/render_frame_host_manager.h b/content/browser/frame_host/render_frame_host_manager.h
index 2e36292..f131a7e 100644
--- a/content/browser/frame_host/render_frame_host_manager.h
+++ b/content/browser/frame_host/render_frame_host_manager.h
@@ -462,6 +462,11 @@
   // supporting calls like window.opener.opener.frames[x][y]).
   void CreateOpenerProxies(SiteInstance* instance);
 
+  // Ensure that this frame has proxies in all SiteInstances that can discover
+  // this frame by name (e.g., via window.open("", "frame_name")).  See
+  // https://crbug.com/511474.
+  void CreateProxiesForNewNamedFrame();
+
   // Returns a routing ID for the current FrameTreeNode's opener node in the
   // given SiteInstance.  May return a routing ID of either a RenderFrameHost
   // (if opener's current or pending RFH has SiteInstance |instance|) or a
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 7f1f029..49d9ab2 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -76,6 +76,20 @@
   return received_messages;
 }
 
+// Helper function to perform a window.open from the |caller_frame| targeting a
+// frame with the specified name.
+void NavigateNamedFrame(const ToRenderFrameHost& caller_frame,
+                        const GURL& url,
+                        const std::string& name) {
+  bool success = false;
+  EXPECT_TRUE(ExecuteScriptAndExtractBool(
+      caller_frame,
+      "window.domAutomationController.send("
+      "    !!window.open('" + url.spec() + "', '" + name + "'));",
+      &success));
+  EXPECT_TRUE(success);
+}
+
 class RedirectNotificationObserver : public NotificationObserver {
  public:
   // Register to listen for notifications of the given type from either a
@@ -2780,4 +2794,109 @@
   EXPECT_TRUE(success);
 }
 
+// Verify that named frames are discoverable from their opener's ancestors.
+// See https://crbug.com/511474.
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
+                       DiscoverNamedFrameFromAncestorOfOpener) {
+  GURL main_url(
+      embedded_test_server()->GetURL("a.com", "/site_per_process_main.html"));
+  NavigateToURL(shell(), main_url);
+
+  // It is safe to obtain the root frame tree node here, as it doesn't change.
+  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+                            ->GetFrameTree()
+                            ->root();
+
+  // Navigate first child cross-site.
+  GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  NavigateFrameToURL(root->child_at(0), frame_url);
+
+  // Open a popup named "foo" from the first child.
+  Shell* foo_shell = OpenPopup(root->child_at(0)->current_frame_host(),
+                               GURL(url::kAboutBlankURL), "foo");
+  EXPECT_TRUE(foo_shell);
+
+  // Check that a proxy was created for the "foo" popup in a.com.
+  FrameTreeNode* foo_root =
+      static_cast<WebContentsImpl*>(foo_shell->web_contents())
+          ->GetFrameTree()
+          ->root();
+  SiteInstance* site_instance_a = root->current_frame_host()->GetSiteInstance();
+  RenderFrameProxyHost* popup_rfph_for_a =
+      foo_root->render_manager()->GetRenderFrameProxyHost(site_instance_a);
+  EXPECT_TRUE(popup_rfph_for_a);
+
+  // Verify that the main frame can find the "foo" popup by name.  If
+  // window.open targets the correct frame, the "foo" popup's current URL
+  // should be updated to |named_frame_url|.
+  GURL named_frame_url(embedded_test_server()->GetURL("c.com", "/title2.html"));
+  NavigateNamedFrame(shell()->web_contents(), named_frame_url, "foo");
+  EXPECT_TRUE(WaitForLoadStop(foo_shell->web_contents()));
+  EXPECT_EQ(named_frame_url, foo_root->current_url());
+
+  // Navigate the popup cross-site and ensure it's still reachable via
+  // window.open from the main frame.
+  GURL d_url(embedded_test_server()->GetURL("d.com", "/title3.html"));
+  NavigateToURL(foo_shell, d_url);
+  EXPECT_EQ(d_url, foo_root->current_url());
+  NavigateNamedFrame(shell()->web_contents(), named_frame_url, "foo");
+  EXPECT_TRUE(WaitForLoadStop(foo_shell->web_contents()));
+  EXPECT_EQ(named_frame_url, foo_root->current_url());
+}
+
+// Similar to DiscoverNamedFrameFromAncestorOfOpener, but check that if a
+// window is created without a name and acquires window.name later, it will
+// still be discoverable from its opener's ancestors.  Also, instead of using
+// an opener's ancestor, this test uses a popup with same origin as that
+// ancestor. See https://crbug.com/511474.
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
+                       DiscoverFrameAfterSettingWindowName) {
+  GURL main_url(
+      embedded_test_server()->GetURL("a.com", "/site_per_process_main.html"));
+  NavigateToURL(shell(), main_url);
+
+  // It is safe to obtain the root frame tree node here, as it doesn't change.
+  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+                            ->GetFrameTree()
+                            ->root();
+
+  // Open a same-site popup from the main frame.
+  GURL a_com_url(embedded_test_server()->GetURL("a.com", "/title3.html"));
+  Shell* a_com_shell =
+      OpenPopup(root->child_at(0)->current_frame_host(), a_com_url, "");
+  EXPECT_TRUE(a_com_shell);
+
+  // Navigate first child on main frame cross-site.
+  GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  NavigateFrameToURL(root->child_at(0), frame_url);
+
+  // Open an unnamed popup from the first child frame.
+  Shell* foo_shell = OpenPopup(root->child_at(0)->current_frame_host(),
+                               GURL(url::kAboutBlankURL), "");
+  EXPECT_TRUE(foo_shell);
+
+  // There should be no proxy created for the "foo" popup in a.com, since
+  // there's no way for the two a.com frames to access it yet.
+  FrameTreeNode* foo_root =
+      static_cast<WebContentsImpl*>(foo_shell->web_contents())
+          ->GetFrameTree()
+          ->root();
+  SiteInstance* site_instance_a = root->current_frame_host()->GetSiteInstance();
+  EXPECT_FALSE(
+      foo_root->render_manager()->GetRenderFrameProxyHost(site_instance_a));
+
+  // Set window.name in the popup's frame.
+  EXPECT_TRUE(ExecuteScript(foo_shell->web_contents(), "window.name = 'foo'"));
+
+  // A proxy for the popup should now exist in a.com.
+  EXPECT_TRUE(
+      foo_root->render_manager()->GetRenderFrameProxyHost(site_instance_a));
+
+  // Verify that the a.com popup can now find the "foo" popup by name.
+  GURL named_frame_url(embedded_test_server()->GetURL("c.com", "/title2.html"));
+  NavigateNamedFrame(a_com_shell->web_contents(), named_frame_url, "foo");
+  EXPECT_TRUE(WaitForLoadStop(foo_shell->web_contents()));
+  EXPECT_EQ(named_frame_url, foo_root->current_url());
+}
+
 }  // namespace content
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 7be4267..158201d 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1734,6 +1734,13 @@
       partition_id,
       session_storage_namespace);
 
+  // If the new frame has a name, make sure any SiteInstances that can find
+  // this named frame have proxies for it.  Must be called after
+  // SetSessionStorageNamespace, since this calls CreateRenderView, which uses
+  // GetSessionStorageNamespace.
+  if (!params.frame_name.empty())
+    new_contents->GetRenderManager()->CreateProxiesForNewNamedFrame();
+
   // Save the window for later if we're not suppressing the opener (since it
   // will be shown immediately).
   if (!params.opener_suppressed) {