Micro-optimize HashMap & String IPC decoding
[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 ALLOW_DEPRECATED_DECLARATIONS_BEGIN
183     _nonZeroStatusBarHeight = UIApplication.sharedApplication.statusBarFrame.size.height;
184     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_statusBarFrameDidChange:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
185 ALLOW_DEPRECATED_DECLARATIONS_END
186     _secheuristic.setRampUpSpeed(Seconds(0.25));
187     _secheuristic.setRampDownSpeed(Seconds(1.));
188     _secheuristic.setXWeight(0);
189     _secheuristic.setGamma(0.1);
190     _secheuristic.setGammaCutoff(0.08);
191
192     self._webView = webView;
193
194     _playbackClient.setParent(self);
195     _videoFullscreenClient.setParent(self);
196
197     return self;
198 }
199
200 - (void)dealloc
201 {
202     [NSObject cancelPreviousPerformRequestsWithTarget:self];
203     [[NSNotificationCenter defaultCenter] removeObserver:self];
204
205     _playbackClient.setParent(nullptr);
206     _playbackClient.setInterface(nullptr);
207     _videoFullscreenClient.setParent(nullptr);
208     _videoFullscreenClient.setInterface(nullptr);
209
210     [_target release];
211     [_location release];
212
213     [super dealloc];
214 }
215
216 - (void)showUI
217 {
218     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideUI) object:nil];
219
220     if (_playing) {
221         NSTimeInterval hideDelay = autoHideDelay;
222         [self performSelector:@selector(hideUI) withObject:nil afterDelay:hideDelay];
223     }
224     [UIView animateWithDuration:showHideAnimationDuration animations:^{
225         [_stackView setHidden:NO];
226         [_stackView setAlpha:1];
227         self.prefersStatusBarHidden = NO;
228         self.prefersHomeIndicatorAutoHidden = NO;
229         if (_topConstraint)
230             [NSLayoutConstraint deactivateConstraints:@[_topConstraint.get()]];
231         _topConstraint = [[_topGuide topAnchor] constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor];
232         [_topConstraint setActive:YES];
233         if (auto* manager = self._manager)
234             manager->setFullscreenControlsHidden(false);
235     }];
236 }
237
238 - (void)hideUI
239 {
240     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideUI) object:nil];
241     [UIView animateWithDuration:showHideAnimationDuration animations:^{
242
243         if (_topConstraint)
244             [NSLayoutConstraint deactivateConstraints:@[_topConstraint.get()]];
245         _topConstraint = [[_topGuide topAnchor] constraintEqualToAnchor:self.view.topAnchor constant:self.view.safeAreaInsets.top];
246         [_topConstraint setActive:YES];
247         [_stackView setAlpha:0];
248         self.prefersStatusBarHidden = YES;
249         self.prefersHomeIndicatorAutoHidden = YES;
250         if (auto* manager = self._manager)
251             manager->setFullscreenControlsHidden(true);
252     } completion:^(BOOL finished) {
253         if (!finished)
254             return;
255
256         [_stackView setHidden:YES];
257     }];
258 }
259
260 - (void)videoControlsManagerDidChange
261 {
262     WebKit::WebPageProxy* page = [self._webView _page];
263     auto* videoFullscreenManager = page ? page->videoFullscreenManager() : nullptr;
264     auto* videoFullscreenInterface = videoFullscreenManager ? videoFullscreenManager->controlsManagerInterface() : nullptr;
265     auto* playbackSessionInterface = videoFullscreenInterface ? &videoFullscreenInterface->playbackSessionInterface() : nullptr;
266
267     _playbackClient.setInterface(playbackSessionInterface);
268     _videoFullscreenClient.setInterface(videoFullscreenInterface);
269
270     WebCore::PlaybackSessionModel* playbackSessionModel = playbackSessionInterface ? playbackSessionInterface->playbackSessionModel() : nullptr;
271     self.playing = playbackSessionModel ? playbackSessionModel->isPlaying() : NO;
272     [_pipButton setHidden:!playbackSessionModel];
273 }
274
275 - (void)setPrefersStatusBarHidden:(BOOL)value
276 {
277     _prefersStatusBarHidden = value;
278     [self setNeedsStatusBarAppearanceUpdate];
279     [self _updateWebViewFullscreenInsets];
280 }
281
282 - (void)setPrefersHomeIndicatorAutoHidden:(BOOL)value
283 {
284     _prefersHomeIndicatorAutoHidden = value;
285     [self setNeedsUpdateOfHomeIndicatorAutoHidden];
286 }
287
288 - (void)setPlaying:(BOOL)isPlaying
289 {
290     if (_playing == isPlaying)
291         return;
292
293     _playing = isPlaying;
294
295     if (![self viewIfLoaded])
296         return;
297
298     if (!_playing)
299         [self showUI];
300     else {
301         [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideUI) object:nil];
302         NSTimeInterval hideDelay = autoHideDelay;
303         [self performSelector:@selector(hideUI) withObject:nil afterDelay:hideDelay];
304     }
305 }
306
307 - (void)setPictureInPictureActive:(BOOL)active
308 {
309     if (_pictureInPictureActive == active)
310         return;
311
312     _pictureInPictureActive = active;
313     [_pipButton setSelected:active];
314 }
315
316 - (void)setAnimating:(BOOL)animating
317 {
318     if (_animating == animating)
319         return;
320     _animating = animating;
321
322     if (![self viewIfLoaded])
323         return
324
325     [self setNeedsStatusBarAppearanceUpdate];
326
327     if (_animating)
328         [self hideUI];
329     else
330         [self showUI];
331 }
332
333 - (void)willEnterPictureInPicture
334 {
335     auto* interface = _videoFullscreenClient.interface();
336     if (!interface || !interface->pictureInPictureWasStartedWhenEnteringBackground())
337         return;
338
339     [UIView animateWithDuration:pipHideAnimationDuration animations:^{
340         _animatingView.get().alpha = 0;
341     }];
342 }
343
344 - (void)didEnterPictureInPicture
345 {
346     [self _cancelAction:self];
347 }
348
349 - (void)failedToEnterPictureInPicture
350 {
351     auto* interface = _videoFullscreenClient.interface();
352     if (!interface || !interface->pictureInPictureWasStartedWhenEnteringBackground())
353         return;
354
355     [UIView animateWithDuration:pipHideAnimationDuration animations:^{
356         _animatingView.get().alpha = 1;
357     }];
358 }
359
360 #pragma mark - UIViewController Overrides
361
362 - (void)loadView
363 {
364     [self setView:adoptNS([[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]).get()];
365     self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
366     self.view.backgroundColor = [UIColor blackColor];
367
368     _animatingView = adoptNS([[UIView alloc] initWithFrame:self.view.bounds]);
369     _animatingView.get().autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
370     [self.view addSubview:_animatingView.get()];
371
372     _cancelButton = [_WKExtrinsicButton buttonWithType:UIButtonTypeSystem];
373     [_cancelButton setTranslatesAutoresizingMaskIntoConstraints:NO];
374     [_cancelButton setAdjustsImageWhenHighlighted:NO];
375     [_cancelButton setExtrinsicContentSize:CGSizeMake(60.0, 47.0)];
376     NSBundle *bundle = [NSBundle bundleForClass:self.class];
377     UIImage *doneImage = [UIImage imageNamed:@"Done" inBundle:bundle compatibleWithTraitCollection:nil];
378     [_cancelButton setImage:[doneImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
379     [_cancelButton setTintColor:[UIColor whiteColor]];
380     [_cancelButton sizeToFit];
381     [_cancelButton addTarget:self action:@selector(_cancelAction:) forControlEvents:UIControlEventTouchUpInside];
382
383     _pipButton = [_WKExtrinsicButton buttonWithType:UIButtonTypeSystem];
384     [_pipButton setTranslatesAutoresizingMaskIntoConstraints:NO];
385     [_pipButton setAdjustsImageWhenHighlighted:NO];
386     [_pipButton setExtrinsicContentSize:CGSizeMake(60.0, 47.0)];
387     UIImage *startPiPImage = [UIImage imageNamed:@"StartPictureInPictureButton" inBundle:bundle compatibleWithTraitCollection:nil];
388     UIImage *stopPiPImage = [UIImage imageNamed:@"StopPictureInPictureButton" inBundle:bundle compatibleWithTraitCollection:nil];
389     [_pipButton setImage:[startPiPImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
390     [_pipButton setImage:[stopPiPImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateSelected];
391     [_pipButton setTintColor:[UIColor whiteColor]];
392     [_pipButton sizeToFit];
393     [_pipButton addTarget:self action:@selector(_togglePiPAction:) forControlEvents:UIControlEventTouchUpInside];
394
395     _stackView = adoptNS([[WKFullscreenStackView alloc] init]);
396     [_stackView setTranslatesAutoresizingMaskIntoConstraints:NO];
397     [_stackView addArrangedSubview:_cancelButton.get() applyingMaterialStyle:AVBackgroundViewMaterialStyleSecondary tintEffectStyle:AVBackgroundViewTintEffectStyleSecondary];
398     [_stackView addArrangedSubview:_pipButton.get() applyingMaterialStyle:AVBackgroundViewMaterialStylePrimary tintEffectStyle:AVBackgroundViewTintEffectStyleSecondary];
399     [_animatingView addSubview:_stackView.get()];
400
401     UILayoutGuide *safeArea = self.view.safeAreaLayoutGuide;
402     UILayoutGuide *margins = self.view.layoutMarginsGuide;
403
404     _topGuide = adoptNS([[UILayoutGuide alloc] init]);
405     [self.view addLayoutGuide:_topGuide.get()];
406     NSLayoutAnchor *topAnchor = [_topGuide topAnchor];
407     _topConstraint = [topAnchor constraintEqualToAnchor:safeArea.topAnchor];
408     [NSLayoutConstraint activateConstraints:@[
409         _topConstraint.get(),
410         [[_stackView topAnchor] constraintEqualToAnchor:topAnchor],
411         [[_stackView leadingAnchor] constraintEqualToAnchor:margins.leadingAnchor],
412     ]];
413
414     [_stackView setAlpha:0];
415     [_stackView setHidden:YES];
416     [self videoControlsManagerDidChange];
417
418     _touchGestureRecognizer = adoptNS([[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_touchDetected:)]);
419     [_touchGestureRecognizer setCancelsTouchesInView:NO];
420     [_touchGestureRecognizer setMinimumPressDuration:0];
421     [_touchGestureRecognizer setDelegate:self];
422     [self.view addGestureRecognizer:_touchGestureRecognizer.get()];
423 }
424
425 - (void)viewWillAppear:(BOOL)animated
426 {
427     self._webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
428     self._webView.frame = self.view.bounds;
429     [_animatingView insertSubview:self._webView atIndex:0];
430
431     if (auto* manager = self._manager)
432         manager->setFullscreenAutoHideDuration(Seconds(showHideAnimationDuration));
433
434     [super viewWillAppear:animated];
435 }
436
437 - (void)viewDidLayoutSubviews
438 {
439     [self _updateWebViewFullscreenInsets];
440     _secheuristic.setSize(self.view.bounds.size);
441 }
442
443 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
444 {
445     [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
446     [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
447         [self._webView _beginAnimatedResizeWithUpdates:^{
448             [self._webView _overrideLayoutParametersWithMinimumLayoutSize:size maximumUnobscuredSizeOverride:size];
449         }];
450  ALLOW_DEPRECATED_DECLARATIONS_BEGIN
451         [self._webView _setInterfaceOrientationOverride:[UIApp statusBarOrientation]];
452  ALLOW_DEPRECATED_DECLARATIONS_END
453     } completion:^(id <UIViewControllerTransitionCoordinatorContext>context) {
454         [self._webView _endAnimatedResize];
455     }];
456 }
457
458 - (UIStatusBarStyle)preferredStatusBarStyle
459 {
460     return UIStatusBarStyleLightContent;
461 }
462
463 - (BOOL)prefersStatusBarHidden
464 {
465     return _animating || _prefersStatusBarHidden;
466 }
467
468 #pragma mark - UIGestureRecognizerDelegate
469
470 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
471 {
472     return YES;
473 }
474
475 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
476 {
477     if (!self.animating)
478         [self showUI];
479     return YES;
480 }
481
482 #pragma mark - Internal Interface
483
484 @dynamic _manager;
485 - (WebKit::WebFullScreenManagerProxy*)_manager
486 {
487     if (auto* page = [self._webView _page])
488         return page->fullScreenManager();
489     return nullptr;
490 }
491
492 @dynamic _effectiveFullscreenInsets;
493 - (WebCore::FloatBoxExtent)_effectiveFullscreenInsets
494 {
495     auto safeAreaInsets = self.view.safeAreaInsets;
496     WebCore::FloatBoxExtent insets { safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left };
497
498     CGRect cancelFrame = _cancelButton.get().frame;
499     CGPoint maxXY = CGPointMake(CGRectGetMaxX(cancelFrame), CGRectGetMaxY(cancelFrame));
500     insets.setTop([_cancelButton convertPoint:maxXY toView:self.view].y);
501     return insets;
502 }
503
504 - (void)_cancelAction:(id)sender
505 {
506     [[self target] performSelector:[self action]];
507 }
508
509 - (void)_togglePiPAction:(id)sender
510 {
511     WebKit::WebPageProxy* page = [self._webView _page];
512     if (!page)
513         return;
514
515     WebKit::PlaybackSessionManagerProxy* playbackSessionManager = page->playbackSessionManager();
516     if (!playbackSessionManager)
517         return;
518
519     PlatformPlaybackSessionInterface* playbackSessionInterface = playbackSessionManager->controlsManagerInterface();
520     if (!playbackSessionInterface)
521         return;
522
523     WebCore::PlaybackSessionModel* playbackSessionModel = playbackSessionInterface->playbackSessionModel();
524     if (!playbackSessionModel)
525         return;
526
527     playbackSessionModel->togglePictureInPicture();
528 }
529
530 - (void)_touchDetected:(id)sender
531 {
532     if ([_touchGestureRecognizer state] == UIGestureRecognizerStateBegan || [_touchGestureRecognizer state] == UIGestureRecognizerStateEnded) {
533         double score = _secheuristic.scoreOfNextTouch([_touchGestureRecognizer locationInView:self.view]);
534         if (score > requiredScore)
535             [self _showPhishingAlert];
536     }
537     if (!self.animating)
538         [self showUI];
539 }
540
541 - (void)_statusBarFrameDidChange:(NSNotificationCenter *)notification
542 {
543 ALLOW_DEPRECATED_DECLARATIONS_BEGIN
544     CGFloat height = UIApplication.sharedApplication.statusBarFrame.size.height;
545 ALLOW_DEPRECATED_DECLARATIONS_END
546     if (!height || height == _nonZeroStatusBarHeight)
547         return;
548
549     _nonZeroStatusBarHeight = height;
550     [self _updateWebViewFullscreenInsets];
551 }
552
553 - (void)_updateWebViewFullscreenInsets
554 {
555     if (auto* manager = self._manager)
556         manager->setFullscreenInsets(self._effectiveFullscreenInsets);
557 }
558
559 - (void)_showPhishingAlert
560 {
561     NSString *alertTitle = WEB_UI_STRING("It looks like you are typing while in full screen", "Full Screen Deceptive Website Warning Sheet Title");
562     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];
563     UIAlertController* alert = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleAlert];
564
565     if (auto* page = [self._webView _page]) {
566         page->suspendAllMediaPlayback();
567         page->suspendActiveDOMObjectsAndAnimations();
568     }
569
570     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) {
571         [self _cancelAction:action];
572         if (auto* page = [self._webView _page]) {
573             page->resumeActiveDOMObjectsAndAnimations();
574             page->resumeAllMediaPlayback();
575         }
576     }];
577
578     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) {
579         if (auto* page = [self._webView _page]) {
580             page->resumeActiveDOMObjectsAndAnimations();
581             page->resumeAllMediaPlayback();
582         }
583         _secheuristic.reset();
584     }];
585
586     [alert addAction:exitAction];
587     [alert addAction:stayAction];
588     [self presentViewController:alert animated:YES completion:nil];
589 }
590
591 @end
592
593 #endif // ENABLE(FULLSCREEN_API) && PLATFORM(IOS_FAMILY)