Use "= default" to denote default constructor or destructor
[WebKit-https.git] / Source / WebCore / rendering / RenderFileUploadControl.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2012 Apple Inc. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "RenderFileUploadControl.h"
23
24 #include "FileList.h"
25 #include "FontCascade.h"
26 #include "GraphicsContext.h"
27 #include "HTMLInputElement.h"
28 #include "HTMLNames.h"
29 #include "Icon.h"
30 #include "LocalizedStrings.h"
31 #include "PaintInfo.h"
32 #include "RenderButton.h"
33 #include "RenderText.h"
34 #include "RenderTheme.h"
35 #include "ShadowRoot.h"
36 #include "StringTruncator.h"
37 #include "TextRun.h"
38 #include "VisiblePosition.h"
39 #include <math.h>
40
41 namespace WebCore {
42
43 using namespace HTMLNames;
44
45 const int afterButtonSpacing = 4;
46 #if !PLATFORM(IOS)
47 const int iconHeight = 16;
48 const int iconWidth = 16;
49 const int iconFilenameSpacing = 2;
50 const int defaultWidthNumChars = 34;
51 #else
52 // On iOS the icon height matches the button height, to maximize the icon size.
53 const int iconFilenameSpacing = afterButtonSpacing;
54 const int defaultWidthNumChars = 38;
55 #endif
56 const int buttonShadowHeight = 2;
57
58 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement& input, RenderStyle&& style)
59     : RenderBlockFlow(input, WTFMove(style))
60     , m_canReceiveDroppedFiles(input.canReceiveDroppedFiles())
61 {
62 }
63
64 RenderFileUploadControl::~RenderFileUploadControl() = default;
65
66 HTMLInputElement& RenderFileUploadControl::inputElement() const
67 {
68     return downcast<HTMLInputElement>(nodeForNonAnonymous());
69 }
70
71 void RenderFileUploadControl::updateFromElement()
72 {
73     ASSERT(inputElement().isFileUpload());
74
75     if (HTMLInputElement* button = uploadButton()) {
76         bool newCanReceiveDroppedFilesState = inputElement().canReceiveDroppedFiles();
77         if (m_canReceiveDroppedFiles != newCanReceiveDroppedFilesState) {
78             m_canReceiveDroppedFiles = newCanReceiveDroppedFilesState;
79             button->setActive(newCanReceiveDroppedFilesState);
80         }
81     }
82
83     // This only supports clearing out the files, but that's OK because for
84     // security reasons that's the only change the DOM is allowed to make.
85     FileList* files = inputElement().files();
86     ASSERT(files);
87     if (files && files->isEmpty())
88         repaint();
89 }
90
91 static int nodeWidth(Node* node)
92 {
93     return (node && node->renderBox()) ? roundToInt(node->renderBox()->size().width()) : 0;
94 }
95
96 #if PLATFORM(IOS)
97 static int nodeHeight(Node* node)
98 {
99     return (node && node->renderBox()) ? roundToInt(node->renderBox()->size().height()) : 0;
100 }
101 #endif
102
103 int RenderFileUploadControl::maxFilenameWidth() const
104 {
105 #if PLATFORM(IOS)
106     int iconWidth = nodeHeight(uploadButton());
107 #endif
108     return std::max(0, snappedIntRect(contentBoxRect()).width() - nodeWidth(uploadButton()) - afterButtonSpacing
109         - (inputElement().icon() ? iconWidth + iconFilenameSpacing : 0));
110 }
111
112 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
113 {
114     if (style().visibility() != VISIBLE)
115         return;
116     
117     // Push a clip.
118     GraphicsContextStateSaver stateSaver(paintInfo.context(), false);
119     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
120         IntRect clipRect = enclosingIntRect(LayoutRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
121                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight));
122         if (clipRect.isEmpty())
123             return;
124         stateSaver.save();
125         paintInfo.context().clip(clipRect);
126     }
127
128     if (paintInfo.phase == PaintPhaseForeground) {
129         const String& displayedFilename = fileTextValue();
130         const FontCascade& font = style().fontCascade();
131         TextRun textRun = constructTextRun(displayedFilename, style(), AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
132
133 #if PLATFORM(IOS)
134         int iconHeight = nodeHeight(uploadButton());
135         int iconWidth = iconHeight;
136 #endif
137         // Determine where the filename should be placed
138         LayoutUnit contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
139         HTMLInputElement* button = uploadButton();
140         if (!button)
141             return;
142
143         LayoutUnit buttonWidth = nodeWidth(button);
144         LayoutUnit buttonAndIconWidth = buttonWidth + afterButtonSpacing
145             + (inputElement().icon() ? iconWidth + iconFilenameSpacing : 0);
146         LayoutUnit textX;
147         if (style().isLeftToRightDirection())
148             textX = contentLeft + buttonAndIconWidth;
149         else
150             textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
151
152         LayoutUnit textY = 0;
153         // We want to match the button's baseline
154         // FIXME: Make this work with transforms.
155         if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer()))
156             textY = paintOffset.y() + borderTop() + paddingTop() + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
157         else
158             textY = baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
159
160         paintInfo.context().setFillColor(style().visitedDependentColor(CSSPropertyColor));
161         
162         // Draw the filename
163         paintInfo.context().drawBidiText(font, textRun, IntPoint(roundToInt(textX), roundToInt(textY)));
164         
165         if (inputElement().icon()) {
166             // Determine where the icon should be placed
167             LayoutUnit iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
168             LayoutUnit iconX;
169             if (style().isLeftToRightDirection())
170                 iconX = contentLeft + buttonWidth + afterButtonSpacing;
171             else
172                 iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth;
173
174 #if PLATFORM(IOS)
175             if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer())) {
176                 // Draw the file icon and decorations.
177                 IntRect iconRect(iconX, iconY, iconWidth, iconHeight);
178                 RenderTheme::FileUploadDecorations decorationsType = inputElement().files()->length() == 1 ? RenderTheme::SingleFile : RenderTheme::MultipleFiles;
179                 theme().paintFileUploadIconDecorations(*this, *buttonRenderer, paintInfo, iconRect, inputElement().icon(), decorationsType);
180             }
181 #else
182             // Draw the file icon
183             inputElement().icon()->paint(paintInfo.context(), IntRect(roundToInt(iconX), roundToInt(iconY), iconWidth, iconHeight));
184 #endif
185         }
186     }
187
188     // Paint the children.
189     RenderBlockFlow::paintObject(paintInfo, paintOffset);
190 }
191
192 void RenderFileUploadControl::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
193 {
194     // Figure out how big the filename space needs to be for a given number of characters
195     // (using "0" as the nominal character).
196     const UChar character = '0';
197     const String characterAsString = String(&character, 1);
198     const FontCascade& font = style().fontCascade();
199     // FIXME: Remove the need for this const_cast by making constructTextRun take a const RenderObject*.
200     float minDefaultLabelWidth = defaultWidthNumChars * font.width(constructTextRun(characterAsString, style(), AllowTrailingExpansion));
201
202     const String label = theme().fileListDefaultLabel(inputElement().multiple());
203     float defaultLabelWidth = font.width(constructTextRun(label, style(), AllowTrailingExpansion));
204     if (HTMLInputElement* button = uploadButton())
205         if (RenderObject* buttonRenderer = button->renderer())
206             defaultLabelWidth += buttonRenderer->maxPreferredLogicalWidth() + afterButtonSpacing;
207     maxLogicalWidth = static_cast<int>(ceilf(std::max(minDefaultLabelWidth, defaultLabelWidth)));
208
209     if (!style().width().isPercentOrCalculated())
210         minLogicalWidth = maxLogicalWidth;
211 }
212
213 void RenderFileUploadControl::computePreferredLogicalWidths()
214 {
215     ASSERT(preferredLogicalWidthsDirty());
216
217     m_minPreferredLogicalWidth = 0;
218     m_maxPreferredLogicalWidth = 0;
219
220     if (style().width().isFixed() && style().width().value() > 0)
221         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width().value());
222     else
223         computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
224
225     if (style().minWidth().isFixed() && style().minWidth().value() > 0) {
226         m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
227         m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
228     }
229
230     if (style().maxWidth().isFixed()) {
231         m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
232         m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
233     }
234
235     int toAdd = horizontalBorderAndPaddingExtent();
236     m_minPreferredLogicalWidth += toAdd;
237     m_maxPreferredLogicalWidth += toAdd;
238
239     setPreferredLogicalWidthsDirty(false);
240 }
241
242 VisiblePosition RenderFileUploadControl::positionForPoint(const LayoutPoint&, const RenderFragmentContainer*)
243 {
244     return VisiblePosition();
245 }
246
247 HTMLInputElement* RenderFileUploadControl::uploadButton() const
248 {
249     ASSERT(inputElement().shadowRoot());
250     Node* buttonNode = inputElement().shadowRoot()->firstChild();
251     return is<HTMLInputElement>(buttonNode) ? downcast<HTMLInputElement>(buttonNode) : nullptr;
252 }
253
254 String RenderFileUploadControl::buttonValue()
255 {
256     if (HTMLInputElement* button = uploadButton())
257         return button->value();
258     
259     return String();
260 }
261
262 String RenderFileUploadControl::fileTextValue() const
263 {
264     auto& input = inputElement();
265     ASSERT(inputElement().files());
266     if (input.files()->length() && !input.displayString().isEmpty())
267         return StringTruncator::rightTruncate(input.displayString(), maxFilenameWidth(), style().fontCascade());
268     return theme().fileListNameForWidth(input.files(), style().fontCascade(), maxFilenameWidth(), input.multiple());
269 }
270     
271 } // namespace WebCore