Reviewed by Darin.
authortomernic <tomernic@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 11 May 2006 01:04:45 +0000 (01:04 +0000)
committertomernic <tomernic@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 11 May 2006 01:04:45 +0000 (01:04 +0000)
        <rdar://problem/4542808> REGRESSION: benchjs test 1 has slowed by over 150% (8740)
        <http://bugzilla.opendarwin.org/show_bug.cgi?id=8740>

        * Misc/WebNSWindowExtras.h:
        * Misc/WebNSWindowExtras.m:
        (+[NSWindow _webkit_enableWindowDisplayThrottle]):
        Overrides certain NSWindow methods so that window autodisplay can be throttled to 60Hz.

        (disableWindowDisplayThrottleApplierFunction):
        CFDictionary applier function for when the throttle is disabled.  Cancels all pending window displays,
        and calls -displayIfNeeded on each window with a pending display.

        (+[NSWindow _webkit_disableWindowDisplayThrottle]):
        Restores default NSWindow method implementations and clears pending window displays.

        (swizzleInstanceMethod):
        Helper function to swizzle ObjC method implementations.

        (replacementPostWindowNeedsDisplay):
        Don't call into -[NSWindow _postWindowNeedsDisplay] if requestWindowDisplay() returns NO (this is the
        function that throttles display).

        (replacementClose):
        Clean up the WindowDisplayInfo struct for the window, since it's about to go away.

        (getWindowDisplayInfo):
        Gets the WindowDisplayInfo struct for the window, or creates it if absent.

        (requestWindowDisplay):
        Returns YES if a display is allowed right now.  Returns NO otherwise, and schedules a timer to try the
        display again.

        (cancelPendingWindowDisplay):
        Cancels the pending display for the window, if any.

        (-[NSWindow _webkit_doPendingPostWindowNeedsDisplay:]):
        Try to call _postWindowNeedsDisplay again.

        * WebView/WebFrameView.m:
        (-[WebFrameView initWithFrame:]):
        If the secret "WebKitThrottleWindowDisplay" default is set, then enable the NSWindow throttle.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@14293 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebKit/ChangeLog
WebKit/Misc/WebNSWindowExtras.h
WebKit/Misc/WebNSWindowExtras.m
WebKit/WebView/WebFrameView.m

index d204746e1590899a9d36499fd246e3da7a24a066..8f6492f496b3efaa15c1a45ecaf25c54fedaaee3 100644 (file)
@@ -1,3 +1,49 @@
+2006-05-10  Tim Omernick  <timo@apple.com>
+
+        Reviewed by Darin.
+
+        <rdar://problem/4542808> REGRESSION: benchjs test 1 has slowed by over 150% (8740)
+        <http://bugzilla.opendarwin.org/show_bug.cgi?id=8740>
+
+        * Misc/WebNSWindowExtras.h:
+        * Misc/WebNSWindowExtras.m:
+        (+[NSWindow _webkit_enableWindowDisplayThrottle]):
+        Overrides certain NSWindow methods so that window autodisplay can be throttled to 60Hz.
+
+        (disableWindowDisplayThrottleApplierFunction):
+        CFDictionary applier function for when the throttle is disabled.  Cancels all pending window displays,
+        and calls -displayIfNeeded on each window with a pending display.
+
+        (+[NSWindow _webkit_disableWindowDisplayThrottle]):
+        Restores default NSWindow method implementations and clears pending window displays.
+
+        (swizzleInstanceMethod):
+        Helper function to swizzle ObjC method implementations.
+
+        (replacementPostWindowNeedsDisplay):
+        Don't call into -[NSWindow _postWindowNeedsDisplay] if requestWindowDisplay() returns NO (this is the
+        function that throttles display).
+
+        (replacementClose):
+        Clean up the WindowDisplayInfo struct for the window, since it's about to go away.
+
+        (getWindowDisplayInfo):
+        Gets the WindowDisplayInfo struct for the window, or creates it if absent.
+
+        (requestWindowDisplay):
+        Returns YES if a display is allowed right now.  Returns NO otherwise, and schedules a timer to try the
+        display again.
+
+        (cancelPendingWindowDisplay):
+        Cancels the pending display for the window, if any.
+
+        (-[NSWindow _webkit_doPendingPostWindowNeedsDisplay:]):
+        Try to call _postWindowNeedsDisplay again.
+
+        * WebView/WebFrameView.m:
+        (-[WebFrameView initWithFrame:]):
+        If the secret "WebKitThrottleWindowDisplay" default is set, then enable the NSWindow throttle.
+
 2006-05-10  Anders Carlsson  <acarlsson@apple.com>
 
         Reviewed by Maciej.
