blob: 1cd9704cfb83c833810e4ea2a4f4f0f72aaa7c08 [file] [log] [blame]
// Copyright 2019 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/url_loading/url_loading_service.h"
#include "base/strings/string_number_conversions.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/prerender/prerender_service.h"
#import "ios/chrome/browser/prerender/prerender_service_factory.h"
#import "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
#import "ios/chrome/browser/ui/ntp/ntp_util.h"
#import "ios/chrome/browser/url_loading/app_url_loading_service.h"
#import "ios/chrome/browser/url_loading/url_loading_notifier.h"
#import "ios/chrome/browser/url_loading/url_loading_params.h"
#import "ios/chrome/browser/url_loading/url_loading_service_factory.h"
#import "ios/chrome/browser/url_loading/url_loading_util.h"
#import "ios/chrome/browser/web/load_timing_tab_helper.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#include "net/base/url_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Helper method for inducing intentional freezes and crashes, in a separate
// function so it will show up in stack traces.
// If a delay parameter is present, the main thread will be frozen for that
// number of seconds.
// If a crash parameter is "true" (which is the default value), the browser will
// crash after this delay. Any other value will not trigger a crash.
void InduceBrowserCrash(const GURL& url) {
int delay = 0;
std::string delay_string;
if (net::GetValueForKeyInQuery(url, "delay", &delay_string)) {
base::StringToInt(delay_string, &delay);
}
if (delay > 0) {
sleep(delay);
}
bool crash = true;
std::string crash_string;
if (net::GetValueForKeyInQuery(url, "crash", &crash_string)) {
crash = crash_string == "" || crash_string == "true";
}
if (crash) {
// Induce an intentional crash in the browser process.
CHECK(false);
// Call another function, so that the above CHECK can't be tail-call
// optimized. This ensures that this method's name will show up in the stack
// for easier identification.
CHECK(true);
}
}
}
UrlLoadingService::UrlLoadingService(UrlLoadingNotifier* notifier)
: notifier_(notifier) {}
void UrlLoadingService::SetAppService(AppUrlLoadingService* app_service) {
app_service_ = app_service;
}
void UrlLoadingService::SetDelegate(id<URLLoadingServiceDelegate> delegate) {
delegate_ = delegate;
}
void UrlLoadingService::SetBrowser(Browser* browser) {
browser_ = browser;
}
void UrlLoadingService::Load(const UrlLoadParams& params) {
// Apply any override load strategy and dispatch.
switch (params.load_strategy) {
case UrlLoadStrategy::ALWAYS_NEW_FOREGROUND_TAB: {
UrlLoadParams fixed_params = params;
fixed_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
Dispatch(fixed_params);
break;
}
case UrlLoadStrategy::ALWAYS_IN_INCOGNITO: {
ios::ChromeBrowserState* browser_state = browser_->GetBrowserState();
if (params.disposition == WindowOpenDisposition::CURRENT_TAB &&
!browser_state->IsOffTheRecord()) {
UrlLoadingServiceFactory::GetForBrowserState(
browser_state->GetOffTheRecordChromeBrowserState())
->Load(params);
} else {
UrlLoadParams fixed_params = params;
fixed_params.in_incognito = YES;
Dispatch(fixed_params);
}
break;
}
case UrlLoadStrategy::NORMAL: {
Dispatch(params);
break;
}
}
}
void UrlLoadingService::Dispatch(const UrlLoadParams& params) {
// Then dispatch.
switch (params.disposition) {
case WindowOpenDisposition::NEW_BACKGROUND_TAB:
case WindowOpenDisposition::NEW_FOREGROUND_TAB:
LoadUrlInNewTab(params);
break;
case WindowOpenDisposition::CURRENT_TAB:
LoadUrlInCurrentTab(params);
break;
case WindowOpenDisposition::SWITCH_TO_TAB:
SwitchToTab(params);
break;
default:
DCHECK(false) << "Unhandled url loading disposition.";
break;
}
}
void UrlLoadingService::LoadUrlInCurrentTab(const UrlLoadParams& params) {
web::NavigationManager::WebLoadParams web_params = params.web_params;
ios::ChromeBrowserState* browser_state = browser_->GetBrowserState();
notifier_->TabWillLoadUrl(web_params.url, web_params.transition_type);
// NOTE: This check for the Crash Host URL is here to avoid the URL from
// ending up in the history causing the app to crash at every subsequent
// restart.
if (web_params.url.host() == kChromeUIBrowserCrashHost) {
InduceBrowserCrash(web_params.url);
// Under a debugger, the app can continue working even after the CHECK.
// Adding a return avoids adding the crash url to history.
notifier_->TabFailedToLoadUrl(web_params.url, web_params.transition_type);
return;
}
PrerenderService* prerenderService =
PrerenderServiceFactory::GetForBrowserState(browser_state);
// Some URLs are not allowed while in incognito. If we are in incognito and
// load a disallowed URL, instead create a new tab not in the incognito state.
// Also if there's no current web state, that means there is no current tab
// to open in, so this also redirects to a new tab.
WebStateList* web_state_list = browser_->GetWebStateList();
web::WebState* current_web_state = web_state_list->GetActiveWebState();
if (!current_web_state || (browser_state->IsOffTheRecord() &&
!IsURLAllowedInIncognito(web_params.url))) {
if (prerenderService) {
prerenderService->CancelPrerender();
}
notifier_->TabFailedToLoadUrl(web_params.url, web_params.transition_type);
if (!current_web_state) {
UrlLoadParams fixed_params = params;
fixed_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
fixed_params.in_incognito = browser_state->IsOffTheRecord();
Load(fixed_params);
} else {
UrlLoadParams fixed_params = UrlLoadParams::InNewTab(web_params);
fixed_params.in_incognito = NO;
fixed_params.append_to = kCurrentTab;
Load(fixed_params);
}
return;
}
// Ask the prerender service to load this URL if it can, and return if it does
// so.
id<SessionWindowRestoring> restorer =
(id<SessionWindowRestoring>)browser_->GetTabModel();
if (prerenderService && prerenderService->MaybeLoadPrerenderedURL(
web_params.url, web_params.transition_type,
web_state_list, restorer)) {
notifier_->TabDidPrerenderUrl(web_params.url, web_params.transition_type);
return;
}
BOOL typedOrGeneratedTransition =
PageTransitionCoreTypeIs(web_params.transition_type,
ui::PAGE_TRANSITION_TYPED) ||
PageTransitionCoreTypeIs(web_params.transition_type,
ui::PAGE_TRANSITION_GENERATED);
if (typedOrGeneratedTransition) {
LoadTimingTabHelper::FromWebState(current_web_state)->DidInitiatePageLoad();
}
// If this is a reload initiated from the omnibox.
// TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLoad is the
// same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
if (PageTransitionCoreTypeIs(web_params.transition_type,
ui::PAGE_TRANSITION_RELOAD)) {
current_web_state->GetNavigationManager()->Reload(
web::ReloadType::NORMAL, true /* check_for_repost */);
notifier_->TabDidReloadUrl(web_params.url, web_params.transition_type);
return;
}
current_web_state->GetNavigationManager()->LoadURLWithParams(web_params);
notifier_->TabDidLoadUrl(web_params.url, web_params.transition_type);
}
void UrlLoadingService::SwitchToTab(const UrlLoadParams& params) {
DCHECK(app_service_);
web::NavigationManager::WebLoadParams web_params = params.web_params;
WebStateList* web_state_list = browser_->GetWebStateList();
NSInteger new_web_state_index =
web_state_list->GetIndexOfInactiveWebStateWithURL(web_params.url);
bool old_tab_is_ntp_without_history =
IsNTPWithoutHistory(web_state_list->GetActiveWebState());
if (new_web_state_index == WebStateList::kInvalidIndex) {
// If the tab containing the URL has been closed.
if (old_tab_is_ntp_without_history) {
// It is NTP, just load the URL.
Load(UrlLoadParams::InCurrentTab(web_params));
} else {
// Load the URL in foreground.
ios::ChromeBrowserState* browser_state = browser_->GetBrowserState();
UrlLoadParams new_tab_params =
UrlLoadParams::InNewTab(web_params.url, web_params.virtual_url);
new_tab_params.web_params.referrer = web::Referrer();
new_tab_params.in_incognito = browser_state->IsOffTheRecord();
new_tab_params.append_to = kCurrentTab;
app_service_->LoadUrlInNewTab(new_tab_params);
}
return;
}
notifier_->WillSwitchToTabWithUrl(web_params.url, new_web_state_index);
NSInteger old_web_state_index = web_state_list->active_index();
web_state_list->ActivateWebStateAt(new_web_state_index);
// Close the tab if it is NTP with no back/forward history to avoid having
// empty tabs.
if (old_tab_is_ntp_without_history) {
web_state_list->CloseWebStateAt(old_web_state_index,
WebStateList::CLOSE_USER_ACTION);
}
notifier_->DidSwitchToTabWithUrl(web_params.url, new_web_state_index);
}
void UrlLoadingService::LoadUrlInNewTab(const UrlLoadParams& params) {
DCHECK(app_service_);
DCHECK(delegate_);
DCHECK(browser_);
ios::ChromeBrowserState* browser_state = browser_->GetBrowserState();
// Two UrlLoadingServices exist, normal and incognito. Handle two special
// cases that need to be sent up to the AppUrlLoadingService:
// 1) The URL needs to be loaded by the UrlLoadingService for the other mode.
// 2) The URL will be loaded in a foreground tab by this UrlLoadingService,
// but the UI associated with this UrlLoadingService is not currently visible,
// so the AppUrlLoadingService needs to switch modes before loading the URL.
if (params.in_incognito != browser_state->IsOffTheRecord() ||
(!params.in_background() &&
params.in_incognito !=
app_service_->GetCurrentBrowserState()->IsOffTheRecord())) {
// When sending a load request that switches modes, ensure the tab
// ends up appended to the end of the model, not just next to what is
// currently selected in the other mode. This is done with the |append_to|
// parameter.
UrlLoadParams app_params = params;
app_params.append_to = kLastTab;
app_service_->LoadUrlInNewTab(app_params);
return;
}
// Notify only after checking incognito match, otherwise the delegate will
// take of changing the mode and try again. Notifying before the checks can
// lead to be calling it twice, and calling 'did' below once.
notifier_->NewTabWillLoadUrl(params.web_params.url, params.user_initiated);
TabModel* tab_model = browser_->GetTabModel();
web::WebState* adjacent_web_state = nil;
if (params.append_to == kCurrentTab)
adjacent_web_state = tab_model.webStateList->GetActiveWebState();
UrlLoadParams saved_params = params;
auto openTab = ^{
[tab_model insertWebStateWithLoadParams:saved_params.web_params
opener:adjacent_web_state
openedByDOM:NO
atIndex:TabModelConstants::
kTabPositionAutomatically
inBackground:saved_params.in_background()];
notifier_->NewTabDidLoadUrl(saved_params.web_params.url,
saved_params.user_initiated);
};
if (!params.in_background()) {
openTab();
} else {
[delegate_ animateOpenBackgroundTabFromParams:params completion:openTab];
}
}