WebDynamicScrollBarsView.mm revision ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddb
1/* 2 * Copyright (C) 2005, 2008, 2010 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 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#import "WebDynamicScrollBarsViewInternal.h" 27 28#import "WebDocument.h" 29#import "WebFrameInternal.h" 30#import "WebFrameView.h" 31#import "WebHTMLViewInternal.h" 32#import <WebCore/Frame.h> 33#import <WebCore/FrameView.h> 34#import <WebKitSystemInterface.h> 35 36using namespace WebCore; 37 38// FIXME: <rdar://problem/5898985> Mail expects a constant of this name to exist. 39const int WebCoreScrollbarAlwaysOn = ScrollbarAlwaysOn; 40 41#ifndef __OBJC2__ 42// In <rdar://problem/7814899> we saw crashes because WebDynamicScrollBarsView increased in size, breaking ABI compatiblity. 43COMPILE_ASSERT(sizeof(WebDynamicScrollBarsView) == 0x8c, WebDynamicScrollBarsView_is_expected_size); 44#endif 45 46struct WebDynamicScrollBarsViewPrivate { 47 unsigned inUpdateScrollersLayoutPass; 48 49 WebCore::ScrollbarMode hScroll; 50 WebCore::ScrollbarMode vScroll; 51 52 bool hScrollModeLocked; 53 bool vScrollModeLocked; 54 bool suppressLayout; 55 bool suppressScrollers; 56 bool inUpdateScrollers; 57 bool verticallyPinnedByPreviousWheelEvent; 58 bool horizontallyPinnedByPreviousWheelEvent; 59 60 bool allowsScrollersToOverlapContent; 61 bool alwaysHideHorizontalScroller; 62 bool alwaysHideVerticalScroller; 63 bool horizontalScrollingAllowedButScrollerHidden; 64 bool verticalScrollingAllowedButScrollerHidden; 65 66 // scrollOrigin is set for various combinations of writing mode and direction. 67 // See the comment next to the corresponding member in ScrollView.h. 68 NSPoint scrollOrigin; 69 70 // Flag to indicate that the scrollbar thumb's initial position needs to 71 // be manually set. 72 bool scrollOriginChanged; 73 NSPoint scrollPositionExcludingOrigin; 74 75 bool inProgrammaticScroll; 76}; 77 78@implementation WebDynamicScrollBarsView 79 80- (id)initWithFrame:(NSRect)frame 81{ 82 if (!(self = [super initWithFrame:frame])) 83 return nil; 84 85 _private = new WebDynamicScrollBarsViewPrivate; 86 memset(_private, 0, sizeof(WebDynamicScrollBarsViewPrivate)); 87 return self; 88} 89 90- (id)initWithCoder:(NSCoder *)aDecoder 91{ 92 if (!(self = [super initWithCoder:aDecoder])) 93 return nil; 94 95 _private = new WebDynamicScrollBarsViewPrivate; 96 memset(_private, 0, sizeof(WebDynamicScrollBarsViewPrivate)); 97 return self; 98} 99 100- (void)dealloc 101{ 102 delete _private; 103 [super dealloc]; 104} 105 106- (void)finalize 107{ 108 delete _private; 109 [super finalize]; 110} 111 112- (void)setAllowsHorizontalScrolling:(BOOL)flag 113{ 114 if (_private->hScrollModeLocked) 115 return; 116 if (flag && _private->hScroll == ScrollbarAlwaysOff) 117 _private->hScroll = ScrollbarAuto; 118 else if (!flag && _private->hScroll != ScrollbarAlwaysOff) 119 _private->hScroll = ScrollbarAlwaysOff; 120 [self updateScrollers]; 121} 122 123- (void)setAllowsScrollersToOverlapContent:(BOOL)flag 124{ 125 if (_private->allowsScrollersToOverlapContent == flag) 126 return; 127 128 _private->allowsScrollersToOverlapContent = flag; 129 130 [[self contentView] setFrame:[self contentViewFrame]]; 131 [[self documentView] setNeedsLayout:YES]; 132 [[self documentView] layout]; 133} 134 135- (void)setAlwaysHideHorizontalScroller:(BOOL)shouldBeHidden 136{ 137 if (_private->alwaysHideHorizontalScroller == shouldBeHidden) 138 return; 139 140 _private->alwaysHideHorizontalScroller = shouldBeHidden; 141 [self updateScrollers]; 142} 143 144- (void)setAlwaysHideVerticalScroller:(BOOL)shouldBeHidden 145{ 146 if (_private->alwaysHideVerticalScroller == shouldBeHidden) 147 return; 148 149 _private->alwaysHideVerticalScroller = shouldBeHidden; 150 [self updateScrollers]; 151} 152 153- (BOOL)horizontalScrollingAllowed 154{ 155 return _private->horizontalScrollingAllowedButScrollerHidden || [self hasHorizontalScroller]; 156} 157 158- (BOOL)verticalScrollingAllowed 159{ 160 return _private->verticalScrollingAllowedButScrollerHidden || [self hasVerticalScroller]; 161} 162 163- (BOOL)inProgramaticScroll 164{ 165 return _private->inProgrammaticScroll; 166} 167 168@end 169 170@implementation WebDynamicScrollBarsView (WebInternal) 171 172- (NSRect)contentViewFrame 173{ 174 NSRect frame = [[self contentView] frame]; 175 176 if ([self hasHorizontalScroller]) 177 frame.size.height = (_private->allowsScrollersToOverlapContent ? NSMaxY([[self horizontalScroller] frame]) : NSMinY([[self horizontalScroller] frame])); 178 if ([self hasVerticalScroller]) 179 frame.size.width = (_private->allowsScrollersToOverlapContent ? NSMaxX([[self verticalScroller] frame]) : NSMinX([[self verticalScroller] frame])); 180 return frame; 181} 182 183- (void)tile 184{ 185 [super tile]; 186 187 // [super tile] sets the contentView size so that it does not overlap with the scrollers, 188 // we want to re-set the contentView to overlap scrollers before displaying. 189 if (_private->allowsScrollersToOverlapContent) 190 [[self contentView] setFrame:[self contentViewFrame]]; 191} 192 193- (void)setSuppressLayout:(BOOL)flag 194{ 195 _private->suppressLayout = flag; 196} 197 198- (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint 199{ 200 _private->suppressScrollers = suppressed; 201 202 // This code was originally changes for a Leopard performance imporvement. We decided to 203 // ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>. 204#ifndef BUILDING_ON_TIGER 205 if (suppressed) { 206 [[self verticalScroller] setNeedsDisplay:NO]; 207 [[self horizontalScroller] setNeedsDisplay:NO]; 208 } 209 210 if (!suppressed && repaint) 211 [super reflectScrolledClipView:[self contentView]]; 212#else 213 if (suppressed || repaint) { 214 [[self verticalScroller] setNeedsDisplay:!suppressed]; 215 [[self horizontalScroller] setNeedsDisplay:!suppressed]; 216 } 217#endif 218} 219 220- (void)adjustForScrollOriginChange 221{ 222 if (!_private->scrollOriginChanged) 223 return; 224 225 _private->scrollOriginChanged = false; 226 227 NSView *documentView = [self documentView]; 228 NSRect documentRect = [documentView bounds]; 229 230 // The call to [NSView scrollPoint:] fires off notification the handler for which needs to know that 231 // we're setting the initial scroll position so it doesn't interpret this as a user action and 232 // fire off a JS event. 233 _private->inProgrammaticScroll = true; 234 [documentView scrollPoint:NSMakePoint(_private->scrollPositionExcludingOrigin.x + documentRect.origin.x, _private->scrollPositionExcludingOrigin.y + documentRect.origin.y)]; 235 _private->inProgrammaticScroll = false; 236} 237 238static const unsigned cMaxUpdateScrollbarsPass = 2; 239 240- (void)updateScrollers 241{ 242 NSView *documentView = [self documentView]; 243 244 // If we came in here with the view already needing a layout, then go ahead and do that 245 // first. (This will be the common case, e.g., when the page changes due to window resizing for example). 246 // This layout will not re-enter updateScrollers and does not count towards our max layout pass total. 247 if (!_private->suppressLayout && !_private->suppressScrollers && [documentView isKindOfClass:[WebHTMLView class]]) { 248 WebHTMLView* htmlView = (WebHTMLView*)documentView; 249 if ([htmlView _needsLayout]) { 250 _private->inUpdateScrollers = YES; 251 [(id <WebDocumentView>)documentView layout]; 252 _private->inUpdateScrollers = NO; 253 } 254 } 255 256 BOOL hasHorizontalScroller = [self hasHorizontalScroller]; 257 BOOL hasVerticalScroller = [self hasVerticalScroller]; 258 259 BOOL newHasHorizontalScroller = hasHorizontalScroller; 260 BOOL newHasVerticalScroller = hasVerticalScroller; 261 262 if (!documentView) { 263 newHasHorizontalScroller = NO; 264 newHasVerticalScroller = NO; 265 } 266 267 if (_private->hScroll != ScrollbarAuto) 268 newHasHorizontalScroller = (_private->hScroll == ScrollbarAlwaysOn); 269 if (_private->vScroll != ScrollbarAuto) 270 newHasVerticalScroller = (_private->vScroll == ScrollbarAlwaysOn); 271 272 if (!documentView || _private->suppressLayout || _private->suppressScrollers || (_private->hScroll != ScrollbarAuto && _private->vScroll != ScrollbarAuto)) { 273 _private->horizontalScrollingAllowedButScrollerHidden = newHasHorizontalScroller && _private->alwaysHideHorizontalScroller; 274 if (_private->horizontalScrollingAllowedButScrollerHidden) 275 newHasHorizontalScroller = NO; 276 277 _private->verticalScrollingAllowedButScrollerHidden = newHasVerticalScroller && _private->alwaysHideVerticalScroller; 278 if (_private->verticalScrollingAllowedButScrollerHidden) 279 newHasVerticalScroller = NO; 280 281 _private->inUpdateScrollers = YES; 282 if (hasHorizontalScroller != newHasHorizontalScroller) 283 [self setHasHorizontalScroller:newHasHorizontalScroller]; 284 if (hasVerticalScroller != newHasVerticalScroller) 285 [self setHasVerticalScroller:newHasVerticalScroller]; 286 if (_private->suppressScrollers) { 287 [[self verticalScroller] setNeedsDisplay:NO]; 288 [[self horizontalScroller] setNeedsDisplay:NO]; 289 } 290 _private->inUpdateScrollers = NO; 291 return; 292 } 293 294 BOOL needsLayout = NO; 295 296 NSSize documentSize = [documentView frame].size; 297 NSSize visibleSize = [self documentVisibleRect].size; 298 NSSize frameSize = [self frame].size; 299 300 // When in HiDPI with a scale factor > 1, the visibleSize and frameSize may be non-integral values, 301 // while the documentSize (set by WebCore) will be integral. Round up the non-integral sizes so that 302 // the mismatch won't cause unwanted scrollbars to appear. This can result in slightly cut off content, 303 // but it will always be less than one pixel, which should not be noticeable. 304 visibleSize.width = ceilf(visibleSize.width); 305 visibleSize.height = ceilf(visibleSize.height); 306 frameSize.width = ceilf(frameSize.width); 307 frameSize.height = ceilf(frameSize.height); 308 309 if (_private->hScroll == ScrollbarAuto) { 310 newHasHorizontalScroller = documentSize.width > visibleSize.width; 311 if (newHasHorizontalScroller && !_private->inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width) 312 newHasHorizontalScroller = NO; 313 } 314 315 if (_private->vScroll == ScrollbarAuto) { 316 newHasVerticalScroller = documentSize.height > visibleSize.height; 317 if (newHasVerticalScroller && !_private->inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width) 318 newHasVerticalScroller = NO; 319 } 320 321 // Unless in ScrollbarsAlwaysOn mode, if we ever turn one scrollbar off, always turn the other one off too. 322 // Never ever try to both gain/lose a scrollbar in the same pass. 323 if (!newHasHorizontalScroller && hasHorizontalScroller && _private->vScroll != ScrollbarAlwaysOn) 324 newHasVerticalScroller = NO; 325 if (!newHasVerticalScroller && hasVerticalScroller && _private->hScroll != ScrollbarAlwaysOn) 326 newHasHorizontalScroller = NO; 327 328 _private->horizontalScrollingAllowedButScrollerHidden = newHasHorizontalScroller && _private->alwaysHideHorizontalScroller; 329 if (_private->horizontalScrollingAllowedButScrollerHidden) 330 newHasHorizontalScroller = NO; 331 332 _private->verticalScrollingAllowedButScrollerHidden = newHasVerticalScroller && _private->alwaysHideVerticalScroller; 333 if (_private->verticalScrollingAllowedButScrollerHidden) 334 newHasVerticalScroller = NO; 335 336 if (hasHorizontalScroller != newHasHorizontalScroller) { 337 _private->inUpdateScrollers = YES; 338 [self setHasHorizontalScroller:newHasHorizontalScroller]; 339 _private->inUpdateScrollers = NO; 340 needsLayout = YES; 341 NSView *documentView = [self documentView]; 342 NSRect documentRect = [documentView bounds]; 343 if (documentRect.origin.y < 0 && !newHasHorizontalScroller) 344 [documentView setBoundsOrigin:NSMakePoint(documentRect.origin.x, documentRect.origin.y + 15)]; 345 } 346 347 if (hasVerticalScroller != newHasVerticalScroller) { 348 _private->inUpdateScrollers = YES; 349 [self setHasVerticalScroller:newHasVerticalScroller]; 350 _private->inUpdateScrollers = NO; 351 needsLayout = YES; 352 NSView *documentView = [self documentView]; 353 NSRect documentRect = [documentView bounds]; 354 if (documentRect.origin.x < 0 && !newHasVerticalScroller) 355 [documentView setBoundsOrigin:NSMakePoint(documentRect.origin.x + 15, documentRect.origin.y)]; 356 } 357 358 if (needsLayout && _private->inUpdateScrollersLayoutPass < cMaxUpdateScrollbarsPass && 359 [documentView conformsToProtocol:@protocol(WebDocumentView)]) { 360 _private->inUpdateScrollersLayoutPass++; 361 [(id <WebDocumentView>)documentView setNeedsLayout:YES]; 362 [(id <WebDocumentView>)documentView layout]; 363 NSSize newDocumentSize = [documentView frame].size; 364 if (NSEqualSizes(documentSize, newDocumentSize)) { 365 // The layout with the new scroll state had no impact on 366 // the document's overall size, so updateScrollers didn't get called. 367 // Recur manually. 368 [self updateScrollers]; 369 } 370 _private->inUpdateScrollersLayoutPass--; 371 } 372} 373 374// Make the horizontal and vertical scroll bars come and go as needed. 375- (void)reflectScrolledClipView:(NSClipView *)clipView 376{ 377 if (clipView == [self contentView]) { 378 // Prevent appearance of trails because of overlapping views 379 if (_private->allowsScrollersToOverlapContent) 380 [self setDrawsBackground:NO]; 381 382 // FIXME: This hack here prevents infinite recursion that takes place when we 383 // gyrate between having a vertical scroller and not having one. A reproducible 384 // case is clicking on the "the Policy Routing text" link at 385 // http://www.linuxpowered.com/archive/howto/Net-HOWTO-8.html. 386 // The underlying cause is some problem in the NSText machinery, but I was not 387 // able to pin it down. 388 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; 389 if (!_private->inUpdateScrollers && (!currentContext || [currentContext isDrawingToScreen])) 390 [self updateScrollers]; 391 } 392 393 // This code was originally changed for a Leopard performance imporvement. We decided to 394 // ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>. 395#ifndef BUILDING_ON_TIGER 396 // Update the scrollers if they're not being suppressed. 397 if (!_private->suppressScrollers) 398 [super reflectScrolledClipView:clipView]; 399#else 400 [super reflectScrolledClipView:clipView]; 401 402 // Validate the scrollers if they're being suppressed. 403 if (_private->suppressScrollers) { 404 [[self verticalScroller] setNeedsDisplay:NO]; 405 [[self horizontalScroller] setNeedsDisplay:NO]; 406 } 407#endif 408 409 // The call to [NSView reflectScrolledClipView] sets the scrollbar thumb 410 // position to 0 (the left) when the view is initially displayed. 411 // This call updates the initial position correctly. 412 [self adjustForScrollOriginChange]; 413 414#if USE(ACCELERATED_COMPOSITING) && defined(BUILDING_ON_LEOPARD) 415 NSView *documentView = [self documentView]; 416 if ([documentView isKindOfClass:[WebHTMLView class]]) { 417 WebHTMLView *htmlView = (WebHTMLView *)documentView; 418 if ([htmlView _isUsingAcceleratedCompositing]) 419 [htmlView _updateLayerHostingViewPosition]; 420 } 421#endif 422} 423 424- (BOOL)allowsHorizontalScrolling 425{ 426 return _private->hScroll != ScrollbarAlwaysOff; 427} 428 429- (BOOL)allowsVerticalScrolling 430{ 431 return _private->vScroll != ScrollbarAlwaysOff; 432} 433 434- (void)scrollingModes:(WebCore::ScrollbarMode*)hMode vertical:(WebCore::ScrollbarMode*)vMode 435{ 436 *hMode = _private->hScroll; 437 *vMode = _private->vScroll; 438} 439 440- (ScrollbarMode)horizontalScrollingMode 441{ 442 return _private->hScroll; 443} 444 445- (ScrollbarMode)verticalScrollingMode 446{ 447 return _private->vScroll; 448} 449 450- (void)setHorizontalScrollingMode:(ScrollbarMode)horizontalMode andLock:(BOOL)lock 451{ 452 [self setScrollingModes:horizontalMode vertical:[self verticalScrollingMode] andLock:lock]; 453} 454 455- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode andLock:(BOOL)lock 456{ 457 [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:lock]; 458} 459 460// Mail uses this method, so we cannot remove it. 461- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode 462{ 463 [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:NO]; 464} 465 466- (void)setScrollingModes:(ScrollbarMode)horizontalMode vertical:(ScrollbarMode)verticalMode andLock:(BOOL)lock 467{ 468 BOOL update = NO; 469 if (verticalMode != _private->vScroll && !_private->vScrollModeLocked) { 470 _private->vScroll = verticalMode; 471 update = YES; 472 } 473 474 if (horizontalMode != _private->hScroll && !_private->hScrollModeLocked) { 475 _private->hScroll = horizontalMode; 476 update = YES; 477 } 478 479 if (lock) 480 [self setScrollingModesLocked:YES]; 481 482 if (update) 483 [self updateScrollers]; 484} 485 486- (void)setHorizontalScrollingModeLocked:(BOOL)locked 487{ 488 _private->hScrollModeLocked = locked; 489} 490 491- (void)setVerticalScrollingModeLocked:(BOOL)locked 492{ 493 _private->vScrollModeLocked = locked; 494} 495 496- (void)setScrollingModesLocked:(BOOL)locked 497{ 498 _private->hScrollModeLocked = _private->vScrollModeLocked = locked; 499} 500 501- (BOOL)horizontalScrollingModeLocked 502{ 503 return _private->hScrollModeLocked; 504} 505 506- (BOOL)verticalScrollingModeLocked 507{ 508 return _private->vScrollModeLocked; 509} 510 511- (BOOL)autoforwardsScrollWheelEvents 512{ 513 return YES; 514} 515 516- (void)scrollWheel:(NSEvent *)event 517{ 518 float deltaX; 519 float deltaY; 520 BOOL isContinuous; 521 WKGetWheelEventDeltas(event, &deltaX, &deltaY, &isContinuous); 522 523 BOOL isLatchingEvent = WKIsLatchingWheelEvent(event); 524 525 if (fabsf(deltaY) > fabsf(deltaX)) { 526 if (![self allowsVerticalScrolling]) { 527 [[self nextResponder] scrollWheel:event]; 528 return; 529 } 530 531 if (isLatchingEvent && !_private->verticallyPinnedByPreviousWheelEvent) { 532 double verticalPosition = [[self verticalScroller] doubleValue]; 533 if ((deltaY >= 0.0 && verticalPosition == 0.0) || (deltaY <= 0.0 && verticalPosition == 1.0)) 534 return; 535 } 536 } else { 537 if (![self allowsHorizontalScrolling]) { 538 [[self nextResponder] scrollWheel:event]; 539 return; 540 } 541 542 if (isLatchingEvent && !_private->horizontallyPinnedByPreviousWheelEvent) { 543 double horizontalPosition = [[self horizontalScroller] doubleValue]; 544 if ((deltaX >= 0.0 && horizontalPosition == 0.0) || (deltaX <= 0.0 && horizontalPosition == 1.0)) 545 return; 546 } 547 } 548 549 // Calling super can release the last reference. <rdar://problem/7400263> 550 // Hold a reference so the code following the super call will not crash. 551 [self retain]; 552 553 [super scrollWheel:event]; 554 555 if (!isLatchingEvent) { 556 double verticalPosition = [[self verticalScroller] doubleValue]; 557 double horizontalPosition = [[self horizontalScroller] doubleValue]; 558 559 _private->verticallyPinnedByPreviousWheelEvent = (verticalPosition == 0.0 || verticalPosition == 1.0); 560 _private->horizontallyPinnedByPreviousWheelEvent = (horizontalPosition == 0.0 || horizontalPosition == 1.0); 561 } 562 563 [self release]; 564} 565 566- (BOOL)accessibilityIsIgnored 567{ 568 return YES; 569} 570 571- (void)setScrollOrigin:(NSPoint)scrollOrigin updatePosition:(BOOL)updatePosition 572{ 573 // The cross-platform ScrollView call already checked to see if the old/new scroll origins were the same or not 574 // so we don't have to check for equivalence here. 575 _private->scrollOrigin = scrollOrigin; 576 id docView = [self documentView]; 577 578 NSRect visibleRect = [self documentVisibleRect]; 579 580 [docView setBoundsOrigin:NSMakePoint(-scrollOrigin.x, -scrollOrigin.y)]; 581 582 _private->scrollOriginChanged = true; 583 584 // Maintain our original position in the presence of the new scroll origin. 585 _private->scrollPositionExcludingOrigin = NSMakePoint(visibleRect.origin.x + scrollOrigin.x, visibleRect.origin.y + scrollOrigin.y); 586 587 if (updatePosition) // Otherwise we'll just let the snap happen when we update for the resize. 588 [self adjustForScrollOriginChange]; 589} 590 591- (NSPoint)scrollOrigin 592{ 593 return _private->scrollOrigin; 594} 595 596@end 597