Bug 993253 Implement DOM InputEvent interface with isComposing attribute r=smaug+ehsan

This commit is contained in:
Masayuki Nakano 2014-04-10 16:11:37 +09:00
Родитель 023a9dda6e
Коммит ecab2beca9
22 изменённых файлов: 356 добавлений и 19 удалений

Просмотреть файл

@ -32,7 +32,6 @@ enum FormControlsTypes {
// are not overlapping with sub-types/masks.
// Elements with different types, the value is used as a mask.
// Adding '_ELEMENT' because NS_FORM_INPUT is used for 'oninput' event.
// When changing the order, adding or removing elements, be sure to update
// the static_assert checks accordingly.
NS_FORM_BUTTON_ELEMENT = 0x40, // 0b01000000

Просмотреть файл

@ -3493,7 +3493,7 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
textControl = numberControlFrame->GetAnonTextControl();
}
if (textControl && aVisitor.mEvent->originalTarget == textControl) {
if (aVisitor.mEvent->message == NS_FORM_INPUT) {
if (aVisitor.mEvent->message == NS_EDITOR_INPUT) {
// Propogate the anon text control's new value to our HTMLInputElement:
nsAutoString value;
numberControlFrame->GetValueOfAnonTextControl(value);

Просмотреть файл

@ -668,13 +668,13 @@ Event::GetEventPopupControlState(WidgetEvent* aEvent)
}
}
break;
case NS_GUI_EVENT :
case NS_EDITOR_INPUT_EVENT :
// For this following event only allow popups if it's triggered
// while handling user input. See
// nsPresShell::HandleEventInternal() for details.
if (EventStateManager::IsHandlingUserInput()) {
switch(aEvent->message) {
case NS_FORM_INPUT :
case NS_EDITOR_INPUT:
if (PopupAllowedForEvent("input")) {
abuse = openControlled;
}

Просмотреть файл

@ -707,6 +707,9 @@ EventDispatcher::CreateEvent(EventTarget* aOwner,
case NS_WHEEL_EVENT:
return NS_NewDOMWheelEvent(aDOMEvent, aOwner, aPresContext,
aEvent->AsWheelEvent());
case NS_EDITOR_INPUT_EVENT:
return NS_NewDOMInputEvent(aDOMEvent, aOwner, aPresContext,
aEvent->AsEditorInputEvent());
case NS_DRAG_EVENT:
return NS_NewDOMDragEvent(aDOMEvent, aOwner, aPresContext,
aEvent->AsDragEvent());

Просмотреть файл

@ -218,9 +218,9 @@ EVENT(ended,
EventNameType_HTML,
NS_EVENT)
EVENT(input,
NS_FORM_INPUT,
NS_EDITOR_INPUT,
EventNameType_HTMLXUL,
NS_UI_EVENT)
NS_EDITOR_INPUT_EVENT)
EVENT(invalid,
NS_FORM_INVALID,
EventNameType_HTMLXUL,

75
dom/events/InputEvent.cpp Normal file
Просмотреть файл

@ -0,0 +1,75 @@
/* -*- Mode: C++; tab-width: 2; 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 "mozilla/dom/InputEvent.h"
#include "mozilla/TextEvents.h"
#include "prtime.h"
namespace mozilla {
namespace dom {
InputEvent::InputEvent(EventTarget* aOwner,
nsPresContext* aPresContext,
InternalEditorInputEvent* aEvent)
: UIEvent(aOwner, aPresContext,
aEvent ? aEvent : new InternalEditorInputEvent(false, 0, nullptr))
{
NS_ASSERTION(mEvent->eventStructType == NS_EDITOR_INPUT_EVENT,
"event type mismatch");
if (aEvent) {
mEventIsInternal = false;
} else {
mEventIsInternal = true;
mEvent->time = PR_Now();
}
}
NS_IMPL_ADDREF_INHERITED(InputEvent, UIEvent)
NS_IMPL_RELEASE_INHERITED(InputEvent, UIEvent)
NS_INTERFACE_MAP_BEGIN(InputEvent)
NS_INTERFACE_MAP_END_INHERITING(UIEvent)
bool
InputEvent::IsComposing()
{
return mEvent->AsEditorInputEvent()->mIsComposing;
}
already_AddRefed<InputEvent>
InputEvent::Constructor(const GlobalObject& aGlobal,
const nsAString& aType,
const InputEventInit& aParam,
ErrorResult& aRv)
{
nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
nsRefPtr<InputEvent> e = new InputEvent(t, nullptr, nullptr);
bool trusted = e->Init(t);
aRv = e->InitUIEvent(aType, aParam.mBubbles, aParam.mCancelable,
aParam.mView, aParam.mDetail);
InternalEditorInputEvent* internalEvent = e->mEvent->AsEditorInputEvent();
internalEvent->mIsComposing = aParam.mIsComposing;
e->SetTrusted(trusted);
return e.forget();
}
} // namespace dom
} // namespace mozilla
using namespace mozilla;
using namespace mozilla::dom;
nsresult
NS_NewDOMInputEvent(nsIDOMEvent** aInstancePtrResult,
EventTarget* aOwner,
nsPresContext* aPresContext,
InternalEditorInputEvent* aEvent)
{
InputEvent* it = new InputEvent(aOwner, aPresContext, aEvent);
NS_ADDREF(it);
*aInstancePtrResult = static_cast<Event*>(it);
return NS_OK;
}

45
dom/events/InputEvent.h Normal file
Просмотреть файл

@ -0,0 +1,45 @@
/* -*- Mode: C++; tab-width: 2; 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/. */
#ifndef mozilla_dom_InputEvent_h_
#define mozilla_dom_InputEvent_h_
#include "mozilla/dom/UIEvent.h"
#include "mozilla/dom/InputEventBinding.h"
#include "mozilla/EventForwards.h"
namespace mozilla {
namespace dom {
class InputEvent : public UIEvent
{
public:
InputEvent(EventTarget* aOwner,
nsPresContext* aPresContext,
InternalEditorInputEvent* aEvent);
NS_DECL_ISUPPORTS_INHERITED
// Forward to base class
NS_FORWARD_TO_UIEVENT
static already_AddRefed<InputEvent> Constructor(const GlobalObject& aGlobal,
const nsAString& aType,
const InputEventInit& aParam,
ErrorResult& aRv);
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE
{
return InputEventBinding::Wrap(aCx, this);
}
bool IsComposing();
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_InputEvent_h_

Просмотреть файл

@ -42,6 +42,7 @@ EXPORTS.mozilla.dom += [
'Event.h',
'EventTarget.h',
'FocusEvent.h',
'InputEvent.h',
'KeyboardEvent.h',
'MessageEvent.h',
'MouseEvent.h',
@ -84,6 +85,7 @@ UNIFIED_SOURCES += [
'FocusEvent.cpp',
'IMEContentObserver.cpp',
'IMEStateManager.cpp',
'InputEvent.cpp',
'JSEventHandler.cpp',
'KeyboardEvent.cpp',
'MessageEvent.cpp',

Просмотреть файл

@ -183,6 +183,10 @@ const kEventConstructors = {
return new IDBVersionChangeEvent(aName, aProps);
},
},
InputEvent: { create: function (aName, aProps) {
return new InputEvent(aName, aProps);
},
},
KeyEvent: { create: function (aName, aProps) {
var e = document.createEvent("keyboardevent");
e.initKeyEvent(aName, aProps.bubbles, aProps.cancelable,

Просмотреть файл

@ -314,6 +314,32 @@ is(e.newURL, "new", "newURL should be 'new'");
document.dispatchEvent(e);
is(receivedEvent, e, "Wrong event!");
// InputEvent
e = new InputEvent("hello");
is(e.type, "hello", "Wrong event type!");
ok(!e.isTrusted, "Event shouldn't be trusted!");
ok(!e.bubbles, "Event shouldn't bubble!");
ok(!e.cancelable, "Event shouldn't be cancelable!");
is(e.detail, 0, "detail should be 0");
ok(!e.isComposing, "isComposing should be false");
e = new InputEvent("hi!", { bubbles: true, detail: 5, isComposing: false });
is(e.type, "hi!", "Wrong event type!");
ok(!e.isTrusted, "Event shouldn't be trusted!");
ok(e.bubbles, "Event should bubble!");
ok(!e.cancelable, "Event shouldn't be cancelable!");
is(e.detail, 5, "detail should be 5");
ok(!e.isComposing, "isComposing should be false");
e = new InputEvent("hi!", { cancelable: true, detail: 0, isComposing: true });
is(e.type, "hi!", "Wrong event type!");
ok(!e.isTrusted, "Event shouldn't be trusted!");
ok(!e.bubbles, "Event shouldn't bubble!");
ok(e.cancelable, "Event should be cancelable!");
is(e.detail, 0, "detail should be 0");
ok(e.isComposing, "isComposing should be true");
// PageTransitionEvent
try {

Просмотреть файл

@ -262,7 +262,11 @@ NS_NewDOMClipboardEvent(nsIDOMEvent** aInstancePtrResult,
mozilla::dom::EventTarget* aOwner,
nsPresContext* aPresContext,
mozilla::InternalClipboardEvent* aEvent);
nsresult
NS_NewDOMInputEvent(nsIDOMEvent** aInstancePtrResult,
mozilla::dom::EventTarget* aOwner,
nsPresContext* aPresContext,
mozilla::InternalEditorInputEvent* aEvent);
nsresult
NS_NewDOMKeyboardEvent(nsIDOMEvent** aInstancePtrResult,
mozilla::dom::EventTarget* aOwner,

Просмотреть файл

@ -559,6 +559,8 @@ var interfaceNamesInGlobalScope =
"Image",
// IMPORTANT: Do not change this list without review from a DOM peer!
"ImageData",
// IMPORTANT: Do not change this list without review from a DOM peer!
"InputEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "InstallTrigger", b2g: false, xbl: false},
// IMPORTANT: Do not change this list without review from a DOM peer!

Просмотреть файл

@ -0,0 +1,16 @@
/* -*- Mode: IDL; tab-width: 2; 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/.
*/
[Constructor(DOMString type, optional InputEventInit eventInitDict)]
interface InputEvent : UIEvent
{
readonly attribute boolean isComposing;
};
dictionary InputEventInit : UIEventInit
{
boolean isComposing = false;
};

Просмотреть файл

@ -207,6 +207,7 @@ WEBIDL_FILES = [
'IDBVersionChangeEvent.webidl',
'ImageData.webidl',
'ImageDocument.webidl',
'InputEvent.webidl',
'InputMethod.webidl',
'InspectorUtils.webidl',
'InterAppConnection.webidl',

Просмотреть файл

@ -567,6 +567,17 @@ nsEditor::GetPresShell()
return ps.forget();
}
already_AddRefed<nsIWidget>
nsEditor::GetWidget()
{
nsCOMPtr<nsIPresShell> ps = GetPresShell();
NS_ENSURE_TRUE(ps, nullptr);
nsPresContext* pc = ps->GetPresContext();
NS_ENSURE_TRUE(pc, nullptr);
nsCOMPtr<nsIWidget> widget = pc->GetRootWidget();
NS_ENSURE_TRUE(widget.get(), nullptr);
return widget.forget();
}
/* attribute string contentsMIMEType; */
NS_IMETHODIMP
@ -1804,8 +1815,11 @@ class EditorInputEventDispatcher : public nsRunnable
{
public:
EditorInputEventDispatcher(nsEditor* aEditor,
nsIContent* aTarget) :
mEditor(aEditor), mTarget(aTarget)
nsIContent* aTarget,
bool aIsComposing)
: mEditor(aEditor)
, mTarget(aTarget)
, mIsComposing(aIsComposing)
{
}
@ -1823,11 +1837,16 @@ public:
return NS_OK;
}
nsCOMPtr<nsIWidget> widget = mEditor->GetWidget();
if (!widget) {
return NS_OK;
}
// Even if the change is caused by untrusted event, we need to dispatch
// trusted input event since it's a fact.
WidgetEvent inputEvent(true, NS_FORM_INPUT);
inputEvent.mFlags.mCancelable = false;
InternalEditorInputEvent inputEvent(true, NS_EDITOR_INPUT, widget);
inputEvent.time = static_cast<uint64_t>(PR_Now() / 1000);
inputEvent.mIsComposing = mIsComposing;
nsEventStatus status = nsEventStatus_eIgnore;
nsresult rv =
ps->HandleEventWithTarget(&inputEvent, nullptr, mTarget, &status);
@ -1838,6 +1857,7 @@ public:
private:
nsRefPtr<nsEditor> mEditor;
nsCOMPtr<nsIContent> mTarget;
bool mIsComposing;
};
void nsEditor::NotifyEditorObservers(void)
@ -1864,8 +1884,11 @@ nsEditor::FireInputEvent()
nsCOMPtr<nsIContent> target = GetInputEventTargetContent();
NS_ENSURE_TRUE_VOID(target);
// NOTE: Don't refer IsIMEComposing() because it returns false even before
// compositionend. However, DOM Level 3 Events defines it should be
// true after compositionstart and before compositionend.
nsContentUtils::AddScriptRunner(
new EditorInputEventDispatcher(this, target));
new EditorInputEventDispatcher(this, target, !!GetComposition()));
}
NS_IMETHODIMP

Просмотреть файл

@ -171,6 +171,7 @@ public:
already_AddRefed<nsIDOMDocument> GetDOMDocument();
already_AddRefed<nsIDocument> GetDocument();
already_AddRefed<nsIPresShell> GetPresShell();
already_AddRefed<nsIWidget> GetWidget();
void NotifyEditorObservers();
/* ------------ nsIEditor methods -------------- */

Просмотреть файл

@ -35,6 +35,7 @@ enum nsEventStructType
NS_TEXT_EVENT, // WidgetTextEvent
NS_QUERY_CONTENT_EVENT, // WidgetQueryContentEvent
NS_SELECTION_EVENT, // WidgetSelectionEvent
NS_EDITOR_INPUT_EVENT, // InternalEditorInputEvent
// MouseEvents.h
NS_MOUSE_EVENT, // WidgetMouseEvent
@ -166,8 +167,7 @@ enum nsEventStructType
#define NS_FORM_RESET (NS_FORM_EVENT_START + 1)
#define NS_FORM_CHANGE (NS_FORM_EVENT_START + 2)
#define NS_FORM_SELECTED (NS_FORM_EVENT_START + 3)
#define NS_FORM_INPUT (NS_FORM_EVENT_START + 4)
#define NS_FORM_INVALID (NS_FORM_EVENT_START + 5)
#define NS_FORM_INVALID (NS_FORM_EVENT_START + 4)
//Need separate focus/blur notifications for non-native widgets
#define NS_FOCUS_EVENT_START 1300
@ -473,6 +473,10 @@ enum nsEventStructType
#define NS_GAMEPAD_END (NS_GAMEPAD_START+4)
#endif
// input and beforeinput events.
#define NS_EDITOR_EVENT_START 6100
#define NS_EDITOR_INPUT (NS_EDITOR_EVENT_START)
namespace mozilla {
/******************************************************************************
@ -1057,6 +1061,18 @@ public:
class InternalUIEvent : public WidgetGUIEvent
{
protected:
InternalUIEvent()
: detail(0)
{
}
InternalUIEvent(bool aIsTrusted, uint32_t aMessage, nsIWidget* aWidget,
nsEventStructType aStructType)
: WidgetGUIEvent(aIsTrusted, aMessage, aWidget, aStructType)
, detail(0)
{
}
InternalUIEvent(bool aIsTrusted, uint32_t aMessage,
nsEventStructType aStructType)
: WidgetGUIEvent(aIsTrusted, aMessage, nullptr, aStructType)

Просмотреть файл

@ -26,6 +26,7 @@ NS_EVENT_CLASS(Widget, TextEvent)
NS_EVENT_CLASS(Widget, CompositionEvent)
NS_EVENT_CLASS(Widget, QueryContentEvent)
NS_EVENT_CLASS(Widget, SelectionEvent)
NS_EVENT_CLASS(Internal, EditorInputEvent)
// MouseEvents.h
NS_EVENT_CLASS(Widget, MouseEventBase)

Просмотреть файл

@ -497,6 +497,62 @@ public:
bool mSucceeded;
};
/******************************************************************************
* mozilla::InternalEditorInputEvent
******************************************************************************/
class InternalEditorInputEvent : public InternalUIEvent
{
private:
InternalEditorInputEvent()
: mIsComposing(false)
{
}
public:
virtual InternalEditorInputEvent* AsEditorInputEvent() MOZ_OVERRIDE
{
return this;
}
InternalEditorInputEvent(bool aIsTrusted, uint32_t aMessage,
nsIWidget* aWidget)
: InternalUIEvent(aIsTrusted, aMessage, aWidget, NS_EDITOR_INPUT_EVENT)
, mIsComposing(false)
{
if (!aIsTrusted) {
mFlags.mBubbles = false;
mFlags.mCancelable = false;
return;
}
mFlags.mBubbles = true;
mFlags.mCancelable = false;
}
virtual WidgetEvent* Duplicate() const MOZ_OVERRIDE
{
MOZ_ASSERT(eventStructType == NS_EDITOR_INPUT_EVENT,
"Duplicate() must be overridden by sub class");
// Not copying widget, it is a weak reference.
InternalEditorInputEvent* result =
new InternalEditorInputEvent(false, message, nullptr);
result->AssignEditorInputEventData(*this, true);
result->mFlags = mFlags;
return result;
}
bool mIsComposing;
void AssignEditorInputEventData(const InternalEditorInputEvent& aEvent,
bool aCopyTargets)
{
AssignUIEventData(aEvent, aCopyTargets);
mIsComposing = aEvent.mIsComposing;
}
};
} // namespace mozilla
#endif // mozilla_TextEvents_h__

Просмотреть файл

@ -48,7 +48,7 @@
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.expectAssertions(0, 16);
SimpleTest.expectAssertions(0, 32);
const kIsMac = (navigator.platform.indexOf("Mac") == 0);
const kIsWin = (navigator.platform.indexOf("Win") == 0);
@ -332,6 +332,60 @@ const kTests = [
},
todoMismatch: [ ],
},
{ description: "InternalEditorInputEvent (input at key input)",
targetID: "input-text", eventType: "input",
dispatchEvent: function () {
document.getElementById(this.targetID).value = "";
document.getElementById(this.targetID).focus();
synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, kIsWin ? WIN_VK_B : MAC_VK_ANSI_B,
{ shiftKey: true }, "B", "B");
},
canRun: function () {
return (kIsMac || kIsWin);
},
todoMismatch: [],
},
{ description: "InternalEditorInputEvent (input at composing)",
targetID: "input-text", eventType: "input",
dispatchEvent: function () {
document.getElementById(this.targetID).value = "";
document.getElementById(this.targetID).focus();
synthesizeComposition({ type: "compositionstart" });
synthesizeComposition({ type: "compositionupdate", data: "\u30E9\u30FC\u30E1\u30F3" });
synthesizeText({ "composition":
{ "string": "\u30E9\u30FC\u30E1\u30F3",
"clauses":
[
{ "length": 4, "attr": COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 4, "length": 0 }
});
},
canRun: function () {
return true;
},
todoMismatch: [ ],
},
{ description: "InternalEditorInputEvent (input at committing)",
targetID: "input-text", eventType: "input",
dispatchEvent: function () {
synthesizeText({ "composition":
{ "string": "\u30E9\u30FC\u30E1\u30F3",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 4, "length": 0 }
});
synthesizeComposition({ type: "compositionend", data: "\u30E9\u30FC\u30E1\u30F3" });
},
canRun: function () {
return true;
},
todoMismatch: [ ],
},
{ description: "WidgetMouseScrollEvent (DOMMouseScroll, vertical)",
targetID: "input-text", eventType: "DOMMouseScroll",
dispatchEvent: function () {

Просмотреть файл

@ -2799,13 +2799,19 @@ function runIsComposingTest()
function eventHandler(aEvent)
{
is(aEvent.isComposing, expectedIsComposing,
"runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
if (aEvent.type == "keydown" || aEvent.type == "keyup") {
is(aEvent.isComposing, expectedIsComposing,
"runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
} else {
is(aEvent.isComposing, expectedIsComposing,
"runIsComposingTest: " + description + " (type=" + aEvent.type + ")");
}
}
textarea.addEventListener("keydown", eventHandler, true);
textarea.addEventListener("keypress", eventHandler, true);
textarea.addEventListener("keyup", eventHandler, true);
textarea.addEventListener("input", eventHandler, true);
textarea.focus();
textarea.value = "";
@ -2848,15 +2854,18 @@ function runIsComposingTest()
},
"caret": { "start": 1, "length": 0 }
});
synthesizeComposition({ type: "compositionend" });
// input event will be fired by synthesizing compositionend event.
// Then, its isComposing should be false.
expectedIsComposing = false;
description = "events after dispatching compositionend";
synthesizeComposition({ type: "compositionend" });
synthesizeKey("VK_RETURN", { type: "keyup" });
textarea.removeEventListener("keydown", eventHandler, true);
textarea.removeEventListener("keypress", eventHandler, true);
textarea.removeEventListener("keyup", eventHandler, true);
textarea.removeEventListener("input", eventHandler, true);
textarea.value = "";
}

Просмотреть файл

@ -1613,10 +1613,10 @@ case _value: eventName.AssignLiteral(_name) ; break
_ASSIGN_eventName(NS_DRAGDROP_ENTER,"NS_DND_ENTER");
_ASSIGN_eventName(NS_DRAGDROP_EXIT,"NS_DND_EXIT");
_ASSIGN_eventName(NS_DRAGDROP_OVER,"NS_DND_OVER");
_ASSIGN_eventName(NS_EDITOR_INPUT,"NS_EDITOR_INPUT");
_ASSIGN_eventName(NS_FOCUS_CONTENT,"NS_FOCUS_CONTENT");
_ASSIGN_eventName(NS_FORM_SELECTED,"NS_FORM_SELECTED");
_ASSIGN_eventName(NS_FORM_CHANGE,"NS_FORM_CHANGE");
_ASSIGN_eventName(NS_FORM_INPUT,"NS_FORM_INPUT");
_ASSIGN_eventName(NS_FORM_RESET,"NS_FORM_RESET");
_ASSIGN_eventName(NS_FORM_SUBMIT,"NS_FORM_SUBMIT");
_ASSIGN_eventName(NS_IMAGE_ABORT,"NS_IMAGE_ABORT");