Bug 1351783 part 5 - Add a KeyboardMap type. r=kats,masayuki draft
authorRyan Hunt <rhunt@eqrion.net>
Mon, 05 Jun 2017 18:29:04 -0500
changeset 599269 0f7fa7995035961af8f52e3aa2bb7343c86ad6b9
parent 599268 6e7da49e9459a91cd2cb43bf172ad290c2583a71
child 599270 bd8770f701277340d8fd012007d86dc7aabe47f8
push id65466
push userbmo:rhunt@eqrion.net
push dateThu, 22 Jun 2017 22:16:51 +0000
reviewerskats, masayuki
bugs1351783
milestone56.0a1
Bug 1351783 part 5 - Add a KeyboardMap type. r=kats,masayuki The XBL bindings used for scrolling are managed by a nsXBLWindowKeyHandler. This class loads the handlers and has logic for searching through them to match a keyboard event. This commit adds a KeyboardMap class for storing KeyboardShortcuts and for mirroring the search logic of nsXBLWindowKeyHandler. MozReview-Commit-ID: 5BmFBilKTJy
dom/xbl/nsXBLWindowKeyHandler.cpp
dom/xbl/nsXBLWindowKeyHandler.h
gfx/layers/apz/src/Keyboard.cpp
gfx/layers/apz/src/Keyboard.h
gfx/layers/ipc/LayersMessageUtils.h
--- a/dom/xbl/nsXBLWindowKeyHandler.cpp
+++ b/dom/xbl/nsXBLWindowKeyHandler.cpp
@@ -20,27 +20,31 @@
 #include "nsNetUtil.h"
 #include "nsContentUtils.h"
 #include "nsXBLPrototypeBinding.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocShell.h"
 #include "nsIPresShell.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
+#include "mozilla/Move.h"
 #include "nsISelectionController.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
+#include "mozilla/layers/Keyboard.h"
 #include "nsIEditor.h"
 #include "nsIHTMLEditor.h"
 #include "nsIDOMDocument.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::layers;
 
 class nsXBLSpecialDocInfo : public nsIObserver
 {
 public:
   RefPtr<nsXBLDocumentInfo> mHTMLBindings;
   RefPtr<nsXBLDocumentInfo> mUserHTMLBindings;
 
   static const char sHTMLBindingStr[];
@@ -141,19 +145,28 @@ nsXBLSpecialDocInfo::GetAllHandlers(cons
     GetHandlers(mUserHTMLBindings, type, aUserHandler);
   }
   if (mHTMLBindings) {
     GetHandlers(mHTMLBindings, nsDependentCString(aType), aHandler);
   }
 }
 
 // Init statics
-nsXBLSpecialDocInfo* nsXBLWindowKeyHandler::sXBLSpecialDocInfo = nullptr;
+static StaticRefPtr<nsXBLSpecialDocInfo> sXBLSpecialDocInfo;
 uint32_t nsXBLWindowKeyHandler::sRefCnt = 0;
 
+/* static */ void
+nsXBLWindowKeyHandler::EnsureSpecialDocInfo()
+{
+  if (!sXBLSpecialDocInfo) {
+    sXBLSpecialDocInfo = new nsXBLSpecialDocInfo();
+  }
+  sXBLSpecialDocInfo->LoadDocInfo();
+}
+
 nsXBLWindowKeyHandler::nsXBLWindowKeyHandler(nsIDOMElement* aElement,
                                              EventTarget* aTarget)
   : mTarget(aTarget),
     mHandler(nullptr),
     mUserHandler(nullptr)
 {
   mWeakPtrForElement = do_GetWeakReference(aElement);
   ++sRefCnt;
@@ -162,17 +175,17 @@ nsXBLWindowKeyHandler::nsXBLWindowKeyHan
 nsXBLWindowKeyHandler::~nsXBLWindowKeyHandler()
 {
   // If mWeakPtrForElement is non-null, we created a prototype handler.
   if (mWeakPtrForElement)
     delete mHandler;
 
   --sRefCnt;
   if (!sRefCnt) {
-    NS_IF_RELEASE(sXBLSpecialDocInfo);
+    sXBLSpecialDocInfo = nullptr;
   }
 }
 
 NS_IMPL_ISUPPORTS(nsXBLWindowKeyHandler,
                   nsIDOMEventListener)
 
 static void
 BuildHandlerChain(nsIContent* aContent, nsXBLPrototypeHandler** aResult)
@@ -223,21 +236,17 @@ nsXBLWindowKeyHandler::EnsureHandlers()
   if (el) {
     // We are actually a XUL <keyset>.
     if (mHandler)
       return NS_OK;
 
     nsCOMPtr<nsIContent> content(do_QueryInterface(el));
     BuildHandlerChain(content, &mHandler);
   } else { // We are an XBL file of handlers.
-    if (!sXBLSpecialDocInfo) {
-      sXBLSpecialDocInfo = new nsXBLSpecialDocInfo();
-      NS_ADDREF(sXBLSpecialDocInfo);
-    }
-    sXBLSpecialDocInfo->LoadDocInfo();
+    EnsureSpecialDocInfo();
 
     // Now determine which handlers we should be using.
     if (IsHTMLEditableFieldFocused()) {
       sXBLSpecialDocInfo->GetAllHandlers("editor", &mHandler, &mUserHandler);
     }
     else {
       sXBLSpecialDocInfo->GetAllHandlers("browser", &mHandler, &mUserHandler);
     }
@@ -392,16 +401,51 @@ nsXBLWindowKeyHandler::RemoveKeyboardEve
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("mozkeydownonplugin"),
                            TrustedEventsAtSystemGroupBubble());
   aEventListenerManager->RemoveEventListenerByType(
                            this, NS_LITERAL_STRING("mozkeyuponplugin"),
                            TrustedEventsAtSystemGroupBubble());
 }
 
