12e841d8330d2e889543515f507338665785da26
[WebKit-https.git] / WebKitTools / Drosera / DebuggerDocument.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 "DebuggerDocument.h"
30 #import "DebuggerApplication.h"
31
32 static NSString *DebuggerContinueToolbarItem = @"DebuggerContinueToolbarItem";
33 static NSString *DebuggerPauseToolbarItem = @"DebuggerPauseToolbarItem";
34 static NSString *DebuggerStepIntoToolbarItem = @"DebuggerStepIntoToolbarItem";
35 static NSString *DebuggerStepOverToolbarItem = @"DebuggerStepOverToolbarItem";
36 static NSString *DebuggerStepOutToolbarItem = @"DebuggerStepOutToolbarItem";
37
38 @interface WebScriptObject (WebScriptObjectPrivate)
39 - (unsigned int)count;
40 @end
41
42 @implementation DebuggerDocument
43 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
44 {
45     return NO;
46 }
47
48 + (BOOL)isKeyExcludedFromWebScript:(const char *)name
49 {
50     return NO;
51 }
52
53 #pragma mark -
54
55 - (id)initWithServerName:(NSString *)serverName
56 {
57     if ((self = [super init]))
58         [self switchToServerNamed:serverName];
59     return self;
60 }
61
62 - (void)dealloc
63 {
64     [server release];
65     [currentServerName release];
66     [super dealloc];
67 }
68
69 #pragma mark -
70 #pragma mark Stack & Variables
71
72 - (WebScriptCallFrame *)currentFrame
73 {
74     return currentFrame;
75 }
76
77 - (NSString *)currentFrameFunctionName
78 {
79     return [currentFrame functionName];
80 }
81
82 - (NSArray *)currentFunctionStack
83 {
84     NSMutableArray *result = [[NSMutableArray alloc] init];
85     WebScriptCallFrame *frame = currentFrame;
86     while (frame) {
87         if ([frame functionName])
88             [result addObject:[frame functionName]];
89         frame = [frame caller];
90     }
91     return [result autorelease];
92 }
93
94 - (NSArray *)webScriptAttributeKeysForScriptObject:(WebScriptObject *)object
95 {
96     WebScriptObject *func = [object evaluateWebScript:@"(function () { var result = new Array(); for (var x in this) { result.push(x); } return result; })"];
97     [object setValue:func forKey:@"__drosera_introspection"];
98
99     NSMutableArray *result = [[NSMutableArray alloc] init];
100     WebScriptObject *variables = [object callWebScriptMethod:@"__drosera_introspection" withArguments:nil];
101     unsigned length = [variables count];
102     for (unsigned i = 0; i < length; i++) {
103         NSString *key = [variables webScriptValueAtIndex:i];
104         if (![key isEqualToString:@"__drosera_introspection"])
105             [result addObject:key];
106     }
107
108     [object removeWebScriptKey:@"__drosera_introspection"];
109
110     [result sortUsingSelector:@selector(compare:)];
111     return [result autorelease];
112 }
113
114 - (NSArray *)localScopeVariableNamesForCallFrame:(int)frame
115 {
116     WebScriptCallFrame *cframe = currentFrame;
117     for (unsigned count = 0; count < frame; count++)
118         cframe = [cframe caller];
119
120     if (![[cframe scopeChain] count])
121         return nil;
122
123     WebScriptObject *scope = [[cframe scopeChain] objectAtIndex:0]; // local is always first
124     return [self webScriptAttributeKeysForScriptObject:scope];
125 }
126
127 - (NSString *)valueForScopeVariableNamed:(NSString *)key inCallFrame:(int)frame
128 {
129     WebScriptCallFrame *cframe = currentFrame;
130     for (unsigned count = 0; count < frame; count++)
131         cframe = [cframe caller];
132
133     if (![[cframe scopeChain] count])
134         return nil;
135
136     unsigned scopeCount = [[cframe scopeChain] count];
137     for (unsigned i = 0; i < scopeCount; i++) {
138         WebScriptObject *scope = [[cframe scopeChain] objectAtIndex:i];
139         id value = [scope valueForKey:key];
140         if ([value isKindOfClass:NSClassFromString(@"WebScriptObject")])
141             return [value callWebScriptMethod:@"toString" withArguments:nil];
142         if (value && ![value isKindOfClass:[NSString class]])
143             return [NSString stringWithFormat:@"%@", value];
144         if (value)
145             return value;
146     }
147
148     return nil;
149 }
150
151 #pragma mark -
152 #pragma mark Pause & Step
153
154 - (BOOL)isPaused
155 {
156     return paused;
157 }
158
159 - (void)pause
160 {
161     paused = YES;
162     if ([[(NSDistantObject *)server connectionForProxy] isValid])
163         [server pause];
164     [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
165 }
166
167 - (void)resume
168 {
169     paused = NO;
170     if ([[(NSDistantObject *)server connectionForProxy] isValid])
171         [server resume];
172 }
173
174 - (void)stepInto
175 {
176     if ([[(NSDistantObject *)server connectionForProxy] isValid])
177         [server step];
178 }
179
180 - (void)log:(NSString *)msg
181 {
182     NSLog(@"%@", msg);
183 }
184
185 #pragma mark -
186 #pragma mark Interface Actions
187
188 - (IBAction)pause:(id)sender
189 {
190     [[webView windowScriptObject] callWebScriptMethod:@"pause" withArguments:nil];
191 }
192
193 - (IBAction)resume:(id)sender
194 {
195     [[webView windowScriptObject] callWebScriptMethod:@"resume" withArguments:nil];
196 }
197
198 - (IBAction)stepInto:(id)sender
199 {
200     [[webView windowScriptObject] callWebScriptMethod:@"stepInto" withArguments:nil];
201 }
202
203 - (IBAction)stepOver:(id)sender
204 {
205     [[webView windowScriptObject] callWebScriptMethod:@"stepOver" withArguments:nil];
206 }
207
208 - (IBAction)stepOut:(id)sender
209 {
210     [[webView windowScriptObject] callWebScriptMethod:@"stepOut" withArguments:nil];
211 }
212
213 #pragma mark -
214 #pragma mark Window Controller Overrides
215
216 - (NSString *)windowNibName
217 {
218     return @"Debugger";
219 }
220
221 - (void)windowDidLoad
222 {
223     [super windowDidLoad];
224
225     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationTerminating:) name:NSApplicationWillTerminateNotification object:nil];
226
227     NSString *path = [[NSBundle mainBundle] pathForResource:@"debugger" ofType:@"html" inDirectory:nil];
228     [[webView mainFrame] loadRequest:[[[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath:path]] autorelease]];
229
230     NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"debugger"];
231     [toolbar setDelegate:self];
232     [toolbar setAllowsUserCustomization:YES];
233     [toolbar setAutosavesConfiguration:YES];
234     [[self window] setToolbar:toolbar];
235     [toolbar release];
236 }
237
238 - (void)windowWillClose:(NSNotification *)notification
239 {
240     [[webView windowScriptObject] removeWebScriptKey:@"DebuggerDocument"];
241
242     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];
243
244     [self switchToServerNamed:nil];
245
246     [self autorelease]; // DebuggerApplication expects us to release on close
247 }
248
249 #pragma mark -
250 #pragma mark Connection Handling
251
252 - (void)switchToServerNamed:(NSString *)name
253 {
254     if (server) {
255         [[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:[(NSDistantObject *)server connectionForProxy]];
256         if ([[(NSDistantObject *)server connectionForProxy] isValid]) {
257             [self resume];
258             [server removeListener:self];
259         }
260     }
261
262     id old = server;
263     server = ([name length] ? [[NSConnection rootProxyForConnectionWithRegisteredName:name host:nil] retain] : nil);
264     [old release];
265
266     old = currentServerName;
267     currentServerName = [name copy];
268     [old release];
269
270     if (server) {
271         @try {
272             [(NSDistantObject *)server setProtocolForProxy:@protocol(WebScriptDebugServer)];
273             [server addListener:self];
274         } @catch (NSException *exception) {
275             [currentServerName release];
276             currentServerName = nil;
277             [server release];
278             server = nil;
279         }
280
281         if (server)
282             [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(serverConnectionDidDie:) name:NSConnectionDidDieNotification object:[(NSDistantObject *)server connectionForProxy]];  
283     }
284 }
285
286 - (void)applicationTerminating:(NSNotification *)notifiction
287 {
288     if (server && [[(NSDistantObject *)server connectionForProxy] isValid]) {
289         [self switchToServerNamed:nil];
290         // call the runloop for a while to make sure our removeListener: is sent to the server
291         [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
292     }
293 }
294
295 - (void)serverConnectionDidDie:(NSNotification *)notifiction
296 {
297     [self switchToServerNamed:nil];
298 }
299
300 #pragma mark -
301 #pragma mark Toolbar Delegate
302
303 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
304 {
305     if ([itemIdentifier isEqualToString:DebuggerContinueToolbarItem]) {
306         NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
307
308         [item setLabel:@"Continue"];
309         [item setPaletteLabel:@"Continue"];
310
311         [item setToolTip:@"Continue script execution"];
312         [item setImage:[NSImage imageNamed:@"continue"]];
313
314         [item setTarget:self];
315         [item setAction:@selector(resume:)];
316
317         return [item autorelease];
318     } else if ([itemIdentifier isEqualToString:DebuggerPauseToolbarItem]) {
319         NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
320
321         [item setLabel:@"Pause"];
322         [item setPaletteLabel:@"Pause"];
323
324         [item setToolTip:@"Pause script execution"];
325         [item setImage:[NSImage imageNamed:@"pause"]];
326
327         [item setTarget:self];
328         [item setAction:@selector(pause:)];
329
330         return [item autorelease];
331     } else if ([itemIdentifier isEqualToString:DebuggerStepIntoToolbarItem]) {
332         NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
333
334         [item setLabel:@"Step Into"];
335         [item setPaletteLabel:@"Step Into"];
336
337         [item setToolTip:@"Step into function call"];
338         [item setImage:[NSImage imageNamed:@"step"]];
339
340         [item setTarget:self];
341         [item setAction:@selector(stepInto:)];
342
343         return [item autorelease];
344     } else if ([itemIdentifier isEqualToString:DebuggerStepOverToolbarItem]) {
345         NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
346
347         [item setLabel:@"Step Over"];
348         [item setPaletteLabel:@"Step Over"];
349
350         [item setToolTip:@"Step over function call"];
351         [item setImage:[NSImage imageNamed:@"stepOver"]];
352
353         [item setTarget:self];
354         [item setAction:@selector(stepOver:)];
355
356         return [item autorelease];
357     } else if ([itemIdentifier isEqualToString:DebuggerStepOutToolbarItem]) {
358         NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
359
360         [item setLabel:@"Step Out"];
361         [item setPaletteLabel:@"Step Over"];
362
363         [item setToolTip:@"Step out of current function"];
364         [item setImage:[NSImage imageNamed:@"stepOut"]];
365
366         [item setTarget:self];
367         [item setAction:@selector(stepOut:)];
368
369         return [item autorelease];
370     }
371
372     return nil;
373 }
374
375 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
376 {
377     return [NSArray arrayWithObjects:DebuggerContinueToolbarItem, DebuggerPauseToolbarItem,
378         NSToolbarSeparatorItemIdentifier, DebuggerStepIntoToolbarItem, DebuggerStepOutToolbarItem,
379         DebuggerStepOverToolbarItem, nil];
380 }
381
382 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
383 {
384     return [NSArray arrayWithObjects:DebuggerContinueToolbarItem, DebuggerPauseToolbarItem,
385         DebuggerStepIntoToolbarItem, DebuggerStepOutToolbarItem, DebuggerStepOverToolbarItem, NSToolbarCustomizeToolbarItemIdentifier,
386         NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarSeparatorItemIdentifier, nil];
387 }
388
389 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)interfaceItem
390 {
391     SEL action = [interfaceItem action];
392     if (action == @selector(pause:))
393         return ![self isPaused];
394     if (action == @selector(resume:) ||
395         action == @selector(stepOver:) ||
396         action == @selector(stepOut:) ||
397         action == @selector(stepInto:))
398         return [self isPaused];
399     return YES;
400 }
401
402 #pragma mark -
403 #pragma mark WebView Frame Load Delegate
404
405 - (void)webView:(WebView *)sender windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject
406 {
407     // note: this is the Debuggers's own WebView, not the one being debugged
408     [[sender windowScriptObject] setValue:self forKey:@"DebuggerDocument"];
409 }
410
411 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
412 {
413     // note: this is the Debuggers's own WebView, not the one being debugged
414     webViewLoaded = YES;
415 }
416
417 #pragma mark -
418 #pragma mark Debug Listener Callbacks
419
420 - (void)webView:(WebView *)view didLoadMainResourceForDataSource:(WebDataSource *)dataSource
421 {
422     NSString *documentSourceCopy = nil;
423     id <WebDocumentRepresentation> rep = [dataSource representation];
424     if ([rep canProvideDocumentSource])
425         documentSourceCopy = [[rep documentSource] copy];
426
427     if (!documentSourceCopy)
428         return;
429
430     NSString *urlCopy = [[[[dataSource response] URL] absoluteString] copy];
431     NSArray *args = [[NSArray alloc] initWithObjects:(documentSourceCopy ? documentSourceCopy : @""), (urlCopy ? urlCopy : @""), [NSNumber numberWithBool:NO], nil];
432     [[webView windowScriptObject] callWebScriptMethod:@"updateFileSource" withArguments:args];
433
434     [args release];
435     [documentSourceCopy release];
436     [urlCopy release];
437 }
438
439 - (void)webView:(WebView *)view didParseSource:(NSString *)source baseLineNumber:(unsigned)baseLine fromURL:(NSURL *)url sourceId:(int)sid forWebFrame:(WebFrame *)webFrame
440 {
441     if (!webViewLoaded)
442         return;
443
444     NSString *sourceCopy = [source copy];
445     if (!sourceCopy)
446         return;
447
448     NSString *documentSourceCopy = nil;
449     NSString *urlCopy = [[url absoluteString] copy];
450
451     WebDataSource *dataSource = [webFrame dataSource];
452     if (!url || [[[dataSource response] URL] isEqual:url]) {
453         id <WebDocumentRepresentation> rep = [dataSource representation];
454         if ([rep canProvideDocumentSource])
455             documentSourceCopy = [[rep documentSource] copy];
456         if (!urlCopy)
457             urlCopy = [[[[dataSource response] URL] absoluteString] copy];
458     }
459
460     NSArray *args = [[NSArray alloc] initWithObjects:sourceCopy, (documentSourceCopy ? documentSourceCopy : @""), (urlCopy ? urlCopy : @""), [NSNumber numberWithInt:sid], [NSNumber numberWithUnsignedInt:baseLine], nil];
461     [[webView windowScriptObject] callWebScriptMethod:@"didParseScript" withArguments:args];
462
463     [args release];
464     [sourceCopy release];
465     [documentSourceCopy release];
466     [urlCopy release];
467 }
468
469 - (void)webView:(WebView *)view failedToParseSource:(NSString *)source baseLineNumber:(unsigned)baseLine fromURL:(NSURL *)url withError:(NSError *)error forWebFrame:(WebFrame *)webFrame
470 {
471 }
472
473 - (void)webView:(WebView *)view didEnterCallFrame:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame
474 {
475     if (!webViewLoaded)
476         return;
477
478     id old = currentFrame;
479     currentFrame = [frame retain];
480     [old release];
481
482     NSArray *args = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:sid], [NSNumber numberWithInt:lineno], nil];
483     [[webView windowScriptObject] callWebScriptMethod:@"didEnterCallFrame" withArguments:args];
484     [args release];
485 }
486
487 - (void)webView:(WebView *)view willExecuteStatement:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame
488 {
489     if (!webViewLoaded)
490         return;
491
492     NSArray *args = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:sid], [NSNumber numberWithInt:lineno], nil];
493     [[webView windowScriptObject] callWebScriptMethod:@"willExecuteStatement" withArguments:args];
494     [args release];
495 }
496
497 - (void)webView:(WebView *)view willLeaveCallFrame:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame
498 {
499     if (!webViewLoaded)
500         return;
501
502     NSArray *args = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:sid], [NSNumber numberWithInt:lineno], nil];
503     [[webView windowScriptObject] callWebScriptMethod:@"willLeaveCallFrame" withArguments:args];
504     [args release];
505
506     id old = currentFrame;
507     currentFrame = [[frame caller] retain];
508     [old release];
509 }
510 @end