Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / web / PopupMenuImpl.cpp
blob01c5be3a14c523a163ebf265a9fcf3fd3e047bfa
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.
5 #include "config.h"
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"
30 namespace blink {
32 namespace {
34 const char* fontWeightToString(FontWeight weight)
36 switch (weight) {
37 case FontWeight100:
38 return "100";
39 case FontWeight200:
40 return "200";
41 case FontWeight300:
42 return "300";
43 case FontWeight400:
44 return "400";
45 case FontWeight500:
46 return "500";
47 case FontWeight600:
48 return "600";
49 case FontWeight700:
50 return "700";
51 case FontWeight800:
52 return "800";
53 case FontWeight900:
54 return "900";
56 ASSERT_NOT_REACHED();
57 return nullptr;
60 const char* fontVariantToString(FontVariant variant)
62 switch (variant) {
63 case FontVariantNormal:
64 return "normal";
65 case FontVariantSmallCaps:
66 return "small-caps";
68 ASSERT_NOT_REACHED();
69 return nullptr;
72 // TODO crbug.com/516675 Add stretch to serialization
74 const char* fontStyleToString(FontStyle style)
76 switch (style) {
77 case FontStyleNormal:
78 return "normal";
79 case FontStyleOblique:
80 return "oblique";
81 case FontStyleItalic:
82 return "italic";
84 ASSERT_NOT_REACHED();
85 return nullptr;
88 const char* textTransformToString(ETextTransform transform)
90 switch (transform) {
91 case CAPITALIZE:
92 return "capitalize";
93 case UPPERCASE:
94 return "uppercase";
95 case LOWERCASE:
96 return "lowercase";
97 case TTNONE:
98 return "none";
100 ASSERT_NOT_REACHED();
101 return "";
104 } // anonymous namespace
106 class PopupMenuCSSFontSelector : public CSSFontSelector, private CSSFontSelectorClient {
107 WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(PopupMenuCSSFontSelector);
108 public:
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();
122 private:
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()
139 #if !ENABLE(OILPAN)
140 m_ownerFontSelector->unregisterForInvalidationCallbacks(this);
141 #endif
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 {
164 STACK_ALLOCATED();
165 public:
166 ItemIterationContext(const ComputedStyle& style, SharedBuffer* buffer)
167 : m_baseStyle(style)
168 , m_backgroundColor(style.visitedDependentColor(CSSPropertyBackgroundColor))
169 , m_listIndex(0)
170 , m_isInGroup(false)
171 , m_buffer(buffer)
173 ASSERT(m_buffer);
174 #if OS(LINUX)
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);
181 #endif
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);
198 if (f->next())
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()
207 // instead.
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);
214 m_isInGroup = true;
215 m_groupStyle = &groupStyle;
217 void finishGroupIfNecessary()
219 if (!m_isInGroup)
220 return;
221 PagePopupClient::addString("],},\n", m_buffer);
222 m_isInGroup = false;
223 m_groupStyle = nullptr;
226 const ComputedStyle& m_baseStyle;
227 Color m_backgroundColor;
228 const ComputedStyle* m_groupStyle;
230 unsigned m_listIndex;
231 bool m_isInGroup;
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)
245 , m_popup(nullptr)
246 , m_needsUpdate(false)
250 PopupMenuImpl::~PopupMenuImpl()
252 ASSERT(!m_popup);
255 DEFINE_TRACE(PopupMenuImpl)
257 visitor->trace(m_ownerElement);
258 PopupMenu::trace(visitor);
261 IntSize PopupMenuImpl::contentSize()
263 return IntSize();
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);
309 ASSERT(style);
310 SharedBuffer* data = context.m_buffer;
311 // TODO(tkent): We generate unnecessary "style: {\n},\n" even if no
312 // additional style.
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);
340 if (f->next())
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)
406 ASSERT(m_popup);
407 ASSERT(m_ownerElement);
408 EventQueueScope scope;
409 RefPtrWillBeRawPtr<PopupMenuImpl> protector(this);
410 bool success;
411 int listIndex = stringValue.toInt(&success);
412 ASSERT(success);
413 m_ownerElement->valueChanged(listIndex);
414 if (m_popup)
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);
429 bool success;
430 int listIndex = value.toInt(&success);
431 ASSERT(success);
432 m_ownerElement->provisionalSelectionChanged(listIndex);
435 void PopupMenuImpl::didClosePopup()
437 // Clearing m_popup first to prevent from trying to close the popup again.
438 m_popup = nullptr;
439 RefPtrWillBeRawPtr<PopupMenuImpl> protector(this);
440 if (m_ownerElement)
441 m_ownerElement->popupDidHide();
444 Element& PopupMenuImpl::ownerElement()
446 return *m_ownerElement;
449 Locale& PopupMenuImpl::locale()
451 return Locale::defaultLocale();
454 void PopupMenuImpl::closePopup()
456 if (m_popup)
457 m_chromeClient->closePagePopup(m_popup);
458 if (m_ownerElement)
459 m_ownerElement->popupDidCancel();
462 void PopupMenuImpl::dispose()
464 if (m_popup)
465 m_chromeClient->closePagePopup(m_popup);
468 void PopupMenuImpl::show(const FloatQuad& /*controlPosition*/, const IntSize& /*controlSize*/, int /*index*/)
470 ASSERT(!m_popup);
471 m_popup = m_chromeClient->openPagePopup(this);
474 void PopupMenuImpl::hide()
476 if (m_popup)
477 m_chromeClient->closePagePopup(m_popup);
480 void PopupMenuImpl::updateFromElement()
482 if (m_needsUpdate)
483 return;
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)
491 return;
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.
524 dispose();
527 } // namespace blink