+/* static */ KeyboardMap
+nsXBLWindowKeyHandler::CollectKeyboardShortcuts()
+{
+  // Load the XBL handlers
+  EnsureSpecialDocInfo();
+
+  nsXBLPrototypeHandler* handlers = nullptr;
+  nsXBLPrototypeHandler* userHandlers = nullptr;
+  sXBLSpecialDocInfo->GetAllHandlers("browser", &handlers, &userHandlers);
+
+  // Convert the handlers into keyboard shortcuts, using an AutoTArray with
+  // the maximum amount of shortcuts used on any platform to minimize allocations
+  AutoTArray<KeyboardShortcut, 46> shortcuts;
+
+  for (nsXBLPrototypeHandler* handler = handlers;
+       handler;
+       handler = handler->GetNextHandler()) {
+    KeyboardShortcut shortcut;
+    if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
+      shortcuts.AppendElement(shortcut);
+    }
+  }
+
+  for (nsXBLPrototypeHandler* handler = userHandlers;
+       handler;
+       handler = handler->GetNextHandler()) {
+    KeyboardShortcut shortcut;
+    if (handler->TryConvertToKeyboardShortcut(&shortcut)) {
+      shortcuts.AppendElement(shortcut);
+    }
+  }
+
+  return KeyboardMap(mozilla::Move(shortcuts));
+}
+
 nsIAtom*
 nsXBLWindowKeyHandler::ConvertEventToDOMEventType(
                          const WidgetKeyboardEvent& aWidgetKeyboardEvent) const
 {
   if (aWidgetKeyboardEvent.IsKeyDownOrKeyDownOnPlugin()) {
     return nsGkAtoms::keydown;
   }
   if (aWidgetKeyboardEvent.IsKeyUpOrKeyUpOnPlugin()) {
--- a/dom/xbl/nsXBLWindowKeyHandler.h
+++ b/dom/xbl/nsXBLWindowKeyHandler.h
@@ -3,47 +3,50 @@
 /* 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/. */
 
 #ifndef nsXBLWindowKeyHandler_h__
 #define nsXBLWindowKeyHandler_h__
 
 #include "mozilla/EventForwards.h"
+#include "mozilla/layers/Keyboard.h"
 #include "nsWeakPtr.h"
 #include "nsIDOMEventListener.h"
 
 class nsIAtom;
 class nsIDOMElement;
 class nsIDOMKeyEvent;
-class nsXBLSpecialDocInfo;
 class nsXBLPrototypeHandler;
 
 namespace mozilla {
 class EventListenerManager;
 struct IgnoreModifierState;
 namespace dom {
 class Element;
 class EventTarget;
 } // namespace dom
 } // namespace mozilla
 
 class nsXBLWindowKeyHandler : public nsIDOMEventListener
 {
   typedef mozilla::EventListenerManager EventListenerManager;
   typedef mozilla::IgnoreModifierState IgnoreModifierState;
+  typedef mozilla::layers::KeyboardMap KeyboardMap;
 
 public:
   nsXBLWindowKeyHandler(nsIDOMElement* aElement, mozilla::dom::EventTarget* aTarget);
 
   void InstallKeyboardEventListenersTo(
          EventListenerManager* aEventListenerManager);
   void RemoveKeyboardEventListenersFrom(
          EventListenerManager* aEventListenerManager);
 
+  static KeyboardMap CollectKeyboardShortcuts();
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMEVENTLISTENER
 
 protected:
   virtual ~nsXBLWindowKeyHandler();
 
   nsresult WalkHandlers(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType);
 
@@ -75,16 +78,19 @@ protected:
                           bool* aOutReservedForChrome = nullptr);
 
   // Returns event type for matching between aWidgetKeyboardEvent and
   // shortcut key handlers.  This is used for calling WalkHandlers(),
   // WalkHandlersInternal() and WalkHandlersAndExecute().
   nsIAtom* ConvertEventToDOMEventType(
              const mozilla::WidgetKeyboardEvent& aWidgetKeyboardEvent) const;
 
+  // lazily load the special doc info for loading handlers
+  static void EnsureSpecialDocInfo();
+
   // lazily load the handlers. Overridden to handle being attached
   // to a particular element rather than the document
   nsresult EnsureHandlers();
 
   // Is an HTML editable element focused
   bool IsHTMLEditableFieldFocused();
 
   // Returns the element which was passed as a parameter to the constructor,
@@ -115,18 +121,17 @@ protected:
   nsWeakPtr              mWeakPtrForElement;
   mozilla::dom::EventTarget* mTarget; // weak ref
 
   // these are not owning references; the prototype handlers are owned
   // by the prototype bindings which are owned by the docinfo.
   nsXBLPrototypeHandler* mHandler;     // platform bindings
   nsXBLPrototypeHandler* mUserHandler; // user-specific bindings
 
-  // holds document info about bindings
-  static nsXBLSpecialDocInfo* sXBLSpecialDocInfo;
+  // holds reference count to document info about bindings
   static uint32_t sRefCnt;
 };
 
 already_AddRefed<nsXBLWindowKeyHandler>
 NS_NewXBLWindowKeyHandler(nsIDOMElement* aElement,
                           mozilla::dom::EventTarget* aTarget);
 
 #endif
--- a/gfx/layers/apz/src/Keyboard.cpp
+++ b/gfx/layers/apz/src/Keyboard.cpp
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "mozilla/layers/Keyboard.h"
 
-#include "mozilla/TextEvents.h" // for IgnoreModifierState
+#include "mozilla/TextEvents.h" // for IgnoreModifierState, ShortcutKeyCandidate
 
 namespace mozilla {
 namespace layers {
 
 /* static */ nsIScrollableFrame::ScrollUnit
 KeyboardScrollAction::GetScrollUnit(KeyboardScrollAction::KeyboardScrollActionType aDeltaType)
 {
   switch (aDeltaType) {
@@ -125,10 +125,67 @@ KeyboardShortcut::MatchesModifiers(const
   if (aIgnore.mShift) {
     modifiersMask &= ~MODIFIER_SHIFT;
   }
 
   // Mask off the modifiers we are ignoring from the keyboard input
   return (aInput.modifiers & modifiersMask) == mModifiers;
 }
 
+KeyboardMap::KeyboardMap(nsTArray<KeyboardShortcut>&& aShortcuts)
+  : mShortcuts(aShortcuts)
+{
+}
+
+KeyboardMap::KeyboardMap()
+{
+}
+
+Maybe<KeyboardShortcut>
+KeyboardMap::FindMatch(const KeyboardInput& aEvent) const
+{
+  // If there are no shortcut candidates, then just search with with the
+  // keyboard input
+  if (aEvent.mShortcutCandidates.IsEmpty()) {
+    return FindMatchInternal(aEvent, IgnoreModifierState());
+  }
+
+  // Otherwise do a search with each shortcut candidate in order
+  for (auto& key : aEvent.mShortcutCandidates) {
+    IgnoreModifierState ignoreModifierState;
+    ignoreModifierState.mShift = key.mIgnoreShift;
+
+    auto match = FindMatchInternal(aEvent, ignoreModifierState, key.mCharCode);
+    if (match) {
+      return match;
+    }
+  }
+  return Nothing();
+}
+
+Maybe<KeyboardShortcut>
+KeyboardMap::FindMatchInternal(const KeyboardInput& aEvent,
+                               const IgnoreModifierState& aIgnore,
+                               uint32_t aOverrideCharCode) const
+{
+  for (auto& shortcut : mShortcuts) {
+    if (shortcut.Matches(aEvent, aIgnore, aOverrideCharCode)) {
+      return Some(shortcut);
+    }
+  }
+
+#ifdef XP_WIN
+  // Windows native applications ignore Windows-Logo key state when checking
+  // shortcut keys even if the key is pressed.  Therefore, if there is no
+  // shortcut key which exactly matches current modifier state, we should
+  // retry to look for a shortcut key without the Windows-Logo key press.
+  if (!aIgnore.mOS && (aEvent.modifiers & MODIFIER_OS)) {
+    IgnoreModifierState ignoreModifierState(aIgnore);
+    ignoreModifierState.mOS = true;
+    return FindMatchInternal(aEvent, ignoreModifierState, aOverrideCharCode);
+  }
+#endif
+
+  return Nothing();
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/Keyboard.h
+++ b/gfx/layers/apz/src/Keyboard.h
@@ -5,23 +5,27 @@
 
 #ifndef mozilla_layers_Keyboard_h
 #define mozilla_layers_Keyboard_h
 
 #include <stdint.h> // for uint32_t
 
 #include "InputData.h"          // for KeyboardInput
 #include "nsIScrollableFrame.h" // for nsIScrollableFrame::ScrollUnit
+#include "nsTArray.h"           // for nsTArray
+#include "mozilla/Maybe.h"      // for mozilla::Maybe
 
 namespace mozilla {
 
 struct IgnoreModifierState;
 
 namespace layers {
 
+class KeyboardMap;
+
 /**
  * This class represents a scrolling action to be performed on a scrollable layer.
  */
 struct KeyboardScrollAction final
 {
 public:
   enum KeyboardScrollActionType : uint8_t
   {
@@ -70,16 +74,19 @@ public:
    * the keyboard event and dispatching it to content.
    */
   KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType,
                    uint32_t aKeyCode,
                    uint32_t aCharCode,
                    Modifiers aModifiers,
                    Modifiers aModifiersMask);
 
+protected:
+  friend mozilla::layers::KeyboardMap;
+
   bool Matches(const KeyboardInput& aInput,
                const IgnoreModifierState& aIgnore,
                uint32_t aOverrideCharCode = 0) const;
 
 private:
   bool MatchesKey(const KeyboardInput& aInput,
                   uint32_t aOverrideCharCode) const;
   bool MatchesModifiers(const KeyboardInput& aInput,
@@ -102,12 +109,36 @@ public:
 
   // The type of keyboard event to match against
   KeyboardInput::KeyboardEventType mEventType;
 
   // Whether events matched by this must be dispatched to content
   bool mDispatchToContent;
 };
 
+/**
+ * A keyboard map is an off main-thread <xul:binding> for scrolling commands.
+ */
+class KeyboardMap final
+{
+public:
+  KeyboardMap();
+  explicit KeyboardMap(nsTArray<KeyboardShortcut>&& aShortcuts);
+
+  const nsTArray<KeyboardShortcut>& Shortcuts() const { return mShortcuts; }
+
+  /**
+   * Search through the internal list of shortcuts for a match for the input event
+   */
+  Maybe<KeyboardShortcut> FindMatch(const KeyboardInput& aEvent) const;
+
+private:
+  Maybe<KeyboardShortcut> FindMatchInternal(const KeyboardInput& aEvent,
+                                            const IgnoreModifierState& aIgnore,
+                                            uint32_t aOverrideCharCode = 0) const;
+
+  nsTArray<KeyboardShortcut> mShortcuts;
+};
+
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_Keyboard_h
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -16,16 +16,17 @@
 #include "mozilla/GfxMessageUtils.h"
 #include "mozilla/layers/AsyncDragMetrics.h"
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/Keyboard.h"
 #include "mozilla/layers/LayerAttributes.h"
 #include "mozilla/layers/LayersTypes.h"
+#include "mozilla/Move.h"
 
 #include <stdint.h>
 
 #ifdef _MSC_VER
 #pragma warning( disable : 4800 )
 #endif
 
 namespace IPC {
@@ -466,16 +467,37 @@ struct ParamTraits<mozilla::layers::Keyb
            ReadParam(aMsg, aIter, &aResult->mCharCode) &&
            ReadParam(aMsg, aIter, &aResult->mModifiers) &&
            ReadParam(aMsg, aIter, &aResult->mModifiersMask) &&
            ReadParam(aMsg, aIter, &aResult->mEventType) &&
            ReadParam(aMsg, aIter, &aResult->mDispatchToContent);
   }
 };
 
+template <>
+struct ParamTraits<mozilla::layers::KeyboardMap>
+{
+  typedef mozilla::layers::KeyboardMap paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.Shortcuts());
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    nsTArray<mozilla::layers::KeyboardShortcut> shortcuts;
+    if (!ReadParam(aMsg, aIter, &shortcuts)) {
+      return false;
+    }
+    *aResult = mozilla::layers::KeyboardMap(mozilla::Move(shortcuts));
+    return true;
+  }
+};
+
 typedef mozilla::layers::GeckoContentController::TapType TapType;
 
 template <>
 struct ParamTraits<TapType>
   : public ContiguousEnumSerializer<
              TapType,
              TapType::eSingleTap,
              TapType::eSentinel>