Commit 56c5ebd9 authored by Scott Violet's avatar Scott Violet Committed by Commit Bot

weblayer: avoid calling WebContents::Stop() at certain points

Calling WebContents::Stop() from
WebContentsObserver::DidStartNavigation() or
WebContentsObserver::DidRedirectNavigation triggers a crash. This patch
works around this by installing a NavigationThrottle. The
NavigationThrottle cancels the navigation as necessary. Does this
ensures we don't attempt to call Stop() while servicing these functions,
yet ends up with the same result.

BUG=1068115
TEST=Covered by weblayer_browsertests

Change-Id: I284842120c2466a2a74856c0f72201200137b9af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2141232
Commit-Queue: Scott Violet <sky@chromium.org>
Reviewed-by: default avatarJohn Abd-El-Malek <jam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#758214}
parent c444b63a
...@@ -527,6 +527,26 @@ public class NavigationTest { ...@@ -527,6 +527,26 @@ public class NavigationTest {
return navigationUrl.get(); return navigationUrl.get();
} }
@Test
@SmallTest
public void testStopFromOnNavigationStarted() throws Exception {
final InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
final BoundedCountDownLatch doneLatch = new BoundedCountDownLatch(1);
NavigationCallback navigationCallback = new NavigationCallback() {
@Override
public void onNavigationStarted(Navigation navigation) {
activity.getTab().getNavigationController().stop();
doneLatch.countDown();
}
};
runOnUiThreadBlocking(() -> {
NavigationController controller = activity.getTab().getNavigationController();
controller.registerNavigationCallback(navigationCallback);
controller.navigate(Uri.parse(URL1));
});
doneLatch.timedAwait();
}
// NavigationCallback implementation that sets a header in either start or redirect. // NavigationCallback implementation that sets a header in either start or redirect.
private static final class HeaderSetter extends NavigationCallback { private static final class HeaderSetter extends NavigationCallback {
private final String mName; private final String mName;
......
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
#include "weblayer/browser/browser_process.h" #include "weblayer/browser/browser_process.h"
#include "weblayer/browser/feature_list_creator.h" #include "weblayer/browser/feature_list_creator.h"
#include "weblayer/browser/i18n_util.h" #include "weblayer/browser/i18n_util.h"
#include "weblayer/browser/navigation_controller_impl.h"
#include "weblayer/browser/profile_impl.h" #include "weblayer/browser/profile_impl.h"
#include "weblayer/browser/system_network_context_manager.h" #include "weblayer/browser/system_network_context_manager.h"
#include "weblayer/browser/tab_impl.h" #include "weblayer/browser/tab_impl.h"
...@@ -406,6 +407,18 @@ std::vector<std::unique_ptr<content::NavigationThrottle>> ...@@ -406,6 +407,18 @@ std::vector<std::unique_ptr<content::NavigationThrottle>>
ContentBrowserClientImpl::CreateThrottlesForNavigation( ContentBrowserClientImpl::CreateThrottlesForNavigation(
content::NavigationHandle* handle) { content::NavigationHandle* handle) {
std::vector<std::unique_ptr<content::NavigationThrottle>> throttles; std::vector<std::unique_ptr<content::NavigationThrottle>> throttles;
// This throttle *must* be first as it's responsible for calling to
// NavigationController for certain events.
TabImpl* tab = TabImpl::FromWebContents(handle->GetWebContents());
if (tab) {
auto throttle =
static_cast<NavigationControllerImpl*>(tab->GetNavigationController())
->CreateNavigationThrottle(handle);
if (throttle)
throttles.push_back(std::move(throttle));
}
throttles.push_back(std::make_unique<SSLErrorNavigationThrottle>( throttles.push_back(std::make_unique<SSLErrorNavigationThrottle>(
handle, std::make_unique<SSLCertReporterImpl>(), handle, std::make_unique<SSLCertReporterImpl>(),
base::BindOnce(&HandleSSLErrorWrapper), base::BindOnce(&IsInHostedApp))); base::BindOnce(&HandleSSLErrorWrapper), base::BindOnce(&IsInHostedApp)));
......
...@@ -20,6 +20,35 @@ namespace weblayer { ...@@ -20,6 +20,35 @@ namespace weblayer {
namespace { namespace {
class StopNavigationObserver : public NavigationObserver {
public:
StopNavigationObserver(NavigationController* controller, bool stop_in_start)
: controller_(controller), stop_in_start_(stop_in_start) {
controller_->AddObserver(this);
}
~StopNavigationObserver() override { controller_->RemoveObserver(this); }
void WaitForNavigation() { run_loop_.Run(); }
// NavigationObserver:
void NavigationStarted(Navigation* navigation) override {
if (stop_in_start_)
controller_->Stop();
}
void NavigationRedirected(Navigation* navigation) override {
if (!stop_in_start_)
controller_->Stop();
}
void NavigationFailed(Navigation* navigation) override { run_loop_.Quit(); }
private:
NavigationController* controller_;
// If true Stop() is called in NavigationStarted(), otherwise Stop() is
// called in NavigationRedirected().
const bool stop_in_start_;
base::RunLoop run_loop_;
};
class OneShotNavigationObserver : public NavigationObserver { class OneShotNavigationObserver : public NavigationObserver {
public: public:
explicit OneShotNavigationObserver(Shell* shell) : tab_(shell->tab()) { explicit OneShotNavigationObserver(Shell* shell) : tab_(shell->tab()) {
...@@ -149,6 +178,28 @@ IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpConnectivityError) { ...@@ -149,6 +178,28 @@ IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpConnectivityError) {
EXPECT_EQ(observer.navigation_state(), NavigationState::kFailed); EXPECT_EQ(observer.navigation_state(), NavigationState::kFailed);
} }
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, StopInOnStart) {
EXPECT_TRUE(embedded_test_server()->Start());
StopNavigationObserver observer(shell()->tab()->GetNavigationController(),
true);
shell()->tab()->GetNavigationController()->Navigate(
embedded_test_server()->GetURL("/simple_page.html"));
observer.WaitForNavigation();
}
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, StopInOnRedirect) {
EXPECT_TRUE(embedded_test_server()->Start());
StopNavigationObserver observer(shell()->tab()->GetNavigationController(),
false);
const GURL original_url = embedded_test_server()->GetURL("/simple_page.html");
shell()->tab()->GetNavigationController()->Navigate(
embedded_test_server()->GetURL("/server-redirect?" +
original_url.spec()));
observer.WaitForNavigation();
}
namespace { namespace {
class HeaderInjectorNavigationObserver : public NavigationObserver { class HeaderInjectorNavigationObserver : public NavigationObserver {
......
...@@ -4,9 +4,12 @@ ...@@ -4,9 +4,12 @@
#include "weblayer/browser/navigation_controller_impl.h" #include "weblayer/browser/navigation_controller_impl.h"
#include "base/auto_reset.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "ui/base/page_transition_types.h" #include "ui/base/page_transition_types.h"
#include "weblayer/browser/tab_impl.h" #include "weblayer/browser/tab_impl.h"
...@@ -26,11 +29,75 @@ using base::android::ScopedJavaLocalRef; ...@@ -26,11 +29,75 @@ using base::android::ScopedJavaLocalRef;
namespace weblayer { namespace weblayer {
// NavigationThrottle implementation responsible for stopping the navigation in
// certain situations. In particular, WebContents::Stop() can not be called from
// WebContentsObserver::DidStartNavigation() or
// WebContentsObserver::DidRedirectNavigation() (to do so crashes). To work
// around this NavigationControllerImpl detects if Stop() is called while
// processing DidStartNavigation() or DidRedirectNavigation() and sets state in
// NavigationThrottleImpl such that the navigation is canceled.
//
// The call order for starting is WebContents::DidStartNavigation() followed
// by NavigationThrottle::WillStartRequest(). To make things interesting,
// the NavigationThrottle is created *after* DidStartNavigation(). OTOH,
// the call order for redirect is NavigationThrottle::WillRedirectRequest()
// followed by WebContentsObserver::DidRedirectNavigation(). To ensure the
// right call order NavigationControllerImpl does nothing in
// DidRedirectNavigation(), instead this class calls to the
// NavigationController and then returns cancel if necessary. While redirect
// handling is initiated from this class, start must be handled from the
// WebContentsObserver call. This is necessary as some code paths do *not*
// call to NavigationThrottle::WillStartRequest().
class NavigationControllerImpl::NavigationThrottleImpl
: public content::NavigationThrottle {
public:
NavigationThrottleImpl(NavigationControllerImpl* controller,
content::NavigationHandle* handle)
: NavigationThrottle(handle), controller_(controller) {}
NavigationThrottleImpl(const NavigationThrottleImpl&) = delete;
NavigationThrottleImpl& operator=(const NavigationThrottleImpl&) = delete;
~NavigationThrottleImpl() override = default;
void ScheduleCancel() { should_cancel_ = true; }
// content::NavigationThrottle:
ThrottleCheckResult WillStartRequest() override {
return should_cancel_ ? CANCEL : PROCEED;
}
ThrottleCheckResult WillRedirectRequest() override {
controller_->WillRedirectRequest(this, navigation_handle());
return should_cancel_ ? CANCEL : PROCEED;
}
const char* GetNameForLogging() override {
return "WebLayerNavigationControllerThrottle";
}
private:
NavigationControllerImpl* controller_;
bool should_cancel_ = false;
};
NavigationControllerImpl::NavigationControllerImpl(TabImpl* tab) NavigationControllerImpl::NavigationControllerImpl(TabImpl* tab)
: WebContentsObserver(tab->web_contents()) {} : WebContentsObserver(tab->web_contents()) {}
NavigationControllerImpl::~NavigationControllerImpl() = default; NavigationControllerImpl::~NavigationControllerImpl() = default;
std::unique_ptr<content::NavigationThrottle>
NavigationControllerImpl::CreateNavigationThrottle(
content::NavigationHandle* handle) {
if (!handle->IsInMainFrame())
return nullptr;
auto throttle = std::make_unique<NavigationThrottleImpl>(this, handle);
DCHECK(navigation_map_.find(handle) != navigation_map_.end());
auto* navigation = navigation_map_[handle].get();
if (navigation->should_stop_when_throttle_created())
throttle->ScheduleCancel();
return throttle;
}
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
void NavigationControllerImpl::SetNavigationControllerImpl( void NavigationControllerImpl::SetNavigationControllerImpl(
JNIEnv* env, JNIEnv* env,
...@@ -74,6 +141,29 @@ ScopedJavaLocalRef<jstring> NavigationControllerImpl::GetNavigationEntryTitle( ...@@ -74,6 +141,29 @@ ScopedJavaLocalRef<jstring> NavigationControllerImpl::GetNavigationEntryTitle(
} }
#endif #endif
void NavigationControllerImpl::WillRedirectRequest(
NavigationThrottleImpl* throttle,
content::NavigationHandle* navigation_handle) {
DCHECK(navigation_handle->IsInMainFrame());
DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end());
auto* navigation = navigation_map_[navigation_handle].get();
navigation->set_safe_to_set_request_headers(true);
DCHECK(!active_throttle_);
base::AutoReset<NavigationThrottleImpl*> auto_reset(&active_throttle_,
throttle);
#if defined(OS_ANDROID)
if (java_controller_) {
TRACE_EVENT0("weblayer",
"Java_NavigationControllerImpl_navigationRedirected");
Java_NavigationControllerImpl_navigationRedirected(
AttachCurrentThread(), java_controller_, navigation->java_navigation());
}
#endif
for (auto& observer : observers_)
observer.NavigationRedirected(navigation);
navigation->set_safe_to_set_request_headers(false);
}
void NavigationControllerImpl::AddObserver(NavigationObserver* observer) { void NavigationControllerImpl::AddObserver(NavigationObserver* observer) {
observers_.AddObserver(observer); observers_.AddObserver(observer);
} }
...@@ -117,7 +207,12 @@ void NavigationControllerImpl::Reload() { ...@@ -117,7 +207,12 @@ void NavigationControllerImpl::Reload() {
} }
void NavigationControllerImpl::Stop() { void NavigationControllerImpl::Stop() {
web_contents()->Stop(); if (navigation_starting_)
navigation_starting_->set_should_stop_when_throttle_created();
else if (active_throttle_)
active_throttle_->ScheduleCancel();
else
web_contents()->Stop();
} }
int NavigationControllerImpl::GetNavigationListSize() { int NavigationControllerImpl::GetNavigationListSize() {
...@@ -147,9 +242,15 @@ void NavigationControllerImpl::DidStartNavigation( ...@@ -147,9 +242,15 @@ void NavigationControllerImpl::DidStartNavigation(
if (!navigation_handle->IsInMainFrame()) if (!navigation_handle->IsInMainFrame())
return; return;
// This function should not be called reentrantly.
DCHECK(!navigation_starting_);
DCHECK(!base::Contains(navigation_map_, navigation_handle));
navigation_map_[navigation_handle] = navigation_map_[navigation_handle] =
std::make_unique<NavigationImpl>(navigation_handle); std::make_unique<NavigationImpl>(navigation_handle);
auto* navigation = navigation_map_[navigation_handle].get(); auto* navigation = navigation_map_[navigation_handle].get();
base::AutoReset<NavigationImpl*> auto_reset(&navigation_starting_,
navigation);
navigation->set_safe_to_set_request_headers(true); navigation->set_safe_to_set_request_headers(true);
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
if (java_controller_) { if (java_controller_) {
...@@ -172,23 +273,9 @@ void NavigationControllerImpl::DidStartNavigation( ...@@ -172,23 +273,9 @@ void NavigationControllerImpl::DidStartNavigation(
void NavigationControllerImpl::DidRedirectNavigation( void NavigationControllerImpl::DidRedirectNavigation(
content::NavigationHandle* navigation_handle) { content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsInMainFrame()) // NOTE: this implementation should remain empty. Real implementation is in
return; // WillRedirectNavigation(). See description of NavigationThrottleImpl for
// more information.
DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end());
auto* navigation = navigation_map_[navigation_handle].get();
navigation->set_safe_to_set_request_headers(true);
#if defined(OS_ANDROID)
if (java_controller_) {
TRACE_EVENT0("weblayer",
"Java_NavigationControllerImpl_navigationRedirected");
Java_NavigationControllerImpl_navigationRedirected(
AttachCurrentThread(), java_controller_, navigation->java_navigation());
}
#endif
for (auto& observer : observers_)
observer.NavigationRedirected(navigation);
navigation->set_safe_to_set_request_headers(false);
} }
void NavigationControllerImpl::ReadyToCommitNavigation( void NavigationControllerImpl::ReadyToCommitNavigation(
......
...@@ -19,6 +19,10 @@ ...@@ -19,6 +19,10 @@
#include "base/android/scoped_java_ref.h" #include "base/android/scoped_java_ref.h"
#endif #endif
namespace content {
class NavigationThrottle;
}
namespace weblayer { namespace weblayer {
class TabImpl; class TabImpl;
...@@ -28,6 +32,11 @@ class NavigationControllerImpl : public NavigationController, ...@@ -28,6 +32,11 @@ class NavigationControllerImpl : public NavigationController,
explicit NavigationControllerImpl(TabImpl* tab); explicit NavigationControllerImpl(TabImpl* tab);
~NavigationControllerImpl() override; ~NavigationControllerImpl() override;
// Creates the NavigationThrottle used to ensure WebContents::Stop() is called
// at safe times. See NavigationControllerImpl for details.
std::unique_ptr<content::NavigationThrottle> CreateNavigationThrottle(
content::NavigationHandle* handle);
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
void SetNavigationControllerImpl( void SetNavigationControllerImpl(
JNIEnv* env, JNIEnv* env,
...@@ -80,6 +89,13 @@ class NavigationControllerImpl : public NavigationController, ...@@ -80,6 +89,13 @@ class NavigationControllerImpl : public NavigationController,
#endif #endif
private: private:
class NavigationThrottleImpl;
// Called from NavigationControllerImpl::WillRedirectRequest(). See
// description of NavigationControllerImpl for details.
void WillRedirectRequest(NavigationThrottleImpl* throttle,
content::NavigationHandle* navigation_handle);
// NavigationController implementation: // NavigationController implementation:
void AddObserver(NavigationObserver* observer) override; void AddObserver(NavigationObserver* observer) override;
void RemoveObserver(NavigationObserver* observer) override; void RemoveObserver(NavigationObserver* observer) override;
...@@ -119,6 +135,13 @@ class NavigationControllerImpl : public NavigationController, ...@@ -119,6 +135,13 @@ class NavigationControllerImpl : public NavigationController,
std::map<content::NavigationHandle*, std::unique_ptr<NavigationImpl>> std::map<content::NavigationHandle*, std::unique_ptr<NavigationImpl>>
navigation_map_; navigation_map_;
// If non-null then processing is inside DidStartNavigation() and
// |navigation_starting_| is the NavigationImpl that was created.
NavigationImpl* navigation_starting_ = nullptr;
// Set to non-null while in WillRedirectRequest().
NavigationThrottleImpl* active_throttle_ = nullptr;
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
base::android::ScopedJavaGlobalRef<jobject> java_controller_; base::android::ScopedJavaGlobalRef<jobject> java_controller_;
#endif #endif
......
...@@ -24,6 +24,13 @@ class NavigationImpl : public Navigation { ...@@ -24,6 +24,13 @@ class NavigationImpl : public Navigation {
explicit NavigationImpl(content::NavigationHandle* navigation_handle); explicit NavigationImpl(content::NavigationHandle* navigation_handle);
~NavigationImpl() override; ~NavigationImpl() override;
void set_should_stop_when_throttle_created() {
should_stop_when_throttle_created_ = true;
}
bool should_stop_when_throttle_created() const {
return should_stop_when_throttle_created_;
}
void set_safe_to_set_request_headers(bool value) { void set_safe_to_set_request_headers(bool value) {
safe_to_set_request_headers_ = value; safe_to_set_request_headers_ = value;
} }
...@@ -81,6 +88,10 @@ class NavigationImpl : public Navigation { ...@@ -81,6 +88,10 @@ class NavigationImpl : public Navigation {
content::NavigationHandle* navigation_handle_; content::NavigationHandle* navigation_handle_;
// Used to delay calling Stop() until safe. See
// NavigationControllerImpl::NavigationThrottleImpl for details.
bool should_stop_when_throttle_created_ = false;
// Whether SetRequestHeader() is allowed at this time. // Whether SetRequestHeader() is allowed at this time.
bool safe_to_set_request_headers_ = false; bool safe_to_set_request_headers_ = false;
......
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