Reviewed by Darin.
[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         [info release];
78     } else {
79         [serverConnection release];
80         serverConnection = nil;
81     }
82
83     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationTerminating:) name:NSApplicationWillTerminateNotification object:nil];
84     [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(serverQuery:) name:WebScriptDebugServerQueryNotification object:nil];
85
86     listeners = [[NSMutableSet alloc] init];
87
88     return self;
89 }
90
91 - (void)dealloc
92 {
93     ASSERT(listenerCount >= [listeners count]);
94     listenerCount -= [listeners count];
95     if (!listenerCount)
96         [self detachScriptDebuggerFromAllWebViews];
97     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];
98     [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:WebScriptDebugServerQueryNotification object:nil];
99     [[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerWillUnloadNotification object:serverName];
100     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:nil];
101     [serverConnection invalidate];
102     [serverConnection release];
103     [serverName release];
104     [listeners release];
105     [super dealloc];
106 }
107
108 - (void)applicationTerminating:(NSNotification *)notifiction
109 {
110     [[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerWillUnloadNotification object:serverName];
111 }
112
113 - (void)attachScriptDebuggerToAllWebViews
114 {
115     [WebView _makeAllWebViewsPerformSelector:@selector(_attachScriptDebuggerToAllFrames)];
116 }
117
118 - (void)detachScriptDebuggerFromAllWebViews
119 {
120     [WebView _makeAllWebViewsPerformSelector:@selector(_detachScriptDebuggerFromAllFrames)];
121 }
122
123 - (void)serverQuery:(NSNotification *)notification
124 {
125     NSProcessInfo *processInfo = [NSProcessInfo processInfo];
126     NSDictionary *info = [[NSDictionary alloc] initWithObjectsAndKeys:[processInfo processName], WebScriptDebugServerProcessNameKey,
127         [[NSBundle mainBundle] bundleIdentifier], WebScriptDebugServerProcessBundleIdentifierKey,
128         [NSNumber numberWithInt:[processInfo processIdentifier]], WebScriptDebugServerProcessIdentifierKey, nil];
129     [[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerQueryReplyNotification object:serverName userInfo:info];
130     [info release];
131 }
132
133 - (void)listenerConnectionDidDie:(NSNotification *)notification
134 {
135     NSConnection *connection = [notification object];
136     NSMutableSet *listenersToRemove = [[NSMutableSet alloc] initWithCapacity:[listeners count]];
137     NSEnumerator *enumerator = [listeners objectEnumerator];
138     NSDistantObject *listener = nil;
139
140     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:connection];
141
142     while ((listener = [enumerator nextObject]))
143         if ([[listener connectionForProxy] isEqualTo:connection])
144             [listenersToRemove addObject:listener];
145
146     ASSERT(listenerCount >= [listenersToRemove count]);
147     listenerCount -= [listenersToRemove count];
148     [listeners minusSet:listenersToRemove];
149     [listenersToRemove release];
150
151     if (!listenerCount)
152         [self detachScriptDebuggerFromAllWebViews];
153 }
154
155 - (oneway void)addListener:(id<WebScriptDebugListener>)listener
156 {
157     // can't use isKindOfClass: here because that will send over the wire and not check the proxy object
158     if (!listener || [listener class] != [NSDistantObject class] || ![listener conformsToProtocol:@protocol(WebScriptDebugListener)])
159         return;
160     listenerCount++;
161     [listeners addObject:listener];
162     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(listenerConnectionDidDie:) name:NSConnectionDidDieNotification object:[(NSDistantObject *)listener connectionForProxy]];
163     if (listenerCount == 1)
164         [self attachScriptDebuggerToAllWebViews];
165 }
166
167 - (oneway void)removeListener:(id<WebScriptDebugListener>)listener
168 {
169     if (!listener || ![listeners containsObject:listener])
170         return;
171     ASSERT(listenerCount >= 1);
172     listenerCount--;
173     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:[(NSDistantObject *)listener connectionForProxy]];
174     [listeners removeObject:listener];
175     if (!listenerCount)
176         [self detachScriptDebuggerFromAllWebViews];
177 }
178
179 - (oneway void)step
180 {
181     step = YES;
182     paused = NO;
183 }
184
185 - (oneway void)pause
186 {
187     paused = YES;
188     step = NO;
189 }
190
191 - (oneway void)resume
192 {
193     paused = NO;
194     step = NO;
195 }
196
197 - (oneway BOOL)isPaused
198 {
199     return paused;
200 }
201
202 - (void)suspendProcessIfPaused
203 {
204     // this method will suspend this process when called during the dubugging callbacks
205     // we need to do this to implement breakpoints and pausing of JavaScript
206
207     while (paused)
208         [[NSRunLoop currentRunLoop] runMode:NSConnectionReplyMode beforeDate:[NSDate distantFuture]];
209
210     if (step) {
211         step = NO;
212         paused = YES;
213     }
214 }
215
216 - (void)webView:(WebView *)webView didLoadMainResourceForDataSource:(WebDataSource *)dataSource
217 {
218     if (![listeners count] || inCallback)
219         return;
220
221     inCallback = YES;
222
223     NSEnumerator *enumerator = [listeners objectEnumerator];
224     NSDistantObject <WebScriptDebugListener> *listener = nil;
225
226     while ((listener = [enumerator nextObject])) {
227         if ([[listener connectionForProxy] isValid])
228             [listener webView:webView didLoadMainResourceForDataSource:dataSource];
229     }
230
231     inCallback = NO;
232 }
233
234 - (void)webView:(WebView *)webView       didParseSource:(NSString *)source
235                                          baseLineNumber:(unsigned)lineNumber
236                                                 fromURL:(NSURL *)url
237                                                sourceId:(int)sid
238                                             forWebFrame:(WebFrame *)webFrame
239 {
240     if (![listeners count] || inCallback)
241         return;
242
243     inCallback = YES;
244
245     NSEnumerator *enumerator = [listeners objectEnumerator];
246     NSDistantObject <WebScriptDebugListener> *listener = nil;
247
248     while ((listener = [enumerator nextObject])) {
249         if ([[listener connectionForProxy] isValid])
250             [listener webView:webView didParseSource:source baseLineNumber:lineNumber fromURL:url sourceId:sid forWebFrame:webFrame];
251     }
252
253     inCallback = NO;
254 }
255
256 - (void)webView:(WebView *)webView  failedToParseSource:(NSString *)source
257                                          baseLineNumber:(unsigned)lineNumber
258                                                 fromURL:(NSURL *)url
259                                               withError:(NSError *)error
260                                             forWebFrame:(WebFrame *)webFrame
261 {
262     if (![listeners count] || inCallback)
263         return;
264
265     inCallback = YES;
266
267     NSEnumerator *enumerator = [listeners objectEnumerator];
268     NSDistantObject <WebScriptDebugListener> *listener = nil;
269
270     while ((listener = [enumerator nextObject])) {
271         if ([[listener connectionForProxy] isValid])
272             [listener webView:webView failedToParseSource:source baseLineNumber:lineNumber fromURL:url withError:error forWebFrame:webFrame];
273     }
274
275     inCallback = NO;
276 }
277
278 - (void)webView:(WebView *)webView    didEnterCallFrame:(WebScriptCallFrame *)frame
279                                                sourceId:(int)sid
280                                                    line:(int)lineno
281                                             forWebFrame:(WebFrame *)webFrame
282 {
283     if (![listeners count] || inCallback)
284         return;
285
286     inCallback = YES;
287
288     NSEnumerator *enumerator = [listeners objectEnumerator];
289     NSDistantObject <WebScriptDebugListener> *listener = nil;
290
291     while ((listener = [enumerator nextObject])) {
292         if ([[listener connectionForProxy] isValid])
293             [listener webView:webView didEnterCallFrame:frame sourceId:sid line:lineno forWebFrame:webFrame];
294     }
295
296     [self suspendProcessIfPaused];
297
298     inCallback = NO;
299 }
300
301 - (void)webView:(WebView *)webView willExecuteStatement:(WebScriptCallFrame *)frame
302                                                sourceId:(int)sid
303                                                    line:(int)lineno
304                                             forWebFrame:(WebFrame *)webFrame
305 {
306     if (![listeners count] || inCallback)
307         return;
308
309     inCallback = YES;
310
311     NSEnumerator *enumerator = [listeners objectEnumerator];
312     NSDistantObject <WebScriptDebugListener> *listener = nil;
313
314     while ((listener = [enumerator nextObject])) {
315         if ([[listener connectionForProxy] isValid])
316             [listener webView:webView willExecuteStatement:frame sourceId:sid line:lineno forWebFrame:webFrame];
317     }
318
319     [self suspendProcessIfPaused];
320
321     inCallback = NO;
322 }
323
324 - (void)webView:(WebView *)webView   willLeaveCallFrame:(WebScriptCallFrame *)frame
325                                                sourceId:(int)sid
326                                                    line:(int)lineno
327                                             forWebFrame:(WebFrame *)webFrame
328 {
329     if (![listeners count] || inCallback)
330         return;
331
332     inCallback = YES;
333
334     NSEnumerator *enumerator = [listeners objectEnumerator];
335     NSDistantObject <WebScriptDebugListener> *listener = nil;
336
337     while ((listener = [enumerator nextObject])) {
338         if ([[listener connectionForProxy] isValid])
339             [listener webView:webView willLeaveCallFrame:frame sourceId:sid line:lineno forWebFrame:webFrame];
340     }
341
342     [self suspendProcessIfPaused];
343
344     inCallback = NO;
345 }
346
347 - (void)webView:(WebView *)webView   exceptionWasRaised:(WebScriptCallFrame *)frame
348                                                sourceId:(int)sid
349                                                    line:(int)lineno
350                                             forWebFrame:(WebFrame *)webFrame
351 {
352     if (![listeners count] || inCallback)
353         return;
354
355     inCallback = YES;
356
357     NSEnumerator *enumerator = [listeners objectEnumerator];
358     NSDistantObject <WebScriptDebugListener> *listener = nil;
359
360     while ((listener = [enumerator nextObject])) {
361         if ([[listener connectionForProxy] isValid])
362             [listener webView:webView exceptionWasRaised:frame sourceId:sid line:lineno forWebFrame:webFrame];
363     }
364
365     [self suspendProcessIfPaused];
366
367     inCallback = NO;
368 }
369
370 @end