diff options
author | Moonchild <mcwerewolf@gmail.com> | 2018-06-27 14:10:49 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-27 14:10:49 +0200 |
commit | 9168a0fc95f523c7c852ca95969edb39069f4a03 (patch) | |
tree | d9e7ea0c5086235d0cfa7a7cc34f1760a4d74bb4 /editor | |
parent | a3724697dc38820c4918b9e674ff56d2c15c5bba (diff) | |
parent | 783b60aae187d75c8c1924eceec8c4c56aa40c5e (diff) | |
download | UXP-9168a0fc95f523c7c852ca95969edb39069f4a03.tar UXP-9168a0fc95f523c7c852ca95969edb39069f4a03.tar.gz UXP-9168a0fc95f523c7c852ca95969edb39069f4a03.tar.lz UXP-9168a0fc95f523c7c852ca95969edb39069f4a03.tar.xz UXP-9168a0fc95f523c7c852ca95969edb39069f4a03.zip |
Merge pull request #554 from MoonchildProductions/issue12
Resolve potential null deref crashes in the editor
Diffstat (limited to 'editor')
-rw-r--r-- | editor/libeditor/EditorBase.cpp | 23 | ||||
-rw-r--r-- | editor/libeditor/EditorBase.h | 11 | ||||
-rw-r--r-- | editor/libeditor/EditorEventListener.cpp | 315 | ||||
-rw-r--r-- | editor/libeditor/EditorEventListener.h | 24 | ||||
-rw-r--r-- | editor/libeditor/HTMLEditor.cpp | 12 | ||||
-rw-r--r-- | editor/libeditor/HTMLEditor.h | 2 | ||||
-rw-r--r-- | editor/libeditor/HTMLEditorEventListener.cpp | 24 | ||||
-rw-r--r-- | editor/libeditor/TextEditor.cpp | 24 | ||||
-rw-r--r-- | editor/libeditor/TextEditor.h | 3 |
9 files changed, 268 insertions, 170 deletions
diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp index 0c4a2a41d..f7988cd1a 100644 --- a/editor/libeditor/EditorBase.cpp +++ b/editor/libeditor/EditorBase.cpp @@ -5092,19 +5092,16 @@ EditorBase::IsActiveInDOMWindow() } bool -EditorBase::IsAcceptableInputEvent(nsIDOMEvent* aEvent) +EditorBase::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) { // If the event is trusted, the event should always cause input. - NS_ENSURE_TRUE(aEvent, false); - - WidgetEvent* widgetEvent = aEvent->WidgetEventPtr(); - if (NS_WARN_IF(!widgetEvent)) { + if (NS_WARN_IF(!aGUIEvent)) { return false; } // If this is dispatched by using cordinates but this editor doesn't have // focus, we shouldn't handle it. - if (widgetEvent->IsUsingCoordinates()) { + if (aGUIEvent->IsUsingCoordinates()) { nsCOMPtr<nsIContent> focusedContent = GetFocusedContent(); if (!focusedContent) { return false; @@ -5117,8 +5114,7 @@ EditorBase::IsAcceptableInputEvent(nsIDOMEvent* aEvent) // Note that if we allow to handle such events, editor may be confused by // strange event order. bool needsWidget = false; - WidgetGUIEvent* widgetGUIEvent = nullptr; - switch (widgetEvent->mMessage) { + switch (aGUIEvent->mMessage) { case eUnidentifiedEvent: // If events are not created with proper event interface, their message // are initialized with eUnidentifiedEvent. Let's ignore such event. @@ -5130,25 +5126,26 @@ EditorBase::IsAcceptableInputEvent(nsIDOMEvent* aEvent) case eCompositionCommitAsIs: // Don't allow composition events whose internal event are not // WidgetCompositionEvent. - widgetGUIEvent = aEvent->WidgetEventPtr()->AsCompositionEvent(); + if (!aGUIEvent->AsCompositionEvent()) { + return false; + } needsWidget = true; break; default: break; } - if (needsWidget && - (!widgetGUIEvent || !widgetGUIEvent->mWidget)) { + if (needsWidget && !aGUIEvent->mWidget) { return false; } // Accept all trusted events. - if (widgetEvent->IsTrusted()) { + if (aGUIEvent->IsTrusted()) { return true; } // Ignore untrusted mouse event. // XXX Why are we handling other untrusted input events? - if (widgetEvent->AsMouseEventBase()) { + if (aGUIEvent->AsMouseEventBase()) { return false; } diff --git a/editor/libeditor/EditorBase.h b/editor/libeditor/EditorBase.h index dd4b9695e..dbd00771e 100644 --- a/editor/libeditor/EditorBase.h +++ b/editor/libeditor/EditorBase.h @@ -247,7 +247,8 @@ public: * IME event handlers. */ virtual nsresult BeginIMEComposition(WidgetCompositionEvent* aEvent); - virtual nsresult UpdateIMEComposition(nsIDOMEvent* aDOMTextEvent) = 0; + virtual nsresult UpdateIMEComposition( + WidgetCompositionEvent* aCompositionChangeEvent) = 0; void EndIMEComposition(); void SwitchTextDirectionTo(uint32_t aDirection); @@ -870,12 +871,12 @@ public: virtual bool IsActiveInDOMWindow(); /** - * Whether the aEvent should be handled by this editor or not. When this - * returns FALSE, The aEvent shouldn't be handled on this editor, - * i.e., The aEvent should be handled by another inner editor or ancestor + * Whether the aGUIEvent should be handled by this editor or not. When this + * returns false, The aGUIEvent shouldn't be handled on this editor, + * i.e., The aGUIEvent should be handled by another inner editor or ancestor * elements. */ - virtual bool IsAcceptableInputEvent(nsIDOMEvent* aEvent); + virtual bool IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent); /** * FindSelectionRoot() returns a selection root of this editor when aNode diff --git a/editor/libeditor/EditorEventListener.cpp b/editor/libeditor/EditorEventListener.cpp index f90458d3e..a6fcfd933 100644 --- a/editor/libeditor/EditorEventListener.cpp +++ b/editor/libeditor/EditorEventListener.cpp @@ -214,7 +214,7 @@ EditorEventListener::InstallToEditor() void EditorEventListener::Disconnect() { - if (!mEditorBase) { + if (DetachedFromEditor()) { return; } UninstallFromEditor(); @@ -301,8 +301,7 @@ EditorEventListener::UninstallFromEditor() already_AddRefed<nsIPresShell> EditorEventListener::GetPresShell() { - NS_PRECONDITION(mEditorBase, - "The caller must check whether this is connected to an editor"); + MOZ_ASSERT(!DetachedFromEditor()); return mEditorBase->GetPresShell(); } @@ -316,8 +315,7 @@ EditorEventListener::GetPresContext() nsIContent* EditorEventListener::GetFocusedRootContent() { - NS_ENSURE_TRUE(mEditorBase, nullptr); - + MOZ_ASSERT(!DetachedFromEditor()); nsCOMPtr<nsIContent> focusedContent = mEditorBase->GetFocusedContent(); if (!focusedContent) { return nullptr; @@ -336,8 +334,7 @@ EditorEventListener::GetFocusedRootContent() bool EditorEventListener::EditorHasFocus() { - NS_PRECONDITION(mEditorBase, - "The caller must check whether this is connected to an editor"); + MOZ_ASSERT(!DetachedFromEditor()); nsCOMPtr<nsIContent> focusedContent = mEditorBase->GetFocusedContent(); if (!focusedContent) { return false; @@ -348,16 +345,23 @@ EditorEventListener::EditorHasFocus() NS_IMPL_ISUPPORTS(EditorEventListener, nsIDOMEventListener) -NS_IMETHODIMP -EditorEventListener::HandleEvent(nsIDOMEvent* aEvent) +bool +EditorEventListener::DetachedFromEditor() const { - NS_ENSURE_TRUE(mEditorBase, NS_ERROR_FAILURE); - - nsCOMPtr<nsIEditor> kungFuDeathGrip = mEditorBase; - Unused << kungFuDeathGrip; // mEditorBase is not referred to in this function + return !mEditorBase; +} - WidgetEvent* internalEvent = aEvent->WidgetEventPtr(); +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", {});|, @@ -368,6 +372,7 @@ EditorEventListener::HandleEvent(nsIDOMEvent* aEvent) // 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: { @@ -454,19 +459,19 @@ EditorEventListener::HandleEvent(nsIDOMEvent* aEvent) } // focus case eFocus: - return Focus(aEvent); + return Focus(internalEvent); // blur case eBlur: - return Blur(aEvent); + return Blur(internalEvent); // text case eCompositionChange: - return HandleText(aEvent); + return HandleChangeComposition(internalEvent->AsCompositionEvent()); // compositionstart case eCompositionStart: - return HandleStartComposition(aEvent); + return HandleStartComposition(internalEvent->AsCompositionEvent()); // compositionend case eCompositionEnd: - HandleEndComposition(aEvent); + HandleEndComposition(internalEvent->AsCompositionEvent()); return NS_OK; default: break; @@ -477,10 +482,10 @@ EditorEventListener::HandleEvent(nsIDOMEvent* aEvent) // 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(aEvent); + return Focus(internalEvent); } if (eventType.EqualsLiteral("blur")) { - return Blur(aEvent); + return Blur(internalEvent); } #ifdef DEBUG nsPrintfCString assertMessage("Editor doesn't handle \"%s\" event " @@ -541,18 +546,22 @@ bool IsCtrlShiftPressed(nsIDOMKeyEvent* aEvent, bool& isRTL) nsresult EditorEventListener::KeyUp(nsIDOMKeyEvent* aKeyEvent) { - NS_ENSURE_TRUE(aKeyEvent, NS_OK); + 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 && mEditorBase->IsPlaintextEditor()) { - mEditorBase->SwitchTextDirectionTo(mSwitchToRTL ? + mShouldSwitchTextDirection && editorBase->IsPlaintextEditor()) { + editorBase->SwitchTextDirectionTo(mSwitchToRTL ? nsIPlaintextEditor::eEditorRightToLeft : nsIPlaintextEditor::eEditorLeftToRight); mShouldSwitchTextDirection = false; @@ -563,12 +572,15 @@ EditorEventListener::KeyUp(nsIDOMKeyEvent* aKeyEvent) nsresult EditorEventListener::KeyDown(nsIDOMKeyEvent* aKeyEvent) { - NS_ENSURE_TRUE(aKeyEvent, NS_OK); + 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) { @@ -588,29 +600,23 @@ EditorEventListener::KeyDown(nsIDOMKeyEvent* aKeyEvent) nsresult EditorEventListener::KeyPress(nsIDOMKeyEvent* aKeyEvent) { - NS_ENSURE_TRUE(aKeyEvent, NS_OK); - - if (!mEditorBase->IsAcceptableInputEvent(aKeyEvent->AsEvent())) { + if (NS_WARN_IF(!aKeyEvent)) { return NS_OK; } - // DOM event handling happens in two passes, the client pass and the system - // pass. We do all of our processing in the system pass, to allow client - // handlers the opportunity to cancel events and prevent typing in the editor. - // If the client pass cancelled the event, defaultPrevented will be true - // below. - - bool defaultPrevented; - aKeyEvent->AsEvent()->GetDefaultPrevented(&defaultPrevented); - if (defaultPrevented) { + 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 = mEditorBase->HandleKeyPressEvent(aKeyEvent); + nsresult rv = editorBase->HandleKeyPressEvent(aKeyEvent); NS_ENSURE_SUCCESS(rv, rv); - - aKeyEvent->AsEvent()->GetDefaultPrevented(&defaultPrevented); - if (defaultPrevented) { + if (DetachedFromEditorOrDefaultPrevented(keypressEvent)) { return NS_OK; } @@ -619,11 +625,7 @@ EditorEventListener::KeyPress(nsIDOMKeyEvent* aKeyEvent) } // Now, ask the native key bindings to handle the event. - WidgetKeyboardEvent* keyEvent = - aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent(); - MOZ_ASSERT(keyEvent, - "DOM key event's internal event must be WidgetKeyboardEvent"); - nsIWidget* widget = keyEvent->mWidget; + nsIWidget* widget = keypressEvent->mWidget; // If the event is created by chrome script, the widget is always nullptr. if (!widget) { nsCOMPtr<nsIPresShell> ps = GetPresShell(); @@ -632,10 +634,10 @@ EditorEventListener::KeyPress(nsIDOMKeyEvent* aKeyEvent) NS_ENSURE_TRUE(widget, NS_OK); } - nsCOMPtr<nsIDocument> doc = mEditorBase->GetDocument(); + nsCOMPtr<nsIDocument> doc = editorBase->GetDocument(); bool handled = widget->ExecuteNativeKeyBinding( nsIWidget::NativeKeyBindingsForRichTextEditor, - *keyEvent, DoCommandCallback, doc); + *keypressEvent, DoCommandCallback, doc); if (handled) { aKeyEvent->AsEvent()->PreventDefault(); } @@ -645,9 +647,15 @@ EditorEventListener::KeyPress(nsIDOMKeyEvent* aKeyEvent) 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. - if (mEditorBase->IsReadonly() || mEditorBase->IsDisabled() || - !mEditorBase->IsAcceptableInputEvent(aMouseEvent->AsEvent())) { + RefPtr<EditorBase> editorBase(mEditorBase); + WidgetMouseEvent* clickEvent = + aMouseEvent->AsEvent()->WidgetEventPtr()->AsMouseEvent(); + if (editorBase->IsReadonly() || editorBase->IsDisabled() || + !editorBase->IsAcceptableInputEvent(clickEvent)) { return NS_OK; } @@ -658,26 +666,23 @@ EditorEventListener::MouseClick(nsIDOMMouseEvent* aMouseEvent) if (presContext) { IMEStateManager::OnClickInEditor(presContext, GetFocusedRootContent(), aMouseEvent); - } + if (DetachedFromEditor()) { + return NS_OK; + } + } } - bool preventDefault; - nsresult rv = aMouseEvent->AsEvent()->GetDefaultPrevented(&preventDefault); - if (NS_FAILED(rv) || preventDefault) { + if (DetachedFromEditorOrDefaultPrevented(clickEvent)) { // We're done if 'preventdefault' is true (see for example bug 70698). - return rv; - } - - // IMEStateManager::OnClickInEditor() may cause anything because it may - // set input context. For example, it may cause opening VKB, changing focus - // or reflow. So, mEditorBase here might have been gone. - if (!mEditorBase) { 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 - mEditorBase->ForceCompositionEnd(); + editorBase->ForceCompositionEnd(); + if (DetachedFromEditor()) { + return NS_OK; + } int16_t button = -1; aMouseEvent->GetButton(&button); @@ -690,6 +695,10 @@ EditorEventListener::MouseClick(nsIDOMMouseEvent* aMouseEvent) 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; @@ -705,7 +714,8 @@ EditorEventListener::HandleMiddleClickPaste(nsIDOMMouseEvent* aMouseEvent) return NS_ERROR_NULL_POINTER; } - RefPtr<Selection> selection = mEditorBase->GetSelection(); + RefPtr<EditorBase> editorBase(mEditorBase); + RefPtr<Selection> selection = editorBase->GetSelection(); if (selection) { selection->Collapse(parent, offset); } @@ -717,7 +727,7 @@ EditorEventListener::HandleMiddleClickPaste(nsIDOMMouseEvent* aMouseEvent) nsCOMPtr<nsIEditorMailSupport> mailEditor; if (ctrlKey) { - mailEditor = do_QueryObject(mEditorBase); + mailEditor = do_QueryObject(editorBase); } nsresult rv; @@ -735,7 +745,7 @@ EditorEventListener::HandleMiddleClickPaste(nsIDOMMouseEvent* aMouseEvent) if (mailEditor) { mailEditor->PasteAsQuotation(clipboard); } else { - mEditorBase->Paste(clipboard); + editorBase->Paste(clipboard); } // Prevent the event from propagating up to be possibly handled @@ -751,16 +761,12 @@ bool EditorEventListener::NotifyIMEOfMouseButtonEvent( nsIDOMMouseEvent* aMouseEvent) { + MOZ_ASSERT(aMouseEvent); + if (!EditorHasFocus()) { return false; } - bool defaultPrevented; - nsresult rv = aMouseEvent->AsEvent()->GetDefaultPrevented(&defaultPrevented); - NS_ENSURE_SUCCESS(rv, false); - if (defaultPrevented) { - return false; - } nsPresContext* presContext = GetPresContext(); NS_ENSURE_TRUE(presContext, false); return IMEStateManager::OnMouseButtonEventInEditor(presContext, @@ -771,27 +777,36 @@ EditorEventListener::NotifyIMEOfMouseButtonEvent( 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 (mEditorBase) { - mEditorBase->ForceCompositionEnd(); + if (DetachedFromEditor()) { + return NS_OK; } + RefPtr<EditorBase> editorBase(mEditorBase); + editorBase->ForceCompositionEnd(); return NS_OK; } nsresult -EditorEventListener::HandleText(nsIDOMEvent* aTextEvent) +EditorEventListener::HandleChangeComposition( + WidgetCompositionEvent* aCompositionChangeEvent) { - if (!mEditorBase->IsAcceptableInputEvent(aTextEvent)) { + 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 (mEditorBase->IsReadonly() || mEditorBase->IsDisabled()) { + if (editorBase->IsReadonly() || editorBase->IsDisabled()) { return NS_OK; } - return mEditorBase->UpdateIMEComposition(aTextEvent); + return editorBase->UpdateIMEComposition(aCompositionChangeEvent); } /** @@ -801,7 +816,9 @@ EditorEventListener::HandleText(nsIDOMEvent* aTextEvent) nsresult EditorEventListener::DragEnter(nsIDOMDragEvent* aDragEvent) { - NS_ENSURE_TRUE(aDragEvent, NS_OK); + if (NS_WARN_IF(!aDragEvent) || DetachedFromEditor()) { + return NS_OK; + } nsCOMPtr<nsIPresShell> presShell = GetPresShell(); NS_ENSURE_TRUE(presShell, NS_OK); @@ -824,15 +841,13 @@ EditorEventListener::DragEnter(nsIDOMDragEvent* aDragEvent) nsresult EditorEventListener::DragOver(nsIDOMDragEvent* aDragEvent) { - NS_ENSURE_TRUE(aDragEvent, NS_OK); - - nsCOMPtr<nsIDOMNode> parent; - bool defaultPrevented; - aDragEvent->AsEvent()->GetDefaultPrevented(&defaultPrevented); - if (defaultPrevented) { + 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); @@ -887,7 +902,15 @@ EditorEventListener::CleanupDragDropCaret() nsresult EditorEventListener::DragExit(nsIDOMDragEvent* aDragEvent) { - NS_ENSURE_TRUE(aDragEvent, NS_OK); + // 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(); @@ -897,13 +920,11 @@ EditorEventListener::DragExit(nsIDOMDragEvent* aDragEvent) nsresult EditorEventListener::Drop(nsIDOMDragEvent* aDragEvent) { - NS_ENSURE_TRUE(aDragEvent, NS_OK); - CleanupDragDropCaret(); - bool defaultPrevented; - aDragEvent->AsEvent()->GetDefaultPrevented(&defaultPrevented); - if (defaultPrevented) { + if (NS_WARN_IF(!aDragEvent) || + DetachedFromEditorOrDefaultPrevented( + aDragEvent->AsEvent()->WidgetEventPtr())) { return NS_OK; } @@ -914,7 +935,8 @@ EditorEventListener::Drop(nsIDOMDragEvent* aDragEvent) if (!dropParent->IsEditable() || !CanDrop(aDragEvent)) { // was it because we're read-only? - if ((mEditorBase->IsReadonly() || mEditorBase->IsDisabled()) && + 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 @@ -927,14 +949,19 @@ EditorEventListener::Drop(nsIDOMDragEvent* aDragEvent) aDragEvent->AsEvent()->StopPropagation(); aDragEvent->AsEvent()->PreventDefault(); - return mEditorBase->InsertFromDrop(aDragEvent->AsEvent()); + 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 - if (mEditorBase->IsReadonly() || mEditorBase->IsDisabled()) { + RefPtr<EditorBase> editorBase(mEditorBase); + if (editorBase->IsReadonly() || editorBase->IsDisabled()) { return false; } @@ -950,7 +977,7 @@ EditorEventListener::CanDrop(nsIDOMDragEvent* aEvent) // can be dropped as well. if (!types.Contains(NS_LITERAL_STRING(kTextMime)) && !types.Contains(NS_LITERAL_STRING(kMozTextInternal)) && - (mEditorBase->IsPlaintextEditor() || + (editorBase->IsPlaintextEditor() || (!types.Contains(NS_LITERAL_STRING(kHTMLMime)) && !types.Contains(NS_LITERAL_STRING(kFileMime))))) { return false; @@ -968,7 +995,7 @@ EditorEventListener::CanDrop(nsIDOMDragEvent* aEvent) // There is a source node, so compare the source documents and this document. // Disallow drops on the same document. - nsCOMPtr<nsIDOMDocument> domdoc = mEditorBase->GetDOMDocument(); + nsCOMPtr<nsIDOMDocument> domdoc = editorBase->GetDOMDocument(); NS_ENSURE_TRUE(domdoc, false); nsCOMPtr<nsIDOMDocument> sourceDoc; @@ -988,7 +1015,7 @@ EditorEventListener::CanDrop(nsIDOMDragEvent* aEvent) return true; } - RefPtr<Selection> selection = mEditorBase->GetSelection(); + RefPtr<Selection> selection = editorBase->GetSelection(); if (!selection) { return false; } @@ -1030,46 +1057,63 @@ EditorEventListener::CanDrop(nsIDOMDragEvent* aEvent) } nsresult -EditorEventListener::HandleStartComposition(nsIDOMEvent* aCompositionEvent) +EditorEventListener::HandleStartComposition( + WidgetCompositionEvent* aCompositionStartEvent) { - if (!mEditorBase->IsAcceptableInputEvent(aCompositionEvent)) { + RefPtr<EditorBase> editorBase(mEditorBase); + if (DetachedFromEditor() || + !editorBase->IsAcceptableInputEvent(aCompositionStartEvent)) { return NS_OK; } - WidgetCompositionEvent* compositionStart = - aCompositionEvent->WidgetEventPtr()->AsCompositionEvent(); - return mEditorBase->BeginIMEComposition(compositionStart); + // 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(nsIDOMEvent* aCompositionEvent) +EditorEventListener::HandleEndComposition( + WidgetCompositionEvent* aCompositionEndEvent) { - if (!mEditorBase->IsAcceptableInputEvent(aCompositionEvent)) { + RefPtr<EditorBase> editorBase(mEditorBase); + if (DetachedFromEditor() || + !editorBase->IsAcceptableInputEvent(aCompositionEndEvent)) { return; } - - mEditorBase->EndIMEComposition(); + MOZ_ASSERT(!aCompositionEndEvent->DefaultPrevented(), + "eCompositionEnd shouldn't be cancelable"); + editorBase->EndIMEComposition(); } nsresult -EditorEventListener::Focus(nsIDOMEvent* aEvent) +EditorEventListener::Focus(WidgetEvent* aFocusEvent) { - NS_ENSURE_TRUE(aEvent, NS_OK); + 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. - if (mEditorBase->IsDisabled()) { + RefPtr<EditorBase> editorBase(mEditorBase); + if (editorBase->IsDisabled()) { return NS_OK; } // Spell check a textarea the first time that it is focused. SpellCheckIfNeeded(); - if (!mEditorBase) { + 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; - aEvent->GetTarget(getter_AddRefs(target)); + nsCOMPtr<nsIDOMEventTarget> target = aFocusEvent->GetDOMEventTarget(); nsCOMPtr<nsINode> node = do_QueryInterface(target); NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED); @@ -1087,7 +1131,7 @@ EditorEventListener::Focus(nsIDOMEvent* aEvent) // 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 = mEditorBase->FindSelectionRoot(node); + 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. @@ -1101,8 +1145,8 @@ EditorEventListener::Focus(nsIDOMEvent* aEvent) return NS_OK; } - nsCOMPtr<nsIDOMEventTarget> originalTarget; - aEvent->GetOriginalTarget(getter_AddRefs(originalTarget)); + nsCOMPtr<nsIDOMEventTarget> originalTarget = + aFocusEvent->GetOriginalDOMEventTarget(); nsCOMPtr<nsIContent> originalTargetAsContent = do_QueryInterface(originalTarget); @@ -1117,21 +1161,32 @@ EditorEventListener::Focus(nsIDOMEvent* aEvent) } } - mEditorBase->OnFocus(target); + editorBase->OnFocus(target); + if (DetachedFromEditorOrDefaultPrevented(aFocusEvent)) { + return NS_OK; + } nsCOMPtr<nsIPresShell> ps = GetPresShell(); NS_ENSURE_TRUE(ps, NS_OK); - nsCOMPtr<nsIContent> focusedContent = mEditorBase->GetFocusedContentForIME(); + nsCOMPtr<nsIContent> focusedContent = editorBase->GetFocusedContentForIME(); IMEStateManager::OnFocusInEditor(ps->GetPresContext(), focusedContent, - mEditorBase); + editorBase); return NS_OK; } nsresult -EditorEventListener::Blur(nsIDOMEvent* aEvent) +EditorEventListener::Blur(WidgetEvent* aBlurEvent) { - NS_ENSURE_TRUE(aEvent, NS_OK); + 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. @@ -1141,7 +1196,8 @@ EditorEventListener::Blur(nsIDOMEvent* aEvent) nsCOMPtr<nsIDOMElement> element; fm->GetFocusedElement(getter_AddRefs(element)); if (!element) { - mEditorBase->FinalizeSelection(); + RefPtr<EditorBase> editorBase(mEditorBase); + editorBase->FinalizeSelection(); } return NS_OK; } @@ -1149,20 +1205,26 @@ EditorEventListener::Blur(nsIDOMEvent* aEvent) 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; - mEditorBase->GetFlags(¤tFlags); + editorBase->GetFlags(¤tFlags); if(currentFlags & nsIPlaintextEditor::eEditorSkipSpellCheck) { currentFlags ^= nsIPlaintextEditor::eEditorSkipSpellCheck; - mEditorBase->SetFlags(currentFlags); + editorBase->SetFlags(currentFlags); } } bool EditorEventListener::IsFileControlTextBox() { - Element* root = mEditorBase->GetRoot(); + MOZ_ASSERT(!DetachedFromEditor()); + + RefPtr<EditorBase> editorBase(mEditorBase); + Element* root = editorBase->GetRoot(); if (!root || !root->ChromeOnlyAccess()) { return false; } @@ -1177,6 +1239,8 @@ EditorEventListener::IsFileControlTextBox() 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. @@ -1192,13 +1256,14 @@ EditorEventListener::ShouldHandleNativeKeyBindings(nsIDOMKeyEvent* aKeyEvent) return false; } + RefPtr<EditorBase> editorBase(mEditorBase); nsCOMPtr<nsIHTMLEditor> htmlEditor = - do_QueryInterface(static_cast<nsIEditor*>(mEditorBase)); + do_QueryInterface(static_cast<nsIEditor*>(editorBase)); if (!htmlEditor) { return false; } - nsCOMPtr<nsIDocument> doc = mEditorBase->GetDocument(); + nsCOMPtr<nsIDocument> doc = editorBase->GetDocument(); if (doc->HasFlag(NODE_IS_EDITABLE)) { // Don't need to perform any checks in designMode documents. return true; diff --git a/editor/libeditor/EditorEventListener.h b/editor/libeditor/EditorEventListener.h index 505b711c7..7244ebea4 100644 --- a/editor/libeditor/EditorEventListener.h +++ b/editor/libeditor/EditorEventListener.h @@ -6,6 +6,7 @@ #ifndef EditorEventListener_h #define EditorEventListener_h +#include "mozilla/EventForwards.h" #include "nsCOMPtr.h" #include "nsError.h" #include "nsIDOMEventListener.h" @@ -60,14 +61,14 @@ protected: nsresult KeyUp(nsIDOMKeyEvent* aKeyEvent); #endif nsresult KeyPress(nsIDOMKeyEvent* aKeyEvent); - nsresult HandleText(nsIDOMEvent* aTextEvent); - nsresult HandleStartComposition(nsIDOMEvent* aCompositionEvent); - void HandleEndComposition(nsIDOMEvent* aCompositionEvent); + nsresult HandleChangeComposition(WidgetCompositionEvent* aCompositionEvent); + nsresult HandleStartComposition(WidgetCompositionEvent* aCompositionEvent); + void HandleEndComposition(WidgetCompositionEvent* aCompositionEvent); virtual nsresult MouseDown(nsIDOMMouseEvent* aMouseEvent); virtual nsresult MouseUp(nsIDOMMouseEvent* aMouseEvent) { return NS_OK; } virtual nsresult MouseClick(nsIDOMMouseEvent* aMouseEvent); - nsresult Focus(nsIDOMEvent* aEvent); - nsresult Blur(nsIDOMEvent* aEvent); + nsresult Focus(WidgetEvent* aFocusEvent); + nsresult Blur(WidgetEvent* aBlurEvent); nsresult DragEnter(nsIDOMDragEvent* aDragEvent); nsresult DragOver(nsIDOMDragEvent* aDragEvent); nsresult DragExit(nsIDOMDragEvent* aDragEvent); @@ -85,6 +86,19 @@ protected: bool ShouldHandleNativeKeyBindings(nsIDOMKeyEvent* aKeyEvent); nsresult HandleMiddleClickPaste(nsIDOMMouseEvent* aMouseEvent); + /** + * DetachedFromEditor() returns true if editor was detached. + * Otherwise, false. + */ + bool DetachedFromEditor() const; + + /** + * DetachedFromEditorOrDefaultPrevented() returns true if editor was detached + * and/or the event was consumed. Otherwise, i.e., attached editor can + * handle the event, returns true. + */ + bool DetachedFromEditorOrDefaultPrevented(WidgetEvent* aEvent) const; + EditorBase* mEditorBase; // weak RefPtr<nsCaret> mCaret; bool mCommitText; diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp index dd47ffd3c..368f7a3e9 100644 --- a/editor/libeditor/HTMLEditor.cpp +++ b/editor/libeditor/HTMLEditor.cpp @@ -5161,23 +5161,22 @@ HTMLEditor::OurWindowHasFocus() } bool -HTMLEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent) +HTMLEditor::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) { - if (!EditorBase::IsAcceptableInputEvent(aEvent)) { + if (!EditorBase::IsAcceptableInputEvent(aGUIEvent)) { return false; } // While there is composition, all composition events in its top level window // are always fired on the composing editor. Therefore, if this editor has // composition, the composition events should be handled in this editor. - if (mComposition && aEvent->WidgetEventPtr()->AsCompositionEvent()) { + if (mComposition && aGUIEvent->AsCompositionEvent()) { return true; } NS_ENSURE_TRUE(mDocWeak, false); - nsCOMPtr<nsIDOMEventTarget> target; - aEvent->GetTarget(getter_AddRefs(target)); + nsCOMPtr<nsIDOMEventTarget> target = aGUIEvent->GetDOMEventTarget(); NS_ENSURE_TRUE(target, false); nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocWeak); @@ -5201,8 +5200,7 @@ HTMLEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent) // If the event is a mouse event, we need to check if the target content is // the focused editing host or its descendant. - nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent); - if (mouseEvent) { + if (aGUIEvent->AsMouseEventBase()) { nsIContent* editingHost = GetActiveEditingHost(); // If there is no active editing host, we cannot handle the mouse event // correctly. diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h index 477ec9741..dfcdd8d6b 100644 --- a/editor/libeditor/HTMLEditor.h +++ b/editor/libeditor/HTMLEditor.h @@ -114,7 +114,7 @@ public: virtual Element* GetEditorRoot() override; virtual already_AddRefed<nsIContent> FindSelectionRoot( nsINode *aNode) override; - virtual bool IsAcceptableInputEvent(nsIDOMEvent* aEvent) override; + virtual bool IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) override; virtual already_AddRefed<nsIContent> GetInputEventTargetContent() override; virtual bool IsEditable(nsINode* aNode) override; using EditorBase::IsEditable; diff --git a/editor/libeditor/HTMLEditorEventListener.cpp b/editor/libeditor/HTMLEditorEventListener.cpp index 8fb9459c2..aa767519c 100644 --- a/editor/libeditor/HTMLEditorEventListener.cpp +++ b/editor/libeditor/HTMLEditorEventListener.cpp @@ -7,6 +7,7 @@ #include "HTMLEditUtils.h" #include "mozilla/HTMLEditor.h" +#include "mozilla/MouseEvents.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/Selection.h" #include "nsCOMPtr.h" @@ -52,6 +53,12 @@ HTMLEditorEventListener::GetHTMLEditor() nsresult HTMLEditorEventListener::MouseUp(nsIDOMMouseEvent* aMouseEvent) { + if (DetachedFromEditor()) { + return NS_OK; + } + + // FYI: We need to notify HTML editor of mouseup even if it's consumed + // because HTML editor always needs to release grabbing resizer. HTMLEditor* htmlEditor = GetHTMLEditor(); nsCOMPtr<nsIDOMEventTarget> target; @@ -71,10 +78,17 @@ HTMLEditorEventListener::MouseUp(nsIDOMMouseEvent* aMouseEvent) nsresult HTMLEditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent) { + if (NS_WARN_IF(!aMouseEvent) || DetachedFromEditor()) { + return NS_OK; + } + + WidgetMouseEvent* mousedownEvent = + aMouseEvent->AsEvent()->WidgetEventPtr()->AsMouseEvent(); + HTMLEditor* htmlEditor = GetHTMLEditor(); // Contenteditable should disregard mousedowns outside it. // IsAcceptableInputEvent() checks it for a mouse event. - if (!htmlEditor->IsAcceptableInputEvent(aMouseEvent->AsEvent())) { + if (!htmlEditor->IsAcceptableInputEvent(mousedownEvent)) { // If it's not acceptable mousedown event (including when mousedown event // is fired outside of the active editing host), we need to commit // composition because it will be change the selection to the clicked @@ -82,6 +96,9 @@ HTMLEditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent) return EditorEventListener::MouseDown(aMouseEvent); } + // XXX This method may change selection. So, we need to commit composition + // here, first. + // Detect only "context menu" click // XXX This should be easier to do! // But eDOMEvents_contextmenu and eContextMenu is not exposed in any event @@ -103,7 +120,7 @@ HTMLEditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent) nsCOMPtr<nsIDOMElement> element = do_QueryInterface(target); if (isContextClick || (buttonNumber == 0 && clickCount == 2)) { - RefPtr<Selection> selection = mEditorBase->GetSelection(); + RefPtr<Selection> selection = htmlEditor->GetSelection(); NS_ENSURE_TRUE(selection, NS_OK); // Get location of mouse within target node @@ -175,6 +192,9 @@ HTMLEditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent) } else { htmlEditor->SelectElement(element); } + if (DetachedFromEditor()) { + return NS_OK; + } } } // HACK !!! Context click places the caret but the context menu consumes diff --git a/editor/libeditor/TextEditor.cpp b/editor/libeditor/TextEditor.cpp index 8fe824e11..d21585597 100644 --- a/editor/libeditor/TextEditor.cpp +++ b/editor/libeditor/TextEditor.cpp @@ -834,17 +834,19 @@ TextEditor::BeginIMEComposition(WidgetCompositionEvent* aEvent) } nsresult -TextEditor::UpdateIMEComposition(nsIDOMEvent* aDOMTextEvent) +TextEditor::UpdateIMEComposition(WidgetCompositionEvent* aCompositionChangeEvent) { - MOZ_ASSERT(aDOMTextEvent, "aDOMTextEvent must not be nullptr"); + MOZ_ASSERT(aCompsitionChangeEvent, + "aCompositionChangeEvent must not be nullptr"); - WidgetCompositionEvent* compositionChangeEvent = - aDOMTextEvent->WidgetEventPtr()->AsCompositionEvent(); - NS_ENSURE_TRUE(compositionChangeEvent, NS_ERROR_INVALID_ARG); - MOZ_ASSERT(compositionChangeEvent->mMessage == eCompositionChange, - "The internal event should be eCompositionChange"); + if (NS_WARN_IF(!aCompositionChangeEvent)) { + return NS_ERROR_INVALID_ARG; + } + + MOZ_ASSERT(aCompositionChangeEvent->mMessage == eCompositionChange, + "The event should be eCompositionChange"); - if (!EnsureComposition(compositionChangeEvent)) { + if (!EnsureComposition(aCompositionChangeEvent)) { return NS_OK; } @@ -865,7 +867,7 @@ TextEditor::UpdateIMEComposition(nsIDOMEvent* aDOMTextEvent) MOZ_ASSERT(!mPlaceHolderBatch, "UpdateIMEComposition() must be called without place holder batch"); TextComposition::CompositionChangeEventHandlingMarker - compositionChangeEventHandlingMarker(mComposition, compositionChangeEvent); + compositionChangeEventHandlingMarker(mComposition, aCompositionChangeEvent); NotifyEditorObservers(eNotifyEditorObserversOfBefore); @@ -875,7 +877,7 @@ TextEditor::UpdateIMEComposition(nsIDOMEvent* aDOMTextEvent) { AutoPlaceHolderBatch batch(this, nsGkAtoms::IMETxnName); - rv = InsertText(compositionChangeEvent->mData); + rv = InsertText(aCompositionChangeEvent->mData); if (caretP) { caretP->SetSelection(selection); @@ -887,7 +889,7 @@ TextEditor::UpdateIMEComposition(nsIDOMEvent* aDOMTextEvent) // compositionend event, we don't need to notify editor observes of this // change. // NOTE: We must notify after the auto batch will be gone. - if (!compositionChangeEvent->IsFollowedByCompositionEnd()) { + if (!aCompositionChangeEvent->IsFollowedByCompositionEnd()) { NotifyEditorObservers(eNotifyEditorObserversOfEnd); } diff --git a/editor/libeditor/TextEditor.h b/editor/libeditor/TextEditor.h index 872cd91d3..31c551f85 100644 --- a/editor/libeditor/TextEditor.h +++ b/editor/libeditor/TextEditor.h @@ -130,7 +130,8 @@ public: virtual already_AddRefed<dom::EventTarget> GetDOMEventTarget() override; virtual nsresult BeginIMEComposition(WidgetCompositionEvent* aEvent) override; - virtual nsresult UpdateIMEComposition(nsIDOMEvent* aTextEvent) override; + virtual nsresult UpdateIMEComposition( + WidgetCompositionEvent* aCompositionChangeEvent) override; virtual already_AddRefed<nsIContent> GetInputEventTargetContent() override; |