Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/xul/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 25 kB image not shown  

Quelle  XULButtonElement.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "XULButtonElement.h"
#include "XULMenuParentElement.h"
#include "XULPopupElement.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/NameSpaceConstants.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/XULMenuBarElement.h"
#include "nsGkAtoms.h"
#include "nsITimer.h"
#include "nsLayoutUtils.h"
#include "nsCaseTreatment.h"
#include "nsChangeHint.h"
#include "nsMenuPopupFrame.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
#include "nsXULPopupManager.h"
#include "nsIDOMXULButtonElement.h"
#include "nsISound.h"

namespace mozilla::dom {

XULButtonElement::XULButtonElement(
    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
    : nsXULElement(std::move(aNodeInfo)),
      mIsAlwaysMenu(IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menulist,
                                       nsGkAtoms::menuitem)) {}

XULButtonElement::~XULButtonElement() {
  StopBlinking();
  KillMenuOpenTimer();
}

nsChangeHint XULButtonElement::GetAttributeChangeHint(const nsAtom* aAttribute,
                                                      int32_t aModType) const {
  if (aAttribute == nsGkAtoms::type &&
      IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton)) {
    // type=menu switches to a menu frame.
    return nsChangeHint_ReconstructFrame;
  }
  return nsXULElement::GetAttributeChangeHint(aAttribute, aModType);
}

// This global flag is used to record the timestamp when a menu was opened or
// closed and is used to ignore the mousemove and mouseup events that would fire
// on the menu after the mousedown occurred.
static TimeStamp gMenuJustOpenedOrClosedTime = TimeStamp();

void XULButtonElement::PopupOpened() {
  if (!IsMenu()) {
    return;
  }
  gMenuJustOpenedOrClosedTime = TimeStamp::Now();
  SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"true"_ns, true);
}

void XULButtonElement::PopupClosed(bool aDeselectMenu) {
  if (!IsMenu()) {
    return;
  }
  nsContentUtils::AddScriptRunner(
      new nsUnsetAttrRunnable(this, nsGkAtoms::open));

  if (aDeselectMenu) {
    if (RefPtr<XULMenuParentElement> parent = GetMenuParent()) {
      if (parent->GetActiveMenuChild() == this) {
        parent->SetActiveMenuChild(nullptr);
      }
    }
  }
}

bool XULButtonElement::IsMenuActive() const {
  if (XULMenuParentElement* menu = GetMenuParent()) {
    return menu->GetActiveMenuChild() == this;
  }
  return false;
}

void XULButtonElement::HandleEnterKeyPress(WidgetEvent& aEvent) {
  if (IsDisabled()) {
#ifdef XP_WIN
    if (XULPopupElement* popup = GetContainingPopupElement()) {
      if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
        pm->HidePopup(
            popup, {HidePopupOption::HideChain, HidePopupOption::DeselectMenu,
                    HidePopupOption::Async});
      }
    }
#endif
    return;
  }
  if (IsMenuPopupOpen()) {
    return;
  }
  // The enter key press applies to us.
  if (IsMenuItem()) {
    ExecuteMenu(aEvent);
  } else {
    OpenMenuPopup(true);
  }
}

bool XULButtonElement::IsMenuPopupOpen() {
  nsMenuPopupFrame* popupFrame = GetMenuPopup(FlushType::None);
  return popupFrame && popupFrame->IsOpen();
}

bool XULButtonElement::IsOnMenu() const {
  auto* popup = XULPopupElement::FromNodeOrNull(GetMenuParent());
  return popup && popup->IsMenu();
}

bool XULButtonElement::IsOnMenuList() const {
  if (XULMenuParentElement* menu = GetMenuParent()) {
    return menu->GetParent() &&
           menu->GetParent()->IsXULElement(nsGkAtoms::menulist);
  }
  return false;
}

bool XULButtonElement::IsOnMenuBar() const {
  if (XULMenuParentElement* menu = GetMenuParent()) {
    return menu->IsMenuBar();
  }
  return false;
}

nsMenuPopupFrame* XULButtonElement::GetContainingPopupWithoutFlushing() const {
  if (XULPopupElement* popup = GetContainingPopupElement()) {
    return do_QueryFrame(popup->GetPrimaryFrame());
  }
  return nullptr;
}

XULPopupElement* XULButtonElement::GetContainingPopupElement() const {
  return XULPopupElement::FromNodeOrNull(GetMenuParent());
}

