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