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