Reviewed by Geoff.
[WebKit-https.git] / WebKit / DefaultDelegates / WebScriptDebugServer.m
1 /*
2  * Copyright (C) 2006 Apple Computer, 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import "WebScriptDebugServer.h"
30 #import "WebScriptDebugServerPrivate.h"
31 #import "WebViewInternal.h"
32
33 #import <JavaScriptCore/Assertions.h>
34
35 NSString *WebScriptDebugServerProcessNameKey = @"WebScriptDebugServerProcessNameKey";
36 NSString *WebScriptDebugServerProcessBundleIdentifierKey = @"WebScriptDebugServerProcessBundleIdentifierKey";
37 NSString *WebScriptDebugServerProcessIdentifierKey = @"WebScriptDebugServerProcessIdentifierKey";
38
39 NSString *WebScriptDebugServerQueryNotification = @"WebScriptDebugServerQueryNotification";
40 NSString *WebScriptDebugServerQueryReplyNotification = @"WebScriptDebugServerQueryReplyNotification";
41
42 NSString *WebScriptDebugServerDidLoadNotification = @"WebScriptDebugServerDidLoadNotification";
43 NSString *WebScriptDebugServerWillUnloadNotification = @"WebScriptDebugServerWillUnloadNotification";
44
45 @implementation WebScriptDebugServer
46
47 static WebScriptDebugServer *sharedServer = nil;
48 static unsigned listenerCount = 0;
49
50 + (WebScriptDebugServer *)sharedScriptDebugServer
51 {
52     if (!sharedServer)
53         sharedServer = [[WebScriptDebugServer alloc] init];
54     return sharedServer;
55 }
56
57 + (unsigned)listenerCount
58 {
59     return listenerCount;
60 }
61
62 - (id)init
63 {
64     self = [super init];
65
66     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
67     serverName = [[NSString alloc] initWithFormat:@"WebScriptDebugServer-%@-%d", [processInfo processName], [processInfo processIdentifier]];
68
69     serverConnection = [[NSConnection alloc] init];
70     if ([serverConnection registerName:serverName]) {
71         [serverConnection setRootObject:self];
72         NSProcessInfo *processInfo = [NSProcessInfo processInfo];
73         NSDictionary *info = [[NSDictionary alloc] initWithObjectsAndKeys:[processInfo processName], WebScriptDebugServerProcessNameKey,
74             [[NSBundle mainBundle] bundleIdentifier], WebScriptDebugServerProcessBundleIdentifierKey,
75             [NSNumber numberWithInt:[processInfo processIdentifier]], WebScriptDebugServerProcessIdentifierKey, nil];
76         [[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerDidLoadNotification object:serverName userInfo:info];
77     } else {
78         [serverConnection release];
79         serverConnection = nil;
80     }
81
82     [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(serverQuery:) name:WebScriptDebugServerQueryNotification object:nil];
83
84     listeners = [[NSMutableSet alloc] init];
85
86     return self;
87 }
88
89 - (void)dealloc
90 {
91     ASSERT(listenerCount >= [listeners count]);
92     listenerCount -= [listeners count];
93     if (!listenerCount)
94         [self detachScriptDebuggerFromAllWebViews];
95     [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:WebScriptDebugServerQueryNotification object:nil];
96     [[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerWillUnloadNotification object:serverName];
97     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:nil];
98     [serverConnection invalidate];
99     [serverConnection release];
100     [serverName release];
101     [listeners release];
102     [super dealloc];
103 }
104
105 - (void)attachScriptDebuggerToAllWebViews
106 {
107     [WebView _makeAllWebViewsPerformSelector:@selector(_attachScriptDebuggerToAllFrames)];
108 }
109
110 - (void)detachScriptDebuggerFromAllWebViews
111 {
112     [WebView _makeAllWebViewsPerformSelector:@selector(_detachScriptDebuggerFromAllFrames)];
113 }
114
115 - (void)serverQuery:(NSNotification *)notification
116 {
117     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
118     NSDictionary *info = [[NSDictionary alloc] initWithObjectsAndKeys:[processInfo processName], WebScriptDebugServerProcessNameKey,
119         [[NSBundle mainBundle] bundleIdentifier], WebScriptDebugServerProcessBundleIdentifierKey,
120         [NSNumber numberWithInt:[processInfo processIdentifier]], WebScriptDebugServerProcessIdentifierKey, nil];
121     [[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerQueryReplyNotification object:serverName userInfo:info];
122     [info release];
123 }
124
125 - (void)listenerConnectionDidDie:(NSNotification *)notification
126 {
127     NSConnection *connection = [notification object];
128     NSMutableSet *listenersToRemove = [[NSMutableSet alloc] initWithCapacity:[listeners count]];
129     NSEnumerator *enumerator = [listeners objectEnumerator];
130     NSDistantObject *listener = nil;
131
132     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:connection];
133
134     while ((listener = [enumerator nextObject]))
135         if ([[listener connectionForProxy] isEqualTo:connection])
136             [listenersToRemove addObject:listener];
137
138     ASSERT(listenerCount >= [listenersToRemove count]);
139     listenerCount -= [listenersToRemove count];
140     [listeners minusSet:listenersToRemove];
141     [listenersToRemove release];
142
143     if (!listenerCount)
144         [self detachScriptDebuggerFromAllWebViews];
145 }
146
147 - (oneway void)addListener:(id<WebScriptDebugListener>)listener
148 {
149     // can't use isKindOfClass: here because that will send over the wire and not check the proxy object
150     if ([listener class] != [NSDistantObject class] || ![listener conformsToProtocol:@protocol(WebScriptDebugListener)])
151         return;
152     listenerCount++;
153     [listeners addObject:listener];
154     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(listenerConnectionDidDie:) name:NSConnectionDidDieNotification object:[(NSDistantObject *)listener connectionForProxy]];
155     if (listenerCount == 1)
156         [self attachScriptDebuggerToAllWebViews];
157 }
158
159 - (oneway void)removeListener:(id<WebScriptDebugListener>)listener
160 {
161     ASSERT(listenerCount >= 1);
162     listenerCount--;
163     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:[(NSDistantObject *)listener connectionForProxy]];
164     [listeners removeObject:listener];
165     if (!listenerCount)
166         [self detachScriptDebuggerFromAllWebViews];
167 }
168
169 - (oneway void)step
170 {
171     step = YES;
172     paused = NO;
173 }
174
175 - (oneway void)pause
176 {
177     paused = YES;
178     step = NO;
179 }
180
181 - (oneway void)resume
182 {
183     paused = NO;
184     step = NO;
185 }
186
187 - (oneway BOOL)isPaused
188 {
189     return paused;
190 }
191
192 - (void)suspendProcessIfPaused
193 {
194     // this method will suspend this process when called during the dubugging callbacks
195     // we need to do this to implement breakpoints and pausing of JavaScript
196
197     while (paused)
198         [[NSRunLoop currentRunLoop] runMode:NSConnectionReplyMode beforeDate:[NSDate distantFuture]];
199
200     if (step) {
201         step = NO;
202         paused = YES;
203     }
204 }
205
206 - (void)webView:(WebView *)webView       didParseSource:(NSString *)source
207                                                 fromURL:(NSString *)url
208                                                sourceId:(int)sid
209                                             forWebFrame:(WebFrame *)webFrame
210 {
211     if (![listeners count])
212         return;
213
214     NSEnumerator *enumerator = [listeners objectEnumerator];
215     NSDistantObject <WebScriptDebugListener> *listener = nil;
216
217     while ((listener = [enumerator nextObject])) {
218         if ([[listener connectionForProxy] isValid])
219             [listener webView:webView didParseSource:source fromURL:url sourceId:sid forWebFrame:webFrame];
220     }
221 }
222
223 - (void)webView:(WebView *)webView    didEnterCallFrame:(WebScriptCallFrame *)frame
224                                                sourceId:(int)sid
225                                                    line:(int)lineno
226                                             forWebFrame:(WebFrame *)webFrame
227 {
228     if (![listeners count])
229         return;
230
231     NSEnumerator *enumerator = [listeners objectEnumerator];
232     NSDistantObject <WebScriptDebugListener> *listener = nil;
233
234     while ((listener = [enumerator nextObject])) {
235         if ([[listener connectionForProxy] isValid])
236             [listener webView:webView didEnterCallFrame:frame sourceId:sid line:lineno forWebFrame:webFrame];
237     }
238
239     // check for messages from the listeners, so they can pause immediately
240     [[NSRunLoop currentRunLoop] runMode:NSConnectionReplyMode beforeDate:[NSDate distantPast]];
241
242     [self suspendProcessIfPaused];
243 }
244
245 - (void)webView:(WebView *)webView willExecuteStatement:(WebScriptCallFrame *)frame
246                                                sourceId:(int)sid
247                                                    line:(int)lineno
248                                             forWebFrame:(WebFrame *)webFrame
249 {
250     if (![listeners count])
251         return;
252
253     NSEnumerator *enumerator = [listeners objectEnumerator];
254     NSDistantObject <WebScriptDebugListener> *listener = nil;
255
256     while ((listener = [enumerator nextObject])) {
257         if ([[listener connectionForProxy] isValid])
258             [listener webView:webView willExecuteStatement:frame sourceId:sid line:lineno forWebFrame:webFrame];
259     }
260
261     // check for messages from the listeners, so they can pause immediately
262     [[NSRunLoop currentRunLoop] runMode:NSConnectionReplyMode beforeDate:[NSDate distantPast]];
263
264     [self suspendProcessIfPaused];
265 }
266
267 - (void)webView:(WebView *)webView   willLeaveCallFrame:(WebScriptCallFrame *)frame
268                                                sourceId:(int)sid
269                                                    line:(int)lineno
270                                             forWebFrame:(WebFrame *)webFrame
271 {
272     if (![listeners count])
273         return;
274
275     NSEnumerator *enumerator = [listeners objectEnumerator];
276     NSDistantObject <WebScriptDebugListener> *listener = nil;
277
278     while ((listener = [enumerator nextObject])) {
279         if ([[listener connectionForProxy] isValid])
280             [listener webView:webView willLeaveCallFrame:frame sourceId:sid line:lineno forWebFrame:webFrame];
281     }
282
283     // check for messages from the listeners, so they can pause immediately
284     [[NSRunLoop currentRunLoop] runMode:NSConnectionReplyMode beforeDate:[NSDate distantPast]];
285
286     [self suspendProcessIfPaused];
287 }
288
289 @end