8d39feb19c05b87c7f1bc59735a292892473c8e6
[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 #include <wtf/IsoMallocInlines.h>
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
46 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderFileUploadControl);
47
48 const int afterButtonSpacing = 4;
49 #if !PLATFORM(IOS)
50 const int iconHeight = 16;
51 const int iconWidth = 16;
52 const int iconFilenameSpacing = 2;
53 const int defaultWidthNumChars = 34;
54 #else
55 // On iOS the icon height matches the button height, to maximize the icon size.
56 const int iconFilenameSpacing = afterButtonSpacing;
57 const int defaultWidthNumChars = 38;
58 #endif
59 const int buttonShadowHeight = 2;
60
61 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement& input, RenderStyle&& style)
62     : RenderBlockFlow(input, WTFMove(style))
63     , m_canReceiveDroppedFiles(input.canReceiveDroppedFiles())
64 {
65 }
66
67 RenderFileUploadControl::~RenderFileUploadControl() = default;
68
69 HTMLInputElement& RenderFileUploadControl::inputElement() const
70 {
71     return downcast<HTMLInputElement>(nodeForNonAnonymous());
72 }
73
74 void RenderFileUploadControl::updateFromElement()
75 {
76     ASSERT(inputElement().isFileUpload());
77
78     if (HTMLInputElement* button = uploadButton()) {
79         bool newCanReceiveDroppedFilesState = inputElement().canReceiveDroppedFiles();
80         if (m_canReceiveDroppedFiles != newCanReceiveDroppedFilesState) {
81             m_canReceiveDroppedFiles = newCanReceiveDroppedFilesState;
82             button->setActive(newCanReceiveDroppedFilesState);
83         }
84     }
85
86     // This only supports clearing out the files, but that's OK because for
87     // security reasons that's the only change the DOM is allowed to make.
88     FileList* files = inputElement().files();
89     ASSERT(files);
90     if (files && files->isEmpty())
91         repaint();
92 }
93
94 static int nodeWidth(Node* node)
95 {
96     return (node && node->renderBox()) ? roundToInt(node->renderBox()->size().width()) : 0;
97 }
98
99 #if PLATFORM(IOS)
100 static int nodeHeight(Node* node)
101 {
102     return (node && node->renderBox()) ? roundToInt(node->renderBox()->size().height()) : 0;
103 }
104 #endif
105
106 int RenderFileUploadControl::maxFilenameWidth() const
107 {
108 #if PLATFORM(IOS)
109     int iconWidth = nodeHeight(uploadButton());
110 #endif
111     return std::max(0, snappedIntRect(contentBoxRect()).width() - nodeWidth(uploadButton()) - afterButtonSpacing
112         - (inputElement().icon() ? iconWidth + iconFilenameSpacing : 0));
113 }
114
115 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
116 {
117     if (style().visibility() != Visibility::Visible)
118         return;
119     
120     // Push a clip.
121     GraphicsContextStateSaver stateSaver(paintInfo.context(), false);
122     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
123         IntRect clipRect = enclosingIntRect(LayoutRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
124                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight));
125         if (clipRect.isEmpty())
126             return;
127         stateSaver.save();
128         paintInfo.context().clip(clipRect);
129     }
130
131     if (paintInfo.phase == PaintPhaseForeground) {
132         const String& displayedFilename = fileTextValue();
133         const FontCascade& font = style().fontCascade();
134         TextRun textRun = constructTextRun(displayedFilename, style(), AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
135
136 #if PLATFORM(IOS)
137         int iconHeight = nodeHeight(uploadButton());
138         int iconWidth = iconHeight;
139 #endif
140         // Determine where the filename should be placed
141         LayoutUnit contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
142         HTMLInputElement* button = uploadButton();
143         if (!button)
144             return;
145
146         LayoutUnit buttonWidth = nodeWidth(button);
147         LayoutUnit buttonAndIconWidth = buttonWidth + afterButtonSpacing
148             + (inputElement().icon() ? iconWidth + iconFilenameSpacing : 0);
149         LayoutUnit textX;
150         if (style().isLeftToRightDirection())
151             textX = contentLeft + buttonAndIconWidth;
152         else
153             textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
154
155         LayoutUnit textY = 0;
156         // We want to match the button's baseline
157         // FIXME: Make this work with transforms.
158         if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer()))
159             textY = paintOffset.y() + borderTop() + paddingTop() + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
160         else
161             textY = baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
162
163         paintInfo.context().setFillColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor));
164         
165         // Draw the filename
166         paintInfo.context().drawBidiText(font, textRun, IntPoint(roundToInt(textX), roundToInt(textY)));
167         
168         if (inputElement().icon()) {
169             // Determine where the icon should be placed
170             LayoutUnit iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
171             LayoutUnit iconX;
172             if (style().isLeftToRightDirection())
173                 iconX = contentLeft + buttonWidth + afterButtonSpacing;
174             else
175                 iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth;
176
177 #if PLATFORM(IOS)
178             if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer())) {
179                 // Draw the file icon and decorations.
180                 IntRect iconRect(iconX, iconY, iconWidth, iconHeight);
181                 RenderTheme::FileUploadDecorations decorationsType = inputElement().files()->length() == 1 ? RenderTheme::SingleFile : RenderTheme::MultipleFiles;
182                 theme().paintFileUploadIconDecorations(*this, *buttonRenderer, paintInfo, iconRect, inputElement().icon(), decorationsType);
183             }
184 #else
185             // Draw the file icon
186             inputElement().icon()->paint(paintInfo.context(), IntRect(roundToInt(iconX), roundToInt(iconY), iconWidth, iconHeight));
187 #endif
188         }
189     }
190
191     // Paint the children.
192     RenderBlockFlow::paintObject(paintInfo, paintOffset);
193 }
194
195 void RenderFileUploadControl::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
196 {
197     // Figure out how big the filename space needs to be for a given number of characters
198     // (using "0" as the nominal character).
199     const UChar character = '0';
200     const String characterAsString = String(&character, 1);
201     const FontCascade& font = style().fontCascade();
202     // FIXME: Remove the need for this const_cast by making constructTextRun take a const RenderObject*.
203     float minDefaultLabelWidth = defaultWidthNumChars * font.width(constructTextRun(characterAsString, style(), AllowTrailingExpansion));
204
205     const String label = theme().fileListDefaultLabel(inputElement().multiple());
206     float defaultLabelWidth = font.width(constructTextRun(label, style(), AllowTrailingExpansion));
207     if (HTMLInputElement* button = uploadButton())
208         if (RenderObject* buttonRenderer = button->renderer())
209             defaultLabelWidth += buttonRenderer->maxPreferredLogicalWidth() + afterButtonSpacing;
210     maxLogicalWidth = static_cast<int>(ceilf(std::max(minDefaultLabelWidth, defaultLabelWidth)));
211
212     if (!style().width().isPercentOrCalculated())
213         minLogicalWidth = maxLogicalWidth;
214 }
215
216 void RenderFileUploadControl::computePreferredLogicalWidths()
217 {
218     ASSERT(preferredLogicalWidthsDirty());
219
220     m_minPreferredLogicalWidth = 0;
221     m_maxPreferredLogicalWidth = 0;
222
223     if (style().width().isFixed() && style().width().value() > 0)
224         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width().value());
225     else
226         computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
227
228     if (style().minWidth().isFixed() && style().minWidth().value() > 0) {
229         m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
230         m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
231     }
232
233     if (style().maxWidth().isFixed()) {
234         m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
235         m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
236     }
237
238     int toAdd = horizontalBorderAndPaddingExtent();
239     m_minPreferredLogicalWidth += toAdd;
240     m_maxPreferredLogicalWidth += toAdd;
241
242     setPreferredLogicalWidthsDirty(false);
243 }
244
245 VisiblePosition RenderFileUploadControl::positionForPoint(const LayoutPoint&, const RenderFragmentContainer*)
246 {
247     return VisiblePosition();
248 }
249
250 HTMLInputElement* RenderFileUploadControl::uploadButton() const
251 {
252     ASSERT(inputElement().shadowRoot());
253     Node* buttonNode = inputElement().shadowRoot()->firstChild();
254     return is<HTMLInputElement>(buttonNode) ? downcast<HTMLInputElement>(buttonNode) : nullptr;
255 }
256
257 String RenderFileUploadControl::buttonValue()
258 {
259     if (HTMLInputElement* button = uploadButton())
260         return button->value();
261     
262     return String();
263 }
264
265 String RenderFileUploadControl::fileTextValue() const
266 {
267     auto& input = inputElement();
268     ASSERT(inputElement().files());
269     if (input.files()->length() && !input.displayString().isEmpty())
270         return StringTruncator::rightTruncate(input.displayString(), maxFilenameWidth(), style().fontCascade());
271     return theme().fileListNameForWidth(input.files(), style().fontCascade(), maxFilenameWidth(), input.multiple());
272 }
273     
274 } // namespace WebCore