XULPopupElement.cpp (11231B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/XULPopupElement.h" 8 9 #include "XULMenuParentElement.h" 10 #include "mozilla/AppUnits.h" 11 #include "mozilla/AsyncEventDispatcher.h" 12 #include "mozilla/dom/DOMRect.h" 13 #include "mozilla/dom/Document.h" 14 #include "mozilla/dom/Element.h" 15 #include "mozilla/dom/Event.h" 16 #include "mozilla/dom/XULButtonElement.h" 17 #include "mozilla/dom/XULMenuElement.h" 18 #include "mozilla/dom/XULPopupElementBinding.h" 19 #include "nsCOMPtr.h" 20 #include "nsDOMCSSDeclaration.h" 21 #include "nsGkAtoms.h" 22 #include "nsIContent.h" 23 #include "nsMenuPopupFrame.h" 24 #include "nsNameSpaceManager.h" 25 #include "nsStringFwd.h" 26 #ifdef MOZ_WAYLAND 27 # include "mozilla/WidgetUtilsGtk.h" 28 #endif 29 30 namespace mozilla::dom { 31 32 nsXULElement* NS_NewXULPopupElement( 33 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) { 34 RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); 35 auto* nim = nodeInfo->NodeInfoManager(); 36 return new (nim) XULPopupElement(nodeInfo.forget()); 37 } 38 39 JSObject* XULPopupElement::WrapNode(JSContext* aCx, 40 JS::Handle<JSObject*> aGivenProto) { 41 return XULPopupElement_Binding::Wrap(aCx, this, aGivenProto); 42 } 43 44 nsMenuPopupFrame* XULPopupElement::GetFrame(FlushType aFlushType) { 45 nsIFrame* f = GetPrimaryFrame(aFlushType); 46 MOZ_ASSERT(!f || f->IsMenuPopupFrame()); 47 return static_cast<nsMenuPopupFrame*>(f); 48 } 49 50 void XULPopupElement::OpenPopup(Element* aAnchorElement, 51 const StringOrOpenPopupOptions& aOptions, 52 int32_t aXPos, int32_t aYPos, 53 bool aIsContextMenu, bool aAttributesOverride, 54 Event* aTriggerEvent) { 55 nsAutoString position; 56 if (aOptions.IsOpenPopupOptions()) { 57 const OpenPopupOptions& options = aOptions.GetAsOpenPopupOptions(); 58 position = options.mPosition; 59 aXPos = options.mX; 60 aYPos = options.mY; 61 aIsContextMenu = options.mIsContextMenu; 62 aAttributesOverride = options.mAttributesOverride; 63 aTriggerEvent = options.mTriggerEvent; 64 } else { 65 position = aOptions.GetAsString(); 66 } 67 68 nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 69 if (!pm) { 70 return; 71 } 72 // As a special case for popups that are menus when no anchor or position 73 // are specified, open the popup with ShowMenu instead of ShowPopup so that 74 // the popup is aligned with the menu. 75 if (!aAnchorElement && position.IsEmpty() && GetPrimaryFrame()) { 76 if (auto* menu = GetContainingMenu()) { 77 pm->ShowMenu(menu, false); 78 return; 79 } 80 } 81 pm->ShowPopup(this, aAnchorElement, position, aXPos, aYPos, aIsContextMenu, 82 aAttributesOverride, false, aTriggerEvent); 83 } 84 85 void XULPopupElement::OpenPopupAtScreen(int32_t aXPos, int32_t aYPos, 86 bool aIsContextMenu, 87 Event* aTriggerEvent) { 88 nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 89 if (pm) { 90 pm->ShowPopupAtScreen(this, aXPos, aYPos, aIsContextMenu, aTriggerEvent); 91 } 92 } 93 94 void XULPopupElement::OpenPopupAtScreenRect(const nsAString& aPosition, 95 int32_t aXPos, int32_t aYPos, 96 int32_t aWidth, int32_t aHeight, 97 bool aIsContextMenu, 98 bool aAttributesOverride, 99 Event* aTriggerEvent) { 100 nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 101 if (pm) { 102 pm->ShowPopupAtScreenRect( 103 this, aPosition, nsIntRect(aXPos, aYPos, aWidth, aHeight), 104 aIsContextMenu, aAttributesOverride, aTriggerEvent); 105 } 106 } 107 108 void XULPopupElement::HidePopup(bool aCancel) { 109 nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 110 if (!pm) { 111 return; 112 } 113 HidePopupOptions options{HidePopupOption::DeselectMenu}; 114 if (aCancel) { 115 options += HidePopupOption::IsRollup; 116 } 117 pm->HidePopup(this, options); 118 } 119 120 static Modifiers ConvertModifiers(const ActivateMenuItemOptions& aModifiers) { 121 Modifiers modifiers = 0; 122 if (aModifiers.mCtrlKey) { 123 modifiers |= MODIFIER_CONTROL; 124 } 125 if (aModifiers.mAltKey) { 126 modifiers |= MODIFIER_ALT; 127 } 128 if (aModifiers.mShiftKey) { 129 modifiers |= MODIFIER_SHIFT; 130 } 131 if (aModifiers.mMetaKey) { 132 modifiers |= MODIFIER_META; 133 } 134 return modifiers; 135 } 136 137 void XULPopupElement::PopupOpened(bool aSelectFirstItem) { 138 if (aSelectFirstItem) { 139 SelectFirstItem(); 140 } 141 if (RefPtr button = GetContainingMenu()) { 142 if (RefPtr parent = button->GetMenuParent()) { 143 parent->SetActiveMenuChild(button); 144 } 145 } 146 } 147 148 void XULPopupElement::PopupClosed(bool aDeselectMenu) { 149 LockMenuUntilClosed(false); 150 SetActiveMenuChild(nullptr); 151 auto dispatcher = MakeRefPtr<AsyncEventDispatcher>( 152 this, u"DOMMenuInactive"_ns, CanBubble::eYes, ChromeOnlyDispatch::eNo); 153 dispatcher->PostDOMEvent(); 154 if (RefPtr button = GetContainingMenu()) { 155 button->PopupClosed(aDeselectMenu); 156 } 157 } 158 159 void XULPopupElement::ActivateItem(Element& aItemElement, 160 const ActivateMenuItemOptions& aOptions, 161 ErrorResult& aRv) { 162 if (!Contains(&aItemElement)) { 163 return aRv.ThrowInvalidStateError("Menu item is not inside this menu."); 164 } 165 166 Modifiers modifiers = ConvertModifiers(aOptions); 167 168 // First, check if a native menu is open, and activate the item in it. 169 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { 170 if (pm->ActivateNativeMenuItem(&aItemElement, modifiers, aOptions.mButton, 171 aRv)) { 172 return; 173 } 174 } 175 176 auto* item = XULButtonElement::FromNode(aItemElement); 177 if (!item || !item->IsMenu()) { 178 return aRv.ThrowInvalidStateError("Not a menu item"); 179 } 180 181 if (!item->GetPrimaryFrame(FlushType::Frames)) { 182 return aRv.ThrowInvalidStateError("Menu item is hidden"); 183 } 184 185 auto* popup = item->GetContainingPopupElement(); 186 if (!popup) { 187 return aRv.ThrowInvalidStateError("No popup"); 188 } 189 190 nsMenuPopupFrame* frame = popup->GetFrame(FlushType::None); 191 if (!frame || !frame->IsOpen()) { 192 return aRv.ThrowInvalidStateError("Popup is not open"); 193 } 194 195 // This is a chrome-only API, so we're trusted. 196 const bool trusted = true; 197 // KnownLive because item is aItemElement. 198 MOZ_KnownLive(item)->ExecuteMenu(modifiers, aOptions.mButton, trusted); 199 } 200 201 void XULPopupElement::MoveTo(int32_t aLeft, int32_t aTop) { 202 if (nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame())) { 203 menuPopupFrame->MoveTo(CSSIntPoint(aLeft, aTop), true); 204 } 205 } 206 207 void XULPopupElement::MoveToAnchor(Element* aAnchorElement, 208 const nsAString& aPosition, int32_t aXPos, 209 int32_t aYPos, bool aAttributesOverride) { 210 nsMenuPopupFrame* menuPopupFrame = GetFrame(FlushType::None); 211 if (menuPopupFrame && menuPopupFrame->IsVisibleOrShowing()) { 212 menuPopupFrame->MoveToAnchor(aAnchorElement, aPosition, aXPos, aYPos, 213 aAttributesOverride); 214 } 215 } 216 217 void XULPopupElement::SizeTo(int32_t aWidth, int32_t aHeight) { 218 nsAutoCString width; 219 nsAutoCString height; 220 width.AppendInt(aWidth); 221 width.AppendLiteral("px"); 222 height.AppendInt(aHeight); 223 height.AppendLiteral("px"); 224 225 nsCOMPtr<nsDOMCSSDeclaration> style = Style(); 226 style->SetProperty("width"_ns, width, ""_ns, IgnoreErrors()); 227 style->SetProperty("height"_ns, height, ""_ns, IgnoreErrors()); 228 229 // If the popup is open, force a reposition of the popup after resizing it 230 // with notifications set to true so that the popuppositioned event is fired. 231 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame()); 232 if (menuPopupFrame && menuPopupFrame->PopupState() == ePopupShown) { 233 menuPopupFrame->SetPopupPosition(false); 234 } 235 } 236 237 void XULPopupElement::GetState(nsString& aState) { 238 // set this here in case there's no frame for the popup 239 aState.AssignLiteral("closed"); 240 241 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { 242 switch (pm->GetPopupState(this)) { 243 case ePopupShown: 244 aState.AssignLiteral("open"); 245 break; 246 case ePopupShowing: 247 case ePopupPositioning: 248 case ePopupOpening: 249 case ePopupVisible: 250 aState.AssignLiteral("showing"); 251 break; 252 case ePopupHiding: 253 case ePopupInvisible: 254 aState.AssignLiteral("hiding"); 255 break; 256 case ePopupClosed: 257 break; 258 default: 259 MOZ_ASSERT_UNREACHABLE("Bad popup state"); 260 break; 261 } 262 } 263 } 264 265 nsINode* XULPopupElement::GetTriggerNode() const { 266 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame()); 267 return nsMenuPopupFrame::GetTriggerContent(menuPopupFrame); 268 } 269 270 // FIXME(emilio): should probably be renamed to GetAnchorElement? 271 Element* XULPopupElement::GetAnchorNode() const { 272 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetPrimaryFrame()); 273 if (!menuPopupFrame) { 274 return nullptr; 275 } 276 return Element::FromNodeOrNull(menuPopupFrame->GetAnchor()); 277 } 278 279 already_AddRefed<DOMRect> XULPopupElement::GetOuterScreenRect() { 280 RefPtr<DOMRect> rect = new DOMRect(ToSupports(OwnerDoc())); 281 282 // Return an empty rectangle if the popup is not open. 283 nsMenuPopupFrame* menuPopupFrame = 284 do_QueryFrame(GetPrimaryFrame(FlushType::Frames)); 285 if (!menuPopupFrame || !menuPopupFrame->IsOpen()) { 286 return rect.forget(); 287 } 288 289 Maybe<CSSRect> screenRect; 290 291 if (menuPopupFrame->IsNativeMenu()) { 292 // For native menus we can't query the true size. Use the anchor rect 293 // instead, which at least has the position at which we were intending to 294 // open the menu. 295 screenRect = Some(CSSRect(menuPopupFrame->GetScreenAnchorRect())); 296 } else if (nsIWidget* widget = menuPopupFrame->GetWidget()) { 297 // For non-native menus, query the bounds from the widget. 298 screenRect = Some(widget->GetScreenBounds() / 299 menuPopupFrame->PresContext()->CSSToDevPixelScale()); 300 } 301 302 if (screenRect) { 303 rect->SetRect(screenRect->X(), screenRect->Y(), screenRect->Width(), 304 screenRect->Height()); 305 } 306 return rect.forget(); 307 } 308 309 void XULPopupElement::SetConstraintRect(dom::DOMRectReadOnly& aRect) { 310 nsMenuPopupFrame* menuPopupFrame = 311 do_QueryFrame(GetPrimaryFrame(FlushType::Frames)); 312 if (menuPopupFrame) { 313 menuPopupFrame->SetOverrideConstraintRect(CSSIntRect::Truncate( 314 aRect.Left(), aRect.Top(), aRect.Width(), aRect.Height())); 315 } 316 } 317 318 bool XULPopupElement::IsWaylandDragSource() const { 319 #ifdef MOZ_WAYLAND 320 nsMenuPopupFrame* f = do_QueryFrame(GetPrimaryFrame()); 321 return f && f->IsDragSource(); 322 #else 323 return false; 324 #endif 325 } 326 327 bool XULPopupElement::IsWaylandPopup() const { 328 #ifdef MOZ_WAYLAND 329 return widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol(); 330 #else 331 return false; 332 #endif 333 } 334 335 } // namespace mozilla::dom