bool XULButtonElement::IsOnContextMenu() const {
  if (nsMenuPopupFrame* popup = GetContainingPopupWithoutFlushing()) {
    return popup->IsContextMenu();
  }
  return false;
}

void XULButtonElement::ToggleMenuState() {
  if (IsMenuPopupOpen()) {
    CloseMenuPopup(false);
  } else {
    OpenMenuPopup(false);
  }
}

void XULButtonElement::KillMenuOpenTimer() {
  if (mMenuOpenTimer) {
    mMenuOpenTimer->Cancel();
    mMenuOpenTimer = nullptr;
  }
}

void XULButtonElement::OpenMenuPopup(bool aSelectFirstItem) {
  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  if (!pm) {
    return;
  }

  pm->KillMenuTimer();
  if (!pm->MayShowMenu(this)) {
    return;
  }

  if (RefPtr<XULMenuParentElement> parent = GetMenuParent()) {
    parent->SetActiveMenuChild(this);
  }

  // Open the menu asynchronously.
  OwnerDoc()->Dispatch(NS_NewRunnableFunction(
      "AsyncOpenMenu", [self = RefPtr{this}, aSelectFirstItem] {
        if (self->GetMenuParent() && !self->IsMenuActive()) {
          return;
        }
        if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
          pm->ShowMenu(self, aSelectFirstItem);
        }
      }));
}

void XULButtonElement::CloseMenuPopup(bool aDeselectMenu) {
  gMenuJustOpenedOrClosedTime = TimeStamp::Now();
  // Close the menu asynchronously
  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  if (!pm) {
    return;
  }
  if (auto* popup = GetMenuPopupContent()) {
    HidePopupOptions options{HidePopupOption::Async};
    if (aDeselectMenu) {
      options += HidePopupOption::DeselectMenu;
    }
    pm->HidePopup(popup, options);
  }
}

int32_t XULButtonElement::MenuOpenCloseDelay() const {
  if (IsOnMenuBar()) {
    return 0;
  }
  return LookAndFeel::GetInt(LookAndFeel::IntID::SubmenuDelay, 300);  // ms
}

void XULButtonElement::ExecuteMenu(Modifiers aModifiers, int16_t aButton,
                                   bool aIsTrusted) {
  MOZ_ASSERT(IsMenu());

  StopBlinking();

  auto menuType = GetMenuType();
  if (NS_WARN_IF(!menuType)) {
    return;
  }

  // Because the command event is firing asynchronously, a flag is needed to
  // indicate whether user input is being handled. This ensures that a popup
  // window won't get blocked.
  const bool userinput = dom::UserActivation::IsHandlingUserInput();

  // Flip "checked" state if we're a checkbox menu, or an un-checked radio menu.
  bool needToFlipChecked = false;
  if (*menuType == MenuType::Checkbox ||
      (*menuType == MenuType::Radio && !GetXULBoolAttr(nsGkAtoms::checked))) {
    needToFlipChecked = !AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
                                     nsGkAtoms::_false, eCaseMatters);
  }

  mDelayedMenuCommandEvent = new nsXULMenuCommandEvent(
      this, aIsTrusted, aModifiers, userinput, needToFlipChecked, aButton);
  StartBlinking();
}

void XULButtonElement::StopBlinking() {
  if (mMenuBlinkTimer) {
    if (auto* parent = GetMenuParent()) {
      parent->LockMenuUntilClosed(false);
    }
    mMenuBlinkTimer->Cancel();
    mMenuBlinkTimer = nullptr;
  }
  mDelayedMenuCommandEvent = nullptr;
}

void XULButtonElement::PassMenuCommandEventToPopupManager() {
  if (mDelayedMenuCommandEvent) {
    if (RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance()) {
      RefPtr<nsXULMenuCommandEvent> event = std::move(mDelayedMenuCommandEvent);
      nsCOMPtr<nsIContent> content = this;
      pm->ExecuteMenu(content, event);
    }
  }
  mDelayedMenuCommandEvent = nullptr;
}

static constexpr int32_t kBlinkDelay = 67;  // milliseconds

