| // Copyright 2015 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 "components/navigation_interception/intercept_navigation_throttle.h" |
| |
| #include "components/navigation_interception/navigation_params.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_handle.h" |
| |
| using content::BrowserThread; |
| |
| namespace navigation_interception { |
| |
| namespace { |
| |
| using ChecksPerformedCallback = base::Callback<void(bool, bool)>; |
| |
| // This is used to run |should_ignore_callback| if it can destroy the |
| // WebContents (and the InterceptNavigationThrottle along). In that case, |
| // |on_checks_performed_callback| will be a no-op. |
| void RunCallback( |
| content::WebContents* web_contents, |
| const NavigationParams& navigation_params, |
| InterceptNavigationThrottle::CheckCallback should_ignore_callback, |
| ChecksPerformedCallback on_checks_performed_callback, |
| base::WeakPtr<InterceptNavigationThrottle> throttle) { |
| bool should_ignore_navigation = |
| should_ignore_callback.Run(web_contents, navigation_params); |
| |
| // If the InterceptNavigationThrottle that called RunCallback is still alive |
| // after |should_ignore_callback| has run, this will run |
| // InterceptNavigationThrottle::OnAsynchronousChecksPerformed. |
| // TODO(clamy): remove this boolean after crbug.com/570200 is fixed. |
| bool throttle_was_destroyed = !throttle.get(); |
| on_checks_performed_callback.Run(should_ignore_navigation, |
| throttle_was_destroyed); |
| } |
| |
| } // namespace |
| |
| InterceptNavigationThrottle::InterceptNavigationThrottle( |
| content::NavigationHandle* navigation_handle, |
| CheckCallback should_ignore_callback, |
| bool run_callback_synchronously) |
| : content::NavigationThrottle(navigation_handle), |
| should_ignore_callback_(should_ignore_callback), |
| run_callback_synchronously_(run_callback_synchronously), |
| weak_factory_(this) {} |
| |
| InterceptNavigationThrottle::~InterceptNavigationThrottle() {} |
| |
| content::NavigationThrottle::ThrottleCheckResult |
| InterceptNavigationThrottle::WillStartRequest() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return CheckIfShouldIgnoreNavigation(false); |
| } |
| |
| content::NavigationThrottle::ThrottleCheckResult |
| InterceptNavigationThrottle::WillRedirectRequest() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return CheckIfShouldIgnoreNavigation(true); |
| } |
| |
| content::NavigationThrottle::ThrottleCheckResult |
| InterceptNavigationThrottle::CheckIfShouldIgnoreNavigation(bool is_redirect) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| NavigationParams navigation_params( |
| navigation_handle()->GetURL(), navigation_handle()->GetReferrer(), |
| navigation_handle()->HasUserGesture(), navigation_handle()->IsPost(), |
| navigation_handle()->GetPageTransition(), is_redirect, |
| navigation_handle()->IsExternalProtocol(), true); |
| |
| if (run_callback_synchronously_) { |
| bool should_ignore_navigation = should_ignore_callback_.Run( |
| navigation_handle()->GetWebContents(), navigation_params); |
| return should_ignore_navigation |
| ? content::NavigationThrottle::CANCEL_AND_IGNORE |
| : content::NavigationThrottle::PROCEED; |
| } |
| |
| // When the callback can potentially destroy the WebContents, along with the |
| // NavigationHandle and this InterceptNavigationThrottle, it should be run |
| // asynchronously. This will ensure that no objects on the stack can be |
| // deleted, and that the stack does not unwind through them in a deleted |
| // state. |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&InterceptNavigationThrottle::RunCallbackAsynchronously, |
| weak_factory_.GetWeakPtr(), navigation_params)); |
| return DEFER; |
| } |
| |
| void InterceptNavigationThrottle::RunCallbackAsynchronously( |
| const NavigationParams& navigation_params) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Run the callback in a helper function as it may lead ot the destruction of |
| // this InterceptNavigationThrottle. |
| RunCallback( |
| navigation_handle()->GetWebContents(), navigation_params, |
| should_ignore_callback_, |
| base::Bind(&InterceptNavigationThrottle::OnAsynchronousChecksPerformed, |
| weak_factory_.GetWeakPtr()), |
| weak_factory_.GetWeakPtr()); |
| |
| // DO NOT ADD CODE AFTER HERE: at this point the InterceptNavigationThrottle |
| // may have been destroyed by the |should_ignore_callback_|. Adding code here |
| // will cause use-after-free bugs. |
| // |
| // Code that needs to act on the result of the |should_ignore_callback_| |
| // should be put inside OnAsynchronousChecksPerformed. This function will be |
| // called after |should_ignore_callback_| has run, if this |
| // InterceptNavigationThrottle is still alive. |
| } |
| |
| void InterceptNavigationThrottle::OnAsynchronousChecksPerformed( |
| bool should_ignore_navigation, |
| bool throttle_was_destroyed) { |
| CHECK(!throttle_was_destroyed); |
| content::NavigationHandle* handle = navigation_handle(); |
| CHECK(handle); |
| if (should_ignore_navigation) { |
| navigation_handle()->CancelDeferredNavigation( |
| content::NavigationThrottle::CANCEL_AND_IGNORE); |
| } else { |
| handle->Resume(); |
| } |
| } |
| |
| } // namespace navigation_interception |