1/*
2 * Copyright (C) 2007 Apple Inc.
3 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4 * Copyright (C) 2008 Collabora Ltd.
5 * Copyright (C) 2008 INdT - Instituto Nokia de Tecnologia
6 * Copyright (C) 2009-2010 ProFUSION embedded systems
7 * Copyright (C) 2009-2011 Samsung Electronics
8 * Copyright (c) 2012 Intel Corporation. All rights reserved.
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB.  If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 *
25 */
26
27#include "config.h"
28#include "RenderThemeEfl.h"
29
30#include "CSSValueKeywords.h"
31#include "CairoUtilitiesEfl.h"
32#include "ExceptionCodePlaceholder.h"
33#include "FloatRoundedRect.h"
34#include "FontDescription.h"
35#include "GraphicsContext.h"
36#include "HTMLInputElement.h"
37#include "InputTypeNames.h"
38#include "NotImplemented.h"
39#include "Page.h"
40#include "PaintInfo.h"
41#include "PlatformContextCairo.h"
42#include "RenderBox.h"
43#include "RenderObject.h"
44#include "RenderProgress.h"
45#include "RenderSlider.h"
46#include "ScrollbarThemeEfl.h"
47#include "Settings.h"
48#include "UserAgentScripts.h"
49#include "UserAgentStyleSheets.h"
50#include <Ecore_Evas.h>
51#include <Edje.h>
52#include <new>
53#include <wtf/text/CString.h>
54#include <wtf/text/StringBuilder.h>
55#include <wtf/text/WTFString.h>
56
57namespace WebCore {
58
59// TODO: change from object count to ecore_evas size (bytes)
60// TODO: as objects are webpage/user defined and they can be very large.
61#define RENDER_THEME_EFL_PART_CACHE_MAX 32
62
63// Initialize default font size.
64float RenderThemeEfl::defaultFontSize = 16.0f;
65
66static const float minCancelButtonSize = 5;
67static const float maxCancelButtonSize = 21;
68
69static const float minSearchDecorationButtonSize = 1;
70static const float maxSearchDecorationButtonSize = 15;
71static const float searchFieldDecorationButtonOffset = 3;
72
73// Constants for progress tag animation.
74// These values have been copied from RenderThemeGtk.cpp
75static const int progressAnimationFrames = 10;
76static const double progressAnimationInterval = 0.125;
77
78static const int sliderThumbWidth = 29;
79static const int sliderThumbHeight = 11;
80
81#define _ASSERT_ON_RELEASE_RETURN(o, fmt, ...) \
82    do { if (!o) { EINA_LOG_CRIT(fmt, ## __VA_ARGS__); ASSERT(o); return; } } while (0)
83#define _ASSERT_ON_RELEASE_RETURN_VAL(o, val, fmt, ...) \
84    do { if (!o) { EINA_LOG_CRIT(fmt, ## __VA_ARGS__); ASSERT(o); return val; } } while (0)
85
86
87static const char* toEdjeGroup(FormType type)
88{
89    static const char* groups[] = {
90        "webkit/widget/button",
91        "webkit/widget/radio",
92        "webkit/widget/entry",
93        "webkit/widget/checkbox",
94        "webkit/widget/combo",
95        "webkit/widget/progressbar",
96        "webkit/widget/search/field",
97        "webkit/widget/search/results_button",
98        "webkit/widget/search/results_decoration",
99        "webkit/widget/search/cancel_button",
100        "webkit/widget/slider/vertical",
101        "webkit/widget/slider/horizontal",
102        "webkit/widget/slider/thumb_vertical",
103        "webkit/widget/slider/thumb_horizontal",
104        "webkit/widget/spinner",
105        0
106    };
107    ASSERT(type >= 0);
108    ASSERT((size_t)type < sizeof(groups) / sizeof(groups[0])); // Out of sync?
109    return groups[type];
110}
111
112static bool setSourceGroupForEdjeObject(Evas_Object* o, const String& themePath, const char* group)
113{
114    ASSERT(o);
115    ASSERT(!themePath.isEmpty());
116
117    if (!edje_object_file_set(o, themePath.utf8().data(), group)) {
118        const char* message = edje_load_error_str(edje_object_load_error_get(o));
119        EINA_LOG_ERR("Could not set theme group '%s' of file '%s': %s", group, themePath.utf8().data(), message);
120        return false;
121    }
122
123    return true;
124}
125
126void RenderThemeEfl::adjustSizeConstraints(RenderStyle& style, FormType type) const
127{
128    loadThemeIfNeeded();
129
130    // These are always valid, even if no theme could be loaded.
131    const ThemePartDesc* desc = m_partDescs + (size_t)type;
132
133    if (style.minWidth().isIntrinsic())
134        style.setMinWidth(desc->min.width());
135    if (style.minHeight().isIntrinsic())
136        style.setMinHeight(desc->min.height());
137
138    if (desc->max.width().value() > 0 && style.maxWidth().isIntrinsicOrAuto())
139        style.setMaxWidth(desc->max.width());
140    if (desc->max.height().value() > 0 && style.maxHeight().isIntrinsicOrAuto())
141        style.setMaxHeight(desc->max.height());
142
143    style.setPaddingTop(desc->padding.top());
144    style.setPaddingBottom(desc->padding.bottom());
145    style.setPaddingLeft(desc->padding.left());
146    style.setPaddingRight(desc->padding.right());
147}
148
149static bool isFormElementTooLargeToDisplay(const IntSize& elementSize)
150{
151    // This limit of 20000 pixels is hardcoded inside edje -- anything above this size
152    // will be clipped. This value seems to be reasonable enough so that hardcoding it
153    // here won't be a problem.
154    static const int maxEdjeDimension = 20000;
155
156    return elementSize.width() > maxEdjeDimension || elementSize.height() > maxEdjeDimension;
157}
158
159PassOwnPtr<RenderThemeEfl::ThemePartCacheEntry> RenderThemeEfl::ThemePartCacheEntry::create(const String& themePath, FormType type, const IntSize& size)
160{
161    ASSERT(!themePath.isEmpty());
162
163    if (isFormElementTooLargeToDisplay(size) || size.isEmpty()) {
164        EINA_LOG_ERR("Cannot render an element of size %dx%d.", size.width(), size.height());
165        return nullptr;
166    }
167
168    OwnPtr<ThemePartCacheEntry> entry = adoptPtr(new ThemePartCacheEntry);
169
170    entry->m_canvas = EflUniquePtr<Ecore_Evas>(ecore_evas_buffer_new(size.width(), size.height()));
171    if (!entry->canvas()) {
172        EINA_LOG_ERR("ecore_evas_buffer_new(%d, %d) failed.", size.width(), size.height());
173        return nullptr;
174    }
175
176    // By default EFL creates buffers without alpha.
177    ecore_evas_alpha_set(entry->canvas(), EINA_TRUE);
178
179    entry->m_edje = EflUniquePtr<Evas_Object>(edje_object_add(ecore_evas_get(entry->canvas())));
180    ASSERT(entry->edje());
181
182    if (!setSourceGroupForEdjeObject(entry->edje(), themePath, toEdjeGroup(type)))
183        return nullptr;
184
185    entry->m_surface = createSurfaceForBackingStore(entry->canvas());
186    if (!entry->surface())
187        return nullptr;
188
189    evas_object_resize(entry->edje(), size.width(), size.height());
190    evas_object_show(entry->edje());
191
192    entry->type = type;
193    entry->size = size;
194
195    return entry.release();
196}
197
198void RenderThemeEfl::ThemePartCacheEntry::reuse(const String& themePath, FormType newType, const IntSize& newSize)
199{
200    ASSERT(!themePath.isEmpty());
201
202    if (type != newType) {
203        type = newType;
204        if (!setSourceGroupForEdjeObject(edje(), themePath, toEdjeGroup(newType))) {
205            type = FormTypeLast; // Invalidate.
206            return;
207        }
208    }
209
210    if (size != newSize) {
211        size = newSize;
212        ecore_evas_resize(canvas(), newSize.width(), newSize.height());
213        evas_object_resize(edje(), newSize.width(), newSize.height());
214
215        m_surface = createSurfaceForBackingStore(canvas());
216        if (!surface()) {
217            type = FormTypeLast; // Invalidate;
218            return;
219        }
220    }
221}
222
223RenderThemeEfl::ThemePartCacheEntry* RenderThemeEfl::getThemePartFromCache(FormType type, const IntSize& size)
224{
225    void* data;
226    Eina_List* node;
227    Eina_List* reusableNode = 0;
228
229    // Look for the item in the cache.
230    EINA_LIST_FOREACH(m_partCache, node, data) {
231        ThemePartCacheEntry* cachedEntry = static_cast<ThemePartCacheEntry*>(data);
232        if (cachedEntry->size == size) {
233            if (cachedEntry->type == type) {
234                // Found the right item, move it to the head of the list
235                // and return it.
236                m_partCache = eina_list_promote_list(m_partCache, node);
237                return cachedEntry;
238            }
239            // We reuse in priority the last item in the list that has
240            // the requested size.
241            reusableNode = node;
242        }
243    }
244
245    if (eina_list_count(m_partCache) < RENDER_THEME_EFL_PART_CACHE_MAX) {
246        ThemePartCacheEntry* entry = ThemePartCacheEntry::create(themePath(), type, size).leakPtr();
247        if (entry)
248            m_partCache = eina_list_prepend(m_partCache, entry);
249
250        return entry;
251    }
252
253    // The cache is full, reuse the last item we found that had the
254    // requested size to avoid resizing. If there was none, reuse
255    // the last item of the list.
256    if (!reusableNode)
257        reusableNode = eina_list_last(m_partCache);
258
259    ThemePartCacheEntry* reusedEntry = static_cast<ThemePartCacheEntry*>(eina_list_data_get(reusableNode));
260    ASSERT(reusedEntry);
261    reusedEntry->reuse(themePath(), type, size);
262    m_partCache = eina_list_promote_list(m_partCache, reusableNode);
263
264    return reusedEntry;
265}
266
267void RenderThemeEfl::clearThemePartCache()
268{
269    void* data;
270    EINA_LIST_FREE(m_partCache, data)
271        delete static_cast<ThemePartCacheEntry*>(data);
272
273}
274
275void RenderThemeEfl::applyEdjeStateFromForm(Evas_Object* object, const ControlStates* states, bool haveBackground)
276{
277    const char *signals[] = { // keep in sync with WebCore/platform/ThemeTypes.h
278        "hovered",
279        "pressed",
280        "focused",
281        "enabled",
282        "checked",
283        "read-only",
284        "default",
285        "window-inactive",
286        "indeterminate",
287        "spinup"
288    };
289
290    edje_object_signal_emit(object, "reset", "");
291
292    for (size_t i = 0; i < WTF_ARRAY_LENGTH(signals); ++i) {
293        if (states->states() & (1 << i))
294            edje_object_signal_emit(object, signals[i], "");
295    }
296
297    if (haveBackground)
298        edje_object_signal_emit(object, "styled", "");
299}
300
301void RenderThemeEfl::applyEdjeRTLState(Evas_Object* edje, const RenderObject& object, FormType type, const IntRect& rect)
302{
303    if (type == SliderVertical || type == SliderHorizontal) {
304        if (!object.isSlider())
305            return; // probably have -webkit-appearance: slider..
306
307        const RenderSlider* renderSlider = toRenderSlider(&object);
308        HTMLInputElement& input = renderSlider->element();
309        double valueRange = input.maximum() - input.minimum();
310
311        OwnPtr<Edje_Message_Float_Set> msg = adoptPtr(static_cast<Edje_Message_Float_Set*>(::operator new (sizeof(Edje_Message_Float_Set) + sizeof(double))));
312        msg->count = 2;
313
314        // The first parameter of the message decides if the progress bar
315        // grows from the end of the slider or from the beginning. On vertical
316        // sliders, it should always be the same and will not be affected by
317        // text direction settings.
318        if (object.style().direction() == RTL || type == SliderVertical)
319            msg->val[0] = 1;
320        else
321            msg->val[0] = 0;
322
323        msg->val[1] = (input.valueAsNumber() - input.minimum()) / valueRange;
324        edje_object_message_send(edje, EDJE_MESSAGE_FLOAT_SET, 0, msg.get());
325    } else if (type == ProgressBar) {
326        const RenderProgress& renderProgress = toRenderProgress(object);
327
328        int max = rect.width();
329        double value = renderProgress.position();
330
331        OwnPtr<Edje_Message_Float_Set> msg = adoptPtr(static_cast<Edje_Message_Float_Set*>(::operator new (sizeof(Edje_Message_Float_Set) + sizeof(double))));
332        msg->count = 2;
333
334        if (object.style().direction() == RTL)
335            msg->val[0] = (1.0 - value) * max;
336        else
337            msg->val[0] = 0;
338        msg->val[1] = value;
339        edje_object_message_send(edje, EDJE_MESSAGE_FLOAT_SET, 0, msg.get());
340    }
341}
342
343bool RenderThemeEfl::isControlStyled(const RenderStyle& style, const BorderData& border, const FillLayer& background, const Color& backgroundColor) const
344{
345    return RenderTheme::isControlStyled(style, border, background, backgroundColor) || style.appearance() == MenulistButtonPart;
346}
347
348bool RenderThemeEfl::paintThemePart(const RenderObject& object, FormType type, const PaintInfo& info, const IntRect& rect)
349{
350    loadThemeIfNeeded();
351    _ASSERT_ON_RELEASE_RETURN_VAL(edje(), false, "Could not paint native HTML part due to missing theme.");
352
353    ThemePartCacheEntry* entry = getThemePartFromCache(type, rect.size());
354    if (!entry)
355        return false;
356
357    bool haveBackgroundColor = isControlStyled(object.style(), object.style().border(), *object.style().backgroundLayers(), Color::white);
358    ControlStates states(extractControlStatesForRenderer(object));
359    applyEdjeStateFromForm(entry->edje(), &states, haveBackgroundColor);
360
361    applyEdjeRTLState(entry->edje(), object, type, rect);
362
363    edje_object_calc_force(entry->edje());
364    edje_object_message_signal_process(entry->edje());
365    evas_render(ecore_evas_get(entry->canvas()));
366
367    cairo_t* cairo = info.context->platformContext()->cr();
368    ASSERT(cairo);
369
370    cairo_save(cairo);
371    cairo_set_source_surface(cairo, entry->surface(), rect.x(), rect.y());
372    cairo_paint_with_alpha(cairo, 1.0);
373    cairo_restore(cairo);
374
375    return false;
376}
377
378PassRefPtr<RenderTheme> RenderThemeEfl::create(Page* page)
379{
380    return adoptRef(new RenderThemeEfl(page));
381}
382
383PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page)
384{
385    if (page)
386        return RenderThemeEfl::create(page);
387
388    static RenderTheme* fallback = RenderThemeEfl::create(0).leakRef();
389    return fallback;
390}
391
392static void applyColorCallback(void* data, Evas_Object*, const char* /* signal */, const char* colorClass)
393{
394    RenderThemeEfl* that = static_cast<RenderThemeEfl*>(data);
395    that->setColorFromThemeClass(colorClass);
396    that->platformColorsDidChange(); // Triggers relayout.
397}
398
399static bool fillColorsFromEdjeClass(Evas_Object* o, const char* colorClass, Color* color1, Color* color2 = 0, Color* color3 = 0)
400{
401    int r1, g1, b1, a1;
402    int r2, g2, b2, a2;
403    int r3, g3, b3, a3;
404
405    if (!edje_object_color_class_get(o, colorClass, &r1, &g1, &b1, &a1, &r2, &g2, &b2, &a2, &r3, &g3, &b3, &a3))
406        return false;
407
408    if (color1)
409        color1->setRGB(makeRGBA(r1, g1, b1, a1));
410    if (color2)
411        color2->setRGB(makeRGBA(r2, g2, b2, a2));
412    if (color3)
413        color3->setRGB(makeRGBA(r3, g3, b3, a3));
414
415    return true;
416}
417
418void RenderThemeEfl::setColorFromThemeClass(const char* colorClass)
419{
420    ASSERT(edje());
421
422    if (!strcmp("webkit/selection/foreground", colorClass))
423        m_supportsSelectionForegroundColor = fillColorsFromEdjeClass(edje(), colorClass, &m_activeSelectionForegroundColor, &m_inactiveSelectionForegroundColor);
424    else if (!strcmp("webkit/selection/background", colorClass))
425        fillColorsFromEdjeClass(edje(), colorClass, &m_activeSelectionBackgroundColor, &m_inactiveSelectionBackgroundColor);
426    else if (!strcmp("webkit/focus_ring", colorClass)) {
427        if (!fillColorsFromEdjeClass(edje(), colorClass, &m_focusRingColor))
428            return;
429
430        // platformFocusRingColor() is only used for the default theme (without page)
431        // The following is ugly, but no other way to do it unless we change it to use page themes as much as possible.
432        RenderTheme::setCustomFocusRingColor(m_focusRingColor);
433    }
434}
435
436void RenderThemeEfl::setThemePath(const String& newThemePath)
437{
438    if (newThemePath == m_themePath)
439        return;
440
441    if (newThemePath.isEmpty()) {
442        EINA_LOG_CRIT("No valid theme defined, things will not work properly.");
443        return;
444    }
445
446    String oldThemePath = m_themePath;
447    m_themePath = newThemePath;
448
449    // Keep the consistence by restoring the previous theme path
450    // if we cannot load the new one.
451    if (!loadTheme())
452        m_themePath = oldThemePath;
453}
454
455String RenderThemeEfl::themePath() const
456{
457#ifndef NDEBUG
458    if (edje()) {
459        const char* path;
460        edje_object_file_get(edje(), &path, 0);
461        ASSERT(m_themePath == path);
462    }
463#endif
464    return m_themePath;
465}
466
467bool RenderThemeEfl::loadTheme()
468{
469    ASSERT(!m_themePath.isEmpty());
470
471    if (!canvas()) {
472        m_canvas = EflUniquePtr<Ecore_Evas>(ecore_evas_buffer_new(1, 1));
473        _ASSERT_ON_RELEASE_RETURN_VAL(canvas(), false,
474                "Could not create canvas required by theme, things will not work properly.");
475    }
476
477    EflUniquePtr<Evas_Object> o = EflUniquePtr<Evas_Object>(edje_object_add(ecore_evas_get(canvas())));
478    _ASSERT_ON_RELEASE_RETURN_VAL(o, false, "Could not create new base Edje object.");
479
480    if (!setSourceGroupForEdjeObject(o.get(), m_themePath, "webkit/base"))
481        return false; // Keep current theme.
482
483    // Invalidate existing theme part cache.
484    if (edje())
485        clearThemePartCache();
486
487    // Set new loaded theme, and apply it.
488    m_edje = WTF::move(o);
489
490    const char* thickness = edje_object_data_get(m_edje.get(), "scrollbar.thickness");
491    if (thickness && !Settings::mockScrollbarsEnabled())
492        static_cast<ScrollbarThemeEfl*>(ScrollbarTheme::theme())->setScrollbarThickness(atoi(thickness));
493
494    edje_object_signal_callback_add(edje(), "color_class,set", "webkit/selection/foreground", applyColorCallback, this);
495    edje_object_signal_callback_add(edje(), "color_class,set", "webkit/selection/background", applyColorCallback, this);
496    edje_object_signal_callback_add(edje(), "color_class,set", "webkit/focus_ring", applyColorCallback, this);
497
498    applyPartDescriptionsFrom(m_themePath);
499
500    setColorFromThemeClass("webkit/selection/foreground");
501    setColorFromThemeClass("webkit/selection/background");
502    setColorFromThemeClass("webkit/focus_ring");
503
504    platformColorsDidChange(); // Schedules a relayout, do last.
505
506    return true;
507}
508
509void RenderThemeEfl::applyPartDescriptionFallback(ThemePartDesc* desc)
510{
511    desc->min.setWidth(Length(0, Fixed));
512    desc->min.setHeight(Length(0, Fixed));
513
514    desc->max.setWidth(Length(0, Fixed));
515    desc->max.setHeight(Length(0, Fixed));
516
517    desc->padding = LengthBox(0, 0, 0, 0);
518}
519
520void RenderThemeEfl::applyPartDescription(Evas_Object* object, ThemePartDesc* desc)
521{
522    Evas_Coord minw, minh, maxw, maxh;
523
524    edje_object_size_min_get(object, &minw, &minh);
525    if (!minw && !minh)
526        edje_object_size_min_calc(object, &minw, &minh);
527
528    desc->min.setWidth(Length(minw, Fixed));
529    desc->min.setHeight(Length(minh, Fixed));
530
531    edje_object_size_max_get(object, &maxw, &maxh);
532    desc->max.setWidth(Length(maxw, Fixed));
533    desc->max.setHeight(Length(maxh, Fixed));
534
535    if (!edje_object_part_exists(object, "text_confinement"))
536        desc->padding = LengthBox(0, 0, 0, 0);
537    else {
538        Evas_Coord px, py, pw, ph;
539        Evas_Coord ox = 0, oy = 0, ow = 0, oh = 0;
540        int t, r, b, l;
541
542        if (minw > 0)
543            ow = minw;
544        else
545            ow = 100;
546        if (minh > 0)
547            oh = minh;
548        else
549            oh = 100;
550        if (maxw > 0 && ow > maxw)
551            ow = maxw;
552        if (maxh > 0 && oh > maxh)
553            oh = maxh;
554
555        evas_object_move(object, ox, oy);
556        evas_object_resize(object, ow, oh);
557        edje_object_calc_force(object);
558        edje_object_message_signal_process(object);
559        edje_object_part_geometry_get(object, "text_confinement", &px, &py, &pw, &ph);
560
561        t = py - oy;
562        b = (oh + oy) - (ph + py);
563
564        l = px - ox;
565        r = (ow + ox) - (pw + px);
566
567        desc->padding = LengthBox(t, r, b, l);
568    }
569}
570
571void RenderThemeEfl::applyPartDescriptionsFrom(const String& themePath)
572{
573    EflUniquePtr<Evas_Object> temp = EflUniquePtr<Evas_Object>(edje_object_add(ecore_evas_get(canvas())));
574    _ASSERT_ON_RELEASE_RETURN(temp, "Could not create Edje object.");
575
576    for (size_t i = 0; i < FormTypeLast; i++) {
577        FormType type = static_cast<FormType>(i);
578        m_partDescs[i].type = type;
579        if (!setSourceGroupForEdjeObject(temp.get(), themePath, toEdjeGroup(type)))
580            applyPartDescriptionFallback(m_partDescs + i);
581        else
582            applyPartDescription(temp.get(), m_partDescs + i);
583    }
584}
585
586RenderThemeEfl::RenderThemeEfl(Page* page)
587    : RenderTheme()
588    , m_page(page)
589    , m_activeSelectionBackgroundColor(0, 0, 255)
590    , m_activeSelectionForegroundColor(Color::white)
591    , m_inactiveSelectionBackgroundColor(0, 0, 128)
592    , m_inactiveSelectionForegroundColor(200, 200, 200)
593    , m_focusRingColor(32, 32, 224, 224)
594    , m_sliderThumbColor(Color::darkGray)
595    , m_supportsSelectionForegroundColor(false)
596    , m_partCache(0)
597{
598}
599
600RenderThemeEfl::~RenderThemeEfl()
601{
602    clearThemePartCache();
603}
604
605static bool supportsFocus(ControlPart appearance)
606{
607    switch (appearance) {
608    case PushButtonPart:
609    case ButtonPart:
610    case TextFieldPart:
611    case TextAreaPart:
612    case SearchFieldPart:
613    case MenulistPart:
614    case RadioPart:
615    case CheckboxPart:
616    case SliderVerticalPart:
617    case SliderHorizontalPart:
618        return true;
619    default:
620        return false;
621    }
622}
623
624bool RenderThemeEfl::supportsFocusRing(const RenderStyle& style) const
625{
626    return supportsFocus(style.appearance());
627}
628
629bool RenderThemeEfl::controlSupportsTints(const RenderObject& object) const
630{
631    return isEnabled(object);
632}
633
634int RenderThemeEfl::baselinePosition(const RenderObject& object) const
635{
636    if (!object.isBox())
637        return 0;
638
639    if (object.style().appearance() == CheckboxPart
640    ||  object.style().appearance() == RadioPart)
641        return toRenderBox(&object)->marginTop() + toRenderBox(&object)->height() - 3;
642
643    return RenderTheme::baselinePosition(object);
644}
645
646Color RenderThemeEfl::platformActiveSelectionBackgroundColor() const
647{
648    loadThemeIfNeeded();
649    return m_activeSelectionBackgroundColor;
650}
651
652Color RenderThemeEfl::platformInactiveSelectionBackgroundColor() const
653{
654    loadThemeIfNeeded();
655    return m_inactiveSelectionBackgroundColor;
656}
657
658Color RenderThemeEfl::platformActiveSelectionForegroundColor() const
659{
660    loadThemeIfNeeded();
661    return m_activeSelectionForegroundColor;
662}
663
664Color RenderThemeEfl::platformInactiveSelectionForegroundColor() const
665{
666    loadThemeIfNeeded();
667    return m_inactiveSelectionForegroundColor;
668}
669
670Color RenderThemeEfl::platformFocusRingColor() const
671{
672    loadThemeIfNeeded();
673    return m_focusRingColor;
674}
675
676bool RenderThemeEfl::supportsSelectionForegroundColors() const
677{
678    loadThemeIfNeeded();
679    return m_supportsSelectionForegroundColor;
680}
681
682bool RenderThemeEfl::paintSliderTrack(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
683{
684    if (object.style().appearance() == SliderHorizontalPart)
685        paintThemePart(object, SliderHorizontal, info, rect);
686    else
687        paintThemePart(object, SliderVertical, info, rect);
688
689#if ENABLE(DATALIST_ELEMENT)
690    paintSliderTicks(object, info, rect);
691#endif
692
693    return false;
694}
695
696void RenderThemeEfl::adjustSliderTrackStyle(StyleResolver&, RenderStyle& style, Element&) const
697{
698    style.setBoxShadow(nullptr);
699}
700
701void RenderThemeEfl::adjustSliderThumbStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
702{
703    RenderTheme::adjustSliderThumbStyle(styleResolver, style, element);
704    style.setBoxShadow(nullptr);
705}
706
707void RenderThemeEfl::adjustSliderThumbSize(RenderStyle& style, Element&) const
708{
709    ControlPart part = style.appearance();
710    if (part == SliderThumbVerticalPart) {
711        style.setWidth(Length(sliderThumbHeight, Fixed));
712        style.setHeight(Length(sliderThumbWidth, Fixed));
713    } else if (part == SliderThumbHorizontalPart) {
714        style.setWidth(Length(sliderThumbWidth, Fixed));
715        style.setHeight(Length(sliderThumbHeight, Fixed));
716    }
717}
718
719#if ENABLE(DATALIST_ELEMENT)
720IntSize RenderThemeEfl::sliderTickSize() const
721{
722    return IntSize(1, 6);
723}
724
725int RenderThemeEfl::sliderTickOffsetFromTrackCenter() const
726{
727    static const int sliderTickOffset = -12;
728
729    return sliderTickOffset;
730}
731
732LayoutUnit RenderThemeEfl::sliderTickSnappingThreshold() const
733{
734    // The same threshold value as the Chromium port.
735    return 5;
736}
737#endif
738
739bool RenderThemeEfl::supportsDataListUI(const AtomicString& type) const
740{
741#if ENABLE(DATALIST_ELEMENT)
742    // FIXME: We need to support other types.
743    return type == InputTypeNames::email()
744        || type == InputTypeNames::range()
745        || type == InputTypeNames::search()
746        || type == InputTypeNames::url();
747#else
748    UNUSED_PARAM(type);
749    return false;
750#endif
751}
752
753bool RenderThemeEfl::paintSliderThumb(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
754{
755    if (object.style().appearance() == SliderThumbHorizontalPart)
756        paintThemePart(object, SliderThumbHorizontal, info, rect);
757    else
758        paintThemePart(object, SliderThumbVertical, info, rect);
759
760    return false;
761}
762
763void RenderThemeEfl::adjustCheckboxStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
764{
765    if (!m_page && element.document().page()) {
766        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustCheckboxStyle(styleResolver, style, element);
767        return;
768    }
769
770    adjustSizeConstraints(style, CheckBox);
771
772    style.resetBorder();
773
774    const ThemePartDesc* desc = m_partDescs + (size_t)CheckBox;
775    if (style.width().value() < desc->min.width().value())
776        style.setWidth(desc->min.width());
777    if (style.height().value() < desc->min.height().value())
778        style.setHeight(desc->min.height());
779}
780
781bool RenderThemeEfl::paintCheckbox(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
782{
783    return paintThemePart(object, CheckBox, info, rect);
784}
785
786void RenderThemeEfl::adjustRadioStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
787{
788    if (!m_page && element.document().page()) {
789        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustRadioStyle(styleResolver, style, element);
790        return;
791    }
792
793    adjustSizeConstraints(style, RadioButton);
794
795    style.resetBorder();
796
797    const ThemePartDesc* desc = m_partDescs + (size_t)RadioButton;
798    if (style.width().value() < desc->min.width().value())
799        style.setWidth(desc->min.width());
800    if (style.height().value() < desc->min.height().value())
801        style.setHeight(desc->min.height());
802}
803
804bool RenderThemeEfl::paintRadio(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
805{
806    return paintThemePart(object, RadioButton, info, rect);
807}
808
809void RenderThemeEfl::adjustButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
810{
811    if (!m_page && element.document().page()) {
812        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustButtonStyle(styleResolver, style, element);
813        return;
814    }
815
816    // adjustSizeConstrains can make SquareButtonPart's size wrong (by adjusting paddings), so call it only for PushButtonPart and ButtonPart
817    if (style.appearance() == PushButtonPart || style.appearance() == ButtonPart)
818        adjustSizeConstraints(style, Button);
819}
820
821bool RenderThemeEfl::paintButton(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
822{
823    return paintThemePart(object, Button, info, rect);
824}
825
826void RenderThemeEfl::adjustMenuListStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
827{
828    if (!m_page && element.document().page()) {
829        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustMenuListStyle(styleResolver, style, element);
830        return;
831    }
832    adjustSizeConstraints(style, ComboBox);
833    style.resetBorder();
834    style.setWhiteSpace(PRE);
835
836    style.setLineHeight(RenderStyle::initialLineHeight());
837}
838
839bool RenderThemeEfl::paintMenuList(const RenderObject& object, const PaintInfo& info, const FloatRect& rect)
840{
841    return paintThemePart(object, ComboBox, info, IntRect(rect));
842}
843
844void RenderThemeEfl::adjustMenuListButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
845{
846    // Height is locked to auto if height is not specified.
847    style.setHeight(Length(Auto));
848
849    // The <select> box must be at least 12px high for the button to render the text inside the box without clipping.
850    const int dropDownBoxMinHeight = 12;
851
852    // Calculate min-height of the <select> element.
853    int minHeight = style.fontMetrics().height();
854    minHeight = std::max(minHeight, dropDownBoxMinHeight);
855    style.setMinHeight(Length(minHeight, Fixed));
856
857    adjustMenuListStyle(styleResolver, style, element);
858}
859
860bool RenderThemeEfl::paintMenuListButtonDecorations(const RenderObject& object, const PaintInfo& info, const FloatRect& rect)
861{
862    return paintMenuList(object, info, rect);
863}
864
865void RenderThemeEfl::adjustTextFieldStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
866{
867    if (!m_page && element.document().page()) {
868        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustTextFieldStyle(styleResolver, style, element);
869        return;
870    }
871    adjustSizeConstraints(style, TextField);
872    style.resetBorder();
873}
874
875bool RenderThemeEfl::paintTextField(const RenderObject& object, const PaintInfo& info, const FloatRect& rect)
876{
877    return paintThemePart(object, TextField, info, IntRect(rect));
878}
879
880void RenderThemeEfl::adjustTextAreaStyle(StyleResolver&, RenderStyle&, Element&) const
881{
882}
883
884bool RenderThemeEfl::paintTextArea(const RenderObject& object, const PaintInfo& info, const FloatRect& rect)
885{
886    return paintTextField(object, info, rect);
887}
888
889void RenderThemeEfl::adjustSearchFieldResultsButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
890{
891    if (!m_page && element.document().page()) {
892        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustSearchFieldResultsButtonStyle(styleResolver, style, element);
893        return;
894    }
895    adjustSizeConstraints(style, SearchFieldResultsButton);
896    style.resetBorder();
897    style.setWhiteSpace(PRE);
898
899    float fontScale = style.fontSize() / defaultFontSize;
900    int decorationSize = lroundf(std::min(std::max(minSearchDecorationButtonSize, defaultFontSize * fontScale), maxSearchDecorationButtonSize));
901
902    style.setWidth(Length(decorationSize + searchFieldDecorationButtonOffset, Fixed));
903    style.setHeight(Length(decorationSize, Fixed));
904}
905
906bool RenderThemeEfl::paintSearchFieldResultsButton(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
907{
908    return paintThemePart(object, SearchFieldResultsButton, info, rect);
909}
910
911void RenderThemeEfl::adjustSearchFieldResultsDecorationPartStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
912{
913    if (!m_page && element.document().page()) {
914        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustSearchFieldResultsDecorationPartStyle(styleResolver, style, element);
915        return;
916    }
917    adjustSizeConstraints(style, SearchFieldResultsDecoration);
918    style.resetBorder();
919    style.setWhiteSpace(PRE);
920
921    float fontScale = style.fontSize() / defaultFontSize;
922    int decorationSize = lroundf(std::min(std::max(minSearchDecorationButtonSize, defaultFontSize * fontScale), maxSearchDecorationButtonSize));
923
924    style.setWidth(Length(decorationSize + searchFieldDecorationButtonOffset, Fixed));
925    style.setHeight(Length(decorationSize, Fixed));
926}
927
928bool RenderThemeEfl::paintSearchFieldResultsDecorationPart(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
929{
930    return paintThemePart(object, SearchFieldResultsDecoration, info, rect);
931}
932
933void RenderThemeEfl::adjustSearchFieldCancelButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
934{
935    if (!m_page && element.document().page()) {
936        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustSearchFieldCancelButtonStyle(styleResolver, style, element);
937        return;
938    }
939    adjustSizeConstraints(style, SearchFieldCancelButton);
940    style.resetBorder();
941    style.setWhiteSpace(PRE);
942
943    // Logic taken from RenderThemeChromium.cpp.
944    // Scale the button size based on the font size.
945    float fontScale = style.fontSize() / defaultFontSize;
946    int cancelButtonSize = lroundf(std::min(std::max(minCancelButtonSize, defaultFontSize * fontScale), maxCancelButtonSize));
947
948    style.setWidth(Length(cancelButtonSize, Fixed));
949    style.setHeight(Length(cancelButtonSize, Fixed));
950}
951
952bool RenderThemeEfl::paintSearchFieldCancelButton(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
953{
954    return paintThemePart(object, SearchFieldCancelButton, info, rect);
955}
956
957void RenderThemeEfl::adjustSearchFieldStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
958{
959    if (!m_page && element.document().page()) {
960        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustSearchFieldStyle(styleResolver, style, element);
961        return;
962    }
963    adjustSizeConstraints(style, SearchField);
964    style.resetBorder();
965    style.setWhiteSpace(PRE);
966}
967
968bool RenderThemeEfl::paintSearchField(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
969{
970    return paintThemePart(object, SearchField, info, rect);
971}
972
973void RenderThemeEfl::adjustInnerSpinButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element& element) const
974{
975    if (!m_page && element.document().page()) {
976        static_cast<RenderThemeEfl&>(element.document().page()->theme()).adjustInnerSpinButtonStyle(styleResolver, style, element);
977        return;
978    }
979    adjustSizeConstraints(style, Spinner);
980}
981
982bool RenderThemeEfl::paintInnerSpinButton(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
983{
984    return paintThemePart(object, Spinner, info, rect);
985}
986
987void RenderThemeEfl::setDefaultFontSize(int size)
988{
989    defaultFontSize = size;
990}
991
992void RenderThemeEfl::systemFont(CSSValueID, FontDescription& fontDescription) const
993{
994    // It was called by RenderEmbeddedObject::paintReplaced to render alternative string.
995    // To avoid cairo_error while rendering, fontDescription should be passed.
996    fontDescription.setOneFamily("Sans");
997    fontDescription.setSpecifiedSize(defaultFontSize);
998    fontDescription.setIsAbsoluteSize(true);
999    fontDescription.setGenericFamily(FontDescription::NoFamily);
1000    fontDescription.setWeight(FontWeightNormal);
1001    fontDescription.setItalic(false);
1002}
1003
1004void RenderThemeEfl::adjustProgressBarStyle(StyleResolver&, RenderStyle& style, Element&) const
1005{
1006    style.setBoxShadow(nullptr);
1007}
1008
1009double RenderThemeEfl::animationRepeatIntervalForProgressBar(RenderProgress&) const
1010{
1011    return progressAnimationInterval;
1012}
1013
1014double RenderThemeEfl::animationDurationForProgressBar(RenderProgress&) const
1015{
1016    return progressAnimationInterval * progressAnimationFrames * 2; // "2" for back and forth;
1017}
1018
1019bool RenderThemeEfl::paintProgressBar(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
1020{
1021    if (!object.isProgress())
1022        return true;
1023
1024    return paintThemePart(object, ProgressBar, info, rect);
1025}
1026
1027#if ENABLE(VIDEO)
1028String RenderThemeEfl::mediaControlsStyleSheet()
1029{
1030    return ASCIILiteral(mediaControlsAppleUserAgentStyleSheet);
1031}
1032
1033String RenderThemeEfl::mediaControlsScript()
1034{
1035    StringBuilder scriptBuilder;
1036    scriptBuilder.append(mediaControlsLocalizedStringsJavaScript, sizeof(mediaControlsLocalizedStringsJavaScript));
1037    scriptBuilder.append(mediaControlsAppleJavaScript, sizeof(mediaControlsAppleJavaScript));
1038    return scriptBuilder.toString();
1039}
1040#endif
1041
1042#undef _ASSERT_ON_RELEASE_RETURN
1043#undef _ASSERT_ON_RELEASE_RETURN_VAL
1044
1045}
1046