blob: 9c000be511b0f4b5ec9d7e83feca3783221e8476 [file] [log] [blame]
// Copyright 2014 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/js_messaging/crw_wk_script_message_router.h"
#include "base/check.h"
#include "ios/web/common/features.h"
#include "ios/web/navigation/wk_navigation_util.h"
#import "net/base/mac/url_conversions.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface CRWWKScriptMessageRouter () <WKScriptMessageHandler>
// Removes a specific message handler. Does nothing if handler does not exist.
- (void)tryRemoveScriptMessageHandlerForName:(NSString*)messageName
webView:(WKWebView*)webView;
@end
@implementation CRWWKScriptMessageRouter {
// Two level map of registed message handlers:
// {MessageName => {WKWebView => MessageCallbacks}}.
NSMutableDictionary<NSString*,
NSMapTable<WKWebView*, void (^)(WKScriptMessage*)>*>*
_handlers;
// Wrapped WKUserContentController.
WKUserContentController* _userContentController;
}
#pragma mark -
#pragma mark Interface
- (WKUserContentController*)userContentController {
return _userContentController;
}
- (instancetype)initWithUserContentController:
(WKUserContentController*)userContentController {
DCHECK(userContentController);
if ((self = [super init])) {
_handlers = [[NSMutableDictionary alloc] init];
_userContentController = userContentController;
}
return self;
}
- (void)setScriptMessageHandler:(void (^)(WKScriptMessage*))handler
name:(NSString*)messageName
webView:(WKWebView*)webView {
DCHECK(handler);
DCHECK(messageName);
DCHECK(webView);
NSMapTable* webViewToHandlerMap = [_handlers objectForKey:messageName];
if (!webViewToHandlerMap) {
webViewToHandlerMap =
[NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory
valueOptions:NSPointerFunctionsCopyIn];
[_handlers setObject:webViewToHandlerMap forKey:messageName];
[_userContentController addScriptMessageHandler:self name:messageName];
}
DCHECK(![webViewToHandlerMap objectForKey:webView]);
[webViewToHandlerMap setObject:handler forKey:webView];
}
- (void)removeScriptMessageHandlerForName:(NSString*)messageName
webView:(WKWebView*)webView {
DCHECK(messageName);
DCHECK(webView);
DCHECK([[_handlers objectForKey:messageName] objectForKey:webView]);
[self tryRemoveScriptMessageHandlerForName:messageName webView:webView];
}
- (void)removeAllScriptMessageHandlersForWebView:(WKWebView*)webView {
DCHECK(webView);
for (NSString* messageName in [_handlers allKeys]) {
[self tryRemoveScriptMessageHandlerForName:messageName webView:webView];
}
}
#pragma mark -
#pragma mark WKScriptMessageHandler
- (void)userContentController:(WKUserContentController*)userContentController
didReceiveScriptMessage:(WKScriptMessage*)message {
// Ignore frame registration messages from internal placeholder pages.
GURL url = net::GURLWithNSURL(message.frameInfo.request.URL);
if (!base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) &&
web::wk_navigation_util::IsPlaceholderUrl(url)) {
return;
}
NSMapTable* webViewToHandlerMap = [_handlers objectForKey:message.name];
DCHECK(webViewToHandlerMap);
id handler = [webViewToHandlerMap objectForKey:message.webView];
if (handler) {
// Web process can send messages even if web view was reset and
// script message handler has been removed from the router.
((void (^)(WKScriptMessage*))handler)(message);
}
}
#pragma mark -
#pragma mark Implementation
- (void)tryRemoveScriptMessageHandlerForName:(NSString*)messageName
webView:(WKWebView*)webView {
NSMapTable* webViewToHandlerMap = [_handlers objectForKey:messageName];
NS_VALID_UNTIL_END_OF_SCOPE id handler =
[webViewToHandlerMap objectForKey:webView];
if (!handler)
return;
if (webViewToHandlerMap.count == 1) {
[_handlers removeObjectForKey:messageName];
[_userContentController removeScriptMessageHandlerForName:messageName];
} else {
[webViewToHandlerMap removeObjectForKey:webView];
}
}
@end