// Copyright (c) 2012 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 "chrome/browser/ui/omnibox/omnibox_navigation_observer.h" #include "chrome/browser/autocomplete/shortcuts_backend.h" #include "chrome/browser/autocomplete/shortcuts_backend_factory.h" #include "chrome/browser/infobars/infobar_service.h" #include "chrome/browser/intranet_redirect_detector.h" #include "chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "net/base/load_flags.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_request.h" // Helpers -------------------------------------------------------------------- namespace { // HTTP 2xx, 401, and 407 all indicate that the target address exists. bool ResponseCodeIndicatesSuccess(int response_code) { return ((response_code / 100) == 2) || (response_code == 401) || (response_code == 407); } // Returns true if |final_url| doesn't represent an ISP hijack of // |original_url|, based on the IntranetRedirectDetector's RedirectOrigin(). bool IsValidNavigation(const GURL& original_url, const GURL& final_url) { const GURL& redirect_url(IntranetRedirectDetector::RedirectOrigin()); return !redirect_url.is_valid() || net::registry_controlled_domains::SameDomainOrHost( original_url, final_url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES) || !net::registry_controlled_domains::SameDomainOrHost( final_url, redirect_url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); } } // namespace // OmniboxNavigationObserver -------------------------------------------------- OmniboxNavigationObserver::OmniboxNavigationObserver( Profile* profile, const base::string16& text, const AutocompleteMatch& match, const AutocompleteMatch& alternate_nav_match) : text_(text), match_(match), alternate_nav_match_(alternate_nav_match), shortcuts_backend_(ShortcutsBackendFactory::GetForProfile(profile)), load_state_(LOAD_NOT_SEEN), fetch_state_(FETCH_NOT_COMPLETE) { if (alternate_nav_match_.destination_url.is_valid()) { fetcher_ = net::URLFetcher::Create(alternate_nav_match_.destination_url, net::URLFetcher::HEAD, this); fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES); fetcher_->SetStopOnRedirect(true); } // We need to start by listening to AllSources, since we don't know which tab // the navigation might occur in. registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, content::NotificationService::AllSources()); } OmniboxNavigationObserver::~OmniboxNavigationObserver() { } void OmniboxNavigationObserver::OnSuccessfulNavigation() { if (shortcuts_backend_.get()) shortcuts_backend_->AddOrUpdateShortcut(text_, match_); } void OmniboxNavigationObserver::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_PENDING, type); // It's possible for an attempted omnibox navigation to cause the extensions // system to synchronously navigate an extension background page. Not only is // this navigation not the one we want to observe, the associated WebContents // is invisible and has no InfoBarService, so trying to show an infobar in it // later will crash. Just ignore this navigation and keep listening. content::NavigationController* controller = content::Source(source).ptr(); content::WebContents* web_contents = controller->GetWebContents(); if (!InfoBarService::FromWebContents(web_contents)) return; // Ignore navgiations to the wrong URL. // This shouldn't actually happen, but right now it's possible because the // prerenderer doesn't properly notify us when it swaps in a prerendered page. // Plus, the swap-in can trigger instant to kick off a new background // prerender, which we _do_ get notified about. Once crbug.com/247848 is // fixed, this conditional should be able to be replaced with a [D]CHECK; // until then we ignore the incorrect navigation (and will be torn down // without having received the correct notification). if (match_.destination_url != content::Details(details)->GetVirtualURL()) return; registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING, content::NotificationService::AllSources()); if (fetcher_) { fetcher_->SetRequestContext( controller->GetBrowserContext()->GetRequestContext()); } WebContentsObserver::Observe(web_contents); // DidStartNavigationToPendingEntry() will be called for this load as well. } void OmniboxNavigationObserver::DidStartNavigationToPendingEntry( const GURL& url, content::NavigationController::ReloadType reload_type) { if (load_state_ == LOAD_NOT_SEEN) { load_state_ = LOAD_PENDING; if (fetcher_) fetcher_->Start(); } else { delete this; } } void OmniboxNavigationObserver::DidFailProvisionalLoad( content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, const base::string16& error_description) { if ((load_state_ != LOAD_COMMITTED) && !render_frame_host->GetParent()) delete this; } void OmniboxNavigationObserver::NavigationEntryCommitted( const content::LoadCommittedDetails& load_details) { load_state_ = LOAD_COMMITTED; if (ResponseCodeIndicatesSuccess(load_details.http_status_code) && IsValidNavigation(match_.destination_url, load_details.entry->GetVirtualURL())) OnSuccessfulNavigation(); if (!fetcher_ || (fetch_state_ != FETCH_NOT_COMPLETE)) OnAllLoadingFinished(); // deletes |this|! } void OmniboxNavigationObserver::WebContentsDestroyed() { delete this; } void OmniboxNavigationObserver::OnURLFetchComplete( const net::URLFetcher* source) { DCHECK_EQ(fetcher_.get(), source); const net::URLRequestStatus& status = source->GetStatus(); int response_code = source->GetResponseCode(); fetch_state_ = (status.is_success() && ResponseCodeIndicatesSuccess(response_code)) || ((status.status() == net::URLRequestStatus::CANCELED) && ((response_code / 100) == 3) && IsValidNavigation(alternate_nav_match_.destination_url, source->GetURL())) ? FETCH_SUCCEEDED : FETCH_FAILED; if (load_state_ == LOAD_COMMITTED) OnAllLoadingFinished(); // deletes |this|! } void OmniboxNavigationObserver::OnAllLoadingFinished() { if (fetch_state_ == FETCH_SUCCEEDED) { AlternateNavInfoBarDelegate::Create( web_contents(), text_, alternate_nav_match_, match_.destination_url); } delete this; }