Bug 1343955 - part 4: Implement static API to synthesize keyboard events into FuzzingFunctions r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 02 Oct 2018 12:16:45 +0000
changeset 497852 ade489313a3d24e51837775e344510de4b144e0d
parent 497851 4a3ba199155a58cd282ee0383925525141593413
child 497853 9ebf0b05b44abcdfeebf98ffcc6db352261d9f5e
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1343955
milestone64.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 1343955 - part 4: Implement static API to synthesize keyboard events into FuzzingFunctions r=smaug Synthesizing keyboard events is dangerous and such API is requested only by fuzzing test. So, we should add it into FuzzingFunctions which is built only when |ac_add_options --enable-fuzzing| is specified and enabled by the pref. This patch implements the API as synthesizing keyboard events in the focused widget and the synthesized events are propagated as native key events except APZ (because keyboard events are synthesized only in the process). This behavior allows to test including any default action handlers such as EventStateManager and setting WidgetGUIEvent::mWidget since some C++ handler checks if it's nullptr. Differential Revision: https://phabricator.services.mozilla.com/D5516
dom/base/FuzzingFunctions.cpp
dom/base/FuzzingFunctions.h
dom/base/TextInputProcessor.cpp
dom/base/TextInputProcessor.h
dom/webidl/FuzzingFunctions.webidl
widget/TextEventDispatcher.cpp
widget/TextEventDispatcher.h
widget/TextEvents.h
--- a/dom/base/FuzzingFunctions.cpp
+++ b/dom/base/FuzzingFunctions.cpp
@@ -3,18 +3,24 @@
 /* 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 "FuzzingFunctions.h"
 
 #include "nsJSEnvironment.h"
 #include "js/GCAPI.h"
+#include "mozIDOMWindow.h"
+#include "mozilla/dom/KeyboardEvent.h"
 #include "mozilla/ErrorResult.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TextInputProcessor.h"
+#include "nsFocusManager.h"
 #include "nsIAccessibilityService.h"
+#include "nsPIDOMWindow.h"
 #include "xpcAccessibilityService.h"
 
 namespace mozilla {
 namespace dom {
 
 /* static */ void
 FuzzingFunctions::GarbageCollect(const GlobalObject&)
 {
@@ -37,10 +43,322 @@ FuzzingFunctions::EnableAccessibility(co
   nsresult rv;
 
   rv = NS_GetAccessibilityService(getter_AddRefs(a11y));
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 }
 
+struct ModifierKey final
+{
+  Modifier mModifier;
+  KeyNameIndex mKeyNameIndex;
+  bool mLockable;
+
+  ModifierKey(Modifier aModifier,
+              KeyNameIndex aKeyNameIndex,
+              bool aLockable)
+    : mModifier(aModifier)
+    , mKeyNameIndex(aKeyNameIndex)
+    , mLockable(aLockable)
+  {
+  }
+};
+
+static const ModifierKey kModifierKeys[] = {
+  ModifierKey(MODIFIER_ALT, KEY_NAME_INDEX_Alt, false),
+  ModifierKey(MODIFIER_ALTGRAPH, KEY_NAME_INDEX_AltGraph, false),
+  ModifierKey(MODIFIER_CONTROL, KEY_NAME_INDEX_Control, false),
+  ModifierKey(MODIFIER_FN, KEY_NAME_INDEX_Fn, false),
+  ModifierKey(MODIFIER_META, KEY_NAME_INDEX_Meta, false),
+  ModifierKey(MODIFIER_OS, KEY_NAME_INDEX_OS, false),
+  ModifierKey(MODIFIER_SHIFT, KEY_NAME_INDEX_Shift, false),
+  ModifierKey(MODIFIER_SYMBOL, KEY_NAME_INDEX_Symbol, false),
+  ModifierKey(MODIFIER_CAPSLOCK, KEY_NAME_INDEX_CapsLock, true),
+  ModifierKey(MODIFIER_FNLOCK, KEY_NAME_INDEX_FnLock, true),
+  ModifierKey(MODIFIER_NUMLOCK, KEY_NAME_INDEX_NumLock, true),
+  ModifierKey(MODIFIER_SCROLLLOCK, KEY_NAME_INDEX_ScrollLock, true),
+  ModifierKey(MODIFIER_SYMBOLLOCK, KEY_NAME_INDEX_SymbolLock, true),
+};
+
+/* static */ Modifiers
+FuzzingFunctions::ActivateModifiers(TextInputProcessor* aTextInputProcessor,
+                                    Modifiers aModifiers,
+                                    nsIWidget* aWidget,
+                                    ErrorResult& aRv)
+{
+  MOZ_ASSERT(aTextInputProcessor);
+
+  if (aModifiers == MODIFIER_NONE) {
+    return MODIFIER_NONE;
+  }
+
+  // We don't want to dispatch modifier key event from here.  In strictly
+  // speaking, all necessary modifiers should be activated with dispatching
+  // each modifier key event.  However, we cannot keep storing
+  // TextInputProcessor instance for multiple SynthesizeKeyboardEvents() calls.
+  // So, if some callers need to emulate modifier key events, they should do
+  // it by themselves.
+  uint32_t flags =
+    nsITextInputProcessor::KEY_NON_PRINTABLE_KEY |
+    nsITextInputProcessor::KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT;
+
+  Modifiers activatedModifiers = MODIFIER_NONE;
+  Modifiers activeModifiers = aTextInputProcessor->GetActiveModifiers();
+  for (const ModifierKey& kModifierKey : kModifierKeys) {
+    if (!(kModifierKey.mModifier & aModifiers)) {
+      continue; // Not requested modifier.
+    }
+    if (kModifierKey.mModifier & activeModifiers) {
+      continue; // Already active, do nothing.
+    }
+    WidgetKeyboardEvent event(true, eVoidEvent, aWidget);
+    // mKeyCode will be computed by TextInputProcessor automatically.
+    event.mKeyNameIndex = kModifierKey.mKeyNameIndex;
+    aRv = aTextInputProcessor->Keydown(event, flags);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return activatedModifiers;
+    }
+    if (kModifierKey.mLockable) {
+      aRv = aTextInputProcessor->Keyup(event, flags);
+      if (NS_WARN_IF(aRv.Failed())) {
+        return activatedModifiers;
+      }
+    }
+    activatedModifiers |= kModifierKey.mModifier;
+  }
+  return activatedModifiers;
+}
+
+/* static */ Modifiers
+FuzzingFunctions::InactivateModifiers(TextInputProcessor* aTextInputProcessor,
+                                      Modifiers aModifiers,
+                                      nsIWidget* aWidget,
+                                      ErrorResult& aRv)
+{
+  MOZ_ASSERT(aTextInputProcessor);
+
+  if (aModifiers == MODIFIER_NONE) {
+    return MODIFIER_NONE;
+  }
+
+  // We don't want to dispatch modifier key event from here.  In strictly
+  // speaking, all necessary modifiers should be activated with dispatching
+  // each modifier key event.  However, we cannot keep storing
+  // TextInputProcessor instance for multiple SynthesizeKeyboardEvents() calls.
+  // So, if some callers need to emulate modifier key events, they should do
+  // it by themselves.
+  uint32_t flags =
+    nsITextInputProcessor::KEY_NON_PRINTABLE_KEY |
+    nsITextInputProcessor::KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT;
+
+  Modifiers inactivatedModifiers = MODIFIER_NONE;
+  Modifiers activeModifiers = aTextInputProcessor->GetActiveModifiers();
+  for (const ModifierKey& kModifierKey : kModifierKeys) {
+    if (!(kModifierKey.mModifier & aModifiers)) {
+      continue; // Not requested modifier.
+    }
+    if (kModifierKey.mModifier & activeModifiers) {
+      continue; // Already active, do nothing.
+    }
+    WidgetKeyboardEvent event(true, eVoidEvent, aWidget);
+    // mKeyCode will be computed by TextInputProcessor automatically.
+    event.mKeyNameIndex = kModifierKey.mKeyNameIndex;
+    if (kModifierKey.mLockable) {
+      aRv = aTextInputProcessor->Keydown(event, flags);
+      if (NS_WARN_IF(aRv.Failed())) {
+        return inactivatedModifiers;
+      }
+    }
+    aRv = aTextInputProcessor->Keyup(event, flags);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return inactivatedModifiers;
+    }
+    inactivatedModifiers |= kModifierKey.mModifier;
+  }
+  return inactivatedModifiers;
+}
+
+/* static */ void
+FuzzingFunctions::SynthesizeKeyboardEvents(const GlobalObject&,
+                                           const nsAString& aKeyValue,
+                                           const KeyboardEventInit& aDict,
+                                           ErrorResult& aRv)
+{
+  // Prepare keyboard event to synthesize first.
+  uint32_t flags = 0;
+  // Don't modify the given dictionary since caller may want to modify
+  // a part of it and call this with it again.
+  WidgetKeyboardEvent event(true, eVoidEvent, nullptr);
+  event.mKeyCode = aDict.mKeyCode;
+  event.mCharCode = 0; // Ignore.
+  event.mKeyNameIndex = WidgetKeyboardEvent::GetKeyNameIndex(aKeyValue);
+  if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+    event.mKeyValue = aKeyValue;
+  }
+  // code value should be empty string or one of valid code value.
+  event.mCodeNameIndex =
+    aDict.mCode.IsEmpty() ? CODE_NAME_INDEX_UNKNOWN :
+                            WidgetKeyboardEvent::GetCodeNameIndex(aDict.mCode);
+  if (NS_WARN_IF(event.mCodeNameIndex == CODE_NAME_INDEX_USE_STRING)) {
+    // Meaning that the code value is specified but it's not a known code
+    // value.  TextInputProcessor does not support synthesizing keyboard
+    // events with unknown code value.  So, returns error now.
+    aRv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+  event.mLocation = aDict.mLocation;
+  event.mIsRepeat = aDict.mRepeat;
+
+#define SET_MODIFIER(aName, aValue) \
+  if (aDict.m##aName) { \
+    event.mModifiers |= aValue; \
+  } \
+
+  SET_MODIFIER(CtrlKey,                 MODIFIER_CONTROL)
+  SET_MODIFIER(ShiftKey,                MODIFIER_SHIFT)
+  SET_MODIFIER(AltKey,                  MODIFIER_ALT)
+  SET_MODIFIER(MetaKey,                 MODIFIER_META)
+  SET_MODIFIER(ModifierAltGraph,        MODIFIER_ALTGRAPH)
+  SET_MODIFIER(ModifierCapsLock,        MODIFIER_CAPSLOCK)
+  SET_MODIFIER(ModifierFn,              MODIFIER_FN)
+  SET_MODIFIER(ModifierFnLock,          MODIFIER_FNLOCK)
+  SET_MODIFIER(ModifierNumLock,         MODIFIER_NUMLOCK)
+  SET_MODIFIER(ModifierOS,              MODIFIER_OS)
+  SET_MODIFIER(ModifierScrollLock,      MODIFIER_SCROLLLOCK)
+  SET_MODIFIER(ModifierSymbol,          MODIFIER_SYMBOL)
+  SET_MODIFIER(ModifierSymbolLock,      MODIFIER_SYMBOLLOCK)
+
+#undef SET_MODIFIER
+
+  // If we could distinguish whether the caller specified 0 explicitly or
+  // not, we would skip computing the key location when it's specified
+  // explicitly.  However, this caller probably won't test tricky keyboard
+  // events, so, it must be enough even though caller cannot set location
+  // to 0.
+  Maybe<uint32_t> maybeNonStandardLocation;
+  if (!event.mLocation) {
+    maybeNonStandardLocation = mozilla::Some(event.mLocation);
+  }
+
+  // If the key is a printable key and |.code| and/or |.keyCode| value is
+  // not specified as non-zero explicitly, let's assume that the caller
+  // emulates US-English keyboard's behavior (because otherwise, caller
+  // should set both values.
+  if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+    if (event.mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
+      event.mCodeNameIndex =
+        TextInputProcessor::GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout(
+          event.mKeyValue, maybeNonStandardLocation);
+      MOZ_ASSERT(event.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+    }
+    if (!event.mKeyCode) {
+      event.mKeyCode =
+        TextInputProcessor::GuessKeyCodeOfPrintableKeyInUSEnglishLayout(
+          event.mKeyValue, maybeNonStandardLocation);
+      if (!event.mKeyCode) {
+        // Prevent to recompute keyCode in TextInputProcessor.
+        flags |= nsITextInputProcessor::KEY_KEEP_KEYCODE_ZERO;
+      }
+    }
+  }
+  // If the key is a non-printable key, we can compute |.code| value of
+  // usual keyboard of the platform.  Note that |.keyCode| value for
+  // non-printable key will be computed by TextInputProcessor.  So, we need
+  // to take care only |.code| value here.
+  else if (event.mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
+    event.mCodeNameIndex =
+      WidgetKeyboardEvent::ComputeCodeNameIndexFromKeyNameIndex(
+        event.mKeyNameIndex, maybeNonStandardLocation);
+  }
+
+  // Synthesize keyboard events on focused widget.
+  nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
+  if (NS_WARN_IF(!focusManager)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  nsPIDOMWindowOuter* activeWindow = focusManager->GetActiveWindow();
+  if (NS_WARN_IF(!activeWindow)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  nsIDocShell* docShell = activeWindow->GetDocShell();
+  if (NS_WARN_IF(!docShell)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  RefPtr<nsPresContext> presContext;
+  docShell->GetPresContext(getter_AddRefs(presContext));
+  if (NS_WARN_IF(!presContext)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  event.mWidget = presContext->GetRootWidget();
+  if (NS_WARN_IF(!event.mWidget)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> activeWindowInner =
+    activeWindow->EnsureInnerWindow();
+  if (NS_WARN_IF(!activeWindowInner)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  RefPtr<TextInputProcessor> textInputProcessor = new TextInputProcessor();
+  bool beganInputTransaction = false;
+  aRv = textInputProcessor->BeginInputTransactionForFuzzing(
+                              activeWindowInner,
+                              nullptr,
+                              &beganInputTransaction);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+  if (NS_WARN_IF(!beganInputTransaction)) {
+    // This is possible if a keyboard event listener or something tries to
+    // dispatch next keyboard events during dispatching a keyboard event via
+    // TextInputProcessor.
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  // First, activate necessary modifiers.
+  Modifiers activatedModifiers =
+    ActivateModifiers(textInputProcessor, event.mModifiers, event.mWidget, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  // Then, dispatch keydown and keypress.
+  aRv = textInputProcessor->Keydown(event, flags);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  // Then, dispatch keyup.
+  aRv = textInputProcessor->Keyup(event, flags);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  // Finally, inactivate some modifiers which are activated by this call.
+  InactivateModifiers(textInputProcessor, activatedModifiers, event.mWidget,
+                      aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  // Unfortunately, we cannot keep storing modifier state in the
+  // TextInputProcessor since if we store it into a static variable,
+  // we need to take care of resetting it when the caller wants.
+  // However, that makes API more complicated.  So, until they need
+  // to want 
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/FuzzingFunctions.h
+++ b/dom/base/FuzzingFunctions.h
@@ -2,33 +2,77 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_FuzzingFunctions
 #define mozilla_dom_FuzzingFunctions
 
+#include "mozilla/EventForwards.h"
+
+class nsIWidget;
+
 namespace mozilla {
 
 class ErrorResult;
+class TextInputProcessor;
 
 namespace dom {
 
 class GlobalObject;
+struct KeyboardEventInit;
 
 class FuzzingFunctions final
 {
 public:
   static void
   GarbageCollect(const GlobalObject&);
 
   static void
   CycleCollect(const GlobalObject&);
 
   static void
   EnableAccessibility(const GlobalObject&, ErrorResult& aRv);
+
+  static void
+  SynthesizeKeyboardEvents(const GlobalObject& aGlobalObject,
+                           const nsAString& aKeyValue,
+                           const KeyboardEventInit& aKeyboardEvent,
+                           ErrorResult& aRv);
+
+private:
+  /**
+   * ActivateModifiers() activates aModifiers in the TextInputProcessor.
+   *
+   * @param aTextInputProcessor The TIP whose modifier state you want to change.
+   * @param aModifiers          Modifiers which you want to activate.
+   * @param aWidget             The widget which should be set to
+   *                            WidgetKeyboardEvent.
+   * @param aRv                 Returns error if TextInputProcessor fails to
+   *                            dispatch a modifier key event.
+   * @return                    Modifiers which are activated by the call.
+   */
+  static Modifiers
+  ActivateModifiers(TextInputProcessor* aTextInputProcessor,
+                    Modifiers aModifiers, nsIWidget* aWidget, ErrorResult& aRv);
+
+  /**
+   * InactivateModifiers() inactivates aModifiers in the TextInputProcessor.
+   *
+   * @param aTextInputProcessor The TIP whose modifier state you want to change.
+   * @param aModifiers          Modifiers which you want to inactivate.
+   * @param aWidget             The widget which should be set to
+   *                            WidgetKeyboardEvent.
+   * @param aRv                 Returns error if TextInputProcessor fails to
+   *                            dispatch a modifier key event.
+   * @return                    Modifiers which are inactivated by the call.
+   */
+  static Modifiers
+  InactivateModifiers(TextInputProcessor* aTextInputProcessor,
+                      Modifiers aModifiers, nsIWidget* aWidget,
+                      ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FuzzingFunctions
--- a/dom/base/TextInputProcessor.cpp
+++ b/dom/base/TextInputProcessor.cpp
@@ -359,16 +359,26 @@ TextInputProcessor::BeginInputTransactio
   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
   nsITextInputProcessorCallback* callback =
     aOptionalArgc >= 1 ? aCallback : nullptr;
   return BeginInputTransactionInternal(aWindow, callback, true, *aSucceeded);
 }
 
 nsresult
+TextInputProcessor::BeginInputTransactionForFuzzing(
+                      nsPIDOMWindowInner* aWindow,
+                      nsITextInputProcessorCallback* aCallback,
+                      bool* aSucceeded)
+{
+  MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+  return BeginInputTransactionInternal(aWindow, aCallback, false, *aSucceeded);
+}
+
+nsresult
 TextInputProcessor::BeginInputTransactionInternal(
                       mozIDOMWindow* aWindow,
                       nsITextInputProcessorCallback* aCallback,
                       bool aForTests,
                       bool& aSucceeded)
 {
   aSucceeded = false;
   if (NS_WARN_IF(!aWindow)) {
@@ -1071,17 +1081,40 @@ TextInputProcessor::PrepareKeyboardEvent
     // If KeyboardEvent.keyCode is 0, it may be uninitialized.  If so, we may
     // be able to decide a good .keyCode value if the .key value is a
     // non-printable key.
     aKeyboardEvent.mKeyCode =
       WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex(
         aKeyboardEvent.mKeyNameIndex);
   }
 
-  aKeyboardEvent.mIsSynthesizedByTIP = (mForTests)? false : true;
+  aKeyboardEvent.mIsSynthesizedByTIP = !mForTests;
+
+  // When this emulates real input only in content process, we need to
+  // initialize edit commands with the main process's widget via PuppetWidget
+  // because they are initialized by TabParent before content process treats
+  // them.
+  if (aKeyboardEvent.mIsSynthesizedByTIP && !XRE_IsParentProcess()) {
+    // Note that retrieving edit commands from content process is expensive.
+    // Let's skip it when the keyboard event is inputting text.
+    if (!aKeyboardEvent.IsInputtingText()) {
+      // FYI: WidgetKeyboardEvent::InitAllEditCommands() isn't available here
+      //      since it checks whether it's called in the main process to
+      //      avoid performance issues so that we need to initialize each
+      //      command manually here.
+      aKeyboardEvent.InitEditCommandsFor(
+                       nsIWidget::NativeKeyBindingsForSingleLineEditor);
+      aKeyboardEvent.InitEditCommandsFor(
+                       nsIWidget::NativeKeyBindingsForMultiLineEditor);
+      aKeyboardEvent.InitEditCommandsFor(
+                       nsIWidget::NativeKeyBindingsForRichTextEditor);
+    } else {
+      aKeyboardEvent.PreventNativeKeyBindings();
+    }
+  }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextInputProcessor::Keydown(Event* aDOMKeyEvent,
                             uint32_t aKeyFlags,
                             uint8_t aOptionalArgc,
@@ -1099,16 +1132,26 @@ TextInputProcessor::Keydown(Event* aDOMK
     aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
   if (NS_WARN_IF(!originalKeyEvent)) {
     return NS_ERROR_INVALID_ARG;
   }
   return KeydownInternal(*originalKeyEvent, aKeyFlags, true, *aConsumedFlags);
 }
 
 nsresult
+TextInputProcessor::Keydown(const WidgetKeyboardEvent& aKeyboardEvent,
+                            uint32_t aKeyFlags,
+                            uint32_t* aConsumedFlags)
+{
+  uint32_t consumedFlags = 0;
+  return KeydownInternal(aKeyboardEvent, aKeyFlags, true,
+                         aConsumedFlags ? *aConsumedFlags : consumedFlags);
+}
+
+nsresult
 TextInputProcessor::KeydownInternal(const WidgetKeyboardEvent& aKeyboardEvent,
                                     uint32_t aKeyFlags,
                                     bool aAllowToDispatchKeypress,
                                     uint32_t& aConsumedFlags)
 {
   aConsumedFlags = KEYEVENT_NOT_CONSUMED;
 
   // We shouldn't modify the internal WidgetKeyboardEvent.
@@ -1192,16 +1235,26 @@ TextInputProcessor::Keyup(Event* aDOMKey
     aDOMKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
   if (NS_WARN_IF(!originalKeyEvent)) {
     return NS_ERROR_INVALID_ARG;
   }
   return KeyupInternal(*originalKeyEvent, aKeyFlags, *aDoDefault);
 }
 
 nsresult
+TextInputProcessor::Keyup(const WidgetKeyboardEvent& aKeyboardEvent,
+                          uint32_t aKeyFlags,
+                          bool* aDoDefault)
+{
+  bool doDefault = false;
+  return KeyupInternal(aKeyboardEvent, aKeyFlags,
+                       aDoDefault ? *aDoDefault : doDefault);
+}
+
+nsresult
 TextInputProcessor::KeyupInternal(const WidgetKeyboardEvent& aKeyboardEvent,
                                   uint32_t aKeyFlags,
                                   bool& aDoDefault)
 {
   aDoDefault = false;
 
   // We shouldn't modify the internal WidgetKeyboardEvent.
   WidgetKeyboardEvent keyEvent(aKeyboardEvent);
@@ -1245,23 +1298,18 @@ TextInputProcessor::KeyupInternal(const 
 }
 
 NS_IMETHODIMP
 TextInputProcessor::GetModifierState(const nsAString& aModifierKeyName,
                                      bool* aActive)
 {
   MOZ_RELEASE_ASSERT(aActive, "aActive must not be null");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
-  if (!mModifierKeyDataArray) {
-    *aActive = false;
-    return NS_OK;
-  }
-  Modifiers activeModifiers = mModifierKeyDataArray->GetActiveModifiers();
   Modifiers modifier = WidgetInputEvent::GetModifier(aModifierKeyName);
-  *aActive = ((activeModifiers & modifier) != 0);
+  *aActive = ((GetActiveModifiers() & modifier) != 0);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextInputProcessor::ShareModifierStateOf(nsITextInputProcessor* aOther)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
   if (!aOther) {
--- a/dom/base/TextInputProcessor.h
+++ b/dom/base/TextInputProcessor.h
@@ -11,16 +11,18 @@
 #include "mozilla/EventForwards.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/TextEventDispatcher.h"
 #include "mozilla/TextEventDispatcherListener.h"
 #include "nsITextInputProcessor.h"
 #include "nsITextInputProcessorCallback.h"
 #include "nsTArray.h"
 
+class nsPIDOMWindowInner;
+
 namespace mozilla {
 
 namespace dom {
 class KeyboardEvent;
 } // namespace dom
 
 class TextInputProcessor final : public nsITextInputProcessor
                                , public widget::TextEventDispatcherListener
@@ -46,16 +48,51 @@ public:
 
   NS_IMETHOD_(void) WillDispatchKeyboardEvent(
                       TextEventDispatcher* aTextEventDispatcher,
                       WidgetKeyboardEvent& aKeyboardEvent,
                       uint32_t aIndexOfKeypress,
                       void* aData) override;
 
   /**
+   * TextInputProcessor manages modifier key state.  E.g., when it dispatches
+   * a modifier keydown event, activates proper modifier state and when it
+   * dispatches a modifier keyup event, inactivates proper modifier state.
+   * This returns all active modifiers in the instance.
+   */
+  Modifiers GetActiveModifiers() const
+  {
+    return mModifierKeyDataArray ?
+      mModifierKeyDataArray->GetActiveModifiers() : MODIFIER_NONE;
+  }
+
+  /**
+   * This begins transaction for fuzzing.  This must be called only by
+   * FuzzingFunctions since this skips the permission check.
+   * See explanation of nsITextInputProcessor::BeginInputTransaction() for
+   * the detail.
+   */
+  nsresult
+  BeginInputTransactionForFuzzing(nsPIDOMWindowInner* aWindow,
+                                  nsITextInputProcessorCallback* aCallback,
+                                  bool* aSucceeded);
+
+  /**
+   * The following Keydown() and KeyUp() are same as nsITextInputProcessor's
+   * same name methods except the type of event class.  See explanation in
+   * nsITextInputProcessor for the detail.
+   */
+  nsresult Keydown(const WidgetKeyboardEvent& aKeyboardEvent,
+                   uint32_t aKeyFlags,
+                   uint32_t* aConsumedFlags = nullptr);
+  nsresult Keyup(const WidgetKeyboardEvent& aKeyboardEvent,
+                 uint32_t aKeyFlags,
+                 bool* aDoDefault = nullptr);
+
+  /**
    * GuessCodeNameIndexOfPrintableKeyInUSEnglishLayout() returns CodeNameIndex
    * of a printable key which is in usual keyboard of the platform and when
    * active keyboard layout is US-English.
    * Note that this does not aware of option key mapping on macOS.
    *
    * @param aKeyValue          The key value. Must be a character which can
    *                           be inputted with US-English keyboard layout.
    * @param aLocation          The location of the key.  This is important
@@ -192,21 +229,16 @@ private:
     void ActivateModifierKey(const ModifierKeyData& aModifierKeyData);
     void InactivateModifierKey(const ModifierKeyData& aModifierKeyData);
     void ToggleModifierKey(const ModifierKeyData& aModifierKeyData);
 
   private:
     virtual ~ModifierKeyDataArray() { }
   };
 
-  Modifiers GetActiveModifiers() const
-  {
-    return mModifierKeyDataArray ?
-      mModifierKeyDataArray->GetActiveModifiers() : 0;
-  }
   void EnsureModifierKeyDataArray()
   {
     if (mModifierKeyDataArray) {
       return;
     }
     mModifierKeyDataArray = new ModifierKeyDataArray();
   }
   void ActivateModifierKey(const ModifierKeyData& aModifierKeyData)
--- a/dom/webidl/FuzzingFunctions.webidl
+++ b/dom/webidl/FuzzingFunctions.webidl
@@ -22,9 +22,91 @@ interface FuzzingFunctions {
    */
   static void cycleCollect();
 
   /**
    * Enable accessibility.
    */
   [Throws]
   static void enableAccessibility();
+
+  /**
+   * synthesizeKeyboardEvents() synthesizes a set of "keydown",
+   * "keypress" (only when it's necessary) and "keyup" events on focused
+   * widget.  This is currently not aware of APZ since this dispatches the
+   * events into focused PresShell in current process.  I.e., dispatched
+   * events won't be handled by some default action handlers which are only
+   * in the main process.  Note that this does not allow to synthesize
+   * keyboard events if this is called from a keyboard event or composition
+   * event listener.
+   *
+   * @param aKeyValue          If you want to synthesize non-printable key
+   *                           events, you need to set one of key values
+   *                           defined by "UI Events KeyboardEvent key Values".
+   *                           You can check our current support values in
+   *                           dom/events/KeyNameList.h
+   *                           If you want to synthesize printable key events,
+   *                           you can set any string value including empty
+   *                           string.
+   *                           Note that |key| value in aDictionary is always
+   *                           ignored.
+   * @param aDictionary        If you want to synthesize simple key press
+   *                           without any modifiers, you can omit this.
+   *                           Otherwise, specify this with proper values.
+   *                           If |code| is omitted or empty string, this
+   *                           guesses proper code value in US-English
+   *                           keyboard.  Otherwise, the value must be empty
+   *                           string or known code value defined by "UI Events
+   *                           KeyboardEvent code Values".  You can check our
+   *                           current support values in
+   *                           dom/events/PhysicalKeyCodeNameList.h.
+   *                           If |keyCode| is omitted or 0, this guesses
+   *                           proper keyCode value in US-English keyboard.
+   *                           If |location| is omitted or 0, this assumes
+   *                           that left modifier key is pressed if aKeyValue
+   *                           is one of such modifier keys.
+   *                           |key|, |isComposing|, |charCode| and |which|
+   *                           are always ignored.
+   *                           Modifier states like |shiftKey|, |altKey|,
+   *                           |modifierAltGraph|, |modifierCapsLock| and
+   *                           |modifierNumLock| are not adjusted for
+   *                           aKeyValue.  Please specify them manually if
+   *                           necessary.
+   *                           Note that this API does not allow to dispatch
+   *                           known key events with empty |code| value and
+   *                           0 |keyCode| value since it's unsual situation
+   *                           especially 0 |keyCode| value with known key.
+   *                           Note that when you specify only one of |code|
+   *                           and |keyCode| value, the other will be guessed
+   *                           from US-English keyboard layout.  So, if you
+   *                           want to emulate key press with another keyboard
+   *                           layout, you should specify both values.
+   *
+   * For example: 
+   *   // Synthesize "Tab" key events.
+   *   synthesizeKeyboardEvents("Tab");
+   *   // Synthesize Shift + Tab key events.
+   *   synthesizeKeyboardEvents("Tab", { shiftKey: true });
+   *   // Synthesize Control + A key events.
+   *   synthesizeKeyboardEvents("a", { controlKey: true });
+   *   // Synthesize Control + Shift + A key events.
+   *   synthesizeKeyboardEvents("A", { controlKey: true,
+   *                                   shitKey: true });
+   *   // Synthesize "Enter" key on numpad.
+   *   synthesizeKeyboardEvents("Enter", { code: "NumpadEnter" });
+   *   // Synthesize right "Shift" key.
+   *   synthesizeKeyboardEvents("Shift", { code: "ShiftRight" });
+   *   // Synthesize "1" on numpad.
+   *   synthesizeKeyboardEvents("1", { code: "Numpad1",
+   *                                   modifierNumLock: true });
+   *   // Synthesize "End" on numpad.
+   *   synthesizeKeyboardEvents("End", { code: "Numpad1" });
+   *   // Synthesize "%" key of US-English keyboard layout.
+   *   synthesizeKeyboardEvents("%", { shiftKey: true });
+   *   // Synthesize "*" key of Japanese keyboard layout.
+   *   synthesizeKeyboardEvents("*", { code: "Quote",
+   *                                   shiftKey: true,
+   *                                   keyCode: KeyboardEvent.DOM_VK_COLON });
+   */
+  [Throws]
+  static void synthesizeKeyboardEvents(DOMString aKeyValue,
+                                       optional KeyboardEventInit aDictionary);
 };
--- a/widget/TextEventDispatcher.cpp
+++ b/widget/TextEventDispatcher.cpp
@@ -595,16 +595,23 @@ TextEventDispatcher::DispatchKeyboardEve
     // XXX If there was mOnlyContentDispatch for this case, it might be useful
     //     because our chrome doesn't assume that key events are fired during
     //     composition.
   }
 
   WidgetKeyboardEvent keyEvent(true, aMessage, mWidget);
   InitEvent(keyEvent);
   keyEvent.AssignKeyEventData(aKeyboardEvent, false);
+  // Command arrays are not duplicated by AssignKeyEventData() due to
+  // both performance and footprint reasons.  So, when TextInputProcessor
+  // emulates real text input, the arrays may be initialized all commands
+  // already.  If so, we need to duplicate the arrays here.
+  if (keyEvent.mIsSynthesizedByTIP) {
+    keyEvent.AssignCommands(aKeyboardEvent);
+  }
 
   if (aStatus == nsEventStatus_eConsumeNoDefault) {
     // If the key event should be dispatched as consumed event, marking it here.
     // This is useful to prevent double action.  This is intended to the system
     // has already consumed the event but we need to dispatch the event for
     // compatibility with older version and other browsers.  So, we should not
     // stop cross process forwarding of them.
     keyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
--- a/widget/TextEventDispatcher.h
+++ b/widget/TextEventDispatcher.h
@@ -396,22 +396,20 @@ private:
     // Input transaction for automated tests which assume events are fired
     // synchronously.  I.e., keyboard events are always dispatched in the
     // current process.
     // In remote processes, this is also used when events come from the parent
     // process and are not dispatched by the instance itself for APZ-aware
     // tests because this instance won't dispatch the events via the parent
     // process again.
     eSameProcessSyncTestInputTransaction,
-    // Input transaction for Others (must be IME on B2G).  Events are fired
-    // synchronously because TextInputProcessor which is the only user of
-    // this input transaction type supports only keyboard apps on B2G.
-    // Keyboard apps on B2G doesn't want to dispatch keyboard events to
-    // chrome process. Therefore, this should dispatch key events only in
-    // the current process.
+    // Input transaction for others (currently, only FuzzingFunctions).
+    // Events are fired synchronously in the process.
+    // XXX Should we make this async for testing default action handlers in
+    //     the main process?
     eSameProcessSyncInputTransaction
   };
 
   InputTransactionType mInputTransactionType;
 
   bool IsForTests() const
   {
     return mInputTransactionType == eAsyncTestInputTransaction ||
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -689,16 +689,44 @@ public:
     mEditCommandsForSingleLineEditorInitialized =
       aEvent.mEditCommandsForSingleLineEditorInitialized;
     mEditCommandsForMultiLineEditorInitialized =
       aEvent.mEditCommandsForMultiLineEditorInitialized;
     mEditCommandsForRichTextEditorInitialized =
       aEvent.mEditCommandsForRichTextEditorInitialized;
   }
 
+  void AssignCommands(const WidgetKeyboardEvent& aEvent)
+  {
+    mEditCommandsForSingleLineEditorInitialized =
+      aEvent.mEditCommandsForSingleLineEditorInitialized;
+    if (mEditCommandsForSingleLineEditorInitialized) {
+      mEditCommandsForSingleLineEditor =
+        aEvent.mEditCommandsForSingleLineEditor;
+    } else {
+      mEditCommandsForSingleLineEditor.Clear();
+    }
+    mEditCommandsForMultiLineEditorInitialized =
+      aEvent.mEditCommandsForMultiLineEditorInitialized;
+    if (mEditCommandsForMultiLineEditorInitialized) {
+      mEditCommandsForMultiLineEditor =
+        aEvent.mEditCommandsForMultiLineEditor;
+    } else {
+      mEditCommandsForMultiLineEditor.Clear();
+    }
+    mEditCommandsForRichTextEditorInitialized =
+      aEvent.mEditCommandsForRichTextEditorInitialized;
+    if (mEditCommandsForRichTextEditorInitialized) {
+      mEditCommandsForRichTextEditor =
+        aEvent.mEditCommandsForRichTextEditor;
+    } else {
+      mEditCommandsForRichTextEditor.Clear();
+    }
+  }
+
 private:
   static const char16_t* const kKeyNames[];
   static const char16_t* const kCodeNames[];
   typedef nsDataHashtable<nsStringHashKey,
                           KeyNameIndex> KeyNameIndexHashtable;
   typedef nsDataHashtable<nsStringHashKey,
                           CodeNameIndex> CodeNameIndexHashtable;
   static KeyNameIndexHashtable* sKeyNameIndexHashtable;