Commit 9b14d51b authored by Michael Thiessen's avatar Michael Thiessen Committed by Commit Bot

Fix crash in InterceptNavigationDelegateImpl#maybeUpdateNavigationHistory

Note that this is a speculative fix as I was unable to repro, but see
the bug for context - I'm pretty certain this will fix the crash.

This introduces NavigationController::RemoveForwardEntries which will
attempt to remove any forward navigation entries (without crashing :)).

Bug: 963783
Change-Id: Ic375e0e4fd89d4ded3a5229bf82ccb932e10ab5f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1775070
Commit-Queue: Michael Thiessen <mthiesse@chromium.org>
Reviewed-by: default avatarCharlie Reis <creis@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#699046}
parent 2eb3102a
...@@ -239,14 +239,7 @@ public class InterceptNavigationDelegateImpl implements InterceptNavigationDeleg ...@@ -239,14 +239,7 @@ public class InterceptNavigationDelegateImpl implements InterceptNavigationDeleg
public void maybeUpdateNavigationHistory() { public void maybeUpdateNavigationHistory() {
WebContents webContents = mTab.getWebContents(); WebContents webContents = mTab.getWebContents();
if (mClearAllForwardHistoryRequired && webContents != null) { if (mClearAllForwardHistoryRequired && webContents != null) {
NavigationController navigationController = webContents.getNavigationController().pruneForwardEntries();
webContents.getNavigationController();
int lastCommittedEntryIndex = getLastCommittedEntryIndex();
while (navigationController.canGoForward()) {
boolean ret = navigationController.removeEntryAtIndex(
lastCommittedEntryIndex + 1);
assert ret;
}
} else if (mShouldClearRedirectHistoryForTabClobbering } else if (mShouldClearRedirectHistoryForTabClobbering
&& webContents != null) { && webContents != null) {
// http://crbug/479056: Even if we clobber the current tab, we want to remove // http://crbug/479056: Even if we clobber the current tab, we want to remove
......
...@@ -425,6 +425,12 @@ jboolean NavigationControllerAndroid::RemoveEntryAtIndex( ...@@ -425,6 +425,12 @@ jboolean NavigationControllerAndroid::RemoveEntryAtIndex(
return navigation_controller_->RemoveEntryAtIndex(index); return navigation_controller_->RemoveEntryAtIndex(index);
} }
void NavigationControllerAndroid::PruneForwardEntries(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
return navigation_controller_->PruneForwardEntries();
}
ScopedJavaLocalRef<jstring> NavigationControllerAndroid::GetEntryExtraData( ScopedJavaLocalRef<jstring> NavigationControllerAndroid::GetEntryExtraData(
JNIEnv* env, JNIEnv* env,
const JavaParamRef<jobject>& obj, const JavaParamRef<jobject>& obj,
......
...@@ -119,6 +119,8 @@ class CONTENT_EXPORT NavigationControllerAndroid { ...@@ -119,6 +119,8 @@ class CONTENT_EXPORT NavigationControllerAndroid {
jboolean RemoveEntryAtIndex(JNIEnv* env, jboolean RemoveEntryAtIndex(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jobject>& obj,
jint index); jint index);
void PruneForwardEntries(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
base::android::ScopedJavaLocalRef<jstring> GetEntryExtraData( base::android::ScopedJavaLocalRef<jstring> GetEntryExtraData(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jobject>& obj,
......
...@@ -887,6 +887,17 @@ bool NavigationControllerImpl::RemoveEntryAtIndex(int index) { ...@@ -887,6 +887,17 @@ bool NavigationControllerImpl::RemoveEntryAtIndex(int index) {
return true; return true;
} }
void NavigationControllerImpl::PruneForwardEntries() {
DiscardNonCommittedEntries();
int remove_start_index = last_committed_entry_index_ + 1;
int num_removed = int(entries_.size()) - remove_start_index;
if (num_removed <= 0)
return;
entries_.erase(entries_.begin() + remove_start_index, entries_.end());
NotifyPrunedEntries(this, remove_start_index /* start index */,
num_removed /* count */);
}
void NavigationControllerImpl::UpdateVirtualURLToURL( void NavigationControllerImpl::UpdateVirtualURLToURL(
NavigationEntryImpl* entry, const GURL& new_url) { NavigationEntryImpl* entry, const GURL& new_url) {
GURL new_virtual_url(new_url); GURL new_virtual_url(new_url);
...@@ -2431,10 +2442,8 @@ void NavigationControllerImpl::InsertOrReplaceEntry( ...@@ -2431,10 +2442,8 @@ void NavigationControllerImpl::InsertOrReplaceEntry(
DiscardNonCommittedEntries(); DiscardNonCommittedEntries();
int current_size = static_cast<int>(entries_.size());
// When replacing, don't prune the forward history. // When replacing, don't prune the forward history.
if (replace && current_size > 0) { if (replace && entries_.size() > 0) {
CopyReplacedNavigationEntryDataIfPreviouslyEmpty( CopyReplacedNavigationEntryDataIfPreviouslyEmpty(
entries_[last_committed_entry_index_].get(), entry.get()); entries_[last_committed_entry_index_].get(), entry.get());
entries_[last_committed_entry_index_] = std::move(entry); entries_[last_committed_entry_index_] = std::move(entry);
...@@ -2444,20 +2453,7 @@ void NavigationControllerImpl::InsertOrReplaceEntry( ...@@ -2444,20 +2453,7 @@ void NavigationControllerImpl::InsertOrReplaceEntry(
// We shouldn't see replace == true when there's no committed entries. // We shouldn't see replace == true when there's no committed entries.
DCHECK(!replace); DCHECK(!replace);
if (current_size > 0) { PruneForwardEntries();
// Prune any entries which are in front of the current entry.
int num_pruned = 0;
while (last_committed_entry_index_ < (current_size - 1)) {
num_pruned++;
entries_.pop_back();
current_size--;
}
if (num_pruned > 0) { // Only notify if we did prune something.
NotifyPrunedEntries(this,
last_committed_entry_index_ + 1 /* start index */,
num_pruned /* count */);
}
}
PruneOldestSkippableEntryIfFull(); PruneOldestSkippableEntryIfFull();
...@@ -3364,6 +3360,9 @@ void NavigationControllerImpl::FinishRestore(int selected_index, ...@@ -3364,6 +3360,9 @@ void NavigationControllerImpl::FinishRestore(int selected_index,
} }
void NavigationControllerImpl::DiscardNonCommittedEntries() { void NavigationControllerImpl::DiscardNonCommittedEntries() {
// Avoid sending a notification if there is nothing to discard.
if (!pending_entry_ && transient_entry_index_ == -1)
return;
DiscardPendingEntry(false); DiscardPendingEntry(false);
DiscardTransientEntry(); DiscardTransientEntry();
if (delegate_) if (delegate_)
......
...@@ -80,6 +80,7 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController { ...@@ -80,6 +80,7 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController {
void GoToIndex(int index) override; void GoToIndex(int index) override;
void GoToOffset(int offset) override; void GoToOffset(int offset) override;
bool RemoveEntryAtIndex(int index) override; bool RemoveEntryAtIndex(int index) override;
void PruneForwardEntries() override;
const SessionStorageNamespaceMap& GetSessionStorageNamespaceMap() override; const SessionStorageNamespaceMap& GetSessionStorageNamespaceMap() override;
SessionStorageNamespace* GetDefaultSessionStorageNamespace() override; SessionStorageNamespace* GetDefaultSessionStorageNamespace() override;
bool NeedsReload() override; bool NeedsReload() override;
......
...@@ -1071,7 +1071,7 @@ TEST_F(NavigationControllerTest, LoadURL_IgnorePreemptsPending) { ...@@ -1071,7 +1071,7 @@ TEST_F(NavigationControllerTest, LoadURL_IgnorePreemptsPending) {
EXPECT_EQ(-1, controller.GetPendingEntryIndex()); EXPECT_EQ(-1, controller.GetPendingEntryIndex());
EXPECT_TRUE(controller.GetPendingEntry()); EXPECT_TRUE(controller.GetPendingEntry());
EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex()); EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(2, delegate->navigation_state_change_count()); EXPECT_EQ(1, delegate->navigation_state_change_count());
// Certain rare cases can make a direct DidCommitProvisionalLoad call without // Certain rare cases can make a direct DidCommitProvisionalLoad call without
// going to the browser. Renderer reload of an about:blank is such a case. // going to the browser. Renderer reload of an about:blank is such a case.
...@@ -1082,7 +1082,7 @@ TEST_F(NavigationControllerTest, LoadURL_IgnorePreemptsPending) { ...@@ -1082,7 +1082,7 @@ TEST_F(NavigationControllerTest, LoadURL_IgnorePreemptsPending) {
EXPECT_EQ(-1, controller.GetPendingEntryIndex()); EXPECT_EQ(-1, controller.GetPendingEntryIndex());
EXPECT_FALSE(controller.GetPendingEntry()); EXPECT_FALSE(controller.GetPendingEntry());
EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex()); EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(3, delegate->navigation_state_change_count()); EXPECT_EQ(2, delegate->navigation_state_change_count());
contents()->SetDelegate(nullptr); contents()->SetDelegate(nullptr);
} }
...@@ -1109,7 +1109,7 @@ TEST_F(NavigationControllerTest, LoadURL_AbortDoesntCancelPending) { ...@@ -1109,7 +1109,7 @@ TEST_F(NavigationControllerTest, LoadURL_AbortDoesntCancelPending) {
EXPECT_EQ(-1, controller.GetPendingEntryIndex()); EXPECT_EQ(-1, controller.GetPendingEntryIndex());
EXPECT_TRUE(controller.GetPendingEntry()); EXPECT_TRUE(controller.GetPendingEntry());
EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex()); EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(2, delegate->navigation_state_change_count()); EXPECT_EQ(1, delegate->navigation_state_change_count());
// It may abort before committing, if it's a download or due to a stop or // It may abort before committing, if it's a download or due to a stop or
// a new navigation from the user. // a new navigation from the user.
...@@ -1121,7 +1121,7 @@ TEST_F(NavigationControllerTest, LoadURL_AbortDoesntCancelPending) { ...@@ -1121,7 +1121,7 @@ TEST_F(NavigationControllerTest, LoadURL_AbortDoesntCancelPending) {
EXPECT_EQ(-1, controller.GetPendingEntryIndex()); EXPECT_EQ(-1, controller.GetPendingEntryIndex());
EXPECT_TRUE(controller.GetPendingEntry()); EXPECT_TRUE(controller.GetPendingEntry());
EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex()); EXPECT_EQ(-1, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(2, delegate->navigation_state_change_count()); EXPECT_EQ(1, delegate->navigation_state_change_count());
NavigationEntry* pending_entry = controller.GetPendingEntry(); NavigationEntry* pending_entry = controller.GetPendingEntry();
// Ensure that a reload keeps the same pending entry. // Ensure that a reload keeps the same pending entry.
...@@ -1162,9 +1162,9 @@ TEST_F(NavigationControllerTest, LoadURL_RedirectAbortDoesntShowPendingURL) { ...@@ -1162,9 +1162,9 @@ TEST_F(NavigationControllerTest, LoadURL_RedirectAbortDoesntShowPendingURL) {
EXPECT_EQ(-1, controller.GetPendingEntryIndex()); EXPECT_EQ(-1, controller.GetPendingEntryIndex());
EXPECT_TRUE(controller.GetPendingEntry()); EXPECT_TRUE(controller.GetPendingEntry());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex()); EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
// The delegate should have been notified at least twice: once for the loading // The delegate should have been notified twice: once for the loading state
// state change, and once for the url change. // change, and once for the url change.
EXPECT_EQ(3, delegate->navigation_state_change_count()); EXPECT_EQ(2, delegate->navigation_state_change_count());
// The visible entry should be the last committed URL, not the pending one. // The visible entry should be the last committed URL, not the pending one.
EXPECT_EQ(kExistingURL, controller.GetVisibleEntry()->GetURL()); EXPECT_EQ(kExistingURL, controller.GetVisibleEntry()->GetURL());
...@@ -1188,7 +1188,7 @@ TEST_F(NavigationControllerTest, LoadURL_RedirectAbortDoesntShowPendingURL) { ...@@ -1188,7 +1188,7 @@ TEST_F(NavigationControllerTest, LoadURL_RedirectAbortDoesntShowPendingURL) {
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex()); EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
// The delegate should have been notified twice: once for the loading state // The delegate should have been notified twice: once for the loading state
// change, and once for the url change. // change, and once for the url change.
EXPECT_EQ(5, delegate->navigation_state_change_count()); EXPECT_EQ(4, delegate->navigation_state_change_count());
// The visible entry should be the last committed URL, not the pending one, // The visible entry should be the last committed URL, not the pending one,
// so that no spoof is possible. // so that no spoof is possible.
...@@ -4838,4 +4838,106 @@ TEST_F(NavigationControllerTest, NoURLRewriteForSubframes) { ...@@ -4838,4 +4838,106 @@ TEST_F(NavigationControllerTest, NoURLRewriteForSubframes) {
BrowserURLHandlerImpl::GetInstance()->SetFixupHandlerForTesting(nullptr); BrowserURLHandlerImpl::GetInstance()->SetFixupHandlerForTesting(nullptr);
} }
// Tests that calling RemoveForwareEntries() clears all forward entries
// including non-committed entries.
TEST_F(NavigationControllerTest, PruneForwardEntries) {
NavigationControllerImpl& controller = controller_impl();
const GURL url_0("http://foo/0");
const GURL url_1("http://foo/1");
const GURL url_2("http://foo/2");
const GURL url_3("http://foo/3");
const GURL url_transient("http://foo/transient");
NavigateAndCommit(url_0);
NavigateAndCommit(url_1);
NavigateAndCommit(url_2);
NavigateAndCommit(url_3);
// Set a WebContentsDelegate to listen for state changes.
std::unique_ptr<TestWebContentsDelegate> delegate(
new TestWebContentsDelegate());
EXPECT_FALSE(contents()->GetDelegate());
contents()->SetDelegate(delegate.get());
controller.GoBack();
// Ensure that non-committed entries are removed even if there are no forward
// entries.
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(2, controller.GetPendingEntryIndex());
EXPECT_EQ(2, controller.GetCurrentEntryIndex());
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
int state_change_count = delegate->navigation_state_change_count();
controller.PruneForwardEntries();
EXPECT_EQ(4, controller.GetEntryCount());
EXPECT_EQ(-1, controller.GetPendingEntryIndex());
EXPECT_EQ(3, controller.GetCurrentEntryIndex());
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(0U, navigation_list_pruned_counter_);
EXPECT_EQ(state_change_count + 1, delegate->navigation_state_change_count());
controller.GoBack();
contents()->CommitPendingNavigation();
controller.GoBack();
contents()->CommitPendingNavigation();
controller.GoBack();
contents()->CommitPendingNavigation();
controller.GoForward();
EXPECT_EQ(1, controller.GetPendingEntryIndex());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
// Insert a transient entry before the pending one.
std::unique_ptr<NavigationEntry> transient_entry(new NavigationEntryImpl);
transient_entry->SetURL(url_transient);
controller.SetTransientEntry(std::move(transient_entry));
state_change_count = delegate->navigation_state_change_count();
controller.PruneForwardEntries();
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_FALSE(controller.CanGoForward());
EXPECT_FALSE(controller.CanGoBack());
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
EXPECT_EQ(0, controller.GetCurrentEntryIndex());
EXPECT_EQ(-1, controller.GetPendingEntryIndex());
EXPECT_EQ(nullptr, controller.GetPendingEntry());
EXPECT_EQ(nullptr, controller.GetTransientEntry());
EXPECT_EQ(url_0, controller.GetVisibleEntry()->GetURL());
EXPECT_EQ(1U, navigation_list_pruned_counter_);
EXPECT_EQ(1, last_navigation_entry_pruned_details_.index);
EXPECT_EQ(3, last_navigation_entry_pruned_details_.count);
EXPECT_EQ(state_change_count + 1, delegate->navigation_state_change_count());
}
// Make sure that cloning a WebContentsImpl and clearing forward entries
// before the first commit doesn't clear all entries.
TEST_F(NavigationControllerTest, PruneForwardEntriesAfterClone) {
NavigationControllerImpl& controller = controller_impl();
const GURL url1("http://foo1");
const GURL url2("http://foo2");
NavigateAndCommit(url1);
NavigateAndCommit(url2);
std::unique_ptr<WebContents> clone(controller.GetWebContents()->Clone());
clone->GetController().LoadIfNecessary();
// Set a WebContentsDelegate to listen for state changes after the clone call
// to only count state changes from the PruneForwardEntries call.
std::unique_ptr<TestWebContentsDelegate> delegate(
new TestWebContentsDelegate());
EXPECT_FALSE(clone->GetDelegate());
clone->SetDelegate(delegate.get());
EXPECT_EQ(1, clone->GetController().GetPendingEntryIndex());
clone->GetController().PruneForwardEntries();
ASSERT_EQ(2, clone->GetController().GetEntryCount());
EXPECT_EQ(-1, clone->GetController().GetPendingEntryIndex());
EXPECT_EQ(url2, clone->GetController().GetVisibleEntry()->GetURL());
EXPECT_EQ(0U, navigation_list_pruned_counter_);
EXPECT_EQ(1, delegate->navigation_state_change_count());
}
} // namespace content } // namespace content
...@@ -271,6 +271,13 @@ import org.chromium.content_public.common.ResourceRequestBody; ...@@ -271,6 +271,13 @@ import org.chromium.content_public.common.ResourceRequestBody;
return false; return false;
} }
@Override
public void pruneForwardEntries() {
if (mNativeNavigationControllerAndroid == 0) return;
NavigationControllerImplJni.get().pruneForwardEntries(
mNativeNavigationControllerAndroid, NavigationControllerImpl.this);
}
@Override @Override
public String getEntryExtraData(int index, String key) { public String getEntryExtraData(int index, String key) {
if (mNativeNavigationControllerAndroid == 0) return null; if (mNativeNavigationControllerAndroid == 0) return null;
...@@ -362,6 +369,8 @@ import org.chromium.content_public.common.ResourceRequestBody; ...@@ -362,6 +369,8 @@ import org.chromium.content_public.common.ResourceRequestBody;
long nativeNavigationControllerAndroid, NavigationControllerImpl caller); long nativeNavigationControllerAndroid, NavigationControllerImpl caller);
boolean removeEntryAtIndex( boolean removeEntryAtIndex(
long nativeNavigationControllerAndroid, NavigationControllerImpl caller, int index); long nativeNavigationControllerAndroid, NavigationControllerImpl caller, int index);
void pruneForwardEntries(
long nativeNavigationControllerAndroid, NavigationControllerImpl caller);
String getEntryExtraData(long nativeNavigationControllerAndroid, String getEntryExtraData(long nativeNavigationControllerAndroid,
NavigationControllerImpl caller, int index, String key); NavigationControllerImpl caller, int index, String key);
void setEntryExtraData(long nativeNavigationControllerAndroid, void setEntryExtraData(long nativeNavigationControllerAndroid,
......
...@@ -172,6 +172,12 @@ public interface NavigationController { ...@@ -172,6 +172,12 @@ public interface NavigationController {
*/ */
public boolean removeEntryAtIndex(int index); public boolean removeEntryAtIndex(int index);
/**
* Discards any transient or pending entries, then discards all entries after the current entry
* index.
*/
void pruneForwardEntries();
/** /**
* Gets extra data on the {@link NavigationEntry} at {@code index}. * Gets extra data on the {@link NavigationEntry} at {@code index}.
* @param index The index of the navigation entry. * @param index The index of the navigation entry.
......
...@@ -414,6 +414,10 @@ class NavigationController { ...@@ -414,6 +414,10 @@ class NavigationController {
// Otherwise this call discards any transient or pending entries. // Otherwise this call discards any transient or pending entries.
virtual bool RemoveEntryAtIndex(int index) = 0; virtual bool RemoveEntryAtIndex(int index) = 0;
// Discards any transient or pending entries, then discards all entries after
// the current entry index.
virtual void PruneForwardEntries() = 0;
// Random -------------------------------------------------------------------- // Random --------------------------------------------------------------------
// Session storage depends on dom_storage that depends on blink::WebString. // Session storage depends on dom_storage that depends on blink::WebString.
......
...@@ -120,6 +120,9 @@ public class MockNavigationController implements NavigationController { ...@@ -120,6 +120,9 @@ public class MockNavigationController implements NavigationController {
return false; return false;
} }
@Override
public void pruneForwardEntries() {}
@Override @Override
public String getEntryExtraData(int index, String key) { public String getEntryExtraData(int index, String key) {
return null; return null;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment