Bug 989198, Patch 2: Implementation of BeforeAfterKeyboardEvent, sr=smaug, r=masayuki.
authorGina Yeh <gyeh@mozilla.com>
Mon, 03 Nov 2014 15:05:32 +0800
changeset 237911 c60a1eb52f1c86f5f875c5231ba8e74f643610e3
parent 237910 71d15f2630aebef7c4cde3f410dc0eaf454ef4cc
child 237912 ee9403c598d6e99a04bd2b8fb420d1308dd78dc8
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, masayuki
bugs989198
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 989198, Patch 2: Implementation of BeforeAfterKeyboardEvent, sr=smaug, r=masayuki.
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* WrapObjectInternal(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
@@ -168,36 +168,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},
@@ -751,17 +755,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"},
@@ -771,33 +775,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",
@@ -845,19 +849,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",
@@ -1231,35 +1235,41 @@ var interfaceNamesInGlobalScope =
     {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: "TVChannel", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "TVCurrentChannelChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+		{name: "TVCurrentChannelChangedEvent", b2g: true, pref: "dom.tv.enabled",
+     permission: "tv"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "TVCurrentSourceChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+		{name: "TVCurrentSourceChangedEvent", b2g: true, pref: "dom.tv.enabled",
+     permission: "tv"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "TVEITBroadcastedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+		{name: "TVEITBroadcastedEvent", b2g: true, pref: "dom.tv.enabled",
+     permission: ["tv"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "TVManager", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+    {name: "TVManager", b2g: true, pref: "dom.tv.enabled", permission: ["tv"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "TVProgram", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+    {name: "TVProgram", b2g: true, pref: "dom.tv.enabled", permission: ["tv"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "TVScanningStateChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+    {name: "TVScanningStateChangedEvent", b2g: true, pref: "dom.tv.enabled",
+     permission: ["tv"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "TVSource", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+    {name: "TVSource", b2g: true, pref: "dom.tv.enabled", permission: ["tv"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "TVTuner", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
+    {name: "TVTuner", b2g: true, pref: "dom.tv.enabled", permission: ["tv"]},
 // 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",
@@ -1355,18 +1365,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
@@ -45,16 +45,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',
     'BoxObject.webidl',
     'BrowserElementDictionaries.webidl',
     'CallsList.webidl',
     'CameraCapabilities.webidl',
     'CameraControl.webidl',