Commit 8126e87e authored by Hiroshi Ichikawa's avatar Hiroshi Ichikawa Committed by Commit Bot

Implement -[CWVWebView setScriptCommandHandler:commandPrefix:].

Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I9cad1d28a2827e5fcd07f0daa96667148f3b5fa2
Reviewed-on: https://chromium-review.googlesource.com/1009882
Commit-Queue: Hiroshi Ichikawa <ichikawa@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#553407}
parent 8475e664
......@@ -50,6 +50,7 @@ ios_web_view_public_headers = [
"public/cwv_navigation_type.h",
"public/cwv_preferences.h",
"public/cwv_preview_element_info.h",
"public/cwv_script_command.h",
"public/cwv_scroll_view.h",
"public/cwv_scroll_view_delegate.h",
"public/cwv_translation_controller.h",
......@@ -107,6 +108,8 @@ ios_web_view_sources = [
"internal/cwv_preferences_internal.h",
"internal/cwv_preview_element_info.mm",
"internal/cwv_preview_element_info_internal.h",
"internal/cwv_script_command.mm",
"internal/cwv_script_command_internal.h",
"internal/cwv_scroll_view.mm",
"internal/cwv_scroll_view_internal.h",
"internal/cwv_user_content_controller.mm",
......
// 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/web_view/public/cwv_script_command.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@implementation CWVScriptCommand
@synthesize content = _content;
@synthesize mainDocumentURL = _mainDocumentURL;
@synthesize userInteracting = _userInteracting;
- (instancetype)initWithContent:(nullable NSDictionary*)content
mainDocumentURL:(NSURL*)mainDocumentURL
userInteracting:(BOOL)userInteracting {
self = [super init];
if (self) {
_content = [content copy];
_mainDocumentURL = [mainDocumentURL copy];
_userInteracting = userInteracting;
}
return self;
}
@end
// 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.
#ifndef IOS_WEB_VIEW_INTERNAL_CWV_SCRIPT_COMMAND_INTERNAL_H_
#define IOS_WEB_VIEW_INTERNAL_CWV_SCRIPT_COMMAND_INTERNAL_H_
#import "ios/web_view/public/cwv_script_command.h"
NS_ASSUME_NONNULL_BEGIN
@interface CWVScriptCommand ()
/**
* Designated initializer.
*
* @param content Content of the command. nil in case of an error converting the
* content object.
* @param mainDocumentURL URL of the document in the main web view frame.
* @param userInteracting YES if the user is currently interacting with the
* page.
*/
- (instancetype)initWithContent:(nullable NSDictionary*)content
mainDocumentURL:(NSURL*)mainDocumentURL
userInteracting:(BOOL)userInteracting NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
#endif // IOS_WEB_VIEW_INTERNAL_CWV_SCRIPT_COMMAND_INTERNAL_H_
......@@ -7,6 +7,8 @@
#include <memory>
#include <utility>
#include "base/json/json_writer.h"
#include "base/mac/bind_objc_block.h"
#include "base/mac/foundation_util.h"
#include "base/strings/sys_string_conversions.h"
#import "components/autofill/ios/browser/autofill_agent.h"
......@@ -30,6 +32,7 @@
#import "ios/web_view/internal/autofill/cwv_autofill_controller_internal.h"
#import "ios/web_view/internal/cwv_html_element_internal.h"
#import "ios/web_view/internal/cwv_navigation_action_internal.h"
#import "ios/web_view/internal/cwv_script_command_internal.h"
#import "ios/web_view/internal/cwv_scroll_view_internal.h"
#import "ios/web_view/internal/cwv_web_view_configuration_internal.h"
#import "ios/web_view/internal/translate/cwv_translation_controller_internal.h"
......@@ -53,7 +56,25 @@
namespace {
// A key used in NSCoder to store the session storage object.
NSString* const kSessionStorageKey = @"sessionStorage";
// Converts base::DictionaryValue to NSDictionary.
NSDictionary* NSDictionaryFromDictionaryValue(
const base::DictionaryValue& value) {
std::string json;
if (!base::JSONWriter::Write(value, &json)) {
NOTREACHED() << "Failed to convert base::DictionaryValue to JSON";
return nil;
}
NSData* json_data = [NSData dataWithBytes:json.c_str() length:json.length()];
NSDictionary* ns_dictionary =
[NSJSONSerialization JSONObjectWithData:json_data
options:kNilOptions
error:nil];
DCHECK(ns_dictionary) << "Failed to convert JSON to NSDictionary";
return ns_dictionary;
}
} // namespace
@interface CWVWebView ()<CRWWebStateDelegate, CRWWebStateObserver> {
CWVWebViewConfiguration* _configuration;
......@@ -394,6 +415,29 @@ static NSString* gUserAgentProduct = nil;
}
}
- (void)addScriptCommandHandler:(id<CWVScriptCommandHandler>)handler
commandPrefix:(NSString*)commandPrefix {
CWVWebView* __weak weakSelf = self;
const web::WebState::ScriptCommandCallback callback = base::BindBlockArc(
^bool(const base::DictionaryValue& content, const GURL& mainDocumentURL,
bool userInteracting) {
NSDictionary* nsContent = NSDictionaryFromDictionaryValue(content);
CWVScriptCommand* command = [[CWVScriptCommand alloc]
initWithContent:nsContent
mainDocumentURL:net::NSURLWithGURL(mainDocumentURL)
userInteracting:userInteracting];
return [handler webView:weakSelf handleScriptCommand:command];
});
_webState->AddScriptCommandCallback(callback,
base::SysNSStringToUTF8(commandPrefix));
}
- (void)removeScriptCommandHandlerForCommandPrefix:(NSString*)commandPrefix {
_webState->RemoveScriptCommandCallback(
base::SysNSStringToUTF8(commandPrefix));
}
#pragma mark - Translation
- (CWVTranslationController*)translationController {
......
// 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.
#ifndef IOS_WEB_VIEW_PUBLIC_CWV_SCRIPT_COMMAND_H_
#define IOS_WEB_VIEW_PUBLIC_CWV_SCRIPT_COMMAND_H_
#import <Foundation/Foundation.h>
#import "cwv_export.h"
NS_ASSUME_NONNULL_BEGIN
@class CWVWebView;
// A script command passed to CWVScriptCommandHandler.
CWV_EXPORT
@interface CWVScriptCommand : NSObject
- (instancetype)init NS_UNAVAILABLE;
// Content of the command. nil in case of an error converting the content
// object.
@property(nonatomic, readonly, nullable, copy) NSDictionary* content;
// URL of the document in the main web view frame.
@property(nonatomic, readonly, copy) NSURL* mainDocumentURL;
// YES if the user is currently interacting with the page.
@property(nonatomic, readonly, getter=isUserInteracting) BOOL userInteracting;
@end
// Provides a method for receiving commands from JavaScript running in a web
// page.
CWV_EXPORT
@protocol CWVScriptCommandHandler<NSObject>
- (BOOL)webView:(CWVWebView*)webView
handleScriptCommand:(CWVScriptCommand*)command;
@end
NS_ASSUME_NONNULL_END
#endif // IOS_WEB_VIEW_PUBLIC_CWV_SCRIPT_COMMAND_H_
......@@ -10,11 +10,13 @@
NS_ASSUME_NONNULL_BEGIN
@class CWVScriptCommand;
@class CWVScrollView;
@class CWVTranslationController;
@class CWVWebViewConfiguration;
@protocol CWVUIDelegate;
@protocol CWVNavigationDelegate;
@protocol CWVScriptCommandHandler;
@protocol CWVUIDelegate;
// A web view component (like WKWebView) which uses iOS Chromium's web view
// implementation.
......@@ -136,6 +138,27 @@ CWV_EXPORT
- (void)evaluateJavaScript:(NSString*)javaScriptString
completionHandler:(void (^)(id, NSError*))completionHandler;
// Registers a handler that will be called when a command matching
// |commandPrefix| is received.
//
// Web pages can send a command by executing JavaScript like this:
// __gCrWeb.message.invokeOnHost(
// {'command': 'test.command1', 'key1':'value1', 'key2': 42});
// And receive it by:
// [webView addScriptCommandHandler:handler commandPrefix:@"test"];
//
// Make sure to call -removeScriptCommandHandlerForCommandPrefix: with the same
// prefix before deallocating a CWVWebView instance. Otherwise it causes an
// assertion failure.
//
// This provides a similar functionarity to -[WKUserContentController
// addScriptMessageHandler:name:].
- (void)addScriptCommandHandler:(id<CWVScriptCommandHandler>)handler
commandPrefix:(NSString*)commandPrefix;
// Removes the handler associated with |commandPrefix|.
- (void)removeScriptCommandHandlerForCommandPrefix:(NSString*)commandPrefix;
@end
NS_ASSUME_NONNULL_END
......
......@@ -22,6 +22,7 @@ NSString* const kWebViewShellJavaScriptDialogTextFieldAccessibiltyIdentifier =
@interface ShellViewController ()<CWVNavigationDelegate,
CWVUIDelegate,
CWVScriptCommandHandler,
UITextFieldDelegate>
// Container for |webView|.
@property(nonatomic, strong) UIView* containerView;
......@@ -281,18 +282,23 @@ NSString* const kWebViewShellJavaScriptDialogTextFieldAccessibiltyIdentifier =
forKeyPath:@"canGoForward"
options:NSKeyValueObservingOptionNew
context:nil];
[_webView addScriptCommandHandler:self commandPrefix:@"test"];
}
- (void)removeWebView {
[_webView removeFromSuperview];
[_webView removeObserver:self forKeyPath:@"canGoBack"];
[_webView removeObserver:self forKeyPath:@"canGoForward"];
[_webView removeScriptCommandHandlerForCommandPrefix:@"test"];
_webView = nil;
}
- (void)dealloc {
[_webView removeObserver:self forKeyPath:@"canGoBack"];
[_webView removeObserver:self forKeyPath:@"canGoForward"];
[_webView removeScriptCommandHandlerForCommandPrefix:@"test"];
}
- (BOOL)textFieldShouldReturn:(UITextField*)field {
......@@ -498,4 +504,12 @@ NSString* const kWebViewShellJavaScriptDialogTextFieldAccessibiltyIdentifier =
NSLog(@"%@", NSStringFromSelector(_cmd));
}
#pragma mark CWVScriptCommandHandler
- (BOOL)webView:(CWVWebView*)webView
handleScriptCommand:(nonnull CWVScriptCommand*)command {
NSLog(@"%@ command.content=%@", NSStringFromSelector(_cmd), command.content);
return YES;
}
@end
......@@ -15,6 +15,7 @@ source_set("inttests") {
"web_view_int_test.mm",
"web_view_kvo_inttest.mm",
"web_view_restorable_state_inttest.mm",
"web_view_script_command_inttest.mm",
]
deps = [
......
// 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 <ChromeWebView/ChromeWebView.h>
#import <Foundation/Foundation.h>
#import "ios/testing/wait_util.h"
#import "ios/web_view/test/web_view_int_test.h"
#import "ios/web_view/test/web_view_test_util.h"
#import "net/base/mac/url_conversions.h"
#include "testing/gtest_mac.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface CWVFakeScriptCommandHandler : NSObject<CWVScriptCommandHandler>
@property(nonatomic) CWVScriptCommand* lastReceivedCommand;
- (BOOL)webView:(CWVWebView*)webView
handleScriptCommand:(CWVScriptCommand*)command;
@end
@implementation CWVFakeScriptCommandHandler
@synthesize lastReceivedCommand = _lastReceivedCommand;
- (BOOL)webView:(CWVWebView*)webView
handleScriptCommand:(CWVScriptCommand*)command {
self.lastReceivedCommand = command;
return YES;
}
@end
namespace ios_web_view {
// Tests the script command feature in CWVWebView.
using WebViewScriptCommandTest = WebViewIntTest;
// Tests that a handler added by -[CWVWebView
// addScriptCommandHandler:commandPrefix] is invoked by JavaScript.
TEST_F(WebViewScriptCommandTest, TestScriptCommand) {
CWVFakeScriptCommandHandler* handler =
[[CWVFakeScriptCommandHandler alloc] init];
[web_view_ addScriptCommandHandler:handler commandPrefix:@"test"];
// Uses GetUrlForPageWithHtmlBody() instead of simply using about:blank
// because it looks __gCrWeb may not be available on about:blank.
// TODO(crbug.com/836114): Analyze why.
NSURL* url = net::NSURLWithGURL(GetUrlForPageWithHtmlBody(""));
ASSERT_TRUE(test::LoadUrl(web_view_, url));
ASSERT_TRUE(test::WaitForWebViewLoadCompletionOrTimeout(web_view_));
NSString* script =
@"__gCrWeb.message.invokeOnHost("
@"{'command': 'test.command1', 'key1': 'value1', 'key2': 42});";
NSError* script_error = nil;
test::EvaluateJavaScript(web_view_, script, &script_error);
ASSERT_NSEQ(nil, script_error);
EXPECT_TRUE(testing::WaitUntilConditionOrTimeout(
testing::kWaitForJSCompletionTimeout, ^{
return handler.lastReceivedCommand != nil;
}));
EXPECT_NSEQ(@"test.command1",
handler.lastReceivedCommand.content[@"command"]);
EXPECT_NSEQ(@"value1", handler.lastReceivedCommand.content[@"key1"]);
EXPECT_NSEQ(@42, handler.lastReceivedCommand.content[@"key2"]);
EXPECT_NSEQ(url, handler.lastReceivedCommand.mainDocumentURL);
EXPECT_FALSE(handler.lastReceivedCommand.userInteracting);
[web_view_ removeScriptCommandHandlerForCommandPrefix:@"test"];
}
} // namespace ios_web_view
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment