Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  NativeMenuMac.mm   Sprache: unbekannt

 
/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
/* 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/. */

#import <Cocoa/Cocoa.h>

#include "NativeMenuMac.h"

#include "mozilla/Assertions.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"

#include "MOZMenuOpeningCoordinator.h"
#include "nsISupports.h"
#include "nsGkAtoms.h"
#include "nsMenuGroupOwnerX.h"
#include "nsMenuItemX.h"
#include "nsMenuUtilsX.h"
#include "nsNativeThemeColors.h"
#include "nsObjCExceptions.h"
#include "nsThreadUtils.h"
#include "PresShell.h"
#include "nsCocoaUtils.h"
#include "nsIFrame.h"
#include "nsPresContext.h"
#include "nsDeviceContext.h"

namespace mozilla {

using dom::Element;

namespace widget {

NativeMenuMac::NativeMenuMac(dom::Element* aElement)
    : mElement(aElement), mContainerStatusBarItem(nil) {
  MOZ_RELEASE_ASSERT(
      aElement->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menupopup));
  mMenuGroupOwner = new nsMenuGroupOwnerX(aElement, nullptr);
  mMenu = MakeRefPtr<nsMenuX>(nullptr, mMenuGroupOwner, aElement);
  mMenu->SetObserver(this);
  mMenu->SetIconListener(this);
  mMenu->SetupIcon();
}

NativeMenuMac::~NativeMenuMac() {
  mMenu->DetachFromGroupOwnerRecursive();
  mMenu->ClearObserver();
  mMenu->ClearIconListener();
}

static void UpdateMenu(nsMenuX* aMenu) {
  aMenu->MenuOpened();
  aMenu->MenuClosed();

  uint32_t itemCount = aMenu->GetItemCount();
  for (uint32_t i = 0; i < itemCount; i++) {
    nsMenuX::MenuChild menuObject = *aMenu->GetItemAt(i);
    if (menuObject.is<RefPtr<nsMenuX>>()) {
      UpdateMenu(menuObject.as<RefPtr<nsMenuX>>());
    }
  }
}

void NativeMenuMac::MenuWillOpen() {
  // Force an update on the mMenu by faking an open/close on all of
  // its submenus.
  UpdateMenu(mMenu.get());
}

bool NativeMenuMac::ActivateNativeMenuItemAt(const nsAString& aIndexString) {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  NSMenu* menu = mMenu->NativeNSMenu();

  nsMenuUtilsX::CheckNativeMenuConsistency(menu);

  NSString* locationString =
      [NSString stringWithCharacters:reinterpret_cast<const unichar*>(
                                         aIndexString.BeginReading())
                              length:aIndexString.Length()];
  NSMenuItem* item =
      nsMenuUtilsX::NativeMenuItemWithLocation(menu, locationString, false);

  // We can't perform an action on an item with a submenu, that will raise
  // an obj-c exception.
  if (item && !item.hasSubmenu) {
    NSMenu* parent = item.menu;
    if (parent) {
      // NSLog(@"Performing action for native menu item titled: %@\n",
      //       [[currentSubmenu itemAtIndex:targetIndex] title]);
      mozilla::AutoRestore<bool> autoRestore(
          nsMenuUtilsX::gIsSynchronouslyActivatingNativeMenuItemDuringTest);
      nsMenuUtilsX::gIsSynchronouslyActivatingNativeMenuItemDuringTest = true;
      [parent performActionForItemAtIndex:[parent indexOfItem:item]];
      return true;
    }
  }

  return false;

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

void NativeMenuMac::ForceUpdateNativeMenuAt(const nsAString& aIndexString) {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  NSString* locationString =
      [NSString stringWithCharacters:reinterpret_cast<const unichar*>(
                                         aIndexString.BeginReading())
                              length:aIndexString.Length()];
  NSArray<NSString*>* indexes =
      [locationString componentsSeparatedByString:@"|"];
  RefPtr<nsMenuX> currentMenu = mMenu.get();

  // now find the correct submenu
  unsigned int indexCount = indexes.count;
  for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
    int targetIndex = [indexes objectAtIndex:i].intValue;
    int visible = 0;
    uint32_t length = currentMenu->GetItemCount();
    for (unsigned int j = 0; j < length; j++) {
      Maybe<nsMenuX::MenuChild> targetMenu = currentMenu->GetItemAt(j);
      if (!targetMenu) {
        return;
      }
      RefPtr<nsIContent> content = targetMenu->match(
          [](const RefPtr<nsMenuX>& aMenu) { return aMenu->Content(); },
          [](const RefPtr<nsMenuItemX>& aMenuItem) {
            return aMenuItem->Content();
          });
      if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(content)) {
        visible++;
        if (targetMenu->is<RefPtr<nsMenuX>>() && visible == (targetIndex + 1)) {
          currentMenu = targetMenu->as<RefPtr<nsMenuX>>();
          break;
        }
      }
    }
  }