index 5e71f7cb7c58334413b6bef9caf27f1ec156f07f..10051de0042d628ea8a3c73d163783c46f531ec3 100644 (file)
 #import <Cocoa/Cocoa.h>
 
 @interface NSWindow (WebExtras)
-- (void)centerOverMainWindow; // centers "visually", putting 1/3 of the remaining space above, and 2/3 below
+// Throttles the NSWindow autodisplay mechanism to a maximum of 60 frames per second.
+// This ensures that dynamically updated web content does not cause the window to display
+// too often, which can cause performance problems with CoreGraphics' coalesced updates
+// feature.  See <http://developer.apple.com/technotes/tn2005/tn2133.html>.
++ (void)_webkit_enableWindowDisplayThrottle;
+
+// Disables NSWindow display throttling.  Any windows with pending displays will be displayed
+// immediately when window throttling is disabled.
++ (void)_webkit_disableWindowDisplayThrottle;
+
+// centers "visually", putting 1/3 of the remaining space above, and 2/3 below
+- (void)centerOverMainWindow; 
 @end
index ef27b13a7adf5067941029f329d66191f590e9b7..391a6137b722f90ddad8aead7725efc7589018cc 100644 (file)
 
 #import "WebNSWindowExtras.h"
 
+#import "WebKitLogging.h"
+#import <JavaScriptCore/Assertions.h>
+#import <objc/objc-runtime.h>
+
+#define DISPLAY_REFRESH_INTERVAL (1.0 / 60.0)
+
+static BOOL throttlingWindowDisplay;
+static CFMutableDictionaryRef windowDisplayInfoDictionary;
+static IMP oldNSWindowPostWindowNeedsDisplayIMP;
+static IMP oldNSWindowCloseIMP;
+
+typedef struct {
+    NSWindow *window;
+    CFTimeInterval lastDisplayTime;
+    NSTimer *displayTimer;
+} WindowDisplayInfo;
+
+@interface NSWindow (WebExtrasInternal)
+static IMP swizzleInstanceMethod(Class class, SEL selector, IMP newImplementation);
+static void replacementPostWindowNeedsDisplay(id self, SEL cmd);
+static void replacementClose(id self, SEL cmd);
+static WindowDisplayInfo *getWindowDisplayInfo(NSWindow *window);
+static BOOL requestWindowDisplay(NSWindow *window);
+static void cancelPendingWindowDisplay(WindowDisplayInfo *displayInfo);
+- (void)_webkit_doPendingPostWindowNeedsDisplay:(NSTimer *)timer;
+@end
+
+@interface NSWindow (AppKitSecretsIKnow)
+- (void)_postWindowNeedsDisplay;
+@end
+
 @implementation NSWindow (WebExtras)
 
