Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "mozilla/dom/KeyboardEvent.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Sprintf.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TextInputProcessor.h"
#include "nsFocusManager.h"
#include "nsIAccessibilityService.h"
#include "nsPIDOMWindow.h"
#include "xpcAccessibilityService.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "nsITimer.h"
#ifdef FUZZING_SNAPSHOT
# include "mozilla/dom/ContentChild.h"
#endif
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::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;
ModifierKey(Modifier aModifier, KeyNameIndex aKeyNameIndex, bool aLockable)
: mModifier(aModifier),
mKeyNameIndex(aKeyNameIndex),
mLockable(aLockable) {}
};
MOZ_RUNINIT 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_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& 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");
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