Implement rendering support for the color-filter CSS property
[WebKit-https.git] / Source / WebCore / rendering / RenderFileUploadControl.cpp
index 8056662..c4c764c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2006, 2007, 2012 Apple Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
 #include "config.h"
 #include "RenderFileUploadControl.h"
 
-#include "Chrome.h"
 #include "FileList.h"
-#include "Frame.h"
-#include "FrameView.h"
+#include "FontCascade.h"
 #include "GraphicsContext.h"
 #include "HTMLInputElement.h"
 #include "HTMLNames.h"
-#include "ShadowElement.h"
 #include "Icon.h"
 #include "LocalizedStrings.h"
-#include "Page.h"
 #include "PaintInfo.h"
 #include "RenderButton.h"
 #include "RenderText.h"
 #include "RenderTheme.h"
-#include "RenderView.h"
+#include "ShadowRoot.h"
+#include "StringTruncator.h"
 #include "TextRun.h"
+#include "VisiblePosition.h"
 #include <math.h>
-
-using namespace std;
+#include <wtf/IsoMallocInlines.h>
 
 namespace WebCore {
 
 using namespace HTMLNames;
 
+WTF_MAKE_ISO_ALLOCATED_IMPL(RenderFileUploadControl);
+
 const int afterButtonSpacing = 4;
+#if !PLATFORM(IOS)
 const int iconHeight = 16;
 const int iconWidth = 16;
 const int iconFilenameSpacing = 2;
 const int defaultWidthNumChars = 34;
-const int buttonShadowHeight = 2;
-
-RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement* input)
-    : RenderBlock(input)
-{
-    FileList* list = input->files();
-    Vector<String> filenames;
-    unsigned length = list ? list->length() : 0;
-    for (unsigned i = 0; i < length; ++i)
-        filenames.append(list->item(i)->path());
-    m_fileChooser = FileChooser::create(this, filenames);
-}
-
-RenderFileUploadControl::~RenderFileUploadControl()
-{
-    if (m_button)
-        m_button->detach();
-    m_fileChooser->disconnectClient();
-}
-
-void RenderFileUploadControl::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
-{
-    RenderBlock::styleDidChange(diff, oldStyle);
-    if (m_button)
-        m_button->renderer()->setStyle(createButtonStyle(style()));
-}
-
-void RenderFileUploadControl::valueChanged()
-{
-    // dispatchFormControlChangeEvent may destroy this renderer
-    RefPtr<FileChooser> fileChooser = m_fileChooser;
-
-    HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
-    inputElement->setFileListFromRenderer(fileChooser->filenames());
-    inputElement->dispatchFormControlChangeEvent();
-    // only repaint if it doesn't seem we have been destroyed
-    if (!fileChooser->disconnected())
-        repaint();
-}
-
-bool RenderFileUploadControl::allowsMultipleFiles()
-{
-#if ENABLE(DIRECTORY_UPLOAD)
-    if (allowsDirectoryUpload())
-      return true;
+#else
+// On iOS the icon height matches the button height, to maximize the icon size.
+const int iconFilenameSpacing = afterButtonSpacing;
+const int defaultWidthNumChars = 38;
 #endif
+const int buttonShadowHeight = 2;
 
-    HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
-    return input->fastHasAttribute(multipleAttr);
-}
-
-#if ENABLE(DIRECTORY_UPLOAD)
-bool RenderFileUploadControl::allowsDirectoryUpload()
-{
-    HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
-    return input->fastHasAttribute(webkitdirectoryAttr);
-}
-
-void RenderFileUploadControl::receiveDropForDirectoryUpload(const Vector<String>& paths)
-{
-    if (Chrome* chromePointer = chrome())
-        chromePointer->enumerateChosenDirectory(paths[0], m_fileChooser.get());
-}
-#endif
-
-String RenderFileUploadControl::acceptTypes()
-{
-    return static_cast<HTMLInputElement*>(node())->accept();
-}
-
-void RenderFileUploadControl::chooseIconForFiles(FileChooser* chooser, const Vector<String>& filenames)
+RenderFileUploadControl::RenderFileUploadControl(HTMLInputElement& input, RenderStyle&& style)
+    : RenderBlockFlow(input, WTFMove(style))
+    , m_canReceiveDroppedFiles(input.canReceiveDroppedFiles())
 {
-    if (Chrome* chromePointer = chrome())
-        chromePointer->chooseIconForFiles(filenames, chooser);
 }
 
-void RenderFileUploadControl::click()
-{
-    // Requires a user gesture to open the file dialog.
-    if (!frame() || !frame()->loader()->isProcessingUserGesture())
-        return;
-    if (Chrome* chromePointer = chrome())
-        chromePointer->runOpenPanel(frame(), m_fileChooser);
-}
+RenderFileUploadControl::~RenderFileUploadControl() = default;
 
