[iOS] Teach WKPDFView to navigate to pageNumber links
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / WKContentView.mm
1 /*
2  * Copyright (C) 2013, 2014 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 "config.h"
27 #import "WKContentViewInteraction.h"
28
29 #if PLATFORM(IOS)
30
31 #import "PageClientImplIOS.h"
32 #import "RemoteLayerTreeDrawingAreaProxy.h"
33 #import "RemoteScrollingCoordinatorProxy.h"
34 #import "SmartMagnificationController.h"
35 #import "WKBrowsingContextControllerInternal.h"
36 #import "WKBrowsingContextGroupPrivate.h"
37 #import "WKInspectorHighlightView.h"
38 #import "WKPreferencesInternal.h"
39 #import "WKProcessGroupPrivate.h"
40 #import "WKWebViewConfiguration.h"
41 #import "WKWebViewInternal.h"
42 #import "WebContext.h"
43 #import "WebFrameProxy.h"
44 #import "WebKit2Initialize.h"
45 #import "WebKitSystemInterfaceIOS.h"
46 #import "WebPageGroup.h"
47 #import "WebSystemInterface.h"
48 #import <CoreGraphics/CoreGraphics.h>
49 #import <UIKit/UIWindow_Private.h>
50 #import <WebCore/FloatQuad.h>
51 #import <WebCore/FrameView.h>
52 #import <WebCore/InspectorOverlay.h>
53 #import <WebCore/NotImplemented.h>
54 #import <wtf/CurrentTime.h>
55 #import <wtf/RetainPtr.h>
56
57 #if __has_include(<QuartzCore/QuartzCorePrivate.h>)
58 #import <QuartzCore/QuartzCorePrivate.h>
59 #endif
60
61 @interface CALayer (Details)
62 @property BOOL hitTestsAsOpaque;
63 @end
64
65 using namespace WebCore;
66 using namespace WebKit;
67
68 namespace WebKit {
69 class HistoricalVelocityData {
70 public:
71     struct VelocityData {
72         VelocityData()
73             : horizontalVelocity(0)
74             , verticalVelocity(0)
75             , scaleChangeRate(0)
76         {
77         }
78
79         VelocityData(double horizontalVelocity, double verticalVelocity, double scaleChangeRate)
80             : horizontalVelocity(horizontalVelocity)
81             , verticalVelocity(verticalVelocity)
82             , scaleChangeRate(scaleChangeRate)
83         {
84         }
85
86         double horizontalVelocity;
87         double verticalVelocity;
88         double scaleChangeRate;
89     };
90
91     HistoricalVelocityData()
92         : m_historySize(0)
93         , m_latestDataIndex(0)
94         , m_lastAppendTimestamp(0)
95     {
96     }
97
98     VelocityData velocityForNewData(CGPoint newPosition, double scale, double timestamp)
99     {
100         // Due to all the source of rect update, the input is very noisy. To smooth the output, we accumulate all changes
101         // within 1 frame as a single update. No speed computation is ever done on data within the same frame.
102         const double filteringThreshold = 1 / 60.;
103
104         VelocityData velocityData;
105         if (m_historySize > 0) {
106             unsigned oldestDataIndex;
107             unsigned distanceToLastHistoricalData = m_historySize - 1;
108             if (distanceToLastHistoricalData <= m_latestDataIndex)
109                 oldestDataIndex = m_latestDataIndex - distanceToLastHistoricalData;
110             else
111                 oldestDataIndex = m_historySize - (distanceToLastHistoricalData - m_latestDataIndex);
112
113             double timeDelta = timestamp - m_history[oldestDataIndex].timestamp;
114             if (timeDelta > filteringThreshold) {
115                 Data& oldestData = m_history[oldestDataIndex];
116                 velocityData = VelocityData((newPosition.x - oldestData.position.x) / timeDelta, (newPosition.y - oldestData.position.y) / timeDelta, (scale - oldestData.scale) / timeDelta);
117             }
118         }
119
120         double timeSinceLastAppend = timestamp - m_lastAppendTimestamp;
121         if (timeSinceLastAppend > filteringThreshold)
122             append(newPosition, scale, timestamp);
123         else
124             m_history[m_latestDataIndex] = { timestamp, newPosition, scale };
125         return velocityData;
126     }
127
128     void clear() { m_historySize = 0; }
129
130 private:
131     void append(CGPoint newPosition, double scale, double timestamp)
132     {
133         m_latestDataIndex = (m_latestDataIndex + 1) % maxHistoryDepth;
134         m_history[m_latestDataIndex] = { timestamp, newPosition, scale };
135
136         unsigned size = m_historySize + 1;
137         if (size <= maxHistoryDepth)
138             m_historySize = size;
139
140         m_lastAppendTimestamp = timestamp;
141     }
142
143
144     static const unsigned maxHistoryDepth = 3;
145
146     unsigned m_historySize;
147     unsigned m_latestDataIndex;
148     double m_lastAppendTimestamp;
149
150     struct Data {
151         double timestamp;
152         CGPoint position;
153         double scale;
154     } m_history[maxHistoryDepth];
155 };
156 } // namespace WebKit
157
158 @interface WKInspectorIndicationView : UIView
159 @end
160
161 @implementation WKInspectorIndicationView
162
163 - (instancetype)initWithFrame:(CGRect)frame
164 {
165     if (!(self = [super initWithFrame:frame]))
166         return nil;
167     self.userInteractionEnabled = NO;
168     self.backgroundColor = [UIColor colorWithRed:(111.0 / 255.0) green:(168.0 / 255.0) blue:(220.0 / 255.0) alpha:0.66f];
169     return self;
170 }
171
172 @end
173
174 @implementation WKContentView {
175     std::unique_ptr<PageClientImpl> _pageClient;
176     RetainPtr<WKBrowsingContextController> _browsingContextController;
177
178     RetainPtr<UIView> _rootContentView;
179     RetainPtr<UIView> _fixedClippingView;
180     RetainPtr<WKInspectorIndicationView> _inspectorIndicationView;
181     RetainPtr<WKInspectorHighlightView> _inspectorHighlightView;
182
183     HistoricalVelocityData _historicalKinematicData;
184 }
185
186 - (instancetype)initWithFrame:(CGRect)frame context:(WebKit::WebContext&)context configuration:(WebKit::WebPageConfiguration)webPageConfiguration webView:(WKWebView *)webView
187 {
188     if (!(self = [super initWithFrame:frame]))
189         return nil;
190
191     InitializeWebKit2();
192
193     _pageClient = std::make_unique<PageClientImpl>(self, webView);
194
195     _page = context.createWebPage(*_pageClient, WTF::move(webPageConfiguration));
196     _page->initializeWebPage();
197     _page->setIntrinsicDeviceScaleFactor(WKGetScaleFactorForScreen([UIScreen mainScreen]));
198     _page->setUseFixedLayout(true);
199     _page->setDelegatesScrolling(true);
200
201     _webView = webView;
202     
203     _isBackground = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground;
204
205     WebContext::statistics().wkViewCount++;
206
207     _rootContentView = adoptNS([[UIView alloc] init]);
208     [_rootContentView layer].masksToBounds = NO;
209     [_rootContentView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
210
211     _fixedClippingView = adoptNS([[UIView alloc] init]);
212     [_fixedClippingView layer].masksToBounds = YES;
213     [_fixedClippingView layer].anchorPoint = CGPointZero;
214 #ifndef NDEBUG
215     [[_fixedClippingView layer] setName:@"Fixed clipping"];
216 #endif
217
218     [self addSubview:_fixedClippingView.get()];
219     [_fixedClippingView addSubview:_rootContentView.get()];
220
221     [self setupInteraction];
222     [self setUserInteractionEnabled:YES];
223
224     self.layer.hitTestsAsOpaque = YES;
225
226     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];
227     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];
228     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:[UIApplication sharedApplication]];
229     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:[UIApplication sharedApplication]];
230
231     return self;
232 }
233
234 - (void)dealloc
235 {
236     [self cleanupInteraction];
237
238     [[NSNotificationCenter defaultCenter] removeObserver:self];
239
240     _page->close();
241
242     WebContext::statistics().wkViewCount--;
243
244     [super dealloc];
245 }
246
247 - (WebPageProxy*)page
248 {
249     return _page.get();
250 }
251
252 - (void)willMoveToWindow:(UIWindow *)newWindow
253 {
254     NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
255     UIWindow *window = self.window;
256
257     if (window)
258         [defaultCenter removeObserver:self name:UIWindowDidMoveToScreenNotification object:window];
259
260     if (newWindow)
261         [defaultCenter addObserver:self selector:@selector(_windowDidMoveToScreenNotification:) name:UIWindowDidMoveToScreenNotification object:newWindow];
262 }
263
264 - (void)didMoveToWindow
265 {
266     if (self.window)
267         [self _updateForScreen:self.window.screen];
268     _page->viewStateDidChange(ViewState::AllFlags);
269 }
270
271 - (WKBrowsingContextController *)browsingContextController
272 {
273     if (!_browsingContextController)
274         _browsingContextController = adoptNS([[WKBrowsingContextController alloc] _initWithPageRef:toAPI(_page.get())]);
275
276     return _browsingContextController.get();
277 }
278
279 - (WKPageRef)_pageRef
280 {
281     return toAPI(_page.get());
282 }
283
284 - (BOOL)isAssistingNode
285 {
286     return [self isEditable];
287 }
288
289 - (BOOL)isBackground
290 {
291     return _isBackground;
292 }
293
294 - (void)_showInspectorHighlight:(const WebCore::Highlight&)highlight
295 {
296     if (!_inspectorHighlightView) {
297         _inspectorHighlightView = adoptNS([[WKInspectorHighlightView alloc] initWithFrame:CGRectZero]);
298         [self insertSubview:_inspectorHighlightView.get() aboveSubview:_rootContentView.get()];
299     }
300
301     [_inspectorHighlightView update:highlight];
302 }
303
304 - (void)_hideInspectorHighlight
305 {
306     if (_inspectorHighlightView) {
307         [_inspectorHighlightView removeFromSuperview];
308         _inspectorHighlightView = nil;
309     }
310 }
311
312 - (BOOL)isShowingInspectorIndication
313 {
314     return !!_inspectorIndicationView;
315 }
316
317 - (void)setShowingInspectorIndication:(BOOL)show
318 {
319     if (show) {
320         if (!_inspectorIndicationView) {
321             _inspectorIndicationView = adoptNS([[WKInspectorIndicationView alloc] initWithFrame:[self bounds]]);
322             [_inspectorIndicationView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
323             [self insertSubview:_inspectorIndicationView.get() aboveSubview:_rootContentView.get()];
324         }
325     } else {
326         if (_inspectorIndicationView) {
327             [_inspectorIndicationView removeFromSuperview];
328             _inspectorIndicationView = nil;
329         }
330     }
331 }
332
333 - (void)updateFixedClippingView:(FloatRect)fixedPositionRectForUI
334 {
335     FloatRect clippingBounds = [self bounds];
336     clippingBounds.unite(fixedPositionRectForUI);
337
338     [_fixedClippingView setCenter:clippingBounds.location()]; // Not really the center since we set an anchor point.
339     [_fixedClippingView setBounds:clippingBounds];
340 }
341
342 - (void)didUpdateVisibleRect:(CGRect)visibleRect unobscuredRect:(CGRect)unobscuredRect unobscuredRectInScrollViewCoordinates:(CGRect)unobscuredRectInScrollViewCoordinates
343     scale:(CGFloat)zoomScale minimumScale:(CGFloat)minimumScale inStableState:(BOOL)isStableState isChangingObscuredInsetsInteractively:(BOOL)isChangingObscuredInsetsInteractively
344 {
345     double timestamp = monotonicallyIncreasingTime();
346     HistoricalVelocityData::VelocityData velocityData;
347     if (!isStableState)
348         velocityData = _historicalKinematicData.velocityForNewData(visibleRect.origin, zoomScale, timestamp);
349     else
350         _historicalKinematicData.clear();
351
352     FloatRect fixedPositionRectForLayout = _page->computeCustomFixedPositionRect(unobscuredRect, zoomScale, WebPageProxy::UnobscuredRectConstraint::ConstrainedToDocumentRect);
353     _page->updateVisibleContentRects(visibleRect, unobscuredRect, unobscuredRectInScrollViewCoordinates, fixedPositionRectForLayout,
354         zoomScale, isStableState, isChangingObscuredInsetsInteractively, timestamp, velocityData.horizontalVelocity, velocityData.verticalVelocity, velocityData.scaleChangeRate);
355
356     RemoteScrollingCoordinatorProxy* scrollingCoordinator = _page->scrollingCoordinatorProxy();
357     FloatRect fixedPositionRect = _page->computeCustomFixedPositionRect(_page->unobscuredContentRect(), zoomScale);
358     scrollingCoordinator->viewportChangedViaDelegatedScrolling(scrollingCoordinator->rootScrollingNodeID(), fixedPositionRect, zoomScale);
359
360     if (auto drawingArea = _page->drawingArea())
361         drawingArea->updateDebugIndicator();
362         
363     [self updateFixedClippingView:fixedPositionRect];
364 }
365
366 - (void)didFinishScrolling
367 {
368     [self _didEndScrollingOrZooming];
369 }
370
371 - (void)willStartZoomOrScroll
372 {
373     [self _willStartScrollingOrZooming];
374 }
375
376 - (void)didZoomToScale:(CGFloat)scale
377 {
378     [self _didEndScrollingOrZooming];
379 }
380
381 #pragma mark Internal
382
383 - (void)_windowDidMoveToScreenNotification:(NSNotification *)notification
384 {
385     ASSERT(notification.object == self.window);
386
387     UIScreen *screen = notification.userInfo[UIWindowNewScreenUserInfoKey];
388     [self _updateForScreen:screen];
389 }
390
391 - (void)_updateForScreen:(UIScreen *)screen
392 {
393     ASSERT(screen);
394     _page->setIntrinsicDeviceScaleFactor(WKGetScaleFactorForScreen(screen));
395     [self _accessibilityRegisterUIProcessTokens];
396 }
397
398 - (void)_setAccessibilityWebProcessToken:(NSData *)data
399 {
400     // This means the web process has checked in and we should send information back to that process.
401     [self _accessibilityRegisterUIProcessTokens];
402 }
403
404 - (void)_accessibilityRegisterUIProcessTokens
405 {
406     RetainPtr<CFUUIDRef> uuid = adoptCF(CFUUIDCreate(kCFAllocatorDefault));
407     NSData *remoteElementToken = WKAXRemoteToken(uuid.get());
408
409     // Store information about the WebProcess that can later be retrieved by the iOS Accessibility runtime.
410     if (_page->process().state() == WebProcessProxy::State::Running) {
411         IPC::Connection* connection = _page->process().connection();
412         WKAXStoreRemoteConnectionInformation(self, _page->process().processIdentifier(), connection->identifier().port, uuid.get());
413
414         IPC::DataReference elementToken = IPC::DataReference(reinterpret_cast<const uint8_t*>([remoteElementToken bytes]), [remoteElementToken length]);
415         _page->registerUIProcessAccessibilityTokens(elementToken, elementToken);
416     }
417 }
418
419 #pragma mark PageClientImpl methods
420
421 - (std::unique_ptr<DrawingAreaProxy>)_createDrawingAreaProxy
422 {
423     return std::make_unique<RemoteLayerTreeDrawingAreaProxy>(*_page);
424 }
425
426 - (void)_processDidExit
427 {
428     [self cleanupInteraction];
429
430     [self setShowingInspectorIndication:NO];
431     [self _hideInspectorHighlight];
432 }
433
434 - (void)_didRelaunchProcess
435 {
436     [self _accessibilityRegisterUIProcessTokens];
437     [self setupInteraction];
438 }
439
440 - (void)_didCommitLoadForMainFrame
441 {
442     [self _stopAssistingNode];
443     [_webView _didCommitLoadForMainFrame];
444 }
445
446 - (void)_didCommitLayerTree:(const WebKit::RemoteLayerTreeTransaction&)layerTreeTransaction
447 {
448     CGSize contentsSize = layerTreeTransaction.contentsSize();
449     CGRect contentBounds = { CGPointZero, contentsSize };
450     CGRect oldBounds = [self bounds];
451
452     BOOL boundsChanged = !CGRectEqualToRect(oldBounds, contentBounds);
453     if (boundsChanged)
454         [self setBounds:contentBounds];
455
456     [_webView _didCommitLayerTree:layerTreeTransaction];
457     
458     if (boundsChanged) {
459         FloatRect fixedPositionRect = _page->computeCustomFixedPositionRect(_page->unobscuredContentRect(), [[_webView scrollView] zoomScale]);
460         [self updateFixedClippingView:fixedPositionRect];
461     }
462     
463     [self _updateChangedSelection];
464 }
465
466 - (void)_setAcceleratedCompositingRootView:(UIView *)rootView
467 {
468     for (UIView* subview in [_rootContentView subviews])
469         [subview removeFromSuperview];
470
471     [_rootContentView addSubview:rootView];
472 }
473
474 - (BOOL)_scrollToRect:(CGRect)targetRect withOrigin:(CGPoint)origin minimumScrollDistance:(CGFloat)minimumScrollDistance
475 {
476     return [_webView _scrollToRect:targetRect origin:origin minimumScrollDistance:minimumScrollDistance];
477 }
478
479 - (void)_zoomToFocusRect:(CGRect)rectToFocus selectionRect:(CGRect)selectionRect fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll
480 {
481     [_webView _zoomToFocusRect:rectToFocus
482                  selectionRect:selectionRect
483                       fontSize:fontSize
484                   minimumScale:minimumScale
485                   maximumScale:maximumScale
486               allowScaling:allowScaling
487                    forceScroll:forceScroll];
488 }
489
490 - (BOOL)_zoomToRect:(CGRect)targetRect withOrigin:(CGPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(CGFloat)minimumScrollDistance
491 {
492     return [_webView _zoomToRect:targetRect withOrigin:origin fitEntireRect:fitEntireRect minimumScale:minimumScale maximumScale:maximumScale minimumScrollDistance:minimumScrollDistance];
493 }
494
495 - (void)_zoomOutWithOrigin:(CGPoint)origin
496 {
497     return [_webView _zoomOutWithOrigin:origin animated:YES];
498 }
499
500 - (void)_applicationWillResignActive:(NSNotification*)notification
501 {
502     _page->applicationWillResignActive();
503 }
504
505 - (void)_applicationDidEnterBackground:(NSNotification*)notification
506 {
507     _isBackground = YES;
508     _page->viewStateDidChange(ViewState::AllFlags & ~ViewState::IsInWindow);
509 }
510
511 - (void)_applicationWillEnterForeground:(NSNotification*)notification
512 {
513     _isBackground = NO;
514     _page->applicationWillEnterForeground();
515     if (auto drawingArea = _page->drawingArea())
516         drawingArea->hideContentUntilNextUpdate();
517     _page->viewStateDidChange(ViewState::AllFlags & ~ViewState::IsInWindow, true, WebPageProxy::ViewStateChangeDispatchMode::Immediate);
518 }
519
520 - (void)_applicationDidBecomeActive:(NSNotification*)notification
521 {
522     _page->applicationDidBecomeActive();
523 }
524
525 @end
526
527 #endif // PLATFORM(IOS)