  // fake open/close to cause lazy update to happen
  currentMenu->MenuOpened();
  currentMenu->MenuClosed();

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

void NativeMenuMac::IconUpdated() {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  if (mContainerStatusBarItem) {
    NSImage* menuImage = mMenu->NativeNSMenuItem().image;
    if (menuImage) {
      [menuImage setTemplate:YES];
    }
    mContainerStatusBarItem.button.image = menuImage;
  }

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

void NativeMenuMac::SetContainerStatusBarItem(NSStatusItem* aItem) {
  mContainerStatusBarItem = aItem;
  IconUpdated();
}

void NativeMenuMac::Dump() {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  mMenu->Dump(0);
  nsMenuUtilsX::DumpNativeMenu(mMenu->NativeNSMenu());

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

void NativeMenuMac::OnMenuWillOpen(dom::Element* aPopupElement) {
  if (aPopupElement == mElement) {
    return;
  }

  // Our caller isn't keeping us alive, so make sure we stay alive throughout
  // this function in case one of the observer notifications destroys us.
  RefPtr<NativeMenuMac> kungFuDeathGrip(this);

  for (NativeMenu::Observer* observer : mObservers.Clone()) {
    observer->OnNativeSubMenuWillOpen(aPopupElement);
  }
}

void NativeMenuMac::OnMenuDidOpen(dom::Element* aPopupElement) {
  // Our caller isn't keeping us alive, so make sure we stay alive throughout
  // this function in case one of the observer notifications destroys us.
  RefPtr<NativeMenuMac> kungFuDeathGrip(this);

  for (NativeMenu::Observer* observer : mObservers.Clone()) {
    if (aPopupElement == mElement) {
      observer->OnNativeMenuOpened();
    } else {
      observer->OnNativeSubMenuDidOpen(aPopupElement);
    }
  }
}

void NativeMenuMac::OnMenuWillActivateItem(dom::Element* aPopupElement,
                                           dom::Element* aMenuItemElement) {
  // Our caller isn't keeping us alive, so make sure we stay alive throughout
  // this function in case one of the observer notifications destroys us.
  RefPtr<NativeMenuMac> kungFuDeathGrip(this);

  for (NativeMenu::Observer* observer : mObservers.Clone()) {
    observer->OnNativeMenuWillActivateItem(aMenuItemElement);
  }
}

void NativeMenuMac::OnMenuClosed(dom::Element* aPopupElement) {
  // Our caller isn't keeping us alive, so make sure we stay alive throughout
  // this function in case one of the observer notifications destroys us.
  RefPtr<NativeMenuMac> kungFuDeathGrip(this);

  for (NativeMenu::Observer* observer : mObservers.Clone()) {
    if (aPopupElement == mElement) {
      observer->OnNativeMenuClosed();
    } else {
      observer->OnNativeSubMenuClosed(aPopupElement);
    }
  }
}

static NSView* NativeViewForFrame(nsIFrame* aFrame) {
  nsIWidget* widget = aFrame->GetNearestWidget();
  return (NSView*)widget->GetNativeData(NS_NATIVE_WIDGET);
}

static NSAppearance* NativeAppearanceForContent(nsIContent* aContent) {
  nsIFrame* f = aContent->GetPrimaryFrame();
  if (!f) {
    return nil;
  }
  return NSAppearanceForColorScheme(LookAndFeel::ColorSchemeForFrame(f));
}

void NativeMenuMac::ShowAsContextMenu(nsIFrame* aClickedFrame,
                                      const CSSIntPoint& aPosition,
                                      bool aIsContextMenu) {
  nsPresContext* pc = aClickedFrame->PresContext();
  auto cssToDesktopScale =
      pc->CSSToDevPixelScale() / pc->DeviceContext()->GetDesktopToDeviceScale();
  const DesktopPoint desktopPoint = aPosition * cssToDesktopScale;

  mMenu->PopupShowingEventWasSentAndApprovedExternally();

  NSMenu* menu = mMenu->NativeNSMenu();
  NSView* view = NativeViewForFrame(aClickedFrame);
  NSAppearance* appearance = NativeAppearanceForContent(mMenu->Content());
  NSPoint locationOnScreen = nsCocoaUtils::GeckoPointToCocoaPoint(desktopPoint);

  // Let the MOZMenuOpeningCoordinator do the actual opening, so that this
  // ShowAsContextMenu call does not spawn a nested event loop, which would be
  // surprising to our callers.
  mOpeningHandle = [MOZMenuOpeningCoordinator.sharedInstance
      asynchronouslyOpenMenu:menu
            atScreenPosition:locationOnScreen
                     forView:view
              withAppearance:appearance
               asContextMenu:aIsContextMenu];
}

bool NativeMenuMac::Close() {
  if (mOpeningHandle) {
    // In case the menu was trying to open, but this Close() call interrupted
    // it, cancel opening.
    [MOZMenuOpeningCoordinator.sharedInstance
        cancelAsynchronousOpening:mOpeningHandle];
  }
  return mMenu->Close();
}

RefPtr<nsMenuX> NativeMenuMac::GetOpenMenuContainingElement(
    dom::Element* aElement) {
  nsTArray<RefPtr<dom::Element>> submenuChain;
  RefPtr<dom::Element> currentElement = aElement->GetParentElement();
  while (currentElement && currentElement != mElement) {
    if (currentElement->IsXULElement(nsGkAtoms::menu)) {
      submenuChain.AppendElement(currentElement);
    }
    currentElement = currentElement->GetParentElement();
  }
  if (!currentElement) {
    // aElement was not a descendent of mElement. Refuse to activate the item.
    return nullptr;
  }

  // Traverse submenuChain from shallow to deep, to find the nsMenuX that
  // contains aElement.
  submenuChain.Reverse();
  RefPtr<nsMenuX> menu = mMenu;
  for (const auto& submenu : submenuChain) {
    if (!menu->IsOpenForGecko()) {
      // Refuse to descend into closed menus.
      return nullptr;
    }
    Maybe<nsMenuX::MenuChild> menuChild = menu->GetItemForElement(submenu);
    if (!menuChild || !menuChild->is<RefPtr<nsMenuX>>()) {
      // Couldn't find submenu.
      return nullptr;
    }
    menu = menuChild->as<RefPtr<nsMenuX>>();
  }

  if (!menu->IsOpenForGecko()) {
    // Refuse to descend into closed menus.
    return nullptr;
  }
  return menu;
}

static NSEventModifierFlags ConvertModifierFlags(Modifiers aModifiers) {
  NSEventModifierFlags flags = 0;
  if (aModifiers & MODIFIER_CONTROL) {
    flags |= NSEventModifierFlagControl;
  }
  if (aModifiers & MODIFIER_ALT) {
    flags |= NSEventModifierFlagOption;
  }
  if (aModifiers & MODIFIER_SHIFT) {
    flags |= NSEventModifierFlagShift;
  }
  if (aModifiers & MODIFIER_META) {
    flags |= NSEventModifierFlagCommand;
  }
  return flags;
}

void NativeMenuMac::ActivateItem(dom::Element* aItemElement,
                                 Modifiers aModifiers, int16_t aButton,
                                 ErrorResult& aRv) {
  RefPtr<nsMenuX> menu = GetOpenMenuContainingElement(aItemElement);
  if (!menu) {
    aRv.ThrowInvalidStateError("Menu containing menu item is not open");
    return;
  }

  nsMenuUtilsX::CheckNativeMenuConsistency(menu->NativeNSMenu());

  Maybe<nsMenuX::MenuChild> child = menu->GetItemForElement(aItemElement);
  if (!child || !child->is<RefPtr<nsMenuItemX>>()) {
    aRv.ThrowInvalidStateError("Could not find the supplied menu item");
    return;
  }

  RefPtr<nsMenuItemX> item = std::move(child->as<RefPtr<nsMenuItemX>>());
  if (!item->IsVisible()) {
    aRv.ThrowInvalidStateError("Menu item is not visible");
    return;
  }

  NSMenuItem* nativeItem = [item->NativeNSMenuItem() retain];

  // First, initiate the closing of the NSMenu.
  // This synchronously calls the menu delegate's menuDidClose handler. So
  // menuDidClose is what runs first; this matches the order of events for
  // user-initiated menu item activation. This call doesn't immediately hide the
  // menu; the menu only hides once the stack unwinds from NSMenu's nested
  // "tracking" event loop.
  [mMenu->NativeNSMenu() cancelTrackingWithoutAnimation];

  // Next, call OnWillActivateItem. This also matches the order of calls that
  // happen when a user activates a menu item in the real world: -[MenuDelegate
  // menu:willActivateItem:] runs after menuDidClose.
  menu->OnWillActivateItem(nativeItem);

  // Finally, call ActivateItemAfterClosing. This also mimics the order in the
  // real world: menuItemHit is called after menu:willActivateItem:.
  menu->ActivateItemAfterClosing(std::move(item),
                                 ConvertModifierFlags(aModifiers), aButton);

  // Tell our native event loop that it should not process any more work before
  // unwinding the stack, so that we can get out of the menu's nested event loop
  // as fast as possible. This was needed to fix spurious failures in tests,
  // where a call to cancelTrackingWithoutAnimation was ignored if more native
  // events were processed before the event loop was exited. As a result, the
  // menu stayed open forever and the test never finished.
  MOZMenuOpeningCoordinator.needToUnwindForMenuClosing = YES;

  [nativeItem release];
}

void NativeMenuMac::OpenSubmenu(dom::Element* aMenuElement) {
  if (RefPtr<nsMenuX> menu = GetOpenMenuContainingElement(aMenuElement)) {
    Maybe<nsMenuX::MenuChild> item = menu->GetItemForElement(aMenuElement);
    if (item && item->is<RefPtr<nsMenuX>>()) {
      item->as<RefPtr<nsMenuX>>()->MenuOpened();
    }
  }
}

void NativeMenuMac::CloseSubmenu(dom::Element* aMenuElement) {
  if (RefPtr<nsMenuX> menu = GetOpenMenuContainingElement(aMenuElement)) {
    Maybe<nsMenuX::MenuChild> item = menu->GetItemForElement(aMenuElement);
    if (item && item->is<RefPtr<nsMenuX>>()) {
      item->as<RefPtr<nsMenuX>>()->MenuClosed();
    }
  }
}

RefPtr<Element> NativeMenuMac::Element() { return mElement; }

}  // namespace widget
}  // namespace mozilla

[ Dauer der Verarbeitung: 0.15 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge