summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-28 19:23:37 +0000
committernasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-28 19:23:37 +0000
commit0720b5393454a0f31039eb9aea7e6ab9c4793f3e (patch)
tree146cbf874db4e241dbde5e0fd2489a5b5fe152be
parentfb9b3fbc72c7c1aa1bcd7c7b6995ed75283e9c3e (diff)
downloadchromium_src-0720b5393454a0f31039eb9aea7e6ab9c4793f3e.zip
chromium_src-0720b5393454a0f31039eb9aea7e6ab9c4793f3e.tar.gz
chromium_src-0720b5393454a0f31039eb9aea7e6ab9c4793f3e.tar.bz2
Support frame tree propagation between renderers in the same browsing instance.
BUG=128767 TEST= Review URL: https://chromiumcodereview.appspot.com/10827078 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@153710 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--content/browser/renderer_host/render_view_host_delegate.h4
-rw-r--r--content/browser/renderer_host/render_view_host_impl.cc34
-rw-r--r--content/browser/renderer_host/render_view_host_impl.h17
-rw-r--r--content/browser/renderer_host/render_view_host_manager_browsertest.cc259
-rw-r--r--content/browser/web_contents/render_view_host_manager.cc40
-rw-r--r--content/browser/web_contents/render_view_host_manager.h5
-rw-r--r--content/browser/web_contents/web_contents_impl.cc20
-rw-r--r--content/browser/web_contents/web_contents_impl.h1
-rw-r--r--content/common/content_constants_internal.cc13
-rw-r--r--content/common/content_constants_internal.h18
-rw-r--r--content/common/view_messages.h52
-rw-r--r--content/content_common.gypi4
-rw-r--r--content/content_tests.gypi2
-rw-r--r--content/public/renderer/render_view_observer.h2
-rw-r--r--content/renderer/render_view_impl.cc183
-rw-r--r--content/renderer/render_view_impl.h61
-rw-r--r--content/test/data/click-noreferrer-links.html2
-rw-r--r--content/test/data/frame_tree/1-1.html13
-rw-r--r--content/test/data/frame_tree/1-2.html10
-rw-r--r--content/test/data/frame_tree/1-3.html13
-rw-r--r--content/test/data/frame_tree/2-1.html18
-rw-r--r--content/test/data/frame_tree/2-2.html18
-rw-r--r--content/test/data/frame_tree/2-3.html10
-rw-r--r--content/test/data/frame_tree/2-4.html12
-rw-r--r--content/test/data/frame_tree/3-1.html10
-rw-r--r--content/test/data/frame_tree/top.html47
-rw-r--r--content/test/data/post_message.html7
-rw-r--r--content/test/data/post_message2.html21
28 files changed, 877 insertions, 19 deletions
diff --git a/content/browser/renderer_host/render_view_host_delegate.h b/content/browser/renderer_host/render_view_host_delegate.h
index bfbac69..c14fc78 100644
--- a/content/browser/renderer_host/render_view_host_delegate.h
+++ b/content/browser/renderer_host/render_view_host_delegate.h
@@ -215,6 +215,10 @@ class CONTENT_EXPORT RenderViewHostDelegate {
// entirely loaded).
virtual void DidChangeLoadProgress(double progress) {}
+ // The RenderView has changed its frame hierarchy, so we need to update all
+ // other renderers interested in this event.
+ virtual void DidUpdateFrameTree(RenderViewHost* rvh) {}
+
// The RenderView's main frame document element is ready. This happens when
// the document has finished parsing.
virtual void DocumentAvailableInMainFrame(RenderViewHost* render_view_host) {}
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index 4e1c71f..65036d9 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -11,6 +11,7 @@
#include "base/i18n/rtl.h"
#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
#include "base/message_loop.h"
#include "base/stl_util.h"
#include "base/string_util.h"
@@ -28,6 +29,7 @@
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/common/accessibility_messages.h"
+#include "content/common/content_constants_internal.h"
#include "content/common/desktop_notification_messages.h"
#include "content/common/drag_messages.h"
#include "content/common/inter_process_time_ticks_converter.h"
@@ -930,6 +932,7 @@ bool RenderViewHostImpl::OnMessageReceived(const IPC::Message& msg) {
OnDomOperationResponse)
IPC_MESSAGE_HANDLER(AccessibilityHostMsg_Notifications,
OnAccessibilityNotifications)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_FrameTreeUpdated, OnFrameTreeUpdated)
// Have the super handle all other messages.
IPC_MESSAGE_UNHANDLED(
handled = RenderWidgetHostImpl::OnMessageReceived(msg))
@@ -1134,6 +1137,21 @@ void RenderViewHostImpl::OnMsgNavigate(const IPC::Message& msg) {
FilterURL(policy, renderer_id, true, &validated_params.password_form.action);
delegate_->DidNavigate(this, validated_params);
+
+ // For top level navigations, if there is no frame tree present for this
+ // instance (for example when the window is first created), then create
+ // an unnamed one with the proper frame id from the renderer.
+ // This should be done after we called DidNavigate, since updating the frame
+ // tree expects the render view being updated to be the active one.
+ if (content::PageTransitionIsMainFrame(validated_params.transition)) {
+ if (frame_tree_.empty()) {
+ base::DictionaryValue tree;
+ tree.SetString(content::kFrameTreeNodeNameKey, std::string());
+ tree.SetInteger(content::kFrameTreeNodeIdKey, validated_params.frame_id);
+ base::JSONWriter::Write(&tree, &frame_tree_);
+ delegate_->DidUpdateFrameTree(this);
+ }
+ }
}
void RenderViewHostImpl::OnMsgUpdateState(int32 page_id,
@@ -1620,6 +1638,17 @@ webkit_glue::WebPreferences RenderViewHostImpl::GetWebkitPreferences() {
return delegate_->GetWebkitPrefs();
}
+void RenderViewHostImpl::UpdateFrameTree(
+ int process_id,
+ int route_id,
+ const std::string& frame_tree) {
+ frame_tree_ = frame_tree;
+ Send(new ViewMsg_UpdateFrameTree(GetRoutingID(),
+ process_id,
+ route_id,
+ frame_tree_));
+}
+
void RenderViewHostImpl::UpdateWebkitPreferences(
const webkit_glue::WebPreferences& prefs) {
Send(new ViewMsg_UpdateWebPreferences(GetRoutingID(), prefs));
@@ -1855,6 +1884,11 @@ void RenderViewHostImpl::OnDomOperationResponse(
content::Details<DomOperationNotificationDetails>(&details));
}
+void RenderViewHostImpl::OnFrameTreeUpdated(const std::string& frame_tree) {
+ frame_tree_ = frame_tree;
+ delegate_->DidUpdateFrameTree(this);
+}
+
void RenderViewHostImpl::SetSwappedOut(bool is_swapped_out) {
is_swapped_out_ = is_swapped_out;
diff --git a/content/browser/renderer_host/render_view_host_impl.h b/content/browser/renderer_host/render_view_host_impl.h
index af0d5d2..991b8db 100644
--- a/content/browser/renderer_host/render_view_host_impl.h
+++ b/content/browser/renderer_host/render_view_host_impl.h
@@ -393,6 +393,17 @@ class CONTENT_EXPORT RenderViewHostImpl
// User rotated the screen. Calls the "onorientationchange" Javascript hook.
void SendOrientationChangeEvent(int orientation);
+ const std::string& frame_tree() const {
+ return frame_tree_;
+ }
+
+ // Updates the frame tree for this RVH and sends an IPC down to the renderer
+ // process to keep them in sync. For more details, see the comments on
+ // ViewHostMsg_FrameTreeUpdated.
+ void UpdateFrameTree(int process_id,
+ int route_id,
+ const std::string& frame_tree);
+
void set_save_accessibility_tree_for_testing(bool save) {
save_accessibility_tree_for_testing_ = save;
}
@@ -542,6 +553,7 @@ class CONTENT_EXPORT RenderViewHostImpl
void OnRunFileChooser(const FileChooserParams& params);
void OnDomOperationResponse(const std::string& json_string,
int automation_id);
+ void OnFrameTreeUpdated(const std::string& frame_tree);
#if defined(OS_MACOSX) || defined(OS_ANDROID)
void OnMsgShowPopup(const ViewHostMsg_ShowPopup_Params& params);
@@ -637,6 +649,11 @@ class CONTENT_EXPORT RenderViewHostImpl
// for unit testing.
bool send_accessibility_updated_notifications_;
+ // A JSON serialized representation of the frame tree for the current document
+ // in the render view. For more details, see the comments on
+ // ViewHostMsg_FrameTreeUpdated.
+ std::string frame_tree_;
+
// The most recently received accessibility tree - for unit testing only.
AccessibilityNodeData accessibility_tree_;
diff --git a/content/browser/renderer_host/render_view_host_manager_browsertest.cc b/content/browser/renderer_host/render_view_host_manager_browsertest.cc
index 657f005..dedb84f 100644
--- a/content/browser/renderer_host/render_view_host_manager_browsertest.cc
+++ b/content/browser/renderer_host/render_view_host_manager_browsertest.cc
@@ -3,9 +3,11 @@
// found in the LICENSE file.
#include "base/file_util.h"
+#include "base/json/json_reader.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/utf_string_conversions.h"
+#include "content/common/content_constants_internal.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
@@ -27,6 +29,64 @@
#include "net/base/net_util.h"
#include "net/test/test_server.h"
+namespace {
+
+bool CompareTrees(base::DictionaryValue* first, base::DictionaryValue* second) {
+ string16 name1;
+ string16 name2;
+ if (!first->GetString(content::kFrameTreeNodeNameKey, &name1) ||
+ !second->GetString(content::kFrameTreeNodeNameKey, &name2))
+ return false;
+ if (name1 != name2)
+ return false;
+
+ int id1 = 0;
+ int id2 = 0;
+ if (!first->GetInteger(content::kFrameTreeNodeIdKey, &id1) ||
+ !second->GetInteger(content::kFrameTreeNodeIdKey, &id2)) {
+ return false;
+ }
+ if (id1 != id2)
+ return false;
+
+ ListValue* subtree1 = NULL;
+ ListValue* subtree2 = NULL;
+ bool result1 = first->GetList(content::kFrameTreeNodeSubtreeKey, &subtree1);
+ bool result2 = second->GetList(content::kFrameTreeNodeSubtreeKey, &subtree2);
+ if (!result1 && !result2)
+ return true;
+ if (!result1 || !result2)
+ return false;
+
+ if (subtree1->GetSize() != subtree2->GetSize())
+ return false;
+
+ base::DictionaryValue* child1 = NULL;
+ base::DictionaryValue* child2 = NULL;
+ for (size_t i = 0; i < subtree1->GetSize(); ++i) {
+ if (!subtree1->GetDictionary(i, &child1) ||
+ !subtree2->GetDictionary(i, &child2)) {
+ return false;
+ }
+ if (!CompareTrees(child1, child2))
+ return false;
+ }
+
+ return true;
+}
+
+base::DictionaryValue* GetTree(content::RenderViewHostImpl* rvh) {
+ std::string frame_tree = rvh->frame_tree();
+ EXPECT_FALSE(frame_tree.empty());
+ base::Value* v = base::JSONReader::Read(frame_tree);
+ base::DictionaryValue* tree = NULL;
+ EXPECT_TRUE(v->IsType(base::Value::TYPE_DICTIONARY));
+ EXPECT_TRUE(v->GetAsDictionary(&tree));
+ return tree;
+}
+
+} // namespace
+
namespace content {
class RenderViewHostManagerTest : public ContentBrowserTest {
@@ -402,6 +462,8 @@ IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
// 2) Fail to post a message from "foo" to opener with the wrong target origin.
// 3) Post a message from "foo" to opener, which replies back to "foo".
// 4) Post a message from _blank to "foo".
+// 5) Post a message from "foo" to a subframe of opener, which replies back.
+// 6) Post a message from _blank to a subframe of "foo".
IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
SupportCrossProcessPostMessage) {
// Start two servers with different sites.
@@ -486,6 +548,7 @@ IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
L"'http://google.com'));",
&success));
EXPECT_TRUE(success);
+ ASSERT_FALSE(opener_manager->GetSwappedOutRenderViewHost(orig_site_instance));
// 3) Post a message from the foo window to the opener. The opener will
// reply, causing the foo window to update its own title.
@@ -497,6 +560,7 @@ IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
L"window.domAutomationController.send(postToOpener('msg','*'));",
&success));
EXPECT_TRUE(success);
+ ASSERT_FALSE(opener_manager->GetSwappedOutRenderViewHost(orig_site_instance));
title_observer.Wait();
// We should have received only 1 message in the opener and "foo" tabs,
@@ -531,6 +595,36 @@ IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest,
// This postMessage should have created a swapped out RVH for the new
// SiteInstance in the target=_blank window.
EXPECT_TRUE(new_manager->GetSwappedOutRenderViewHost(foo_site_instance));
+
+ NavigateToURL(new_shell, https_server.GetURL("files/post_message2.html"));
+
+ // 5) Now verify that posting a message from the foo window to a subframe of
+ // the opener window works fine. The opener subframe will reply, causing the
+ // foo window to update its own title.
+ WindowedNotificationObserver title_observer3(
+ NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
+ Source<WebContents>(new_shell->web_contents()));
+ EXPECT_TRUE(ExecuteJavaScriptAndExtractBool(
+ foo_contents->GetRenderViewHost(), L"",
+ L"window.domAutomationController.send(postToOpenerFrame('msg3','*'));",
+ &success));
+ EXPECT_TRUE(success);
+ title_observer3.Wait();
+ EXPECT_EQ(ASCIIToUTF16("msg3"), new_shell->web_contents()->GetTitle());
+
+ // 6) Lastly, verify that the _blank window can post a message to a subframe
+ // of the foo window. The subframe of foo will set the foo window title and
+ // will reply, setting the _blank window title.
+ WindowedNotificationObserver title_observer4(
+ NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
+ Source<WebContents>(new_shell2->web_contents()));
+ EXPECT_TRUE(ExecuteJavaScriptAndExtractBool(
+ new_contents->GetRenderViewHost(), L"",
+ L"window.domAutomationController.send(postToFooFrame('msg4'));",
+ &success));
+ EXPECT_TRUE(success);
+ title_observer4.Wait();
+ EXPECT_EQ(ASCIIToUTF16("msg4"), new_shell2->web_contents()->GetTitle());
}
// Test for crbug.com/116192. Navigations to a window's opener should
@@ -1039,4 +1133,169 @@ IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest, LeakingRenderViewHosts) {
EXPECT_EQ(0U, rvh_observers.GetNumObservers());
}
+// Test for correct propagation of the frame hierarchy across processes in the
+// same BrowsingInstance. The test starts by navigating to a page that has
+// multiple nested frames. It then opens two windows and navigates each one
+// to a separate site, so at the end we have 3 SiteInstances. The opened
+// windows have swapped out RenderViews corresponding to the opener, so those
+// swapped out views must have a matching frame hierarchy. The test checks
+// that frame hierarchies are kept in sync through navigations, reloading, and
+// JavaScript manipulation of the frame tree.
+IN_PROC_BROWSER_TEST_F(RenderViewHostManagerTest, FrameTreeUpdates) {
+ // Start two servers to allow using different sites.
+ EXPECT_TRUE(test_server()->Start());
+ net::TestServer https_server(
+ net::TestServer::TYPE_HTTPS,
+ net::TestServer::kLocalhost,
+ FilePath(FILE_PATH_LITERAL("content/test/data")));
+ EXPECT_TRUE(https_server.Start());
+
+ GURL frame_tree_url(test_server()->GetURL("files/frame_tree/top.html"));
+
+ // Replace the 127.0.0.1 with localhost, which will give us a different
+ // site instance.
+ GURL::Replacements replacements;
+ std::string new_host("localhost");
+ replacements.SetHostStr(new_host);
+ GURL remote_frame = test_server()->GetURL(
+ "files/frame_tree/1-1.html").ReplaceComponents(replacements);
+
+ bool success = false;
+ base::DictionaryValue* frames = NULL;
+ base::ListValue* subtree = NULL;
+
+ // First navigate to a page with no frames and ensure the frame tree has no
+ // subtrees.
+ NavigateToURL(shell(), test_server()->GetURL("files/simple_page.html"));
+ WebContents* opener_contents = shell()->web_contents();
+ RenderViewHostManager* opener_rvhm = static_cast<WebContentsImpl*>(
+ opener_contents)->GetRenderManagerForTesting();
+ frames = GetTree(opener_rvhm->current_host());
+ EXPECT_FALSE(frames->GetList(content::kFrameTreeNodeSubtreeKey, &subtree));
+
+ NavigateToURL(shell(), frame_tree_url);
+ frames = GetTree(opener_rvhm->current_host());
+ EXPECT_TRUE(frames->GetList(content::kFrameTreeNodeSubtreeKey, &subtree));
+ EXPECT_TRUE(subtree->GetSize() == 3);
+
+ scoped_refptr<SiteInstance> orig_site_instance(
+ opener_contents->GetSiteInstance());
+ EXPECT_TRUE(orig_site_instance != NULL);
+
+ ShellAddedObserver shell_observer1;
+ EXPECT_TRUE(ExecuteJavaScriptAndExtractBool(
+ opener_contents->GetRenderViewHost(), L"",
+ L"window.domAutomationController.send(openWindow('1-3.html'));",
+ &success));
+ EXPECT_TRUE(success);
+
+ Shell* shell1 = shell_observer1.GetShell();
+ WebContents* contents1 = shell1->web_contents();
+ WaitForLoadStop(contents1);
+ RenderViewHostManager* rvhm1 = static_cast<WebContentsImpl*>(
+ contents1)->GetRenderManagerForTesting();
+ EXPECT_EQ("/files/frame_tree/1-3.html", contents1->GetURL().path());
+
+ // Now navigate the new window to a different SiteInstance.
+ NavigateToURL(shell1, https_server.GetURL("files/title1.html"));
+ EXPECT_EQ("/files/title1.html", contents1->GetURL().path());
+ scoped_refptr<SiteInstance> site_instance1(
+ contents1->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, site_instance1);
+
+ ShellAddedObserver shell_observer2;
+ EXPECT_TRUE(ExecuteJavaScriptAndExtractBool(
+ opener_contents->GetRenderViewHost(), L"",
+ L"window.domAutomationController.send(openWindow('../title2.html'));",
+ &success));
+ EXPECT_TRUE(success);
+
+ Shell* shell2 = shell_observer2.GetShell();
+ WebContents* contents2 = shell2->web_contents();
+ WaitForLoadStop(contents2);
+ EXPECT_EQ("/files/title2.html", contents2->GetURL().path());
+
+ // Navigate the second new window to a different SiteInstance as well.
+ NavigateToURL(shell2, remote_frame);
+ EXPECT_EQ("/files/frame_tree/1-1.html", contents2->GetURL().path());
+ scoped_refptr<SiteInstance> site_instance2(
+ contents2->GetSiteInstance());
+ EXPECT_NE(orig_site_instance, site_instance2);
+ EXPECT_NE(site_instance1, site_instance2);
+
+ RenderViewHostManager* rvhm2 = static_cast<WebContentsImpl*>(
+ contents2)->GetRenderManagerForTesting();
+
+ EXPECT_TRUE(CompareTrees(
+ GetTree(opener_rvhm->current_host()),
+ GetTree(opener_rvhm->GetSwappedOutRenderViewHost(site_instance1))));
+ EXPECT_TRUE(CompareTrees(
+ GetTree(opener_rvhm->current_host()),
+ GetTree(opener_rvhm->GetSwappedOutRenderViewHost(site_instance2))));
+
+ EXPECT_TRUE(CompareTrees(
+ GetTree(rvhm1->current_host()),
+ GetTree(rvhm1->GetSwappedOutRenderViewHost(orig_site_instance))));
+ EXPECT_TRUE(CompareTrees(
+ GetTree(rvhm2->current_host()),
+ GetTree(rvhm2->GetSwappedOutRenderViewHost(orig_site_instance))));
+
+ // Verify that the frame trees from different windows aren't equal.
+ EXPECT_FALSE(CompareTrees(
+ GetTree(opener_rvhm->current_host()), GetTree(rvhm1->current_host())));
+ EXPECT_FALSE(CompareTrees(
+ GetTree(opener_rvhm->current_host()), GetTree(rvhm2->current_host())));
+
+ // Reload the original page, which will cause subframe ids to change. This
+ // will ensure that the ids are properly replicated across reload.
+ NavigateToURL(shell(), frame_tree_url);
+
+ EXPECT_TRUE(CompareTrees(
+ GetTree(opener_rvhm->current_host()),
+ GetTree(opener_rvhm->GetSwappedOutRenderViewHost(site_instance1))));
+ EXPECT_TRUE(CompareTrees(
+ GetTree(opener_rvhm->current_host()),
+ GetTree(opener_rvhm->GetSwappedOutRenderViewHost(site_instance2))));
+
+ EXPECT_FALSE(CompareTrees(
+ GetTree(opener_rvhm->current_host()), GetTree(rvhm1->current_host())));
+ EXPECT_FALSE(CompareTrees(
+ GetTree(opener_rvhm->current_host()), GetTree(rvhm2->current_host())));
+
+ // Now let's ensure that using JS to add/remove frames results in proper
+ // updates.
+ EXPECT_TRUE(ExecuteJavaScriptAndExtractBool(
+ opener_contents->GetRenderViewHost(), L"",
+ L"window.domAutomationController.send(removeFrame());",
+ &success));
+ EXPECT_TRUE(success);
+ frames = GetTree(opener_rvhm->current_host());
+ EXPECT_TRUE(frames->GetList(content::kFrameTreeNodeSubtreeKey, &subtree));
+ EXPECT_EQ(subtree->GetSize(), 2U);
+
+ // Create a load observer for the iframe that will be created by the
+ // JavaScript code we will execute.
+ WindowedNotificationObserver load_observer(
+ NOTIFICATION_LOAD_STOP,
+ content::Source<NavigationController>(
+ &opener_contents->GetController()));
+ EXPECT_TRUE(ExecuteJavaScriptAndExtractBool(
+ opener_contents->GetRenderViewHost(), L"",
+ L"window.domAutomationController.send(addFrame());",
+ &success));
+ EXPECT_TRUE(success);
+ load_observer.Wait();
+
+ frames = GetTree(opener_rvhm->current_host());
+ EXPECT_TRUE(frames->GetList(content::kFrameTreeNodeSubtreeKey, &subtree));
+ EXPECT_EQ(subtree->GetSize(), 3U);
+
+ EXPECT_TRUE(CompareTrees(
+ GetTree(opener_rvhm->current_host()),
+ GetTree(opener_rvhm->GetSwappedOutRenderViewHost(site_instance1))));
+ EXPECT_TRUE(CompareTrees(
+ GetTree(opener_rvhm->current_host()),
+ GetTree(opener_rvhm->GetSwappedOutRenderViewHost(site_instance2))));
+}
+
} // namespace content
diff --git a/content/browser/web_contents/render_view_host_manager.cc b/content/browser/web_contents/render_view_host_manager.cc
index 62fec79..0ad2906 100644
--- a/content/browser/web_contents/render_view_host_manager.cc
+++ b/content/browser/web_contents/render_view_host_manager.cc
@@ -239,6 +239,26 @@ void RenderViewHostManager::DidNavigateMainFrame(
}
}
+void RenderViewHostManager::DidUpdateFrameTree(
+ RenderViewHost* render_view_host) {
+ CHECK_EQ(render_view_host, current_host());
+
+ RenderViewHostImpl* render_view_host_impl = static_cast<RenderViewHostImpl*>(
+ render_view_host);
+
+ for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin();
+ iter != swapped_out_hosts_.end();
+ ++iter) {
+ DCHECK_NE(iter->second->GetSiteInstance(),
+ current_host()->GetSiteInstance());
+
+ iter->second->UpdateFrameTree(
+ render_view_host_impl->GetProcess()->GetID(),
+ render_view_host_impl->GetRoutingID(),
+ render_view_host_impl->frame_tree());
+ }
+}
+
void RenderViewHostManager::SetWebUIPostCommit(WebUIImpl* web_ui) {
DCHECK(!web_ui_.get());
web_ui_.reset(web_ui);
@@ -495,6 +515,15 @@ SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry(
if (entry.IsViewSourceMode())
return SiteInstance::CreateForURL(browser_context, dest_url);
+ // If we are navigating from a blank SiteInstance to a WebUI, make sure we
+ // create a new SiteInstance.
+ const WebUIControllerFactory* web_ui_factory =
+ content::GetContentClient()->browser()->GetWebUIControllerFactory();
+ if (web_ui_factory &&
+ web_ui_factory->UseWebUIForURL(browser_context, dest_url)) {
+ return SiteInstance::CreateForURL(browser_context, dest_url);
+ }
+
// Normally the "site" on the SiteInstance is set lazily when the load
// actually commits. This is to support better process sharing in case
// the site redirects to some other site: we want to use the destination
@@ -607,6 +636,15 @@ int RenderViewHostManager::CreateRenderView(
if (success) {
// Don't show the view until we get a DidNavigate from it.
new_render_view_host->GetView()->Hide();
+
+ // If we are creating a swapped out RVH, send a message to update its
+ // frame tree based on the active RVH for this RenderViewHostManager.
+ if (swapped_out) {
+ new_render_view_host->UpdateFrameTree(
+ current_host()->GetProcess()->GetID(),
+ current_host()->GetRoutingID(),
+ current_host()->frame_tree());
+ }
} else if (!swapped_out) {
CancelPending();
}
@@ -920,7 +958,7 @@ bool RenderViewHostManager::IsSwappedOut(RenderViewHost* rvh) {
swapped_out_hosts_.end();
}
-RenderViewHost* RenderViewHostManager::GetSwappedOutRenderViewHost(
+RenderViewHostImpl* RenderViewHostManager::GetSwappedOutRenderViewHost(
SiteInstance* instance) {
RenderViewHostMap::iterator iter = swapped_out_hosts_.find(instance->GetId());
if (iter != swapped_out_hosts_.end())
diff --git a/content/browser/web_contents/render_view_host_manager.h b/content/browser/web_contents/render_view_host_manager.h
index 9be59d3..e523a72 100644
--- a/content/browser/web_contents/render_view_host_manager.h
+++ b/content/browser/web_contents/render_view_host_manager.h
@@ -162,6 +162,9 @@ class CONTENT_EXPORT RenderViewHostManager
// Called when a renderer's main frame navigates.
void DidNavigateMainFrame(content::RenderViewHost* render_view_host);
+ // Called when a renderer has navigated and when its frame tree is updated.
+ void DidUpdateFrameTree(content::RenderViewHost* render_view_host);
+
// Helper method to create a RenderViewHost. If |swapped_out| is true, it
// will be initially placed on the swapped out hosts list. Otherwise, it
// will be used for a pending cross-site navigation.
@@ -220,7 +223,7 @@ class CONTENT_EXPORT RenderViewHostManager
bool IsSwappedOut(content::RenderViewHost* rvh);
// Returns the swapped out RenderViewHost for the given SiteInstance, if any.
- content::RenderViewHost* GetSwappedOutRenderViewHost(
+ content::RenderViewHostImpl* GetSwappedOutRenderViewHost(
content::SiteInstance* instance);
private:
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 077e5b3..a9d512e 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2768,6 +2768,10 @@ void WebContentsImpl::DidChangeLoadProgress(double progress) {
delegate_->LoadProgressChanged(this, progress);
}
+void WebContentsImpl::DidUpdateFrameTree(RenderViewHost* rvh) {
+ render_manager_.DidUpdateFrameTree(rvh);
+}
+
void WebContentsImpl::DocumentAvailableInMainFrame(
RenderViewHost* render_view_host) {
FOR_EACH_OBSERVER(WebContentsObserver, observers_,
@@ -2862,6 +2866,12 @@ void WebContentsImpl::RouteMessageEvent(
ViewMsg_PostMessage_Params new_params(params);
+ // If the renderer has changed while the post message is being routed,
+ // drop the message, as it will not be delivered to the right target.
+ if (GetRenderViewHost()->GetProcess()->GetID() != params.target_process_id)
+ return;
+ DCHECK(params.target_frame_id != 0);
+
// If there is a source_routing_id, translate it to the routing ID for
// the equivalent swapped out RVH in the target process. If we need
// to create a swapped out RVH for the source tab, we create its opener
@@ -3109,6 +3119,16 @@ int WebContentsImpl::CreateOpenerRenderViews(SiteInstance* instance) {
if (opener_)
opener_route_id = opener_->CreateOpenerRenderViews(instance);
+ // If any of the renderers for this WebContents has the same SiteInstance,
+ // use it.
+ if (render_manager_.current_host()->GetSiteInstance() == instance)
+ return render_manager_.current_host()->GetRoutingID();
+
+ RenderViewHostImpl* rvh = render_manager_.GetSwappedOutRenderViewHost(
+ instance);
+ if (rvh)
+ return rvh->GetRoutingID();
+
// Create a swapped out RenderView in the given SiteInstance if none exists,
// setting its opener to the given route_id. Return the new view's route_id.
return render_manager_.CreateRenderView(instance, opener_route_id, true);
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index a74bc9e..654ec64 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -322,6 +322,7 @@ class CONTENT_EXPORT WebContentsImpl
content::RenderViewHost* render_view_host) OVERRIDE;
virtual void DidCancelLoading() OVERRIDE;
virtual void DidChangeLoadProgress(double progress) OVERRIDE;
+ virtual void DidUpdateFrameTree(content::RenderViewHost* rvh) OVERRIDE;
virtual void DocumentAvailableInMainFrame(
content::RenderViewHost* render_view_host) OVERRIDE;
virtual void DocumentOnLoadCompletedInMainFrame(
diff --git a/content/common/content_constants_internal.cc b/content/common/content_constants_internal.cc
new file mode 100644
index 0000000..03f638e
--- /dev/null
+++ b/content/common/content_constants_internal.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/common/content_constants_internal.h"
+
+namespace content {
+
+const char kFrameTreeNodeNameKey[] = "name";
+const char kFrameTreeNodeIdKey[] = "id";
+const char kFrameTreeNodeSubtreeKey[] = "subtree";
+
+} // namespace content
diff --git a/content/common/content_constants_internal.h b/content/common/content_constants_internal.h
new file mode 100644
index 0000000..8020f17
--- /dev/null
+++ b/content/common/content_constants_internal.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_COMMON_CONTENT_CONSTANTS_INTERNAL_H_
+#define CONTENT_COMMON_CONTENT_CONSTANTS_INTERNAL_H_
+
+namespace content {
+
+// Keys used for serializing the frame tree of a renderer process, used for
+// ViewMsg_UpdateFrameTree and ViewHostMsg_FrameTreeUpdated.
+extern const char kFrameTreeNodeNameKey[];
+extern const char kFrameTreeNodeIdKey[];
+extern const char kFrameTreeNodeSubtreeKey[];
+
+} // namespace content
+
+#endif // CONTENT_COMMON_CONTENT_CONSTANTS_INTERNAL_H_
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index 09b881f..f0c1bd7 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -597,9 +597,16 @@ IPC_STRUCT_BEGIN(ViewMsg_PostMessage_Params)
// When sent to the browser, this is the routing ID of the source frame in
// the source process. The browser replaces it with the routing ID of the
- // equivalent (swapped out) frame in the destination process. Set to
- // MSG_ROUTING_NONE if the source frame isn't supported (e.g., subframes).
+ // equivalent (swapped out) frame in the destination process.
IPC_STRUCT_MEMBER(int, source_routing_id)
+ // The identifier of the source frame in the source process.
+ IPC_STRUCT_MEMBER(int, source_frame_id)
+
+ // The full set of identifiers to uniquely describe the target frame. See
+ // the comment on ViewMsg_FrameTreeUpdated for details.
+ IPC_STRUCT_MEMBER(int, target_process_id)
+ IPC_STRUCT_MEMBER(int, target_routing_id)
+ IPC_STRUCT_MEMBER(int, target_frame_id)
// The origin of the source frame.
IPC_STRUCT_MEMBER(string16, source_origin)
@@ -1063,6 +1070,16 @@ IPC_MESSAGE_ROUTED4(ViewMsg_ScriptEvalRequest,
IPC_MESSAGE_ROUTED1(ViewMsg_PostMessageEvent,
ViewMsg_PostMessage_Params)
+// Sends a JSON serialized frame tree to RenderView along with the process id
+// and route id of the source renderer.
+//
+// This message must be sent to swapped out RenderViews every time the browser
+// receives a ViewHostMsg_FrameTreeUpdated message.
+IPC_MESSAGE_ROUTED3(ViewMsg_UpdateFrameTree,
+ int, /* the child process id of the active renderer */
+ int, /* route_id of the active renderer */
+ std::string /* json encoded frame tree */)
+
// Request for the renderer to evaluate an xpath to a frame and insert css
// into that frame's document. See ViewMsg_ScriptEvalRequest for details on
// allowed xpath expressions.
@@ -2232,3 +2249,34 @@ IPC_MESSAGE_ROUTED3(ViewHostMsg_PepperPluginHung,
// Screen was rotated. Dispatched to the onorientationchange javascript API.
IPC_MESSAGE_ROUTED1(ViewMsg_OrientationChangeEvent,
int /* orientation */)
+
+// Chrome allows JavaScript calls to be routed across process boundaries. To
+// achieve this, each active RenderView in the source process has a swapped out
+// "mirror" in the target process. The active RenderView and its mirror
+// need to have identical frame tree structure, so calls originating in and
+// targeting subframes can be routed properly. This is achieved by each active
+// RenderView sending a ViewHostMsg_FrameTreeUpdated message to the browser,
+// which in turn sends the update to the corresponding mirror RenderView(s)
+// through the ViewMsg_UpdateFrameTree. We use best effort to keep these
+// trees synchronized across processes.
+//
+// When routing JavaScript calls across processes, the target information
+// is kept in the renderer process instead of the browser process. This design
+// was chosen because frame ids are allocated by the renderer process. If the
+// browser was to keep a mapping of the frame ids across processes, it would
+// require an extra IPC message with the newly allocated frame ids, as a
+// response to this particular message.
+//
+// The frame tree for a RenderView is serialized to JSON, so it can be sent to
+// the browser process. Each node in the tree is an object with three
+// properties:
+// * id - (integer) the frame identifier in this RenderView
+// * name - (string) the name of the frame, if one has been assigned
+// * subtree - an array of the same type of objects for each frame that is a
+// direct child of the current frame. This property can be omitted if
+// there are no direct child frames, so less data is transferred.
+//
+// This message must be sent on any events that modify the tree structure or
+// the names of any frames.
+IPC_MESSAGE_ROUTED1(ViewHostMsg_FrameTreeUpdated,
+ std::string /* json encoded frame tree */)
diff --git a/content/content_common.gypi b/content/content_common.gypi
index 3701239..e32632d 100644
--- a/content/content_common.gypi
+++ b/content/content_common.gypi
@@ -147,9 +147,11 @@
'common/clipboard_messages.cc',
'common/clipboard_messages.h',
'common/compositor_util.cc',
+ 'common/content_constants_internal.cc',
+ 'common/content_constants_internal.h',
+ 'common/content_export.h',
'common/content_message_generator.cc',
'common/content_message_generator.h',
- 'common/content_export.h',
'common/content_paths.cc',
'common/css_colors.h',
'common/database_messages.h',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index ff1fe0c..5138529 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -588,6 +588,8 @@
'browser/speech/speech_recognition_browsertest.cc',
'browser/webkit_browsertest.cc',
'browser/worker_host/test/worker_browsertest.cc',
+ 'common/content_constants_internal.cc',
+ 'common/content_constants_internal.h',
'renderer/browser_plugin/mock_browser_plugin.h',
'renderer/browser_plugin/mock_browser_plugin.cc',
'renderer/browser_plugin/mock_browser_plugin_manager.h',
diff --git a/content/public/renderer/render_view_observer.h b/content/public/renderer/render_view_observer.h
index 94f294e..744d9d8 100644
--- a/content/public/renderer/render_view_observer.h
+++ b/content/public/renderer/render_view_observer.h
@@ -68,6 +68,8 @@ class CONTENT_EXPORT RenderViewObserver : public IPC::Listener,
virtual void DidCompleteClientRedirect(WebKit::WebFrame* frame,
const WebKit::WebURL& from) {}
virtual void DidCreateDocumentElement(WebKit::WebFrame* frame) {}
+ virtual void FrameCreated(WebKit::WebFrame* parent,
+ WebKit::WebFrame* frame) {}
virtual void FrameDetached(WebKit::WebFrame* frame) {}
virtual void FrameWillClose(WebKit::WebFrame* frame) {}
virtual void WillSubmitForm(WebKit::WebFrame* frame,
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 4cf9502..411ea82 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -13,6 +13,7 @@
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
+#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/message_loop_proxy.h"
@@ -29,6 +30,7 @@
#include "content/common/appcache/appcache_dispatcher.h"
#include "content/common/child_thread.h"
#include "content/common/clipboard_messages.h"
+#include "content/common/content_constants_internal.h"
#include "content/common/database_messages.h"
#include "content/common/drag_messages.h"
#include "content/common/fileapi/file_system_dispatcher.h"
@@ -366,6 +368,14 @@ static RenderViewImpl* FromRoutingID(int32 routing_id) {
ChildThread::current()->ResolveRoute(routing_id));
}
+static WebKit::WebFrame* FindFrameByID(WebKit::WebFrame* root, int frame_id) {
+ for (WebFrame* frame = root; frame; frame = frame->traverseNext(false)) {
+ if (frame->identifier() == frame_id)
+ return frame;
+ }
+ return NULL;
+}
+
static void GetRedirectChain(WebDataSource* ds, std::vector<GURL>* result) {
WebVector<WebURL> urls;
ds->redirectChain(urls);
@@ -470,6 +480,31 @@ static bool IsNonLocalTopLevelNavigation(const GURL& url,
return false;
}
+// Recursively walks the frame tree and serializes it to JSON as described in
+// the comment for ViewMsg_UpdateFrameTree. If |exclude_frame_subtree| is not
+// NULL, the subtree for the frame is not included in the serialized form.
+// This is used when a frame is going to be removed from the tree.
+static void ConstructFrameTree(WebKit::WebFrame* frame,
+ WebKit::WebFrame* exclude_frame_subtree,
+ base::DictionaryValue* dict) {
+ dict->SetString(content::kFrameTreeNodeNameKey,
+ UTF16ToUTF8(frame->assignedName()).c_str());
+ dict->SetInteger(content::kFrameTreeNodeIdKey, frame->identifier());
+
+ WebFrame* child = frame->firstChild();
+ ListValue* children = new ListValue();
+ for (; child; child = child->nextSibling()) {
+ if (child == exclude_frame_subtree)
+ continue;
+
+ base::DictionaryValue* d = new base::DictionaryValue();
+ ConstructFrameTree(child, exclude_frame_subtree, d);
+ children->Append(d);
+ }
+ if (children->GetSize() > 0)
+ dict->Set(content::kFrameTreeNodeSubtreeKey, children);
+}
+
///////////////////////////////////////////////////////////////////////////////
struct RenderViewImpl::PendingFileChooser {
@@ -582,6 +617,10 @@ RenderViewImpl::RenderViewImpl(
guest_to_embedder_channel_(guest_to_embedder_channel),
guest_pp_instance_(0),
guest_uninitialized_context_(NULL),
+ updating_frame_tree_(false),
+ pending_frame_tree_update_(false),
+ target_process_id_(0),
+ target_routing_id_(0),
ALLOW_THIS_IN_INITIALIZER_LIST(pepper_delegate_(this)) {
set_throttle_input_events(renderer_prefs.throttle_input_events);
routing_id_ = routing_id;
@@ -692,7 +731,6 @@ RenderViewImpl::RenderViewImpl(
// If we have an opener_id but we weren't created by a renderer, then
// it's the browser asking us to set our opener to another RenderView.
- // TODO(creis): This doesn't yet handle openers that are subframes.
if (opener_id != MSG_ROUTING_NONE && !is_renderer_created) {
RenderViewImpl* opener_view = FromRoutingID(opener_id);
if (opener_view)
@@ -702,7 +740,7 @@ RenderViewImpl::RenderViewImpl(
// If we are initially swapped out, navigate to kSwappedOutURL.
// This ensures we are in a unique origin that others cannot script.
if (is_swapped_out_)
- NavigateToSwappedOutURL();
+ NavigateToSwappedOutURL(webview()->mainFrame());
}
RenderViewImpl::~RenderViewImpl() {
@@ -1007,6 +1045,7 @@ bool RenderViewImpl::OnMessageReceived(const IPC::Message& message) {
IPC_MESSAGE_HANDLER(ViewMsg_EnableViewSourceMode, OnEnableViewSourceMode)
IPC_MESSAGE_HANDLER(JavaBridgeMsg_Init, OnJavaBridgeInit)
IPC_MESSAGE_HANDLER(ViewMsg_SetAccessibilityMode, OnSetAccessibilityMode)
+ IPC_MESSAGE_HANDLER(ViewMsg_UpdateFrameTree, OnUpdatedFrameTree)
// Have the super handle all other messages.
IPC_MESSAGE_UNHANDLED(handled = RenderWidget::OnMessageReceived(message))
@@ -1900,6 +1939,11 @@ void RenderViewImpl::didStopLoading() {
is_loading_ = false;
+ if (pending_frame_tree_update_) {
+ pending_frame_tree_update_ = false;
+ SendUpdatedFrameTree(NULL);
+ }
+
// NOTE: For now we're doing the safest thing, and sending out notification
// when done loading. This currently isn't an issue as the favicon is only
// displayed when done loading. Ideally we would send notification when
@@ -2487,7 +2531,25 @@ WebCookieJar* RenderViewImpl::cookieJar(WebFrame* frame) {
return &cookie_jar_;
}
+void RenderViewImpl::didCreateFrame(WebFrame* parent, WebFrame* child) {
+ if (is_loading_) {
+ pending_frame_tree_update_ = true;
+ return;
+ }
+ if (!updating_frame_tree_)
+ SendUpdatedFrameTree(NULL);
+}
+
void RenderViewImpl::frameDetached(WebFrame* frame) {
+ if (is_loading_) {
+ pending_frame_tree_update_ = true;
+ // Make sure observers are notified, even if we return right away.
+ FOR_EACH_OBSERVER(RenderViewObserver, observers_, FrameDetached(frame));
+ return;
+ }
+ if (!updating_frame_tree_)
+ SendUpdatedFrameTree(frame);
+
FOR_EACH_OBSERVER(RenderViewObserver, observers_, FrameDetached(frame));
}
@@ -3640,6 +3702,66 @@ WebGraphicsContext3D* RenderViewImpl::CreateGraphicsContext3D(
}
}
+// The browser process needs to know the shape of the tree, as well as the names
+// and ids of all frames. This allows it to properly route JavaScript messages
+// across processes and frames. The serialization format is described in the
+// comments of the ViewMsg_FrameTreeUpdated message.
+// This function sends those updates to the browser and updates the RVH
+// corresponding to this object. It must be called on any events that modify
+// the tree structure or the names of any frames.
+void RenderViewImpl::SendUpdatedFrameTree(
+ WebKit::WebFrame* exclude_frame_subtree) {
+ std::string json;
+ base::DictionaryValue tree;
+
+ ConstructFrameTree(webview()->mainFrame(), exclude_frame_subtree, &tree);
+ base::JSONWriter::Write(&tree, &json);
+
+ Send(new ViewHostMsg_FrameTreeUpdated(routing_id_, json));
+}
+
+void RenderViewImpl::CreateFrameTree(WebKit::WebFrame* frame,
+ DictionaryValue* frame_tree) {
+ NavigateToSwappedOutURL(frame);
+
+ string16 name;
+ if (frame_tree->GetString(content::kFrameTreeNodeNameKey, &name) &&
+ name != string16()) {
+ frame->setName(name);
+ }
+
+ int remote_id;
+ if (frame_tree->GetInteger(content::kFrameTreeNodeIdKey, &remote_id))
+ active_frame_id_map_.insert(std::pair<int, int>(frame->identifier(),
+ remote_id));
+
+ ListValue* children;
+ if (!frame_tree->GetList(content::kFrameTreeNodeSubtreeKey, &children))
+ return;
+
+ base::DictionaryValue* child;
+ for (size_t i = 0; i < children->GetSize(); ++i) {
+ if (!children->GetDictionary(i, &child))
+ continue;
+ WebElement element = frame->document().createElement("iframe");
+ if (frame->document().body().appendChild(element)) {
+ WebFrame* subframe = WebFrame::fromFrameOwnerElement(element);
+ if (subframe)
+ CreateFrameTree(subframe, child);
+ } else {
+ LOG(ERROR) << "Failed to append created iframe element.";
+ }
+ }
+}
+
+WebKit::WebFrame* RenderViewImpl::GetFrameByMappedID(int frame_id) {
+ std::map<int, int>::iterator it = active_frame_id_map_.find(frame_id);
+ if (it == active_frame_id_map_.end())
+ return NULL;
+
+ return FindFrameByID(webview()->mainFrame(), it->second);
+}
+
void RenderViewImpl::EnsureMediaStreamImpl() {
if (!RenderThreadImpl::current()) // Will be NULL during unit tests.
return;
@@ -3855,7 +3977,8 @@ void RenderViewImpl::dispatchIntent(
}
bool RenderViewImpl::willCheckAndDispatchMessageEvent(
- WebKit::WebFrame* source,
+ WebKit::WebFrame* sourceFrame,
+ WebKit::WebFrame* targetFrame,
WebKit::WebSecurityOrigin target_origin,
WebKit::WebDOMMessageEvent event) {
if (!is_swapped_out_)
@@ -3870,11 +3993,25 @@ bool RenderViewImpl::willCheckAndDispatchMessageEvent(
// Include the routing ID for the source frame, which the browser process
// will translate into the routing ID for the equivalent frame in the target
// process.
- // TODO(creis): Support source subframes.
params.source_routing_id = MSG_ROUTING_NONE;
- RenderViewImpl* source_view = FromWebView(source->view());
+ RenderViewImpl* source_view = FromWebView(sourceFrame->view());
if (source_view)
params.source_routing_id = source_view->routing_id();
+ params.source_frame_id = sourceFrame->identifier();
+
+ // Include the process, route, and frame IDs of the target frame. This allows
+ // the browser to detect races between this message being sent and the target
+ // frame no longer being valid.
+ params.target_process_id = target_process_id_;
+ params.target_routing_id = target_routing_id_;
+
+ std::map<int,int>::iterator it = active_frame_id_map_.find(
+ targetFrame->identifier());
+ if (it != active_frame_id_map_.end()) {
+ params.target_frame_id = it->second;
+ } else {
+ params.target_frame_id = 0;
+ }
Send(new ViewHostMsg_RouteMessageEvent(routing_id_, params));
return true;
@@ -4604,16 +4741,19 @@ void RenderViewImpl::OnScriptEvalRequest(const string16& frame_xpath,
void RenderViewImpl::OnPostMessageEvent(
const ViewMsg_PostMessage_Params& params) {
- // TODO(creis): Support sending to subframes.
- WebFrame* frame = webview()->mainFrame();
+ // Find the target frame of this message. The source tags the message with
+ // |target_frame_id|, so use it to locate the frame.
+ WebFrame* frame = FindFrameByID(webview()->mainFrame(),
+ params.target_frame_id);
+ if (!frame)
+ return;
// Find the source frame if it exists.
- // TODO(creis): Support source subframes.
WebFrame* source_frame = NULL;
if (params.source_routing_id != MSG_ROUTING_NONE) {
RenderViewImpl* source_view = FromRoutingID(params.source_routing_id);
if (source_view)
- source_frame = source_view->webview()->mainFrame();
+ source_frame = source_view->GetFrameByMappedID(params.source_frame_id);
}
// Create an event with the message. The final parameter to initMessageEvent
@@ -4962,7 +5102,7 @@ void RenderViewImpl::OnSwapOut(const ViewMsg_SwapOut_Params& params) {
// run a second time, thanks to a check in FrameLoader::stopLoading.
// TODO(creis): Need to add a better way to do this that avoids running the
// beforeunload handler. For now, we just run it a second time silently.
- NavigateToSwappedOutURL();
+ NavigateToSwappedOutURL(webview()->mainFrame());
// Let WebKit know that this view is hidden so it can drop resources and
// stop compositing.
@@ -4973,14 +5113,14 @@ void RenderViewImpl::OnSwapOut(const ViewMsg_SwapOut_Params& params) {
Send(new ViewHostMsg_SwapOut_ACK(routing_id_, params));
}
-void RenderViewImpl::NavigateToSwappedOutURL() {
+void RenderViewImpl::NavigateToSwappedOutURL(WebKit::WebFrame* frame) {
// We use loadRequest instead of loadHTMLString because the former commits
// synchronously. Otherwise a new navigation can interrupt the navigation
// to content::kSwappedOutURL. If that happens to be to the page we had been
// showing, then WebKit will never send a commit and we'll be left spinning.
GURL swappedOutURL(content::kSwappedOutURL);
WebURLRequest request(swappedOutURL);
- webview()->mainFrame()->loadRequest(request);
+ frame->loadRequest(request);
}
void RenderViewImpl::OnClosePage() {
@@ -5892,3 +6032,22 @@ void RenderViewImpl::OnJavaBridgeInit() {
java_bridge_dispatcher_ = new JavaBridgeDispatcher(this);
#endif
}
+
+void RenderViewImpl::OnUpdatedFrameTree(
+ int process_id,
+ int route_id,
+ const std::string& frame_tree) {
+ base::DictionaryValue* frames = NULL;
+ scoped_ptr<base::Value> tree(base::JSONReader::Read(frame_tree));
+ if (tree.get() && tree->IsType(base::Value::TYPE_DICTIONARY))
+ tree->GetAsDictionary(&frames);
+
+ updating_frame_tree_ = true;
+ active_frame_id_map_.clear();
+
+ target_process_id_ = process_id;
+ target_routing_id_ = route_id;
+ CreateFrameTree(webview()->mainFrame(), frames);
+
+ updating_frame_tree_ = false;
+}
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 0864788..bd23238 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -18,6 +18,7 @@
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/timer.h"
+#include "base/values.h"
#include "build/build_config.h"
#include "content/common/content_export.h"
#include "content/common/edit_command.h"
@@ -539,6 +540,8 @@ class RenderViewImpl : public RenderWidget,
WebKit::WebFrame* frame,
WebKit::WebApplicationCacheHostClient* client);
virtual WebKit::WebCookieJar* cookieJar(WebKit::WebFrame* frame);
+ virtual void didCreateFrame(WebKit::WebFrame* parent,
+ WebKit::WebFrame* child);
virtual void frameDetached(WebKit::WebFrame* frame);
virtual void willClose(WebKit::WebFrame* frame);
virtual void loadURLExternally(WebKit::WebFrame* frame,
@@ -677,7 +680,8 @@ class RenderViewImpl : public RenderWidget,
virtual void willOpenSocketStream(
WebKit::WebSocketStreamHandle* handle);
virtual bool willCheckAndDispatchMessageEvent(
- WebKit::WebFrame* source,
+ WebKit::WebFrame* sourceFrame,
+ WebKit::WebFrame* targetFrame,
WebKit::WebSecurityOrigin targetOrigin,
WebKit::WebDOMMessageEvent event) OVERRIDE;
virtual WebKit::WebString userAgentOverride(
@@ -1040,6 +1044,10 @@ class RenderViewImpl : public RenderWidget,
void OnJavaBridgeInit();
+ void OnUpdatedFrameTree(int process_id,
+ int route_id,
+ const std::string& frame_tree);
+
// Adding a new message handler? Please add it in alphabetical order above
// and put it in the same position in the .cc file.
@@ -1057,6 +1065,27 @@ class RenderViewImpl : public RenderWidget,
WebKit::WebGraphicsContext3D* CreateGraphicsContext3D(
const WebKit::WebGraphicsContext3D::Attributes& attributes);
+ // This method walks the entire frame tree for this RenderView and sends an
+ // update to the browser process as described in the
+ // ViewHostMsg_FrameTreeUpdated comments. If |exclude_frame_subtree|
+ // frame is non-NULL, the subtree starting at that frame not included in the
+ // serialized form.
+ // This is used when a frame is going to be removed from the tree.
+ void SendUpdatedFrameTree(WebKit::WebFrame* exclude_frame_subtree);
+
+ // Recursively creates a DOM frame tree starting with |frame|, based on
+ // |frame_tree|. For each node, the frame is navigated to the swapped out URL,
+ // the name (if present) is set on it, and all the subframes are created
+ // and added to the DOM.
+ void CreateFrameTree(WebKit::WebFrame* frame, DictionaryValue* frame_tree);
+
+ // If this is a swapped out RenderView, which maintains a copy of the frame
+ // tree of an active RenderView, we keep a map from frame ids in this view to
+ // the frame ids of the active view for each corresponding frame.
+ // This method uses the map to find the frame in this RenderView that
+ // corresponds to the frame in the active RenderView specified by |frame_id|.
+ WebKit::WebFrame* GetFrameByMappedID(int frame_id);
+
void EnsureMediaStreamImpl();
// This callback is triggered when DownloadFavicon completes, either
@@ -1105,7 +1134,7 @@ class RenderViewImpl : public RenderWidget,
bool replace);
// Make this RenderView show an empty, unscriptable page.
- void NavigateToSwappedOutURL();
+ void NavigateToSwappedOutURL(WebKit::WebFrame* frame);
// If we initiated a navigation, this function will populate |document_state|
// with the navigation information saved in OnNavigate().
@@ -1469,6 +1498,34 @@ class RenderViewImpl : public RenderWidget,
// before the guest_to_embedder_channel was ready.
WebKit::WebGraphicsContext3D::Attributes guest_attributes_;
+ // Boolean indicating whether we are in the process of creating the frame
+ // tree for this renderer in response to ViewMsg_UpdateFrameTree. If true,
+ // we won't be sending ViewHostMsg_FrameTreeUpdated messages back to the
+ // browser, as those will be redundant.
+ bool updating_frame_tree_;
+
+ // Boolean indicating that the frame tree has changed, but a message has not
+ // been sent to the browser because a page has been loading. This helps
+ // avoid extra messages being sent to the browser when navigating away from a
+ // page with subframes, which will be destroyed. Instead, a single message
+ // is sent when the load is stopped with the final state of the frame tree.
+ //
+ // TODO(nasko): Relying on the is_loading_ means that frame tree updates will
+ // not be sent until *all* subframes have completed loading. This can cause
+ // JavaScript calls to fail, if they occur prior to the first update message
+ // being sent. This will be fixed by bug http://crbug.com/145014.
+ bool pending_frame_tree_update_;
+
+ // If this render view is a swapped out "mirror" of an active render view in a
+ // different process, we record the process id and route id for the active RV.
+ // For further details, see the comments on ViewHostMsg_FrameTreeUpdated.
+ int target_process_id_;
+ int target_routing_id_;
+
+ // A map of the current process's frame ids to ids in the remote active render
+ // view, if this is a swapped out render view.
+ std::map<int, int> active_frame_id_map_;
+
// NOTE: pepper_delegate_ should be last member because its constructor calls
// AddObservers method of RenderViewImpl from c-tor.
content::PepperPluginDelegateImpl pepper_delegate_;
diff --git a/content/test/data/click-noreferrer-links.html b/content/test/data/click-noreferrer-links.html
index f9b1a0d..6229aa1 100644
--- a/content/test/data/click-noreferrer-links.html
+++ b/content/test/data/click-noreferrer-links.html
@@ -75,4 +75,6 @@
<a href="https://REPLACE_WITH_HOST_AND_PORT/files/title2.html" id="noref_link"
rel="noreferrer">rel=noreferrer</a><br>
+<iframe id="frame1" src="frame_tree/1-1.html"></iframe>
+
</html>
diff --git a/content/test/data/frame_tree/1-1.html b/content/test/data/frame_tree/1-1.html
new file mode 100644
index 0000000..b3abee0
--- /dev/null
+++ b/content/test/data/frame_tree/1-1.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+</head>
+<body>
+
+Frame 1-1.
+<br>
+
+<iframe src="2-1.html"></iframe>
+<iframe src="2-2.html"></iframe>
+
+</body>
+</html>
diff --git a/content/test/data/frame_tree/1-2.html b/content/test/data/frame_tree/1-2.html
new file mode 100644
index 0000000..c7f6427
--- /dev/null
+++ b/content/test/data/frame_tree/1-2.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body>
+
+Frame 1-2.
+<br>
+
+</body>
+</html>
diff --git a/content/test/data/frame_tree/1-3.html b/content/test/data/frame_tree/1-3.html
new file mode 100644
index 0000000..9e83f42
--- /dev/null
+++ b/content/test/data/frame_tree/1-3.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+</head>
+<body>
+
+Frame 1-3.
+<br>
+
+<iframe src="2-3.html"></iframe>
+<iframe src="2-4.html"></iframe>
+
+</body>
+</html>
diff --git a/content/test/data/frame_tree/2-1.html b/content/test/data/frame_tree/2-1.html
new file mode 100644
index 0000000..7560d6a
--- /dev/null
+++ b/content/test/data/frame_tree/2-1.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<script>
+window.addEventListener("message", messageReceived, false);
+
+function messageReceived(event) {
+ window.top.document.title = event.data;
+ event.source.postMessage(event.data, "*");
+}
+</script>
+</head>
+<body>
+
+Frame 2-1.
+<br>
+
+</body>
+</html>
diff --git a/content/test/data/frame_tree/2-2.html b/content/test/data/frame_tree/2-2.html
new file mode 100644
index 0000000..c008a19
--- /dev/null
+++ b/content/test/data/frame_tree/2-2.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<script>
+window.addEventListener("message", messageReceived, false);
+
+function messageReceived(event) {
+ window.top.document.title = event.data;
+ event.source.postMessage(event.data, "*");
+}
+</script>
+</head>
+<body>
+
+Frame 2-2.
+<br>
+
+</body>
+</html>
diff --git a/content/test/data/frame_tree/2-3.html b/content/test/data/frame_tree/2-3.html
new file mode 100644
index 0000000..22e80bd
--- /dev/null
+++ b/content/test/data/frame_tree/2-3.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body>
+
+Frame 2-3.
+<br>
+
+</body>
+</html>
diff --git a/content/test/data/frame_tree/2-4.html b/content/test/data/frame_tree/2-4.html
new file mode 100644
index 0000000..3bdea4b
--- /dev/null
+++ b/content/test/data/frame_tree/2-4.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+<body>
+
+Frame 2-4.
+<br>
+
+<iframe src="3-1.html"></iframe>
+
+</body>
+</html>
diff --git a/content/test/data/frame_tree/3-1.html b/content/test/data/frame_tree/3-1.html
new file mode 100644
index 0000000..4e6d292
--- /dev/null
+++ b/content/test/data/frame_tree/3-1.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body>
+
+Frame 3-1.
+<br>
+
+</body>
+</html>
diff --git a/content/test/data/frame_tree/top.html b/content/test/data/frame_tree/top.html
new file mode 100644
index 0000000..37cd446
--- /dev/null
+++ b/content/test/data/frame_tree/top.html
@@ -0,0 +1,47 @@
+<html>
+<head>
+<script>
+ window.name = "foo";
+
+ function simulateClick(target) {
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent("click", true, true, window,
+ 0, 0, 0, 0, 0, false, false,
+ false, false, 0, null);
+
+ return target.dispatchEvent(evt);
+ }
+
+ function openWindow(url) {
+ window.open(url);
+ return true;
+ }
+
+ function removeFrame() {
+ var frame = document.getElementById('1-2-id');
+ frame.parentNode.removeChild(frame);
+ return true;
+ }
+
+ function addFrame() {
+ var frame = document.createElement('iframe');
+ frame.setAttribute('src', '1-2.html');
+ document.body.appendChild(frame);
+ return true;
+ }
+</script>
+</head>
+<body>
+
+Top frame.
+<br>
+<iframe src="1-1.html" name="1-1-name"></iframe>
+<br>
+<iframe src="1-2.html" id="1-2-id" name="1-2-name"></iframe>
+<br>
+<iframe src="1-3.html" id="1-3-id"></iframe>
+<br>
+
+</html>
+</body>
+</html>
diff --git a/content/test/data/post_message.html b/content/test/data/post_message.html
index 76ea08d..6d3982e 100644
--- a/content/test/data/post_message.html
+++ b/content/test/data/post_message.html
@@ -14,6 +14,13 @@
w.postMessage(msg, "*");
return true;
}
+
+ // Send a message to a subframe of window named "foo".
+ function postToFooFrame(msg) {
+ var w = window.open("", "foo");
+ w.frames[0][0].postMessage(msg, "*");
+ return true;
+ }
// Listen to incoming messages.
var receivedMessages = 0;
diff --git a/content/test/data/post_message2.html b/content/test/data/post_message2.html
new file mode 100644
index 0000000..2e0cfe5
--- /dev/null
+++ b/content/test/data/post_message2.html
@@ -0,0 +1,21 @@
+<html>
+
+<head><title>Post message subframe tests</title>
+<script>
+ function postToOpenerFrame(msg, origin) {
+ window.opener.frames[0][1].postMessage(msg, origin);
+ return true;
+ }
+
+ window.addEventListener("message", messageReceived, false);
+
+ function messageReceived(event) {
+ // Change the title to generate a notification.
+ document.title = event.data;
+ }
+</script>
+</head>
+
+<iframe id="frame1" src="frame_tree/1-1.html"></iframe>
+
+</html>