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@end 164 165@implementation WebDynamicScrollBarsView (WebInternal) 166 167- (NSRect)contentViewFrame 168{ 169 NSRect frame = [[self contentView] frame]; 170 171 if ([self hasHorizontalScroller]) 172 frame.size.height = (_private->allowsScrollersToOverlapContent ? NSMaxY([[self horizontalScroller] frame]) : NSMinY([[self horizontalScroller] frame])); 173 if ([self hasVerticalScroller]) 174 frame.size.width = (_private->allowsScrollersToOverlapContent ? NSMaxX([[self verticalScroller] frame]) : NSMinX([[self verticalScroller] frame])); 175 return frame; 176} 177 178- (void)tile 179{ 180 [super tile]; 181 182 // [super tile] sets the contentView size so that it does not overlap with the scrollers, 183 // we want to re-set the contentView to overlap scrollers before displaying. 184 if (_private->allowsScrollersToOverlapContent) 185 [[self contentView] setFrame:[self contentViewFrame]]; 186} 187 188- (void)setSuppressLayout:(BOOL)flag 189{ 190 _private->suppressLayout = flag; 191} 192 193- (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint 194{ 195 _private->suppressScrollers = suppressed; 196 197 // This code was originally changes for a Leopard performance imporvement. We decided to 198 // ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>. 199#ifndef BUILDING_ON_TIGER 200 if (suppressed) { 201 [[self verticalScroller] setNeedsDisplay:NO]; 202 [[self horizontalScroller] setNeedsDisplay:NO]; 203 } 204 205 if (!suppressed && repaint) 206 [super reflectScrolledClipView:[self contentView]]; 207#else 208 if (suppressed || repaint) { 209 [[self verticalScroller] setNeedsDisplay:!suppressed]; 210 [[self horizontalScroller] setNeedsDisplay:!suppressed]; 211 } 212#endif 213} 214 215- (void)adjustForScrollOriginChange 216{ 217 if (!_private->scrollOriginChanged) 218 return; 219 220 _private->scrollOriginChanged = false; 221 222 NSView *documentView = [self documentView]; 223 NSRect documentRect = [documentView bounds]; 224 225 // The call to [NSView scrollPoint:] fires off notification the handler for which needs to know that 226 // we're setting the initial scroll position so it doesn't interpret this as a user action and 227 // fire off a JS event. 228 _private->inProgrammaticScroll = true; 229 [documentView scrollPoint:NSMakePoint(_private->scrollPositionExcludingOrigin.x + documentRect.origin.x, _private->scrollPositionExcludingOrigin.y + documentRect.origin.y)]; 230 _private->inProgrammaticScroll = false; 231} 232 233static const unsigned cMaxUpdateScrollbarsPass = 2; 234 235- (void)updateScrollers 236{ 237 NSView *documentView = [self documentView]; 238 239 // If we came in here with the view already needing a layout, then go ahead and do that 240 // first. (This will be the common case, e.g., when the page changes due to window resizing for example). 241 // This layout will not re-enter updateScrollers and does not count towards our max layout pass total. 242 if (!_private->suppressLayout && !_private->suppressScrollers && [documentView isKindOfClass:[WebHTMLView class]]) { 243 WebHTMLView* htmlView = (WebHTMLView*)documentView; 244 if ([htmlView _needsLayout]) { 245 _private->inUpdateScrollers = YES; 246 [(id <WebDocumentView>)documentView layout]; 247 _private->inUpdateScrollers = NO; 248 } 249 } 250 251 BOOL hasHorizontalScroller = [self hasHorizontalScroller]; 252 BOOL hasVerticalScroller = [self hasVerticalScroller]; 253 254 BOOL newHasHorizontalScroller = hasHorizontalScroller; 255 BOOL newHasVerticalScroller = hasVerticalScroller; 256 257 if (!documentView) { 258 newHasHorizontalScroller = NO; 259 newHasVerticalScroller = NO; 260 } 261 262 if (_private->hScroll != ScrollbarAuto) 263 newHasHorizontalScroller = (_private->hScroll == ScrollbarAlwaysOn); 264 if (_private->vScroll != ScrollbarAuto) 265 newHasVerticalScroller = (_private->vScroll == ScrollbarAlwaysOn); 266 267 if (!documentView || _private->suppressLayout || _private->suppressScrollers || (_private->hScroll != ScrollbarAuto && _private->vScroll != ScrollbarAuto)) { 268 _private->horizontalScrollingAllowedButScrollerHidden = newHasHorizontalScroller && _private->alwaysHideHorizontalScroller; 269 if (_private->horizontalScrollingAllowedButScrollerHidden) 270 newHasHorizontalScroller = NO; 271 272 _private->verticalScrollingAllowedButScrollerHidden = newHasVerticalScroller && _private->alwaysHideVerticalScroller; 273 if (_private->verticalScrollingAllowedButScrollerHidden) 274 newHasVerticalScroller = NO; 275 276 _private->inUpdateScrollers = YES; 277 if (hasHorizontalScroller != newHasHorizontalScroller) 278 [self setHasHorizontalScroller:newHasHorizontalScroller]; 279 if (hasVerticalScroller != newHasVerticalScroller) 280 [self setHasVerticalScroller:newHasVerticalScroller]; 281 if (_private->suppressScrollers) { 282 [[self verticalScroller] setNeedsDisplay:NO]; 283 [[self horizontalScroller] setNeedsDisplay:NO]; 284 } 285 _private->inUpdateScrollers = NO; 286 return; 287 } 288 289 BOOL needsLayout = NO; 290 291 NSSize documentSize = [documentView frame].size; 292 NSSize visibleSize = [self documentVisibleRect].size; 293 NSSize frameSize = [self frame].size; 294 295 // When in HiDPI with a scale factor > 1, the visibleSize and frameSize may be non-integral values, 296 // while the documentSize (set by WebCore) will be integral. Round up the non-integral sizes so that 297 // the mismatch won't cause unwanted scrollbars to appear. This can result in slightly cut off content, 298 // but it will always be less than one pixel, which should not be noticeable. 299 visibleSize.width = ceilf(visibleSize.width); 300 visibleSize.height = ceilf(visibleSize.height); 301 frameSize.width = ceilf(frameSize.width); 302 frameSize.height = ceilf(frameSize.height); 303 304 if (_private->hScroll == ScrollbarAuto) { 305 newHasHorizontalScroller = documentSize.width > visibleSize.width; 306 if (newHasHorizontalScroller && !_private->inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width) 307 newHasHorizontalScroller = NO; 308 } 309 310 if (_private->vScroll == ScrollbarAuto) { 311 newHasVerticalScroller = documentSize.height > visibleSize.height; 312 if (newHasVerticalScroller && !_private->inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width) 313 newHasVerticalScroller = NO; 314 } 315 316 // Unless in ScrollbarsAlwaysOn mode, if we ever turn one scrollbar off, always turn the other one off too. 317 // Never ever try to both gain/lose a scrollbar in the same pass. 318 if (!newHasHorizontalScroller && hasHorizontalScroller && _private->vScroll != ScrollbarAlwaysOn) 319 newHasVerticalScroller = NO; 320 if (!newHasVerticalScroller && hasVerticalScroller && _private->hScroll != ScrollbarAlwaysOn) 321 newHasHorizontalScroller = NO; 322 323 _private->horizontalScrollingAllowedButScrollerHidden = newHasHorizontalScroller && _private->alwaysHideHorizontalScroller; 324 if (_private->horizontalScrollingAllowedButScrollerHidden) 325 newHasHorizontalScroller = NO; 326 327 _private->verticalScrollingAllowedButScrollerHidden = newHasVerticalScroller && _private->alwaysHideVerticalScroller; 328 if (_private->verticalScrollingAllowedButScrollerHidden) 329 newHasVerticalScroller = NO; 330 331 if (hasHorizontalScroller != newHasHorizontalScroller) { 332 _private->inUpdateScrollers = YES; 333 [self setHasHorizontalScroller:newHasHorizontalScroller]; 334 _private->inUpdateScrollers = NO; 335 needsLayout = YES; 336 NSView *documentView = [self documentView]; 337 NSRect documentRect = [documentView bounds]; 338 if (documentRect.origin.y < 0 && !newHasHorizontalScroller) 339 [documentView setBoundsOrigin:NSMakePoint(documentRect.origin.x, documentRect.origin.y + 15)]; 340 } 341 342 if (hasVerticalScroller != newHasVerticalScroller) { 343 _private->inUpdateScrollers = YES; 344 [self setHasVerticalScroller:newHasVerticalScroller]; 345 _private->inUpdateScrollers = NO; 346 needsLayout = YES; 347 NSView *documentView = [self documentView]; 348 NSRect documentRect = [documentView bounds]; 349 if (documentRect.origin.x < 0 && !newHasVerticalScroller) 350 [documentView setBoundsOrigin:NSMakePoint(documentRect.origin.x + 15, documentRect.origin.y)]; 351 } 352 353 if (needsLayout && _private->inUpdateScrollersLayoutPass < cMaxUpdateScrollbarsPass && 354 [documentView conformsToProtocol:@protocol(WebDocumentView)]) { 355 _private->inUpdateScrollersLayoutPass++; 356 [(id <WebDocumentView>)documentView setNeedsLayout:YES]; 357 [(id <WebDocumentView>)documentView layout]; 358 NSSize newDocumentSize = [documentView frame].size; 359 if (NSEqualSizes(documentSize, newDocumentSize)) { 360 // The layout with the new scroll state had no impact on 361 // the document's overall size, so updateScrollers didn't get called. 362 // Recur manually. 363 [self updateScrollers]; 364 } 365 _private->inUpdateScrollersLayoutPass--; 366 } 367} 368 369// Make the horizontal and vertical scroll bars come and go as needed. 370- (void)reflectScrolledClipView:(NSClipView *)clipView 371{ 372 if (clipView == [self contentView]) { 373 // Prevent appearance of trails because of overlapping views 374 if (_private->allowsScrollersToOverlapContent) 375 [self setDrawsBackground:NO]; 376 377 // FIXME: This hack here prevents infinite recursion that takes place when we 378 // gyrate between having a vertical scroller and not having one. A reproducible 379 // case is clicking on the "the Policy Routing text" link at 380 // http://www.linuxpowered.com/archive/howto/Net-HOWTO-8.html. 381 // The underlying cause is some problem in the NSText machinery, but I was not 382 // able to pin it down. 383 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; 384 if (!_private->inUpdateScrollers && (!currentContext || [currentContext isDrawingToScreen])) 385 [self updateScrollers]; 386 } 387 388 // This code was originally changed for a Leopard performance imporvement. We decided to 389 // ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>. 390#ifndef BUILDING_ON_TIGER 391 // Update the scrollers if they're not being suppressed. 392 if (!_private->suppressScrollers) 393 [super reflectScrolledClipView:clipView]; 394#else 395 [super reflectScrolledClipView:clipView]; 396 397 // Validate the scrollers if they're being suppressed. 398 if (_private->suppressScrollers) { 399 [[self verticalScroller] setNeedsDisplay:NO]; 400 [[self horizontalScroller] setNeedsDisplay:NO]; 401 } 402#endif 403 404 // The call to [NSView reflectScrolledClipView] sets the scrollbar thumb 405 // position to 0 (the left) when the view is initially displayed. 406 // This call updates the initial position correctly. 407 [self adjustForScrollOriginChange]; 408 409#if USE(ACCELERATED_COMPOSITING) && defined(BUILDING_ON_LEOPARD) 410 NSView *documentView = [self documentView]; 411 if ([documentView isKindOfClass:[WebHTMLView class]]) { 412 WebHTMLView *htmlView = (WebHTMLView *)documentView; 413 if ([htmlView _isUsingAcceleratedCompositing]) 414 [htmlView _updateLayerHostingViewPosition]; 415 } 416#endif 417} 418 419- (BOOL)allowsHorizontalScrolling 420{ 421 return _private->hScroll != ScrollbarAlwaysOff; 422} 423 424- (BOOL)allowsVerticalScrolling 425{ 426 return _private->vScroll != ScrollbarAlwaysOff; 427} 428 429- (void)scrollingModes:(WebCore::ScrollbarMode*)hMode vertical:(WebCore::ScrollbarMode*)vMode 430{ 431 *hMode = _private->hScroll; 432 *vMode = _private->vScroll; 433} 434 435- (ScrollbarMode)horizontalScrollingMode 436{ 437 return _private->hScroll; 438} 439 440- (ScrollbarMode)verticalScrollingMode 441{ 442 return _private->vScroll; 443} 444 445- (void)setHorizontalScrollingMode:(ScrollbarMode)horizontalMode andLock:(BOOL)lock 446{ 447 [self setScrollingModes:horizontalMode vertical:[self verticalScrollingMode] andLock:lock]; 448} 449 450- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode andLock:(BOOL)lock 451{ 452 [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:lock]; 453} 454 455// Mail uses this method, so we cannot remove it. 456- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode 457{ 458 [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:NO]; 459} 460 461- (void)setScrollingModes:(ScrollbarMode)horizontalMode vertical:(ScrollbarMode)verticalMode andLock:(BOOL)lock 462{ 463 BOOL update = NO; 464 if (verticalMode != _private->vScroll && !_private->vScrollModeLocked) { 465 _private->vScroll = verticalMode; 466 update = YES; 467 } 468 469 if (horizontalMode != _private->hScroll && !_private->hScrollModeLocked) { 470 _private->hScroll = horizontalMode; 471 update = YES; 472 } 473 474 if (lock) 475 [self setScrollingModesLocked:YES]; 476 477 if (update) 478 [self updateScrollers]; 479} 480 481- (void)setHorizontalScrollingModeLocked:(BOOL)locked 482{ 483 _private->hScrollModeLocked = locked; 484} 485 486- (void)setVerticalScrollingModeLocked:(BOOL)locked 487{ 488 _private->vScrollModeLocked = locked; 489} 490 491- (void)setScrollingModesLocked:(BOOL)locked 492{ 493 _private->hScrollModeLocked = _private->vScrollModeLocked = locked; 494} 495 496- (BOOL)horizontalScrollingModeLocked 497{ 498 return _private->hScrollModeLocked; 499} 500 501- (BOOL)verticalScrollingModeLocked 502{ 503 return _private->vScrollModeLocked; 504} 505 506- (BOOL)autoforwardsScrollWheelEvents 507{ 508 return YES; 509} 510 511- (void)scrollWheel:(NSEvent *)event 512{ 513 float deltaX; 514 float deltaY; 515 BOOL isContinuous; 516 WKGetWheelEventDeltas(event, &deltaX, &deltaY, &isContinuous); 517 518#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) 519 NSEventPhase momentumPhase = [event momentumPhase]; 520 BOOL isLatchingEvent = momentumPhase & NSEventPhaseBegan || momentumPhase & NSEventPhaseChanged; 521#else 522 int momentumPhase = WKGetNSEventMomentumPhase(event); 523 BOOL isLatchingEvent = momentumPhase == WKEventPhaseBegan || momentumPhase == WKEventPhaseChanged; 524#endif 525 526 if (fabsf(deltaY) > fabsf(deltaX)) { 527 if (![self allowsVerticalScrolling]) { 528 [[self nextResponder] scrollWheel:event]; 529 return; 530 } 531 532 if (isLatchingEvent && !_private->verticallyPinnedByPreviousWheelEvent) { 533 double verticalPosition = [[self verticalScroller] doubleValue]; 534 if ((deltaY >= 0.0 && verticalPosition == 0.0) || (deltaY <= 0.0 && verticalPosition == 1.0)) 535 return; 536 } 537 } else { 538 if (![self allowsHorizontalScrolling]) { 539 [[self nextResponder] scrollWheel:event]; 540 return; 541 } 542 543 if (isLatchingEvent && !_private->horizontallyPinnedByPreviousWheelEvent) { 544 double horizontalPosition = [[self horizontalScroller] doubleValue]; 545 if ((deltaX >= 0.0 && horizontalPosition == 0.0) || (deltaX <= 0.0 && horizontalPosition == 1.0)) 546 return; 547 } 548 } 549 550 // Calling super can release the last reference. <rdar://problem/7400263> 551 // Hold a reference so the code following the super call will not crash. 552 [self retain]; 553 554 [super scrollWheel:event]; 555 556 if (!isLatchingEvent) { 557 double verticalPosition = [[self verticalScroller] doubleValue]; 558 double horizontalPosition = [[self horizontalScroller] doubleValue]; 559 560 _private->verticallyPinnedByPreviousWheelEvent = (verticalPosition == 0.0 || verticalPosition == 1.0); 561 _private->horizontallyPinnedByPreviousWheelEvent = (horizontalPosition == 0.0 || horizontalPosition == 1.0); 562 } 563 564 [self release]; 565} 566 567// This object will be the parent of the web area in WK1, so it should not be ignored. 568- (BOOL)accessibilityIsIgnored 569{ 570 return NO; 571} 572 573- (void)setScrollOrigin:(NSPoint)scrollOrigin updatePositionAtAll:(BOOL)updatePositionAtAll immediately:(BOOL)updatePositionSynchronously 574{ 575 // The cross-platform ScrollView call already checked to see if the old/new scroll origins were the same or not 576 // so we don't have to check for equivalence here. 577 _private->scrollOrigin = scrollOrigin; 578 id docView = [self documentView]; 579 580 NSRect visibleRect = [self documentVisibleRect]; 581 582 [docView setBoundsOrigin:NSMakePoint(-scrollOrigin.x, -scrollOrigin.y)]; 583 584 if (updatePositionAtAll) 585 _private->scrollOriginChanged = true; 586 587 // Maintain our original position in the presence of the new scroll origin. 588 _private->scrollPositionExcludingOrigin = NSMakePoint(visibleRect.origin.x + scrollOrigin.x, visibleRect.origin.y + scrollOrigin.y); 589 590 if (updatePositionAtAll && updatePositionSynchronously) // Otherwise we'll just let the snap happen when we update for the resize. 591 [self adjustForScrollOriginChange]; 592} 593 594- (NSPoint)scrollOrigin 595{ 596 return _private->scrollOrigin; 597} 598 599- (BOOL)inProgrammaticScroll 600{ 601 return _private->inProgrammaticScroll; 602} 603 604@end 605