Fix crashes for <input> and <textarea> with display:run-in.
[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 "ElementShadow.h"
25 #include "FileList.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 "TextRun.h"
37 #include "VisiblePosition.h"
38 #include <math.h>
39
40 using namespace std;
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
46 const int afterButtonSpacing = 4;
47 const int iconHeight = 16;
48 const int iconWidth = 16;
49 const int iconFilenameSpacing = 2;
50 const int defaultWidthNumChars = 34;
51 const int buttonShadowHeight = 2;
52
53 RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input)
54     : RenderBlock(input)
55     , m_canReceiveDroppedFiles(input->canReceiveDroppedFiles())
56 {
57 }
58
59 RenderFileUploadControl::~RenderFileUploadControl()
60 {
61 }
62
63 bool RenderFileUploadControl::canBeReplacedWithInlineRunIn() const
64 {
65     return false;
66 }
67
68 void RenderFileUploadControl::updateFromElement()
69 {
70     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
71     ASSERT(input->isFileUpload());
72
73     if (HTMLInputElement* button = uploadButton()) {
74         bool newDisabled = !theme()->isEnabled(this);
75         // We should avoid to call HTMLFormControlElement::setDisabled() as
76         // possible because setAttribute() in setDisabled() can cause style
77         // recalculation, and HTMLFormControlElement::recalcStyle() calls
78         // updateFromElement() eventually.
79         if (button->disabled() != newDisabled)
80             button->setDisabled(newDisabled);
81
82         bool newCanReceiveDroppedFilesState = input->canReceiveDroppedFiles();
83         if (m_canReceiveDroppedFiles != newCanReceiveDroppedFilesState) {
84             m_canReceiveDroppedFiles = newCanReceiveDroppedFilesState;
85             button->setActive(newCanReceiveDroppedFilesState);
86         }
87     }
88
89     // This only supports clearing out the files, but that's OK because for
90     // security reasons that's the only change the DOM is allowed to make.
91     FileList* files = input->files();
92     ASSERT(files);
93     if (files && files->isEmpty())
94         repaint();
95 }
96
97 static int nodeWidth(Node* node)
98 {
99     return node ? node->renderBox()->pixelSnappedWidth() : 0;
100 }
101
102 int RenderFileUploadControl::maxFilenameWidth() const
103 {
104     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
105     return max(0, contentBoxRect().pixelSnappedWidth() - nodeWidth(uploadButton()) - afterButtonSpacing
106         - (input->icon() ? iconWidth + iconFilenameSpacing : 0));
107 }
108
109 void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
110 {
111     if (style()->visibility() != VISIBLE)
112         return;
113     
114     // Push a clip.
115     GraphicsContextStateSaver stateSaver(*paintInfo.context, false);
116     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
117         IntRect clipRect = enclosingIntRect(LayoutRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
118                          width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight));
119         if (clipRect.isEmpty())
120             return;
121         stateSaver.save();
122         paintInfo.context->clip(clipRect);
123     }
124
125     if (paintInfo.phase == PaintPhaseForeground) {
126         const String& displayedFilename = fileTextValue();
127         const Font& font = style()->font();
128         TextRun textRun = constructTextRun(this, font, displayedFilename, style(), TextRun::AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
129         textRun.disableRoundingHacks();
130
131         // Determine where the filename should be placed
132         LayoutUnit contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
133         HTMLInputElement* button = uploadButton();
134         if (!button)
135             return;
136
137         HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
138         LayoutUnit buttonWidth = nodeWidth(button);
139         LayoutUnit buttonAndIconWidth = buttonWidth + afterButtonSpacing
140             + (input->icon() ? iconWidth + iconFilenameSpacing : 0);
141         LayoutUnit textX;
142         if (style()->isLeftToRightDirection())
143             textX = contentLeft + buttonAndIconWidth;
144         else
145             textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
146         // We want to match the button's baseline
147         RenderButton* buttonRenderer = toRenderButton(button->renderer());
148         // FIXME: Make this work with transforms.
149         LayoutUnit textY = buttonRenderer->absoluteBoundingBoxRectIgnoringTransforms().y()
150             + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
151
152         paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
153         
154         // Draw the filename
155         paintInfo.context->drawBidiText(font, textRun, IntPoint(roundToInt(textX), roundToInt(textY)));
156         
157         if (input->icon()) {
158             // Determine where the icon should be placed
159             LayoutUnit iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
160             LayoutUnit iconX;
161             if (style()->isLeftToRightDirection())
162                 iconX = contentLeft + buttonWidth + afterButtonSpacing;
163             else
164                 iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth;
165
166             // Draw the file icon
167             input->icon()->paint(paintInfo.context, IntRect(roundToInt(iconX), roundToInt(iconY), iconWidth, iconHeight));
168         }
169     }
170
171     // Paint the children.
172     RenderBlock::paintObject(paintInfo, paintOffset);
173 }
174
175 void RenderFileUploadControl::computePreferredLogicalWidths()
176 {
177     ASSERT(preferredLogicalWidthsDirty());
178
179     m_minPreferredLogicalWidth = 0;
180     m_maxPreferredLogicalWidth = 0;
181
182     RenderStyle* style = this->style();
183     ASSERT(style);
184
185     const Font& font = style->font();
186     if (style->width().isFixed() && style->width().value() > 0)
187         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style->width().value());
188     else {
189         // Figure out how big the filename space needs to be for a given number of characters
190         // (using "0" as the nominal character).
191         const UChar character = '0';
192         const String characterAsString = String(&character, 1);
193         float minDefaultLabelWidth = defaultWidthNumChars * font.width(constructTextRun(this, font, characterAsString, style, TextRun::AllowTrailingExpansion));
194
195         const String label = theme()->fileListDefaultLabel(node()->toInputElement()->multiple());
196         float defaultLabelWidth = font.width(constructTextRun(this, font, label, style, TextRun::AllowTrailingExpansion));
197         if (HTMLInputElement* button = uploadButton())
198             defaultLabelWidth += button->renderer()->maxPreferredLogicalWidth() + afterButtonSpacing;
199         m_maxPreferredLogicalWidth = static_cast<int>(ceilf(max(minDefaultLabelWidth, defaultLabelWidth)));
200     }
201
202     if (style->minWidth().isFixed() && style->minWidth().value() > 0) {
203         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
204         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->minWidth().value()));
205     } else if (style->width().isPercent() || (style->width().isAuto() && style->height().isPercent()))
206         m_minPreferredLogicalWidth = 0;
207     else
208         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
209
210     if (style->maxWidth().isFixed()) {
211         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
212         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style->maxWidth().value()));
213     }
214
215     int toAdd = borderAndPaddingWidth();
216     m_minPreferredLogicalWidth += toAdd;
217     m_maxPreferredLogicalWidth += toAdd;
218
219     setPreferredLogicalWidthsDirty(false);
220 }
221
222 VisiblePosition RenderFileUploadControl::positionForPoint(const LayoutPoint&)
223 {
224     return VisiblePosition();
225 }
226
227 HTMLInputElement* RenderFileUploadControl::uploadButton() const
228 {
229     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
230
231     ASSERT(input->shadow());
232
233     Node* buttonNode = input->shadow()->oldestShadowRoot()->firstChild();
234     return buttonNode && buttonNode->isHTMLElement() && buttonNode->hasTagName(inputTag) ? static_cast<HTMLInputElement*>(buttonNode) : 0;
235 }
236
237 String RenderFileUploadControl::buttonValue()
238 {
239     if (HTMLInputElement* button = uploadButton())
240         return button->value();
241     
242     return String();
243 }
244
245 String RenderFileUploadControl::fileTextValue() const
246 {
247     HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
248     ASSERT(input->files());
249     return theme()->fileListNameForWidth(input->files(), style()->font(), maxFilenameWidth(), input->multiple());
250 }
251     
252 } // namespace WebCore