-Chrome* RenderFileUploadControl::chrome() const
+HTMLInputElement& RenderFileUploadControl::inputElement() const
 {
-    Frame* frame = node()->document()->frame();
-    if (!frame)
-        return 0;
-    Page* page = frame->page();
-    if (!page)
-        return 0;
-    return page->chrome();
+    return downcast<HTMLInputElement>(nodeForNonAnonymous());
 }
 
 void RenderFileUploadControl::updateFromElement()
 {
-    HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
-    ASSERT(inputElement->isFileUpload());
-    
-    if (!m_button) {
-        m_button = ShadowInputElement::create(inputElement);
-        m_button->setType("button");
-        m_button->setValue(fileButtonChooseFileLabel());
-        RefPtr<RenderStyle> buttonStyle = createButtonStyle(style());
-        RenderObject* renderer = m_button->createRenderer(renderArena(), buttonStyle.get());
-        m_button->setRenderer(renderer);
-        renderer->setStyle(buttonStyle.release());
-        renderer->updateFromElement();
-        m_button->setAttached();
-        m_button->setInDocument();
-
-        addChild(renderer);
-    }
+    ASSERT(inputElement().isFileUpload());
 
-    m_button->setDisabled(!theme()->isEnabled(this));
+    if (HTMLInputElement* button = uploadButton()) {
+        bool newCanReceiveDroppedFilesState = inputElement().canReceiveDroppedFiles();
+        if (m_canReceiveDroppedFiles != newCanReceiveDroppedFilesState) {
+            m_canReceiveDroppedFiles = newCanReceiveDroppedFilesState;
+            button->setActive(newCanReceiveDroppedFilesState);
+        }
+    }
 
     // This only supports clearing out the files, but that's OK because for
     // security reasons that's the only change the DOM is allowed to make.
-    FileList* files = inputElement->files();
+    FileList* files = inputElement().files();
     ASSERT(files);
-    if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) {
-        m_fileChooser->clear();
+    if (files && files->isEmpty())
         repaint();
-    }
 }
 
-int RenderFileUploadControl::maxFilenameWidth() const
+static int nodeWidth(Node* node)
 {
-    return max(0, contentWidth() - m_button->renderBox()->width() - afterButtonSpacing
-        - (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0));
+    return (node && node->renderBox()) ? roundToInt(node->renderBox()->size().width()) : 0;
 }
 
-PassRefPtr<RenderStyle> RenderFileUploadControl::createButtonStyle(const RenderStyle* parentStyle) const
+#if PLATFORM(IOS)
+static int nodeHeight(Node* node)
 {
-    RefPtr<RenderStyle> style = getCachedPseudoStyle(FILE_UPLOAD_BUTTON);
-    if (!style) {
-        style = RenderStyle::create();
-        if (parentStyle)
-            style->inheritFrom(parentStyle);
-    }
-
-    // Button text will wrap on file upload controls with widths smaller than the intrinsic button width
-    // without this setWhiteSpace.
-    style->setWhiteSpace(NOWRAP);
+    return (node && node->renderBox()) ? roundToInt(node->renderBox()->size().height()) : 0;
+}
+#endif
 
-    return style.release();
+int RenderFileUploadControl::maxFilenameWidth() const
+{
+#if PLATFORM(IOS)
+    int iconWidth = nodeHeight(uploadButton());
+#endif
+    return std::max(0, snappedIntRect(contentBoxRect()).width() - nodeWidth(uploadButton()) - afterButtonSpacing
+        - (inputElement().icon() ? iconWidth + iconFilenameSpacing : 0));
 }
 