void XULButtonElement::StartBlinking() {
  if (!LookAndFeel::GetInt(LookAndFeel::IntID::ChosenMenuItemsShouldBlink)) {
    PassMenuCommandEventToPopupManager();
    return;
  }

  // Blink off.
  UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
  if (auto* parent = GetMenuParent()) {
    // Make this menu ignore events from now on.
    parent->LockMenuUntilClosed(true);
  }

  // Set up a timer to blink back on.
  NS_NewTimerWithFuncCallback(
      getter_AddRefs(mMenuBlinkTimer),
      [](nsITimer*, void* aClosure) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
        RefPtr self = static_cast<XULButtonElement*>(aClosure);
        if (auto* parent = self->GetMenuParent()) {
          if (parent->GetActiveMenuChild() == self) {
            // Restore the highlighting if we're still the active item.
            self->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, u"true"_ns,
                          true);
          }
        }
        // Reuse our timer to actually execute.
        self->mMenuBlinkTimer->InitWithNamedFuncCallback(
            [](nsITimer*, void* aClosure) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
              RefPtr self = static_cast<XULButtonElement*>(aClosure);
              if (auto* parent = self->GetMenuParent()) {
                parent->LockMenuUntilClosed(false);
              }
              self->PassMenuCommandEventToPopupManager();
              self->StopBlinking();
            },
            aClosure, kBlinkDelay, nsITimer::TYPE_ONE_SHOT,
            "XULButtonElement::ContinueBlinking");
      },
      this, kBlinkDelay, nsITimer::TYPE_ONE_SHOT,
      "XULButtonElement::StartBlinking", GetMainThreadSerialEventTarget());
}

void XULButtonElement::UnbindFromTree(UnbindContext& aContext) {
  StopBlinking();
  nsXULElement::UnbindFromTree(aContext);
}

void XULButtonElement::ExecuteMenu(WidgetEvent& aEvent) {
  MOZ_ASSERT(IsMenu());
  if (nsCOMPtr<nsISound> sound = do_GetService("@mozilla.org/sound;1")) {
    sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE);
  }

  Modifiers modifiers = 0;
  if (WidgetInputEvent* inputEvent = aEvent.AsInputEvent()) {
    modifiers = inputEvent->mModifiers;
  }

  int16_t button = 0;
  if (WidgetMouseEventBase* mouseEvent = aEvent.AsMouseEventBase()) {
    button = mouseEvent->mButton;
  }

  ExecuteMenu(modifiers, button, aEvent.IsTrusted());
}

