Convert some JSC code over to std::mutex
[WebKit.git] / Source / JavaScriptCore / inspector / remote / RemoteInspector.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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "RemoteInspector.h"
28
29 #if ENABLE(REMOTE_INSPECTOR)
30
31 #import "InitializeThreading.h"
32 #import "RemoteInspectorConstants.h"
33 #import "RemoteInspectorDebuggable.h"
34 #import "RemoteInspectorDebuggableConnection.h"
35 #import <Foundation/Foundation.h>
36 #import <notify.h>
37 #import <wtf/NeverDestroyed.h>
38 #import <wtf/Assertions.h>
39 #import <wtf/MainThread.h>
40 #import <wtf/text/WTFString.h>
41 #import <xpc/xpc.h>
42
43 #if PLATFORM(IOS)
44 #import <wtf/ios/WebCoreThread.h>
45 #endif
46
47 namespace Inspector {
48
49 static void dispatchAsyncOnQueueSafeForAnyDebuggable(void (^block)())
50 {
51 #if PLATFORM(IOS)
52     if (WebCoreWebThreadIsEnabled && WebCoreWebThreadIsEnabled()) {
53         WebCoreWebThreadRun(block);
54         return;
55     }
56 #endif
57
58     dispatch_async(dispatch_get_main_queue(), block);
59 }
60
61 bool RemoteInspector::startEnabled = true;
62
63 void RemoteInspector::startDisabled()
64 {
65     RemoteInspector::startEnabled = false;
66 }
67
68 RemoteInspector& RemoteInspector::shared()
69 {
70     static NeverDestroyed<RemoteInspector> shared;
71
72     static dispatch_once_t once;
73     dispatch_once(&once, ^{
74         JSC::initializeThreading();
75         WTF::initializeMainThread();
76         if (RemoteInspector::startEnabled)
77             shared.get().start();
78     });
79
80     return shared;
81 }
82
83 RemoteInspector::RemoteInspector()
84     : m_xpcQueue(dispatch_queue_create("com.apple.JavaScriptCore.remote-inspector-xpc", DISPATCH_QUEUE_SERIAL))
85     , m_nextAvailableIdentifier(1)
86     , m_notifyToken(0)
87     , m_enabled(false)
88     , m_hasActiveDebugSession(false)
89     , m_pushScheduled(false)
90 {
91 }
92
93 unsigned RemoteInspector::nextAvailableIdentifier()
94 {
95     unsigned nextValidIdentifier;
96     do {
97         nextValidIdentifier = m_nextAvailableIdentifier++;
98     } while (!nextValidIdentifier || nextValidIdentifier == std::numeric_limits<unsigned>::max() || m_debuggableMap.contains(nextValidIdentifier));
99     return nextValidIdentifier;
100 }
101
102 void RemoteInspector::registerDebuggable(RemoteInspectorDebuggable* debuggable)
103 {
104     std::lock_guard<std::mutex> lock(m_mutex);
105
106     unsigned identifier = nextAvailableIdentifier();
107     debuggable->setIdentifier(identifier);
108
109     auto result = m_debuggableMap.set(identifier, std::make_pair(debuggable, debuggable->info()));
110     ASSERT_UNUSED(result, result.isNewEntry);
111
112     if (debuggable->remoteDebuggingAllowed())
113         pushListingSoon();
114 }
115
116 void RemoteInspector::unregisterDebuggable(RemoteInspectorDebuggable* debuggable)
117 {
118     std::lock_guard<std::mutex> lock(m_mutex);
119
120     unsigned identifier = debuggable->identifier();
121     if (!identifier)
122         return;
123
124     bool wasRemoved = m_debuggableMap.remove(identifier);
125     ASSERT_UNUSED(wasRemoved, wasRemoved);
126
127     if (RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.take(identifier))
128         connection->closeFromDebuggable();
129
130     if (debuggable->remoteDebuggingAllowed())
131         pushListingSoon();
132 }
133
134 void RemoteInspector::updateDebuggable(RemoteInspectorDebuggable* debuggable)
135 {
136     std::lock_guard<std::mutex> lock(m_mutex);
137
138     unsigned identifier = debuggable->identifier();
139     if (!identifier)
140         return;
141
142     auto result = m_debuggableMap.set(identifier, std::make_pair(debuggable, debuggable->info()));
143     ASSERT_UNUSED(result, !result.isNewEntry);
144
145     pushListingSoon();
146 }
147
148 void RemoteInspector::sendMessageToRemoteFrontend(unsigned identifier, const String& message)
149 {
150     std::lock_guard<std::mutex> lock(m_mutex);
151
152     if (!m_xpcConnection)
153         return;
154
155     RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(identifier);
156     if (!connection)
157         return;
158
159     NSDictionary *userInfo = @{
160         WIRRawDataKey: [static_cast<NSString *>(message) dataUsingEncoding:NSUTF8StringEncoding],
161         WIRConnectionIdentifierKey: connection->connectionIdentifier(),
162         WIRDestinationKey: connection->destination()
163     };
164
165     m_xpcConnection->sendMessage(WIRRawDataMessage, userInfo);
166 }
167
168 void RemoteInspector::start()
169 {
170     std::lock_guard<std::mutex> lock(m_mutex);
171
172     if (m_enabled)
173         return;
174
175     m_enabled = true;
176
177     notify_register_dispatch(WIRServiceAvailableNotification, &m_notifyToken, m_xpcQueue, ^(int) {
178         RemoteInspector::shared().setupXPCConnectionIfNeeded();
179     });
180
181     notify_post(WIRServiceAvailabilityCheckNotification);
182 }
183
184 void RemoteInspector::stop()
185 {
186     std::lock_guard<std::mutex> lock(m_mutex);
187
188     if (!m_enabled)
189         return;
190
191     m_enabled = false;
192
193     m_pushScheduled = false;
194
195     for (auto it = m_connectionMap.begin(), end = m_connectionMap.end(); it != end; ++it)
196         it->value->close();
197     m_connectionMap.clear();
198
199     updateHasActiveDebugSession();
200
201     if (m_xpcConnection) {
202         m_xpcConnection->close();
203         m_xpcConnection = nullptr;
204     }
205
206     notify_cancel(m_notifyToken);
207 }
208
209 void RemoteInspector::setupXPCConnectionIfNeeded()
210 {
211     std::lock_guard<std::mutex> lock(m_mutex);
212
213     if (m_xpcConnection)
214         return;
215
216     xpc_connection_t connection = xpc_connection_create_mach_service(WIRXPCMachPortName, m_xpcQueue, 0);
217     if (!connection)
218         return;
219
220     m_xpcConnection = adoptRef(new RemoteInspectorXPCConnection(connection, m_xpcQueue, this));
221     m_xpcConnection->sendMessage(@"syn", nil); // Send a simple message to initialize the XPC connection.
222     xpc_release(connection);
223
224     pushListingSoon();
225 }
226
227 #pragma mark - RemoteInspectorXPCConnection::Client
228
229 void RemoteInspector::xpcConnectionReceivedMessage(RemoteInspectorXPCConnection*, NSString *messageName, NSDictionary *userInfo)
230 {
231     std::lock_guard<std::mutex> lock(m_mutex);
232
233     if ([messageName isEqualToString:WIRPermissionDenied]) {
234         stop();
235         return;
236     }
237
238     if ([messageName isEqualToString:WIRSocketDataMessage])
239         receivedDataMessage(userInfo);
240     else if ([messageName isEqualToString:WIRSocketSetupMessage])
241         receivedSetupMessage(userInfo);
242     else if ([messageName isEqualToString:WIRWebPageCloseMessage])
243         receivedDidCloseMessage(userInfo);
244     else if ([messageName isEqualToString:WIRApplicationGetListingMessage])
245         receivedGetListingMessage(userInfo);
246     else if ([messageName isEqualToString:WIRIndicateMessage])
247         receivedIndicateMessage(userInfo);
248     else if ([messageName isEqualToString:WIRConnectionDiedMessage])
249         receivedConnectionDiedMessage(userInfo);
250     else
251         NSLog(@"Unrecognized RemoteInspector XPC Message: %@", messageName);
252 }
253
254 void RemoteInspector::xpcConnectionFailed(RemoteInspectorXPCConnection* connection)
255 {
256     std::lock_guard<std::mutex> lock(m_mutex);
257     if (connection != m_xpcConnection)
258         return;
259
260     m_pushScheduled = false;
261
262     for (auto it = m_connectionMap.begin(), end = m_connectionMap.end(); it != end; ++it)
263         it->value->close();
264     m_connectionMap.clear();
265
266     updateHasActiveDebugSession();
267
268     if (m_xpcConnection) {
269         m_xpcConnection->close();
270         m_xpcConnection = nullptr;
271     }
272 }
273
274 void RemoteInspector::xpcConnectionUnhandledMessage(RemoteInspectorXPCConnection*, xpc_object_t)
275 {
276     // Intentionally ignored.
277 }
278
279 #pragma mark - Listings
280
281 NSDictionary *RemoteInspector::listingForDebuggable(const RemoteInspectorDebuggableInfo& debuggableInfo) const
282 {
283     NSMutableDictionary *debuggableDetails = [NSMutableDictionary dictionary];
284
285     [debuggableDetails setObject:@(debuggableInfo.identifier) forKey:WIRPageIdentifierKey];
286
287     switch (debuggableInfo.type) {
288     case RemoteInspectorDebuggable::JavaScript: {
289         NSString *name = debuggableInfo.name;
290         [debuggableDetails setObject:name forKey:WIRTitleKey];
291         [debuggableDetails setObject:WIRTypeJavaScript forKey:WIRTypeKey];
292         break;
293     }
294     case RemoteInspectorDebuggable::Web: {
295         NSString *url = debuggableInfo.url;
296         NSString *title = debuggableInfo.name;
297         [debuggableDetails setObject:url forKey:WIRURLKey];
298         [debuggableDetails setObject:title forKey:WIRTitleKey];
299         [debuggableDetails setObject:WIRTypeWeb forKey:WIRTypeKey];
300         break;
301     }
302     default:
303         ASSERT_NOT_REACHED();
304         break;
305     }
306
307     if (RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(debuggableInfo.identifier))
308         [debuggableDetails setObject:connection->connectionIdentifier() forKey:WIRConnectionIdentifierKey];
309
310     if (debuggableInfo.hasLocalDebugger)
311         [debuggableDetails setObject:@YES forKey:WIRHasLocalDebuggerKey];
312
313     if (debuggableInfo.hasParentProcess()) {
314         NSString *parentApplicationIdentifier = [NSString stringWithFormat:@"PID:%lu", (unsigned long)debuggableInfo.parentProcessIdentifier];
315         [debuggableDetails setObject:parentApplicationIdentifier forKey:WIRHostApplicationIdentifierKey];
316     }
317
318     return debuggableDetails;
319 }
320
321 void RemoteInspector::pushListingNow()
322 {
323     ASSERT(m_xpcConnection);
324     if (!m_xpcConnection)
325         return;
326
327     m_pushScheduled = false;
328
329     RetainPtr<NSMutableDictionary> response = adoptNS([[NSMutableDictionary alloc] init]);
330     for (auto it = m_debuggableMap.begin(), end = m_debuggableMap.end(); it != end; ++it) {
331         const RemoteInspectorDebuggableInfo& debuggableInfo = it->value.second;
332         if (debuggableInfo.remoteDebuggingAllowed) {
333             NSDictionary *details = listingForDebuggable(debuggableInfo);
334             [response setObject:details forKey:[NSString stringWithFormat:@"%u", debuggableInfo.identifier]];
335         }
336     }
337
338     RetainPtr<NSMutableDictionary> outgoing = adoptNS([[NSMutableDictionary alloc] init]);
339     [outgoing setObject:response.get() forKey:WIRListingKey];
340
341     m_xpcConnection->sendMessage(WIRListingMessage, outgoing.get());
342 }
343
344 void RemoteInspector::pushListingSoon()
345 {
346     if (!m_xpcConnection)
347         return;
348
349     if (m_pushScheduled)
350         return;
351
352     m_pushScheduled = true;
353     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.02 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
354         std::lock_guard<std::mutex> lock(m_mutex);
355         if (m_pushScheduled)
356             pushListingNow();
357     });
358 }
359
360 #pragma mark - Active Debugger Sessions
361
362 void RemoteInspector::updateHasActiveDebugSession()
363 {
364     bool hasActiveDebuggerSession = !m_connectionMap.isEmpty();
365     if (hasActiveDebuggerSession == m_hasActiveDebugSession)
366         return;
367
368     m_hasActiveDebugSession = hasActiveDebuggerSession;
369
370     // FIXME: Expose some way to access this state in an embedder.
371     // Legacy iOS WebKit 1 had a notification. This will need to be smarter with WebKit2.
372 }
373
374 #pragma mark - Received XPC Messages
375
376 void RemoteInspector::receivedSetupMessage(NSDictionary *userInfo)
377 {
378     NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
379     if (!pageId)
380         return;
381
382     NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey];
383     if (!connectionIdentifier)
384         return;
385
386     NSString *sender = [userInfo objectForKey:WIRSenderKey];
387     if (!sender)
388         return;
389
390     unsigned identifier = [pageId unsignedIntValue];
391     if (m_connectionMap.contains(identifier))
392         return;
393
394     auto it = m_debuggableMap.find(identifier);
395     if (it == m_debuggableMap.end())
396         return;
397
398     // Attempt to create a connection. This may fail if the page already has an inspector or if it disallows inspection.
399     RemoteInspectorDebuggable* debuggable = it->value.first;
400     RemoteInspectorDebuggableInfo debuggableInfo = it->value.second;
401     RefPtr<RemoteInspectorDebuggableConnection> connection = adoptRef(new RemoteInspectorDebuggableConnection(debuggable, connectionIdentifier, sender, debuggableInfo.type));
402     if (!connection->setup()) {
403         connection->close();
404         return;
405     }
406
407     m_connectionMap.set(identifier, connection.release());
408
409     updateHasActiveDebugSession();
410
411     pushListingSoon();
412 }
413
414 void RemoteInspector::receivedDataMessage(NSDictionary *userInfo)
415 {
416     NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
417     if (!pageId)
418         return;
419
420     unsigned pageIdentifier = [pageId unsignedIntValue];
421     RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(pageIdentifier);
422     if (!connection)
423         return;
424
425     NSData *data = [userInfo objectForKey:WIRSocketDataKey];
426     RetainPtr<NSString> message = adoptNS([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
427     connection->sendMessageToBackend(message.get());
428 }
429
430 void RemoteInspector::receivedDidCloseMessage(NSDictionary *userInfo)
431 {
432     NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
433     if (!pageId)
434         return;
435
436     NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey];
437     if (!connectionIdentifier)
438         return;
439
440     unsigned identifier = [pageId unsignedIntValue];
441     RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(identifier);
442     if (!connection)
443         return;
444
445     if (![connectionIdentifier isEqualToString:connection->connectionIdentifier()])
446         return;
447
448     connection->close();
449     m_connectionMap.remove(identifier);
450
451     updateHasActiveDebugSession();
452
453     pushListingSoon();
454 }
455
456 void RemoteInspector::receivedGetListingMessage(NSDictionary *)
457 {
458     pushListingNow();
459 }
460
461 void RemoteInspector::receivedIndicateMessage(NSDictionary *userInfo)
462 {
463     NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
464     if (!pageId)
465         return;
466
467     unsigned identifier = [pageId unsignedIntValue];
468     BOOL indicateEnabled = [[userInfo objectForKey:WIRIndicateEnabledKey] boolValue];
469
470     dispatchAsyncOnQueueSafeForAnyDebuggable(^{
471         std::lock_guard<std::mutex> lock(m_mutex);
472
473         auto it = m_debuggableMap.find(identifier);
474         if (it == m_debuggableMap.end())
475             return;
476
477         RemoteInspectorDebuggable* debuggable = it->value.first;
478         debuggable->setIndicating(indicateEnabled);
479     });
480 }
481
482 void RemoteInspector::receivedConnectionDiedMessage(NSDictionary *userInfo)
483 {
484     NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey];
485     if (!connectionIdentifier)
486         return;
487
488     auto it = m_connectionMap.begin();
489     auto end = m_connectionMap.end();
490     for (; it != end; ++it) {
491         if ([connectionIdentifier isEqualToString:it->value->connectionIdentifier()])
492             break;
493     }
494
495     if (it == end)
496         return;
497
498     RefPtr<RemoteInspectorDebuggableConnection> connection = it->value;
499     connection->close();
500     m_connectionMap.remove(it);
501
502     updateHasActiveDebugSession();
503 }
504
505 } // namespace Inspector
506
507 #endif // ENABLE(REMOTE_INSPECTOR)