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/Assertions.h> 38#import <wtf/NeverDestroyed.h> 39#import <wtf/text/WTFString.h> 40#import <xpc/xpc.h> 41 42#if PLATFORM(IOS) 43#import <wtf/ios/WebCoreThread.h> 44#endif 45 46namespace Inspector { 47 48static void dispatchAsyncOnQueueSafeForAnyDebuggable(void (^block)()) 49{ 50#if PLATFORM(IOS) 51 if (WebCoreWebThreadIsEnabled && WebCoreWebThreadIsEnabled()) { 52 WebCoreWebThreadRun(block); 53 return; 54 } 55#endif 56 57 dispatch_async(dispatch_get_main_queue(), block); 58} 59 60bool RemoteInspector::startEnabled = true; 61 62void RemoteInspector::startDisabled() 63{ 64 RemoteInspector::startEnabled = false; 65} 66 67RemoteInspector& RemoteInspector::shared() 68{ 69 static NeverDestroyed<RemoteInspector> shared; 70 71 static dispatch_once_t once; 72 dispatch_once(&once, ^{ 73 JSC::initializeThreading(); 74 if (RemoteInspector::startEnabled) 75 shared.get().start(); 76 }); 77 78 return shared; 79} 80 81RemoteInspector::RemoteInspector() 82 : m_xpcQueue(dispatch_queue_create("com.apple.JavaScriptCore.remote-inspector-xpc", DISPATCH_QUEUE_SERIAL)) 83 , m_nextAvailableIdentifier(1) 84 , m_notifyToken(0) 85 , m_enabled(false) 86 , m_hasActiveDebugSession(false) 87 , m_pushScheduled(false) 88 , m_parentProcessIdentifier(0) 89 , m_shouldSendParentProcessInformation(false) 90{ 91} 92 93unsigned 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 102void 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 116void 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 134void 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 148void 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 168void RemoteInspector::setupFailed(unsigned identifier) 169{ 170 std::lock_guard<std::mutex> lock(m_mutex); 171 172 m_connectionMap.remove(identifier); 173 174 updateHasActiveDebugSession(); 175 176 pushListingSoon(); 177} 178 179void RemoteInspector::start() 180{ 181 std::lock_guard<std::mutex> lock(m_mutex); 182 183 if (m_enabled) 184 return; 185 186 m_enabled = true; 187 188 notify_register_dispatch(WIRServiceAvailableNotification, &m_notifyToken, m_xpcQueue, ^(int) { 189 RemoteInspector::shared().setupXPCConnectionIfNeeded(); 190 }); 191 192 notify_post(WIRServiceAvailabilityCheckNotification); 193} 194 195void RemoteInspector::stop() 196{ 197 std::lock_guard<std::mutex> lock(m_mutex); 198 199 stopInternal(StopSource::API); 200} 201 202void RemoteInspector::stopInternal(StopSource source) 203{ 204 if (!m_enabled) 205 return; 206 207 m_enabled = false; 208 209 m_pushScheduled = false; 210 211 for (auto it = m_connectionMap.begin(), end = m_connectionMap.end(); it != end; ++it) 212 it->value->close(); 213 m_connectionMap.clear(); 214 215 updateHasActiveDebugSession(); 216 217 if (m_xpcConnection) { 218 switch (source) { 219 case StopSource::API: 220 m_xpcConnection->close(); 221 break; 222 case StopSource::XPCMessage: 223 m_xpcConnection->closeFromMessage(); 224 break; 225 } 226 227 m_xpcConnection = nullptr; 228 } 229 230 notify_cancel(m_notifyToken); 231} 232 233void RemoteInspector::setupXPCConnectionIfNeeded() 234{ 235 std::lock_guard<std::mutex> lock(m_mutex); 236 237 if (m_xpcConnection) 238 return; 239 240 xpc_connection_t connection = xpc_connection_create_mach_service(WIRXPCMachPortName, m_xpcQueue, 0); 241 if (!connection) 242 return; 243 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); 247 248 pushListingSoon(); 249} 250 251#pragma mark - Proxy Application Information 252 253void RemoteInspector::setParentProcessInformation(pid_t pid, RetainPtr<CFDataRef> auditData) 254{ 255 std::lock_guard<std::mutex> lock(m_mutex); 256 257 if (m_parentProcessIdentifier || m_parentProcessAuditData) 258 return; 259 260 m_parentProcessIdentifier = pid; 261 m_parentProcessAuditData = auditData; 262 263 if (m_shouldSendParentProcessInformation) 264 receivedProxyApplicationSetupMessage(nil); 265} 266 267#pragma mark - RemoteInspectorXPCConnection::Client 268 269void RemoteInspector::xpcConnectionReceivedMessage(RemoteInspectorXPCConnection*, NSString *messageName, NSDictionary *userInfo) 270{ 271 std::lock_guard<std::mutex> lock(m_mutex); 272 273 if ([messageName isEqualToString:WIRPermissionDenied]) { 274 stopInternal(StopSource::XPCMessage); 275 return; 276 } 277 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); 292 else 293 NSLog(@"Unrecognized RemoteInspector XPC Message: %@", messageName); 294} 295 296void RemoteInspector::xpcConnectionFailed(RemoteInspectorXPCConnection* connection) 297{ 298 std::lock_guard<std::mutex> lock(m_mutex); 299 300 ASSERT(connection == m_xpcConnection); 301 if (connection != m_xpcConnection) 302 return; 303 304 m_pushScheduled = false; 305 306 for (auto it = m_connectionMap.begin(), end = m_connectionMap.end(); it != end; ++it) 307 it->value->close(); 308 m_connectionMap.clear(); 309 310 updateHasActiveDebugSession(); 311 312 // The connection will close itself. 313 m_xpcConnection = nullptr; 314} 315 316void RemoteInspector::xpcConnectionUnhandledMessage(RemoteInspectorXPCConnection*, xpc_object_t) 317{ 318 // Intentionally ignored. 319} 320 321#pragma mark - Listings 322 323NSDictionary *RemoteInspector::listingForDebuggable(const RemoteInspectorDebuggableInfo& debuggableInfo) const 324{ 325 NSMutableDictionary *debuggableDetails = [NSMutableDictionary dictionary]; 326 327 [debuggableDetails setObject:@(debuggableInfo.identifier) forKey:WIRPageIdentifierKey]; 328 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]; 334 break; 335 } 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]; 342 break; 343 } 344 default: 345 ASSERT_NOT_REACHED(); 346 break; 347 } 348 349 if (RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(debuggableInfo.identifier)) 350 [debuggableDetails setObject:connection->connectionIdentifier() forKey:WIRConnectionIdentifierKey]; 351 352 if (debuggableInfo.hasLocalDebugger) 353 [debuggableDetails setObject:@YES forKey:WIRHasLocalDebuggerKey]; 354 355 return debuggableDetails; 356} 357 358void RemoteInspector::pushListingNow() 359{ 360 ASSERT(m_xpcConnection); 361 if (!m_xpcConnection) 362 return; 363 364 m_pushScheduled = false; 365 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]]; 372 } 373 } 374 375 RetainPtr<NSMutableDictionary> outgoing = adoptNS([[NSMutableDictionary alloc] init]); 376 [outgoing setObject:response.get() forKey:WIRListingKey]; 377 378 m_xpcConnection->sendMessage(WIRListingMessage, outgoing.get()); 379} 380 381void RemoteInspector::pushListingSoon() 382{ 383 if (!m_xpcConnection) 384 return; 385 386 if (m_pushScheduled) 387 return; 388 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); 392 if (m_pushScheduled) 393 pushListingNow(); 394 }); 395} 396 397#pragma mark - Active Debugger Sessions 398 399void RemoteInspector::updateHasActiveDebugSession() 400{ 401 bool hasActiveDebuggerSession = !m_connectionMap.isEmpty(); 402 if (hasActiveDebuggerSession == m_hasActiveDebugSession) 403 return; 404 405 m_hasActiveDebugSession = hasActiveDebuggerSession; 406 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. 409} 410 411#pragma mark - Received XPC Messages 412 413void RemoteInspector::receivedSetupMessage(NSDictionary *userInfo) 414{ 415 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey]; 416 if (!pageId) 417 return; 418 419 NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey]; 420 if (!connectionIdentifier) 421 return; 422 423 NSString *sender = [userInfo objectForKey:WIRSenderKey]; 424 if (!sender) 425 return; 426 427 unsigned identifier = [pageId unsignedIntValue]; 428 if (m_connectionMap.contains(identifier)) 429 return; 430 431 auto it = m_debuggableMap.find(identifier); 432 if (it == m_debuggableMap.end()) 433 return; 434 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()) { 440 connection->close(); 441 return; 442 } 443 444 m_connectionMap.set(identifier, connection.release()); 445 446 updateHasActiveDebugSession(); 447 448 pushListingSoon(); 449} 450 451void RemoteInspector::receivedDataMessage(NSDictionary *userInfo) 452{ 453 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey]; 454 if (!pageId) 455 return; 456 457 unsigned pageIdentifier = [pageId unsignedIntValue]; 458 RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(pageIdentifier); 459 if (!connection) 460 return; 461 462 NSData *data = [userInfo objectForKey:WIRSocketDataKey]; 463 RetainPtr<NSString> message = adoptNS([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); 464 connection->sendMessageToBackend(message.get()); 465} 466 467void RemoteInspector::receivedDidCloseMessage(NSDictionary *userInfo) 468{ 469 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey]; 470 if (!pageId) 471 return; 472 473 NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey]; 474 if (!connectionIdentifier) 475 return; 476 477 unsigned identifier = [pageId unsignedIntValue]; 478 RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(identifier); 479 if (!connection) 480 return; 481 482 if (![connectionIdentifier isEqualToString:connection->connectionIdentifier()]) 483 return; 484 485 connection->close(); 486 m_connectionMap.remove(identifier); 487 488 updateHasActiveDebugSession(); 489 490 pushListingSoon(); 491} 492 493void RemoteInspector::receivedGetListingMessage(NSDictionary *) 494{ 495 pushListingNow(); 496} 497 498void RemoteInspector::receivedIndicateMessage(NSDictionary *userInfo) 499{ 500 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey]; 501 if (!pageId) 502 return; 503 504 unsigned identifier = [pageId unsignedIntValue]; 505 BOOL indicateEnabled = [[userInfo objectForKey:WIRIndicateEnabledKey] boolValue]; 506 507 dispatchAsyncOnQueueSafeForAnyDebuggable(^{ 508 RemoteInspectorDebuggable* debuggable = nullptr; 509 { 510 std::lock_guard<std::mutex> lock(m_mutex); 511 512 auto it = m_debuggableMap.find(identifier); 513 if (it == m_debuggableMap.end()) 514 return; 515 516 debuggable = it->value.first; 517 } 518 debuggable->setIndicating(indicateEnabled); 519 }); 520} 521 522void RemoteInspector::receivedProxyApplicationSetupMessage(NSDictionary *) 523{ 524 ASSERT(m_xpcConnection); 525 if (!m_xpcConnection) 526 return; 527 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); 536 }); 537 return; 538 } 539 540 m_shouldSendParentProcessInformation = false; 541 542 m_xpcConnection->sendMessage(WIRProxyApplicationSetupResponseMessage, @{ 543 WIRProxyApplicationParentPIDKey: @(m_parentProcessIdentifier), 544 WIRProxyApplicationParentAuditDataKey: (NSData *)m_parentProcessAuditData.get(), 545 }); 546} 547 548void RemoteInspector::receivedConnectionDiedMessage(NSDictionary *userInfo) 549{ 550 NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey]; 551 if (!connectionIdentifier) 552 return; 553 554 auto it = m_connectionMap.begin(); 555 auto end = m_connectionMap.end(); 556 for (; it != end; ++it) { 557 if ([connectionIdentifier isEqualToString:it->value->connectionIdentifier()]) 558 break; 559 } 560 561 if (it == end) 562 return; 563 564 RefPtr<RemoteInspectorDebuggableConnection> connection = it->value; 565 connection->close(); 566 m_connectionMap.remove(it); 567 568 updateHasActiveDebugSession(); 569} 570 571} // namespace Inspector 572 573#endif // ENABLE(REMOTE_INSPECTOR) 574