blob: fcb0f5a4690ba33e76e4b11bea258f21bf806192 [file] [log] [blame]
// Copyright (c) 2011 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/alternate_nav_url_fetcher.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/infobars/infobar_tab_helper.h"
#include "chrome/browser/intranet_redirect_detector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/link_infobar_delegate.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/chrome_notification_types.h"
#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/common/url_fetcher.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources_standard.h"
#include "net/base/registry_controlled_domain.h"
#include "net/url_request/url_request.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
// AlternateNavInfoBarDelegate ------------------------------------------------
class AlternateNavInfoBarDelegate : public LinkInfoBarDelegate {
public:
AlternateNavInfoBarDelegate(InfoBarTabHelper* owner,
const GURL& alternate_nav_url);
virtual ~AlternateNavInfoBarDelegate();
private:
// LinkInfoBarDelegate
virtual gfx::Image* GetIcon() const OVERRIDE;
virtual Type GetInfoBarType() const OVERRIDE;
virtual string16 GetMessageTextWithOffset(size_t* link_offset) const OVERRIDE;
virtual string16 GetLinkText() const OVERRIDE;
virtual bool LinkClicked(WindowOpenDisposition disposition) OVERRIDE;
GURL alternate_nav_url_;
DISALLOW_COPY_AND_ASSIGN(AlternateNavInfoBarDelegate);
};
AlternateNavInfoBarDelegate::AlternateNavInfoBarDelegate(
InfoBarTabHelper* owner,
const GURL& alternate_nav_url)
: LinkInfoBarDelegate(owner),
alternate_nav_url_(alternate_nav_url) {
}
AlternateNavInfoBarDelegate::~AlternateNavInfoBarDelegate() {
}
gfx::Image* AlternateNavInfoBarDelegate::GetIcon() const {
return &ResourceBundle::GetSharedInstance().GetNativeImageNamed(
IDR_INFOBAR_ALT_NAV_URL);
}
InfoBarDelegate::Type AlternateNavInfoBarDelegate::GetInfoBarType() const {
return PAGE_ACTION_TYPE;
}
string16 AlternateNavInfoBarDelegate::GetMessageTextWithOffset(
size_t* link_offset) const {
const string16 label = l10n_util::GetStringFUTF16(
IDS_ALTERNATE_NAV_URL_VIEW_LABEL, string16(), link_offset);
return label;
}
string16 AlternateNavInfoBarDelegate::GetLinkText() const {
return UTF8ToUTF16(alternate_nav_url_.spec());
}
bool AlternateNavInfoBarDelegate::LinkClicked(
WindowOpenDisposition disposition) {
owner()->tab_contents()->OpenURL(
alternate_nav_url_, GURL(), disposition,
// Pretend the user typed this URL, so that navigating to
// it will be the default action when it's typed again in
// the future.
content::PAGE_TRANSITION_TYPED);
// We should always close, even if the navigation did not occur within this
// TabContents.
return true;
}
// AlternateNavURLFetcher -----------------------------------------------------
AlternateNavURLFetcher::AlternateNavURLFetcher(
const GURL& alternate_nav_url)
: alternate_nav_url_(alternate_nav_url),
controller_(NULL),
state_(NOT_STARTED),
navigated_to_entry_(false) {
registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
content::NotificationService::AllSources());
registrar_.Add(this, chrome::NOTIFICATION_INSTANT_COMMITTED,
content::NotificationService::AllSources());
}
AlternateNavURLFetcher::~AlternateNavURLFetcher() {
}
void AlternateNavURLFetcher::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case content::NOTIFICATION_NAV_ENTRY_PENDING: {
// If we've already received a notification for the same controller, we
// should delete ourselves as that indicates that the page is being
// re-loaded so this instance is now stale.
NavigationController* controller =
content::Source<NavigationController>(source).ptr();
if (controller_ == controller) {
delete this;
} else if (!controller_) {
// Start listening for the commit notification.
DCHECK(controller->pending_entry());
registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::Source<NavigationController>(controller));
StartFetch(controller);
}
break;
}
case chrome::NOTIFICATION_INSTANT_COMMITTED: {
// See above.
NavigationController* controller =
&content::Source<TabContentsWrapper>(source)->controller();
if (controller_ == controller) {
delete this;
} else if (!controller_) {
navigated_to_entry_ = true;
StartFetch(controller);
}
break;
}
case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
// The page was navigated, we can show the infobar now if necessary.
registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::Source<NavigationController>(controller_));
navigated_to_entry_ = true;
ShowInfobarIfPossible();
// WARNING: |this| may be deleted!
break;
case content::NOTIFICATION_TAB_CLOSED:
// We have been closed. In order to prevent the URLFetcher from trying to
// access the controller that will be invalid, we delete ourselves.
// This deletes the URLFetcher and insures its callback won't be called.
delete this;
break;
default:
NOTREACHED();
}
}
void AlternateNavURLFetcher::OnURLFetchComplete(
const content::URLFetcher* source) {
DCHECK_EQ(fetcher_.get(), source);
SetStatusFromURLFetch(
source->GetURL(), source->GetStatus(), source->GetResponseCode());
ShowInfobarIfPossible();
// WARNING: |this| may be deleted!
}
void AlternateNavURLFetcher::StartFetch(NavigationController* controller) {
controller_ = controller;
registrar_.Add(this, content::NOTIFICATION_TAB_CLOSED,
content::Source<NavigationController>(controller_));
DCHECK_EQ(NOT_STARTED, state_);
state_ = IN_PROGRESS;
fetcher_.reset(content::URLFetcher::Create(
GURL(alternate_nav_url_), content::URLFetcher::HEAD, this));
fetcher_->SetRequestContext(
controller_->browser_context()->GetRequestContext());
fetcher_->Start();
}
void AlternateNavURLFetcher::SetStatusFromURLFetch(
const GURL& url,
const net::URLRequestStatus& status,
int response_code) {
if (!status.is_success() ||
// HTTP 2xx, 401, and 407 all indicate that the target address exists.
(((response_code / 100) != 2) &&
(response_code != 401) && (response_code != 407)) ||
// Fail if we're redirected to a common location.
// This happens for ISPs/DNS providers/etc. who return
// provider-controlled pages to arbitrary user navigation attempts.
// Because this can result in infobars on large fractions of user
// searches, we don't show automatic infobars for these. Note that users
// can still choose to explicitly navigate to or search for pages in
// these domains, and can still get infobars for cases that wind up on
// other domains (e.g. legit intranet sites), we're just trying to avoid
// erroneously harassing the user with our own UI prompts.
net::RegistryControlledDomainService::SameDomainOrHost(url,
IntranetRedirectDetector::RedirectOrigin())) {
state_ = FAILED;
return;
}
state_ = SUCCEEDED;
}
void AlternateNavURLFetcher::ShowInfobarIfPossible() {
if (!navigated_to_entry_ || state_ != SUCCEEDED) {
if (state_ == FAILED)
delete this;
return;
}
InfoBarTabHelper* infobar_helper =
TabContentsWrapper::GetCurrentWrapperForContents(
controller_->tab_contents())->infobar_tab_helper();
infobar_helper->AddInfoBar(
new AlternateNavInfoBarDelegate(infobar_helper, alternate_nav_url_));
delete this;
}