blob: 7db1960dd4223eb3fae4e927073417a81176ad40 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/web/web_state_delegate_browser_agent.h"
#import "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/overlays/public/overlay_callback_manager.h"
#import "ios/chrome/browser/overlays/public/overlay_modality.h"
#import "ios/chrome/browser/overlays/public/overlay_request.h"
#import "ios/chrome/browser/overlays/public/overlay_request_queue.h"
#import "ios/chrome/browser/overlays/public/overlay_response.h"
#import "ios/chrome/browser/overlays/public/web_content_area/http_auth_overlay.h"
#import "ios/chrome/browser/permissions/permissions_tab_helper.h"
#import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
#import "ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.h"
#import "ios/chrome/browser/ui/dialogs/nsurl_protection_space_util.h"
#import "ios/chrome/browser/url_loading/url_loading_browser_agent.h"
#import "ios/chrome/browser/url_loading/url_loading_params.h"
#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
#import "ios/chrome/browser/web/repost_form_tab_helper.h"
#import "ios/chrome/browser/web/web_state_container_view_provider.h"
#import "ios/chrome/browser/web_state_list/tab_insertion_browser_agent.h"
#import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
#import "ios/web/public/ui/context_menu_params.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
BROWSER_USER_DATA_KEY_IMPL(WebStateDelegateBrowserAgent)
namespace {
// Callback for HTTP authentication dialogs. This callback is a standalone
// function rather than an instance method. This is to ensure that the callback
// can be executed regardless of whether the browser agent has been destroyed.
void OnHTTPAuthOverlayFinished(web::WebStateDelegate::AuthCallback callback,
OverlayResponse* response) {
if (response) {
HTTPAuthOverlayResponseInfo* auth_info =
response->GetInfo<HTTPAuthOverlayResponseInfo>();
if (auth_info) {
std::move(callback).Run(base::SysUTF8ToNSString(auth_info->username()),
base::SysUTF8ToNSString(auth_info->password()));
return;
}
}
std::move(callback).Run(nil, nil);
}
} // namespace
WebStateDelegateBrowserAgent::WebStateDelegateBrowserAgent(
Browser* browser,
TabInsertionBrowserAgent* tab_insertion_agent)
: web_state_list_(browser->GetWebStateList()),
tab_insertion_agent_(tab_insertion_agent) {
DCHECK(tab_insertion_agent_);
browser_ = browser;
browser_observation_.Observe(browser);
web_state_list_observation_.Observe(web_state_list_);
// All the BrowserAgent are attached to the Browser during the creation,
// the WebStateList must be empty at this point.
DCHECK(web_state_list_->empty())
<< "WebStateDelegateBrowserAgent created for a Browser with a non-empty "
"WebStateList.";
}
WebStateDelegateBrowserAgent::~WebStateDelegateBrowserAgent() {}
void WebStateDelegateBrowserAgent::SetUIProviders(
ContextMenuConfigurationProvider* context_menu_provider,
id<CRWResponderInputView> input_view_provider,
id<WebStateContainerViewProvider> container_view_provider) {
context_menu_provider_ = context_menu_provider;
input_view_provider_ = input_view_provider;
container_view_provider_ = container_view_provider;
}
void WebStateDelegateBrowserAgent::ClearUIProviders() {
context_menu_provider_ = nil;
input_view_provider_ = nil;
container_view_provider_ = nil;
}
#pragma mark - WebStateListObserver
void WebStateDelegateBrowserAgent::WebStateListChanged(
WebStateList* web_state_list,
const WebStateListChange& change,
const WebStateSelection& selection) {
switch (change.type()) {
case WebStateListChange::Type::kSelectionOnly:
// Do nothing when a WebState is selected and its status is updated.
break;
case WebStateListChange::Type::kDetach:
// TODO(crbug.com/1442546): Move the implementation from
// WebStateDetachedAt() to here.
break;
case WebStateListChange::Type::kMove:
// Do nothing when a WebState is moved.
break;
case WebStateListChange::Type::kReplace: {
const WebStateListChangeReplace& replace_change =
change.As<WebStateListChangeReplace>();
ClearWebStateDelegate(replace_change.replaced_web_state());
SetWebStateDelegate(replace_change.inserted_web_state());
break;
}
case WebStateListChange::Type::kInsert: {
const WebStateListChangeInsert& insert_change =
change.As<WebStateListChangeInsert>();
SetWebStateDelegate(insert_change.inserted_web_state());
break;
}
}
}
void WebStateDelegateBrowserAgent::WebStateDetachedAt(
WebStateList* web_state_list,
web::WebState* web_state,
int index) {
ClearWebStateDelegate(web_state);
}
// BrowserObserver::
void WebStateDelegateBrowserAgent::BrowserDestroyed(Browser* browser) {
DCHECK(browser_observation_.IsObservingSource(browser));
WebStateList* web_state_list = browser->GetWebStateList();
DCHECK(web_state_list_observation_.IsObservingSource(web_state_list));
DCHECK_EQ(web_state_list_, web_state_list);
// Remove all web state delegates.
for (int index = 0; index < web_state_list_->count(); ++index)
web_state_list_->GetWebStateAt(index)->SetDelegate(nullptr);
web_state_observations_.RemoveAllObservations();
web_state_list_observation_.Reset();
browser_observation_.Reset();
}
// WebStateObserver::
void WebStateDelegateBrowserAgent::WebStateRealized(web::WebState* web_state) {
SetWebStateDelegate(web_state);
web_state_observations_.RemoveObservation(web_state);
}
void WebStateDelegateBrowserAgent::WebStateDestroyed(web::WebState* web_state) {
web_state_observations_.RemoveObservation(web_state);
}
// WebStateDelegate::
web::WebState* WebStateDelegateBrowserAgent::CreateNewWebState(
web::WebState* source,
const GURL& url,
const GURL& opener_url,
bool initiated_by_user) {
// Under some circumstances, this callback may be triggered from WebKit
// synchronously as part of handling some other WebStateList mutation
// (typically deleting a WebState and then activating another as a side
// effect). See crbug.com/988504 for details. In this case, the request to
// create a new WebState is silently dropped.
if (web_state_list_->IsMutating())
return nullptr;
// Check if requested web state is a popup and block it if necessary.
if (!initiated_by_user) {
auto* helper = BlockedPopupTabHelper::FromWebState(source);
if (helper->ShouldBlockPopup(opener_url)) {
// It's possible for a page to inject a popup into a window created via
// window.open before its initial load is committed. Rather than relying
// on the last committed or pending NavigationItem's referrer policy, just
// use ReferrerPolicyDefault.
// TODO(crbug.com/719993): Update this to a more appropriate referrer
// policy once referrer policies are correctly recorded in
// NavigationItems.
web::Referrer referrer(opener_url, web::ReferrerPolicyDefault);
helper->HandlePopup(url, referrer);
return nullptr;
}
}
// Requested web state should not be blocked from opening.
SnapshotTabHelper::FromWebState(source)->UpdateSnapshotWithCallback(nil);
return tab_insertion_agent_->InsertWebStateOpenedByDOM(source);
}
void WebStateDelegateBrowserAgent::CloseWebState(web::WebState* source) {
int index = web_state_list_->GetIndexOfWebState(source);
if (index != WebStateList::kInvalidIndex)
web_state_list_->CloseWebStateAt(index, WebStateList::CLOSE_USER_ACTION);
}
web::WebState* WebStateDelegateBrowserAgent::OpenURLFromWebState(
web::WebState* source,
const web::WebState::OpenURLParams& params) {
web::NavigationManager::WebLoadParams load_params(params.url);
load_params.referrer = params.referrer;
load_params.transition_type = params.transition;
load_params.is_renderer_initiated = params.is_renderer_initiated;
load_params.virtual_url = params.virtual_url;
switch (params.disposition) {
case WindowOpenDisposition::NEW_FOREGROUND_TAB:
case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
return tab_insertion_agent_->InsertWebState(
load_params, source, false, TabInsertion::kPositionAutomatically,
(params.disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB),
/*inherit_opener=*/false, /*should_show_start_surface=*/false,
/*should_skip_new_tab_animation=*/false);
}
case WindowOpenDisposition::CURRENT_TAB: {
source->GetNavigationManager()->LoadURLWithParams(load_params);
return source;
}
case WindowOpenDisposition::NEW_POPUP: {
return tab_insertion_agent_->InsertWebState(
load_params, source, true, TabInsertion::kPositionAutomatically,
/*in_background=*/false, /*inherit_opener=*/false,
/*should_show_start_surface=*/false,
/*should_skip_new_tab_animation=*/false);
}
default:
NOTIMPLEMENTED();
return nullptr;
};
}
void WebStateDelegateBrowserAgent::ShowRepostFormWarningDialog(
web::WebState* source,
base::OnceCallback<void(bool)> callback) {
if (!container_view_provider_) {
// There's no way to show the dialog so treat it as if the user said no.
std::move(callback).Run(false);
return;
}
// TODO(crbug.com/1266052) : Clean up this API.
RepostFormTabHelper::FromWebState(source)->PresentDialog(
[container_view_provider_ dialogLocation], std::move(callback));
}
web::JavaScriptDialogPresenter*
WebStateDelegateBrowserAgent::GetJavaScriptDialogPresenter(
web::WebState* source) {
return &java_script_dialog_presenter_;
}
void WebStateDelegateBrowserAgent::HandlePermissionsDecisionRequest(
web::WebState* source,
NSArray<NSNumber*>* permissions,
web::WebStatePermissionDecisionHandler handler) API_AVAILABLE(ios(15.0)) {
PermissionsTabHelper::FromWebState(source)
->PresentPermissionsDecisionDialogWithCompletionHandler(permissions,
handler);
}
void WebStateDelegateBrowserAgent::OnAuthRequired(
web::WebState* source,
NSURLProtectionSpace* protection_space,
NSURLCredential* proposed_credential,
web::WebStateDelegate::AuthCallback callback) {
std::string message = base::SysNSStringToUTF8(
nsurlprotectionspace_util::MessageForHTTPAuth(protection_space));
std::string default_username;
if (proposed_credential.user)
default_username = base::SysNSStringToUTF8(proposed_credential.user);
std::unique_ptr<OverlayRequest> request =
OverlayRequest::CreateWithConfig<HTTPAuthOverlayRequestConfig>(
nsurlprotectionspace_util::RequesterOrigin(protection_space), message,
default_username);
request->GetCallbackManager()->AddCompletionCallback(
base::BindOnce(&OnHTTPAuthOverlayFinished, std::move(callback)));
OverlayRequestQueue::FromWebState(source, OverlayModality::kWebContentArea)
->AddRequest(std::move(request));
}
UIView* WebStateDelegateBrowserAgent::GetWebViewContainer(
web::WebState* source) {
return [container_view_provider_ containerView];
}
void WebStateDelegateBrowserAgent::ContextMenuConfiguration(
web::WebState* source,
const web::ContextMenuParams& params,
void (^completion_handler)(UIContextMenuConfiguration*)) {
UIContextMenuConfiguration* configuration =
[context_menu_provider_ contextMenuConfigurationForWebState:source
params:params];
completion_handler(configuration);
}
void WebStateDelegateBrowserAgent::ContextMenuWillCommitWithAnimator(
web::WebState* source,
id<UIContextMenuInteractionCommitAnimating> animator) {
GURL url_to_load = [context_menu_provider_ URLToLoad];
if (!url_to_load.is_valid())
return;
UrlLoadParams params = UrlLoadParams::InCurrentTab(url_to_load);
UrlLoadingBrowserAgent::FromBrowser(browser_)->Load(params);
}
id<CRWResponderInputView> WebStateDelegateBrowserAgent::GetResponderInputView(
web::WebState* source) {
return input_view_provider_;
}
void WebStateDelegateBrowserAgent::SetWebStateDelegate(
web::WebState* web_state) {
DCHECK(web_state);
if (web_state->IsRealized()) {
web_state->SetDelegate(this);
} else {
web_state_observations_.AddObservation(web_state);
}
}
void WebStateDelegateBrowserAgent::ClearWebStateDelegate(
web::WebState* web_state) {
DCHECK(web_state);
if (web_state->IsRealized()) {
web_state->SetDelegate(nullptr);
} else {
web_state_observations_.RemoveObservation(web_state);
}
}