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