[iOS] Draw caps lock indicator in password fields
[WebKit-https.git] / Source / WebCore / platform / ios / ValidationBubbleIOS.mm
1 /*
2  * Copyright (C) 2016 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 PLATFORM(IOS_FAMILY)
29
30 #import "ValidationBubble.h"
31
32 #import <pal/spi/ios/UIKitSPI.h>
33 #import <wtf/RetainPtr.h>
34 #import <wtf/SoftLinking.h>
35 #import <wtf/text/WTFString.h>
36
37 SOFT_LINK_FRAMEWORK(UIKit);
38 SOFT_LINK_CLASS(UIKit, UIFont);
39 SOFT_LINK_CLASS(UIKit, UILabel);
40 SOFT_LINK_CLASS(UIKit, UIPopoverPresentationController);
41 SOFT_LINK_CLASS(UIKit, UITapGestureRecognizer);
42 SOFT_LINK_CLASS(UIKit, UIView);
43 SOFT_LINK_CLASS(UIKit, UIViewController);
44 SOFT_LINK_CONSTANT(UIKit, UIFontTextStyleCallout, UIFontTextStyle);
45 SOFT_LINK_CONSTANT(UIKit, UIAccessibilityAnnouncementNotification, UIAccessibilityNotifications);
46 #define UIAccessibilityPostNotification getUIAccessibilityPostNotification
47 SOFT_LINK(UIKit, UIAccessibilityPostNotification, void, (UIAccessibilityNotifications n, id argument), (n, argument));
48
49 @interface WebValidationBubbleTapRecognizer : NSObject
50 @end
51
52 @implementation WebValidationBubbleTapRecognizer {
53     RetainPtr<UIViewController> _popoverController;
54     RetainPtr<UITapGestureRecognizer> _tapGestureRecognizer;
55 }
56
57 - (WebValidationBubbleTapRecognizer *)initWithPopoverController:(UIViewController *)popoverController
58 {
59     self = [super init];
60     if (!self)
61         return nil;
62
63     _popoverController = popoverController;
64     _tapGestureRecognizer = adoptNS([allocUITapGestureRecognizerInstance() initWithTarget:self action:@selector(dismissPopover)]);
65     [[_popoverController view] addGestureRecognizer:_tapGestureRecognizer.get()];
66
67     return self;
68 }
69
70 - (void)dealloc
71 {
72     [[_popoverController view] removeGestureRecognizer:_tapGestureRecognizer.get()];
73     [super dealloc];
74 }
75
76 - (void)dismissPopover
77 {
78     [_popoverController dismissViewControllerAnimated:NO completion:nil];
79 }
80
81 @end
82
83 @interface WebValidationBubbleDelegate : NSObject <UIPopoverPresentationControllerDelegate> {
84 }
85 @end
86
87 @implementation WebValidationBubbleDelegate
88
89 - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection
90 {
91     UNUSED_PARAM(controller);
92     UNUSED_PARAM(traitCollection);
93     // This is needed to force UIKit to use a popover on iPhone as well.
94     return UIModalPresentationNone;
95 }
96
97 @end
98
99 namespace WebCore {
100
101 static const CGFloat horizontalPadding = 17;
102 static const CGFloat verticalPadding = 9;
103 static const CGFloat maxLabelWidth = 300;
104
105 ValidationBubble::ValidationBubble(UIView* view, const String& message, const Settings&)
106     : m_view(view)
107     , m_message(message)
108 {
109     m_popoverController = adoptNS([allocUIViewControllerInstance() init]);
110     [m_popoverController setModalPresentationStyle:UIModalPresentationPopover];
111
112     RetainPtr<UIView> popoverView = adoptNS([allocUIViewInstance() initWithFrame:CGRectZero]);
113     [m_popoverController setView:popoverView.get()];
114     m_tapRecognizer = adoptNS([[WebValidationBubbleTapRecognizer alloc] initWithPopoverController:m_popoverController.get()]);
115
116     RetainPtr<UILabel> label = adoptNS([allocUILabelInstance() initWithFrame:CGRectZero]);
117     [label setText:message];
118     [label setFont:[getUIFontClass() preferredFontForTextStyle:getUIFontTextStyleCallout()]];
119     m_fontSize = [[label font] pointSize];
120     [label setLineBreakMode:NSLineBreakByTruncatingTail];
121     [label setNumberOfLines:4];
122     [popoverView addSubview:label.get()];
123
124     CGSize labelSize = [label sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
125     [label setFrame:CGRectMake(horizontalPadding, verticalPadding, labelSize.width, labelSize.height)];
126     [popoverView setFrame:CGRectMake(horizontalPadding, verticalPadding, labelSize.width + horizontalPadding * 2, labelSize.height + verticalPadding * 2)];
127
128     [m_popoverController setPreferredContentSize:popoverView.get().frame.size];
129 }
130
131 ValidationBubble::~ValidationBubble()
132 {
133     [m_popoverController dismissViewControllerAnimated:NO completion:nil];
134 }
135
136 void ValidationBubble::show()
137 {
138     if ([m_popoverController parentViewController] || [m_popoverController presentingViewController])
139         return;
140
141     // Protect the validation bubble so it stays alive until it is effectively presented. UIKit does not deal nicely with
142     // dismissing a popover that is being presented.
143     RefPtr<ValidationBubble> protectedThis(this);
144     [m_presentingViewController presentViewController:m_popoverController.get() animated:NO completion:[protectedThis]() {
145         // Hide this popover from VoiceOver and instead announce the message.
146         [protectedThis->m_popoverController.get().view setAccessibilityElementsHidden:YES];
147     }];
148
149     UIAccessibilityPostNotification(getUIAccessibilityAnnouncementNotification(), m_message);
150 }
151
152 static UIViewController *fallbackViewController(UIView *view)
153 {
154     for (UIView *currentView = view; currentView; currentView = currentView.superview) {
155         if (UIViewController *viewController = [getUIViewControllerClass() viewControllerForView:currentView])
156             return viewController;
157     }
158     NSLog(@"Failed to find a view controller to show form validation popover");
159     return nil;
160 }
161
162 void ValidationBubble::setAnchorRect(const IntRect& anchorRect, UIViewController* presentingViewController)
163 {
164     if (!presentingViewController)
165         presentingViewController = fallbackViewController(m_view);
166
167     UIPopoverPresentationController *presentationController = [m_popoverController popoverPresentationController];
168     m_popoverDelegate = adoptNS([[WebValidationBubbleDelegate alloc] init]);
169     presentationController.delegate = m_popoverDelegate.get();
170     presentationController.passthroughViews = [NSArray arrayWithObjects:presentingViewController.view, m_view, nil];
171
172     presentationController.permittedArrowDirections = UIPopoverArrowDirectionUp;
173     presentationController.sourceView = m_view;
174     presentationController.sourceRect = CGRectMake(anchorRect.x(), anchorRect.y(), anchorRect.width(), anchorRect.height());
175     m_presentingViewController = presentingViewController;
176 }
177
178 } // namespace WebCore
179
180 #endif // PLATFORM(IOS_FAMILY)