blob: 4a404c3c424964808b6ff1f9940cf6c4e10a96c6 [file] [log] [blame]
// Copyright 2016 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/webui/mojo_facade.h"
#include <memory>
#include "base/message_loop/message_loop.h"
#import "base/mac/scoped_nsobject.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/sys_string_conversions.h"
#include "base/test/ios/wait_util.h"
#include "ios/web/public/test/web_test.h"
#import "ios/web/public/web_state/js/crw_js_injection_evaluator.h"
#include "ios/web/test/mojo_test.mojom.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/shell/public/cpp/interface_factory.h"
#include "services/shell/public/cpp/interface_registry.h"
#import "testing/gtest_mac.h"
#import "third_party/ocmock/OCMock/OCMock.h"
namespace web {
namespace {
// Serializes the given |object| to JSON string.
std::string GetJson(id object) {
NSData* json_as_data =
[NSJSONSerialization dataWithJSONObject:object options:0 error:nil];
base::scoped_nsobject<NSString> json_as_string([[NSString alloc]
initWithData:json_as_data
encoding:NSUTF8StringEncoding]);
return base::SysNSStringToUTF8(json_as_string);
}
// Deserializes the given |json| to an object.
id GetObject(const std::string& json) {
NSData* json_as_data =
[base::SysUTF8ToNSString(json) dataUsingEncoding:NSUTF8StringEncoding];
return [NSJSONSerialization JSONObjectWithData:json_as_data
options:0
error:nil];
}
// Test mojo handler factory.
class TestUIHandlerFactory : public shell::InterfaceFactory<TestUIHandlerMojo> {
public:
~TestUIHandlerFactory() override {}
private:
// shell::InterfaceFactory overrides.
void Create(shell::Connection* connection,
mojo::InterfaceRequest<TestUIHandlerMojo> request) override {}
};
} // namespace
// A test fixture to test MojoFacade class.
class MojoFacadeTest : public WebTest {
protected:
MojoFacadeTest() {
interface_registry_.reset(new shell::InterfaceRegistry(nullptr));
interface_registry_->AddInterface(&ui_handler_factory_);
evaluator_.reset([[OCMockObject
mockForProtocol:@protocol(CRWJSInjectionEvaluator)] retain]);
facade_.reset(new MojoFacade(
interface_registry_.get(),
static_cast<id<CRWJSInjectionEvaluator>>(evaluator_.get())));
}
OCMockObject* evaluator() { return evaluator_.get(); }
MojoFacade* facade() { return facade_.get(); }
private:
TestUIHandlerFactory ui_handler_factory_;
std::unique_ptr<shell::InterfaceRegistry> interface_registry_;
base::scoped_nsobject<OCMockObject> evaluator_;
std::unique_ptr<MojoFacade> facade_;
};
// Tests connecting to existing service and closing the handle.
TEST_F(MojoFacadeTest, ConnectToServiceAndCloseHandle) {
// Connect to the service.
NSDictionary* connect = @{
@"name" : @"service_provider.connectToService",
@"args" : @{
@"serviceName" : @"::TestUIHandlerMojo",
},
};
std::string handle_as_string = facade()->HandleMojoMessage(GetJson(connect));
EXPECT_FALSE(handle_as_string.empty());
int handle = 0;
EXPECT_TRUE(base::StringToInt(handle_as_string, &handle));
// Close the handle.
NSDictionary* close = @{
@"name" : @"core.close",
@"args" : @{
@"handle" : @(handle),
},
};
std::string result_as_string = facade()->HandleMojoMessage(GetJson(close));
EXPECT_FALSE(result_as_string.empty());
int result = 0;
EXPECT_TRUE(base::StringToInt(result_as_string, &result));
EXPECT_EQ(MOJO_RESULT_OK, static_cast<MojoResult>(result));
}
// Tests creating a message pipe without options.
TEST_F(MojoFacadeTest, CreateMessagePipeWithoutOptions) {
// Create a message pipe.
NSDictionary* create = @{
@"name" : @"core.createMessagePipe",
@"args" : @{
@"optionsDict" : [NSNull null],
},
};
std::string response_as_string = facade()->HandleMojoMessage(GetJson(create));
// Verify handles.
EXPECT_FALSE(response_as_string.empty());
NSDictionary* response_as_dict = GetObject(response_as_string);
EXPECT_TRUE([response_as_dict isKindOfClass:[NSDictionary class]]);
id handle0 = response_as_dict[@"handle0"];
EXPECT_TRUE(handle0);
id handle1 = response_as_dict[@"handle1"];
EXPECT_TRUE(handle1);
// Close handle0.
NSDictionary* close0 = @{
@"name" : @"core.close",
@"args" : @{
@"handle" : handle0,
},
};
std::string result0_as_string = facade()->HandleMojoMessage(GetJson(close0));
EXPECT_FALSE(result0_as_string.empty());
int result0 = 0;
EXPECT_TRUE(base::StringToInt(result0_as_string, &result0));
EXPECT_EQ(MOJO_RESULT_OK, static_cast<MojoResult>(result0));
// Close handle1.
NSDictionary* close1 = @{
@"name" : @"core.close",
@"args" : @{
@"handle" : handle1,
},
};
std::string result1_as_string = facade()->HandleMojoMessage(GetJson(close1));
EXPECT_FALSE(result1_as_string.empty());
int result1 = 0;
EXPECT_TRUE(base::StringToInt(result1_as_string, &result1));
EXPECT_EQ(MOJO_RESULT_OK, static_cast<MojoResult>(result1));
}
// Tests watching the pipe.
TEST_F(MojoFacadeTest, Watch) {
// Create a message pipe.
NSDictionary* create = @{
@"name" : @"core.createMessagePipe",
@"args" : @{
@"optionsDict" : [NSNull null],
},
};
std::string response_as_string = facade()->HandleMojoMessage(GetJson(create));
// Verify handles.
EXPECT_FALSE(response_as_string.empty());
NSDictionary* response_as_dict = GetObject(response_as_string);
EXPECT_TRUE([response_as_dict isKindOfClass:[NSDictionary class]]);
id handle0 = response_as_dict[@"handle0"];
EXPECT_TRUE(handle0);
id handle1 = response_as_dict[@"handle1"];
EXPECT_TRUE(handle1);
// Start watching one end of the pipe.
int callback_id = 99;
NSDictionary* watch = @{
@"name" : @"support.watch",
@"args" : @{
@"handle" : handle0,
@"signals" : @(MOJO_HANDLE_SIGNAL_READABLE),
@"callbackId" : @(callback_id),
},
};
std::string watch_id_as_string = facade()->HandleMojoMessage(GetJson(watch));
EXPECT_FALSE(watch_id_as_string.empty());
int watch_id = 0;
EXPECT_TRUE(base::StringToInt(watch_id_as_string, &watch_id));
// Start waiting for the watch callback.
__block bool callback_received = false;
NSString* expected_script =
[NSString stringWithFormat:@"__crWeb.mojo.signalWatch(%d, %d)",
callback_id, MOJO_RESULT_OK];
[[[evaluator() expect] andDo:^(NSInvocation*) {
callback_received = true;
}] executeJavaScript:expected_script completionHandler:nil];
// Write to the other end of the pipe.
NSDictionary* write = @{
@"name" : @"core.writeMessage",
@"args" : @{
@"handle" : handle1,
@"handles" : @[],
@"flags" : @(MOJO_WRITE_MESSAGE_FLAG_NONE),
@"buffer" : @{@"0" : @0}
},
};
std::string result_as_string = facade()->HandleMojoMessage(GetJson(write));
EXPECT_FALSE(result_as_string.empty());
int result = 0;
EXPECT_TRUE(base::StringToInt(result_as_string, &result));
EXPECT_EQ(MOJO_RESULT_OK, static_cast<MojoResult>(result));
base::test::ios::WaitUntilCondition(^{
return callback_received;
}, base::MessageLoop::current(), base::TimeDelta());
}
// Tests reading the message from the pipe.
TEST_F(MojoFacadeTest, ReadWrite) {
// Create a message pipe.
NSDictionary* create = @{
@"name" : @"core.createMessagePipe",
@"args" : @{
@"optionsDict" : [NSNull null],
},
};
std::string response_as_string = facade()->HandleMojoMessage(GetJson(create));
// Verify handles.
EXPECT_FALSE(response_as_string.empty());
NSDictionary* response_as_dict = GetObject(response_as_string);
EXPECT_TRUE([response_as_dict isKindOfClass:[NSDictionary class]]);
id handle0 = response_as_dict[@"handle0"];
EXPECT_TRUE(handle0);
id handle1 = response_as_dict[@"handle1"];
EXPECT_TRUE(handle1);
// Write to the other end of the pipe.
NSDictionary* write = @{
@"name" : @"core.writeMessage",
@"args" : @{
@"handle" : handle1,
@"handles" : @[],
@"flags" : @(MOJO_WRITE_MESSAGE_FLAG_NONE),
@"buffer" : @{@"0" : @9, @"1" : @2, @"2" : @2008}
},
};
std::string result_as_string = facade()->HandleMojoMessage(GetJson(write));
EXPECT_FALSE(result_as_string.empty());
int result = 0;
EXPECT_TRUE(base::StringToInt(result_as_string, &result));
EXPECT_EQ(MOJO_RESULT_OK, static_cast<MojoResult>(result));
// Read the message from the pipe.
NSDictionary* read = @{
@"name" : @"core.readMessage",
@"args" : @{
@"handle" : handle0,
@"flags" : @(MOJO_READ_MESSAGE_FLAG_NONE),
},
};
NSDictionary* message = GetObject(facade()->HandleMojoMessage(GetJson(read)));
EXPECT_TRUE([message isKindOfClass:[NSDictionary class]]);
EXPECT_TRUE(message);
NSArray* expected_message = @[ @9, @2, @216 ]; // 2008 does not fit 8-bit.
EXPECT_NSEQ(expected_message, message[@"buffer"]);
EXPECT_FALSE([message[@"handles"] count]);
EXPECT_EQ(MOJO_RESULT_OK, [message[@"result"] unsignedIntValue]);
}
} // namespace web