-void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, int tx, int ty)
+void RenderFileUploadControl::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
 {
-    if (style()->visibility() != VISIBLE)
+    if (style().visibility() != VISIBLE)
         return;
-    ASSERT(m_fileChooser);
     
     // Push a clip.
+    GraphicsContextStateSaver stateSaver(paintInfo.context(), false);
     if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
-        IntRect clipRect(tx + borderLeft(), ty + borderTop(),
-                         width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight);
+        IntRect clipRect = enclosingIntRect(LayoutRect(paintOffset.x() + borderLeft(), paintOffset.y() + borderTop(),
+                         width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop() + buttonShadowHeight));
         if (clipRect.isEmpty())
             return;
-        paintInfo.context->save();
-        paintInfo.context->clip(clipRect);
+        stateSaver.save();
+        paintInfo.context().clip(clipRect);
     }
 
     if (paintInfo.phase == PaintPhaseForeground) {
         const String& displayedFilename = fileTextValue();
-        unsigned length = displayedFilename.length();
-        const UChar* string = displayedFilename.characters();
-        TextRun textRun(string, length, false, 0, 0, TextRun::AllowTrailingExpansion, !style()->isLeftToRightDirection(), style()->unicodeBidi() == Override);
-        
+        const FontCascade& font = style().fontCascade();
+        TextRun textRun = constructTextRun(displayedFilename, style(), AllowTrailingExpansion, RespectDirection | RespectDirectionOverride);
+
+#if PLATFORM(IOS)
+        int iconHeight = nodeHeight(uploadButton());
+        int iconWidth = iconHeight;
+#endif
         // Determine where the filename should be placed
-        int contentLeft = tx + borderLeft() + paddingLeft();
-        int buttonAndIconWidth = m_button->renderBox()->width() + afterButtonSpacing
-            + (m_fileChooser->icon() ? iconWidth + iconFilenameSpacing : 0);
-        int textX;
-        if (style()->isLeftToRightDirection())
+        LayoutUnit contentLeft = paintOffset.x() + borderLeft() + paddingLeft();
+        HTMLInputElement* button = uploadButton();
+        if (!button)
+            return;
+
+        LayoutUnit buttonWidth = nodeWidth(button);
+        LayoutUnit buttonAndIconWidth = buttonWidth + afterButtonSpacing
+            + (inputElement().icon() ? iconWidth + iconFilenameSpacing : 0);
+        LayoutUnit textX;
+        if (style().isLeftToRightDirection())
             textX = contentLeft + buttonAndIconWidth;
         else
-            textX = contentLeft + contentWidth() - buttonAndIconWidth - style()->font().width(textRun);
+            textX = contentLeft + contentWidth() - buttonAndIconWidth - font.width(textRun);
+
+        LayoutUnit textY = 0;
         // We want to match the button's baseline
-        RenderButton* buttonRenderer = toRenderButton(m_button->renderer());
-        int textY = buttonRenderer->absoluteBoundingBoxRect().y()
-            + buttonRenderer->marginTop() + buttonRenderer->borderTop() + buttonRenderer->paddingTop()
-            + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
+        // FIXME: Make this work with transforms.
+        if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer()))
+            textY = paintOffset.y() + borderTop() + paddingTop() + buttonRenderer->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
+        else
+            textY = baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOnContainingLine);
 
-        paintInfo.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
+        paintInfo.context().setFillColor(style().visitedDependentColorWithColorFilter(CSSPropertyColor));
         
         // Draw the filename
-        paintInfo.context->drawBidiText(style()->font(), textRun, IntPoint(textX, textY));
+        paintInfo.context().drawBidiText(font, textRun, IntPoint(roundToInt(textX), roundToInt(textY)));
         
-        if (m_fileChooser->icon()) {
+        if (inputElement().icon()) {
             // Determine where the icon should be placed
-            int iconY = ty + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
-            int iconX;
-            if (style()->isLeftToRightDirection())
-                iconX = contentLeft + m_button->renderBox()->width() + afterButtonSpacing;
+            LayoutUnit iconY = paintOffset.y() + borderTop() + paddingTop() + (contentHeight() - iconHeight) / 2;
+            LayoutUnit iconX;
+            if (style().isLeftToRightDirection())
+                iconX = contentLeft + buttonWidth + afterButtonSpacing;
             else
-                iconX = contentLeft + contentWidth() - m_button->renderBox()->width() - afterButtonSpacing - iconWidth;
-
+                iconX = contentLeft + contentWidth() - buttonWidth - afterButtonSpacing - iconWidth;
+
+#if PLATFORM(IOS)
+            if (RenderButton* buttonRenderer = downcast<RenderButton>(button->renderer())) {
+                // Draw the file icon and decorations.
+                IntRect iconRect(iconX, iconY, iconWidth, iconHeight);
+                RenderTheme::FileUploadDecorations decorationsType = inputElement().files()->length() == 1 ? RenderTheme::SingleFile : RenderTheme::MultipleFiles;
+                theme().paintFileUploadIconDecorations(*this, *buttonRenderer, paintInfo, iconRect, inputElement().icon(), decorationsType);
+            }
+#else
             // Draw the file icon
-            m_fileChooser->icon()->paint(paintInfo.context, IntRect(iconX, iconY, iconWidth, iconHeight));
+            inputElement().icon()->paint(paintInfo.context(), IntRect(roundToInt(iconX), roundToInt(iconY), iconWidth, iconHeight));
+#endif
         }
     }
 
     // Paint the children.
-    RenderBlock::paintObject(paintInfo, tx, ty);
+    RenderBlockFlow::paintObject(paintInfo, paintOffset);
+}
 