++ (void)_webkit_enableWindowDisplayThrottle
+{
+    if (throttlingWindowDisplay)
+        return;
+        
+    windowDisplayInfoDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
+    ASSERT(windowDisplayInfoDictionary);
+
+    // Override -[NSWindow _postWindowNeedsDisplay]
+    ASSERT(!oldNSWindowPostWindowNeedsDisplayIMP);
+    oldNSWindowPostWindowNeedsDisplayIMP = swizzleInstanceMethod(self, @selector(_postWindowNeedsDisplay), (IMP)replacementPostWindowNeedsDisplay);
+    ASSERT(oldNSWindowPostWindowNeedsDisplayIMP);
+    
+    // Override -[NSWindow close]    
+    ASSERT(!oldNSWindowCloseIMP);
+    oldNSWindowCloseIMP = swizzleInstanceMethod(self, @selector(close), (IMP)replacementClose);
+    ASSERT(oldNSWindowCloseIMP);
+    
+//    NSLog(@"Throttling window display to %.3f times per second", 1.0 / DISPLAY_REFRESH_INTERVAL);
+    
+    throttlingWindowDisplay = YES;
+}
+
+static void disableWindowDisplayThrottleApplierFunction(const void *key, const void *value, void *context)
+{
+    WindowDisplayInfo *displayInfo = (WindowDisplayInfo *)value;
+    
+    // Display immediately
+    cancelPendingWindowDisplay(displayInfo);
+    [displayInfo->window _postWindowNeedsDisplay];
+    
+    free(displayInfo);
+}
+
++ (void)_webkit_disableWindowDisplayThrottle
+{
+    if (!throttlingWindowDisplay)
+        return;
+    
+    //
+    // Restore NSWindow method implementations first.  When window display throttling is disabled, we display any windows
+    // with pending displays.  If the window re-displays during our call to -_postWindowNeedsDisplay, we want those displays to
+    // not be throttled.
+    //
+    
+    // Restore -[NSWindow _postWindowNeedsDisplay]
+    ASSERT(oldNSWindowPostWindowNeedsDisplayIMP);
+    swizzleInstanceMethod(self, @selector(_postWindowNeedsDisplay), oldNSWindowPostWindowNeedsDisplayIMP);
+    oldNSWindowPostWindowNeedsDisplayIMP = NULL;
+    
+    // Restore -[NSWindow close]    
+    ASSERT(oldNSWindowCloseIMP);
+    swizzleInstanceMethod(self, @selector(close), oldNSWindowCloseIMP);
+    oldNSWindowCloseIMP = NULL;
+
+    CFDictionaryApplyFunction(windowDisplayInfoDictionary, disableWindowDisplayThrottleApplierFunction, NULL);
+    CFRelease(windowDisplayInfoDictionary);
+    windowDisplayInfoDictionary = NULL;
+    
+    throttlingWindowDisplay = NO;
+}
+
 - (void)centerOverMainWindow
 {
     NSRect frameToCenterOver;
 }
 
 @end
