diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index 661a6560d..073a13204 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -44,6 +44,7 @@ #include "chrome/browser/spellchecker/spellcheck_service.h" #include "chrome/browser/ui/prefs/prefs_tab_helper.h" #include "components/zoom/zoom_controller.h" +#include "content/browser/frame_host/navigation_handle_impl.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/gpu/compositor_util.h" #include "content/common/view_messages.h" @@ -53,6 +54,7 @@ #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" +#include "content/public/browser/navigation_handle.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" @@ -61,6 +63,7 @@ #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/resource_request_info.h" +#include "net/base/net_errors.h" #include "third_party/WebKit/public/web/WebFindOptions.h" #include "ui/events/base_event_utils.h" @@ -2499,36 +2502,51 @@ void CefBrowserHostImpl::RenderProcessGone(base::TerminationStatus status) { } } -void CefBrowserHostImpl::DidCommitProvisionalLoadForFrame( - content::RenderFrameHost* render_frame_host, - const GURL& url, - ui::PageTransition transition_type) { - const bool is_main_frame = !render_frame_host->GetParent(); - CefRefPtr frame = GetOrCreateFrame( - render_frame_host->GetRoutingID(), - CefFrameHostImpl::kUnspecifiedFrameId, - is_main_frame, - base::string16(), - url); - OnLoadStart(frame, url, transition_type); - if (is_main_frame) - OnAddressChange(frame, url); -} +void CefBrowserHostImpl::DidFinishNavigation( + content::NavigationHandle* navigation_handle) { + CHECK(navigation_handle->GetRenderFrameHost()); -void CefBrowserHostImpl::DidFailProvisionalLoad( - content::RenderFrameHost* render_frame_host, - const GURL& validated_url, - int error_code, - const base::string16& error_description, - bool was_ignored_by_handler) { - const bool is_main_frame = !render_frame_host->GetParent(); - CefRefPtr frame = GetOrCreateFrame( - render_frame_host->GetRoutingID(), - CefFrameHostImpl::kUnspecifiedFrameId, - is_main_frame, - base::string16(), - GURL()); - OnLoadError(frame, validated_url, error_code, error_description); + const net::Error error_code = navigation_handle->GetNetErrorCode(); + if (error_code == net::OK) { + // The navigation has been committed. + const bool is_main_frame = navigation_handle->IsInMainFrame(); + const GURL& url = navigation_handle->GetURL(); + + // This also updates the URL associated with the frame. + CefRefPtr frame = GetOrCreateFrame( + navigation_handle->GetRenderFrameHost()->GetRoutingID(), + CefFrameHostImpl::kUnspecifiedFrameId, + is_main_frame, base::string16(), url); + + // Don't call OnLoadStart for same page navigations (fragments, + // history state). + if (!navigation_handle->IsSamePage()) + OnLoadStart(frame, navigation_handle->GetPageTransition()); + + if (is_main_frame) + OnAddressChange(frame, url); + } else { + // The navigation failed before commit. Originates from + // RenderFrameHostImpl::OnDidFailProvisionalLoadWithError. + CefRefPtr frame = GetOrCreateFrame( + navigation_handle->GetRenderFrameHost()->GetRoutingID(), + CefFrameHostImpl::kUnspecifiedFrameId, + navigation_handle->IsInMainFrame(), base::string16(), GURL()); + + // OnLoadStart/OnLoadEnd will not be called. + OnLoadError(frame, navigation_handle->GetURL(), error_code); + } + + if (!web_contents()) + return; + + CefBrowserContext* context = + static_cast(web_contents()->GetBrowserContext()); + if (!context) + return; + + context->AddVisitedURLs(static_cast( + navigation_handle)->redirect_chain()); } void CefBrowserHostImpl::DocumentAvailableInMainFrame() { @@ -2542,6 +2560,8 @@ void CefBrowserHostImpl::DidFailLoad( int error_code, const base::string16& error_description, bool was_ignored_by_handler) { + // The navigation failed after commit. OnLoadStart was called so we also call + // OnLoadEnd. const bool is_main_frame = !render_frame_host->GetParent(); CefRefPtr frame = GetOrCreateFrame( render_frame_host->GetRoutingID(), @@ -2549,7 +2569,7 @@ void CefBrowserHostImpl::DidFailLoad( is_main_frame, base::string16(), validated_url); - OnLoadError(frame, validated_url, error_code, error_description); + OnLoadError(frame, validated_url, error_code); OnLoadEnd(frame, validated_url, error_code); } @@ -2570,21 +2590,6 @@ void CefBrowserHostImpl::FrameDeleted( focused_frame_id_ = CefFrameHostImpl::kInvalidFrameId; } -void CefBrowserHostImpl::DidNavigateAnyFrame( - content::RenderFrameHost* render_frame_host, - const content::LoadCommittedDetails& details, - const content::FrameNavigateParams& params) { - if (!web_contents()) - return; - - CefBrowserContext* context = - static_cast(web_contents()->GetBrowserContext()); - if (!context) - return; - - context->AddVisitedURLs(params.redirects); -} - void CefBrowserHostImpl::TitleWasSet(content::NavigationEntry* entry, bool explicit_set) { // |entry| may be NULL if a popup is created via window.open and never @@ -3034,7 +3039,6 @@ void CefBrowserHostImpl::OnAddressChange(CefRefPtr frame, } void CefBrowserHostImpl::OnLoadStart(CefRefPtr frame, - const GURL& url, ui::PageTransition transition_type) { if (client_.get()) { CefRefPtr handler = client_->GetLoadHandler(); @@ -3048,8 +3052,7 @@ void CefBrowserHostImpl::OnLoadStart(CefRefPtr frame, void CefBrowserHostImpl::OnLoadError(CefRefPtr frame, const GURL& url, - int error_code, - const base::string16& error_description) { + int error_code) { if (client_.get()) { CefRefPtr handler = client_->GetLoadHandler(); if (handler.get()) { @@ -3057,7 +3060,7 @@ void CefBrowserHostImpl::OnLoadError(CefRefPtr frame, // Notify the handler that loading has failed. handler->OnLoadError(this, frame, static_cast(error_code), - CefString(error_description), + net::ErrorToShortString(error_code), url.spec()); frame_destruction_pending_ = false; } diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index 77fcf9c9f..40b2b90b4 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -448,38 +448,21 @@ class CefBrowserHostImpl : public CefBrowserHost, // content::WebContentsObserver methods. using content::WebContentsObserver::BeforeUnloadFired; using content::WebContentsObserver::WasHidden; - void RenderFrameCreated( - content::RenderFrameHost* render_frame_host) override; - void RenderFrameDeleted( - content::RenderFrameHost* render_frame_host) override; - void RenderViewCreated( - content::RenderViewHost* render_view_host) override; - void RenderViewDeleted( - content::RenderViewHost* render_view_host) override; + void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override; + void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; + void RenderViewCreated(content::RenderViewHost* render_view_host) override; + void RenderViewDeleted(content::RenderViewHost* render_view_host) override; void RenderViewReady() override; void RenderProcessGone(base::TerminationStatus status) override; - void DidCommitProvisionalLoadForFrame( - content::RenderFrameHost* render_frame_host, - const GURL& url, - ui::PageTransition transition_type) override; - void DidFailProvisionalLoad( - content::RenderFrameHost* render_frame_host, - const GURL& validated_url, - int error_code, - const base::string16& error_description, - bool was_ignored_by_handler) override; + void DidFinishNavigation( + content::NavigationHandle* navigation_handle) override; void DocumentAvailableInMainFrame() override; void DidFailLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, const base::string16& error_description, bool was_ignored_by_handler) override; - void FrameDeleted( - content::RenderFrameHost* render_frame_host) override; - void DidNavigateAnyFrame( - content::RenderFrameHost* render_frame_host, - const content::LoadCommittedDetails& details, - const content::FrameNavigateParams& params) override; + void FrameDeleted(content::RenderFrameHost* render_frame_host) override; void TitleWasSet(content::NavigationEntry* entry, bool explicit_set) override; void PluginCrashed(const base::FilePath& plugin_path, base::ProcessId plugin_pid) override; @@ -554,12 +537,10 @@ class CefBrowserHostImpl : public CefBrowserHost, void OnAddressChange(CefRefPtr frame, const GURL& url); void OnLoadStart(CefRefPtr frame, - const GURL& url, ui::PageTransition transition_type); void OnLoadError(CefRefPtr frame, const GURL& url, - int error_code, - const base::string16& error_description); + int error_code); void OnLoadEnd(CefRefPtr frame, const GURL& url, int http_status_code); diff --git a/patch/patch.cfg b/patch/patch.cfg index 042ef2ce8..a9e57b12f 100644 --- a/patch/patch.cfg +++ b/patch/patch.cfg @@ -327,4 +327,11 @@ patches = [ 'name': 'views_menu_2102', 'path': '../', }, + { + # Expose RFH via NavigationHandle for retrieval in DidFinishNavigation on + # network error. + # https://groups.google.com/a/chromium.org/d/msg/chromium-dev/6iAQPx_hwh8/gaTR5f1GAQAJ + 'name': 'navigation_handle', + 'path': '../', + }, ] diff --git a/patch/patches/navigation_handle.patch b/patch/patches/navigation_handle.patch new file mode 100644 index 000000000..52061355e --- /dev/null +++ b/patch/patches/navigation_handle.patch @@ -0,0 +1,42 @@ +diff --git content/browser/frame_host/navigation_handle_impl.cc content/browser/frame_host/navigation_handle_impl.cc +index 30536bd..6fdb9a3 100644 +--- content/browser/frame_host/navigation_handle_impl.cc ++++ content/browser/frame_host/navigation_handle_impl.cc +@@ -223,12 +223,6 @@ net::Error NavigationHandleImpl::GetNetErrorCode() { + } + + RenderFrameHostImpl* NavigationHandleImpl::GetRenderFrameHost() { +- // TODO(mkwst): Change this to check against 'READY_TO_COMMIT' once +- // ReadyToCommitNavigation is available whether or not PlzNavigate is +- // enabled. https://crbug.com/621856 +- CHECK_GE(state_, WILL_PROCESS_RESPONSE) +- << "This accessor should only be called after a response has been " +- "delivered for processing."; + return render_frame_host_; + } + +diff --git content/browser/frame_host/navigation_handle_impl.h content/browser/frame_host/navigation_handle_impl.h +index 19b6871..6e85c69 100644 +--- content/browser/frame_host/navigation_handle_impl.h ++++ content/browser/frame_host/navigation_handle_impl.h +@@ -300,6 +300,8 @@ class CONTENT_EXPORT NavigationHandleImpl : public NavigationHandle { + searchable_form_encoding_ = encoding; + } + ++ const std::vector& redirect_chain() const { return redirect_chain_; } ++ + private: + friend class NavigationHandleImplTest; + +diff --git content/browser/frame_host/render_frame_host_impl.cc content/browser/frame_host/render_frame_host_impl.cc +index e496ce3..de019a8 100644 +--- content/browser/frame_host/render_frame_host_impl.cc ++++ content/browser/frame_host/render_frame_host_impl.cc +@@ -1139,6 +1139,7 @@ void RenderFrameHostImpl::OnDidFailProvisionalLoadWithError( + if (navigation_handle_) { + navigation_handle_->set_net_error_code( + static_cast(params.error_code)); ++ navigation_handle_->set_render_frame_host(this); + } + + frame_tree_node_->navigator()->DidFailProvisionalLoadWithError(this, params); diff --git a/tests/ceftests/message_router_unittest.cc b/tests/ceftests/message_router_unittest.cc index 28ff3c067..7be243296 100644 --- a/tests/ceftests/message_router_unittest.cc +++ b/tests/ceftests/message_router_unittest.cc @@ -1058,6 +1058,8 @@ class MultiQueryManager : public CefMessageRouterBrowserSide::Handler { virtual ~MultiQueryManager() {} + std::string label() const { return label_; } + void AddObserver(Observer* observer) { EXPECT_FALSE(running_); observer_set_.insert(observer); @@ -2274,15 +2276,12 @@ class MultiQueryManagerMap : MultiQueryManager* CreateManager(const std::string& url, bool synchronous) { EXPECT_FALSE(finalized_); - // The sub-frame resource should not already exist. - URLManagerMap::const_iterator it = manager_map_.find(url); - EXPECT_EQ(it, manager_map_.end()); - MultiQueryManager* manager = new MultiQueryManager(url, synchronous, static_cast(manager_map_.size()) * 1000); manager->AddObserver(this); - manager_map_.insert(std::make_pair(url, manager)); + all_managers_.push_back(manager); + pending_managers_.push_back(manager); return manager; } @@ -2298,11 +2297,10 @@ class MultiQueryManagerMap : std::string html = "\n"; - URLManagerMap::const_iterator it = manager_map_.begin(); - for (; it != manager_map_.end(); ++it) { - const std::string& name = GetNameForURL(it->first); - html += "\n"; + for (size_t i = 0; i < all_managers_.size(); ++i) { + const std::string& url = all_managers_[i]->label(); + const std::string& name = GetNameForURL(url); + html += "\n"; } html += ""; @@ -2347,7 +2345,7 @@ class MultiQueryManagerMap : } void OnManualQueriesCompleted(MultiQueryManager* manager) override { - const int size = static_cast(manager_map_.size()); + const int size = static_cast(all_managers_.size()); EXPECT_LT(manual_complete_count_, size); if (++manual_complete_count_ == size) { running_ = false; @@ -2367,7 +2365,7 @@ class MultiQueryManagerMap : } void OnAllQueriesCompleted(MultiQueryManager* manager) override { - const int size = static_cast(manager_map_.size()); + const int size = static_cast(all_managers_.size()); EXPECT_LT(total_complete_count_, size); if (++total_complete_count_ == size) { running_ = false; @@ -2389,9 +2387,8 @@ class MultiQueryManagerMap : bool AllComplete() const { EXPECT_TRUE(finalized_); - URLManagerMap::const_iterator it = manager_map_.begin(); - for (; it != manager_map_.end(); ++it) { - if (!it->second->IsAllComplete()) + for (size_t i = 0; i < all_managers_.size(); ++i) { + if (!all_managers_[i]->IsAllComplete()) return false; } return true; @@ -2399,53 +2396,82 @@ class MultiQueryManagerMap : void AssertAllComplete() const { EXPECT_TRUE(finalized_); + EXPECT_TRUE(pending_managers_.empty()); EXPECT_FALSE(running_); - URLManagerMap::const_iterator it = manager_map_.begin(); - for (; it != manager_map_.end(); ++it) - it->second->AssertAllComplete(); + for (size_t i = 0; i < all_managers_.size(); ++i) { + all_managers_[i]->AssertAllComplete(); + } } bool HasAutoQueries() const { - if (manager_map_.empty()) - return false; - - URLManagerMap::const_iterator it = manager_map_.begin(); - for (; it != manager_map_.end(); ++it) { - if (it->second->HasAutoQueries()) + for (size_t i = 0; i < all_managers_.size(); ++i) { + if (all_managers_[i]->HasAutoQueries()) return true; } return false; } + void OnLoadStart(CefRefPtr browser, + CefRefPtr frame) { + if (pending_managers_.empty()) + return; + + const std::string& expected_url = frame->GetURL(); + MultiQueryManager* next_manager = nullptr; + + // Find the pending manager that matches the expected URL. + ManagerList::iterator it = pending_managers_.begin(); + for (; it != pending_managers_.end(); ++it) { + if ((*it)->label() == expected_url) { + next_manager = *it; + pending_managers_.erase(it); + break; + } + } + + EXPECT_TRUE(next_manager); + + const int browser_id = browser->GetIdentifier(); + // Always use the same ID for the main frame. + const int64 frame_id = frame->IsMain() ? -1 : frame->GetIdentifier(); + + const std::pair& id = std::make_pair(browser_id, frame_id); + + // Remove the currently active manager, if any. + ManagerMap::iterator it2 = manager_map_.find(id); + if (it2 != manager_map_.end()) + manager_map_.erase(it2); + + // Add the next manager to the active map. + manager_map_.insert(std::make_pair(id, next_manager)); + } + MultiQueryManager* GetManager(CefRefPtr browser, CefRefPtr frame) const { - const std::string& url = frame->GetURL(); - URLManagerMap::const_iterator it = manager_map_.find(url); - EXPECT_NE(it, manager_map_.end()); + const int browser_id = browser->GetIdentifier(); + // Always use the same ID for the main frame. + const int64 frame_id = frame->IsMain() ? -1 : frame->GetIdentifier(); + + // Find the manager in the active map. + ManagerMap::const_iterator it = + manager_map_.find(std::make_pair(browser_id, frame_id)); + EXPECT_NE(it, manager_map_.end()) << + "browser_id = " << browser_id << ", frame_id = " << frame_id; return it->second; } void RemoveAllManagers() { - if (manager_map_.empty()) + EXPECT_TRUE(pending_managers_.empty()); + if (all_managers_.empty()) return; - URLManagerMap::const_iterator it = manager_map_.begin(); - for (; it != manager_map_.end(); ++it) - delete it->second; - manager_map_.clear(); - } - - std::string GetURLForManager(MultiQueryManager* manager) const { - if (!manager_map_.empty()) { - URLManagerMap::const_iterator it = manager_map_.begin(); - for (; it != manager_map_.end(); ++it) { - if (it->second == manager) - return it->first; - } + for (size_t i = 0; i < all_managers_.size(); ++i) { + delete all_managers_[i]; } - return std::string(); + all_managers_.clear(); + manager_map_.clear(); } static std::string GetNameForURL(const std::string& url) { @@ -2457,9 +2483,16 @@ class MultiQueryManagerMap : } private: - // Map of page URL to MultiQueryManager instance. - typedef std::map URLManagerMap; - URLManagerMap manager_map_; + typedef std::vector ManagerList; + // Map of (browser ID, frame ID) to manager. + typedef std::map, MultiQueryManager*> ManagerMap; + + // All managers that have been created. + ManagerList all_managers_; + // Managers that have not yet associated with a frame. + ManagerList pending_managers_; + // Managers that are currently active. + ManagerMap manager_map_; typedef std::set ObserverSet; ObserverSet observer_set_; @@ -2501,6 +2534,14 @@ class MultiQueryMultiFrameTestHandler : return manager_map_.GetMainHTML(); } + void OnLoadStart(CefRefPtr browser, + CefRefPtr frame, + TransitionType transition_type) override { + AssertMainBrowser(browser); + if (!frame->IsMain()) + manager_map_.OnLoadStart(browser, frame); + } + void OnNotify(CefRefPtr browser, CefRefPtr frame, const std::string& message) override { @@ -2637,6 +2678,12 @@ class MultiQueryMultiLoadTestHandler : manager_map_.AddObserver(this); } + void OnLoadStart(CefRefPtr browser, + CefRefPtr frame, + TransitionType transition_type) override { + manager_map_.OnLoadStart(browser, frame); + } + void OnNotify(CefRefPtr browser, CefRefPtr frame, const std::string& message) override { @@ -2797,7 +2844,7 @@ class MultiQueryMultiNavigateTestHandler : public MultiQueryMultiLoadTestHandler } void OnManualQueriesCompleted(MultiQueryManager* manager) override { - const std::string& url = manager_map_.GetURLForManager(manager); + const std::string& url = manager->label(); if (url == url1_) // 2. Load the 2nd url. GetBrowser()->GetMainFrame()->LoadURL(url2_); else if (url == url2_) // 3. Load the 3rd url. diff --git a/tests/ceftests/navigation_unittest.cc b/tests/ceftests/navigation_unittest.cc index 21b8eb014..a9a2ebe08 100644 --- a/tests/ceftests/navigation_unittest.cc +++ b/tests/ceftests/navigation_unittest.cc @@ -2325,6 +2325,23 @@ class PopupNavTestHandler : public TestHandler { } } + void OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) override { + if (failedUrl == kPopupNavPageUrl) { + EXPECT_FALSE(got_load_error_); + got_load_error_.yes(); + } else if (failedUrl == kPopupNavPopupUrl) { + EXPECT_FALSE(got_popup_load_error_); + got_popup_load_error_.yes(); + } else if (failedUrl == kPopupNavPopupUrl2) { + EXPECT_FALSE(got_popup_load_error2_); + got_popup_load_error2_.yes(); + } + } + void OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode) override { @@ -2370,22 +2387,29 @@ class PopupNavTestHandler : public TestHandler { private: void DestroyTest() override { EXPECT_TRUE(got_load_start_); + EXPECT_FALSE(got_load_error_); EXPECT_TRUE(got_load_end_); EXPECT_TRUE(got_on_before_popup_); if (mode_ == ALLOW) { EXPECT_TRUE(got_popup_load_start_); + EXPECT_FALSE(got_popup_load_error_); EXPECT_TRUE(got_popup_load_end_); EXPECT_FALSE(got_popup_load_start2_); + EXPECT_FALSE(got_popup_load_error2_); EXPECT_FALSE(got_popup_load_end2_); } else if (mode_ == DENY) { EXPECT_FALSE(got_popup_load_start_); + EXPECT_FALSE(got_popup_load_error_); EXPECT_FALSE(got_popup_load_end_); EXPECT_FALSE(got_popup_load_start2_); + EXPECT_FALSE(got_popup_load_error2_); EXPECT_FALSE(got_popup_load_end2_); } else if (mode_ == NAVIGATE_AFTER_CREATION) { EXPECT_FALSE(got_popup_load_start_); + EXPECT_TRUE(got_popup_load_error_); EXPECT_FALSE(got_popup_load_end_); EXPECT_TRUE(got_popup_load_start2_); + EXPECT_FALSE(got_popup_load_error2_); EXPECT_TRUE(got_popup_load_end2_); } @@ -2396,10 +2420,13 @@ class PopupNavTestHandler : public TestHandler { TrackCallback got_on_before_popup_; TrackCallback got_load_start_; + TrackCallback got_load_error_; TrackCallback got_load_end_; TrackCallback got_popup_load_start_; + TrackCallback got_popup_load_error_; TrackCallback got_popup_load_end_; TrackCallback got_popup_load_start2_; + TrackCallback got_popup_load_error2_; TrackCallback got_popup_load_end2_; IMPLEMENT_REFCOUNTING(PopupNavTestHandler); @@ -2865,6 +2892,671 @@ TEST(NavigationTest, BrowseDeny) { ReleaseAndWaitForDestructor(handler); } +namespace { + +const char kSameNavPageUrl[] = "http://tests-samenav/nav.html"; + +// Browser side. +class SameNavTestHandler : public TestHandler { + public: + SameNavTestHandler() + : destroyed_(false), + step_(0) {} + + void RunTest() override { + AddResource(kSameNavPageUrl, "Test", "text/html"); + + // Create the browser. + expected_url_ = kSameNavPageUrl; + CreateBrowser(kSameNavPageUrl); + + // Time out the test after a reasonable period of time. + SetTestTimeout(); + } + + bool OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_redirect) override { + const std::string& url = request->GetURL(); + EXPECT_STREQ(expected_url_.c_str(), url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_before_browse_.yes(); + + return false; + } + + void OnLoadStart(CefRefPtr browser, + CefRefPtr frame, + TransitionType transition_type) override { + const std::string& url = frame->GetURL(); + EXPECT_STREQ(expected_url_.c_str(), url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_load_start_.yes(); + } + + void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) override { + const std::string& url = frame->GetURL(); + EXPECT_STREQ(expected_url_.c_str(), url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_load_end_.yes(); + ContinueTestIfDone(); + } + + void OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) override { + got_load_error_.yes(); + } + + void OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) override { + const std::string& url = browser->GetMainFrame()->GetURL(); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + + if (isLoading) { + // Verify the previous URL. + if (step_ == 0) + EXPECT_TRUE(url.empty()); + else + EXPECT_STREQ(kSameNavPageUrl, url.c_str()); + + got_loading_state_changed_start_.yes(); + } else { + EXPECT_STREQ(expected_url_.c_str(), url.c_str()); + + got_loading_state_changed_end_.yes(); + ContinueTestIfDone(); + } + } + + private: + void ContinueTestIfDone() { + if (step_ == 0) { + // First navigation should trigger all callbacks except OnLoadError. + if (got_loading_state_changed_end_ && got_load_end_) { + EXPECT_TRUE(got_before_browse_); + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_load_start_); + EXPECT_FALSE(got_load_error_); + + got_before_browse_.reset(); + got_loading_state_changed_start_.reset(); + got_loading_state_changed_end_.reset(); + got_load_start_.reset(); + got_load_end_.reset(); + + step_++; + expected_url_ = kSameNavPageUrl + std::string("#fragment"); + GetBrowser()->GetMainFrame()->LoadURL(expected_url_); + } + } else if (step_ == 1) { + step_++; + DestroyTest(); + } else { + EXPECT_TRUE(false); // Not reached. + } + } + + void DestroyTest() override { + if (destroyed_) + return; + destroyed_ = true; + + EXPECT_EQ(2, step_); + + // Second (fragment) navigation should only trigger OnLoadingStateChange. + EXPECT_FALSE(got_before_browse_); + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_loading_state_changed_end_); + EXPECT_FALSE(got_load_start_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_load_error_); + + TestHandler::DestroyTest(); + } + + bool destroyed_; + int step_; + std::string expected_url_; + + TrackCallback got_before_browse_; + TrackCallback got_load_start_; + TrackCallback got_load_end_; + TrackCallback got_load_error_; + TrackCallback got_loading_state_changed_start_; + TrackCallback got_loading_state_changed_end_; + + IMPLEMENT_REFCOUNTING(SameNavTestHandler); +}; + +} // namespace + +// Test that same page navigation does not call OnLoadStart/OnLoadEnd. +TEST(NavigationTest, SamePage) { + CefRefPtr handler = new SameNavTestHandler(); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +namespace { + +const char kCancelPageUrl[] = "http://tests-cancelnav/nav.html"; + +// A scheme handler that never starts sending data. +class UnstartedSchemeHandler : public CefResourceHandler { + public: + UnstartedSchemeHandler() {} + + bool ProcessRequest(CefRefPtr request, + CefRefPtr callback) override { + callback->Continue(); + return true; + } + + void GetResponseHeaders(CefRefPtr response, + int64& response_length, + CefString& redirectUrl) override { + response->SetStatus(200); + response->SetMimeType("text/html"); + response_length = 100; + } + + void Cancel() override { + callback_ = nullptr; + } + + bool ReadResponse(void* data_out, + int bytes_to_read, + int& bytes_read, + CefRefPtr callback) override { + callback_ = callback; + + // Pretend that we'll provide the data later. + bytes_read = 0; + return true; + } + + protected: + CefRefPtr callback_; + + IMPLEMENT_REFCOUNTING(UnstartedSchemeHandler); +}; + +// Browser side. +class CancelBeforeNavTestHandler : public TestHandler { + public: + CancelBeforeNavTestHandler() + : destroyed_(false) {} + + void RunTest() override { + // Create the browser. + CreateBrowser(kCancelPageUrl); + + // Time out the test after a reasonable period of time. + SetTestTimeout(); + } + + bool OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_redirect) override { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_FALSE(got_before_browse_); + EXPECT_FALSE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_FALSE(got_cancel_load_); + EXPECT_FALSE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + const std::string& url = request->GetURL(); + EXPECT_STREQ(kCancelPageUrl, url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_before_browse_.yes(); + + return false; + } + + CefRefPtr GetResourceHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) override { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_FALSE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_FALSE(got_cancel_load_); + EXPECT_FALSE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + const std::string& url = request->GetURL(); + EXPECT_STREQ(kCancelPageUrl, url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_get_resource_handler_.yes(); + + CefPostDelayedTask(TID_UI, + base::Bind(&CancelBeforeNavTestHandler::CancelLoad, this), 100); + + return new UnstartedSchemeHandler(); + } + + void OnLoadStart(CefRefPtr browser, + CefRefPtr frame, + TransitionType transition_type) override { + EXPECT_TRUE(false); // Not reached. + got_load_start_.yes(); + } + + void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) override { + EXPECT_TRUE(false); // Not reached. + got_load_end_.yes(); + } + + void OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) override { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_TRUE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_TRUE(got_cancel_load_); + EXPECT_FALSE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + const std::string& url = failedUrl; + EXPECT_STREQ(kCancelPageUrl, url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_load_error_.yes(); + } + + void OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) override { + const std::string& url = browser->GetMainFrame()->GetURL(); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(url.empty()); + + if (isLoading) { + EXPECT_FALSE(got_loading_state_changed_start_); + EXPECT_FALSE(got_before_browse_); + EXPECT_FALSE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_FALSE(got_cancel_load_); + EXPECT_FALSE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + got_loading_state_changed_start_.yes(); + } else { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_TRUE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_TRUE(got_cancel_load_); + EXPECT_TRUE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + got_loading_state_changed_end_.yes(); + + DestroyTest(); + } + } + + private: + void CancelLoad() { + got_cancel_load_.yes(); + GetBrowser()->StopLoad(); + } + + void DestroyTest() override { + if (destroyed_) + return; + destroyed_ = true; + + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_TRUE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_TRUE(got_cancel_load_); + EXPECT_TRUE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_TRUE(got_loading_state_changed_end_); + + TestHandler::DestroyTest(); + } + + bool destroyed_; + + TrackCallback got_loading_state_changed_start_; + TrackCallback got_before_browse_; + TrackCallback got_get_resource_handler_; + TrackCallback got_load_start_; + TrackCallback got_cancel_load_; + TrackCallback got_load_error_; + TrackCallback got_load_end_; + TrackCallback got_loading_state_changed_end_; + + IMPLEMENT_REFCOUNTING(CancelBeforeNavTestHandler); +}; + +} // namespace + +// Test that navigation canceled before commit does not call +// OnLoadStart/OnLoadEnd. +TEST(NavigationTest, CancelBeforeCommit) { + CefRefPtr handler = + new CancelBeforeNavTestHandler(); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + + +namespace { + +// A scheme handler that stalls after writing some data. +class StalledSchemeHandler : public CefResourceHandler { + public: + StalledSchemeHandler() : offset_(0), write_size_(0) {} + + bool ProcessRequest(CefRefPtr request, + CefRefPtr callback) override { + callback->Continue(); + return true; + } + + void GetResponseHeaders(CefRefPtr response, + int64& response_length, + CefString& redirectUrl) override { + response->SetStatus(200); + response->SetMimeType("text/html"); + content_ = "Test"; + // Write this number of bytes and then stall. + write_size_ = content_.size() / 2U; + response_length = content_.size(); + } + + void Cancel() override { + callback_ = nullptr; + } + + bool ReadResponse(void* data_out, + int bytes_to_read, + int& bytes_read, + CefRefPtr callback) override { + size_t size = content_.size(); + if (offset_ >= write_size_) { + // Now stall. + bytes_read = 0; + callback_ = callback; + return true; + } + + if (offset_ < size) { + // Write up to |write_size_| bytes. + int transfer_size = + std::min(bytes_to_read, std::min(static_cast(write_size_), + static_cast(size - offset_))); + memcpy(data_out, content_.c_str() + offset_, transfer_size); + offset_ += transfer_size; + + bytes_read = transfer_size; + return true; + } + + return false; + } + + protected: + std::string content_; + size_t offset_; + size_t write_size_; + CefRefPtr callback_; + + IMPLEMENT_REFCOUNTING(StalledSchemeHandler); +}; + +// Browser side. +class CancelAfterNavTestHandler : public TestHandler { + public: + CancelAfterNavTestHandler() + : destroyed_(false) {} + + void RunTest() override { + // Create the browser. + CreateBrowser(kCancelPageUrl); + + // Time out the test after a reasonable period of time. + SetTestTimeout(); + } + + bool OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_redirect) override { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_FALSE(got_before_browse_); + EXPECT_FALSE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_FALSE(got_cancel_load_); + EXPECT_FALSE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + const std::string& url = request->GetURL(); + EXPECT_STREQ(kCancelPageUrl, url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_before_browse_.yes(); + + return false; + } + + CefRefPtr GetResourceHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) override { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_FALSE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_FALSE(got_cancel_load_); + EXPECT_FALSE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + const std::string& url = request->GetURL(); + EXPECT_STREQ(kCancelPageUrl, url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_get_resource_handler_.yes(); + + CefPostDelayedTask(TID_UI, + base::Bind(&CancelAfterNavTestHandler::CancelLoad, this), 100); + + return new StalledSchemeHandler(); + } + + void OnLoadStart(CefRefPtr browser, + CefRefPtr frame, + TransitionType transition_type) override { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_TRUE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_FALSE(got_cancel_load_); + EXPECT_FALSE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + const std::string& url = frame->GetURL(); + EXPECT_STREQ(kCancelPageUrl, url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_load_start_.yes(); + } + + void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) override { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_TRUE(got_get_resource_handler_); + EXPECT_TRUE(got_load_start_); + EXPECT_TRUE(got_cancel_load_); + EXPECT_TRUE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + const std::string& url = frame->GetURL(); + EXPECT_STREQ(kCancelPageUrl, url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_load_end_.yes(); + DestroyTestIfDone(); + } + + void OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) override { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_TRUE(got_get_resource_handler_); + EXPECT_TRUE(got_load_start_); + EXPECT_TRUE(got_cancel_load_); + EXPECT_FALSE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + const std::string& url = failedUrl; + EXPECT_STREQ(kCancelPageUrl, url.c_str()); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + EXPECT_TRUE(frame->IsMain()); + + got_load_error_.yes(); + } + + void OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) override { + const std::string& url = browser->GetMainFrame()->GetURL(); + EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + + if (isLoading) { + EXPECT_FALSE(got_loading_state_changed_start_); + EXPECT_FALSE(got_before_browse_); + EXPECT_FALSE(got_get_resource_handler_); + EXPECT_FALSE(got_load_start_); + EXPECT_FALSE(got_cancel_load_); + EXPECT_FALSE(got_load_error_); + EXPECT_FALSE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + EXPECT_TRUE(url.empty()); + + got_loading_state_changed_start_.yes(); + } else { + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_TRUE(got_get_resource_handler_); + EXPECT_TRUE(got_load_start_); + EXPECT_TRUE(got_cancel_load_); + EXPECT_TRUE(got_load_error_); + EXPECT_TRUE(got_load_end_); + EXPECT_FALSE(got_loading_state_changed_end_); + + EXPECT_STREQ(kCancelPageUrl, url.c_str()); + + got_loading_state_changed_end_.yes(); + DestroyTestIfDone(); + } + } + + private: + void CancelLoad() { + got_cancel_load_.yes(); + GetBrowser()->StopLoad(); + } + + void DestroyTestIfDone() { + if (got_loading_state_changed_end_ && got_load_end_) + DestroyTest(); + } + + void DestroyTest() override { + if (destroyed_) + return; + destroyed_ = true; + + EXPECT_TRUE(got_loading_state_changed_start_); + EXPECT_TRUE(got_before_browse_); + EXPECT_TRUE(got_get_resource_handler_); + EXPECT_TRUE(got_load_start_); + EXPECT_TRUE(got_cancel_load_); + EXPECT_TRUE(got_load_error_); + EXPECT_TRUE(got_load_end_); + EXPECT_TRUE(got_loading_state_changed_end_); + + TestHandler::DestroyTest(); + } + + bool destroyed_; + + TrackCallback got_loading_state_changed_start_; + TrackCallback got_before_browse_; + TrackCallback got_get_resource_handler_; + TrackCallback got_load_start_; + TrackCallback got_cancel_load_; + TrackCallback got_load_error_; + TrackCallback got_load_end_; + TrackCallback got_loading_state_changed_end_; + + IMPLEMENT_REFCOUNTING(CancelAfterNavTestHandler); +}; + +} // namespace + +// Test that navigation canceled after commit calls everything. +TEST(NavigationTest, CancelAfterCommit) { + CefRefPtr handler = + new CancelAfterNavTestHandler(); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + // Entry point for creating navigation browser test objects. // Called from client_app_delegates.cc.