blob: bc9aa4baa276dfcd7f7bb7cbe2bd50e2c5771141 [file] [log] [blame]
// Copyright 2018 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.
#import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "components/strings/grit/components_strings.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#include "ios/chrome/browser/ntp/features.h"
#include "ios/chrome/browser/ntp/new_tab_page_tab_helper_delegate.h"
#import "ios/web/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h"
#import "ios/web/public/web_state/navigation_context.h"
#import "ios/web/public/web_state/web_state.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Internally the NTP URL is about://newtab/. However, with
// |url::kAboutScheme|, there's no host value, only a path. Use this value for
// matching the NTP.
const char kAboutNewTabPath[] = "//newtab/";
// Maximum number of seconds for |ignore_load_requests_| to be set to YES.
static const size_t kMaximumIgnoreLoadRequestsTime = 10;
} // namespace
// static
void NewTabPageTabHelper::CreateForWebState(
web::WebState* web_state,
id<NewTabPageTabHelperDelegate> delegate) {
DCHECK(web_state);
if (!FromWebState(web_state)) {
web_state->SetUserData(
UserDataKey(),
base::WrapUnique(new NewTabPageTabHelper(web_state, delegate)));
}
}
NewTabPageTabHelper::~NewTabPageTabHelper() = default;
NewTabPageTabHelper::NewTabPageTabHelper(
web::WebState* web_state,
id<NewTabPageTabHelperDelegate> delegate)
: delegate_(delegate), web_state_(web_state) {
DCHECK(delegate);
web_state->AddObserver(this);
active_ = IsNTPURL(web_state->GetVisibleURL());
if (active_) {
UpdateItem(web_state_->GetNavigationManager()->GetPendingItem());
[delegate_ newTabPageHelperDidChangeVisibility:this forWebState:web_state_];
// If about://newtab is currently loading but has not yet committed, block
// loads until it does commit.
if (!IsNTPURL(web_state->GetLastCommittedURL())) {
EnableIgnoreLoadRequests();
}
}
}
bool NewTabPageTabHelper::IsActive() const {
return active_;
}
void NewTabPageTabHelper::Deactivate() {
SetActive(false);
}
bool NewTabPageTabHelper::IgnoreLoadRequests() const {
DCHECK(active_);
return ignore_load_requests_;
}
void NewTabPageTabHelper::EnableIgnoreLoadRequests() {
if (!base::FeatureList::IsEnabled(kBlockNewTabPagePendingLoad))
return;
ignore_load_requests_ = YES;
// |ignore_load_requests_timer_| is deleted when the tab helper is deleted, so
// it's safe to use Unretained here.
ignore_load_requests_timer_.reset(new base::OneShotTimer());
ignore_load_requests_timer_->Start(
FROM_HERE, base::TimeDelta::FromSeconds(kMaximumIgnoreLoadRequestsTime),
base::BindOnce(&NewTabPageTabHelper::DisableIgnoreLoadRequests,
base::Unretained(this)));
}
void NewTabPageTabHelper::DisableIgnoreLoadRequests() {
if (ignore_load_requests_timer_) {
ignore_load_requests_timer_->Stop();
ignore_load_requests_timer_.reset();
}
ignore_load_requests_ = NO;
}
#pragma mark - WebStateObserver
void NewTabPageTabHelper::WebStateDestroyed(web::WebState* web_state) {
web_state->RemoveObserver(this);
SetActive(false);
DisableIgnoreLoadRequests();
}
void NewTabPageTabHelper::DidStartNavigation(
web::WebState* web_state,
web::NavigationContext* navigation_context) {
if (IsNTPURL(navigation_context->GetUrl())) {
UpdateItem(web_state_->GetNavigationManager()->GetPendingItem());
} else {
SetActive(false);
}
}
void NewTabPageTabHelper::DidFinishNavigation(
web::WebState* web_state,
web::NavigationContext* navigation_context) {
web::NavigationItem* item =
web_state_->GetNavigationManager()->GetLastCommittedItem();
if (navigation_context->IsSameDocument() || !item) {
return;
}
UpdateItem(web_state_->GetNavigationManager()->GetLastCommittedItem());
DisableIgnoreLoadRequests();
SetActive(IsNTPURL(web_state->GetLastCommittedURL()));
}
#pragma mark - Private
void NewTabPageTabHelper::SetActive(bool active) {
bool was_active = active_;
active_ = active;
// Tell |delegate_| to show or hide the NTP, if necessary.
if (active_ != was_active) {
[delegate_ newTabPageHelperDidChangeVisibility:this forWebState:web_state_];
}
}
void NewTabPageTabHelper::UpdateItem(web::NavigationItem* item) {
if (item && item->GetURL() == GURL(kChromeUIAboutNewTabURL)) {
item->SetVirtualURL(GURL(kChromeUINewTabURL));
item->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
}
}
bool NewTabPageTabHelper::IsNTPURL(const GURL& url) {
// |url| can be chrome://newtab/ or about://newtab/ depending on where |url|
// comes from (the VisibleURL chrome:// from a navigation item or the actual
// webView url about://). If the url is about://newtab/, there is no origin
// to match, so instead check the scheme and the path.
return url.GetOrigin() == kChromeUINewTabURL ||
(url.SchemeIs(url::kAboutScheme) && url.path() == kAboutNewTabPath);
}
WEB_STATE_USER_DATA_KEY_IMPL(NewTabPageTabHelper)