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