1/* 2 * Copyright (C) 2005, 2008 Apple 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 "WebDynamicScrollBarsViewInternal.h" 30 31#import "WebDocument.h" 32#import "WebFrameInternal.h" 33#import "WebFrameView.h" 34#import "WebHTMLViewInternal.h" 35#import <WebCore/Frame.h> 36#import <WebCore/FrameView.h> 37#import <WebKitSystemInterface.h> 38 39using namespace WebCore; 40 41// FIXME: <rdar://problem/5898985> Mail expects a constant of this name to exist. 42const int WebCoreScrollbarAlwaysOn = ScrollbarAlwaysOn; 43 44@implementation WebDynamicScrollBarsView 45 46- (void)setAllowsHorizontalScrolling:(BOOL)flag 47{ 48 if (hScrollModeLocked) 49 return; 50 if (flag && hScroll == ScrollbarAlwaysOff) 51 hScroll = ScrollbarAuto; 52 else if (!flag && hScroll != ScrollbarAlwaysOff) 53 hScroll = ScrollbarAlwaysOff; 54 [self updateScrollers]; 55} 56 57@end 58 59@implementation WebDynamicScrollBarsView (WebInternal) 60 61- (void)setSuppressLayout:(BOOL)flag; 62{ 63 suppressLayout = flag; 64} 65 66- (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint 67{ 68 suppressScrollers = suppressed; 69 70 // This code was originally changes for a Leopard performance imporvement. We decided to 71 // ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>. 72#ifndef BUILDING_ON_TIGER 73 if (suppressed) { 74 [[self verticalScroller] setNeedsDisplay:NO]; 75 [[self horizontalScroller] setNeedsDisplay:NO]; 76 } 77 78 if (!suppressed && repaint) 79 [super reflectScrolledClipView:[self contentView]]; 80#else 81 if (suppressed || repaint) { 82 [[self verticalScroller] setNeedsDisplay: !suppressed]; 83 [[self horizontalScroller] setNeedsDisplay: !suppressed]; 84 } 85#endif 86} 87 88static const unsigned cMaxUpdateScrollbarsPass = 2; 89 90- (void)updateScrollers 91{ 92 NSView *documentView = [self documentView]; 93 94 // If we came in here with the view already needing a layout, then go ahead and do that 95 // first. (This will be the common case, e.g., when the page changes due to window resizing for example). 96 // This layout will not re-enter updateScrollers and does not count towards our max layout pass total. 97 if (!suppressLayout && !suppressScrollers && [documentView isKindOfClass:[WebHTMLView class]]) { 98 WebHTMLView* htmlView = (WebHTMLView*)documentView; 99 if ([htmlView _needsLayout]) { 100 inUpdateScrollers = YES; 101 [(id <WebDocumentView>)documentView layout]; 102 inUpdateScrollers = NO; 103 } 104 } 105 106 BOOL hasHorizontalScroller = [self hasHorizontalScroller]; 107 BOOL hasVerticalScroller = [self hasVerticalScroller]; 108 109 BOOL newHasHorizontalScroller = hasHorizontalScroller; 110 BOOL newHasVerticalScroller = hasVerticalScroller; 111 112 if (!documentView) { 113 newHasHorizontalScroller = NO; 114 newHasVerticalScroller = NO; 115 } 116 117 if (hScroll != ScrollbarAuto) 118 newHasHorizontalScroller = (hScroll == ScrollbarAlwaysOn); 119 if (vScroll != ScrollbarAuto) 120 newHasVerticalScroller = (vScroll == ScrollbarAlwaysOn); 121 122 if (!documentView || suppressLayout || suppressScrollers || (hScroll != ScrollbarAuto && vScroll != ScrollbarAuto)) { 123 inUpdateScrollers = YES; 124 if (hasHorizontalScroller != newHasHorizontalScroller) 125 [self setHasHorizontalScroller:newHasHorizontalScroller]; 126 if (hasVerticalScroller != newHasVerticalScroller) 127 [self setHasVerticalScroller:newHasVerticalScroller]; 128 if (suppressScrollers) { 129 [[self verticalScroller] setNeedsDisplay:NO]; 130 [[self horizontalScroller] setNeedsDisplay:NO]; 131 } 132 inUpdateScrollers = NO; 133 return; 134 } 135 136 BOOL needsLayout = NO; 137 138 NSSize documentSize = [documentView frame].size; 139 NSSize visibleSize = [self documentVisibleRect].size; 140 NSSize frameSize = [self frame].size; 141 142 if (hScroll == ScrollbarAuto) { 143 newHasHorizontalScroller = documentSize.width > visibleSize.width; 144 if (newHasHorizontalScroller && !inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width) 145 newHasHorizontalScroller = NO; 146 } 147 148 if (vScroll == ScrollbarAuto) { 149 newHasVerticalScroller = documentSize.height > visibleSize.height; 150 if (newHasVerticalScroller && !inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width) 151 newHasVerticalScroller = NO; 152 } 153 154 // Unless in ScrollbarsAlwaysOn mode, if we ever turn one scrollbar off, always turn the other one off too. 155 // Never ever try to both gain/lose a scrollbar in the same pass. 156 if (!newHasHorizontalScroller && hasHorizontalScroller && vScroll != ScrollbarAlwaysOn) 157 newHasVerticalScroller = NO; 158 if (!newHasVerticalScroller && hasVerticalScroller && hScroll != ScrollbarAlwaysOn) 159 newHasHorizontalScroller = NO; 160 161 if (hasHorizontalScroller != newHasHorizontalScroller) { 162 inUpdateScrollers = YES; 163 [self setHasHorizontalScroller:newHasHorizontalScroller]; 164 inUpdateScrollers = NO; 165 needsLayout = YES; 166 } 167 168 if (hasVerticalScroller != newHasVerticalScroller) { 169 inUpdateScrollers = YES; 170 [self setHasVerticalScroller:newHasVerticalScroller]; 171 inUpdateScrollers = NO; 172 needsLayout = YES; 173 } 174 175 if (needsLayout && inUpdateScrollersLayoutPass < cMaxUpdateScrollbarsPass && 176 [documentView conformsToProtocol:@protocol(WebDocumentView)]) { 177 inUpdateScrollersLayoutPass++; 178 [(id <WebDocumentView>)documentView setNeedsLayout:YES]; 179 [(id <WebDocumentView>)documentView layout]; 180 NSSize newDocumentSize = [documentView frame].size; 181 if (NSEqualSizes(documentSize, newDocumentSize)) { 182 // The layout with the new scroll state had no impact on 183 // the document's overall size, so updateScrollers didn't get called. 184 // Recur manually. 185 [self updateScrollers]; 186 } 187 inUpdateScrollersLayoutPass--; 188 } 189} 190 191// Make the horizontal and vertical scroll bars come and go as needed. 192- (void)reflectScrolledClipView:(NSClipView *)clipView 193{ 194 if (clipView == [self contentView]) { 195 // FIXME: This hack here prevents infinite recursion that takes place when we 196 // gyrate between having a vertical scroller and not having one. A reproducible 197 // case is clicking on the "the Policy Routing text" link at 198 // http://www.linuxpowered.com/archive/howto/Net-HOWTO-8.html. 199 // The underlying cause is some problem in the NSText machinery, but I was not 200 // able to pin it down. 201 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; 202 if (!inUpdateScrollers && (!currentContext || [currentContext isDrawingToScreen])) 203 [self updateScrollers]; 204 } 205 206 // This code was originally changed for a Leopard performance imporvement. We decided to 207 // ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>. 208#ifndef BUILDING_ON_TIGER 209 // Update the scrollers if they're not being suppressed. 210 if (!suppressScrollers) 211 [super reflectScrolledClipView:clipView]; 212#else 213 [super reflectScrolledClipView:clipView]; 214 215 // Validate the scrollers if they're being suppressed. 216 if (suppressScrollers) { 217 [[self verticalScroller] setNeedsDisplay: NO]; 218 [[self horizontalScroller] setNeedsDisplay: NO]; 219 } 220#endif 221 222#if USE(ACCELERATED_COMPOSITING) && defined(BUILDING_ON_LEOPARD) 223 NSView *documentView = [self documentView]; 224 if ([documentView isKindOfClass:[WebHTMLView class]]) { 225 WebHTMLView *htmlView = (WebHTMLView *)documentView; 226 if ([htmlView _isUsingAcceleratedCompositing]) 227 [htmlView _updateLayerHostingViewPosition]; 228 } 229#endif 230} 231 232- (BOOL)allowsHorizontalScrolling 233{ 234 return hScroll != ScrollbarAlwaysOff; 235} 236 237- (BOOL)allowsVerticalScrolling 238{ 239 return vScroll != ScrollbarAlwaysOff; 240} 241 242- (void)scrollingModes:(WebCore::ScrollbarMode*)hMode vertical:(WebCore::ScrollbarMode*)vMode 243{ 244 *hMode = static_cast<ScrollbarMode>(hScroll); 245 *vMode = static_cast<ScrollbarMode>(vScroll); 246} 247 248- (ScrollbarMode)horizontalScrollingMode 249{ 250 return static_cast<ScrollbarMode>(hScroll); 251} 252 253- (ScrollbarMode)verticalScrollingMode 254{ 255 return static_cast<ScrollbarMode>(vScroll); 256} 257 258- (void)setHorizontalScrollingMode:(ScrollbarMode)horizontalMode andLock:(BOOL)lock 259{ 260 [self setScrollingModes:horizontalMode vertical:[self verticalScrollingMode] andLock:lock]; 261} 262 263- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode andLock:(BOOL)lock 264{ 265 [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:lock]; 266} 267 268// Mail uses this method, so we cannot remove it. 269- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode 270{ 271 [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:NO]; 272} 273 274- (void)setScrollingModes:(ScrollbarMode)horizontalMode vertical:(ScrollbarMode)verticalMode andLock:(BOOL)lock 275{ 276 BOOL update = NO; 277 if (verticalMode != vScroll && !vScrollModeLocked) { 278 vScroll = verticalMode; 279 update = YES; 280 } 281 282 if (horizontalMode != hScroll && !hScrollModeLocked) { 283 hScroll = horizontalMode; 284 update = YES; 285 } 286 287 if (lock) 288 [self setScrollingModesLocked:YES]; 289 290 if (update) 291 [self updateScrollers]; 292} 293 294- (void)setHorizontalScrollingModeLocked:(BOOL)locked 295{ 296 hScrollModeLocked = locked; 297} 298 299- (void)setVerticalScrollingModeLocked:(BOOL)locked 300{ 301 vScrollModeLocked = locked; 302} 303 304- (void)setScrollingModesLocked:(BOOL)locked 305{ 306 hScrollModeLocked = vScrollModeLocked = locked; 307} 308 309- (BOOL)horizontalScrollingModeLocked 310{ 311 return hScrollModeLocked; 312} 313 314- (BOOL)verticalScrollingModeLocked 315{ 316 return vScrollModeLocked; 317} 318 319- (BOOL)autoforwardsScrollWheelEvents 320{ 321 return YES; 322} 323 324- (void)scrollWheel:(NSEvent *)event 325{ 326 float deltaX; 327 float deltaY; 328 BOOL isContinuous; 329 WKGetWheelEventDeltas(event, &deltaX, &deltaY, &isContinuous); 330 331 BOOL isLatchingEvent = WKIsLatchingWheelEvent(event); 332 333 if (fabsf(deltaY) > fabsf(deltaX)) { 334 if (![self allowsVerticalScrolling]) { 335 [[self nextResponder] scrollWheel:event]; 336 return; 337 } 338 339 if (isLatchingEvent && !verticallyPinnedByPreviousWheelEvent) { 340 double verticalPosition = [[self verticalScroller] doubleValue]; 341 if ((deltaY >= 0.0 && verticalPosition == 0.0) || (deltaY <= 0.0 && verticalPosition == 1.0)) 342 return; 343 } 344 } else { 345 if (![self allowsHorizontalScrolling]) { 346 [[self nextResponder] scrollWheel:event]; 347 return; 348 } 349 350 if (isLatchingEvent && !horizontallyPinnedByPreviousWheelEvent) { 351 double horizontalPosition = [[self horizontalScroller] doubleValue]; 352 if ((deltaX >= 0.0 && horizontalPosition == 0.0) || (deltaX <= 0.0 && horizontalPosition == 1.0)) 353 return; 354 } 355 } 356 357 // Calling super can release the last reference. <rdar://problem/7400263> 358 // Hold a reference so the code following the super call will not crash. 359 [self retain]; 360 361 [super scrollWheel:event]; 362 363 if (!isLatchingEvent) { 364 double verticalPosition = [[self verticalScroller] doubleValue]; 365 double horizontalPosition = [[self horizontalScroller] doubleValue]; 366 367 verticallyPinnedByPreviousWheelEvent = (verticalPosition == 0.0 || verticalPosition == 1.0); 368 horizontallyPinnedByPreviousWheelEvent = (horizontalPosition == 0.0 || horizontalPosition == 1.0); 369 } 370 371 [self release]; 372} 373 374- (BOOL)accessibilityIsIgnored 375{ 376 id docView = [self documentView]; 377 if ([docView isKindOfClass:[WebFrameView class]] && ![(WebFrameView *)docView allowsScrolling]) 378 return YES; 379 380 return [super accessibilityIsIgnored]; 381} 382 383@end 384