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