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