/* -*- 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 "js/GCAPI.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/Sprintf.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TextInputProcessor.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/KeyboardEvent.h"
#include "nsFocusManager.h"
#include "nsIAccessibilityService.h"
#include "nsITimer.h"
#include "nsJSEnvironment.h"
#include "nsPIDOMWindow.h"
#include "xpcAccessibilityService.h"

namespace mozilla::dom {

/* static */
void FuzzingFunctions::GarbageCollect(const GlobalObject&) {
  nsJSContext::GarbageCollectNow(JS::GCReason::COMPONENT_UTILS,
                                 nsJSContext::NonShrinkingGC);
}

/* static */
void FuzzingFunctions::GarbageCollectCompacting(const GlobalObject&) {
  nsJSContext::GarbageCollectNow(JS::GCReason::COMPONENT_UTILS,
                                 nsJSContext::ShrinkingGC);
}

/* static */
void FuzzingFunctions::Crash(const GlobalObject& aGlobalObject,
                             const nsAString& aKeyValue) {
  char msgbuf[250];

  SprintfLiteral(msgbuf, "%s", NS_ConvertUTF16toUTF8(aKeyValue).get());
  if (aKeyValue.Length() >= sizeof(msgbuf)) {
    // Update the end of a truncated message to '...'.
    strcpy(&msgbuf[sizeof(msgbuf) - 4], "...");
  }
  MOZ_CRASH_UNSAFE_PRINTF("%s", msgbuf);
}

/* static */
void FuzzingFunctions::KillGPUProcess(const GlobalObject&) {
  ContentChild::GetSingleton()->SendKillGPUProcess();
}

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

void FuzzingFunctions::MemoryPressure(const GlobalObject&) {
  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
  os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
}

/* static */
void FuzzingFunctions::SignalIPCReady(const GlobalObject&) {
#ifdef FUZZING_SNAPSHOT
  ContentChild::GetSingleton()->SendSignalFuzzingReady();
#endif
}

/* 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;

  constexpr ModifierKey(Modifier aModifier, KeyNameIndex aKeyNameIndex,
                        bool aLockable)
      : mModifier(aModifier),
        mKeyNameIndex(aKeyNameIndex),
        mLockable(aLockable) {}
};

static constexpr ModifierKey kModifierKeys[] = {
    {MODIFIER_ALT, KEY_NAME_INDEX_Alt, false},
    {MODIFIER_ALTGRAPH, KEY_NAME_INDEX_AltGraph, false},
    {MODIFIER_CONTROL, KEY_NAME_INDEX_Control, false},
    {MODIFIER_FN, KEY_NAME_INDEX_Fn, false},
    {MODIFIER_META, KEY_NAME_INDEX_Meta, false},
    {MODIFIER_SHIFT, KEY_NAME_INDEX_Shift, false},
    {MODIFIER_SYMBOL, KEY_NAME_INDEX_Symbol, false},
    {MODIFIER_CAPSLOCK, KEY_NAME_INDEX_CapsLock, true},
    {MODIFIER_FNLOCK, KEY_NAME_INDEX_FnLock, true},
    {MODIFIER_NUMLOCK, KEY_NAME_INDEX_NumLock, true},
    {MODIFIER_SCROLLLOCK, KEY_NAME_INDEX_ScrollLock, true},
    {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& aGlobalObject, 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(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 in a DOM window which is in-process top one.
  // For emulating user input, this is better than dispatching the events in
  // the caller's DOM window because this approach can test the path redirecting
  // the events to focused subdocument too.  However, for now, we cannot
  // dispatch it via another process without big changes.  Therefore, we should
  // use in-process top window instead.  If you need to test the path in the
  // parent process to, please file a feature request bug.
  nsCOMPtr<nsPIDOMWindowInner> windowInner =
      do_QueryInterface(aGlobalObject.GetAsSupports());
  if (!windowInner) {
    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
    return;
  }

  nsPIDOMWindowOuter* inProcessTopWindowOuter =
      windowInner->GetInProcessScriptableTop();
  if (NS_WARN_IF(!inProcessTopWindowOuter)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  nsIDocShell* docShell = inProcessTopWindowOuter->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> inProcessTopWindowInner =
      inProcessTopWindowOuter->EnsureInnerWindow();
  if (NS_WARN_IF(!inProcessTopWindowInner)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  RefPtr<TextInputProcessor> textInputProcessor = new TextInputProcessor();
  bool beganInputTransaction = false;
  aRv = textInputProcessor->BeginInputTransactionForFuzzing(
      inProcessTopWindowInner, 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.
  // MOZ_KnownLive(event.mWidget) is safe because `event` is an instance in
  // the stack, and `mWidget` is `nsCOMPtr<nsIWidget>`.
  Modifiers activatedModifiers = ActivateModifiers(
      textInputProcessor, event.mModifiers, MOZ_KnownLive(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.
  // MOZ_KnownLive(event.mWidget) is safe because `event` is an instance in
  // the stack, and `mWidget` is `nsCOMPtr<nsIWidget>`.
  InactivateModifiers(textInputProcessor, activatedModifiers,
                      MOZ_KnownLive(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
}

static void SpinEventLoopForCallback(nsITimer* aTimer, void* aClosure) {
  *static_cast<bool*>(aClosure) = true;
}

/* static */
void FuzzingFunctions::SpinEventLoopFor(const GlobalObject&,
                                        uint32_t aMilliseconds) {
  bool didRun = false;
  nsCOMPtr<nsITimer> timer = NS_NewTimer();
  nsresult rv = timer->InitWithNamedFuncCallback(
      SpinEventLoopForCallback, &didRun, aMilliseconds, nsITimer::TYPE_ONE_SHOT,
      "FuzzingFunctions::SpinEventLoopFor"_ns);
  if (NS_FAILED(rv)) {
    return;
  }

  SpinEventLoopUntil("FuzzingFunctions::SpinEventLoopFor"_ns,
                     [&]() { return didRun; });

  // Ensure the timer is stopped in case we're shutting down the process and
  // didn't get a chance to spin the event loop long enough.
  timer->Cancel();
}

}  // namespace mozilla::dom
