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