blob: 2cd0e8f8ed31ea0e3a214a3c23d332d45b57ce1a [file] [log] [blame]
/*
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "WebScriptDebugServer.h"
#import "WebScriptDebugServerPrivate.h"
#import "WebViewInternal.h"
#import <JavaScriptCore/Assertions.h>
NSString *WebScriptDebugServerProcessNameKey = @"WebScriptDebugServerProcessNameKey";
NSString *WebScriptDebugServerProcessBundleIdentifierKey = @"WebScriptDebugServerProcessBundleIdentifierKey";
NSString *WebScriptDebugServerProcessIdentifierKey = @"WebScriptDebugServerProcessIdentifierKey";
NSString *WebScriptDebugServerQueryNotification = @"WebScriptDebugServerQueryNotification";
NSString *WebScriptDebugServerQueryReplyNotification = @"WebScriptDebugServerQueryReplyNotification";
NSString *WebScriptDebugServerDidLoadNotification = @"WebScriptDebugServerDidLoadNotification";
NSString *WebScriptDebugServerWillUnloadNotification = @"WebScriptDebugServerWillUnloadNotification";
@implementation WebScriptDebugServer
static WebScriptDebugServer *sharedServer = nil;
static unsigned listenerCount = 0;
+ (WebScriptDebugServer *)sharedScriptDebugServer
{
if (!sharedServer)
sharedServer = [[WebScriptDebugServer alloc] init];
return sharedServer;
}
+ (unsigned)listenerCount
{
return listenerCount;
}
- (id)init
{
self = [super init];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
serverName = [[NSString alloc] initWithFormat:@"WebScriptDebugServer-%@-%d", [processInfo processName], [processInfo processIdentifier]];
serverConnection = [[NSConnection alloc] init];
if ([serverConnection registerName:serverName]) {
[serverConnection setRootObject:self];
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
NSDictionary *info = [[NSDictionary alloc] initWithObjectsAndKeys:[processInfo processName], WebScriptDebugServerProcessNameKey,
[[NSBundle mainBundle] bundleIdentifier], WebScriptDebugServerProcessBundleIdentifierKey,
[NSNumber numberWithInt:[processInfo processIdentifier]], WebScriptDebugServerProcessIdentifierKey, nil];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerDidLoadNotification object:serverName userInfo:info];
[info release];
} else {
[serverConnection release];
serverConnection = nil;
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationTerminating:) name:NSApplicationWillTerminateNotification object:nil];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(serverQuery:) name:WebScriptDebugServerQueryNotification object:nil];
listeners = [[NSMutableSet alloc] init];
return self;
}
- (void)dealloc
{
// FIXME: Bad to do all this work in dealloc. What about under GC?
ASSERT(listenerCount >= [listeners count]);
listenerCount -= [listeners count];
if (!listenerCount)
[self detachScriptDebuggerFromAllWebViews];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:WebScriptDebugServerQueryNotification object:nil];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerWillUnloadNotification object:serverName];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:nil];
[serverConnection invalidate];
[serverConnection release];
[serverName release];
[listeners release];
[super dealloc];
}
- (void)applicationTerminating:(NSNotification *)notifiction
{
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerWillUnloadNotification object:serverName];
}
- (void)attachScriptDebuggerToAllWebViews
{
[WebView _makeAllWebViewsPerformSelector:@selector(_attachScriptDebuggerToAllFrames)];
}
- (void)detachScriptDebuggerFromAllWebViews
{
[WebView _makeAllWebViewsPerformSelector:@selector(_detachScriptDebuggerFromAllFrames)];
}
- (void)serverQuery:(NSNotification *)notification
{
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
NSDictionary *info = [[NSDictionary alloc] initWithObjectsAndKeys:[processInfo processName], WebScriptDebugServerProcessNameKey,
[[NSBundle mainBundle] bundleIdentifier], WebScriptDebugServerProcessBundleIdentifierKey,
[NSNumber numberWithInt:[processInfo processIdentifier]], WebScriptDebugServerProcessIdentifierKey, nil];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:WebScriptDebugServerQueryReplyNotification object:serverName userInfo:info];
[info release];
}
- (void)listenerConnectionDidDie:(NSNotification *)notification
{
NSConnection *connection = [notification object];
NSMutableSet *listenersToRemove = [[NSMutableSet alloc] initWithCapacity:[listeners count]];
NSEnumerator *enumerator = [listeners objectEnumerator];
NSDistantObject *listener = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:connection];
while ((listener = [enumerator nextObject]))
if ([[listener connectionForProxy] isEqualTo:connection])
[listenersToRemove addObject:listener];
ASSERT(listenerCount >= [listenersToRemove count]);
listenerCount -= [listenersToRemove count];
[listeners minusSet:listenersToRemove];
[listenersToRemove release];
if (!listenerCount)
[self detachScriptDebuggerFromAllWebViews];
}
- (oneway void)addListener:(id<WebScriptDebugListener>)listener
{
// can't use isKindOfClass: here because that will send over the wire and not check the proxy object
if (!listener || [listener class] != [NSDistantObject class] || ![listener conformsToProtocol:@protocol(WebScriptDebugListener)])
return;
listenerCount++;
[listeners addObject:listener];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(listenerConnectionDidDie:) name:NSConnectionDidDieNotification object:[(NSDistantObject *)listener connectionForProxy]];
if (listenerCount == 1)
[self attachScriptDebuggerToAllWebViews];
}
- (oneway void)removeListener:(id<WebScriptDebugListener>)listener
{
if (!listener || ![listeners containsObject:listener])
return;
ASSERT(listenerCount >= 1);
listenerCount--;
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSConnectionDidDieNotification object:[(NSDistantObject *)listener connectionForProxy]];
[listeners removeObject:listener];
if (!listenerCount)
[self detachScriptDebuggerFromAllWebViews];
}
- (oneway void)step
{
step = YES;
paused = NO;
}
- (oneway void)pause
{
paused = YES;
step = NO;
}
- (oneway void)resume
{
paused = NO;
step = NO;
}
- (oneway BOOL)isPaused
{
return paused;
}
- (void)suspendProcessIfPaused
{
static BOOL alreadyHere = NO;
if (alreadyHere)
return;
alreadyHere = YES;
// this method will suspend this process when called during the dubugging callbacks
// we need to do this to implement breakpoints and pausing of JavaScript
while (paused)
[[NSRunLoop currentRunLoop] runMode:NSConnectionReplyMode beforeDate:[NSDate distantFuture]];
if (step) {
step = NO;
paused = YES;
}
alreadyHere = NO;
}
- (void)webView:(WebView *)webView didLoadMainResourceForDataSource:(WebDataSource *)dataSource
{
if (![listeners count] || inCallback)
return;
inCallback = YES;
NSEnumerator *enumerator = [listeners objectEnumerator];
NSDistantObject <WebScriptDebugListener> *listener = nil;
while ((listener = [enumerator nextObject])) {
if ([[listener connectionForProxy] isValid])
[listener webView:webView didLoadMainResourceForDataSource:dataSource];
}
inCallback = NO;
}
- (void)webView:(WebView *)webView didParseSource:(NSString *)source
baseLineNumber:(NSUInteger)lineNumber
fromURL:(NSURL *)url
sourceId:(int)sid
forWebFrame:(WebFrame *)webFrame
{
if (![listeners count] || inCallback)
return;
inCallback = YES;
NSEnumerator *enumerator = [listeners objectEnumerator];
NSDistantObject <WebScriptDebugListener> *listener = nil;
while ((listener = [enumerator nextObject])) {
if ([[listener connectionForProxy] isValid])
[listener webView:webView didParseSource:source baseLineNumber:lineNumber fromURL:url sourceId:sid forWebFrame:webFrame];
}
inCallback = NO;
}
- (void)webView:(WebView *)webView failedToParseSource:(NSString *)source
baseLineNumber:(NSUInteger)lineNumber
fromURL:(NSURL *)url
withError:(NSError *)error
forWebFrame:(WebFrame *)webFrame
{
if (![listeners count] || inCallback)
return;
inCallback = YES;
NSEnumerator *enumerator = [listeners objectEnumerator];
NSDistantObject <WebScriptDebugListener> *listener = nil;
while ((listener = [enumerator nextObject])) {
if ([[listener connectionForProxy] isValid])
[listener webView:webView failedToParseSource:source baseLineNumber:lineNumber fromURL:url withError:error forWebFrame:webFrame];
}
inCallback = NO;
}
- (void)webView:(WebView *)webView didEnterCallFrame:(WebScriptCallFrame *)frame
sourceId:(int)sid
line:(int)lineno
forWebFrame:(WebFrame *)webFrame
{
if (![listeners count] || inCallback)
return;
inCallback = YES;
NSEnumerator *enumerator = [listeners objectEnumerator];
NSDistantObject <WebScriptDebugListener> *listener = nil;
while ((listener = [enumerator nextObject])) {
if ([[listener connectionForProxy] isValid])
[listener webView:webView didEnterCallFrame:frame sourceId:sid line:lineno forWebFrame:webFrame];
}
[self suspendProcessIfPaused];
inCallback = NO;
}
- (void)webView:(WebView *)webView willExecuteStatement:(WebScriptCallFrame *)frame
sourceId:(int)sid
line:(int)lineno
forWebFrame:(WebFrame *)webFrame
{
if (![listeners count] || inCallback)
return;
inCallback = YES;
NSEnumerator *enumerator = [listeners objectEnumerator];
NSDistantObject <WebScriptDebugListener> *listener = nil;
while ((listener = [enumerator nextObject])) {
if ([[listener connectionForProxy] isValid])
[listener webView:webView willExecuteStatement:frame sourceId:sid line:lineno forWebFrame:webFrame];
}
[self suspendProcessIfPaused];
inCallback = NO;
}
- (void)webView:(WebView *)webView willLeaveCallFrame:(WebScriptCallFrame *)frame
sourceId:(int)sid
line:(int)lineno
forWebFrame:(WebFrame *)webFrame
{
if (![listeners count] || inCallback)
return;
inCallback = YES;
NSEnumerator *enumerator = [listeners objectEnumerator];
NSDistantObject <WebScriptDebugListener> *listener = nil;
while ((listener = [enumerator nextObject])) {
if ([[listener connectionForProxy] isValid])
[listener webView:webView willLeaveCallFrame:frame sourceId:sid line:lineno forWebFrame:webFrame];
}
[self suspendProcessIfPaused];
inCallback = NO;
}
- (void)webView:(WebView *)webView exceptionWasRaised:(WebScriptCallFrame *)frame
sourceId:(int)sid
line:(int)lineno
forWebFrame:(WebFrame *)webFrame
{
if (![listeners count] || inCallback)
return;
inCallback = YES;
NSEnumerator *enumerator = [listeners objectEnumerator];
NSDistantObject <WebScriptDebugListener> *listener = nil;
while ((listener = [enumerator nextObject])) {
if ([[listener connectionForProxy] isValid])
[listener webView:webView exceptionWasRaised:frame sourceId:sid line:lineno forWebFrame:webFrame];
}
[self suspendProcessIfPaused];
inCallback = NO;
}
@end