1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "web/PopupMenuImpl.h"
8 #include "core/HTMLNames.h"
9 #include "core/css/CSSFontSelector.h"
10 #include "core/dom/ElementTraversal.h"
11 #include "core/dom/ExecutionContextTask.h"
12 #include "core/dom/NodeComputedStyle.h"
13 #include "core/dom/StyleEngine.h"
14 #include "core/events/ScopedEventQueue.h"
15 #include "core/frame/FrameView.h"
16 #include "core/html/HTMLHRElement.h"
17 #include "core/html/HTMLOptGroupElement.h"
18 #include "core/html/HTMLOptionElement.h"
19 #include "core/html/HTMLSelectElement.h"
20 #include "core/html/parser/HTMLParserIdioms.h"
21 #include "core/layout/LayoutTheme.h"
22 #include "core/page/PagePopup.h"
23 #include "platform/geometry/IntRect.h"
24 #include "platform/text/PlatformLocale.h"
25 #include "public/platform/Platform.h"
26 #include "public/web/WebColorChooser.h"
27 #include "web/ChromeClientImpl.h"
28 #include "web/WebViewImpl.h"
34 const char* fontWeightToString(FontWeight weight
)
60 const char* fontVariantToString(FontVariant variant
)
63 case FontVariantNormal
:
65 case FontVariantSmallCaps
:
72 // TODO crbug.com/516675 Add stretch to serialization
74 const char* fontStyleToString(FontStyle style
)
79 case FontStyleOblique
:
88 const char* textTransformToString(ETextTransform transform
)
100 ASSERT_NOT_REACHED();
104 } // anonymous namespace
106 class PopupMenuCSSFontSelector
: public CSSFontSelector
, private CSSFontSelectorClient
{
107 WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(PopupMenuCSSFontSelector
);
109 static PassRefPtrWillBeRawPtr
<PopupMenuCSSFontSelector
> create(Document
* document
, CSSFontSelector
* ownerFontSelector
)
111 return adoptRefWillBeNoop(new PopupMenuCSSFontSelector(document
, ownerFontSelector
));
114 ~PopupMenuCSSFontSelector();
116 // We don't override willUseFontData() for now because the old PopupListBox
117 // only worked with fonts loaded when opening the popup.
118 PassRefPtr
<FontData
> getFontData(const FontDescription
&, const AtomicString
&) override
;
120 DECLARE_VIRTUAL_TRACE();
123 PopupMenuCSSFontSelector(Document
*, CSSFontSelector
*);
125 void fontsNeedUpdate(CSSFontSelector
*) override
;
127 RefPtrWillBeMember
<CSSFontSelector
> m_ownerFontSelector
;
130 PopupMenuCSSFontSelector::PopupMenuCSSFontSelector(Document
* document
, CSSFontSelector
* ownerFontSelector
)
131 : CSSFontSelector(document
)
132 , m_ownerFontSelector(ownerFontSelector
)
134 m_ownerFontSelector
->registerForInvalidationCallbacks(this);
137 PopupMenuCSSFontSelector::~PopupMenuCSSFontSelector()
140 m_ownerFontSelector
->unregisterForInvalidationCallbacks(this);
144 PassRefPtr
<FontData
> PopupMenuCSSFontSelector::getFontData(const FontDescription
& description
, const AtomicString
& name
)
146 return m_ownerFontSelector
->getFontData(description
, name
);
149 void PopupMenuCSSFontSelector::fontsNeedUpdate(CSSFontSelector
* fontSelector
)
151 dispatchInvalidationCallbacks();
154 DEFINE_TRACE(PopupMenuCSSFontSelector
)
156 visitor
->trace(m_ownerFontSelector
);
157 CSSFontSelector::trace(visitor
);
158 CSSFontSelectorClient::trace(visitor
);
161 // ----------------------------------------------------------------
163 class PopupMenuImpl::ItemIterationContext
{
166 ItemIterationContext(const ComputedStyle
& style
, SharedBuffer
* buffer
)
168 , m_backgroundColor(style
.visitedDependentColor(CSSPropertyBackgroundColor
))
175 // On other platforms, the <option> background color is the same as the
176 // <select> background color. On Linux, that makes the <option>
177 // background color very dark, so by default, try to use a lighter
178 // background color for <option>s.
179 if (LayoutTheme::theme().systemColor(CSSValueButtonface
) == m_backgroundColor
)
180 m_backgroundColor
= LayoutTheme::theme().systemColor(CSSValueMenu
);
184 void serializeBaseStyle()
186 ASSERT(!m_isInGroup
);
187 PagePopupClient::addString("baseStyle: {", m_buffer
);
188 addProperty("backgroundColor", m_backgroundColor
.serialized(), m_buffer
);
189 addProperty("color", baseStyle().visitedDependentColor(CSSPropertyColor
).serialized(), m_buffer
);
190 addProperty("textTransform", String(textTransformToString(baseStyle().textTransform())), m_buffer
);
191 addProperty("fontSize", baseFont().computedPixelSize(), m_buffer
);
192 addProperty("fontStyle", String(fontStyleToString(baseFont().style())), m_buffer
);
193 addProperty("fontVariant", String(fontVariantToString(baseFont().variant())), m_buffer
);
195 PagePopupClient::addString("fontFamily: [", m_buffer
);
196 for (const FontFamily
* f
= &baseFont().family(); f
; f
= f
->next()) {
197 addJavaScriptString(f
->family().string(), m_buffer
);
199 PagePopupClient::addString(",", m_buffer
);
201 PagePopupClient::addString("]", m_buffer
);
202 PagePopupClient::addString("},\n", m_buffer
);
205 Color
backgroundColor() const { return m_isInGroup
? m_groupStyle
->visitedDependentColor(CSSPropertyBackgroundColor
) : m_backgroundColor
; }
206 // Do not use baseStyle() for background-color, use backgroundColor()
208 const ComputedStyle
& baseStyle() { return m_isInGroup
? *m_groupStyle
: m_baseStyle
; }
209 const FontDescription
& baseFont() { return m_isInGroup
? m_groupStyle
->fontDescription() : m_baseStyle
.fontDescription(); }
210 void startGroupChildren(const ComputedStyle
& groupStyle
)
212 ASSERT(!m_isInGroup
);
213 PagePopupClient::addString("children: [", m_buffer
);
215 m_groupStyle
= &groupStyle
;
217 void finishGroupIfNecessary()
221 PagePopupClient::addString("],},\n", m_buffer
);
223 m_groupStyle
= nullptr;
226 const ComputedStyle
& m_baseStyle
;
227 Color m_backgroundColor
;
228 const ComputedStyle
* m_groupStyle
;
230 unsigned m_listIndex
;
232 SharedBuffer
* m_buffer
;
235 // ----------------------------------------------------------------
237 PassRefPtrWillBeRawPtr
<PopupMenuImpl
> PopupMenuImpl::create(ChromeClientImpl
* chromeClient
, HTMLSelectElement
& ownerElement
)
239 return adoptRefWillBeNoop(new PopupMenuImpl(chromeClient
, ownerElement
));
242 PopupMenuImpl::PopupMenuImpl(ChromeClientImpl
* chromeClient
, HTMLSelectElement
& ownerElement
)
243 : m_chromeClient(chromeClient
)
244 , m_ownerElement(ownerElement
)
246 , m_needsUpdate(false)
250 PopupMenuImpl::~PopupMenuImpl()
255 DEFINE_TRACE(PopupMenuImpl
)
257 visitor
->trace(m_ownerElement
);
258 PopupMenu::trace(visitor
);
261 IntSize
PopupMenuImpl::contentSize()
266 void PopupMenuImpl::writeDocument(SharedBuffer
* data
)
268 HTMLSelectElement
& ownerElement
= *m_ownerElement
;
269 IntRect anchorRectInScreen
= m_chromeClient
->viewportToScreen(ownerElement
.elementRectRelativeToViewport());
271 PagePopupClient::addString("<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", data
);
272 data
->append(Platform::current()->loadResource("pickerCommon.css"));
273 data
->append(Platform::current()->loadResource("listPicker.css"));
274 PagePopupClient::addString("</style></head><body><div id=main>Loading...</div><script>\n"
275 "window.dialogArguments = {\n", data
);
276 addProperty("selectedIndex", ownerElement
.optionToListIndex(ownerElement
.selectedIndex()), data
);
277 const ComputedStyle
* ownerStyle
= ownerElement
.computedStyle();
278 ItemIterationContext
context(*ownerStyle
, data
);
279 context
.serializeBaseStyle();
280 PagePopupClient::addString("children: [\n", data
);
281 const WillBeHeapVector
<RawPtrWillBeMember
<HTMLElement
>>& items
= ownerElement
.listItems();
282 for (; context
.m_listIndex
< items
.size(); ++context
.m_listIndex
) {
283 Element
& child
= *items
[context
.m_listIndex
];
284 if (!isHTMLOptGroupElement(child
.parentNode()))
285 context
.finishGroupIfNecessary();
286 if (isHTMLOptionElement(child
))
287 addOption(context
, toHTMLOptionElement(child
));
288 else if (isHTMLOptGroupElement(child
))
289 addOptGroup(context
, toHTMLOptGroupElement(child
));
290 else if (isHTMLHRElement(child
))
291 addSeparator(context
, toHTMLHRElement(child
));
293 context
.finishGroupIfNecessary();
294 PagePopupClient::addString("],\n", data
);
296 addProperty("anchorRectInScreen", anchorRectInScreen
, data
);
297 bool isRTL
= !ownerStyle
->isLeftToRightDirection();
298 addProperty("isRTL", isRTL
, data
);
299 addProperty("paddingStart", isRTL
? ownerElement
.clientPaddingRight().toDouble() : ownerElement
.clientPaddingLeft().toDouble(), data
);
300 PagePopupClient::addString("};\n", data
);
301 data
->append(Platform::current()->loadResource("pickerCommon.js"));
302 data
->append(Platform::current()->loadResource("listPicker.js"));
303 PagePopupClient::addString("</script></body>\n", data
);
306 void PopupMenuImpl::addElementStyle(ItemIterationContext
& context
, HTMLElement
& element
)
308 const ComputedStyle
* style
= m_ownerElement
->itemComputedStyle(element
);
310 SharedBuffer
* data
= context
.m_buffer
;
311 // TODO(tkent): We generate unnecessary "style: {\n},\n" even if no
313 PagePopupClient::addString("style: {\n", data
);
314 if (style
->visibility() == HIDDEN
)
315 addProperty("visibility", String("hidden"), data
);
316 if (style
->display() == NONE
)
317 addProperty("display", String("none"), data
);
318 const ComputedStyle
& baseStyle
= context
.baseStyle();
319 if (baseStyle
.direction() != style
->direction())
320 addProperty("direction", String(style
->direction() == RTL
? "rtl" : "ltr"), data
);
321 if (isOverride(style
->unicodeBidi()))
322 addProperty("unicodeBidi", String("bidi-override"), data
);
323 Color foregroundColor
= style
->visitedDependentColor(CSSPropertyColor
);
324 if (baseStyle
.visitedDependentColor(CSSPropertyColor
) != foregroundColor
)
325 addProperty("color", foregroundColor
.serialized(), data
);
326 Color backgroundColor
= style
->visitedDependentColor(CSSPropertyBackgroundColor
);
327 if (context
.backgroundColor() != backgroundColor
&& backgroundColor
!= Color::transparent
)
328 addProperty("backgroundColor", backgroundColor
.serialized(), data
);
329 const FontDescription
& baseFont
= context
.baseFont();
330 const FontDescription
& fontDescription
= style
->font().fontDescription();
331 if (baseFont
.computedPixelSize() != fontDescription
.computedPixelSize())
332 addProperty("fontSize", fontDescription
.computedPixelSize(), data
);
333 // Our UA stylesheet has font-weight:normal for OPTION.
334 if (FontWeightNormal
!= fontDescription
.weight())
335 addProperty("fontWeight", String(fontWeightToString(fontDescription
.weight())), data
);
336 if (baseFont
.family() != fontDescription
.family()) {
337 PagePopupClient::addString("fontFamily: [\n", data
);
338 for (const FontFamily
* f
= &fontDescription
.family(); f
; f
= f
->next()) {
339 addJavaScriptString(f
->family().string(), data
);
341 PagePopupClient::addString(",\n", data
);
343 PagePopupClient::addString("],\n", data
);
345 if (baseFont
.style() != fontDescription
.style())
346 addProperty("fontStyle", String(fontStyleToString(fontDescription
.style())), data
);
347 if (baseFont
.variant() != fontDescription
.variant())
348 addProperty("fontVariant", String(fontVariantToString(fontDescription
.variant())), data
);
349 if (baseStyle
.textTransform() != style
->textTransform())
350 addProperty("textTransform", String(textTransformToString(style
->textTransform())), data
);
352 PagePopupClient::addString("},\n", data
);
355 void PopupMenuImpl::addOption(ItemIterationContext
& context
, HTMLOptionElement
& element
)
357 SharedBuffer
* data
= context
.m_buffer
;
358 PagePopupClient::addString("{", data
);
359 addProperty("label", element
.text(), data
);
360 addProperty("value", context
.m_listIndex
, data
);
361 if (!element
.title().isEmpty())
362 addProperty("title", element
.title(), data
);
363 const AtomicString
& ariaLabel
= element
.fastGetAttribute(HTMLNames::aria_labelAttr
);
364 if (!ariaLabel
.isEmpty())
365 addProperty("ariaLabel", ariaLabel
, data
);
366 if (element
.isDisabledFormControl())
367 addProperty("disabled", true, data
);
368 addElementStyle(context
, element
);
369 PagePopupClient::addString("},", data
);
372 void PopupMenuImpl::addOptGroup(ItemIterationContext
& context
, HTMLOptGroupElement
& element
)
374 SharedBuffer
* data
= context
.m_buffer
;
375 PagePopupClient::addString("{\n", data
);
376 PagePopupClient::addString("type: \"optgroup\",\n", data
);
377 addProperty("label", element
.groupLabelText(), data
);
378 addProperty("title", element
.title(), data
);
379 addProperty("ariaLabel", element
.fastGetAttribute(HTMLNames::aria_labelAttr
), data
);
380 addProperty("disabled", element
.isDisabledFormControl(), data
);
381 addElementStyle(context
, element
);
382 context
.startGroupChildren(*m_ownerElement
->itemComputedStyle(element
));
383 // We should call ItemIterationContext::finishGroupIfNecessary() later.
386 void PopupMenuImpl::addSeparator(ItemIterationContext
& context
, HTMLHRElement
& element
)
388 SharedBuffer
* data
= context
.m_buffer
;
389 PagePopupClient::addString("{\n", data
);
390 PagePopupClient::addString("type: \"separator\",\n", data
);
391 addProperty("title", element
.title(), data
);
392 addProperty("ariaLabel", element
.fastGetAttribute(HTMLNames::aria_labelAttr
), data
);
393 addProperty("disabled", element
.isDisabledFormControl(), data
);
394 addElementStyle(context
, element
);
395 PagePopupClient::addString("},\n", data
);
398 void PopupMenuImpl::selectFontsFromOwnerDocument(Document
& document
)
400 Document
& ownerDocument
= ownerElement().document();
401 document
.styleEngine().setFontSelector(PopupMenuCSSFontSelector::create(&document
, ownerDocument
.styleEngine().fontSelector()));
404 void PopupMenuImpl::setValueAndClosePopup(int numValue
, const String
& stringValue
)
407 ASSERT(m_ownerElement
);
408 EventQueueScope scope
;
409 RefPtrWillBeRawPtr
<PopupMenuImpl
> protector(this);
411 int listIndex
= stringValue
.toInt(&success
);
413 m_ownerElement
->valueChanged(listIndex
);
415 m_chromeClient
->closePagePopup(m_popup
);
416 // We dispatch events on the owner element to match the legacy behavior.
417 // Other browsers dispatch click events before and after showing the popup.
418 if (m_ownerElement
) {
419 PlatformMouseEvent event
;
420 RefPtrWillBeRawPtr
<Element
> owner
= &ownerElement();
421 owner
->dispatchMouseEvent(event
, EventTypeNames::mouseup
);
422 owner
->dispatchMouseEvent(event
, EventTypeNames::click
);
426 void PopupMenuImpl::setValue(const String
& value
)
428 ASSERT(m_ownerElement
);
430 int listIndex
= value
.toInt(&success
);
432 m_ownerElement
->provisionalSelectionChanged(listIndex
);
435 void PopupMenuImpl::didClosePopup()
437 // Clearing m_popup first to prevent from trying to close the popup again.
439 RefPtrWillBeRawPtr
<PopupMenuImpl
> protector(this);
441 m_ownerElement
->popupDidHide();
444 Element
& PopupMenuImpl::ownerElement()
446 return *m_ownerElement
;
449 Locale
& PopupMenuImpl::locale()
451 return Locale::defaultLocale();
454 void PopupMenuImpl::closePopup()
457 m_chromeClient
->closePagePopup(m_popup
);
459 m_ownerElement
->popupDidCancel();
462 void PopupMenuImpl::dispose()
465 m_chromeClient
->closePagePopup(m_popup
);
468 void PopupMenuImpl::show(const FloatQuad
& /*controlPosition*/, const IntSize
& /*controlSize*/, int /*index*/)
471 m_popup
= m_chromeClient
->openPagePopup(this);
474 void PopupMenuImpl::hide()
477 m_chromeClient
->closePagePopup(m_popup
);
480 void PopupMenuImpl::updateFromElement()
484 m_needsUpdate
= true;
485 ownerElement().document().postTask(FROM_HERE
, createSameThreadTask(&PopupMenuImpl::update
, PassRefPtrWillBeRawPtr
<PopupMenuImpl
>(this)));
488 void PopupMenuImpl::update()
490 if (!m_popup
|| !m_ownerElement
)
492 ownerElement().document().updateLayoutTreeIfNeeded();
493 m_needsUpdate
= false;
494 RefPtr
<SharedBuffer
> data
= SharedBuffer::create();
495 PagePopupClient::addString("window.updateData = {\n", data
.get());
496 PagePopupClient::addString("type: \"update\",\n", data
.get());
497 ItemIterationContext
context(*m_ownerElement
->computedStyle(), data
.get());
498 context
.serializeBaseStyle();
499 PagePopupClient::addString("children: [", data
.get());
500 const WillBeHeapVector
<RawPtrWillBeMember
<HTMLElement
>>& items
= m_ownerElement
->listItems();
501 for (; context
.m_listIndex
< items
.size(); ++context
.m_listIndex
) {
502 Element
& child
= *items
[context
.m_listIndex
];
503 if (!isHTMLOptGroupElement(child
.parentNode()))
504 context
.finishGroupIfNecessary();
505 if (isHTMLOptionElement(child
))
506 addOption(context
, toHTMLOptionElement(child
));
507 else if (isHTMLOptGroupElement(child
))
508 addOptGroup(context
, toHTMLOptGroupElement(child
));
509 else if (isHTMLHRElement(child
))
510 addSeparator(context
, toHTMLHRElement(child
));
512 context
.finishGroupIfNecessary();
513 PagePopupClient::addString("],\n", data
.get());
514 PagePopupClient::addString("}\n", data
.get());
515 m_popup
->postMessage(String::fromUTF8(data
->data(), data
->size()));
519 void PopupMenuImpl::disconnectClient()
521 m_ownerElement
= nullptr;
522 // Cannot be done during finalization, so instead done when the
523 // layout object is destroyed and disconnected.