-    // Pop the clip.
-    if (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseChildBlockBackgrounds)
-        paintInfo.context->restore();
+void RenderFileUploadControl::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
+{
+    // Figure out how big the filename space needs to be for a given number of characters
+    // (using "0" as the nominal character).
+    const UChar character = '0';
+    const String characterAsString = String(&character, 1);
+    const FontCascade& font = style().fontCascade();
+    // FIXME: Remove the need for this const_cast by making constructTextRun take a const RenderObject*.
+    float minDefaultLabelWidth = defaultWidthNumChars * font.width(constructTextRun(characterAsString, style(), AllowTrailingExpansion));
+
+    const String label = theme().fileListDefaultLabel(inputElement().multiple());
+    float defaultLabelWidth = font.width(constructTextRun(label, style(), AllowTrailingExpansion));
+    if (HTMLInputElement* button = uploadButton())
+        if (RenderObject* buttonRenderer = button->renderer())
+            defaultLabelWidth += buttonRenderer->maxPreferredLogicalWidth() + afterButtonSpacing;
+    maxLogicalWidth = static_cast<int>(ceilf(std::max(minDefaultLabelWidth, defaultLabelWidth)));
+
+    if (!style().width().isPercentOrCalculated())
+        minLogicalWidth = maxLogicalWidth;
 }
 
 void RenderFileUploadControl::computePreferredLogicalWidths()
@@ -273,67 +220,55 @@ void RenderFileUploadControl::computePreferredLogicalWidths()
     m_minPreferredLogicalWidth = 0;
     m_maxPreferredLogicalWidth = 0;
 
-    if (style()->width().isFixed() && style()->width().value() > 0)
-        m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
-    else {
-        // Figure out how big the filename space needs to be for a given number of characters
-        // (using "0" as the nominal character).
-        const UChar ch = '0';
-        float charWidth = style()->font().width(TextRun(&ch, 1, false, 0, 0, TextRun::AllowTrailingExpansion, false));
-        m_maxPreferredLogicalWidth = (int)ceilf(charWidth * defaultWidthNumChars);
-    }
-
-    if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
-        m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
-        m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
-    } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
-        m_minPreferredLogicalWidth = 0;
+    if (style().width().isFixed() && style().width().value() > 0)
+        m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width().value());
     else
-        m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
+        computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
+
+    if (style().minWidth().isFixed() && style().minWidth().value() > 0) {
+        m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
+        m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
+    }
 
-    if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
-        m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
-        m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
+    if (style().maxWidth().isFixed()) {
+        m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
+        m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
     }
 
-    int toAdd = borderAndPaddingWidth();
+    int toAdd = horizontalBorderAndPaddingExtent();
     m_minPreferredLogicalWidth += toAdd;
     m_maxPreferredLogicalWidth += toAdd;
 
     setPreferredLogicalWidthsDirty(false);
 }
 
-VisiblePosition RenderFileUploadControl::positionForPoint(const IntPoint&)
+VisiblePosition RenderFileUploadControl::positionForPoint(const LayoutPoint&, const RenderFragmentContainer*)
 {
     return VisiblePosition();
 }
 
-void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths)
+HTMLInputElement* RenderFileUploadControl::uploadButton() const
 {
-#if ENABLE(DIRECTORY_UPLOAD)
-    if (allowsDirectoryUpload()) {
-        receiveDropForDirectoryUpload(paths);
-        return;
-    }
-#endif
-
-    if (allowsMultipleFiles())
-        m_fileChooser->chooseFiles(paths);
-    else
-        m_fileChooser->chooseFile(paths[0]);
+    ASSERT(inputElement().shadowRoot());
+    Node* buttonNode = inputElement().shadowRoot()->firstChild();
+    return is<HTMLInputElement>(buttonNode) ? downcast<HTMLInputElement>(buttonNode) : nullptr;
 }
 
 String RenderFileUploadControl::buttonValue()
 {
-    if (!m_button)
-        return String();
+    if (HTMLInputElement* button = uploadButton())
+        return button->value();
     
-    return m_button->value();
+    return String();
 }
 
 String RenderFileUploadControl::fileTextValue() const
 {
-    return m_fileChooser->basenameForWidth(style()->font(), maxFilenameWidth());
+    auto& input = inputElement();
+    ASSERT(inputElement().files());
+    if (input.files()->length() && !input.displayString().isEmpty())
+        return StringTruncator::rightTruncate(input.displayString(), maxFilenameWidth(), style().fontCascade());
+    return theme().fileListNameForWidth(input.files(), style().fontCascade(), maxFilenameWidth(), input.multiple());
 }
     
 } // namespace WebCore