2 * Copyright (C) 2013 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "RemoteInspector.h"
29 #if ENABLE(REMOTE_INSPECTOR)
31 #import "InitializeThreading.h"
32 #import "RemoteInspectorConstants.h"
33 #import "RemoteInspectorDebuggable.h"
34 #import "RemoteInspectorDebuggableConnection.h"
35 #import <Foundation/Foundation.h>
37 #import <wtf/Assertions.h>
38 #import <wtf/NeverDestroyed.h>
39 #import <wtf/text/WTFString.h>
43 #import <wtf/ios/WebCoreThread.h>
48 static void dispatchAsyncOnQueueSafeForAnyDebuggable(void (^block)())
51 if (WebCoreWebThreadIsEnabled && WebCoreWebThreadIsEnabled()) {
52 WebCoreWebThreadRun(block);
57 dispatch_async(dispatch_get_main_queue(), block);
60 bool RemoteInspector::startEnabled = true;
62 void RemoteInspector::startDisabled()
64 RemoteInspector::startEnabled = false;
67 RemoteInspector& RemoteInspector::shared()
69 static NeverDestroyed<RemoteInspector> shared;
71 static dispatch_once_t once;
72 dispatch_once(&once, ^{
73 JSC::initializeThreading();
74 if (RemoteInspector::startEnabled)
81 RemoteInspector::RemoteInspector()
82 : m_xpcQueue(dispatch_queue_create("com.apple.JavaScriptCore.remote-inspector-xpc", DISPATCH_QUEUE_SERIAL))
83 , m_nextAvailableIdentifier(1)
86 , m_hasActiveDebugSession(false)
87 , m_pushScheduled(false)
88 , m_parentProcessIdentifier(0)
89 , m_shouldSendParentProcessInformation(false)
93 unsigned RemoteInspector::nextAvailableIdentifier()
95 unsigned nextValidIdentifier;
97 nextValidIdentifier = m_nextAvailableIdentifier++;
98 } while (!nextValidIdentifier || nextValidIdentifier == std::numeric_limits<unsigned>::max() || m_debuggableMap.contains(nextValidIdentifier));
99 return nextValidIdentifier;
102 void RemoteInspector::registerDebuggable(RemoteInspectorDebuggable* debuggable)
104 std::lock_guard<std::mutex> lock(m_mutex);
106 unsigned identifier = nextAvailableIdentifier();
107 debuggable->setIdentifier(identifier);
109 auto result = m_debuggableMap.set(identifier, std::make_pair(debuggable, debuggable->info()));
110 ASSERT_UNUSED(result, result.isNewEntry);
112 if (debuggable->remoteDebuggingAllowed())
116 void RemoteInspector::unregisterDebuggable(RemoteInspectorDebuggable* debuggable)
118 std::lock_guard<std::mutex> lock(m_mutex);
120 unsigned identifier = debuggable->identifier();
124 bool wasRemoved = m_debuggableMap.remove(identifier);
125 ASSERT_UNUSED(wasRemoved, wasRemoved);
127 if (RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.take(identifier))
128 connection->closeFromDebuggable();
130 if (debuggable->remoteDebuggingAllowed())
134 void RemoteInspector::updateDebuggable(RemoteInspectorDebuggable* debuggable)
136 std::lock_guard<std::mutex> lock(m_mutex);
138 unsigned identifier = debuggable->identifier();
142 auto result = m_debuggableMap.set(identifier, std::make_pair(debuggable, debuggable->info()));
143 ASSERT_UNUSED(result, !result.isNewEntry);
148 void RemoteInspector::sendMessageToRemoteFrontend(unsigned identifier, const String& message)
150 std::lock_guard<std::mutex> lock(m_mutex);
152 if (!m_xpcConnection)
155 RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(identifier);
159 NSDictionary *userInfo = @{
160 WIRRawDataKey: [static_cast<NSString *>(message) dataUsingEncoding:NSUTF8StringEncoding],
161 WIRConnectionIdentifierKey: connection->connectionIdentifier(),
162 WIRDestinationKey: connection->destination()
165 m_xpcConnection->sendMessage(WIRRawDataMessage, userInfo);
168 void RemoteInspector::setupFailed(unsigned identifier)
170 std::lock_guard<std::mutex> lock(m_mutex);
172 m_connectionMap.remove(identifier);
174 updateHasActiveDebugSession();
179 void RemoteInspector::start()
181 std::lock_guard<std::mutex> lock(m_mutex);
188 notify_register_dispatch(WIRServiceAvailableNotification, &m_notifyToken, m_xpcQueue, ^(int) {
189 RemoteInspector::shared().setupXPCConnectionIfNeeded();
192 notify_post(WIRServiceAvailabilityCheckNotification);
195 void RemoteInspector::stop()
197 std::lock_guard<std::mutex> lock(m_mutex);
199 stopInternal(StopSource::API);
202 void RemoteInspector::stopInternal(StopSource source)
209 m_pushScheduled = false;
211 for (auto it = m_connectionMap.begin(), end = m_connectionMap.end(); it != end; ++it)
213 m_connectionMap.clear();
215 updateHasActiveDebugSession();
217 if (m_xpcConnection) {
219 case StopSource::API:
220 m_xpcConnection->close();
222 case StopSource::XPCMessage:
223 m_xpcConnection->closeFromMessage();
227 m_xpcConnection = nullptr;
230 notify_cancel(m_notifyToken);
233 void RemoteInspector::setupXPCConnectionIfNeeded()
235 std::lock_guard<std::mutex> lock(m_mutex);
240 xpc_connection_t connection = xpc_connection_create_mach_service(WIRXPCMachPortName, m_xpcQueue, 0);
244 m_xpcConnection = adoptRef(new RemoteInspectorXPCConnection(connection, m_xpcQueue, this));
245 m_xpcConnection->sendMessage(@"syn", nil); // Send a simple message to initialize the XPC connection.
246 xpc_release(connection);
251 #pragma mark - Proxy Application Information
253 void RemoteInspector::setParentProcessInformation(pid_t pid, RetainPtr<CFDataRef> auditData)
255 std::lock_guard<std::mutex> lock(m_mutex);
257 if (m_parentProcessIdentifier || m_parentProcessAuditData)
260 m_parentProcessIdentifier = pid;
261 m_parentProcessAuditData = auditData;
263 if (m_shouldSendParentProcessInformation)
264 receivedProxyApplicationSetupMessage(nil);
267 #pragma mark - RemoteInspectorXPCConnection::Client
269 void RemoteInspector::xpcConnectionReceivedMessage(RemoteInspectorXPCConnection*, NSString *messageName, NSDictionary *userInfo)
271 std::lock_guard<std::mutex> lock(m_mutex);
273 if ([messageName isEqualToString:WIRPermissionDenied]) {
274 stopInternal(StopSource::XPCMessage);
278 if ([messageName isEqualToString:WIRSocketDataMessage])
279 receivedDataMessage(userInfo);
280 else if ([messageName isEqualToString:WIRSocketSetupMessage])
281 receivedSetupMessage(userInfo);
282 else if ([messageName isEqualToString:WIRWebPageCloseMessage])
283 receivedDidCloseMessage(userInfo);
284 else if ([messageName isEqualToString:WIRApplicationGetListingMessage])
285 receivedGetListingMessage(userInfo);
286 else if ([messageName isEqualToString:WIRIndicateMessage])
287 receivedIndicateMessage(userInfo);
288 else if ([messageName isEqualToString:WIRProxyApplicationSetupMessage])
289 receivedProxyApplicationSetupMessage(userInfo);
290 else if ([messageName isEqualToString:WIRConnectionDiedMessage])
291 receivedConnectionDiedMessage(userInfo);
293 NSLog(@"Unrecognized RemoteInspector XPC Message: %@", messageName);
296 void RemoteInspector::xpcConnectionFailed(RemoteInspectorXPCConnection* connection)
298 std::lock_guard<std::mutex> lock(m_mutex);
300 ASSERT(connection == m_xpcConnection);
301 if (connection != m_xpcConnection)
304 m_pushScheduled = false;
306 for (auto it = m_connectionMap.begin(), end = m_connectionMap.end(); it != end; ++it)
308 m_connectionMap.clear();
310 updateHasActiveDebugSession();
312 // The connection will close itself.
313 m_xpcConnection = nullptr;
316 void RemoteInspector::xpcConnectionUnhandledMessage(RemoteInspectorXPCConnection*, xpc_object_t)
318 // Intentionally ignored.
321 #pragma mark - Listings
323 NSDictionary *RemoteInspector::listingForDebuggable(const RemoteInspectorDebuggableInfo& debuggableInfo) const
325 NSMutableDictionary *debuggableDetails = [NSMutableDictionary dictionary];
327 [debuggableDetails setObject:@(debuggableInfo.identifier) forKey:WIRPageIdentifierKey];
329 switch (debuggableInfo.type) {
330 case RemoteInspectorDebuggable::JavaScript: {
331 NSString *name = debuggableInfo.name;
332 [debuggableDetails setObject:name forKey:WIRTitleKey];
333 [debuggableDetails setObject:WIRTypeJavaScript forKey:WIRTypeKey];
336 case RemoteInspectorDebuggable::Web: {
337 NSString *url = debuggableInfo.url;
338 NSString *title = debuggableInfo.name;
339 [debuggableDetails setObject:url forKey:WIRURLKey];
340 [debuggableDetails setObject:title forKey:WIRTitleKey];
341 [debuggableDetails setObject:WIRTypeWeb forKey:WIRTypeKey];
345 ASSERT_NOT_REACHED();
349 if (RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(debuggableInfo.identifier))
350 [debuggableDetails setObject:connection->connectionIdentifier() forKey:WIRConnectionIdentifierKey];
352 if (debuggableInfo.hasLocalDebugger)
353 [debuggableDetails setObject:@YES forKey:WIRHasLocalDebuggerKey];
355 return debuggableDetails;
358 void RemoteInspector::pushListingNow()
360 ASSERT(m_xpcConnection);
361 if (!m_xpcConnection)
364 m_pushScheduled = false;
366 RetainPtr<NSMutableDictionary> response = adoptNS([[NSMutableDictionary alloc] init]);
367 for (auto it = m_debuggableMap.begin(), end = m_debuggableMap.end(); it != end; ++it) {
368 const RemoteInspectorDebuggableInfo& debuggableInfo = it->value.second;
369 if (debuggableInfo.remoteDebuggingAllowed) {
370 NSDictionary *details = listingForDebuggable(debuggableInfo);
371 [response setObject:details forKey:[NSString stringWithFormat:@"%u", debuggableInfo.identifier]];
375 RetainPtr<NSMutableDictionary> outgoing = adoptNS([[NSMutableDictionary alloc] init]);
376 [outgoing setObject:response.get() forKey:WIRListingKey];
378 m_xpcConnection->sendMessage(WIRListingMessage, outgoing.get());
381 void RemoteInspector::pushListingSoon()
383 if (!m_xpcConnection)
389 m_pushScheduled = true;
390 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
391 std::lock_guard<std::mutex> lock(m_mutex);
397 #pragma mark - Active Debugger Sessions
399 void RemoteInspector::updateHasActiveDebugSession()
401 bool hasActiveDebuggerSession = !m_connectionMap.isEmpty();
402 if (hasActiveDebuggerSession == m_hasActiveDebugSession)
405 m_hasActiveDebugSession = hasActiveDebuggerSession;
407 // FIXME: Expose some way to access this state in an embedder.
408 // Legacy iOS WebKit 1 had a notification. This will need to be smarter with WebKit2.
411 #pragma mark - Received XPC Messages
413 void RemoteInspector::receivedSetupMessage(NSDictionary *userInfo)
415 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
419 NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey];
420 if (!connectionIdentifier)
423 NSString *sender = [userInfo objectForKey:WIRSenderKey];
427 unsigned identifier = [pageId unsignedIntValue];
428 if (m_connectionMap.contains(identifier))
431 auto it = m_debuggableMap.find(identifier);
432 if (it == m_debuggableMap.end())
435 // Attempt to create a connection. This may fail if the page already has an inspector or if it disallows inspection.
436 RemoteInspectorDebuggable* debuggable = it->value.first;
437 RemoteInspectorDebuggableInfo debuggableInfo = it->value.second;
438 RefPtr<RemoteInspectorDebuggableConnection> connection = adoptRef(new RemoteInspectorDebuggableConnection(debuggable, connectionIdentifier, sender, debuggableInfo.type));
439 if (!connection->setup()) {
444 m_connectionMap.set(identifier, connection.release());
446 updateHasActiveDebugSession();
451 void RemoteInspector::receivedDataMessage(NSDictionary *userInfo)
453 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
457 unsigned pageIdentifier = [pageId unsignedIntValue];
458 RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(pageIdentifier);
462 NSData *data = [userInfo objectForKey:WIRSocketDataKey];
463 RetainPtr<NSString> message = adoptNS([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
464 connection->sendMessageToBackend(message.get());
467 void RemoteInspector::receivedDidCloseMessage(NSDictionary *userInfo)
469 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
473 NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey];
474 if (!connectionIdentifier)
477 unsigned identifier = [pageId unsignedIntValue];
478 RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(identifier);
482 if (![connectionIdentifier isEqualToString:connection->connectionIdentifier()])
486 m_connectionMap.remove(identifier);
488 updateHasActiveDebugSession();
493 void RemoteInspector::receivedGetListingMessage(NSDictionary *)
498 void RemoteInspector::receivedIndicateMessage(NSDictionary *userInfo)
500 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
504 unsigned identifier = [pageId unsignedIntValue];
505 BOOL indicateEnabled = [[userInfo objectForKey:WIRIndicateEnabledKey] boolValue];
507 dispatchAsyncOnQueueSafeForAnyDebuggable(^{
508 RemoteInspectorDebuggable* debuggable = nullptr;
510 std::lock_guard<std::mutex> lock(m_mutex);
512 auto it = m_debuggableMap.find(identifier);
513 if (it == m_debuggableMap.end())
516 debuggable = it->value.first;
518 debuggable->setIndicating(indicateEnabled);
522 void RemoteInspector::receivedProxyApplicationSetupMessage(NSDictionary *)
524 ASSERT(m_xpcConnection);
525 if (!m_xpcConnection)
528 if (!m_parentProcessIdentifier || !m_parentProcessAuditData) {
529 // We are a proxy application without parent process information.
530 // Wait a bit for the information, but give up after a second.
531 m_shouldSendParentProcessInformation = true;
532 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
533 std::lock_guard<std::mutex> lock(m_mutex);
534 if (m_shouldSendParentProcessInformation)
535 stopInternal(StopSource::XPCMessage);
540 m_shouldSendParentProcessInformation = false;
542 m_xpcConnection->sendMessage(WIRProxyApplicationSetupResponseMessage, @{
543 WIRProxyApplicationParentPIDKey: @(m_parentProcessIdentifier),
544 WIRProxyApplicationParentAuditDataKey: (NSData *)m_parentProcessAuditData.get(),
548 void RemoteInspector::receivedConnectionDiedMessage(NSDictionary *userInfo)
550 NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey];
551 if (!connectionIdentifier)
554 auto it = m_connectionMap.begin();
555 auto end = m_connectionMap.end();
556 for (; it != end; ++it) {
557 if ([connectionIdentifier isEqualToString:it->value->connectionIdentifier()])
564 RefPtr<RemoteInspectorDebuggableConnection> connection = it->value;
566 m_connectionMap.remove(it);
568 updateHasActiveDebugSession();
571 } // namespace Inspector
573 #endif // ENABLE(REMOTE_INSPECTOR)