void XULButtonElement::PostHandleEventForMenus(
    EventChainPostVisitor& aVisitor) {
  auto* event = aVisitor.mEvent;

  if (event->mOriginalTarget != this) {
    return;
  }

  if (auto* parent = GetMenuParent()) {
    if (NS_WARN_IF(parent->IsLocked())) {
      return;
    }
  }

  // If a menu just opened, ignore the mouseup event that might occur after a
  // the mousedown event that opened it. However, if a different mousedown event
  // occurs, just clear this flag.
  if (!gMenuJustOpenedOrClosedTime.IsNull()) {
    if (event->mMessage == eMouseDown) {
      gMenuJustOpenedOrClosedTime = TimeStamp();
    } else if (event->mMessage == eMouseUp) {
      return;
    }
  }

  if (event->mMessage == eKeyPress && !IsDisabled()) {
    WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
    uint32_t keyCode = keyEvent->mKeyCode;
#ifdef XP_MACOSX
    // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
    if (!IsMenuPopupOpen() &&
        ((keyEvent->mCharCode == ' ' && !keyEvent->IsMeta()) ||
         (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) {
      // When pressing space, don't open the menu if performing an incremental
      // search.
      if (keyEvent->mCharCode != ' ' ||
          !nsMenuPopupFrame::IsWithinIncrementalTime(keyEvent->mTimeStamp)) {
        aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
        OpenMenuPopup(false);
      }
    }
#else
    // On other platforms, toggle menulist on unmodified F4 or Alt arrow
    if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) ||
        ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) {
      aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
      ToggleMenuState();
    }
#endif
  } else if (event->mMessage == eMouseDown &&
             event->AsMouseEvent()->mButton == MouseButton::ePrimary &&
#ifdef XP_MACOSX
             // On mac, ctrl-click will send a context menu event from the
             // widget, so we don't want to bring up the menu.
             !event->AsMouseEvent()->IsControl() &&
#endif
             !IsDisabled() && !IsMenuItem()) {
    // The menu item was selected. Bring up the menu.
    // We have children.
    // Don't prevent the default action here, since that will also cancel
    // potential drag starts.
    if (!IsOnMenu()) {
      ToggleMenuState();
    } else if (!IsMenuPopupOpen()) {
      OpenMenuPopup(false);
    }
  } else if (event->mMessage == eMouseUp && IsMenuItem() && !IsDisabled() &&
             !event->mFlags.mMultipleActionsPrevented) {
    // We accept left and middle clicks on all menu items to activate the item.
    // On context menus we also accept right click to activate the item, because
    // right-clicking on an item in a context menu cannot open another context
    // menu.
    bool isMacCtrlClick = false;
#ifdef XP_MACOSX
    isMacCtrlClick = event->AsMouseEvent()->mButton == MouseButton::ePrimary &&
                     event->AsMouseEvent()->IsControl();
#endif
    bool clickMightOpenContextMenu =
        event->AsMouseEvent()->mButton == MouseButton::eSecondary ||
        isMacCtrlClick;
    if (!clickMightOpenContextMenu || IsOnContextMenu()) {
      aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
      ExecuteMenu(*event);
    }
  } else if (event->mMessage == eContextMenu && IsOnContextMenu() &&
             !IsMenuItem() && !IsDisabled()) {
    // Make sure we cancel default processing of the context menu event so
    // that it doesn't bubble and get seen again by the popuplistener and show
    // another context menu.
    aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
  } else if (event->mMessage == eMouseOut) {
    KillMenuOpenTimer();
    if (RefPtr<XULMenuParentElement> parent = GetMenuParent()) {
      if (parent->GetActiveMenuChild() == this) {
        // Deactivate the menu on mouse out in some cases...
        const bool shouldDeactivate = [&] {
          if (IsMenuPopupOpen()) {
            // If we're open we never deselect. PopupClosed will do as needed.
            return false;
          }
          if (auto* menubar = XULMenuBarElement::FromNode(*parent)) {
            // De-select when exiting a menubar item, if the menubar wasn't
            // activated by keyboard.
            return !menubar->IsActiveByKeyboard();
          }
          if (IsOnMenuList()) {
            // Don't de-select if on a menu-list. That matches Chromium and our
            // historical Windows behavior, see bug 1197913.
            return false;
          }
          // De-select elsewhere.
          return true;
        }();

        if (shouldDeactivate) {
          parent->SetActiveMenuChild(nullptr);
        }
      }
    }
  } else if (event->mMessage == eMouseMove && (IsOnMenu() || IsOnMenuBar())) {
    // Use a tolerance to address situations where a user might perform a
    // "wiggly" click that is accompanied by near-simultaneous mousemove events.
    const TimeDuration kTolerance = TimeDuration::FromMilliseconds(200);
    if (!gMenuJustOpenedOrClosedTime.IsNull() &&
        gMenuJustOpenedOrClosedTime + kTolerance < TimeStamp::Now()) {
      gMenuJustOpenedOrClosedTime = TimeStamp();
      return;
    }

    if (IsDisabled() && IsOnMenuList()) {
      return;
    }

    RefPtr<XULMenuParentElement> parent = GetMenuParent();
    MOZ_ASSERT(parent, "How did IsOnMenu{,Bar} return true then?");

    const bool isOnOpenMenubar =
        parent->IsMenuBar() && parent->GetActiveMenuChild() &&
        parent->GetActiveMenuChild()->IsMenuPopupOpen();

    parent->SetActiveMenuChild(this);

    // We need to check if we really became the current menu item or not.
    if (!IsMenuActive()) {
      // We didn't (presumably because a context menu was active)
      return;
    }
    if (IsDisabled() || IsMenuItem() || IsMenuPopupOpen() || mMenuOpenTimer) {
      // Disabled, or already opening or what not.
      return;
    }

    if (parent->IsMenuBar() && !isOnOpenMenubar) {
      // We should only open on hover in the menubar iff the menubar is open
      // already.
      return;
    }

    // A timer is used so that it doesn't open if the user moves the mouse
    // quickly past the menu. The MenuOpenCloseDelay ensures that only menus
    // have this behaviour.
    NS_NewTimerWithFuncCallback(
        getter_AddRefs(mMenuOpenTimer),
        [](nsITimer*, void* aClosure) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
          RefPtr self = static_cast<XULButtonElement*>(aClosure);
          self->mMenuOpenTimer = nullptr;
          if (self->IsMenuPopupOpen()) {
            return;
          }
          // make sure we didn't open a context menu in the meantime
          // (i.e. the user right-clicked while hovering over a submenu).
          nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
          if (!pm) {
            return;
          }
          if (pm->HasContextMenu(nullptr) && !self->IsOnContextMenu()) {
            return;
          }
          if (!self->IsMenuActive()) {
            return;
          }
          self->OpenMenuPopup(false);
        },
        this, MenuOpenCloseDelay(), nsITimer::TYPE_ONE_SHOT,
        "XULButtonElement::OpenMenu", GetMainThreadSerialEventTarget());
  }
}

