b64a33bd1bd16a37c8610ea22955d56ffb439cd8
[WebKit-https.git] / Source / WebKit / Shared / API / Cocoa / _WKRemoteObjectRegistry.mm
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "_WKRemoteObjectRegistryInternal.h"
28
29 #if WK_API_ENABLED
30
31 #import "APIDictionary.h"
32 #import "BlockSPI.h"
33 #import "Connection.h"
34 #import "RemoteObjectInvocation.h"
35 #import "RemoteObjectRegistry.h"
36 #import "UserData.h"
37 #import "WKConnectionRef.h"
38 #import "WKRemoteObject.h"
39 #import "WKRemoteObjectCoder.h"
40 #import "WKSharedAPICast.h"
41 #import "_WKRemoteObjectInterface.h"
42 #import <objc/runtime.h>
43
44 extern "C" id __NSMakeSpecialForwardingCaptureBlock(const char *signature, void (^handler)(NSInvocation *inv));
45
46 static const void* replyBlockKey = &replyBlockKey;
47
48 @interface NSMethodSignature ()
49 - (NSString *)_typeString;
50 @end
51
52 NSString * const invocationKey = @"invocation";
53
54 using namespace WebKit;
55
56 struct PendingReply {
57     PendingReply() = default;
58
59     PendingReply(_WKRemoteObjectInterface* interface, SEL selector, id block)
60         : interface(interface)
61         , selector(selector)
62         , block(adoptNS([block copy]))
63     {
64     }
65
66     RetainPtr<_WKRemoteObjectInterface> interface;
67     SEL selector { nullptr };
68     RetainPtr<id> block;
69 };
70
71 @implementation _WKRemoteObjectRegistry {
72     std::unique_ptr<RemoteObjectRegistry> _remoteObjectRegistry;
73
74     RetainPtr<NSMapTable> _remoteObjectProxies;
75     HashMap<String, std::pair<RetainPtr<id>, RetainPtr<_WKRemoteObjectInterface>>> _exportedObjects;
76
77     HashMap<uint64_t, PendingReply> _pendingReplies;
78 }
79
80 - (void)registerExportedObject:(id)object interface:(_WKRemoteObjectInterface *)interface
81 {
82     ASSERT(!_exportedObjects.contains(interface.identifier));
83     _exportedObjects.add(interface.identifier, std::make_pair<RetainPtr<id>, RetainPtr<_WKRemoteObjectInterface>>(object, interface));
84 }
85
86 - (void)unregisterExportedObject:(id)object interface:(_WKRemoteObjectInterface *)interface
87 {
88     ASSERT(_exportedObjects.get(interface.identifier).first == object);
89     ASSERT(_exportedObjects.get(interface.identifier).second == interface);
90
91     _exportedObjects.remove(interface.identifier);
92 }
93
94 - (id)remoteObjectProxyWithInterface:(_WKRemoteObjectInterface *)interface
95 {
96     if (!_remoteObjectProxies)
97         _remoteObjectProxies = [NSMapTable strongToWeakObjectsMapTable];
98
99     if (id remoteObjectProxy = [_remoteObjectProxies objectForKey:interface.identifier])
100         return remoteObjectProxy;
101
102     RetainPtr<NSString> identifier = adoptNS([interface.identifier copy]);
103     auto remoteObject = adoptNS([[WKRemoteObject alloc] _initWithObjectRegistry:self interface:interface]);
104     [_remoteObjectProxies setObject:remoteObject.get() forKey:identifier.get()];
105
106     return remoteObject.autorelease();
107 }
108
109 - (id)_initWithWebPage:(WebKit::WebPage&)page
110 {
111     if (!(self = [super init]))
112         return nil;
113
114     _remoteObjectRegistry = std::make_unique<RemoteObjectRegistry>(self, page);
115
116     return self;
117 }
118
119 - (id)_initWithWebPageProxy:(WebKit::WebPageProxy&)page
120 {
121     if (!(self = [super init]))
122         return nil;
123
124     _remoteObjectRegistry = std::make_unique<RemoteObjectRegistry>(self, page);
125
126     return self;
127 }
128
129 - (void)_invalidate
130 {
131     _remoteObjectRegistry = nullptr;
132 }
133
134 static uint64_t generateReplyIdentifier()
135 {
136     static uint64_t identifier;
137
138     return ++identifier;
139 }
140
141 - (void)_sendInvocation:(NSInvocation *)invocation interface:(_WKRemoteObjectInterface *)interface
142 {
143     std::unique_ptr<RemoteObjectInvocation::ReplyInfo> replyInfo;
144
145     NSMethodSignature *methodSignature = invocation.methodSignature;
146     for (NSUInteger i = 0, count = methodSignature.numberOfArguments; i < count; ++i) {
147         const char *type = [methodSignature getArgumentTypeAtIndex:i];
148
149         if (strcmp(type, "@?"))
150             continue;
151
152         if (replyInfo)
153             [NSException raise:NSInvalidArgumentException format:@"Only one reply block is allowed per message send. (%s)", sel_getName(invocation.selector)];
154
155         id replyBlock = nullptr;
156         [invocation getArgument:&replyBlock atIndex:i];
157         if (!replyBlock)
158             [NSException raise:NSInvalidArgumentException format:@"A NULL reply block was passed into a message. (%s)", sel_getName(invocation.selector)];
159
160         const char* replyBlockSignature = _Block_signature((__bridge void*)replyBlock);
161
162         if (strcmp([NSMethodSignature signatureWithObjCTypes:replyBlockSignature].methodReturnType, "v"))
163             [NSException raise:NSInvalidArgumentException format:@"Return value of block argument must be 'void'. (%s)", sel_getName(invocation.selector)];
164
165         replyInfo = std::make_unique<RemoteObjectInvocation::ReplyInfo>(generateReplyIdentifier(), replyBlockSignature);
166
167         // Replace the block object so we won't try to encode it.
168         id null = nullptr;
169         [invocation setArgument:&null atIndex:i];
170
171         ASSERT(!_pendingReplies.contains(replyInfo->replyID));
172         _pendingReplies.add(replyInfo->replyID, PendingReply(interface, invocation.selector, replyBlock));
173     }
174
175     RetainPtr<WKRemoteObjectEncoder> encoder = adoptNS([[WKRemoteObjectEncoder alloc] init]);
176
177     [encoder encodeObject:invocation forKey:invocationKey];
178
179     if (!_remoteObjectRegistry)
180         return;
181
182     _remoteObjectRegistry->sendInvocation(RemoteObjectInvocation(interface.identifier, [encoder rootObjectDictionary], WTFMove(replyInfo)));
183 }
184
185 - (WebKit::RemoteObjectRegistry&)remoteObjectRegistry
186 {
187     return *_remoteObjectRegistry;
188 }
189
190 - (void)_invokeMethod:(const RemoteObjectInvocation&)remoteObjectInvocation
191 {
192     auto& interfaceIdentifier = remoteObjectInvocation.interfaceIdentifier();
193     auto* encodedInvocation = remoteObjectInvocation.encodedInvocation();
194
195     auto interfaceAndObject = _exportedObjects.get(interfaceIdentifier);
196     if (!interfaceAndObject.second) {
197         NSLog(@"Did not find a registered object for the interface \"%@\"", (NSString *)interfaceIdentifier);
198         return;
199     }
200
201     RetainPtr<_WKRemoteObjectInterface> interface = interfaceAndObject.second;
202
203     auto decoder = adoptNS([[WKRemoteObjectDecoder alloc] initWithInterface:interface.get() rootObjectDictionary:encodedInvocation replyToSelector:nullptr]);
204
205     NSInvocation *invocation = nil;
206
207     @try {
208         invocation = [decoder decodeObjectOfClass:[NSInvocation class] forKey:invocationKey];
209     } @catch (NSException *exception) {
210         NSLog(@"Exception caught during decoding of message: %@", exception);
211     }
212
213     if (auto* replyInfo = remoteObjectInvocation.replyInfo()) {
214         NSMethodSignature *methodSignature = invocation.methodSignature;
215
216         // Look for the block argument.
217         for (NSUInteger i = 0, count = methodSignature.numberOfArguments; i < count; ++i) {
218             const char *type = [methodSignature getArgumentTypeAtIndex:i];
219
220             if (strcmp(type, "@?"))
221                 continue;
222
223             // We found the block.
224             // FIXME: Validate the signature.
225             NSMethodSignature *wireBlockSignature = [NSMethodSignature signatureWithObjCTypes:replyInfo->blockSignature.utf8().data()];
226
227             RetainPtr<_WKRemoteObjectRegistry> remoteObjectRegistry = self;
228             uint64_t replyID = replyInfo->replyID;
229
230             class ReplyBlockCallChecker : public WTF::ThreadSafeRefCounted<ReplyBlockCallChecker> {
231             public:
232                 static Ref<ReplyBlockCallChecker> create(_WKRemoteObjectRegistry *registry, uint64_t replyID) { return adoptRef(*new ReplyBlockCallChecker(registry, replyID)); }
233
234                 ~ReplyBlockCallChecker()
235                 {
236                     if (m_didCallReplyBlock)
237                         return;
238
239                     // FIXME: Instead of not sending anything when the remote object registry is null, we should
240                     // keep track of all reply block checkers and invalidate them (sending the unused reply message) in
241                     // -[_WKRemoteObjectRegistry _invalidate].
242                     if (!m_remoteObjectRegistry->_remoteObjectRegistry)
243                         return;
244
245                     m_remoteObjectRegistry->_remoteObjectRegistry->sendUnusedReply(m_replyID);
246                 }
247
248                 void didCallReplyBlock() { m_didCallReplyBlock = true; }
249
250             private:
251                 ReplyBlockCallChecker(_WKRemoteObjectRegistry *registry, uint64_t replyID)
252                     : m_remoteObjectRegistry(registry)
253                     , m_replyID(replyID)
254                 {
255                 }
256
257                 RetainPtr<_WKRemoteObjectRegistry> m_remoteObjectRegistry;
258                 uint64_t m_replyID = 0;
259                 bool m_didCallReplyBlock = false;
260             };
261
262             RefPtr<ReplyBlockCallChecker> checker = ReplyBlockCallChecker::create(self, replyID);
263             id replyBlock = __NSMakeSpecialForwardingCaptureBlock(wireBlockSignature._typeString.UTF8String, [interface, remoteObjectRegistry, replyID, checker](NSInvocation *invocation) {
264                 auto encoder = adoptNS([[WKRemoteObjectEncoder alloc] init]);
265                 [encoder encodeObject:invocation forKey:invocationKey];
266
267                 remoteObjectRegistry->_remoteObjectRegistry->sendReplyBlock(replyID, UserData([encoder rootObjectDictionary]));
268                 checker->didCallReplyBlock();
269             });
270
271             [invocation setArgument:&replyBlock atIndex:i];
272
273             // Make sure that the block won't be destroyed before the invocation.
274             objc_setAssociatedObject(invocation, replyBlockKey, replyBlock, OBJC_ASSOCIATION_RETAIN);
275             [replyBlock release];
276
277             break;
278         }
279     }
280
281     invocation.target = interfaceAndObject.first.get();
282
283     @try {
284         [invocation invoke];
285     } @catch (NSException *exception) {
286         NSLog(@"%@: Warning: Exception caught during invocation of received message, dropping incoming message .\nException: %@", self, exception);
287     }
288 }
289
290 - (void)_callReplyWithID:(uint64_t)replyID blockInvocation:(const WebKit::UserData&)blockInvocation
291 {
292     auto encodedInvocation = blockInvocation.object();
293     if (!encodedInvocation || encodedInvocation->type() != API::Object::Type::Dictionary)
294         return;
295
296     auto it = _pendingReplies.find(replyID);
297     if (it == _pendingReplies.end())
298         return;
299
300     auto pendingReply = it->value;
301     _pendingReplies.remove(it);
302
303     auto decoder = adoptNS([[WKRemoteObjectDecoder alloc] initWithInterface:pendingReply.interface.get() rootObjectDictionary:static_cast<API::Dictionary*>(encodedInvocation) replyToSelector:pendingReply.selector]);
304
305     NSInvocation *replyInvocation = nil;
306
307     @try {
308         replyInvocation = [decoder decodeObjectOfClass:[NSInvocation class] forKey:invocationKey];
309     } @catch (NSException *exception) {
310         NSLog(@"Exception caught during decoding of reply: %@", exception);
311         return;
312     }
313
314     [replyInvocation setTarget:pendingReply.block.get()];
315     [replyInvocation invoke];
316 }
317
318 - (void)_releaseReplyWithID:(uint64_t)replyID
319 {
320     _pendingReplies.remove(replyID);
321 }
322
323 @end
324
325 #endif // WK_API_ENABLED