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