nsresult XULButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
  if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
    return nsXULElement::PostHandleEvent(aVisitor);
  }

  if (IsMenu()) {
    PostHandleEventForMenus(aVisitor);
    return nsXULElement::PostHandleEvent(aVisitor);
  }

  auto* event = aVisitor.mEvent;
  switch (event->mMessage) {
    case eBlur: {
      Blurred();
      break;
    }
    case eKeyDown: {
      WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
      if (!keyEvent) {
        break;
      }
      if (keyEvent->ShouldWorkAsSpaceKey() && aVisitor.mPresContext) {
        EventStateManager* esm = aVisitor.mPresContext->EventStateManager();
        // :hover:active state
        esm->SetContentState(this, ElementState::HOVER);
        esm->SetContentState(this, ElementState::ACTIVE);
        mIsHandlingKeyEvent = true;
      }
      break;
    }

// On mac, Return fires the default button, not the focused one.
#ifndef XP_MACOSX
    case eKeyPress: {
      WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
      if (!keyEvent) {
        break;
      }
      if (NS_VK_RETURN == keyEvent->mKeyCode) {
        if (RefPtr<nsIDOMXULButtonElement> button = AsXULButton()) {
          if (OnPointerClicked(*keyEvent)) {
            aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
          }
        }
      }
      break;
    }
#endif

    case eKeyUp: {
      WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
      if (!keyEvent) {
        break;
      }
      if (keyEvent->ShouldWorkAsSpaceKey()) {
        mIsHandlingKeyEvent = false;
        ElementState buttonState = State();
        if (buttonState.HasAllStates(ElementState::ACTIVE |
                                     ElementState::HOVER) &&
            aVisitor.mPresContext) {
          // return to normal state
          EventStateManager* esm = aVisitor.mPresContext->EventStateManager();
          esm->SetContentState(nullptr, ElementState::ACTIVE);
          esm->SetContentState(nullptr, ElementState::HOVER);
          if (OnPointerClicked(*keyEvent)) {
            aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
          }
        }
      }
      break;
    }

    case ePointerClick: {
      WidgetMouseEvent* mouseEvent = event->AsMouseEvent();
      if (mouseEvent->IsLeftClickEvent()) {
        if (OnPointerClicked(*mouseEvent)) {
          aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
        }
      }
      break;
    }

    default:
      break;
  }

  return nsXULElement::PostHandleEvent(aVisitor);
}

void XULButtonElement::Blurred() {
  ElementState buttonState = State();
  if (mIsHandlingKeyEvent &&
      buttonState.HasAllStates(ElementState::ACTIVE | ElementState::HOVER)) {
    // Return to normal state
    if (nsPresContext* pc = OwnerDoc()->GetPresContext()) {
      EventStateManager* esm = pc->EventStateManager();
      esm->SetContentState(nullptr, ElementState::ACTIVE);
      esm->SetContentState(nullptr, ElementState::HOVER);
    }
  }
  mIsHandlingKeyEvent = false;
}

bool XULButtonElement::OnPointerClicked(WidgetGUIEvent& aEvent) {
  // Don't execute if we're disabled.
  if (IsDisabled() || !IsInComposedDoc()) {
    return false;
  }

  // Have the content handle the event, propagating it according to normal DOM
  // rules.
  RefPtr<mozilla::PresShell> presShell = OwnerDoc()->GetPresShell();
  if (!presShell) {
    return false;
  }

  // Execute the oncommand event handler.
  WidgetInputEvent* inputEvent = aEvent.AsInputEvent();
  WidgetMouseEventBase* mouseEvent = aEvent.AsMouseEventBase();
  WidgetKeyboardEvent* keyEvent = aEvent.AsKeyboardEvent();
  // TODO: Set aSourceEvent?
  nsContentUtils::DispatchXULCommand(
      this, aEvent.IsTrusted(), /* aSourceEvent = */ nullptr, presShell,
      inputEvent->IsControl(), inputEvent->IsAlt(), inputEvent->IsShift(),
      inputEvent->IsMeta(),
      mouseEvent ? mouseEvent->mInputSource
                 : (keyEvent ? MouseEvent_Binding::MOZ_SOURCE_KEYBOARD
                             : MouseEvent_Binding::MOZ_SOURCE_UNKNOWN),
      mouseEvent ? mouseEvent->mButton : 0);
  return true;
}

bool XULButtonElement::IsMenu() const {
  if (mIsAlwaysMenu) {
    return true;
  }
  return IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton) &&
         AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::menu,
                     eCaseMatters);
}

