blob: 359a801254a14444c55ca8072404270275157c17 [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/web/web_state/ui/crw_wk_ui_handler.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#import "ios/web/navigation/wk_navigation_action_util.h"
#import "ios/web/navigation/wk_navigation_util.h"
#import "ios/web/public/ui/context_menu_params.h"
#import "ios/web/public/ui/java_script_dialog_type.h"
#import "ios/web/public/web_client.h"
#import "ios/web/web_state/ui/crw_legacy_context_menu_controller.h"
#import "ios/web/web_state/ui/crw_wk_ui_handler_delegate.h"
#import "ios/web/web_state/user_interaction_state.h"
#import "ios/web/web_state/web_state_impl.h"
#import "ios/web/web_view/wk_security_origin_util.h"
#import "ios/web/webui/mojo_facade.h"
#import "net/base/mac/url_conversions.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface CRWWKUIHandler () {
// Backs up property with the same name.
std::unique_ptr<web::MojoFacade> _mojoFacade;
}
@property(nonatomic, assign, readonly) web::WebStateImpl* webStateImpl;
// Facade for Mojo API.
@property(nonatomic, readonly) web::MojoFacade* mojoFacade;
@end
@implementation CRWWKUIHandler
#pragma mark - CRWWebViewHandler
- (void)close {
[super close];
_mojoFacade.reset();
}
#pragma mark - Property
- (web::WebStateImpl*)webStateImpl {
return [self.delegate webStateImplForWebViewHandler:self];
}
- (web::MojoFacade*)mojoFacade {
if (!_mojoFacade)
_mojoFacade = std::make_unique<web::MojoFacade>(self.webStateImpl);
return _mojoFacade.get();
}
#pragma mark - WKUIDelegate
- (WKWebView*)webView:(WKWebView*)webView
createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration
forNavigationAction:(WKNavigationAction*)action
windowFeatures:(WKWindowFeatures*)windowFeatures {
// Do not create windows for non-empty invalid URLs.
GURL requestURL = net::GURLWithNSURL(action.request.URL);
if (!requestURL.is_empty() && !requestURL.is_valid()) {
DLOG(WARNING) << "Unable to open a window with invalid URL: "
<< requestURL.possibly_invalid_spec();
return nil;
}
NSString* referrer = [action.request
valueForHTTPHeaderField:web::wk_navigation_util::kReferrerHeaderName];
GURL openerURL = referrer.length
? GURL(base::SysNSStringToUTF8(referrer))
: [self.delegate documentURLForWebViewHandler:self];
// There is no reliable way to tell if there was a user gesture, so this code
// checks if user has recently tapped on web view. TODO(crbug.com/809706):
// Remove the usage of -userIsInteracting when rdar://19989909 is fixed.
bool initiatedByUser = [self.delegate UIHandler:self
isUserInitiatedAction:action];
if (UIAccessibilityIsVoiceOverRunning()) {
// -userIsInteracting returns NO if VoiceOver is On. Inspect action's
// description, which may contain the information about user gesture for
// certain link clicks.
initiatedByUser = initiatedByUser ||
web::GetNavigationActionInitiationTypeWithVoiceOverOn(
action.description) ==
web::NavigationActionInitiationType::kUserInitiated;
}
web::WebState* childWebState = self.webStateImpl->CreateNewWebState(
requestURL, openerURL, initiatedByUser);
if (!childWebState)
return nil;
// WKWebView requires WKUIDelegate to return a child view created with
// exactly the same |configuration| object (exception is raised if config is
// different). |configuration| param and config returned by
// WKWebViewConfigurationProvider are different objects because WKWebView
// makes a shallow copy of the config inside init, so every WKWebView
// owns a separate shallow copy of WKWebViewConfiguration.
return [self.delegate UIHandler:self
createWebViewWithConfiguration:configuration
forWebState:childWebState];
}
- (void)webViewDidClose:(WKWebView*)webView {
if (self.webStateImpl && self.webStateImpl->HasOpener())
self.webStateImpl->CloseWebState();
}
- (void)webView:(WKWebView*)webView
runJavaScriptAlertPanelWithMessage:(NSString*)message
initiatedByFrame:(WKFrameInfo*)frame
completionHandler:(void (^)())completionHandler {
[self runJavaScriptDialogOfType:web::JAVASCRIPT_DIALOG_TYPE_ALERT
initiatedByFrame:frame
message:message
defaultText:nil
completion:^(BOOL, NSString*) {
completionHandler();
}];
}
- (void)webView:(WKWebView*)webView
runJavaScriptConfirmPanelWithMessage:(NSString*)message
initiatedByFrame:(WKFrameInfo*)frame
completionHandler:
(void (^)(BOOL result))completionHandler {
[self runJavaScriptDialogOfType:web::JAVASCRIPT_DIALOG_TYPE_CONFIRM
initiatedByFrame:frame
message:message
defaultText:nil
completion:^(BOOL success, NSString*) {
if (completionHandler) {
completionHandler(success);
}
}];
}
- (void)webView:(WKWebView*)webView
runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
defaultText:(NSString*)defaultText
initiatedByFrame:(WKFrameInfo*)frame
completionHandler:
(void (^)(NSString* result))completionHandler {
GURL origin(web::GURLOriginWithWKSecurityOrigin(frame.securityOrigin));
if (web::GetWebClient()->IsAppSpecificURL(origin)) {
std::string mojoResponse =
self.mojoFacade->HandleMojoMessage(base::SysNSStringToUTF8(prompt));
completionHandler(base::SysUTF8ToNSString(mojoResponse));
return;
}
[self runJavaScriptDialogOfType:web::JAVASCRIPT_DIALOG_TYPE_PROMPT
initiatedByFrame:frame
message:prompt
defaultText:defaultText
completion:^(BOOL, NSString* input) {
if (completionHandler) {
completionHandler(input);
}
}];
}
#if !defined(__IPHONE_13_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0
// TODO(crbug.com/1131852): Preview depracted is iOS13+
- (BOOL)webView:(WKWebView*)webView
shouldPreviewElement:(WKPreviewElementInfo*)elementInfo {
return self.webStateImpl->ShouldPreviewLink(
net::GURLWithNSURL(elementInfo.linkURL));
}
- (UIViewController*)webView:(WKWebView*)webView
previewingViewControllerForElement:(WKPreviewElementInfo*)elementInfo
defaultActions:
(NSArray<id<WKPreviewActionItem>>*)previewActions {
// Prevent |_contextMenuController| from intercepting the default behavior for
// the current on-going touch. Otherwise it would cancel the on-going Peek&Pop
// action and show its own context menu instead (crbug.com/770619).
[self.contextMenuController allowSystemUIForCurrentGesture];
return self.webStateImpl->GetPreviewingViewController(
net::GURLWithNSURL(elementInfo.linkURL));
}
- (void)webView:(WKWebView*)webView
commitPreviewingViewController:(UIViewController*)previewingViewController {
return self.webStateImpl->CommitPreviewingViewController(
previewingViewController);
}
- (void)webView:(WKWebView*)webView
contextMenuConfigurationForElement:(WKContextMenuElementInfo*)elementInfo
completionHandler:
(void (^)(UIContextMenuConfiguration* _Nullable))
completionHandler API_AVAILABLE(ios(13.0)) {
web::WebStateDelegate* delegate = self.webStateImpl->GetDelegate();
if (!delegate) {
completionHandler(nil);
return;
}
web::ContextMenuParams params;
params.link_url = net::GURLWithNSURL(elementInfo.linkURL);
delegate->ContextMenuConfiguration(
self.webStateImpl, params, /*preview_provider=*/nil, completionHandler);
}
#endif // End of >iOS13 deprecated block.
- (void)webView:(WKWebView*)webView
contextMenuDidEndForElement:(WKContextMenuElementInfo*)elementInfo
API_AVAILABLE(ios(13.0)) {
web::WebStateDelegate* delegate = self.webStateImpl->GetDelegate();
if (!delegate) {
return;
}
delegate->ContextMenuDidEnd(self.webStateImpl,
net::GURLWithNSURL(elementInfo.linkURL));
}
- (void)webView:(WKWebView*)webView
contextMenuForElement:(nonnull WKContextMenuElementInfo*)elementInfo
willCommitWithAnimator:
(nonnull id<UIContextMenuInteractionCommitAnimating>)animator
API_AVAILABLE(ios(13.0)) {
web::WebStateDelegate* delegate = self.webStateImpl->GetDelegate();
if (!delegate) {
return;
}
delegate->ContextMenuWillCommitWithAnimator(
self.webStateImpl, net::GURLWithNSURL(elementInfo.linkURL), animator);
}
- (void)webView:(WKWebView*)webView
contextMenuWillPresentForElement:(WKContextMenuElementInfo*)elementInfo
API_AVAILABLE(ios(13.0)) {
web::WebStateDelegate* delegate = self.webStateImpl->GetDelegate();
if (!delegate) {
return;
}
delegate->ContextMenuWillPresent(self.webStateImpl,
net::GURLWithNSURL(elementInfo.linkURL));
}
#pragma mark - Helper
// Helper to respond to |webView:runJavaScript...| delegate methods.
// |completionHandler| must not be nil.
- (void)runJavaScriptDialogOfType:(web::JavaScriptDialogType)type
initiatedByFrame:(WKFrameInfo*)frame
message:(NSString*)message
defaultText:(NSString*)defaultText
completion:(void (^)(BOOL, NSString*))completionHandler {
DCHECK(completionHandler);
// JavaScript dialogs should not be presented if there is no information about
// the requesting page's URL.
GURL requestURL = net::GURLWithNSURL(frame.request.URL);
if (!requestURL.is_valid()) {
completionHandler(NO, nil);
return;
}
if (self.webStateImpl->GetVisibleURL().GetOrigin() !=
requestURL.GetOrigin() &&
frame.mainFrame) {
// Dialog was requested by web page's main frame, but visible URL has
// different origin. This could happen if the user has started a new
// browser initiated navigation. There is no value in showing dialogs
// requested by page, which this WebState is about to leave. But presenting
// the dialog can lead to phishing and other abusive behaviors.
completionHandler(NO, nil);
return;
}
self.webStateImpl->RunJavaScriptDialog(
requestURL, type, message, defaultText,
base::BindOnce(^(bool success, NSString* input) {
completionHandler(success, input);
}));
}
@end