webengine/osswebengine/WebKit/DefaultDelegates/WebScriptDebugServer.m
changeset 0 dd21522fd290
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webengine/osswebengine/WebKit/DefaultDelegates/WebScriptDebugServer.m	Mon Mar 30 12:54:55 2009 +0300
@@ -0,0 +1,372 @@
+/*
+ * 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
+{
+    // 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;
+    }
+}
+
+- (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