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_FAMILY) |
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_FAMILY) |
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_FAMILY) |
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 | if (paintInfo.context().paintingDisabled()) |
121 | return; |
122 | |
123 | // Push a clip. |
124 | GraphicsContextStateSaver stateSaver(paintInfo.context(), false); |
125 | if (paintInfo.phase == PaintPhase::Foreground || paintInfo.phase == PaintPhase::ChildBlockBackgrounds) { |
126 | IntRect clipRect = enclosingIntRect(LayoutRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(), |
127 | width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight)); |
128 | if (clipRect.isEmpty()) |
129 | return; |
130 | stateSaver.save(); |
131 | paintInfo.context().clip(clipRect); |
132 | } |
133 | |
134 | if (paintInfo.phase == PaintPhase::Foreground) { |
135 | const String& displayedFilename = fileTextValue(); |
136 | const FontCascade& font = style().fontCascade(); |
137 | TextRun textRun = constructTextRun(displayedFilename, style(), AllowTrailingExpansion, RespectDirection | RespectDirectionOverride); |
138 | |
139 | #if PLATFORM(IOS_FAMILY) |
140 | int iconHeight = nodeHeight(uploadButton()); |
141 | int iconWidth = iconHeight; |
142 | #endif |
143 | // Determine where the filename should be placed |
144 | LayoutUnit contentLeft = paintOffset.x() + borderLeft() + paddingLeft(); |
145 | HTMLInputElement* button = uploadButton(); |
146 | if (!button) |
147 | return; |
148 | |
149 | LayoutUnit buttonWidth = nodeWidth(button); |
150 | LayoutUnit buttonAndIconWidth = buttonWidth + afterButtonSpacing |
151 | + (inputElement().icon() ? iconWidth + iconFilenameSpacing : 0); |
152 | LayoutUnit textX; |
153 | if (style().isLeftToRightDirection()) |
154 | textX = contentLeft + buttonAndIconWidth; |
155 | else |
156 | textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun); |
157 | |
158 | LayoutUnit textY; |
159 | // We want to match the button's baseline |
160 | // FIXME: Make this work with transforms. |
161 | if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer())) |
162 | textY = paintOffset.y() + borderTop() + paddingTop() + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine); |
163 | else |
164 | textY = baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine); |
165 | |
166 | paintInfo.context().setFillColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor)); |
167 | |
168 | // Draw the filename |
169 | paintInfo.context().drawBidiText(font, textRun, IntPoint(roundToInt(textX), roundToInt(textY))); |
170 | |
171 | if (inputElement().icon()) { |
172 | // Determine where the icon should be placed |
173 | LayoutUnit iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2; |
174 | LayoutUnit iconX; |
175 | if (style().isLeftToRightDirection()) |
176 | iconX = contentLeft + buttonWidth + afterButtonSpacing; |
177 | else |
178 | iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth; |
179 | |
180 | #if PLATFORM(IOS_FAMILY) |
181 | if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer())) { |
182 | // Draw the file icon and decorations. |
183 | IntRect iconRect(iconX, iconY, iconWidth, iconHeight); |
184 | RenderTheme::FileUploadDecorations decorationsType = inputElement().files()->length() == 1 ? RenderTheme::SingleFile : RenderTheme::MultipleFiles; |
185 | theme().paintFileUploadIconDecorations(*this, *buttonRenderer, paintInfo, iconRect, inputElement().icon(), decorationsType); |
186 | } |
187 | #else |
188 | // Draw the file icon |
189 | inputElement().icon()->paint(paintInfo.context(), IntRect(roundToInt(iconX), roundToInt(iconY), iconWidth, iconHeight)); |
190 | #endif |
191 | } |
192 | } |
193 | |
194 | // Paint the children. |
195 | RenderBlockFlow::paintObject(paintInfo, paintOffset); |
196 | } |
197 | |
198 | void RenderFileUploadControl::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const |
199 | { |
200 | // Figure out how big the filename space needs to be for a given number of characters |
201 | // (using "0" as the nominal character). |
202 | const UChar character = '0'; |
203 | const String characterAsString = String(&character, 1); |
204 | const FontCascade& font = style().fontCascade(); |
205 | // FIXME: Remove the need for this const_cast by making constructTextRun take a const RenderObject*. |
206 | float minDefaultLabelWidth = defaultWidthNumChars * font.width(constructTextRun(characterAsString, style(), AllowTrailingExpansion)); |
207 | |
208 | const String label = theme().fileListDefaultLabel(inputElement().multiple()); |
209 | float defaultLabelWidth = font.width(constructTextRun(label, style(), AllowTrailingExpansion)); |
210 | if (HTMLInputElement* button = uploadButton()) |
211 | if (RenderObject* buttonRenderer = button->renderer()) |
212 | defaultLabelWidth += buttonRenderer->maxPreferredLogicalWidth() + afterButtonSpacing; |
213 | maxLogicalWidth = static_cast<int>(ceilf(std::max(minDefaultLabelWidth, defaultLabelWidth))); |
214 | |
215 | if (!style().width().isPercentOrCalculated()) |
216 | minLogicalWidth = maxLogicalWidth; |
217 | } |
218 | |
219 | void RenderFileUploadControl::computePreferredLogicalWidths() |
220 | { |
221 | ASSERT(preferredLogicalWidthsDirty()); |
222 | |
223 | m_minPreferredLogicalWidth = 0; |
224 | m_maxPreferredLogicalWidth = 0; |
225 | |
226 | if (style().width().isFixed() && style().width().value() > 0) |
227 | m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width().value()); |
228 | else |
229 | computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); |
230 | |
231 | if (style().minWidth().isFixed() && style().minWidth().value() > 0) { |
232 | m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value())); |
233 | m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value())); |
234 | } |
235 | |
236 | if (style().maxWidth().isFixed()) { |
237 | m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value())); |
238 | m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value())); |
239 | } |
240 | |
241 | int toAdd = horizontalBorderAndPaddingExtent(); |
242 | m_minPreferredLogicalWidth += toAdd; |
243 | m_maxPreferredLogicalWidth += toAdd; |
244 | |
245 | setPreferredLogicalWidthsDirty(false); |
246 | } |
247 | |
248 | VisiblePosition RenderFileUploadControl::positionForPoint(const LayoutPoint&, const RenderFragmentContainer*) |
249 | { |
250 | return VisiblePosition(); |
251 | } |
252 | |
253 | HTMLInputElement* RenderFileUploadControl::uploadButton() const |
254 | { |
255 | ASSERT(inputElement().shadowRoot()); |
256 | Node* buttonNode = inputElement().shadowRoot()->firstChild(); |
257 | return is<HTMLInputElement>(buttonNode) ? downcast<HTMLInputElement>(buttonNode) : nullptr; |
258 | } |
259 | |
260 | String RenderFileUploadControl::buttonValue() |
261 | { |
262 | if (HTMLInputElement* button = uploadButton()) |
263 | return button->value(); |
264 | |
265 | return String(); |
266 | } |
267 | |
268 | String RenderFileUploadControl::fileTextValue() const |
269 | { |
270 | auto& input = inputElement(); |
271 | ASSERT(inputElement().files()); |
272 | if (input.files()->length() && !input.displayString().isEmpty()) |
273 | return StringTruncator::rightTruncate(input.displayString(), maxFilenameWidth(), style().fontCascade()); |
274 | return theme().fileListNameForWidth(input.files(), style().fontCascade(), maxFilenameWidth(), input.multiple()); |
275 | } |
276 | |
277 | } // namespace WebCore |
278 | |