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