1280 lines
42 KiB
C++
1280 lines
42 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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 "EditorEventListener.h"
|
|
|
|
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
|
|
#include "mozilla/EditorBase.h" // for EditorBase, etc.
|
|
#include "mozilla/EventListenerManager.h" // for EventListenerManager
|
|
#include "mozilla/IMEStateManager.h" // for IMEStateManager
|
|
#include "mozilla/Preferences.h" // for Preferences
|
|
#include "mozilla/TextEvents.h" // for WidgetCompositionEvent
|
|
#include "mozilla/dom/Element.h" // for Element
|
|
#include "mozilla/dom/Event.h" // for Event
|
|
#include "mozilla/dom/EventTarget.h" // for EventTarget
|
|
#include "mozilla/dom/Selection.h"
|
|
#include "nsAString.h"
|
|
#include "nsCaret.h" // for nsCaret
|
|
#include "nsDebug.h" // for NS_ENSURE_TRUE, etc.
|
|
#include "nsFocusManager.h" // for nsFocusManager
|
|
#include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::input
|
|
#include "nsIClipboard.h" // for nsIClipboard, etc.
|
|
#include "nsIContent.h" // for nsIContent
|
|
#include "nsIController.h" // for nsIController
|
|
#include "nsID.h"
|
|
#include "mozilla/dom/DOMStringList.h"
|
|
#include "mozilla/dom/DataTransfer.h"
|
|
#include "nsIDOMDocument.h" // for nsIDOMDocument
|
|
#include "nsIDOMDragEvent.h" // for nsIDOMDragEvent
|
|
#include "nsIDOMElement.h" // for nsIDOMElement
|
|
#include "nsIDOMEvent.h" // for nsIDOMEvent
|
|
#include "nsIDOMEventTarget.h" // for nsIDOMEventTarget
|
|
#include "nsIDOMKeyEvent.h" // for nsIDOMKeyEvent
|
|
#include "nsIDOMMouseEvent.h" // for nsIDOMMouseEvent
|
|
#include "nsIDOMNode.h" // for nsIDOMNode
|
|
#include "nsIDocument.h" // for nsIDocument
|
|
#include "nsIEditor.h" // for EditorBase::GetSelection, etc.
|
|
#include "nsIEditorIMESupport.h"
|
|
#include "nsIEditorMailSupport.h" // for nsIEditorMailSupport
|
|
#include "nsIFocusManager.h" // for nsIFocusManager
|
|
#include "nsIFormControl.h" // for nsIFormControl, etc.
|
|
#include "nsIHTMLEditor.h" // for nsIHTMLEditor
|
|
#include "nsINode.h" // for nsINode, ::NODE_IS_EDITABLE, etc.
|
|
#include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc.
|
|
#include "nsIPresShell.h" // for nsIPresShell
|
|
#include "nsISelectionController.h" // for nsISelectionController, etc.
|
|
#include "nsITransferable.h" // for kFileMime, kHTMLMime, etc.
|
|
#include "nsIWidget.h" // for nsIWidget
|
|
#include "nsLiteralString.h" // for NS_LITERAL_STRING
|
|
#include "nsPIWindowRoot.h" // for nsPIWindowRoot
|
|
#include "nsPrintfCString.h" // for nsPrintfCString
|
|
#include "nsRange.h"
|
|
#include "nsServiceManagerUtils.h" // for do_GetService
|
|
#include "nsString.h" // for nsAutoString
|
|
#include "nsQueryObject.h" // for do_QueryObject
|
|
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
|
#include "nsContentUtils.h" // for nsContentUtils, etc.
|
|
#include "nsIBidiKeyboard.h" // for nsIBidiKeyboard
|
|
#endif
|
|
|
|
#include "mozilla/dom/TabParent.h"
|
|
|
|
class nsPresContext;
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace dom;
|
|
|
|
static void
|
|
DoCommandCallback(Command aCommand, void* aData)
|
|
{
|
|
nsIDocument* doc = static_cast<nsIDocument*>(aData);
|
|
nsPIDOMWindowOuter* win = doc->GetWindow();
|
|
if (!win) {
|
|
return;
|
|
}
|
|
nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
|
|
|
|
nsCOMPtr<nsIController> controller;
|
|
root->GetControllerForCommand(commandStr, getter_AddRefs(controller));
|
|
if (!controller) {
|
|
return;
|
|
}
|
|
|
|
bool commandEnabled;
|
|
nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
if (commandEnabled) {
|
|
controller->DoCommand(commandStr);
|
|
}
|
|
}
|
|
|
|
EditorEventListener::EditorEventListener()
|
|
: mEditorBase(nullptr)
|
|
, mCommitText(false)
|
|
, mInTransaction(false)
|
|
, mMouseDownOrUpConsumedByIME(false)
|
|
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
|
, mHaveBidiKeyboards(false)
|
|
, mShouldSwitchTextDirection(false)
|
|
, mSwitchToRTL(false)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
EditorEventListener::~EditorEventListener()
|
|
{
|
|
if (mEditorBase) {
|
|
NS_WARNING("We're not uninstalled");
|
|
Disconnect();
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::Connect(EditorBase* aEditorBase)
|
|
{
|
|
NS_ENSURE_ARG(aEditorBase);
|
|
|
|
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
|
nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
|
|
if (bidiKeyboard) {
|
|
bool haveBidiKeyboards = false;
|
|
bidiKeyboard->GetHaveBidiKeyboards(&haveBidiKeyboards);
|
|
mHaveBidiKeyboards = haveBidiKeyboards;
|
|
}
|
|
#endif
|
|
|
|
mEditorBase = aEditorBase;
|
|
|
|
nsresult rv = InstallToEditor();
|
|
if (NS_FAILED(rv)) {
|
|
Disconnect();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::InstallToEditor()
|
|
{
|
|
NS_PRECONDITION(mEditorBase, "The caller must set mEditorBase");
|
|
|
|
nsCOMPtr<EventTarget> piTarget = mEditorBase->GetDOMEventTarget();
|
|
NS_ENSURE_TRUE(piTarget, NS_ERROR_FAILURE);
|
|
|
|
// register the event listeners with the listener manager
|
|
EventListenerManager* elmP = piTarget->GetOrCreateListenerManager();
|
|
NS_ENSURE_STATE(elmP);
|
|
|
|
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("keydown"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("keyup"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
#endif
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("keypress"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("dragenter"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("dragover"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("dragexit"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("drop"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
// XXX We should add the mouse event listeners as system event group.
|
|
// E.g., web applications cannot prevent middle mouse paste by
|
|
// preventDefault() of click event at bubble phase.
|
|
// However, if we do so, all click handlers in any frames and frontend
|
|
// code need to check if it's editable. It makes easier create new bugs.
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("mousedown"),
|
|
TrustedEventsAtCapture());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("mouseup"),
|
|
TrustedEventsAtCapture());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("click"),
|
|
TrustedEventsAtCapture());
|
|
// Focus event doesn't bubble so adding the listener to capturing phase.
|
|
// Make sure this works after bug 235441 gets fixed.
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("blur"),
|
|
TrustedEventsAtCapture());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("focus"),
|
|
TrustedEventsAtCapture());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("text"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("compositionstart"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->AddEventListenerByType(this,
|
|
NS_LITERAL_STRING("compositionend"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
EditorEventListener::Disconnect()
|
|
{
|
|
if (DetachedFromEditor()) {
|
|
return;
|
|
}
|
|
UninstallFromEditor();
|
|
|
|
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
if (fm) {
|
|
nsCOMPtr<nsIDOMElement> domFocus;
|
|
fm->GetFocusedElement(getter_AddRefs(domFocus));
|
|
nsCOMPtr<nsINode> focusedElement = do_QueryInterface(domFocus);
|
|
mozilla::dom::Element* root = mEditorBase->GetRoot();
|
|
if (focusedElement && root &&
|
|
nsContentUtils::ContentIsDescendantOf(focusedElement, root)) {
|
|
// Reset the Selection ancestor limiter and SelectionController state
|
|
// that EditorBase::InitializeSelection set up.
|
|
mEditorBase->FinalizeSelection();
|
|
}
|
|
}
|
|
|
|
mEditorBase = nullptr;
|
|
}
|
|
|
|
void
|
|
EditorEventListener::UninstallFromEditor()
|
|
{
|
|
nsCOMPtr<EventTarget> piTarget = mEditorBase->GetDOMEventTarget();
|
|
if (!piTarget) {
|
|
return;
|
|
}
|
|
|
|
EventListenerManager* elmP = piTarget->GetOrCreateListenerManager();
|
|
if (!elmP) {
|
|
return;
|
|
}
|
|
|
|
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("keydown"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("keyup"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
#endif
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("keypress"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("dragenter"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("dragover"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("dragexit"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("drop"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("mousedown"),
|
|
TrustedEventsAtCapture());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("mouseup"),
|
|
TrustedEventsAtCapture());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("click"),
|
|
TrustedEventsAtCapture());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("blur"),
|
|
TrustedEventsAtCapture());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("focus"),
|
|
TrustedEventsAtCapture());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("text"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("compositionstart"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
elmP->RemoveEventListenerByType(this,
|
|
NS_LITERAL_STRING("compositionend"),
|
|
TrustedEventsAtSystemGroupBubble());
|
|
}
|
|
|
|
already_AddRefed<nsIPresShell>
|
|
EditorEventListener::GetPresShell()
|
|
{
|
|
MOZ_ASSERT(!DetachedFromEditor());
|
|
return mEditorBase->GetPresShell();
|
|
}
|
|
|
|
nsPresContext*
|
|
EditorEventListener::GetPresContext()
|
|
{
|
|
nsCOMPtr<nsIPresShell> presShell = GetPresShell();
|
|
return presShell ? presShell->GetPresContext() : nullptr;
|
|
}
|
|
|
|
nsIContent*
|
|
EditorEventListener::GetFocusedRootContent()
|
|
{
|
|
MOZ_ASSERT(!DetachedFromEditor());
|
|
nsCOMPtr<nsIContent> focusedContent = mEditorBase->GetFocusedContent();
|
|
if (!focusedContent) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsIDocument* composedDoc = focusedContent->GetComposedDoc();
|
|
NS_ENSURE_TRUE(composedDoc, nullptr);
|
|
|
|
if (composedDoc->HasFlag(NODE_IS_EDITABLE)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return focusedContent;
|
|
}
|
|
|
|
bool
|
|
EditorEventListener::EditorHasFocus()
|
|
{
|
|
MOZ_ASSERT(!DetachedFromEditor());
|
|
nsCOMPtr<nsIContent> focusedContent = mEditorBase->GetFocusedContent();
|
|
if (!focusedContent) {
|
|
return false;
|
|
}
|
|
nsIDocument* composedDoc = focusedContent->GetComposedDoc();
|
|
return !!composedDoc;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(EditorEventListener, nsIDOMEventListener)
|
|
|
|
bool
|
|
EditorEventListener::DetachedFromEditor() const
|
|
{
|
|
return !mEditorBase;
|
|
}
|
|
|
|
bool
|
|
EditorEventListener::DetachedFromEditorOrDefaultPrevented(
|
|
WidgetEvent* aWidgetEvent) const
|
|
{
|
|
return NS_WARN_IF(!aWidgetEvent) || DetachedFromEditor() ||
|
|
aWidgetEvent->DefaultPrevented();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
EditorEventListener::HandleEvent(nsIDOMEvent* aEvent)
|
|
{
|
|
// Let's handle each event with the message of the internal event of the
|
|
// coming event. If the DOM event was created with improper interface,
|
|
// e.g., keydown event is created with |new MouseEvent("keydown", {});|,
|
|
// its message is always 0. Therefore, we can ban such strange event easy.
|
|
// However, we need to handle strange "focus" and "blur" event. See the
|
|
// following code of this switch statement.
|
|
// NOTE: Each event handler may require specific event interface. Before
|
|
// calling it, this queries the specific interface. If it would fail,
|
|
// each event handler would just ignore the event. So, in this method,
|
|
// you don't need to check if the QI succeeded before each call.
|
|
WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
|
|
switch (internalEvent->mMessage) {
|
|
// dragenter
|
|
case eDragEnter: {
|
|
nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
|
|
return DragEnter(dragEvent);
|
|
}
|
|
// dragover
|
|
case eDragOver: {
|
|
nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
|
|
return DragOver(dragEvent);
|
|
}
|
|
// dragexit
|
|
case eDragExit: {
|
|
nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
|
|
return DragExit(dragEvent);
|
|
}
|
|
// drop
|
|
case eDrop: {
|
|
nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
|
|
return Drop(dragEvent);
|
|
}
|
|
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
|
// keydown
|
|
case eKeyDown: {
|
|
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
|
|
return KeyDown(keyEvent);
|
|
}
|
|
// keyup
|
|
case eKeyUp: {
|
|
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
|
|
return KeyUp(keyEvent);
|
|
}
|
|
#endif // #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
|
// keypress
|
|
case eKeyPress: {
|
|
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
|
|
return KeyPress(keyEvent);
|
|
}
|
|
// mousedown
|
|
case eMouseDown: {
|
|
nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
|
|
NS_ENSURE_TRUE(mouseEvent, NS_OK);
|
|
// EditorEventListener may receive (1) all mousedown, mouseup and click
|
|
// events, (2) only mousedown event or (3) only mouseup event.
|
|
// mMouseDownOrUpConsumedByIME is used only for ignoring click event if
|
|
// preceding mousedown and/or mouseup event is consumed by IME.
|
|
// Therefore, even if case #2 or case #3 occurs,
|
|
// mMouseDownOrUpConsumedByIME is true here. Therefore, we should always
|
|
// overwrite it here.
|
|
mMouseDownOrUpConsumedByIME = NotifyIMEOfMouseButtonEvent(mouseEvent);
|
|
return mMouseDownOrUpConsumedByIME ? NS_OK : MouseDown(mouseEvent);
|
|
}
|
|
// mouseup
|
|
case eMouseUp: {
|
|
nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
|
|
NS_ENSURE_TRUE(mouseEvent, NS_OK);
|
|
// See above comment in the eMouseDown case, first.
|
|
// This code assumes that case #1 is occuring. However, if case #3 may
|
|
// occurs after case #2 and the mousedown is consumed,
|
|
// mMouseDownOrUpConsumedByIME is true even though EditorEventListener
|
|
// has not received the preceding mousedown event of this mouseup event.
|
|
// So, mMouseDownOrUpConsumedByIME may be invalid here. However,
|
|
// this is not a matter because mMouseDownOrUpConsumedByIME is referred
|
|
// only by eMouseClick case but click event is fired only in case #1.
|
|
// So, before a click event is fired, mMouseDownOrUpConsumedByIME is
|
|
// always initialized in the eMouseDown case if it's referred.
|
|
if (NotifyIMEOfMouseButtonEvent(mouseEvent)) {
|
|
mMouseDownOrUpConsumedByIME = true;
|
|
}
|
|
return mMouseDownOrUpConsumedByIME ? NS_OK : MouseUp(mouseEvent);
|
|
}
|
|
// click
|
|
case eMouseClick: {
|
|
nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
|
|
NS_ENSURE_TRUE(mouseEvent, NS_OK);
|
|
// If the preceding mousedown event or mouseup event was consumed,
|
|
// editor shouldn't handle this click event.
|
|
if (mMouseDownOrUpConsumedByIME) {
|
|
mMouseDownOrUpConsumedByIME = false;
|
|
mouseEvent->AsEvent()->PreventDefault();
|
|
return NS_OK;
|
|
}
|
|
return MouseClick(mouseEvent);
|
|
}
|
|
// focus
|
|
case eFocus:
|
|
return Focus(internalEvent);
|
|
// blur
|
|
case eBlur:
|
|
return Blur(internalEvent);
|
|
// text
|
|
case eCompositionChange:
|
|
return HandleChangeComposition(internalEvent->AsCompositionEvent());
|
|
// compositionstart
|
|
case eCompositionStart:
|
|
return HandleStartComposition(internalEvent->AsCompositionEvent());
|
|
// compositionend
|
|
case eCompositionEnd:
|
|
HandleEndComposition(internalEvent->AsCompositionEvent());
|
|
return NS_OK;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
nsAutoString eventType;
|
|
aEvent->GetType(eventType);
|
|
// We should accept "focus" and "blur" event even if it's synthesized with
|
|
// wrong interface for compatibility with older Gecko.
|
|
if (eventType.EqualsLiteral("focus")) {
|
|
return Focus(internalEvent);
|
|
}
|
|
if (eventType.EqualsLiteral("blur")) {
|
|
return Blur(internalEvent);
|
|
}
|
|
#ifdef DEBUG
|
|
nsPrintfCString assertMessage("Editor doesn't handle \"%s\" event "
|
|
"because its internal event doesn't have proper message",
|
|
NS_ConvertUTF16toUTF8(eventType).get());
|
|
NS_ASSERTION(false, assertMessage.get());
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
|
namespace {
|
|
|
|
// This function is borrowed from Chromium's ImeInput::IsCtrlShiftPressed
|
|
bool IsCtrlShiftPressed(nsIDOMKeyEvent* aEvent, bool& isRTL)
|
|
{
|
|
// To check if a user is pressing only a control key and a right-shift key
|
|
// (or a left-shift key), we use the steps below:
|
|
// 1. Check if a user is pressing a control key and a right-shift key (or
|
|
// a left-shift key).
|
|
// 2. If the condition 1 is true, we should check if there are any other
|
|
// keys pressed at the same time.
|
|
// To ignore the keys checked in 1, we set their status to 0 before
|
|
// checking the key status.
|
|
WidgetKeyboardEvent* keyboardEvent =
|
|
aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
|
|
MOZ_ASSERT(keyboardEvent,
|
|
"DOM key event's internal event must be WidgetKeyboardEvent");
|
|
|
|
if (!keyboardEvent->IsControl()) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t location = keyboardEvent->mLocation;
|
|
if (location == nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT) {
|
|
isRTL = true;
|
|
} else if (location == nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT) {
|
|
isRTL = false;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
// Scan the key status to find pressed keys. We should abandon changing the
|
|
// text direction when there are other pressed keys.
|
|
if (keyboardEvent->IsAlt() || keyboardEvent->IsOS()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
// This logic is mostly borrowed from Chromium's
|
|
// RenderWidgetHostViewWin::OnKeyEvent.
|
|
|
|
nsresult
|
|
EditorEventListener::KeyUp(nsIDOMKeyEvent* aKeyEvent)
|
|
{
|
|
if (NS_WARN_IF(!aKeyEvent) || DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mHaveBidiKeyboards) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX Why doesn't this method check if it's consumed?
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
uint32_t keyCode = 0;
|
|
aKeyEvent->GetKeyCode(&keyCode);
|
|
if ((keyCode == nsIDOMKeyEvent::DOM_VK_SHIFT ||
|
|
keyCode == nsIDOMKeyEvent::DOM_VK_CONTROL) &&
|
|
mShouldSwitchTextDirection && editorBase->IsPlaintextEditor()) {
|
|
editorBase->SwitchTextDirectionTo(mSwitchToRTL ?
|
|
nsIPlaintextEditor::eEditorRightToLeft :
|
|
nsIPlaintextEditor::eEditorLeftToRight);
|
|
mShouldSwitchTextDirection = false;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::KeyDown(nsIDOMKeyEvent* aKeyEvent)
|
|
{
|
|
if (NS_WARN_IF(!aKeyEvent) || DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mHaveBidiKeyboards) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX Why isn't this method check if it's consumed?
|
|
uint32_t keyCode = 0;
|
|
aKeyEvent->GetKeyCode(&keyCode);
|
|
if (keyCode == nsIDOMKeyEvent::DOM_VK_SHIFT) {
|
|
bool switchToRTL;
|
|
if (IsCtrlShiftPressed(aKeyEvent, switchToRTL)) {
|
|
mShouldSwitchTextDirection = true;
|
|
mSwitchToRTL = switchToRTL;
|
|
}
|
|
} else if (keyCode != nsIDOMKeyEvent::DOM_VK_CONTROL) {
|
|
// In case the user presses any other key besides Ctrl and Shift
|
|
mShouldSwitchTextDirection = false;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
#endif
|
|
|
|
nsresult
|
|
EditorEventListener::KeyPress(nsIDOMKeyEvent* aKeyEvent)
|
|
{
|
|
if (NS_WARN_IF(!aKeyEvent)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
WidgetKeyboardEvent* keypressEvent =
|
|
aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
|
|
MOZ_ASSERT(keypressEvent,
|
|
"DOM key event's internal event must be WidgetKeyboardEvent");
|
|
if (!editorBase->IsAcceptableInputEvent(keypressEvent) ||
|
|
DetachedFromEditorOrDefaultPrevented(keypressEvent)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv = editorBase->HandleKeyPressEvent(aKeyEvent);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (DetachedFromEditorOrDefaultPrevented(keypressEvent)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!ShouldHandleNativeKeyBindings(aKeyEvent)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Now, ask the native key bindings to handle the event.
|
|
nsIWidget* widget = keypressEvent->mWidget;
|
|
// If the event is created by chrome script, the widget is always nullptr.
|
|
if (!widget) {
|
|
nsCOMPtr<nsIPresShell> ps = GetPresShell();
|
|
nsPresContext* pc = ps ? ps->GetPresContext() : nullptr;
|
|
widget = pc ? pc->GetNearestWidget() : nullptr;
|
|
NS_ENSURE_TRUE(widget, NS_OK);
|
|
}
|
|
|
|
nsCOMPtr<nsIDocument> doc = editorBase->GetDocument();
|
|
bool handled = widget->ExecuteNativeKeyBinding(
|
|
nsIWidget::NativeKeyBindingsForRichTextEditor,
|
|
*keypressEvent, DoCommandCallback, doc);
|
|
if (handled) {
|
|
aKeyEvent->AsEvent()->PreventDefault();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::MouseClick(nsIDOMMouseEvent* aMouseEvent)
|
|
{
|
|
if (NS_WARN_IF(!aMouseEvent) || DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
// nothing to do if editor isn't editable or clicked on out of the editor.
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
WidgetMouseEvent* clickEvent =
|
|
aMouseEvent->AsEvent()->WidgetEventPtr()->AsMouseEvent();
|
|
if (editorBase->IsReadonly() || editorBase->IsDisabled() ||
|
|
!editorBase->IsAcceptableInputEvent(clickEvent)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Notifies clicking on editor to IMEStateManager even when the event was
|
|
// consumed.
|
|
if (EditorHasFocus()) {
|
|
nsPresContext* presContext = GetPresContext();
|
|
if (presContext) {
|
|
IMEStateManager::OnClickInEditor(presContext, GetFocusedRootContent(),
|
|
aMouseEvent);
|
|
if (DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DetachedFromEditorOrDefaultPrevented(clickEvent)) {
|
|
// We're done if 'preventdefault' is true (see for example bug 70698).
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we got a mouse down inside the editing area, we should force the
|
|
// IME to commit before we change the cursor position
|
|
editorBase->ForceCompositionEnd();
|
|
if (DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
int16_t button = -1;
|
|
aMouseEvent->GetButton(&button);
|
|
if (button == 1) {
|
|
return HandleMiddleClickPaste(aMouseEvent);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::HandleMiddleClickPaste(nsIDOMMouseEvent* aMouseEvent)
|
|
{
|
|
MOZ_ASSERT(aMouseEvent);
|
|
MOZ_ASSERT(!DetachedFromEditorOrDefaultPrevented(
|
|
aMouseEvent->AsEvent()->WidgetEventPtr()));
|
|
|
|
if (!Preferences::GetBool("middlemouse.paste", false)) {
|
|
// Middle click paste isn't enabled.
|
|
return NS_OK;
|
|
}
|
|
|
|
// Set the selection to the point under the mouse cursor:
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
if (NS_FAILED(aMouseEvent->GetRangeParent(getter_AddRefs(parent)))) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
int32_t offset = 0;
|
|
if (NS_FAILED(aMouseEvent->GetRangeOffset(&offset))) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
RefPtr<Selection> selection = editorBase->GetSelection();
|
|
if (selection) {
|
|
selection->Collapse(parent, offset);
|
|
}
|
|
|
|
// If the ctrl key is pressed, we'll do paste as quotation.
|
|
// Would've used the alt key, but the kde wmgr treats alt-middle specially.
|
|
bool ctrlKey = false;
|
|
aMouseEvent->GetCtrlKey(&ctrlKey);
|
|
|
|
nsCOMPtr<nsIEditorMailSupport> mailEditor;
|
|
if (ctrlKey) {
|
|
mailEditor = do_QueryObject(editorBase);
|
|
}
|
|
|
|
nsresult rv;
|
|
int32_t clipboard = nsIClipboard::kGlobalClipboard;
|
|
nsCOMPtr<nsIClipboard> clipboardService =
|
|
do_GetService("@mozilla.org/widget/clipboard;1", &rv);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
bool selectionSupported;
|
|
rv = clipboardService->SupportsSelectionClipboard(&selectionSupported);
|
|
if (NS_SUCCEEDED(rv) && selectionSupported) {
|
|
clipboard = nsIClipboard::kSelectionClipboard;
|
|
}
|
|
}
|
|
|
|
if (mailEditor) {
|
|
mailEditor->PasteAsQuotation(clipboard);
|
|
} else {
|
|
editorBase->Paste(clipboard);
|
|
}
|
|
|
|
// Prevent the event from propagating up to be possibly handled
|
|
// again by the containing window:
|
|
aMouseEvent->AsEvent()->StopPropagation();
|
|
aMouseEvent->AsEvent()->PreventDefault();
|
|
|
|
// We processed the event, whether drop/paste succeeded or not
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
EditorEventListener::NotifyIMEOfMouseButtonEvent(
|
|
nsIDOMMouseEvent* aMouseEvent)
|
|
{
|
|
MOZ_ASSERT(aMouseEvent);
|
|
|
|
if (!EditorHasFocus()) {
|
|
return false;
|
|
}
|
|
|
|
nsPresContext* presContext = GetPresContext();
|
|
NS_ENSURE_TRUE(presContext, false);
|
|
return IMEStateManager::OnMouseButtonEventInEditor(presContext,
|
|
GetFocusedRootContent(),
|
|
aMouseEvent);
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent)
|
|
{
|
|
// FYI: We don't need to check if it's already consumed here because
|
|
// we need to commit composition at mouse button operation.
|
|
// FYI: This may be called by HTMLEditorEventListener::MouseDown() even
|
|
// when the event is not acceptable for committing composition.
|
|
if (DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
editorBase->ForceCompositionEnd();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::HandleChangeComposition(
|
|
WidgetCompositionEvent* aCompositionChangeEvent)
|
|
{
|
|
MOZ_ASSERT(!aCompositionChangeEvent->DefaultPrevented(),
|
|
"eCompositionChange event shouldn't be cancelable");
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
if (DetachedFromEditor() ||
|
|
!editorBase->IsAcceptableInputEvent(aCompositionChangeEvent)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// if we are readonly or disabled, then do nothing.
|
|
if (editorBase->IsReadonly() || editorBase->IsDisabled()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
return editorBase->UpdateIMEComposition(aCompositionChangeEvent);
|
|
}
|
|
|
|
/**
|
|
* Drag event implementation
|
|
*/
|
|
|
|
nsresult
|
|
EditorEventListener::DragEnter(nsIDOMDragEvent* aDragEvent)
|
|
{
|
|
if (NS_WARN_IF(!aDragEvent) || DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = GetPresShell();
|
|
NS_ENSURE_TRUE(presShell, NS_OK);
|
|
|
|
if (!mCaret) {
|
|
mCaret = new nsCaret();
|
|
mCaret->Init(presShell);
|
|
mCaret->SetCaretReadOnly(true);
|
|
// This is to avoid the requirement that the Selection is Collapsed which
|
|
// it can't be when dragging a selection in the same shell.
|
|
// See nsCaret::IsVisible().
|
|
mCaret->SetVisibilityDuringSelection(true);
|
|
}
|
|
|
|
presShell->SetCaret(mCaret);
|
|
|
|
return DragOver(aDragEvent);
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::DragOver(nsIDOMDragEvent* aDragEvent)
|
|
{
|
|
if (NS_WARN_IF(!aDragEvent) ||
|
|
DetachedFromEditorOrDefaultPrevented(
|
|
aDragEvent->AsEvent()->WidgetEventPtr())) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
aDragEvent->GetRangeParent(getter_AddRefs(parent));
|
|
nsCOMPtr<nsIContent> dropParent = do_QueryInterface(parent);
|
|
NS_ENSURE_TRUE(dropParent, NS_ERROR_FAILURE);
|
|
|
|
if (dropParent->IsEditable() && CanDrop(aDragEvent)) {
|
|
aDragEvent->AsEvent()->PreventDefault(); // consumed
|
|
|
|
if (!mCaret) {
|
|
return NS_OK;
|
|
}
|
|
|
|
int32_t offset = 0;
|
|
nsresult rv = aDragEvent->GetRangeOffset(&offset);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mCaret->SetVisible(true);
|
|
mCaret->SetCaretPosition(parent, offset);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!IsFileControlTextBox()) {
|
|
// This is needed when dropping on an input, to prevent the editor for
|
|
// the editable parent from receiving the event.
|
|
aDragEvent->AsEvent()->StopPropagation();
|
|
}
|
|
|
|
if (mCaret) {
|
|
mCaret->SetVisible(false);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
EditorEventListener::CleanupDragDropCaret()
|
|
{
|
|
if (!mCaret) {
|
|
return;
|
|
}
|
|
|
|
mCaret->SetVisible(false); // hide it, so that it turns off its timer
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = GetPresShell();
|
|
if (presShell) {
|
|
presShell->RestoreCaret();
|
|
}
|
|
|
|
mCaret->Terminate();
|
|
mCaret = nullptr;
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::DragExit(nsIDOMDragEvent* aDragEvent)
|
|
{
|
|
// XXX If aDragEvent was created by chrome script, its defaultPrevented
|
|
// may be true, though. We shouldn't handle such event but we don't
|
|
// have a way to distinguish if coming event is created by chrome script.
|
|
NS_WARNING_ASSERTION(
|
|
!aDragEvent->AsEvent()->WidgetEventPtr()->DefaultPrevented(),
|
|
"eDragExit shouldn't be cancelable");
|
|
if (NS_WARN_IF(!aDragEvent) || DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
CleanupDragDropCaret();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::Drop(nsIDOMDragEvent* aDragEvent)
|
|
{
|
|
CleanupDragDropCaret();
|
|
|
|
if (NS_WARN_IF(!aDragEvent) ||
|
|
DetachedFromEditorOrDefaultPrevented(
|
|
aDragEvent->AsEvent()->WidgetEventPtr())) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
aDragEvent->GetRangeParent(getter_AddRefs(parent));
|
|
nsCOMPtr<nsIContent> dropParent = do_QueryInterface(parent);
|
|
NS_ENSURE_TRUE(dropParent, NS_ERROR_FAILURE);
|
|
|
|
if (!dropParent->IsEditable() || !CanDrop(aDragEvent)) {
|
|
// was it because we're read-only?
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
if ((editorBase->IsReadonly() || editorBase->IsDisabled()) &&
|
|
!IsFileControlTextBox()) {
|
|
// it was decided to "eat" the event as this is the "least surprise"
|
|
// since someone else handling it might be unintentional and the
|
|
// user could probably re-drag to be not over the disabled/readonly
|
|
// editfields if that is what is desired.
|
|
return aDragEvent->AsEvent()->StopPropagation();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
aDragEvent->AsEvent()->StopPropagation();
|
|
aDragEvent->AsEvent()->PreventDefault();
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
return editorBase->InsertFromDrop(aDragEvent->AsEvent());
|
|
}
|
|
|
|
bool
|
|
EditorEventListener::CanDrop(nsIDOMDragEvent* aEvent)
|
|
{
|
|
MOZ_ASSERT(!DetachedFromEditorOrDefaultPrevented(
|
|
aEvent->AsEvent()->WidgetEventPtr()));
|
|
|
|
// if the target doc is read-only, we can't drop
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
if (editorBase->IsReadonly() || editorBase->IsDisabled()) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMDataTransfer> domDataTransfer;
|
|
aEvent->GetDataTransfer(getter_AddRefs(domDataTransfer));
|
|
nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(domDataTransfer);
|
|
NS_ENSURE_TRUE(dataTransfer, false);
|
|
|
|
nsTArray<nsString> types;
|
|
dataTransfer->GetTypes(types, *nsContentUtils::GetSystemPrincipal());
|
|
|
|
// Plaintext editors only support dropping text. Otherwise, HTML and files
|
|
// can be dropped as well.
|
|
if (!types.Contains(NS_LITERAL_STRING(kTextMime)) &&
|
|
!types.Contains(NS_LITERAL_STRING(kMozTextInternal)) &&
|
|
(editorBase->IsPlaintextEditor() ||
|
|
(!types.Contains(NS_LITERAL_STRING(kHTMLMime)) &&
|
|
!types.Contains(NS_LITERAL_STRING(kFileMime))))) {
|
|
return false;
|
|
}
|
|
|
|
// If there is no source node, this is probably an external drag and the
|
|
// drop is allowed. The later checks rely on checking if the drag target
|
|
// is the same as the drag source.
|
|
nsCOMPtr<nsIDOMNode> sourceNode;
|
|
dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode));
|
|
if (!sourceNode) {
|
|
return true;
|
|
}
|
|
|
|
// There is a source node, so compare the source documents and this document.
|
|
// Disallow drops on the same document.
|
|
|
|
nsCOMPtr<nsIDOMDocument> domdoc = editorBase->GetDOMDocument();
|
|
NS_ENSURE_TRUE(domdoc, false);
|
|
|
|
nsCOMPtr<nsIDOMDocument> sourceDoc;
|
|
nsresult rv = sourceNode->GetOwnerDocument(getter_AddRefs(sourceDoc));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
// If the source and the dest are not same document, allow to drop it always.
|
|
if (domdoc != sourceDoc) {
|
|
return true;
|
|
}
|
|
|
|
// If the source node is a remote browser, treat this as coming from a
|
|
// different document and allow the drop.
|
|
nsCOMPtr<nsIContent> sourceContent = do_QueryInterface(sourceNode);
|
|
TabParent* tp = TabParent::GetFrom(sourceContent);
|
|
if (tp) {
|
|
return true;
|
|
}
|
|
|
|
RefPtr<Selection> selection = editorBase->GetSelection();
|
|
if (!selection) {
|
|
return false;
|
|
}
|
|
|
|
// If selection is collapsed, allow to drop it always.
|
|
if (selection->Collapsed()) {
|
|
return true;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
rv = aEvent->GetRangeParent(getter_AddRefs(parent));
|
|
if (NS_FAILED(rv) || !parent) {
|
|
return false;
|
|
}
|
|
|
|
int32_t offset = 0;
|
|
rv = aEvent->GetRangeOffset(&offset);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
int32_t rangeCount;
|
|
rv = selection->GetRangeCount(&rangeCount);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
for (int32_t i = 0; i < rangeCount; i++) {
|
|
RefPtr<nsRange> range = selection->GetRangeAt(i);
|
|
if (!range) {
|
|
// Don't bail yet, iterate through them all
|
|
continue;
|
|
}
|
|
|
|
bool inRange = true;
|
|
range->IsPointInRange(parent, offset, &inRange);
|
|
if (inRange) {
|
|
// Okay, now you can bail, we are over the orginal selection
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::HandleStartComposition(
|
|
WidgetCompositionEvent* aCompositionStartEvent)
|
|
{
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
if (DetachedFromEditor() ||
|
|
!editorBase->IsAcceptableInputEvent(aCompositionStartEvent)) {
|
|
return NS_OK;
|
|
}
|
|
// Although, "compositionstart" should be cancelable, but currently,
|
|
// eCompositionStart event coming from widget is not cancelable.
|
|
MOZ_ASSERT(!aCompositionStartEvent->DefaultPrevented(),
|
|
"eCompositionStart shouldn't be cancelable");
|
|
return editorBase->BeginIMEComposition(aCompositionStartEvent);
|
|
}
|
|
|
|
void
|
|
EditorEventListener::HandleEndComposition(
|
|
WidgetCompositionEvent* aCompositionEndEvent)
|
|
{
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
if (DetachedFromEditor() ||
|
|
!editorBase->IsAcceptableInputEvent(aCompositionEndEvent)) {
|
|
return;
|
|
}
|
|
MOZ_ASSERT(!aCompositionEndEvent->DefaultPrevented(),
|
|
"eCompositionEnd shouldn't be cancelable");
|
|
editorBase->EndIMEComposition();
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::Focus(WidgetEvent* aFocusEvent)
|
|
{
|
|
if (NS_WARN_IF(!aFocusEvent) || DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX If aFocusEvent was created by chrome script, its defaultPrevented
|
|
// may be true, though. We shouldn't handle such event but we don't
|
|
// have a way to distinguish if coming event is created by chrome script.
|
|
NS_WARNING_ASSERTION(!aFocusEvent->DefaultPrevented(),
|
|
"eFocus event shouldn't be cancelable");
|
|
|
|
// Don't turn on selection and caret when the editor is disabled.
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
if (editorBase->IsDisabled()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Spell check a textarea the first time that it is focused.
|
|
SpellCheckIfNeeded();
|
|
if (!editorBase) {
|
|
// In e10s, this can cause us to flush notifications, which can destroy
|
|
// the node we're about to focus.
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMEventTarget> target = aFocusEvent->GetDOMEventTarget();
|
|
nsCOMPtr<nsINode> node = do_QueryInterface(target);
|
|
NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED);
|
|
|
|
// If the target is a document node but it's not editable, we should ignore
|
|
// it because actual focused element's event is going to come.
|
|
if (node->IsNodeOfType(nsINode::eDOCUMENT) &&
|
|
!node->HasFlag(NODE_IS_EDITABLE)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (node->IsNodeOfType(nsINode::eCONTENT)) {
|
|
// XXX If the focus event target is a form control in contenteditable
|
|
// element, perhaps, the parent HTML editor should do nothing by this
|
|
// handler. However, FindSelectionRoot() returns the root element of the
|
|
// contenteditable editor. So, the editableRoot value is invalid for
|
|
// the plain text editor, and it will be set to the wrong limiter of
|
|
// the selection. However, fortunately, actual bugs are not found yet.
|
|
nsCOMPtr<nsIContent> editableRoot = editorBase->FindSelectionRoot(node);
|
|
|
|
// make sure that the element is really focused in case an earlier
|
|
// listener in the chain changed the focus.
|
|
if (editableRoot) {
|
|
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
NS_ENSURE_TRUE(fm, NS_OK);
|
|
|
|
nsCOMPtr<nsIDOMElement> element;
|
|
fm->GetFocusedElement(getter_AddRefs(element));
|
|
if (!element) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMEventTarget> originalTarget =
|
|
aFocusEvent->GetOriginalDOMEventTarget();
|
|
|
|
nsCOMPtr<nsIContent> originalTargetAsContent =
|
|
do_QueryInterface(originalTarget);
|
|
nsCOMPtr<nsIContent> focusedElementAsContent =
|
|
do_QueryInterface(element);
|
|
|
|
if (!SameCOMIdentity(
|
|
focusedElementAsContent->FindFirstNonChromeOnlyAccessContent(),
|
|
originalTargetAsContent->FindFirstNonChromeOnlyAccessContent())) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
editorBase->OnFocus(target);
|
|
if (DetachedFromEditorOrDefaultPrevented(aFocusEvent)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIPresShell> ps = GetPresShell();
|
|
NS_ENSURE_TRUE(ps, NS_OK);
|
|
nsCOMPtr<nsIContent> focusedContent = editorBase->GetFocusedContentForIME();
|
|
IMEStateManager::OnFocusInEditor(ps->GetPresContext(), focusedContent,
|
|
editorBase);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
EditorEventListener::Blur(WidgetEvent* aBlurEvent)
|
|
{
|
|
if (NS_WARN_IF(!aBlurEvent) || DetachedFromEditor()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX If aBlurEvent was created by chrome script, its defaultPrevented
|
|
// may be true, though. We shouldn't handle such event but we don't
|
|
// have a way to distinguish if coming event is created by chrome script.
|
|
NS_WARNING_ASSERTION(!aBlurEvent->DefaultPrevented(),
|
|
"eBlur event shouldn't be cancelable");
|
|
|
|
// check if something else is focused. If another element is focused, then
|
|
// we should not change the selection.
|
|
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
NS_ENSURE_TRUE(fm, NS_OK);
|
|
|
|
nsCOMPtr<nsIDOMElement> element;
|
|
fm->GetFocusedElement(getter_AddRefs(element));
|
|
if (!element) {
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
editorBase->FinalizeSelection();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
EditorEventListener::SpellCheckIfNeeded()
|
|
{
|
|
MOZ_ASSERT(!DetachedFromEditor());
|
|
|
|
// If the spell check skip flag is still enabled from creation time,
|
|
// disable it because focused editors are allowed to spell check.
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
uint32_t currentFlags = 0;
|
|
editorBase->GetFlags(¤tFlags);
|
|
if(currentFlags & nsIPlaintextEditor::eEditorSkipSpellCheck) {
|
|
currentFlags ^= nsIPlaintextEditor::eEditorSkipSpellCheck;
|
|
editorBase->SetFlags(currentFlags);
|
|
}
|
|
}
|
|
|
|
bool
|
|
EditorEventListener::IsFileControlTextBox()
|
|
{
|
|
MOZ_ASSERT(!DetachedFromEditor());
|
|
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
Element* root = editorBase->GetRoot();
|
|
if (!root || !root->ChromeOnlyAccess()) {
|
|
return false;
|
|
}
|
|
nsIContent* parent = root->FindFirstNonChromeOnlyAccessContent();
|
|
if (!parent || !parent->IsHTMLElement(nsGkAtoms::input)) {
|
|
return false;
|
|
}
|
|
nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(parent);
|
|
return formControl->GetType() == NS_FORM_INPUT_FILE;
|
|
}
|
|
|
|
bool
|
|
EditorEventListener::ShouldHandleNativeKeyBindings(nsIDOMKeyEvent* aKeyEvent)
|
|
{
|
|
MOZ_ASSERT(!DetachedFromEditor());
|
|
|
|
// Only return true if the target of the event is a desendant of the active
|
|
// editing host in order to match the similar decision made in
|
|
// nsXBLWindowKeyHandler.
|
|
// Note that IsAcceptableInputEvent doesn't check for the active editing
|
|
// host for keyboard events, otherwise this check would have been
|
|
// unnecessary. IsAcceptableInputEvent currently makes a similar check for
|
|
// mouse events.
|
|
|
|
nsCOMPtr<nsIDOMEventTarget> target;
|
|
aKeyEvent->AsEvent()->GetTarget(getter_AddRefs(target));
|
|
nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
|
|
if (!targetContent) {
|
|
return false;
|
|
}
|
|
|
|
RefPtr<EditorBase> editorBase(mEditorBase);
|
|
nsCOMPtr<nsIHTMLEditor> htmlEditor =
|
|
do_QueryInterface(static_cast<nsIEditor*>(editorBase));
|
|
if (!htmlEditor) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocument> doc = editorBase->GetDocument();
|
|
if (doc->HasFlag(NODE_IS_EDITABLE)) {
|
|
// Don't need to perform any checks in designMode documents.
|
|
return true;
|
|
}
|
|
|
|
nsIContent* editingHost = htmlEditor->GetActiveEditingHost();
|
|
if (!editingHost) {
|
|
return false;
|
|
}
|
|
|
|
return nsContentUtils::ContentIsDescendantOf(targetContent, editingHost);
|
|
}
|
|
|
|
} // namespace mozilla
|