dom/base/FuzzingFunctions.cpp
author Boris Zbarsky <bzbarsky@mit.edu>
Mon, 19 Nov 2018 20:17:53 -0500
changeset 503588 a1e826769668287bd7250e85fa529a6c66bfb495
parent 495140 ade489313a3d24e51837775e344510de4b144e0d
child 505383 6f3709b3878117466168c40affa7bca0b60cf75b
permissions -rw-r--r--
Bug 1507540 part 2. Use more notxpcom attributes in docshell/. r=smaug

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

#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&)
{
  nsJSContext::GarbageCollectNow(JS::gcreason::COMPONENT_UTILS,
                                 nsJSContext::NonIncrementalGC,
                                 nsJSContext::NonShrinkingGC);
}

/* static */ void
FuzzingFunctions::CycleCollect(const GlobalObject&)
{
  nsJSContext::CycleCollectNow();
}

/* static */ void
FuzzingFunctions::EnableAccessibility(const GlobalObject&,
                                      ErrorResult& aRv)
{
  RefPtr<nsIAccessibilityService> a11y;
  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();
  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