Chromium Code Reviews| Index: content/browser/frame_host/navigator_impl.cc |
| diff --git a/content/browser/frame_host/navigator_impl.cc b/content/browser/frame_host/navigator_impl.cc |
| index b005335680788b1666cb42c811ec3aec12de70f1..22c4d3e83b75e55b11503fde6493038e4975319d 100644 |
| --- a/content/browser/frame_host/navigator_impl.cc |
| +++ b/content/browser/frame_host/navigator_impl.cc |
| @@ -151,11 +151,12 @@ void NavigatorImpl::DidStartProvisionalLoad( |
| render_frame_host->SetNavigationHandle(scoped_ptr<NavigationHandleImpl>()); |
| } |
| + NavigationEntry* pending_entry = controller_->GetPendingEntry(); |
| render_frame_host->SetNavigationHandle(NavigationHandleImpl::Create( |
| validated_url, render_frame_host->frame_tree_node(), |
| false, // is_synchronous |
| is_iframe_srcdoc, // is_srcdoc |
| - navigation_start)); |
| + navigation_start, pending_entry ? pending_entry->GetUniqueID() : 0)); |
|
clamy
2016/02/26 14:08:13
Can the pending entry ever be null here except in
Charlie Harrison
2016/02/26 17:39:51
It can be null for subframes. Good catch.
|
| } |
| void NavigatorImpl::DidFailProvisionalLoadWithError( |
| @@ -195,36 +196,50 @@ void NavigatorImpl::DidFailProvisionalLoadWithError( |
| // We used to cancel the pending renderer here for cross-site downloads. |
| // However, it's not safe to do that because the download logic repeatedly |
| - // looks for this WebContents based on a render ID. Instead, we just |
| + // looks for this WebContents based on a render ID. Instead, we just |
| // leave the pending renderer around until the next navigation event |
| // (Navigate, DidNavigate, etc), which will clean it up properly. |
| // |
| // TODO(creis): Find a way to cancel any pending RFH here. |
| } |
| - // We usually clear the pending entry when it fails, so that an arbitrary URL |
| - // isn't left visible above a committed page. This must be enforced when |
| - // the pending entry isn't visible (e.g., renderer-initiated navigations) to |
| - // prevent URL spoofs for in-page navigations that don't go through |
| - // DidStartProvisionalLoadForFrame. |
| - // |
| - // However, we do preserve the pending entry in some cases, such as on the |
| - // initial navigation of an unmodified blank tab. We also allow the delegate |
| - // to say when it's safe to leave aborted URLs in the omnibox, to let the user |
| - // edit the URL and try again. This may be useful in cases that the committed |
| - // page cannot be attacker-controlled. In these cases, we still allow the |
| - // view to clear the pending entry and typed URL if the user requests |
| - // (e.g., hitting Escape with focus in the address bar). |
| - // |
| - // Note: don't touch the transient entry, since an interstitial may exist. |
| - bool should_preserve_entry = controller_->IsUnmodifiedBlankTab() || |
| - delegate_->ShouldPreserveAbortedURLs(); |
| - if (controller_->GetPendingEntry() != controller_->GetVisibleEntry() || |
| - !should_preserve_entry) { |
| - controller_->DiscardPendingEntry(true); |
| - |
| - // Also force the UI to refresh. |
| - controller_->delegate()->NotifyNavigationStateChanged(INVALIDATE_TYPE_URL); |
| + // Racy conditions can cause a fail message to arrive after its corresponding |
| + // pending entry has been replaced by another navigation. If |
| + // |DiscardPendingEntry| is called in this case, then the completely valid |
| + // entry for the new navigation would be discarded. See crbug.com/513742. To |
| + // catch this case, the current pending entry is compared against the current |
| + // navigation handle's entry id, which should correspond to the failed load. |
| + NavigationHandleImpl* handle = render_frame_host->navigation_handle(); |
| + NavigationEntry* pending_entry = controller_->GetPendingEntry(); |
| + bool pending_matches_fail_msg = |
| + handle && pending_entry && |
| + handle->pending_nav_entry_id() == pending_entry->GetUniqueID(); |
| + if (pending_matches_fail_msg) { |
| + // We usually clear the pending entry when it fails, so that an arbitrary |
| + // URL isn't left visible above a committed page. This must be enforced |
| + // when the pending entry isn't visible (e.g., renderer-initiated |
| + // navigations) to prevent URL spoofs for in-page navigations that don't go |
| + // through DidStartProvisionalLoadForFrame. |
| + // |
| + // However, we do preserve the pending entry in some cases, such as on the |
| + // initial navigation of an unmodified blank tab. We also allow the |
| + // delegate to say when it's safe to leave aborted URLs in the omnibox, to |
| + // let the user edit the URL and try again. This may be useful in cases |
| + // that the committed page cannot be attacker-controlled. In these cases, |
| + // we still allow the view to clear the pending entry and typed URL if the |
| + // user requests (e.g., hitting Escape with focus in the address bar). |
| + // |
| + // Note: don't touch the transient entry, since an interstitial may exist. |
| + bool should_preserve_entry = controller_->IsUnmodifiedBlankTab() || |
| + delegate_->ShouldPreserveAbortedURLs(); |
| + if (pending_entry != controller_->GetVisibleEntry() || |
| + !should_preserve_entry) { |
| + controller_->DiscardPendingEntry(true); |
| + |
| + // Also force the UI to refresh. |
| + controller_->delegate()->NotifyNavigationStateChanged( |
| + INVALIDATE_TYPE_URL); |
| + } |
| } |
| if (delegate_) |
| @@ -799,8 +814,6 @@ void NavigatorImpl::OnBeginNavigation( |
| controller_->GetLastCommittedEntryIndex(), |
| controller_->GetEntryCount())); |
| NavigationRequest* navigation_request = frame_tree_node->navigation_request(); |
| - navigation_request->CreateNavigationHandle(); |
| - |
| if (frame_tree_node->IsMainFrame()) { |
| // Renderer-initiated main-frame navigations that need to swap processes |
| // will go to the browser via a OpenURL call, and then be handled by the |
| @@ -815,6 +828,13 @@ void NavigatorImpl::OnBeginNavigation( |
| navigation_data_.reset(); |
| } |
| + // The NavigationHandle must be created after the call to |
| + // |DidStartMainFrameNavigation|, so it receives the most up to date pending |
| + // entry from the NavigationController. The pending entry is guaranteed to be |
| + // non null at this point. |
| + DCHECK(controller_->GetPendingEntry()); |
|
clamy
2016/02/26 14:08:13
Is it always the case with subframes? Subframes wi
Charlie Harrison
2016/02/26 17:39:51
You've caught an error. I don't believe that subfr
|
| + navigation_request->CreateNavigationHandle( |
| + controller_->GetPendingEntry()->GetUniqueID()); |
| navigation_request->BeginNavigation(); |
| } |
| @@ -968,7 +988,7 @@ void NavigatorImpl::RequestNavigation( |
| navigation_type, lofi_state, is_same_document_history_load, |
| navigation_start, controller_)); |
| NavigationRequest* navigation_request = frame_tree_node->navigation_request(); |
| - navigation_request->CreateNavigationHandle(); |
| + navigation_request->CreateNavigationHandle(entry.GetUniqueID()); |
| // Have the current renderer execute its beforeunload event if needed. If it |
| // is not needed (when beforeunload dispatch is not needed or this navigation |