Unify most of the WebKit Objective-C API sources
[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 struct PendingReply {
55     PendingReply() = default;
56
57     PendingReply(_WKRemoteObjectInterface* interface, SEL selector, id block)
58         : interface(interface)
59         , selector(selector)
60         , block(adoptNS([block copy]))
61     {
62     }
63
64     RetainPtr<_WKRemoteObjectInterface> interface;
65     SEL selector { nullptr };
66     RetainPtr<id> block;
67 };
68
69 @implementation _WKRemoteObjectRegistry {
70     std::unique_ptr<WebKit::RemoteObjectRegistry> _remoteObjectRegistry;
71
72     RetainPtr<NSMapTable> _remoteObjectProxies;
73     HashMap<String, std::pair<RetainPtr<id>, RetainPtr<_WKRemoteObjectInterface>>> _exportedObjects;
74
75     HashMap<uint64_t, PendingReply> _pendingReplies;
76 }
77
78 - (void)registerExportedObject:(id)object interface:(_WKRemoteObjectInterface *)interface
79 {
80     ASSERT(!_exportedObjects.contains(interface.identifier));
81     _exportedObjects.add(interface.identifier, std::make_pair<RetainPtr<id>, RetainPtr<_WKRemoteObjectInterface>>(object, interface));
82 }
83
84 - (void)unregisterExportedObject:(id)object interface:(_WKRemoteObjectInterface *)interface
85 {
86     ASSERT(_exportedObjects.get(interface.identifier).first == object);
87     ASSERT(_exportedObjects.get(interface.identifier).second == interface);
88
89     _exportedObjects.remove(interface.identifier);
90 }
91
92 - (id)remoteObjectProxyWithInterface:(_WKRemoteObjectInterface *)interface
93 {
94     if (!_remoteObjectProxies)
95         _remoteObjectProxies = [NSMapTable strongToWeakObjectsMapTable];
96
97     if (id remoteObjectProxy = [_remoteObjectProxies objectForKey:interface.identifier])
98         return remoteObjectProxy;
99
100     RetainPtr<NSString> identifier = adoptNS([interface.identifier copy]);
101     auto remoteObject = adoptNS([[WKRemoteObject alloc] _initWithObjectRegistry:self interface:interface]);
102     [_remoteObjectProxies setObject:remoteObject.get() forKey:identifier.get()];
103
104     return remoteObject.autorelease();
105 }
106
107 - (id)_initWithWebPage:(WebKit::WebPage&)page
108 {
109     if (!(self = [super init]))
110         return nil;
111
112     _remoteObjectRegistry = std::make_unique<WebKit::RemoteObjectRegistry>(self, page);
113
114     return self;
115 }
116
117 - (id)_initWithWebPageProxy:(WebKit::WebPageProxy&)page
118 {
119     if (!(self = [super init]))
120         return nil;
121
122     _remoteObjectRegistry = std::make_unique<WebKit::RemoteObjectRegistry>(self, page);
123
124     return self;
125 }
126
127 - (void)_invalidate
128 {
129     _remoteObjectRegistry = nullptr;
130 }
131
132 static uint64_t generateReplyIdentifier()
133 {
134     static uint64_t identifier;
135
136     return ++identifier;
137 }
138
139 - (void)_sendInvocation:(NSInvocation *)invocation interface:(_WKRemoteObjectInterface *)interface
140 {
141     std::unique_ptr<WebKit::RemoteObjectInvocation::ReplyInfo> replyInfo;
142
143     NSMethodSignature *methodSignature = invocation.methodSignature;
144     for (NSUInteger i = 0, count = methodSignature.numberOfArguments; i < count; ++i) {
145         const char *type = [methodSignature getArgumentTypeAtIndex:i];
146
147         if (strcmp(type, "@?"))
148             continue;
149
150         if (replyInfo)
151             [NSException raise:NSInvalidArgumentException format:@"Only one reply block is allowed per message send. (%s)", sel_getName(invocation.selector)];
152
153         id replyBlock = nullptr;
154         [invocation getArgument:&replyBlock atIndex:i];
155         if (!replyBlock)
156             [NSException raise:NSInvalidArgumentException format:@"A NULL reply block was passed into a message. (%s)", sel_getName(invocation.selector)];
157
158         const char* replyBlockSignature = _Block_signature((__bridge void*)replyBlock);
159
160         if (strcmp([NSMethodSignature signatureWithObjCTypes:replyBlockSignature].methodReturnType, "v"))
161             [NSException raise:NSInvalidArgumentException format:@"Return value of block argument must be 'void'. (%s)", sel_getName(invocation.selector)];
162
163         replyInfo = std::make_unique<WebKit::RemoteObjectInvocation::ReplyInfo>(generateReplyIdentifier(), replyBlockSignature);
164
165         // Replace the block object so we won't try to encode it.
166         id null = nullptr;
167         [invocation setArgument:&null atIndex:i];
168
169         ASSERT(!_pendingReplies.contains(replyInfo->replyID));
170         _pendingReplies.add(replyInfo->replyID, PendingReply(interface, invocation.selector, replyBlock));
171     }
172
173     RetainPtr<WKRemoteObjectEncoder> encoder = adoptNS([[WKRemoteObjectEncoder alloc] init]);
174
175     [encoder encodeObject:invocation forKey:invocationKey];
176
177     if (!_remoteObjectRegistry)
178         return;
179
180     _remoteObjectRegistry->sendInvocation(WebKit::RemoteObjectInvocation(interface.identifier, [encoder rootObjectDictionary], WTFMove(replyInfo)));
181 }
182
183 - (WebKit::RemoteObjectRegistry&)remoteObjectRegistry
184 {
185     return *_remoteObjectRegistry;
186 }
187
188 - (void)_invokeMethod:(const WebKit::RemoteObjectInvocation&)remoteObjectInvocation
189 {
190     auto& interfaceIdentifier = remoteObjectInvocation.interfaceIdentifier();
191     auto* encodedInvocation = remoteObjectInvocation.encodedInvocation();
192
193     auto interfaceAndObject = _exportedObjects.get(interfaceIdentifier);
194     if (!interfaceAndObject.second) {
195         NSLog(@"Did not find a registered object for the interface \"%@\"", (NSString *)interfaceIdentifier);
196         return;
197     }
198
199     RetainPtr<_WKRemoteObjectInterface> interface = interfaceAndObject.second;
200
201     auto decoder = adoptNS([[WKRemoteObjectDecoder alloc] initWithInterface:interface.get() rootObjectDictionary:encodedInvocation replyToSelector:nullptr]);
202
203     NSInvocation *invocation = nil;
204
205     @try {
206         invocation = [decoder decodeObjectOfClass:[NSInvocation class] forKey:invocationKey];
207     } @catch (NSException *exception) {
208         NSLog(@"Exception caught during decoding of message: %@", exception);
209     }
210
211     if (auto* replyInfo = remoteObjectInvocation.replyInfo()) {
212         NSMethodSignature *methodSignature = invocation.methodSignature;
213
214         // Look for the block argument.
215         for (NSUInteger i = 0, count = methodSignature.numberOfArguments; i < count; ++i) {
216             const char *type = [methodSignature getArgumentTypeAtIndex:i];
217
218             if (strcmp(type, "@?"))
219                 continue;
220
221             // We found the block.
222             // FIXME: Validate the signature.
223             NSMethodSignature *wireBlockSignature = [NSMethodSignature signatureWithObjCTypes:replyInfo->blockSignature.utf8().data()];
224
225             RetainPtr<_WKRemoteObjectRegistry> remoteObjectRegistry = self;
226             uint64_t replyID = replyInfo->replyID;
227
228             class ReplyBlockCallChecker : public WTF::ThreadSafeRefCounted<ReplyBlockCallChecker> {
229             public:
230                 static Ref<ReplyBlockCallChecker> create(_WKRemoteObjectRegistry *registry, uint64_t replyID) { return adoptRef(*new ReplyBlockCallChecker(registry, replyID)); }
231
232                 ~ReplyBlockCallChecker()
233                 {
234                     if (m_didCallReplyBlock)
235                         return;
236
237                     // FIXME: Instead of not sending anything when the remote object registry is null, we should
238                     // keep track of all reply block checkers and invalidate them (sending the unused reply message) in
239                     // -[_WKRemoteObjectRegistry _invalidate].
240                     if (!m_remoteObjectRegistry->_remoteObjectRegistry)
241                         return;
242
243                     m_remoteObjectRegistry->_remoteObjectRegistry->sendUnusedReply(m_replyID);
244                 }
245
246                 void didCallReplyBlock() { m_didCallReplyBlock = true; }
247
248             private:
249                 ReplyBlockCallChecker(_WKRemoteObjectRegistry *registry, uint64_t replyID)
250                     : m_remoteObjectRegistry(registry)
251                     , m_replyID(replyID)
252                 {
253                 }
254
255                 RetainPtr<_WKRemoteObjectRegistry> m_remoteObjectRegistry;
256                 uint64_t m_replyID = 0;
257                 bool m_didCallReplyBlock = false;
258             };
259
260             RefPtr<ReplyBlockCallChecker> checker = ReplyBlockCallChecker::create(self, replyID);
261             id replyBlock = __NSMakeSpecialForwardingCaptureBlock(wireBlockSignature._typeString.UTF8String, [interface, remoteObjectRegistry, replyID, checker](NSInvocation *invocation) {
262                 auto encoder = adoptNS([[WKRemoteObjectEncoder alloc] init]);
263                 [encoder encodeObject:invocation forKey:invocationKey];
264
265                 remoteObjectRegistry->_remoteObjectRegistry->sendReplyBlock(replyID, WebKit::UserData([encoder rootObjectDictionary]));
266                 checker->didCallReplyBlock();
267             });
268
269             [invocation setArgument:&replyBlock atIndex:i];
270
271             // Make sure that the block won't be destroyed before the invocation.
272             objc_setAssociatedObject(invocation, replyBlockKey, replyBlock, OBJC_ASSOCIATION_RETAIN);
273             [replyBlock release];
274
275             break;
276         }
277     }
278
279     invocation.target = interfaceAndObject.first.get();
280
281     @try {
282         [invocation invoke];
283     } @catch (NSException *exception) {
284         NSLog(@"%@: Warning: Exception caught during invocation of received message, dropping incoming message .\nException: %@", self, exception);
285     }
286 }
287
288 - (void)_callReplyWithID:(uint64_t)replyID blockInvocation:(const WebKit::UserData&)blockInvocation
289 {
290     auto encodedInvocation = blockInvocation.object();
291     if (!encodedInvocation || encodedInvocation->type() != API::Object::Type::Dictionary)
292         return;
293
294     auto it = _pendingReplies.find(replyID);
295     if (it == _pendingReplies.end())
296         return;
297
298     auto pendingReply = it->value;
299     _pendingReplies.remove(it);
300
301     auto decoder = adoptNS([[WKRemoteObjectDecoder alloc] initWithInterface:pendingReply.interface.get() rootObjectDictionary:static_cast<API::Dictionary*>(encodedInvocation) replyToSelector:pendingReply.selector]);
302
303     NSInvocation *replyInvocation = nil;
304
305     @try {
306         replyInvocation = [decoder decodeObjectOfClass:[NSInvocation class] forKey:invocationKey];
307     } @catch (NSException *exception) {
308         NSLog(@"Exception caught during decoding of reply: %@", exception);
309         return;
310     }
311
312     [replyInvocation setTarget:pendingReply.block.get()];
313     [replyInvocation invoke];
314 }
315
316 - (void)_releaseReplyWithID:(uint64_t)replyID
317 {
318     _pendingReplies.remove(replyID);
319 }
320
321 @end
322
323 #endif // WK_API_ENABLED