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