+
+@implementation NSWindow (WebExtrasInternal)
+
+// Returns the old method implementation
+static IMP swizzleInstanceMethod(Class class, SEL selector, IMP newImplementation)
+{
+    Method method = class_getInstanceMethod(class, selector);
+    ASSERT(method);
+    IMP oldIMP;
+#if OBJC_API_VERSION > 0
+    oldIMP = method_setImplementation(method, newImplementation);
+#else
+    oldIMP = method->method_imp;
+    method->method_imp = newImplementation;
+#endif
+    return oldIMP;
+}
+
+static void replacementPostWindowNeedsDisplay(id self, SEL cmd)
+{
+    ASSERT(throttlingWindowDisplay);
+
+    // Do not call into -[NSWindow _postWindowNeedsDisplay] if requestWindowDisplay() returns NO.  In that case, requestWindowDisplay()
+    // will schedule a timer to display at the appropriate time.
+    if (requestWindowDisplay(self))
+        oldNSWindowPostWindowNeedsDisplayIMP(self, cmd);
+}
+
+static void replacementClose(id self, SEL cmd)
+{
+    ASSERT(throttlingWindowDisplay);
+
+    // Remove WindowDisplayInfo for this window
+    WindowDisplayInfo *displayInfo = (WindowDisplayInfo *)CFDictionaryGetValue(windowDisplayInfoDictionary, self);
+    if (displayInfo) {
+        cancelPendingWindowDisplay(displayInfo);
+        free(displayInfo);
+        CFDictionaryRemoveValue(windowDisplayInfoDictionary, self);
+    }
+    
+    oldNSWindowCloseIMP(self, cmd);
+}
+
+static WindowDisplayInfo *getWindowDisplayInfo(NSWindow *window)
+{
+    ASSERT(throttlingWindowDisplay);
+    ASSERT(windowDisplayInfoDictionary);
+    
+    // Get the WindowDisplayInfo for this window, or create it if it does not exist
+    WindowDisplayInfo *displayInfo = (WindowDisplayInfo *)CFDictionaryGetValue(windowDisplayInfoDictionary, window);
+    if (!displayInfo) {
+        displayInfo = (WindowDisplayInfo *)malloc(sizeof(WindowDisplayInfo));
+        displayInfo->window = window;
+        displayInfo->lastDisplayTime = 0;
+        displayInfo->displayTimer = nil;
+        CFDictionarySetValue(windowDisplayInfoDictionary, window, displayInfo);
+    }
+    
+    return displayInfo;
+}
+
+static BOOL requestWindowDisplay(NSWindow *window)
+{
+    ASSERT(throttlingWindowDisplay);
+
+    // Defer display if there is already a pending display
+    WindowDisplayInfo *displayInfo = getWindowDisplayInfo(window);
+    if (displayInfo->displayTimer)
+        return NO;
+        
+    // Defer display if it hasn't been at least DISPLAY_REFRESH_INTERVAL seconds since the last display
+    CFTimeInterval now = CFAbsoluteTimeGetCurrent();
+    CFTimeInterval timeSinceLastDisplay = now - displayInfo->lastDisplayTime;
+    if (timeSinceLastDisplay < DISPLAY_REFRESH_INTERVAL) {
+        // Redisplay soon -- if we redisplay too quickly, we'll block due to pending CG coalesced updates
+        displayInfo->displayTimer = [[NSTimer timerWithTimeInterval:(DISPLAY_REFRESH_INTERVAL - timeSinceLastDisplay)
+                                                             target:window
+                                                           selector:@selector(_webkit_doPendingPostWindowNeedsDisplay:)
+                                                           userInfo:nil
+                                                            repeats:NO] retain];
+        
+        // The NSWindow autodisplay mechanism is documented to only work for NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, and NSModalPanelRunLoopMode
+        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
+        [runLoop addTimer:displayInfo->displayTimer forMode:NSDefaultRunLoopMode];
+        [runLoop addTimer:displayInfo->displayTimer forMode:NSEventTrackingRunLoopMode];
+        [runLoop addTimer:displayInfo->displayTimer forMode:NSModalPanelRunLoopMode];
+        
+        return NO;
+    }
+
+    // Allow the display: there is no pending display, and it's been long enough to display again.
+    displayInfo->lastDisplayTime = now;
+    return YES;
+}
+
+static void cancelPendingWindowDisplay(WindowDisplayInfo *displayInfo)
+{
+    ASSERT(throttlingWindowDisplay);
+
+    if (!displayInfo->displayTimer)
+        return;
+
+    [displayInfo->displayTimer invalidate];
+    [displayInfo->displayTimer release];
+    displayInfo->displayTimer = nil;
+}
+
+- (void)_webkit_doPendingPostWindowNeedsDisplay:(NSTimer *)timer
+{
+    WindowDisplayInfo *displayInfo = getWindowDisplayInfo(self);
+    ASSERT(timer == displayInfo->displayTimer);
+    ASSERT(throttlingWindowDisplay);
+
+    // -_postWindowNeedsDisplay will short-circuit if there is a displayTimer, so invalidate it first.
+    cancelPendingWindowDisplay(displayInfo);
+
+    [self _postWindowNeedsDisplay];
+}
+
+@end
index c3da9401cf609e50e4ae36ea4cce422f7403d2d1..4cf199daac1d567d99ffdf0154450bf6798ef1b2 100644 (file)
@@ -47,6 +47,7 @@
 #import "WebNSObjectExtras.h"
 #import "WebNSPasteboardExtras.h"
 #import "WebNSViewExtras.h"
+#import "WebNSWindowExtras.h"
 #import "WebPDFView.h"
 #import "WebSystemInterface.h"
 #import "WebViewFactory.h"
@@ -66,6 +67,8 @@ enum {
     SpaceKey = 0x0020
 };
 
+static NSString *WebKitThrottleWindowDisplayPreferenceKey = @"WebKitThrottleWindowDisplay";
+
 @interface WebFrameView (WebFrameViewFileInternal) <WebCoreBridgeHolder>
 - (float)_verticalKeyboardScrollDistance;
 - (void)_tile;
@@ -309,6 +312,8 @@ static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class class,
         [WebViewFactory createSharedFactory];
         [WebImageRendererFactory createSharedFactory];
         [WebKeyGenerator createSharedGenerator];
+        if ([[NSUserDefaults standardUserDefaults] boolForKey:WebKitThrottleWindowDisplayPreferenceKey])
+            [NSWindow _webkit_enableWindowDisplayThrottle];
     }
     
     _private = [[WebFrameViewPrivate alloc] init];