Use a 1-byte enum class for TextDirection
[WebKit-https.git] / Source / WebKit / UIProcess / mac / WebPopupMenuProxyMac.mm
1 /*
2  * Copyright (C) 2010-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 #import "WebPopupMenuProxyMac.h"
28
29 #if USE(APPKIT)
30
31 #import "NativeWebMouseEvent.h"
32 #import "PageClientImplMac.h"
33 #import "PlatformPopupMenuData.h"
34 #import "StringUtilities.h"
35 #import "WebPopupItem.h"
36 #import <pal/system/mac/PopupMenu.h>
37 #import <wtf/ProcessPrivilege.h>
38
39 using namespace WebCore;
40
41 namespace WebKit {
42
43 WebPopupMenuProxyMac::WebPopupMenuProxyMac(NSView *webView, WebPopupMenuProxy::Client& client)
44     : WebPopupMenuProxy(client)
45     , m_webView(webView)
46     , m_wasCanceled(false)
47 {
48 }
49
50 WebPopupMenuProxyMac::~WebPopupMenuProxyMac()
51 {
52     if (m_popup)
53         [m_popup setControlView:nil];
54 }
55
56 void WebPopupMenuProxyMac::populate(const Vector<WebPopupItem>& items, NSFont *font, TextDirection menuTextDirection)
57 {
58     if (m_popup)
59         [m_popup removeAllItems];
60     else {
61         m_popup = adoptNS([[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]);
62         [m_popup setUsesItemFromMenu:NO];
63         [m_popup setAutoenablesItems:NO];
64     }
65
66     int size = items.size();
67
68     for (int i = 0; i < size; i++) {
69         if (items[i].m_type == WebPopupItem::Separator)
70             [[m_popup menu] addItem:[NSMenuItem separatorItem]];
71         else {
72             [m_popup addItemWithTitle:@""];
73             NSMenuItem *menuItem = [m_popup lastItem];
74
75             RetainPtr<NSMutableParagraphStyle> paragraphStyle = adoptNS([[NSParagraphStyle defaultParagraphStyle] mutableCopy]);
76             NSWritingDirection writingDirection = items[i].m_textDirection == TextDirection::LTR ? NSWritingDirectionLeftToRight : NSWritingDirectionRightToLeft;
77             [paragraphStyle setBaseWritingDirection:writingDirection];
78             [paragraphStyle setAlignment:menuTextDirection == TextDirection::LTR ? NSTextAlignmentLeft : NSTextAlignmentRight];
79             RetainPtr<NSMutableDictionary> attributes = adoptNS([[NSMutableDictionary alloc] initWithObjectsAndKeys:
80                 paragraphStyle.get(), NSParagraphStyleAttributeName,
81                 font, NSFontAttributeName,
82             nil]);
83             if (items[i].m_hasTextDirectionOverride) {
84                 RetainPtr<NSNumber> writingDirectionValue = adoptNS([[NSNumber alloc] initWithInteger:writingDirection + NSWritingDirectionOverride]);
85                 RetainPtr<NSArray> writingDirectionArray = adoptNS([[NSArray alloc] initWithObjects:writingDirectionValue.get(), nil]);
86                 [attributes setObject:writingDirectionArray.get() forKey:NSWritingDirectionAttributeName];
87             }
88             RetainPtr<NSAttributedString> string = adoptNS([[NSAttributedString alloc] initWithString:nsStringFromWebCoreString(items[i].m_text) attributes:attributes.get()]);
89
90             [menuItem setAttributedTitle:string.get()];
91             // We set the title as well as the attributed title here. The attributed title will be displayed in the menu,
92             // but typeahead will use the non-attributed string that doesn't contain any leading or trailing whitespace.
93             [menuItem setTitle:[[string string] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
94             [menuItem setEnabled:items[i].m_isEnabled];
95             [menuItem setToolTip:nsStringFromWebCoreString(items[i].m_toolTip)];
96         }
97     }
98 }
99
100 void WebPopupMenuProxyMac::showPopupMenu(const IntRect& rect, TextDirection textDirection, double pageScaleFactor, const Vector<WebPopupItem>& items, const PlatformPopupMenuData& data, int32_t selectedIndex)
101 {
102     ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
103     NSFont *font;
104     if (data.fontInfo.fontAttributeDictionary) {
105         NSFontDescriptor *fontDescriptor = [NSFontDescriptor fontDescriptorWithFontAttributes:(__bridge NSDictionary *)data.fontInfo.fontAttributeDictionary.get()];
106         font = [NSFont fontWithDescriptor:fontDescriptor size:((pageScaleFactor != 1) ? [fontDescriptor pointSize] * pageScaleFactor : 0)];
107     } else
108         font = [NSFont menuFontOfSize:0];
109
110     populate(items, font, textDirection);
111
112     [m_popup attachPopUpWithFrame:rect inView:m_webView];
113     [m_popup selectItemAtIndex:selectedIndex];
114     [m_popup setUserInterfaceLayoutDirection:textDirection == TextDirection::LTR ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft];
115
116     NSMenu *menu = [m_popup menu];
117     [menu setUserInterfaceLayoutDirection:textDirection == TextDirection::LTR ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft];
118
119     // These values were borrowed from AppKit to match their placement of the menu.
120     const int popOverHorizontalAdjust = -13;
121     const int popUnderHorizontalAdjust = 6;
122     const int popUnderVerticalAdjust = 6;
123     
124     // Menus that pop-over directly obscure the node that generated the popup menu.
125     // Menus that pop-under are offset underneath it.
126     NSPoint location;
127     if (data.shouldPopOver) {
128         NSRect titleFrame = [m_popup  titleRectForBounds:rect];
129         if (titleFrame.size.width <= 0 || titleFrame.size.height <= 0)
130             titleFrame = rect;
131         float verticalOffset = roundf((NSMaxY(rect) - NSMaxY(titleFrame)) + NSHeight(titleFrame)) + 1;
132         if (textDirection == TextDirection::LTR)
133             location = NSMakePoint(NSMinX(rect) + popOverHorizontalAdjust, NSMaxY(rect) - verticalOffset);
134         else
135             location = NSMakePoint(NSMaxX(rect) - popOverHorizontalAdjust, NSMaxY(rect) - verticalOffset);
136     } else {
137         if (textDirection == TextDirection::LTR)
138             location = NSMakePoint(NSMinX(rect) + popUnderHorizontalAdjust, NSMaxY(rect) + popUnderVerticalAdjust);
139         else
140             location = NSMakePoint(NSMaxX(rect) - popUnderHorizontalAdjust, NSMaxY(rect) + popUnderVerticalAdjust);
141     }
142     RetainPtr<NSView> dummyView = adoptNS([[NSView alloc] initWithFrame:rect]);
143     [dummyView.get() setUserInterfaceLayoutDirection:textDirection == TextDirection::LTR ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft];
144     [m_webView addSubview:dummyView.get()];
145     location = [dummyView convertPoint:location fromView:m_webView];
146
147     NSControlSize controlSize;
148     switch (data.menuSize) {
149     case WebCore::PopupMenuStyle::PopupMenuSizeNormal:
150         controlSize = NSControlSizeRegular;
151         break;
152     case WebCore::PopupMenuStyle::PopupMenuSizeSmall:
153         controlSize = NSControlSizeSmall;
154         break;
155     case WebCore::PopupMenuStyle::PopupMenuSizeMini:
156         controlSize = NSControlSizeMini;
157         break;
158     }
159
160     Ref<WebPopupMenuProxyMac> protect(*this);
161     PAL::popUpMenu(menu, location, roundf(NSWidth(rect)), dummyView.get(), selectedIndex, font, controlSize, data.hideArrows);
162
163     [m_popup dismissPopUp];
164     [dummyView removeFromSuperview];
165     
166     if (!m_client || m_wasCanceled)
167         return;
168     
169     m_client->valueChangedForPopupMenu(this, [m_popup indexOfSelectedItem]);
170     
171     // <https://bugs.webkit.org/show_bug.cgi?id=57904> This code is adopted from EventHandler::sendFakeEventsAfterWidgetTracking().
172     if (!m_client->currentlyProcessedMouseDownEvent())
173         return;
174     
175     NSEvent* initiatingNSEvent = m_client->currentlyProcessedMouseDownEvent()->nativeEvent();
176     if ([initiatingNSEvent type] != NSEventTypeLeftMouseDown)
177         return;
178
179     NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
180                                             location:[initiatingNSEvent locationInWindow]
181                                        modifierFlags:[initiatingNSEvent modifierFlags]
182                                            timestamp:[initiatingNSEvent timestamp]
183                                         windowNumber:[initiatingNSEvent windowNumber]
184                                              context:nullptr
185                                          eventNumber:[initiatingNSEvent eventNumber]
186                                           clickCount:[initiatingNSEvent clickCount]
187                                             pressure:[initiatingNSEvent pressure]];
188
189     [NSApp postEvent:fakeEvent atStart:YES];
190     fakeEvent = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
191                                    location:[[m_webView window]
192 #pragma clang diagnostic push
193 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
194                         convertScreenToBase:[NSEvent mouseLocation]]
195 #pragma clang diagnostic pop
196                               modifierFlags:[initiatingNSEvent modifierFlags]
197                                   timestamp:[initiatingNSEvent timestamp]
198                                windowNumber:[initiatingNSEvent windowNumber]
199                                     context:nullptr
200                                 eventNumber:0
201                                  clickCount:0
202                                    pressure:0];
203     [NSApp postEvent:fakeEvent atStart:YES];
204 }
205
206 void WebPopupMenuProxyMac::hidePopupMenu()
207 {
208     [m_popup dismissPopUp];
209 }
210
211 void WebPopupMenuProxyMac::cancelTracking()
212 {
213     [[m_popup menu] cancelTracking];
214     m_wasCanceled = true;
215 }
216
217 } // namespace WebKit
218
219 #endif // USE(APPKIT)