Bug 989198, Patch 2: Implementation of BeforeAfterKeyboardEvent, sr=smaug, r=masayuki.
authorGina Yeh <gyeh@mozilla.com>
Tue, 14 Oct 2014 15:09:20 +0800
changeset 210321 3ff227d79d74d1a0ce0d57a999f8395838d47db7
parent 210320 186743fc6f6d388ddbf02e2bdf9402dbd6acff77
child 210322 f5a538109e367708626eccba5584f8ac2d87e88a
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerssmaug, masayuki
bugs989198, 100644
milestone36.0a1
Bug 989198, Patch 2: Implementation of BeforeAfterKeyboardEvent, sr=smaug, r=masayuki. --- dom/events/BeforeAfterKeyboardEvent.cpp | 92 ++++++++++++++++++++++ dom/events/BeforeAfterKeyboardEvent.h | 45 +++++++++++ dom/events/EventDispatcher.cpp | 3 + dom/events/KeyboardEvent.cpp | 60 ++++++++------ dom/events/KeyboardEvent.h | 8 +- dom/events/moz.build | 2 + dom/events/test/test_all_synthetic_events.html | 4 + dom/interfaces/events/nsIDOMEvent.idl | 7 ++ dom/tests/mochitest/general/test_interfaces.html | 48 ++++++----- dom/webidl/BeforeAfterKeyboardEvent.webidl | 24 ++++++ dom/webidl/moz.build | 1 + 11 files changed, 250 insertions(+), 44 deletions(-) create mode 100644 dom/events/BeforeAfterKeyboardEvent.cpp create mode 100644 dom/events/BeforeAfterKeyboardEvent.h create mode 100644 dom/webidl/BeforeAfterKeyboardEvent.webidl
dom/events/BeforeAfterKeyboardEvent.cpp
dom/events/BeforeAfterKeyboardEvent.h
dom/events/EventDispatcher.cpp
dom/events/KeyboardEvent.cpp
dom/events/KeyboardEvent.h
dom/events/moz.build
dom/events/test/test_all_synthetic_events.html
dom/interfaces/events/nsIDOMEvent.idl
dom/tests/mochitest/general/test_interfaces.html
dom/webidl/BeforeAfterKeyboardEvent.webidl
dom/webidl/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/events/BeforeAfterKeyboardEvent.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; 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/dom/BeforeAfterKeyboardEvent.h"
+#include "mozilla/TextEvents.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace dom {
+
+BeforeAfterKeyboardEvent::BeforeAfterKeyboardEvent(
+                                       EventTarget* aOwner,
+                                       nsPresContext* aPresContext,
+                                       InternalBeforeAfterKeyboardEvent* aEvent)
+  : KeyboardEvent(aOwner, aPresContext,
+                  aEvent ? aEvent :
+                           new InternalBeforeAfterKeyboardEvent(false, 0,
+                                                                nullptr))
+{
+  MOZ_ASSERT(mEvent->mClass == eBeforeAfterKeyboardEventClass,
+             "event type mismatch eBeforeAfterKeyboardEventClass");
+
+  if (!aEvent) {
+    mEventIsInternal = true;
+    mEvent->time = PR_Now();
+  }
+}
+
+// static
+already_AddRefed<BeforeAfterKeyboardEvent>
+BeforeAfterKeyboardEvent::Constructor(
+                            EventTarget* aOwner,
+                            const nsAString& aType,
+                            const BeforeAfterKeyboardEventInit& aParam)
+{
+  nsRefPtr<BeforeAfterKeyboardEvent> event =
+    new BeforeAfterKeyboardEvent(aOwner, nullptr, nullptr);
+  ErrorResult rv;
+  event->InitWithKeyboardEventInit(aOwner, aType, aParam, rv);
+  NS_WARN_IF(rv.Failed());
+
+  event->mEvent->AsBeforeAfterKeyboardEvent()->mEmbeddedCancelled =
+    aParam.mEmbeddedCancelled;
+
+  return event.forget();
+}
+
+// static
+already_AddRefed<BeforeAfterKeyboardEvent>
+BeforeAfterKeyboardEvent::Constructor(
+                            const GlobalObject& aGlobal,
+                            const nsAString& aType,
+                            const BeforeAfterKeyboardEventInit& aParam,
+                            ErrorResult& aRv)
+{
+  nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+  return Constructor(owner, aType, aParam);
+}
+
+Nullable<bool>
+BeforeAfterKeyboardEvent::GetEmbeddedCancelled()
+{
+  nsAutoString type;
+  GetType(type);
+  if (type.EqualsLiteral("mozbrowserafterkeydown") ||
+      type.EqualsLiteral("mozbrowserafterkeyup")) {
+    return mEvent->AsBeforeAfterKeyboardEvent()->mEmbeddedCancelled;
+  }
+  return Nullable<bool>();
+}
+
+} // namespace dom
+} // namespace mozilla
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsresult
+NS_NewDOMBeforeAfterKeyboardEvent(nsIDOMEvent** aInstancePtrResult,
+                                  EventTarget* aOwner,
+                                  nsPresContext* aPresContext,
+                                  InternalBeforeAfterKeyboardEvent* aEvent)
+{
+  BeforeAfterKeyboardEvent* it =
+    new BeforeAfterKeyboardEvent(aOwner, aPresContext, aEvent);
+
+  NS_ADDREF(it);
+  *aInstancePtrResult = static_cast<Event*>(it);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/events/BeforeAfterKeyboardEvent.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_dom_BeforeAfterKeyboardEvent_h_
+#define mozilla_dom_BeforeAfterKeyboardEvent_h_
+
+#include "mozilla/dom/KeyboardEvent.h"
+#include "mozilla/dom/BeforeAfterKeyboardEventBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class BeforeAfterKeyboardEvent : public KeyboardEvent
+{
+public:
+  BeforeAfterKeyboardEvent(EventTarget* aOwner,
+                           nsPresContext* aPresContext,
+                           InternalBeforeAfterKeyboardEvent* aEvent);
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE
+  {
+    return BeforeAfterKeyboardEventBinding::Wrap(aCx, this);
+  }
+
+  static already_AddRefed<BeforeAfterKeyboardEvent>
+  Constructor(const GlobalObject& aGlobal,
+              const nsAString& aType,
+              const BeforeAfterKeyboardEventInit& aParam,
+              ErrorResult& aRv);
+
+  static already_AddRefed<BeforeAfterKeyboardEvent>
+  Constructor(EventTarget* aOwner, const nsAString& aType,
+              const BeforeAfterKeyboardEventInit& aEventInitDict);
+
+  // This function returns a boolean value when event typs is either
+  // "mozbrowserafterkeydown" or "mozbrowserafterkeyup".
+  Nullable<bool> GetEmbeddedCancelled();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BeforeAfterKeyboardEvent_h_
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -696,16 +696,19 @@ EventDispatcher::CreateEvent(EventTarget
       return NS_NewDOMUIEvent(aDOMEvent, aOwner, aPresContext,
                               aEvent->AsGUIEvent());
     case eScrollAreaEventClass:
       return NS_NewDOMScrollAreaEvent(aDOMEvent, aOwner, aPresContext,
                                       aEvent->AsScrollAreaEvent());
     case eKeyboardEventClass:
       return NS_NewDOMKeyboardEvent(aDOMEvent, aOwner, aPresContext,
                                     aEvent->AsKeyboardEvent());
+    case eBeforeAfterKeyboardEventClass:
+      return NS_NewDOMBeforeAfterKeyboardEvent(aDOMEvent, aOwner, aPresContext,
+                                               aEvent->AsBeforeAfterKeyboardEvent());
     case eCompositionEventClass:
       return NS_NewDOMCompositionEvent(aDOMEvent, aOwner, aPresContext,
                                        aEvent->AsCompositionEvent());
     case eMouseEventClass:
       return NS_NewDOMMouseEvent(aDOMEvent, aOwner, aPresContext,
                                  aEvent->AsMouseEvent());
     case eFocusEventClass:
       return NS_NewDOMFocusEvent(aDOMEvent, aOwner, aPresContext,
--- a/dom/events/KeyboardEvent.cpp
+++ b/dom/events/KeyboardEvent.cpp
@@ -12,20 +12,18 @@ namespace mozilla {
 namespace dom {
 
 KeyboardEvent::KeyboardEvent(EventTarget* aOwner,
                              nsPresContext* aPresContext,
                              WidgetKeyboardEvent* aEvent)
   : UIEvent(aOwner, aPresContext,
             aEvent ? aEvent : new WidgetKeyboardEvent(false, 0, nullptr))
   , mInitializedByCtor(false)
-  , mInitialzedWhichValue(0)
+  , mInitializedWhichValue(0)
 {
-  NS_ASSERTION(mEvent->mClass == eKeyboardEventClass, "event type mismatch");
-
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
     mEvent->AsKeyboardEvent()->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
   }
@@ -252,18 +250,22 @@ uint32_t
 KeyboardEvent::CharCode()
 {
   // If this event is initialized with ctor, we shouldn't check event type.
   if (mInitializedByCtor) {
     return mEvent->AsKeyboardEvent()->charCode;
   }
 
   switch (mEvent->message) {
+  case NS_KEY_BEFORE_DOWN:
+  case NS_KEY_DOWN:
+  case NS_KEY_AFTER_DOWN:
+  case NS_KEY_BEFORE_UP:
   case NS_KEY_UP:
-  case NS_KEY_DOWN:
+  case NS_KEY_AFTER_UP:
     return 0;
   case NS_KEY_PRESS:
     return mEvent->AsKeyboardEvent()->charCode;
   }
   return 0;
 }
 
 NS_IMETHODIMP
@@ -277,36 +279,37 @@ KeyboardEvent::GetKeyCode(uint32_t* aKey
 uint32_t
 KeyboardEvent::KeyCode()
 {
   // If this event is initialized with ctor, we shouldn't check event type.
   if (mInitializedByCtor) {
     return mEvent->AsKeyboardEvent()->keyCode;
   }
 
-  switch (mEvent->message) {
-  case NS_KEY_UP:
-  case NS_KEY_PRESS:
-  case NS_KEY_DOWN:
+  if (mEvent->HasKeyEventMessage()) {
     return mEvent->AsKeyboardEvent()->keyCode;
   }
   return 0;
 }
 
 uint32_t
 KeyboardEvent::Which()
 {
   // If this event is initialized with ctor, which can have independent value.
   if (mInitializedByCtor) {
-    return mInitialzedWhichValue;
+    return mInitializedWhichValue;
   }
 
   switch (mEvent->message) {
+    case NS_KEY_BEFORE_DOWN:
+    case NS_KEY_DOWN:
+    case NS_KEY_AFTER_DOWN:
+    case NS_KEY_BEFORE_UP:
     case NS_KEY_UP:
-    case NS_KEY_DOWN:
+    case NS_KEY_AFTER_UP:
       return KeyCode();
     case NS_KEY_PRESS:
       //Special case for 4xp bug 62878.  Try to make value of which
       //more closely mirror the values that 4.x gave for RETURN and BACKSPACE
       {
         uint32_t keyCode = mEvent->AsKeyboardEvent()->keyCode;
         if (keyCode == NS_VK_RETURN || keyCode == NS_VK_BACK) {
           return keyCode;
@@ -338,36 +341,45 @@ already_AddRefed<KeyboardEvent>
 KeyboardEvent::Constructor(const GlobalObject& aGlobal,
                            const nsAString& aType,
                            const KeyboardEventInit& aParam,
                            ErrorResult& aRv)
 {
   nsCOMPtr<EventTarget> target = do_QueryInterface(aGlobal.GetAsSupports());
   nsRefPtr<KeyboardEvent> newEvent =
     new KeyboardEvent(target, nullptr, nullptr);
-  bool trusted = newEvent->Init(target);
-  aRv = newEvent->InitKeyEvent(aType, aParam.mBubbles, aParam.mCancelable,
-                               aParam.mView, aParam.mCtrlKey, aParam.mAltKey,
-                               aParam.mShiftKey, aParam.mMetaKey,
-                               aParam.mKeyCode, aParam.mCharCode);
-  newEvent->SetTrusted(trusted);
-  newEvent->mDetail = aParam.mDetail;
-  newEvent->mInitializedByCtor = true;
-  newEvent->mInitialzedWhichValue = aParam.mWhich;
+  newEvent->InitWithKeyboardEventInit(target, aType, aParam, aRv);
+
+  return newEvent.forget();
+}
 
-  WidgetKeyboardEvent* internalEvent = newEvent->mEvent->AsKeyboardEvent();
+void
+KeyboardEvent::InitWithKeyboardEventInit(EventTarget* aOwner,
+                                         const nsAString& aType,
+                                         const KeyboardEventInit& aParam,
+                                         ErrorResult& aRv)
+{
+  bool trusted = Init(aOwner);
+  aRv = InitKeyEvent(aType, aParam.mBubbles, aParam.mCancelable,
+                     aParam.mView, aParam.mCtrlKey, aParam.mAltKey,
+                     aParam.mShiftKey, aParam.mMetaKey,
+                     aParam.mKeyCode, aParam.mCharCode);
+  SetTrusted(trusted);
+  mDetail = aParam.mDetail;
+  mInitializedByCtor = true;
+  mInitializedWhichValue = aParam.mWhich;
+
+  WidgetKeyboardEvent* internalEvent = mEvent->AsKeyboardEvent();
   internalEvent->location = aParam.mLocation;
   internalEvent->mIsRepeat = aParam.mRepeat;
   internalEvent->mIsComposing = aParam.mIsComposing;
   internalEvent->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+  internalEvent->mCodeNameIndex = CODE_NAME_INDEX_USE_STRING;
   internalEvent->mKeyValue = aParam.mKey;
-  internalEvent->mCodeNameIndex = CODE_NAME_INDEX_USE_STRING;
   internalEvent->mCodeValue = aParam.mCode;
-
-  return newEvent.forget();
 }
 
 NS_IMETHODIMP
 KeyboardEvent::InitKeyEvent(const nsAString& aType,
                             bool aCanBubble,
                             bool aCancelable,
                             nsIDOMWindow* aView,
                             bool aCtrlKey,
--- a/dom/events/KeyboardEvent.h
+++ b/dom/events/KeyboardEvent.h
@@ -69,21 +69,27 @@ public:
     aRv = InitKeyEvent(aType, aCanBubble, aCancelable, aView,
                        aCtrlKey, aAltKey, aShiftKey,aMetaKey,
                        aKeyCode, aCharCode);
   }
 
 protected:
   ~KeyboardEvent() {}
 
+  void InitWithKeyboardEventInit(EventTarget* aOwner,
+                                 const nsAString& aType,
+                                 const KeyboardEventInit& aParam,
+                                 ErrorResult& aRv);
+
 private:
   // True, if the instance is created with Constructor().
   bool mInitializedByCtor;
+
   // If the instance is created with Constructor(), which may have independent
   // value.  mInitializedWhichValue stores it.  I.e., this is invalid when
   // mInitializedByCtor is false.
-  uint32_t mInitialzedWhichValue;
+  uint32_t mInitializedWhichValue;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyboardEvent_h_
--- a/dom/events/moz.build
+++ b/dom/events/moz.build
@@ -27,16 +27,17 @@ EXPORTS.mozilla += [
     'KeyNameList.h',
     'PhysicalKeyCodeNameList.h',
     'TextComposition.h',
     'VirtualKeyCodeList.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'AnimationEvent.h',
+    'BeforeAfterKeyboardEvent.h',
     'BeforeUnloadEvent.h',
     'ClipboardEvent.h',
     'CommandEvent.h',
     'CompositionEvent.h',
     'CustomEvent.h',
     'DataContainerEvent.h',
     'DataTransfer.h',
     'DeviceMotionEvent.h',
@@ -66,16 +67,17 @@ EXPORTS.mozilla.dom += [
 ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     EXPORTS.mozilla.dom += ['SpeechRecognitionError.h']
 
 UNIFIED_SOURCES += [
     'AnimationEvent.cpp',
     'AsyncEventDispatcher.cpp',
+    'BeforeAfterKeyboardEvent.cpp',
     'BeforeUnloadEvent.cpp',
     'ClipboardEvent.cpp',
     'CommandEvent.cpp',
     'CompositionEvent.cpp',
     'ContentEventHandler.cpp',
     'DataContainerEvent.cpp',
     'DataTransfer.cpp',
     'DeviceMotionEvent.cpp',
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -29,16 +29,20 @@ const kEventConstructors = {
                                                        },
                                              },
   AnimationEvent:                            { create: function (aName, aProps) {
                                                          return new AnimationEvent(aName, aProps);
                                                        },
                                              },
   AudioProcessingEvent:                      { create: null, // Cannot create untrusted event from JS.
                                              },
+  BeforeAfterKeyboardEvent:                  { create: function (aName, aProps) {
+                                                         return new BeforeAfterKeyboardEvent(aName, aProps);
+                                                       },
+                                             },
   BeforeUnloadEvent:                         { create: function (aName, aProps) {
                                                          var e = document.createEvent("beforeunloadevent");
                                                          e.initEvent(aName, aProps.bubbles, aProps.cancelable);
                                                          return e;
                                                        },
                                              },
   BlobEvent:                                 { create: function (aName, aProps) {
                                                          return new BlobEvent(aName, aProps);
--- a/dom/interfaces/events/nsIDOMEvent.idl
+++ b/dom/interfaces/events/nsIDOMEvent.idl
@@ -267,16 +267,23 @@ NS_NewDOMInputEvent(nsIDOMEvent** aInsta
                     mozilla::dom::EventTarget* aOwner,
                     nsPresContext* aPresContext,
                     mozilla::InternalEditorInputEvent* aEvent);
 nsresult
 NS_NewDOMKeyboardEvent(nsIDOMEvent** aInstancePtrResult,
                        mozilla::dom::EventTarget* aOwner,
                        nsPresContext* aPresContext,
                        mozilla::WidgetKeyboardEvent* aEvent);
+
+nsresult
+NS_NewDOMBeforeAfterKeyboardEvent(nsIDOMEvent** aInstancePtrResult,
+                                  mozilla::dom::EventTarget* aOwner,
+                                  nsPresContext* aPresContext,
+                                  mozilla::InternalBeforeAfterKeyboardEvent* aEvent);
+
 nsresult
 NS_NewDOMCompositionEvent(nsIDOMEvent** aInstancePtrResult,
                           mozilla::dom::EventTarget* aOwner,
                           nsPresContext* aPresContext,
                           mozilla::WidgetCompositionEvent* aEvent);
 nsresult
 NS_NewDOMMutationEvent(nsIDOMEvent** aResult,
                        mozilla::dom::EventTarget* aOwner,
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -167,36 +167,40 @@ var interfaceNamesInGlobalScope =
     "AudioProcessingEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AudioStreamTrack",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "BarProp",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "BatteryManager",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BeforeAfterKeyboardEvent", b2g: true,
+     pref: "dom.beforeAfterKeyboardEvent.enabled",
+     permission: ["embed-apps", "before-after-keyboard-event"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "BeforeUnloadEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "BiquadFilterNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Blob",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "BlobEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "BluetoothAdapter", b2g: true, permission: "bluetooth"},
+    {name: "BluetoothAdapter", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "BluetoothDevice", b2g: true, permission: "bluetooth"},
+    {name: "BluetoothDevice", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "BluetoothDeviceEvent", b2g: true, permission: "bluetooth"},
+    {name: "BluetoothDeviceEvent", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothDiscoveryStateChangedEvent", b2g: true,
-     permission: "bluetooth"},
+     permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "BluetoothManager", b2g: true, permission: "bluetooth"},
+    {name: "BluetoothManager", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "BluetoothStatusChangedEvent", b2g: true, permission: "bluetooth"},
+    {name: "BluetoothStatusChangedEvent", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BoxObject", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CallEvent", b2g: true, pref: "dom.telephony.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CallGroupErrorEvent", b2g: true, pref: "dom.telephony.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraCapabilities", b2g: true},
@@ -746,17 +750,17 @@ var interfaceNamesInGlobalScope =
     {name: "mozRTCIceCandidate", pref: "media.peerconnection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "mozRTCPeerConnection", pref: "media.peerconnection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "mozRTCSessionDescription", pref: "media.peerconnection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MozSettingsEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MozSettingsTransactionEvent", permission: "settings-api-read"},
+    {name: "MozSettingsTransactionEvent", permission: ["settings-api-read"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MozSmsEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MozSmsMessage",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozSpeakerManager", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozStkCommandEvent", b2g: true, pref: "dom.icc.enabled"},
@@ -766,33 +770,33 @@ var interfaceNamesInGlobalScope =
     {name: "MozVoicemail", b2g: true, pref: "dom.voicemail.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozVoicemailEvent", b2g: true, pref: "dom.voicemail.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozVoicemailStatus", b2g: true, pref: "dom.voicemail.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWakeLock", b2g: true, pref: "dom.wakelock.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MozWifiCapabilities", b2g: true, permission: "wifi-manage"},
+    {name: "MozWifiCapabilities", b2g: true, permission: ["wifi-manage"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiConnectionInfoEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiStationInfoEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-   {name: "MozWifiManager", b2g: true, permission: "wifi-manage"},
+   {name: "MozWifiManager", b2g: true, permission: ["wifi-manage"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MozWifiNetwork", b2g: true, permission: "wifi-manage"},
+    {name: "MozWifiNetwork", b2g: true, permission: ["wifi-manage"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiStatusChangeEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MozWifiP2pGroupOwner", b2g: true, permission: "wifi-manage"},
+    {name: "MozWifiP2pGroupOwner", b2g: true, permission: ["wifi-manage"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MozWifiP2pManager", b2g: true, permission: "wifi-manage"},
+    {name: "MozWifiP2pManager", b2g: true, permission: ["wifi-manage"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MozWifiP2pStatusChangeEvent", b2g: true, permission: "wifi-manage"},
+    {name: "MozWifiP2pStatusChangeEvent", b2g: true, permission: ["wifi-manage"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MutationEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MutationObserver",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MutationRecord",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "NamedNodeMap",
@@ -840,19 +844,19 @@ var interfaceNamesInGlobalScope =
     "PerformanceNavigation",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceResourceTiming",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceTiming",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PeriodicWave",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "PermissionSettings", b2g: true, permission: "permissions"},
+    {name: "PermissionSettings", b2g: true, permission: ["permissions"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "PhoneNumberService", permission: "phonenumberservice"},
+    {name: "PhoneNumberService", permission: ["phonenumberservice"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Plugin",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PluginArray",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PointerEvent", pref: "dom.w3c_pointer_events.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PopStateEvent",
@@ -1222,19 +1226,21 @@ var interfaceNamesInGlobalScope =
     {name: "TreeColumns", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "TreeContentView", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "TreeSelection", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "TreeWalker",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "UDPMessageEvent", pref: "dom.udpsocket.enabled", permission: "udp-socket"},
+    {name: "UDPMessageEvent", pref: "dom.udpsocket.enabled",
+     permission: ["udp-socket"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "UDPSocket", pref: "dom.udpsocket.enabled", permission: "udp-socket"},
+    {name: "UDPSocket", pref: "dom.udpsocket.enabled",
+     permission: ["udp-socket"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "UIEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "UndoManager",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "URL",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "URLSearchParams",
@@ -1334,18 +1340,22 @@ var interfaceNamesInGlobalScope =
 
 function createInterfaceMap(isXBLScope) {
   var prefs = SpecialPowers.Services.prefs;
   var version = SpecialPowers.Cc["@mozilla.org/xre/app-info;1"].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
   var isNightly = version.endsWith("a1");
   var isRelease = !version.contains("a");
   var isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
   var isB2G = !isDesktop && !navigator.userAgent.contains("Android");
-  var hasPermission = function (aPermission) {
-    return SpecialPowers.hasPermission(aPermission, window.document);
+  var hasPermission = function (aPermissions) {
+    var result = false;
+    for (var p of aPermissions) {
+      result = result || SpecialPowers.hasPermission(p, window.document);
+    }
+    return result;
   };
 
   var interfaceMap = {};
 
   function addInterfaces(interfaces)
   {
     for (var entry of interfaces) {
       if (typeof(entry) === "string") {
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BeforeAfterKeyboardEvent.webidl
@@ -0,0 +1,24 @@
+/* -*- Mode: IDL; tab-width: 2; 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/.
+ */
+
+[Constructor(DOMString typeArg,
+ optional BeforeAfterKeyboardEventInit eventInitDict),
+ CheckPermissions="embed-apps before-after-keyboard-event",
+ Pref="dom.beforeAfterKeyboardEvent.enabled"]
+interface BeforeAfterKeyboardEvent : KeyboardEvent
+{
+  // The valid value of embeddedCancelled is:
+  // - "mozbrowserbeforekeydown": null
+  // - "mozbrowserbeforekeyup": null
+  // - "mozbrowserafterkeydown": true/false
+  // - "mozbrowserafterkeyup": true/false
+  readonly attribute boolean? embeddedCancelled;
+};
+
+dictionary BeforeAfterKeyboardEventInit : KeyboardEventInit
+{
+  boolean? embeddedCancelled = null;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -44,16 +44,17 @@ WEBIDL_FILES = [
     'AudioParam.webidl',
     'AudioProcessingEvent.webidl',
     'AudioStreamTrack.webidl',
     'AudioTrack.webidl',
     'AudioTrackList.webidl',
     'AutocompleteInfo.webidl',
     'BarProp.webidl',
     'BatteryManager.webidl',
+    'BeforeAfterKeyboardEvent.webidl',
     'BeforeUnloadEvent.webidl',
     'BiquadFilterNode.webidl',
     'Blob.webidl',
     'BrowserElementDictionaries.webidl',
     'CallsList.webidl',
     'CameraCapabilities.webidl',
     'CameraControl.webidl',
     'CameraManager.webidl',