void XULButtonElement::UncheckRadioSiblings() {
  MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
  MOZ_ASSERT(GetMenuType() == Some(MenuType::Radio));
  nsAutoString groupName;
  GetAttr(nsGkAtoms::name, groupName);

  nsIContent* parent = GetParent();
  if (!parent) {
    return;
  }

  auto ShouldUncheck = [&](const nsIContent& aSibling) {
    const auto* button = XULButtonElement::FromNode(aSibling);
    if (!button || button->GetMenuType() != Some(MenuType::Radio)) {
      return false;
    }
    if (const auto* attr = button->GetParsedAttr(nsGkAtoms::name)) {
      if (!attr->Equals(groupName, eCaseMatters)) {
        return false;
      }
    } else if (!groupName.IsEmpty()) {
      return false;
    }
    // we're in the same group, only uncheck if we're checked (for some reason,
    // some tests rely on that specifically).
    return button->GetXULBoolAttr(nsGkAtoms::checked);
  };

  for (nsIContent* child = parent->GetFirstChild(); child;
       child = child->GetNextSibling()) {
    if (child == this || !ShouldUncheck(*child)) {
      continue;
    }
    child->AsElement()->UnsetAttr(nsGkAtoms::checked, IgnoreErrors());
  }
}

void XULButtonElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
                                    const nsAttrValue* aValue,
                                    const nsAttrValue* aOldValue,
                                    nsIPrincipal* aSubjectPrincipal,
                                    bool aNotify) {
  nsXULElement::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue,
                             aSubjectPrincipal, aNotify);
  if (IsAlwaysMenu() && aNamespaceID == kNameSpaceID_None) {
    // We need to uncheck radio siblings when we're a checked radio and switch
    // groups, or become checked.
    const bool shouldUncheckSiblings = [&] {
      if (aName == nsGkAtoms::type || aName == nsGkAtoms::name) {
        return *GetMenuType() == MenuType::Radio &&
               GetXULBoolAttr(nsGkAtoms::checked);
      }
      if (aName == nsGkAtoms::checked && aValue &&
          aValue->Equals(nsGkAtoms::_true, eCaseMatters)) {
        return *GetMenuType() == MenuType::Radio;
      }
      return false;
    }();
    if (shouldUncheckSiblings) {
      UncheckRadioSiblings();
    }
  }
}

auto XULButtonElement::GetMenuType() const -> Maybe<MenuType> {
  if (!IsAlwaysMenu()) {
    return Nothing();
  }

  static Element::AttrValuesArray values[] = {nsGkAtoms::checkbox,
                                              nsGkAtoms::radio, nullptr};
  switch (FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, values,
                          eCaseMatters)) {
    case 0:
      return Some(MenuType::Checkbox);
    case 1:
      return Some(MenuType::Radio);
    default:
      return Some(MenuType::Normal);
  }
}

XULMenuBarElement* XULButtonElement::GetMenuBar() const {
  if (!IsMenu()) {
    return nullptr;
  }
  return FirstAncestorOfType<XULMenuBarElement>();
}

XULMenuParentElement* XULButtonElement::GetMenuParent() const {
  if (IsXULElement(nsGkAtoms::menulist)) {
    return nullptr;
  }
  return FirstAncestorOfType<XULMenuParentElement>();
}

XULPopupElement* XULButtonElement::GetMenuPopupContent() const {
  if (!IsMenu()) {
    return nullptr;
  }
  for (auto* child = GetFirstChild(); child; child = child->GetNextSibling()) {
    if (auto* popup = XULPopupElement::FromNode(child)) {
      return popup;
    }
  }
  return nullptr;
}

nsMenuPopupFrame* XULButtonElement::GetMenuPopupWithoutFlushing() const {
  return const_cast<XULButtonElement*>(this)->GetMenuPopup(FlushType::None);
}

nsMenuPopupFrame* XULButtonElement::GetMenuPopup(FlushType aFlushType) {
  RefPtr popup = GetMenuPopupContent();
  if (!popup) {
    return nullptr;
  }
  return do_QueryFrame(popup->GetPrimaryFrame(aFlushType));
}

bool XULButtonElement::OpenedWithKey() const {
  auto* menubar = GetMenuBar();
  return menubar && menubar->IsActiveByKeyboard();
}

}  // namespace mozilla::dom

Messung V0.5
C=90 H=94 G=91

¤ Dauer der Verarbeitung: 0.4 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.