render_frame_host_impl.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/browser/frame_host/render_frame_host_impl.h" 6 7#include "base/containers/hash_tables.h" 8#include "base/lazy_instance.h" 9#include "base/metrics/user_metrics_action.h" 10#include "content/browser/child_process_security_policy_impl.h" 11#include "content/browser/frame_host/cross_process_frame_connector.h" 12#include "content/browser/frame_host/frame_tree.h" 13#include "content/browser/frame_host/frame_tree_node.h" 14#include "content/browser/frame_host/navigator.h" 15#include "content/browser/frame_host/render_frame_host_delegate.h" 16#include "content/browser/renderer_host/render_view_host_impl.h" 17#include "content/common/frame_messages.h" 18#include "content/public/browser/browser_thread.h" 19#include "content/public/browser/content_browser_client.h" 20#include "content/public/browser/render_process_host.h" 21#include "content/public/browser/render_widget_host_view.h" 22#include "content/public/browser/user_metrics.h" 23#include "content/public/common/url_constants.h" 24#include "url/gurl.h" 25 26namespace content { 27 28// The (process id, routing id) pair that identifies one RenderFrame. 29typedef std::pair<int32, int32> RenderFrameHostID; 30typedef base::hash_map<RenderFrameHostID, RenderFrameHostImpl*> 31 RoutingIDFrameMap; 32static base::LazyInstance<RoutingIDFrameMap> g_routing_id_frame_map = 33 LAZY_INSTANCE_INITIALIZER; 34 35RenderFrameHost* RenderFrameHost::FromID(int render_process_id, 36 int render_frame_id) { 37 return RenderFrameHostImpl::FromID(render_process_id, render_frame_id); 38} 39 40// static 41RenderFrameHostImpl* RenderFrameHostImpl::FromID( 42 int process_id, int routing_id) { 43 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 44 RoutingIDFrameMap* frames = g_routing_id_frame_map.Pointer(); 45 RoutingIDFrameMap::iterator it = frames->find( 46 RenderFrameHostID(process_id, routing_id)); 47 return it == frames->end() ? NULL : it->second; 48} 49 50RenderFrameHostImpl::RenderFrameHostImpl( 51 RenderViewHostImpl* render_view_host, 52 RenderFrameHostDelegate* delegate, 53 FrameTree* frame_tree, 54 FrameTreeNode* frame_tree_node, 55 int routing_id, 56 bool is_swapped_out) 57 : render_view_host_(render_view_host), 58 delegate_(delegate), 59 cross_process_frame_connector_(NULL), 60 frame_tree_(frame_tree), 61 frame_tree_node_(frame_tree_node), 62 routing_id_(routing_id), 63 is_swapped_out_(is_swapped_out) { 64 frame_tree_->RegisterRenderFrameHost(this); 65 GetProcess()->AddRoute(routing_id_, this); 66 g_routing_id_frame_map.Get().insert(std::make_pair( 67 RenderFrameHostID(GetProcess()->GetID(), routing_id_), 68 this)); 69} 70 71RenderFrameHostImpl::~RenderFrameHostImpl() { 72 GetProcess()->RemoveRoute(routing_id_); 73 g_routing_id_frame_map.Get().erase( 74 RenderFrameHostID(GetProcess()->GetID(), routing_id_)); 75 if (delegate_) 76 delegate_->RenderFrameDeleted(this); 77 78 // Notify the FrameTree that this RFH is going away, allowing it to shut down 79 // the corresponding RenderViewHost if it is no longer needed. 80 frame_tree_->UnregisterRenderFrameHost(this); 81} 82 83SiteInstance* RenderFrameHostImpl::GetSiteInstance() { 84 return render_view_host_->GetSiteInstance(); 85} 86 87RenderProcessHost* RenderFrameHostImpl::GetProcess() { 88 // TODO(nasko): This should return its own process, once we have working 89 // cross-process navigation for subframes. 90 return render_view_host_->GetProcess(); 91} 92 93int RenderFrameHostImpl::GetRoutingID() { 94 return routing_id_; 95} 96 97gfx::NativeView RenderFrameHostImpl::GetNativeView() { 98 RenderWidgetHostView* view = render_view_host_->GetView(); 99 if (!view) 100 return NULL; 101 return view->GetNativeView(); 102} 103 104void RenderFrameHostImpl::NotifyContextMenuClosed( 105 const CustomContextMenuContext& context) { 106 Send(new FrameMsg_ContextMenuClosed(routing_id_, context)); 107} 108 109void RenderFrameHostImpl::ExecuteCustomContextMenuCommand( 110 int action, const CustomContextMenuContext& context) { 111 Send(new FrameMsg_CustomContextMenuAction(routing_id_, context, action)); 112} 113 114RenderViewHost* RenderFrameHostImpl::GetRenderViewHost() { 115 return render_view_host_; 116} 117 118bool RenderFrameHostImpl::Send(IPC::Message* message) { 119 return GetProcess()->Send(message); 120} 121 122bool RenderFrameHostImpl::OnMessageReceived(const IPC::Message &msg) { 123 if (delegate_->OnMessageReceived(this, msg)) 124 return true; 125 126 if (cross_process_frame_connector_ && 127 cross_process_frame_connector_->OnMessageReceived(msg)) 128 return true; 129 130 bool handled = true; 131 bool msg_is_ok = true; 132 IPC_BEGIN_MESSAGE_MAP_EX(RenderFrameHostImpl, msg, msg_is_ok) 133 IPC_MESSAGE_HANDLER(FrameHostMsg_Detach, OnDetach) 134 IPC_MESSAGE_HANDLER(FrameHostMsg_DidStartProvisionalLoadForFrame, 135 OnDidStartProvisionalLoadForFrame) 136 IPC_MESSAGE_HANDLER(FrameHostMsg_DidFailProvisionalLoadWithError, 137 OnDidFailProvisionalLoadWithError) 138 IPC_MESSAGE_HANDLER(FrameHostMsg_DidRedirectProvisionalLoad, 139 OnDidRedirectProvisionalLoad) 140 IPC_MESSAGE_HANDLER(FrameHostMsg_DidFailLoadWithError, 141 OnDidFailLoadWithError) 142 IPC_MESSAGE_HANDLER_GENERIC(FrameHostMsg_DidCommitProvisionalLoad, 143 OnNavigate(msg)) 144 IPC_MESSAGE_HANDLER(FrameHostMsg_DidStartLoading, OnDidStartLoading) 145 IPC_MESSAGE_HANDLER(FrameHostMsg_DidStopLoading, OnDidStopLoading) 146 IPC_MESSAGE_HANDLER(FrameHostMsg_SwapOut_ACK, OnSwapOutACK) 147 IPC_MESSAGE_HANDLER(FrameHostMsg_ContextMenu, OnContextMenu) 148 IPC_END_MESSAGE_MAP_EX() 149 150 if (!msg_is_ok) { 151 // The message had a handler, but its de-serialization failed. 152 // Kill the renderer. 153 RecordAction(base::UserMetricsAction("BadMessageTerminate_RFH")); 154 GetProcess()->ReceivedBadMessage(); 155 } 156 157 return handled; 158} 159 160void RenderFrameHostImpl::Init() { 161 GetProcess()->ResumeRequestsForView(routing_id_); 162} 163 164void RenderFrameHostImpl::OnCreateChildFrame(int new_frame_routing_id, 165 int64 parent_frame_id, 166 int64 frame_id, 167 const std::string& frame_name) { 168 RenderFrameHostImpl* new_frame = frame_tree_->AddFrame( 169 new_frame_routing_id, parent_frame_id, frame_id, frame_name); 170 if (delegate_) 171 delegate_->RenderFrameCreated(new_frame); 172} 173 174void RenderFrameHostImpl::OnDetach(int64 parent_frame_id, int64 frame_id) { 175 frame_tree_->RemoveFrame(this, parent_frame_id, frame_id); 176} 177 178void RenderFrameHostImpl::OnDidStartProvisionalLoadForFrame( 179 int64 frame_id, 180 int64 parent_frame_id, 181 bool is_main_frame, 182 const GURL& url) { 183 frame_tree_node_->navigator()->DidStartProvisionalLoad( 184 this, frame_id, parent_frame_id, is_main_frame, url); 185} 186 187void RenderFrameHostImpl::OnDidFailProvisionalLoadWithError( 188 const FrameHostMsg_DidFailProvisionalLoadWithError_Params& params) { 189 frame_tree_node_->navigator()->DidFailProvisionalLoadWithError(this, params); 190} 191 192void RenderFrameHostImpl::OnDidFailLoadWithError( 193 int64 frame_id, 194 const GURL& url, 195 bool is_main_frame, 196 int error_code, 197 const base::string16& error_description) { 198 GURL validated_url(url); 199 GetProcess()->FilterURL(false, &validated_url); 200 201 frame_tree_node_->navigator()->DidFailLoadWithError( 202 this, frame_id, validated_url, is_main_frame, error_code, 203 error_description); 204} 205 206void RenderFrameHostImpl::OnDidRedirectProvisionalLoad( 207 int32 page_id, 208 const GURL& source_url, 209 const GURL& target_url) { 210 frame_tree_node_->navigator()->DidRedirectProvisionalLoad( 211 this, page_id, source_url, target_url); 212} 213 214// Called when the renderer navigates. For every frame loaded, we'll get this 215// notification containing parameters identifying the navigation. 216// 217// Subframes are identified by the page transition type. For subframes loaded 218// as part of a wider page load, the page_id will be the same as for the top 219// level frame. If the user explicitly requests a subframe navigation, we will 220// get a new page_id because we need to create a new navigation entry for that 221// action. 222void RenderFrameHostImpl::OnNavigate(const IPC::Message& msg) { 223 // Read the parameters out of the IPC message directly to avoid making another 224 // copy when we filter the URLs. 225 PickleIterator iter(msg); 226 FrameHostMsg_DidCommitProvisionalLoad_Params validated_params; 227 if (!IPC::ParamTraits<FrameHostMsg_DidCommitProvisionalLoad_Params>:: 228 Read(&msg, &iter, &validated_params)) 229 return; 230 231 // If we're waiting for a cross-site beforeunload ack from this renderer and 232 // we receive a Navigate message from the main frame, then the renderer was 233 // navigating already and sent it before hearing the ViewMsg_Stop message. 234 // We do not want to cancel the pending navigation in this case, since the 235 // old page will soon be stopped. Instead, treat this as a beforeunload ack 236 // to allow the pending navigation to continue. 237 if (render_view_host_->is_waiting_for_beforeunload_ack_ && 238 render_view_host_->unload_ack_is_for_cross_site_transition_ && 239 PageTransitionIsMainFrame(validated_params.transition)) { 240 render_view_host_->OnShouldCloseACK( 241 true, render_view_host_->send_should_close_start_time_, 242 base::TimeTicks::Now()); 243 return; 244 } 245 246 // If we're waiting for an unload ack from this renderer and we receive a 247 // Navigate message, then the renderer was navigating before it received the 248 // unload request. It will either respond to the unload request soon or our 249 // timer will expire. Either way, we should ignore this message, because we 250 // have already committed to closing this renderer. 251 if (render_view_host_->IsWaitingForUnloadACK()) 252 return; 253 254 // Cache the main frame id, so we can use it for creating the frame tree 255 // root node when needed. 256 if (PageTransitionIsMainFrame(validated_params.transition)) { 257 if (render_view_host_->main_frame_id_ == -1) { 258 render_view_host_->main_frame_id_ = validated_params.frame_id; 259 } else { 260 // TODO(nasko): We plan to remove the usage of frame_id in navigation 261 // and move to routing ids. This is in place to ensure that a 262 // renderer is not misbehaving and sending us incorrect data. 263 DCHECK_EQ(render_view_host_->main_frame_id_, validated_params.frame_id); 264 } 265 } 266 RenderProcessHost* process = GetProcess(); 267 268 // Attempts to commit certain off-limits URL should be caught more strictly 269 // than our FilterURL checks below. If a renderer violates this policy, it 270 // should be killed. 271 if (!CanCommitURL(validated_params.url)) { 272 VLOG(1) << "Blocked URL " << validated_params.url.spec(); 273 validated_params.url = GURL(kAboutBlankURL); 274 RecordAction(base::UserMetricsAction("CanCommitURL_BlockedAndKilled")); 275 // Kills the process. 276 process->ReceivedBadMessage(); 277 } 278 279 // Now that something has committed, we don't need to track whether the 280 // initial page has been accessed. 281 render_view_host_->has_accessed_initial_document_ = false; 282 283 // Without this check, an evil renderer can trick the browser into creating 284 // a navigation entry for a banned URL. If the user clicks the back button 285 // followed by the forward button (or clicks reload, or round-trips through 286 // session restore, etc), we'll think that the browser commanded the 287 // renderer to load the URL and grant the renderer the privileges to request 288 // the URL. To prevent this attack, we block the renderer from inserting 289 // banned URLs into the navigation controller in the first place. 290 process->FilterURL(false, &validated_params.url); 291 process->FilterURL(true, &validated_params.referrer.url); 292 for (std::vector<GURL>::iterator it(validated_params.redirects.begin()); 293 it != validated_params.redirects.end(); ++it) { 294 process->FilterURL(false, &(*it)); 295 } 296 process->FilterURL(true, &validated_params.searchable_form_url); 297 298 // Without this check, the renderer can trick the browser into using 299 // filenames it can't access in a future session restore. 300 if (!render_view_host_->CanAccessFilesOfPageState( 301 validated_params.page_state)) { 302 GetProcess()->ReceivedBadMessage(); 303 return; 304 } 305 306 frame_tree_node()->navigator()->DidNavigate(this, validated_params); 307} 308 309void RenderFrameHostImpl::SwapOut() { 310 if (render_view_host_->IsRenderViewLive()) { 311 Send(new FrameMsg_SwapOut(routing_id_)); 312 } else { 313 // Our RenderViewHost doesn't have a live renderer, so just skip the unload 314 // event. 315 OnSwappedOut(true); 316 } 317} 318 319void RenderFrameHostImpl::OnDidStartLoading() { 320 delegate_->DidStartLoading(this); 321} 322 323void RenderFrameHostImpl::OnDidStopLoading() { 324 delegate_->DidStopLoading(this); 325} 326 327void RenderFrameHostImpl::OnSwapOutACK() { 328 OnSwappedOut(false); 329} 330 331void RenderFrameHostImpl::OnSwappedOut(bool timed_out) { 332 frame_tree_node_->render_manager()->SwappedOutFrame(this); 333} 334 335void RenderFrameHostImpl::OnContextMenu(const ContextMenuParams& params) { 336 // Validate the URLs in |params|. If the renderer can't request the URLs 337 // directly, don't show them in the context menu. 338 ContextMenuParams validated_params(params); 339 RenderProcessHost* process = GetProcess(); 340 341 // We don't validate |unfiltered_link_url| so that this field can be used 342 // when users want to copy the original link URL. 343 process->FilterURL(true, &validated_params.link_url); 344 process->FilterURL(true, &validated_params.src_url); 345 process->FilterURL(false, &validated_params.page_url); 346 process->FilterURL(true, &validated_params.frame_url); 347 348 delegate_->ShowContextMenu(this, validated_params); 349} 350 351void RenderFrameHostImpl::SetPendingShutdown(const base::Closure& on_swap_out) { 352 render_view_host_->SetPendingShutdown(on_swap_out); 353} 354 355bool RenderFrameHostImpl::CanCommitURL(const GURL& url) { 356 // TODO(creis): We should also check for WebUI pages here. Also, when the 357 // out-of-process iframes implementation is ready, we should check for 358 // cross-site URLs that are not allowed to commit in this process. 359 360 // Give the client a chance to disallow URLs from committing. 361 return GetContentClient()->browser()->CanCommitURL(GetProcess(), url); 362} 363 364void RenderFrameHostImpl::Navigate(const FrameMsg_Navigate_Params& params) { 365 TRACE_EVENT0("frame_host", "RenderFrameHostImpl::Navigate"); 366 // Browser plugin guests are not allowed to navigate outside web-safe schemes, 367 // so do not grant them the ability to request additional URLs. 368 if (!GetProcess()->IsGuest()) { 369 ChildProcessSecurityPolicyImpl::GetInstance()->GrantRequestURL( 370 GetProcess()->GetID(), params.url); 371 if (params.url.SchemeIs(kDataScheme) && 372 params.base_url_for_data_url.SchemeIs(kFileScheme)) { 373 // If 'data:' is used, and we have a 'file:' base url, grant access to 374 // local files. 375 ChildProcessSecurityPolicyImpl::GetInstance()->GrantRequestURL( 376 GetProcess()->GetID(), params.base_url_for_data_url); 377 } 378 } 379 380 // Only send the message if we aren't suspended at the start of a cross-site 381 // request. 382 if (render_view_host_->navigations_suspended_) { 383 // Shouldn't be possible to have a second navigation while suspended, since 384 // navigations will only be suspended during a cross-site request. If a 385 // second navigation occurs, RenderFrameHostManager will cancel this pending 386 // RFH and create a new pending RFH. 387 DCHECK(!render_view_host_->suspended_nav_params_.get()); 388 render_view_host_->suspended_nav_params_.reset( 389 new FrameMsg_Navigate_Params(params)); 390 } else { 391 // Get back to a clean state, in case we start a new navigation without 392 // completing a RVH swap or unload handler. 393 render_view_host_->SetState(RenderViewHostImpl::STATE_DEFAULT); 394 395 Send(new FrameMsg_Navigate(GetRoutingID(), params)); 396 } 397 398 // Force the throbber to start. We do this because Blink's "started 399 // loading" message will be received asynchronously from the UI of the 400 // browser. But we want to keep the throbber in sync with what's happening 401 // in the UI. For example, we want to start throbbing immediately when the 402 // user naivgates even if the renderer is delayed. There is also an issue 403 // with the throbber starting because the WebUI (which controls whether the 404 // favicon is displayed) happens synchronously. If the start loading 405 // messages was asynchronous, then the default favicon would flash in. 406 // 407 // Blink doesn't send throb notifications for JavaScript URLs, so we 408 // don't want to either. 409 if (!params.url.SchemeIs(kJavaScriptScheme)) 410 delegate_->DidStartLoading(this); 411} 412 413void RenderFrameHostImpl::NavigateToURL(const GURL& url) { 414 FrameMsg_Navigate_Params params; 415 params.page_id = -1; 416 params.pending_history_list_offset = -1; 417 params.current_history_list_offset = -1; 418 params.current_history_list_length = 0; 419 params.url = url; 420 params.transition = PAGE_TRANSITION_LINK; 421 params.navigation_type = FrameMsg_Navigate_Type::NORMAL; 422 Navigate(params); 423} 424 425} // namespace content 426