// 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; // 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 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