diff options
author | nasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-28 19:23:37 +0000 |
---|---|---|
committer | nasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-28 19:23:37 +0000 |
commit | 0720b5393454a0f31039eb9aea7e6ab9c4793f3e (patch) | |
tree | 146cbf874db4e241dbde5e0fd2489a5b5fe152be | |
parent | fb9b3fbc72c7c1aa1bcd7c7b6995ed75283e9c3e (diff) | |
download | chromium_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
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> |