2 * Copyright (c) 2011, Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include "web/PopupContainer.h"
34 #include "core/dom/Document.h"
35 #include "core/frame/FrameView.h"
36 #include "core/frame/LocalFrame.h"
37 #include "core/page/Chrome.h"
38 #include "core/page/ChromeClient.h"
39 #include "core/page/Page.h"
40 #include "platform/PlatformGestureEvent.h"
41 #include "platform/PlatformKeyboardEvent.h"
42 #include "platform/PlatformMouseEvent.h"
43 #include "platform/PlatformScreen.h"
44 #include "platform/PlatformTouchEvent.h"
45 #include "platform/PlatformWheelEvent.h"
46 #include "platform/PopupMenuClient.h"
47 #include "platform/UserGestureIndicator.h"
48 #include "platform/geometry/IntRect.h"
49 #include "platform/graphics/GraphicsContext.h"
50 #include "public/web/WebPopupMenuInfo.h"
51 #include "public/web/WebPopupType.h"
52 #include "public/web/WebViewClient.h"
53 #include "web/PopupContainerClient.h"
54 #include "web/WebPopupMenuImpl.h"
55 #include "web/WebViewImpl.h"
60 static const int borderSize = 1;
62 static PlatformMouseEvent constructRelativeMouseEvent(const PlatformMouseEvent& e, PopupContainer* parent, PopupListBox* child)
64 IntPoint pos = parent->convertSelfToChild(child, e.position());
66 // FIXME: This is a horrible hack since PlatformMouseEvent has no setters for x/y.
67 PlatformMouseEvent relativeEvent = e;
68 IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.position());
69 relativePos.setX(pos.x());
70 relativePos.setY(pos.y());
74 static PlatformWheelEvent constructRelativeWheelEvent(const PlatformWheelEvent& e, PopupContainer* parent, PopupListBox* child)
76 IntPoint pos = parent->convertSelfToChild(child, e.position());
78 // FIXME: This is a horrible hack since PlatformWheelEvent has no setters for x/y.
79 PlatformWheelEvent relativeEvent = e;
80 IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.position());
81 relativePos.setX(pos.x());
82 relativePos.setY(pos.y());
87 PassRefPtr<PopupContainer> PopupContainer::create(PopupMenuClient* client, bool deviceSupportsTouch)
89 return adoptRef(new PopupContainer(client, deviceSupportsTouch));
92 PopupContainer::PopupContainer(PopupMenuClient* client, bool deviceSupportsTouch)
93 : m_listBox(PopupListBox::create(client, deviceSupportsTouch, this))
99 PopupContainer::~PopupContainer()
101 if (m_listBox->parent())
102 m_listBox->setParent(0);
105 IntRect PopupContainer::layoutAndCalculateWidgetRectInternal(IntRect widgetRectInScreen, int targetControlHeight, const FloatRect& windowRect, const FloatRect& screen, bool isRTL, const int rtlOffset, const int verticalOffset, const IntSize& transformOffset, PopupContent* listBox, bool& needToResizeView)
108 if (windowRect.x() >= screen.x() && windowRect.maxX() <= screen.maxX() && (widgetRectInScreen.x() < screen.x() || widgetRectInScreen.maxX() > screen.maxX())) {
109 // First, inverse the popup alignment if it does not fit the screen -
110 // this might fix things (or make them better).
111 IntRect inverseWidgetRectInScreen = widgetRectInScreen;
112 inverseWidgetRectInScreen.setX(inverseWidgetRectInScreen.x() + (isRTL ? -rtlOffset : rtlOffset));
113 inverseWidgetRectInScreen.setY(inverseWidgetRectInScreen.y() + (isRTL ? -verticalOffset : verticalOffset));
114 IntRect enclosingScreen = enclosingIntRect(screen);
115 unsigned originalCutoff = std::max(enclosingScreen.x() - widgetRectInScreen.x(), 0) + std::max(widgetRectInScreen.maxX() - enclosingScreen.maxX(), 0);
116 unsigned inverseCutoff = std::max(enclosingScreen.x() - inverseWidgetRectInScreen.x(), 0) + std::max(inverseWidgetRectInScreen.maxX() - enclosingScreen.maxX(), 0);
118 // Accept the inverse popup alignment if the trimmed content gets
119 // shorter than that in the original alignment case.
120 if (inverseCutoff < originalCutoff)
121 widgetRectInScreen = inverseWidgetRectInScreen;
123 if (widgetRectInScreen.x() < screen.x()) {
124 widgetRectInScreen.setWidth(widgetRectInScreen.maxX() - screen.x());
125 widgetRectInScreen.setX(screen.x());
126 listBox->setMaxWidthAndLayout(std::max(widgetRectInScreen.width() - borderSize * 2, 0));
127 } else if (widgetRectInScreen.maxX() > screen.maxX()) {
128 widgetRectInScreen.setWidth(screen.maxX() - widgetRectInScreen.x());
129 listBox->setMaxWidthAndLayout(std::max(widgetRectInScreen.width() - borderSize * 2, 0));
133 // Calculate Y axis size.
134 if (widgetRectInScreen.maxY() > static_cast<int>(screen.maxY())) {
135 if (widgetRectInScreen.y() - widgetRectInScreen.height() - targetControlHeight - transformOffset.height() > 0) {
136 // There is enough room to open upwards.
137 widgetRectInScreen.move(-transformOffset.width(), -(widgetRectInScreen.height() + targetControlHeight + transformOffset.height()));
139 // Figure whether upwards or downwards has more room and set the
140 // maximum number of items.
141 int spaceAbove = widgetRectInScreen.y() - targetControlHeight + transformOffset.height();
142 int spaceBelow = screen.maxY() - widgetRectInScreen.y();
143 if (spaceAbove > spaceBelow)
144 listBox->setMaxHeight(spaceAbove);
146 listBox->setMaxHeight(spaceBelow);
148 needToResizeView = true;
149 widgetRectInScreen.setHeight(listBox->popupContentHeight() + borderSize * 2);
150 // Move WebWidget upwards if necessary.
151 if (spaceAbove > spaceBelow)
152 widgetRectInScreen.move(-transformOffset.width(), -(widgetRectInScreen.height() + targetControlHeight + transformOffset.height()));
155 return widgetRectInScreen;
158 IntRect PopupContainer::layoutAndCalculateWidgetRect(int targetControlHeight, const IntSize& transformOffset, const IntPoint& popupInitialCoordinate)
160 // Reset the max width and height to their default values, they will be
161 // recomputed below if necessary.
162 m_listBox->setMaxHeight(PopupListBox::defaultMaxHeight);
163 m_listBox->setMaxWidth(std::numeric_limits<int>::max());
165 // Lay everything out to figure out our preferred size, then tell the view's
166 // WidgetClient about it. It should assign us a client.
169 bool isRTL = this->isRTL();
171 // Compute the starting x-axis for a normal RTL or right-aligned LTR
172 // dropdown. For those, the right edge of dropdown box should be aligned
173 // with the right edge of <select>/<input> element box, and the dropdown box
174 // should be expanded to the left if more space is needed.
175 // m_originalFrameRect.width() is the width of the target <select>/<input>
177 int rtlOffset = m_controlPosition.p2().x() - m_controlPosition.p1().x() - (m_listBox->width() + borderSize * 2);
178 int rightOffset = isRTL ? rtlOffset : 0;
180 // Compute the y-axis offset between the bottom left and bottom right
181 // points. If the <select>/<input> is transformed, they are not the same.
182 int verticalOffset = - m_controlPosition.p4().y() + m_controlPosition.p3().y();
183 int verticalForRTLOffset = isRTL ? verticalOffset : 0;
185 // Assume m_listBox size is already calculated.
186 IntSize targetSize(m_listBox->width() + borderSize * 2, m_listBox->height() + borderSize * 2);
188 IntRect widgetRectInScreen;
189 // If the popup would extend past the bottom of the screen, open upwards
191 FloatRect screen = screenAvailableRect(m_frameView.get());
192 // Use popupInitialCoordinate.x() + rightOffset because RTL position
193 // needs to be considered.
194 float pageScaleFactor = m_frameView->frame().page()->pageScaleFactor();
195 int popupX = round((popupInitialCoordinate.x() + rightOffset) * pageScaleFactor);
196 int popupY = round((popupInitialCoordinate.y() + verticalForRTLOffset) * pageScaleFactor);
197 widgetRectInScreen = chromeClient().rootViewToScreen(IntRect(popupX, popupY, targetSize.width(), targetSize.height()));
199 // If we have multiple screens and the browser rect is in one screen, we
200 // have to clip the window width to the screen width.
201 // When clipping, we also need to set a maximum width for the list box.
202 FloatRect windowRect = chromeClient().windowRect();
204 bool needToResizeView = false;
205 widgetRectInScreen = layoutAndCalculateWidgetRectInternal(widgetRectInScreen, targetControlHeight, windowRect, screen, isRTL, rtlOffset, verticalOffset, transformOffset, m_listBox.get(), needToResizeView);
206 if (needToResizeView)
209 return widgetRectInScreen;
212 void PopupContainer::showPopup(FrameView* view)
215 m_listBox->m_focusedElement = m_frameView->frame().document()->focusedElement();
217 IntSize transformOffset(m_controlPosition.p4().x() - m_controlPosition.p1().x(), m_controlPosition.p4().y() - m_controlPosition.p1().y() - m_controlSize.height());
218 popupOpened(layoutAndCalculateWidgetRect(m_controlSize.height(), transformOffset, roundedIntPoint(m_controlPosition.p4())));
221 if (!m_listBox->parent())
222 m_listBox->setParent(this);
224 m_listBox->scrollToRevealSelection();
229 void PopupContainer::hidePopup()
231 m_listBox->abandon();
234 void PopupContainer::notifyPopupHidden()
240 // With Oilpan, we cannot assume that the FrameView's LocalFrame's
241 // page is still available, as the LocalFrame itself may have been
242 // detached from its FrameHost by now.
244 // So, if a popup menu is left in an open/shown state when
245 // finalized, the PopupMenu implementation of this container's
246 // listbox will hide itself when destructed, delivering the
247 // notifyPopupHidden() notification in the process & ending up here.
248 // If the LocalFrame has been detached already -- done when its
249 // HTMLFrameOwnerElement frame owner is detached as part of being
250 // torn down -- the connection to the FrameHost has been snipped &
251 // there's no page. Hence the null check.
253 // In a non-Oilpan setting, the RenderMenuList that controls/owns
254 // the PopupMenuChromium object and this PopupContainer is torn
255 // down and destructed before the frame and frame owner, hence the
256 // page will always be available in that setting and this will
258 if (WebViewImpl* webView = WebViewImpl::fromPage(m_frameView->frame().page()))
259 webView->popupClosed(this);
262 void PopupContainer::fitToListBox()
264 // Place the listbox within our border.
265 m_listBox->move(borderSize, borderSize);
267 // Size ourselves to contain listbox + border.
268 resize(m_listBox->width() + borderSize * 2, m_listBox->height() + borderSize * 2);
272 bool PopupContainer::handleMouseDownEvent(const PlatformMouseEvent& event)
274 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
275 return m_listBox->handleMouseDownEvent(
276 constructRelativeMouseEvent(event, this, m_listBox.get()));
279 bool PopupContainer::handleMouseMoveEvent(const PlatformMouseEvent& event)
281 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
282 return m_listBox->handleMouseMoveEvent(
283 constructRelativeMouseEvent(event, this, m_listBox.get()));
286 bool PopupContainer::handleMouseReleaseEvent(const PlatformMouseEvent& event)
288 RefPtr<PopupContainer> protect(this);
289 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
290 return m_listBox->handleMouseReleaseEvent(
291 constructRelativeMouseEvent(event, this, m_listBox.get()));
294 bool PopupContainer::handleWheelEvent(const PlatformWheelEvent& event)
296 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
297 return m_listBox->handleWheelEvent(
298 constructRelativeWheelEvent(event, this, m_listBox.get()));
301 bool PopupContainer::handleTouchEvent(const PlatformTouchEvent&)
306 // FIXME: Refactor this code to share functionality with
307 // EventHandler::handleGestureEvent.
308 bool PopupContainer::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
310 switch (gestureEvent.type()) {
311 case PlatformEvent::GestureTap: {
312 PlatformMouseEvent fakeMouseMove(gestureEvent.position(), gestureEvent.globalPosition(), NoButton, PlatformEvent::MouseMoved, /* clickCount */ 1, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), PlatformMouseEvent::FromTouch, gestureEvent.timestamp());
313 PlatformMouseEvent fakeMouseDown(gestureEvent.position(), gestureEvent.globalPosition(), LeftButton, PlatformEvent::MousePressed, /* clickCount */ 1, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), PlatformMouseEvent::FromTouch, gestureEvent.timestamp());
314 PlatformMouseEvent fakeMouseUp(gestureEvent.position(), gestureEvent.globalPosition(), LeftButton, PlatformEvent::MouseReleased, /* clickCount */ 1, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), PlatformMouseEvent::FromTouch, gestureEvent.timestamp());
315 // handleMouseMoveEvent(fakeMouseMove);
316 handleMouseDownEvent(fakeMouseDown);
317 handleMouseReleaseEvent(fakeMouseUp);
320 case PlatformEvent::GestureScrollUpdate:
321 case PlatformEvent::GestureScrollUpdateWithoutPropagation: {
322 PlatformWheelEvent syntheticWheelEvent(gestureEvent.position(), gestureEvent.globalPosition(), gestureEvent.deltaX(), gestureEvent.deltaY(), gestureEvent.deltaX() / 120.0f, gestureEvent.deltaY() / 120.0f, ScrollByPixelWheelEvent, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey());
323 handleWheelEvent(syntheticWheelEvent);
326 case PlatformEvent::GestureScrollBegin:
327 case PlatformEvent::GestureScrollEnd:
328 case PlatformEvent::GestureTapDown:
329 case PlatformEvent::GestureShowPress:
332 ASSERT_NOT_REACHED();
337 bool PopupContainer::handleKeyEvent(const PlatformKeyboardEvent& event)
339 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
340 return m_listBox->handleKeyEvent(event);
343 void PopupContainer::hide()
345 m_listBox->abandon();
348 void PopupContainer::paint(GraphicsContext* gc, const IntRect& rect)
350 // Adjust coords for scrolled frame.
351 IntRect r = intersection(rect, frameRect());
357 gc->translate(static_cast<float>(tx), static_cast<float>(ty));
358 m_listBox->paint(gc, r);
359 gc->translate(-static_cast<float>(tx), -static_cast<float>(ty));
361 paintBorder(gc, rect);
364 void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect)
366 // FIXME: Where do we get the border color from?
367 Color borderColor(127, 157, 185);
369 gc->setStrokeStyle(NoStroke);
370 gc->setFillColor(borderColor);
375 // top, left, bottom, right
376 gc->drawRect(IntRect(tx, ty, width(), borderSize));
377 gc->drawRect(IntRect(tx, ty, borderSize, height()));
378 gc->drawRect(IntRect(tx, ty + height() - borderSize, width(), borderSize));
379 gc->drawRect(IntRect(tx + width() - borderSize, ty, borderSize, height()));
382 bool PopupContainer::isInterestedInEventForKey(int keyCode)
384 return m_listBox->isInterestedInEventForKey(keyCode);
387 ChromeClient& PopupContainer::chromeClient()
389 return m_frameView->frame().page()->chrome().client();
392 void PopupContainer::showInRect(const FloatQuad& controlPosition, const IntSize& controlSize, FrameView* v, int index)
394 // The controlSize is the size of the select box. It's usually larger than
395 // we need. Subtract border size so that usually the container will be
396 // displayed exactly the same width as the select box.
397 m_listBox->setBaseWidth(max(controlSize.width() - borderSize * 2, 0));
399 m_listBox->updateFromElement();
401 // We set the selected item in updateFromElement(), and disregard the
402 // index passed into this function (same as Webkit's PopupMenuWin.cpp)
403 // FIXME: make sure this is correct, and add an assertion.
404 // ASSERT(popupWindow(popup)->listBox()->selectedIndex() == index);
406 // Save and convert the controlPosition to main window coords. Each point is converted separately
407 // to window coordinates because the control could be in a transformed webview and then each point
408 // would be transformed by a different delta.
409 m_controlPosition.setP1(v->contentsToWindow(IntPoint(controlPosition.p1().x(), controlPosition.p1().y())));
410 m_controlPosition.setP2(v->contentsToWindow(IntPoint(controlPosition.p2().x(), controlPosition.p2().y())));
411 m_controlPosition.setP3(v->contentsToWindow(IntPoint(controlPosition.p3().x(), controlPosition.p3().y())));
412 m_controlPosition.setP4(v->contentsToWindow(IntPoint(controlPosition.p4().x(), controlPosition.p4().y())));
414 m_controlSize = controlSize;
416 // Position at (0, 0) since the frameRect().location() is relative to the
418 setFrameRect(IntRect(IntPoint(), controlSize));
422 IntRect PopupContainer::refresh(const IntRect& targetControlRect)
424 m_listBox->setBaseWidth(max(m_controlSize.width() - borderSize * 2, 0));
425 m_listBox->updateFromElement();
427 IntPoint locationInWindow = m_frameView->contentsToWindow(targetControlRect.location());
429 // Move it below the select widget.
430 locationInWindow.move(0, targetControlRect.height());
432 IntRect widgetRectInScreen = layoutAndCalculateWidgetRect(targetControlRect.height(), IntSize(), locationInWindow);
434 // Reset the size (which can be set to the PopupListBox size in
435 // layoutAndGetRTLOffset(), exceeding the available widget rectangle.)
436 if (size() != widgetRectInScreen.size())
437 resize(widgetRectInScreen.size());
441 return widgetRectInScreen;
444 inline bool PopupContainer::isRTL() const
446 return m_listBox->m_popupClient->menuStyle().textDirection() == RTL;
449 int PopupContainer::selectedIndex() const
451 return m_listBox->selectedIndex();
454 int PopupContainer::menuItemHeight() const
456 return m_listBox->getRowHeight(0);
459 int PopupContainer::menuItemFontSize() const
461 return m_listBox->getRowFont(0).fontDescription().computedSize();
464 PopupMenuStyle PopupContainer::menuStyle() const
466 return m_listBox->m_popupClient->menuStyle();
469 const WTF::Vector<PopupItem*>& PopupContainer:: popupData() const
471 return m_listBox->items();
474 String PopupContainer::getSelectedItemToolTip()
476 // We cannot use m_popupClient->selectedIndex() to choose tooltip message,
477 // because the selectedIndex() might return final selected index, not
478 // hovering selection.
479 return m_listBox->m_popupClient->itemToolTip(m_listBox->m_selectedIndex);
482 void PopupContainer::popupOpened(const IntRect& bounds)
484 WebViewImpl* webView = WebViewImpl::fromPage(m_frameView->frame().page());
485 if (!webView->client())
488 WebWidget* webwidget = webView->client()->createPopupMenu(WebPopupTypeSelect);
491 // We only notify when the WebView has to handle the popup, as when
492 // the popup is handled externally, the fact that a popup is showing is
493 // transparent to the WebView.
494 webView->popupOpened(this);
495 toWebPopupMenuImpl(webwidget)->initialize(this, bounds);
498 void PopupContainer::getPopupMenuInfo(WebPopupMenuInfo* info)
500 const Vector<PopupItem*>& inputItems = popupData();
502 WebVector<WebMenuItemInfo> outputItems(inputItems.size());
504 for (size_t i = 0; i < inputItems.size(); ++i) {
505 const PopupItem& inputItem = *inputItems[i];
506 WebMenuItemInfo& outputItem = outputItems[i];
508 outputItem.label = inputItem.label;
509 outputItem.enabled = inputItem.enabled;
510 outputItem.textDirection = toWebTextDirection(inputItem.textDirection);
511 outputItem.hasTextDirectionOverride = inputItem.hasTextDirectionOverride;
513 switch (inputItem.type) {
514 case PopupItem::TypeOption:
515 outputItem.type = WebMenuItemInfo::Option;
517 case PopupItem::TypeGroup:
518 outputItem.type = WebMenuItemInfo::Group;
520 case PopupItem::TypeSeparator:
521 outputItem.type = WebMenuItemInfo::Separator;
526 info->itemHeight = menuItemHeight();
527 info->itemFontSize = menuItemFontSize();
528 info->selectedIndex = selectedIndex();
529 info->items.swap(outputItems);
530 info->rightAligned = menuStyle().textDirection() == RTL;
533 void PopupContainer::invalidateRect(const IntRect& rect)
535 if (HostWindow* h = hostWindow())
536 h->invalidateContentsAndRootView(rect);
539 HostWindow* PopupContainer::hostWindow() const
541 return const_cast<PopupContainerClient*>(m_client);
544 IntPoint PopupContainer::convertChildToSelf(const Widget* child, const IntPoint& point) const
546 IntPoint newPoint = point;
547 newPoint.moveBy(child->location());
551 IntPoint PopupContainer::convertSelfToChild(const Widget* child, const IntPoint& point) const
553 IntPoint newPoint = point;
554 newPoint.moveBy(-child->location());