/* -*- 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" #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 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 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& 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(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 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 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 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 inProcessTopWindowInner = inProcessTopWindowOuter->EnsureInnerWindow(); if (NS_WARN_IF(!inProcessTopWindowInner)) { aRv.Throw(NS_ERROR_FAILURE); return; } RefPtr 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`. 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`. 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 } } // namespace mozilla::dom