2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #import "DebuggerDocument.h"
30 #import "DebuggerApplication.h"
32 static NSString *DebuggerConsoleToolbarItem = @"DebuggerConsoleToolbarItem";
33 static NSString *DebuggerContinueToolbarItem = @"DebuggerContinueToolbarItem";
34 static NSString *DebuggerPauseToolbarItem = @"DebuggerPauseToolbarItem";
35 static NSString *DebuggerStepIntoToolbarItem = @"DebuggerStepIntoToolbarItem";
36 static NSString *DebuggerStepOverToolbarItem = @"DebuggerStepOverToolbarItem";
37 static NSString *DebuggerStepOutToolbarItem = @"DebuggerStepOutToolbarItem";
39 @interface WebScriptObject (WebScriptObjectPrivate)
40 - (unsigned int)count;
43 @implementation DebuggerDocument
44 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
49 + (BOOL)isKeyExcludedFromWebScript:(const char *)name
56 - (id)initWithServerName:(NSString *)serverName
58 if ((self = [super init]))
59 [self switchToServerNamed:serverName];
66 [currentServerName release];
71 #pragma mark Stack & Variables
73 - (WebScriptCallFrame *)currentFrame
78 - (NSString *)currentFrameFunctionName
80 return [currentFrame functionName];
83 - (NSArray *)currentFunctionStack
85 NSMutableArray *result = [[NSMutableArray alloc] init];
86 WebScriptCallFrame *frame = currentFrame;
88 if ([frame functionName])
89 [result addObject:[frame functionName]];
90 frame = [frame caller];
92 return [result autorelease];
95 - (id)evaluateScript:(NSString *)script inCallFrame:(int)frame
97 WebScriptCallFrame *cframe = currentFrame;
98 for (unsigned count = 0; count < frame; count++)
99 cframe = [cframe caller];
103 id result = [cframe evaluateWebScript:script];
104 if ([result isKindOfClass:NSClassFromString(@"WebScriptObject")])
105 return [result callWebScriptMethod:@"toString" withArguments:nil];
109 - (NSArray *)webScriptAttributeKeysForScriptObject:(WebScriptObject *)object
111 WebScriptObject *func = [object evaluateWebScript:@"(function () { var result = new Array(); for (var x in this) { result.push(x); } return result; })"];
112 [object setValue:func forKey:@"__drosera_introspection"];
114 NSMutableArray *result = [[NSMutableArray alloc] init];
115 WebScriptObject *variables = [object callWebScriptMethod:@"__drosera_introspection" withArguments:nil];
116 unsigned length = [variables count];
117 for (unsigned i = 0; i < length; i++) {
118 NSString *key = [variables webScriptValueAtIndex:i];
119 if (![key isEqualToString:@"__drosera_introspection"])
120 [result addObject:key];
123 [object removeWebScriptKey:@"__drosera_introspection"];
125 [result sortUsingSelector:@selector(compare:)];
126 return [result autorelease];
129 - (NSArray *)localScopeVariableNamesForCallFrame:(int)frame
131 WebScriptCallFrame *cframe = currentFrame;
132 for (unsigned count = 0; count < frame; count++)
133 cframe = [cframe caller];
135 if (![[cframe scopeChain] count])
138 WebScriptObject *scope = [[cframe scopeChain] objectAtIndex:0]; // local is always first
139 return [self webScriptAttributeKeysForScriptObject:scope];
142 - (NSString *)valueForScopeVariableNamed:(NSString *)key inCallFrame:(int)frame
144 WebScriptCallFrame *cframe = currentFrame;
145 for (unsigned count = 0; count < frame; count++)
146 cframe = [cframe caller];
148 if (![[cframe scopeChain] count])
151 unsigned scopeCount = [[cframe scopeChain] count];
152 for (unsigned i = 0; i < scopeCount; i++) {
153 WebScriptObject *scope = [[cframe scopeChain] objectAtIndex:i];
154 id value = [scope valueForKey:key];
155 if ([value isKindOfClass:NSClassFromString(@"WebScriptObject")])
156 return [value callWebScriptMethod:@"toString" withArguments:nil];
157 if (value && ![value isKindOfClass:[NSString class]])
158 return [NSString stringWithFormat:@"%@", value];
167 #pragma mark Pause & Step
177 if ([[(NSDistantObject *)server connectionForProxy] isValid])
179 [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
185 if ([[(NSDistantObject *)server connectionForProxy] isValid])
191 if ([[(NSDistantObject *)server connectionForProxy] isValid])
195 - (void)log:(NSString *)msg
201 #pragma mark Interface Actions
203 - (IBAction)pause:(id)sender
205 [[webView windowScriptObject] callWebScriptMethod:@"pause" withArguments:nil];
208 - (IBAction)resume:(id)sender
210 [[webView windowScriptObject] callWebScriptMethod:@"resume" withArguments:nil];
213 - (IBAction)stepInto:(id)sender
215 [[webView windowScriptObject] callWebScriptMethod:@"stepInto" withArguments:nil];
218 - (IBAction)stepOver:(id)sender
220 [[webView windowScriptObject] callWebScriptMethod:@"stepOver" withArguments:nil];
223 - (IBAction)stepOut:(id)sender
225 [[webView windowScriptObject] callWebScriptMethod:@"stepOut" withArguments:nil];
228 - (IBAction)showConsole:(id)sender
230 [[webView windowScriptObject] callWebScriptMethod:@"showConsoleWindow" withArguments:nil];
234 #pragma mark Window Controller Overrides
236 - (NSString *)windowNibName
241 - (void)windowDidLoad
243 [super windowDidLoad];
245 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationTerminating:) name:NSApplicationWillTerminateNotification object:nil];
247 NSString *path = [[NSBundle mainBundle] pathForResource:@"debugger" ofType:@"html" inDirectory:nil];
248 [[webView mainFrame] loadRequest:[[[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath:path]] autorelease]];
250 NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"debugger"];
251 [toolbar setDelegate:self];
252 [toolbar setAllowsUserCustomization:YES];
253 [toolbar setAutosavesConfiguration:YES];
254 [[self window] setToolbar:toolbar];
258 - (void)windowWillClose:(NSNotification *)notification
260 [[webView windowScriptObject] removeWebScriptKey:@"DebuggerDocument"];
262 [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];
264 [self switchToServerNamed:nil];
266 [self autorelease]; // DebuggerApplication expects us to release on close
270 #pragma mark Connection Handling
272 - (void)switchToServerNamed:(NSString *)name
275 [[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:[(NSDistantObject *)server connectionForProxy]];
276 if ([[(NSDistantObject *)server connectionForProxy] isValid]) {
278 [server removeListener:self];
283 server = ([name length] ? [[NSConnection rootProxyForConnectionWithRegisteredName:name host:nil] retain] : nil);
286 old = currentServerName;
287 currentServerName = [name copy];
292 [(NSDistantObject *)server setProtocolForProxy:@protocol(WebScriptDebugServer)];
293 [server addListener:self];
294 } @catch (NSException *exception) {
295 [currentServerName release];
296 currentServerName = nil;
302 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(serverConnectionDidDie:) name:NSConnectionDidDieNotification object:[(NSDistantObject *)server connectionForProxy]];
306 - (void)applicationTerminating:(NSNotification *)notifiction
308 if (server && [[(NSDistantObject *)server connectionForProxy] isValid]) {
309 [self switchToServerNamed:nil];
310 // call the runloop for a while to make sure our removeListener: is sent to the server
311 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
315 - (void)serverConnectionDidDie:(NSNotification *)notifiction
317 [self switchToServerNamed:nil];
321 #pragma mark Toolbar Delegate
323 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
325 if ([itemIdentifier isEqualToString:DebuggerContinueToolbarItem]) {
326 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
328 [item setLabel:@"Continue"];
329 [item setPaletteLabel:@"Continue"];
331 [item setToolTip:@"Continue script execution"];
332 [item setImage:[NSImage imageNamed:@"continue"]];
334 [item setTarget:self];
335 [item setAction:@selector(resume:)];
337 return [item autorelease];
338 } else if ([itemIdentifier isEqualToString:DebuggerConsoleToolbarItem]) {
339 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
341 [item setLabel:@"Console"];
342 [item setPaletteLabel:@"Console"];
344 [item setToolTip:@"Console"];
345 [item setImage:[NSImage imageNamed:@"console"]];
347 [item setTarget:self];
348 [item setAction:@selector(showConsole:)];
350 return [item autorelease];
351 } else if ([itemIdentifier isEqualToString:DebuggerPauseToolbarItem]) {
352 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
354 [item setLabel:@"Pause"];
355 [item setPaletteLabel:@"Pause"];
357 [item setToolTip:@"Pause script execution"];
358 [item setImage:[NSImage imageNamed:@"pause"]];
360 [item setTarget:self];
361 [item setAction:@selector(pause:)];
363 return [item autorelease];
364 } else if ([itemIdentifier isEqualToString:DebuggerStepIntoToolbarItem]) {
365 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
367 [item setLabel:@"Step Into"];
368 [item setPaletteLabel:@"Step Into"];
370 [item setToolTip:@"Step into function call"];
371 [item setImage:[NSImage imageNamed:@"step"]];
373 [item setTarget:self];
374 [item setAction:@selector(stepInto:)];
376 return [item autorelease];
377 } else if ([itemIdentifier isEqualToString:DebuggerStepOverToolbarItem]) {
378 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
380 [item setLabel:@"Step Over"];
381 [item setPaletteLabel:@"Step Over"];
383 [item setToolTip:@"Step over function call"];
384 [item setImage:[NSImage imageNamed:@"stepOver"]];
386 [item setTarget:self];
387 [item setAction:@selector(stepOver:)];
389 return [item autorelease];
390 } else if ([itemIdentifier isEqualToString:DebuggerStepOutToolbarItem]) {
391 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
393 [item setLabel:@"Step Out"];
394 [item setPaletteLabel:@"Step Over"];
396 [item setToolTip:@"Step out of current function"];
397 [item setImage:[NSImage imageNamed:@"stepOut"]];
399 [item setTarget:self];
400 [item setAction:@selector(stepOut:)];
402 return [item autorelease];
408 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
410 return [NSArray arrayWithObjects:DebuggerContinueToolbarItem, DebuggerPauseToolbarItem,
411 NSToolbarSeparatorItemIdentifier, DebuggerStepIntoToolbarItem, DebuggerStepOutToolbarItem,
412 DebuggerStepOverToolbarItem, NSToolbarFlexibleSpaceItemIdentifier, DebuggerConsoleToolbarItem, nil];
415 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
417 return [NSArray arrayWithObjects:DebuggerConsoleToolbarItem, DebuggerContinueToolbarItem, DebuggerPauseToolbarItem,
418 DebuggerStepIntoToolbarItem, DebuggerStepOutToolbarItem, DebuggerStepOverToolbarItem, NSToolbarCustomizeToolbarItemIdentifier,
419 NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarSeparatorItemIdentifier, nil];
422 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)interfaceItem
424 SEL action = [interfaceItem action];
425 if (action == @selector(pause:))
426 return ![self isPaused];
427 if (action == @selector(resume:) ||
428 action == @selector(stepOver:) ||
429 action == @selector(stepOut:) ||
430 action == @selector(stepInto:))
431 return [self isPaused];
436 #pragma mark WebView UI Delegate
438 - (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request
440 WebView *newWebView = [[WebView alloc] initWithFrame:NSZeroRect frameName:nil groupName:nil];
441 [newWebView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
442 [newWebView setUIDelegate:self];
443 [newWebView setPolicyDelegate:self];
444 [newWebView setFrameLoadDelegate:self];
446 [[newWebView mainFrame] loadRequest:request];
448 NSWindow *window = [[NSWindow alloc] initWithContentRect:NSZeroRect styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSUnifiedTitleAndToolbarWindowMask) backing:NSBackingStoreBuffered defer:NO screen:[[webView window] screen]];
449 [window setReleasedWhenClosed:YES];
450 [newWebView setFrame:[[window contentView] frame]];
451 [[window contentView] addSubview:newWebView];
452 [newWebView release];
457 - (void)webViewShow:(WebView *)sender
459 [[sender window] makeKeyAndOrderFront:sender];
462 - (BOOL)webViewAreToolbarsVisible:(WebView *)sender
464 return [[[sender window] toolbar] isVisible];
467 - (void)webView:(WebView *)sender setToolbarsVisible:(BOOL)visible
469 [[[sender window] toolbar] setVisible:visible];
472 - (void)webView:(WebView *)sender setResizable:(BOOL)resizable
474 [[sender window] setShowsResizeIndicator:resizable];
475 [[[sender window] standardWindowButton:NSWindowZoomButton] setEnabled:resizable];
478 - (void)webView:(WebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame
480 NSRange range = [message rangeOfString:@"\t"];
481 NSString *title = @"Alert";
482 if (range.location != NSNotFound) {
483 title = [message substringToIndex:range.location];
484 message = [message substringFromIndex:(range.location + range.length)];
487 NSBeginInformationalAlertSheet(title, nil, nil, nil, [sender window], nil, NULL, NULL, NULL, message);
490 - (void)scriptConfirmSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(int *)contextInfo
492 *contextInfo = returnCode;
495 - (BOOL)webView:(WebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame
497 NSRange range = [message rangeOfString:@"\t"];
498 NSString *title = @"Alert";
499 if (range.location != NSNotFound) {
500 title = [message substringToIndex:range.location];
501 message = [message substringFromIndex:(range.location + range.length)];
504 int result = NSNotFound;
505 NSBeginInformationalAlertSheet(title, nil, @"Cancel", nil, [sender window], self, @selector(scriptConfirmSheetDidEnd:returnCode:contextInfo:), NULL, &result, message);
507 while (result == NSNotFound) {
508 NSEvent *nextEvent = [[NSApplication sharedApplication] nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES];
509 [[NSApplication sharedApplication] sendEvent:nextEvent];
516 #pragma mark WebView Frame Load Delegate
518 - (void)webView:(WebView *)sender windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject
520 // note: this is the Debuggers's own WebView, not the one being debugged
521 if ([[sender window] isEqual:[self window]])
522 [[sender windowScriptObject] setValue:self forKey:@"DebuggerDocument"];
525 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
527 // note: this is the Debuggers's own WebView, not the one being debugged
528 if ([[sender window] isEqual:[self window]])
532 - (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
534 // note: this is the Debuggers's own WebViews, not the one being debugged
535 if ([frame isEqual:[sender mainFrame]]) {
536 NSDictionary *info = [[(DebuggerApplication *)[[NSApplication sharedApplication] delegate] knownServers] objectForKey:currentServerName];
537 NSString *processName = [info objectForKey:WebScriptDebugServerProcessNameKey];
538 if (info && [processName length]) {
539 NSMutableString *newTitle = [[NSMutableString alloc] initWithString:processName];
540 [newTitle appendString:@" - "];
541 [newTitle appendString:title];
542 [[sender window] setTitle:newTitle];
545 [[sender window] setTitle:title];
550 #pragma mark Debug Listener Callbacks
552 - (void)webView:(WebView *)view didLoadMainResourceForDataSource:(WebDataSource *)dataSource
554 NSString *documentSourceCopy = nil;
555 id <WebDocumentRepresentation> rep = [dataSource representation];
556 if ([rep canProvideDocumentSource])
557 documentSourceCopy = [[rep documentSource] copy];
559 if (!documentSourceCopy)
562 NSString *urlCopy = [[[[dataSource response] URL] absoluteString] copy];
563 NSArray *args = [[NSArray alloc] initWithObjects:(documentSourceCopy ? documentSourceCopy : @""), (urlCopy ? urlCopy : @""), [NSNumber numberWithBool:NO], nil];
564 [[webView windowScriptObject] callWebScriptMethod:@"updateFileSource" withArguments:args];
567 [documentSourceCopy release];
571 - (void)webView:(WebView *)view didParseSource:(NSString *)source baseLineNumber:(unsigned)baseLine fromURL:(NSURL *)url sourceId:(int)sid forWebFrame:(WebFrame *)webFrame
576 NSString *sourceCopy = [source copy];
580 NSString *documentSourceCopy = nil;
581 NSString *urlCopy = [[url absoluteString] copy];
583 WebDataSource *dataSource = [webFrame dataSource];
584 if (!url || [[[dataSource response] URL] isEqual:url]) {
585 id <WebDocumentRepresentation> rep = [dataSource representation];
586 if ([rep canProvideDocumentSource])
587 documentSourceCopy = [[rep documentSource] copy];
589 urlCopy = [[[[dataSource response] URL] absoluteString] copy];
592 NSArray *args = [[NSArray alloc] initWithObjects:sourceCopy, (documentSourceCopy ? documentSourceCopy : @""), (urlCopy ? urlCopy : @""), [NSNumber numberWithInt:sid], [NSNumber numberWithUnsignedInt:baseLine], nil];
593 [[webView windowScriptObject] callWebScriptMethod:@"didParseScript" withArguments:args];
596 [sourceCopy release];
597 [documentSourceCopy release];
601 - (void)webView:(WebView *)view failedToParseSource:(NSString *)source baseLineNumber:(unsigned)baseLine fromURL:(NSURL *)url withError:(NSError *)error forWebFrame:(WebFrame *)webFrame
605 - (void)webView:(WebView *)view didEnterCallFrame:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame
610 id old = currentFrame;
611 currentFrame = [frame retain];
614 NSArray *args = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:sid], [NSNumber numberWithInt:lineno], nil];
615 [[webView windowScriptObject] callWebScriptMethod:@"didEnterCallFrame" withArguments:args];
619 - (void)webView:(WebView *)view willExecuteStatement:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame
624 NSArray *args = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:sid], [NSNumber numberWithInt:lineno], nil];
625 [[webView windowScriptObject] callWebScriptMethod:@"willExecuteStatement" withArguments:args];
629 - (void)webView:(WebView *)view willLeaveCallFrame:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame
634 NSArray *args = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:sid], [NSNumber numberWithInt:lineno], nil];
635 [[webView windowScriptObject] callWebScriptMethod:@"willLeaveCallFrame" withArguments:args];
638 id old = currentFrame;
639 currentFrame = [[frame caller] retain];