PiP from Element Fullscreen should match AVKit's behavior
[WebKit-https.git] / Source / WebKit / UIProcess / ios / fullscreen / WKFullScreenViewController.mm
1 /*
2  * Copyright (C) 2018 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
28 #if ENABLE(FULLSCREEN_API) && PLATFORM(IOS)
29 #import "WKFullScreenViewController.h"
30
31 #import "FullscreenTouchSecheuristic.h"
32 #import "PlaybackSessionManagerProxy.h"
33 #import "UIKitSPI.h"
34 #import "VideoFullscreenManagerProxy.h"
35 #import "WKFullscreenStackView.h"
36 #import "WKWebViewInternal.h"
37 #import "WebFullScreenManagerProxy.h"
38 #import "WebPageProxy.h"
39 #import <WebCore/LocalizedStrings.h>
40 #import <pal/spi/cocoa/AVKitSPI.h>
41 #import <wtf/RetainPtr.h>
42
43 using namespace WebCore;
44 using namespace WebKit;
45
46 static const NSTimeInterval showHideAnimationDuration = 0.1;
47 static const NSTimeInterval pipHideAnimationDuration = 0.2;
48 static const NSTimeInterval autoHideDelay = 4.0;
49 static const double requiredScore = 0.1;
50
51 @class WKFullscreenStackView;
52
53 @interface WKFullScreenViewController (VideoFullscreenClientCallbacks)
54 - (void)willEnterPictureInPicture;
55 - (void)didEnterPictureInPicture;
56 - (void)failedToEnterPictureInPicture;
57 @end
58
59 class WKFullScreenViewControllerPlaybackSessionModelClient : PlaybackSessionModelClient {
60 public:
61     void setParent(WKFullScreenViewController *parent) { m_parent = parent; }
62
63     void rateChanged(bool isPlaying, float) override
64     {
65         m_parent.playing = isPlaying;
66     }
67
68     void pictureInPictureActiveChanged(bool active) override
69     {
70         m_parent.pictureInPictureActive = active;
71     }
72
73     void setInterface(PlaybackSessionInterfaceAVKit* interface)
74     {
75         if (m_interface == interface)
76             return;
77
78         if (m_interface && m_interface->playbackSessionModel())
79             m_interface->playbackSessionModel()->removeClient(*this);
80         m_interface = interface;
81         if (m_interface && m_interface->playbackSessionModel())
82             m_interface->playbackSessionModel()->addClient(*this);
83     }
84
85 private:
86     WKFullScreenViewController *m_parent { nullptr };
87     RefPtr<PlaybackSessionInterfaceAVKit> m_interface;
88 };
89
90 class WKFullScreenViewControllerVideoFullscreenModelClient : VideoFullscreenModelClient {
91 public:
92     void setParent(WKFullScreenViewController *parent) { m_parent = parent; }
93
94     void setInterface(VideoFullscreenInterfaceAVKit* interface)
95     {
96         if (m_interface == interface)
97             return;
98
99         if (m_interface && m_interface->videoFullscreenModel())
100             m_interface->videoFullscreenModel()->removeClient(*this);
101         m_interface = interface;
102         if (m_interface && m_interface->videoFullscreenModel())
103             m_interface->videoFullscreenModel()->addClient(*this);
104     }
105
106     VideoFullscreenInterfaceAVKit* interface() const { return m_interface.get(); }
107
108     void willEnterPictureInPicture() final
109     {
110         [m_parent willEnterPictureInPicture];
111     }
112
113     void didEnterPictureInPicture() final
114     {
115         [m_parent didEnterPictureInPicture];
116     }
117
118     void failedToEnterPictureInPicture() final
119     {
120         [m_parent failedToEnterPictureInPicture];
121     }
122
123 private:
124     WKFullScreenViewController *m_parent { nullptr };
125     RefPtr<VideoFullscreenInterfaceAVKit> m_interface;
126 };
127
128 #pragma mark - _WKExtrinsicButton
129
130 @interface _WKExtrinsicButton : UIButton
131 @property (assign, nonatomic) CGSize extrinsicContentSize;
132 @end
133
134 @implementation _WKExtrinsicButton
135 - (void)setExtrinsicContentSize:(CGSize)size
136 {
137     _extrinsicContentSize = size;
138     [self invalidateIntrinsicContentSize];
139 }
140
141 - (CGSize)intrinsicContentSize
142 {
143     return _extrinsicContentSize;
144 }
145 @end
146
147 #pragma mark - WKFullScreenViewController
148
149 @interface WKFullScreenViewController () <UIGestureRecognizerDelegate, UIToolbarDelegate>
150 @property (weak, nonatomic) WKWebView *_webView; // Cannot be retained, see <rdar://problem/14884666>.
151 @property (readonly, nonatomic) WebFullScreenManagerProxy* _manager;
152 @property (readonly, nonatomic) WebCore::FloatBoxExtent _effectiveFullscreenInsets;
153 @end
154
155 @implementation WKFullScreenViewController {
156     RetainPtr<UILongPressGestureRecognizer> _touchGestureRecognizer;
157     RetainPtr<UIView> _animatingView;
158     RetainPtr<WKFullscreenStackView> _stackView;
159     RetainPtr<_WKExtrinsicButton> _cancelButton;
160     RetainPtr<_WKExtrinsicButton> _pipButton;
161     RetainPtr<UIButton> _locationButton;
162     RetainPtr<UILayoutGuide> _topGuide;
163     RetainPtr<NSLayoutConstraint> _topConstraint;
164     WebKit::FullscreenTouchSecheuristic _secheuristic;
165     WKFullScreenViewControllerPlaybackSessionModelClient _playbackClient;
166     WKFullScreenViewControllerVideoFullscreenModelClient _videoFullscreenClient;
167     CGFloat _nonZeroStatusBarHeight;
168 }
169
170 @synthesize prefersStatusBarHidden=_prefersStatusBarHidden;
171 @synthesize prefersHomeIndicatorAutoHidden=_prefersHomeIndicatorAutoHidden;
172
173 #pragma mark - External Interface
174
175 - (id)initWithWebView:(WKWebView *)webView
176 {
177     self = [super init];
178     if (!self)
179         return nil;
180
181     _nonZeroStatusBarHeight = UIApplication.sharedApplication.statusBarFrame.size.height;
182     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_statusBarFrameDidChange:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
183     _secheuristic.setRampUpSpeed(Seconds(0.25));
184     _secheuristic.setRampDownSpeed(Seconds(1.));
185     _secheuristic.setXWeight(0);
186     _secheuristic.setGamma(0.1);
187     _secheuristic.setGammaCutoff(0.08);
188
189     self._webView = webView;
190
191     _playbackClient.setParent(self);
192     _videoFullscreenClient.setParent(self);
193
194     return self;
195 }
196
197 - (void)dealloc
198 {
199     [NSObject cancelPreviousPerformRequestsWithTarget:self];
200     [[NSNotificationCenter defaultCenter] removeObserver:self];
201
202     _playbackClient.setInterface(nullptr);
203     _videoFullscreenClient.setParent(nullptr);
204     _videoFullscreenClient.setInterface(nullptr);
205
206     [_target release];
207     [_location release];
208
209     [super dealloc];
210 }
211
212 - (void)showUI
213 {
214     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideUI) object:nil];
215
216     if (_playing) {
217         NSTimeInterval hideDelay = autoHideDelay;
218         [self performSelector:@selector(hideUI) withObject:nil afterDelay:hideDelay];
219     }
220     [UIView animateWithDuration:showHideAnimationDuration animations:^{
221         [_stackView setHidden:NO];
222         [_stackView setAlpha:1];
223         self.prefersStatusBarHidden = NO;
224         self.prefersHomeIndicatorAutoHidden = NO;
225         if (_topConstraint)
226             [NSLayoutConstraint deactivateConstraints:@[_topConstraint.get()]];
227         _topConstraint = [[_topGuide topAnchor] constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor];
228         [_topConstraint setActive:YES];
229         if (auto* manager = self._manager)
230             manager->setFullscreenControlsHidden(false);
231     }];
232 }
233
234 - (void)hideUI
235 {
236     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideUI) object:nil];
237     [UIView animateWithDuration:showHideAnimationDuration animations:^{
238
239         if (_topConstraint)
240             [NSLayoutConstraint deactivateConstraints:@[_topConstraint.get()]];
241         _topConstraint = [[_topGuide topAnchor] constraintEqualToAnchor:self.view.topAnchor constant:self.view.safeAreaInsets.top];
242         [_topConstraint setActive:YES];
243         [_stackView setAlpha:0];
244         self.prefersStatusBarHidden = YES;
245         self.prefersHomeIndicatorAutoHidden = YES;
246         if (auto* manager = self._manager)
247             manager->setFullscreenControlsHidden(true);
248     } completion:^(BOOL finished) {
249         if (!finished)
250             return;
251
252         [_stackView setHidden:YES];
253     }];
254 }
255
256 - (void)videoControlsManagerDidChange
257 {
258     WebPageProxy* page = [self._webView _page];
259     auto* videoFullscreenManager = page ? page->videoFullscreenManager() : nullptr;
260     auto* videoFullscreenInterface = videoFullscreenManager ? videoFullscreenManager->controlsManagerInterface() : nullptr;
261     auto* playbackSessionInterface = videoFullscreenInterface ? &videoFullscreenInterface->playbackSessionInterface() : nullptr;
262
263     _playbackClient.setInterface(playbackSessionInterface);
264     _videoFullscreenClient.setInterface(videoFullscreenInterface);
265
266     PlaybackSessionModel* playbackSessionModel = playbackSessionInterface ? playbackSessionInterface->playbackSessionModel() : nullptr;
267     self.playing = playbackSessionModel ? playbackSessionModel->isPlaying() : NO;
268     [_pipButton setHidden:!playbackSessionModel];
269 }
270
271 - (void)setPrefersStatusBarHidden:(BOOL)value
272 {
273     _prefersStatusBarHidden = value;
274     [self setNeedsStatusBarAppearanceUpdate];
275     [self _updateWebViewFullscreenInsets];
276 }
277
278 - (void)setPrefersHomeIndicatorAutoHidden:(BOOL)value
279 {
280     _prefersHomeIndicatorAutoHidden = value;
281     [self setNeedsUpdateOfHomeIndicatorAutoHidden];
282 }
283
284 - (void)setPlaying:(BOOL)isPlaying
285 {
286     if (_playing == isPlaying)
287         return;
288
289     _playing = isPlaying;
290     if (!_playing)
291         [self showUI];
292     else {
293         [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideUI) object:nil];
294         NSTimeInterval hideDelay = autoHideDelay;
295         [self performSelector:@selector(hideUI) withObject:nil afterDelay:hideDelay];
296     }
297 }
298
299 - (void)setPictureInPictureActive:(BOOL)active
300 {
301     if (_pictureInPictureActive == active)
302         return;
303
304     _pictureInPictureActive = active;
305     [_pipButton setSelected:active];
306 }
307
308 - (void)setAnimating:(BOOL)animating
309 {
310     if (_animating == animating)
311         return;
312     _animating = animating;
313     [self setNeedsStatusBarAppearanceUpdate];
314
315     if (_animating)
316         [self hideUI];
317     else
318         [self showUI];
319 }
320
321 - (void)willEnterPictureInPicture
322 {
323     auto* interface = _videoFullscreenClient.interface();
324     if (!interface || !interface->pictureInPictureWasStartedWhenEnteringBackground())
325         return;
326
327     [UIView animateWithDuration:pipHideAnimationDuration animations:^{
328         _animatingView.get().alpha = 0;
329     }];
330 }
331
332 - (void)didEnterPictureInPicture
333 {
334     [self _cancelAction:self];
335 }
336
337 - (void)failedToEnterPictureInPicture
338 {
339     auto* interface = _videoFullscreenClient.interface();
340     if (!interface || !interface->pictureInPictureWasStartedWhenEnteringBackground())
341         return;
342
343     [UIView animateWithDuration:pipHideAnimationDuration animations:^{
344         _animatingView.get().alpha = 1;
345     }];
346 }
347
348 #pragma mark - UIViewController Overrides
349
350 - (void)loadView
351 {
352     [self setView:adoptNS([[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]).get()];
353     self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
354     self.view.backgroundColor = [UIColor blackColor];
355
356     _animatingView = adoptNS([[UIView alloc] initWithFrame:self.view.bounds]);
357     _animatingView.get().autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
358     [self.view addSubview:_animatingView.get()];
359
360     _cancelButton = [_WKExtrinsicButton buttonWithType:UIButtonTypeSystem];
361     [_cancelButton setTranslatesAutoresizingMaskIntoConstraints:NO];
362     [_cancelButton setAdjustsImageWhenHighlighted:NO];
363     [_cancelButton setExtrinsicContentSize:CGSizeMake(60.0, 47.0)];
364     NSBundle *bundle = [NSBundle bundleForClass:self.class];
365     UIImage *doneImage = [UIImage imageNamed:@"Done" inBundle:bundle compatibleWithTraitCollection:nil];
366     [_cancelButton setImage:[doneImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
367     [_cancelButton setTintColor:[UIColor whiteColor]];
368     [_cancelButton sizeToFit];
369     [_cancelButton addTarget:self action:@selector(_cancelAction:) forControlEvents:UIControlEventTouchUpInside];
370
371     _pipButton = [_WKExtrinsicButton buttonWithType:UIButtonTypeSystem];
372     [_pipButton setTranslatesAutoresizingMaskIntoConstraints:NO];
373     [_pipButton setAdjustsImageWhenHighlighted:NO];
374     [_pipButton setExtrinsicContentSize:CGSizeMake(60.0, 47.0)];
375     UIImage *startPiPImage = [UIImage imageNamed:@"StartPictureInPictureButton" inBundle:bundle compatibleWithTraitCollection:nil];
376     UIImage *stopPiPImage = [UIImage imageNamed:@"StopPictureInPictureButton" inBundle:bundle compatibleWithTraitCollection:nil];
377     [_pipButton setImage:[startPiPImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
378     [_pipButton setImage:[stopPiPImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateSelected];
379     [_pipButton setTintColor:[UIColor whiteColor]];
380     [_pipButton sizeToFit];
381     [_pipButton addTarget:self action:@selector(_togglePiPAction:) forControlEvents:UIControlEventTouchUpInside];
382
383     _stackView = adoptNS([[WKFullscreenStackView alloc] init]);
384     [_stackView setTranslatesAutoresizingMaskIntoConstraints:NO];
385     [_stackView addArrangedSubview:_cancelButton.get() applyingMaterialStyle:AVBackgroundViewMaterialStyleSecondary tintEffectStyle:AVBackgroundViewTintEffectStyleSecondary];
386     [_stackView addArrangedSubview:_pipButton.get() applyingMaterialStyle:AVBackgroundViewMaterialStylePrimary tintEffectStyle:AVBackgroundViewTintEffectStyleSecondary];
387     [_animatingView addSubview:_stackView.get()];
388
389     UILayoutGuide *safeArea = self.view.safeAreaLayoutGuide;
390     UILayoutGuide *margins = self.view.layoutMarginsGuide;
391
392     _topGuide = adoptNS([[UILayoutGuide alloc] init]);
393     [self.view addLayoutGuide:_topGuide.get()];
394     NSLayoutAnchor *topAnchor = [_topGuide topAnchor];
395     _topConstraint = [topAnchor constraintEqualToAnchor:safeArea.topAnchor];
396     [NSLayoutConstraint activateConstraints:@[
397         _topConstraint.get(),
398         [[_stackView topAnchor] constraintEqualToAnchor:topAnchor],
399         [[_stackView leadingAnchor] constraintEqualToAnchor:margins.leadingAnchor],
400     ]];
401
402     [_stackView setAlpha:0];
403     [_stackView setHidden:YES];
404     [self videoControlsManagerDidChange];
405
406     _touchGestureRecognizer = adoptNS([[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_touchDetected:)]);
407     [_touchGestureRecognizer setCancelsTouchesInView:NO];
408     [_touchGestureRecognizer setMinimumPressDuration:0];
409     [_touchGestureRecognizer setDelegate:self];
410     [self.view addGestureRecognizer:_touchGestureRecognizer.get()];
411 }
412
413 - (void)viewWillAppear:(BOOL)animated
414 {
415     self._webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
416     self._webView.frame = self.view.bounds;
417     [_animatingView insertSubview:self._webView atIndex:0];
418
419     if (auto* manager = self._manager)
420         manager->setFullscreenAutoHideDuration(Seconds(showHideAnimationDuration));
421
422     [super viewWillAppear:animated];
423 }
424
425 - (void)viewDidLayoutSubviews
426 {
427     [self _updateWebViewFullscreenInsets];
428     _secheuristic.setSize(self.view.bounds.size);
429 }
430
431 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
432 {
433     [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
434     [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
435         [self._webView _beginAnimatedResizeWithUpdates:^{
436             [self._webView _overrideLayoutParametersWithMinimumLayoutSize:size maximumUnobscuredSizeOverride:size];
437         }];
438         [self._webView _setInterfaceOrientationOverride:[UIApp statusBarOrientation]];
439     } completion:^(id <UIViewControllerTransitionCoordinatorContext>context) {
440         [self._webView _endAnimatedResize];
441     }];
442 }
443
444 - (UIStatusBarStyle)preferredStatusBarStyle
445 {
446     return UIStatusBarStyleLightContent;
447 }
448
449 - (BOOL)prefersStatusBarHidden
450 {
451     return _animating || _prefersStatusBarHidden;
452 }
453
454 #pragma mark - UIGestureRecognizerDelegate
455
456 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
457 {
458     return YES;
459 }
460
461 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
462 {
463     if (!self.animating)
464         [self showUI];
465     return YES;
466 }
467
468 #pragma mark - Internal Interface
469
470 @dynamic _manager;
471 - (WebFullScreenManagerProxy*)_manager
472 {
473     if (auto* page = [self._webView _page])
474         return page->fullScreenManager();
475     return nullptr;
476 }
477
478 @dynamic _effectiveFullscreenInsets;
479 - (WebCore::FloatBoxExtent)_effectiveFullscreenInsets
480 {
481     auto safeAreaInsets = self.view.safeAreaInsets;
482     WebCore::FloatBoxExtent insets { safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left };
483
484     CGRect cancelFrame = _cancelButton.get().frame;
485     CGPoint maxXY = CGPointMake(CGRectGetMaxX(cancelFrame), CGRectGetMaxY(cancelFrame));
486     insets.setTop([_cancelButton convertPoint:maxXY toView:self.view].y);
487     return insets;
488 }
489
490 - (void)_cancelAction:(id)sender
491 {
492     [[self target] performSelector:[self action]];
493 }
494
495 - (void)_togglePiPAction:(id)sender
496 {
497     WebPageProxy* page = [self._webView _page];
498     if (!page)
499         return;
500
501     PlaybackSessionManagerProxy* playbackSessionManager = page->playbackSessionManager();
502     if (!playbackSessionManager)
503         return;
504
505     PlatformPlaybackSessionInterface* playbackSessionInterface = playbackSessionManager->controlsManagerInterface();
506     if (!playbackSessionInterface)
507         return;
508
509     PlaybackSessionModel* playbackSessionModel = playbackSessionInterface->playbackSessionModel();
510     if (!playbackSessionModel)
511         return;
512
513     playbackSessionModel->togglePictureInPicture();
514 }
515
516 - (void)_touchDetected:(id)sender
517 {
518     if ([_touchGestureRecognizer state] == UIGestureRecognizerStateBegan || [_touchGestureRecognizer state] == UIGestureRecognizerStateEnded) {
519         double score = _secheuristic.scoreOfNextTouch([_touchGestureRecognizer locationInView:self.view]);
520         if (score > requiredScore)
521             [self _showPhishingAlert];
522     }
523     if (!self.animating)
524         [self showUI];
525 }
526
527 - (void)_statusBarFrameDidChange:(NSNotificationCenter *)notification
528 {
529     CGFloat height = UIApplication.sharedApplication.statusBarFrame.size.height;
530     if (!height || height == _nonZeroStatusBarHeight)
531         return;
532
533     _nonZeroStatusBarHeight = height;
534     [self _updateWebViewFullscreenInsets];
535 }
536
537 - (void)_updateWebViewFullscreenInsets
538 {
539     if (auto* manager = self._manager)
540         manager->setFullscreenInsets(self._effectiveFullscreenInsets);
541 }
542
543 - (void)_showPhishingAlert
544 {
545     NSString *alertTitle = [NSString stringWithFormat:WEB_UI_STRING("It looks like you are typing on “%@”", "Fullscreen Deceptive Website Warning Sheet Title"), (NSString *)self.location];
546     NSString *alertMessage = WEB_UI_STRING("Typing is not allowed in full screen. This website may be showing a fake keyboard to trick you into disclosing personal or financial information.", "Fullscreen Deceptive Website Warning Sheet Content Text");
547     UIAlertController* alert = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleAlert];
548
549     if (auto* page = [self._webView _page])
550         page->suspendActiveDOMObjectsAndAnimations();
551
552     UIAlertAction* exitAction = [UIAlertAction actionWithTitle:WEB_UI_STRING_KEY("Exit Full Screen", "Exit Full Screen (Element Fullscreen)", "Fullscreen Deceptive Website Exit Action") style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
553         [self _cancelAction:action];
554         if (auto* page = [self._webView _page])
555             page->resumeActiveDOMObjectsAndAnimations();
556     }];
557
558     UIAlertAction* stayAction = [UIAlertAction actionWithTitle:WEB_UI_STRING_KEY("Stay in Full Screen", "Stay in Full Screen (Element Fullscreen)", "Fullscreen Deceptive Website Stay Action") style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
559         if (auto* page = [self._webView _page])
560             page->resumeActiveDOMObjectsAndAnimations();
561         _secheuristic.reset();
562     }];
563
564     [alert addAction:exitAction];
565     [alert addAction:stayAction];
566     [self presentViewController:alert animated:YES completion:nil];
567 }
568
569 @end
570
571 #endif // ENABLE(FULLSCREEN_API) && PLATFORM(IOS)