mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
Bug 389372 Contenteditable node is still editable without focus r=ehsan+roc, sr=jst
This commit is contained in:
parent
717098c537
commit
a6e67f7876
@ -71,8 +71,8 @@ enum nsLinkState {
|
||||
|
||||
// IID for the nsIContent interface
|
||||
#define NS_ICONTENT_IID \
|
||||
{ 0x1450010b, 0xcdca, 0x451c, \
|
||||
{ 0xba, 0xdc, 0x07, 0x90, 0x89, 0x7b, 0xce, 0xb8 } }
|
||||
{ 0x2ac19ac3, 0x1dac, 0x42dc, \
|
||||
{ 0xb2, 0x43, 0x78, 0x46, 0xed, 0x6f, 0x1c, 0x89 } }
|
||||
|
||||
/**
|
||||
* A node of content in a document's content model. This interface
|
||||
@ -928,6 +928,19 @@ public:
|
||||
|
||||
virtual PRBool IsEqualNode(nsINode* aOther);
|
||||
|
||||
/**
|
||||
* If this content has independent selection, e.g., if this is input field
|
||||
* or textarea, this return TRUE. Otherwise, false.
|
||||
*/
|
||||
PRBool HasIndependentSelection();
|
||||
|
||||
/**
|
||||
* If the content is a part of HTML editor, this returns editing
|
||||
* host content. When the content is in designMode, this returns its body
|
||||
* element. Also, when the content isn't editable, this returns null.
|
||||
*/
|
||||
nsIContent* GetEditingHost();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Hook for implementing GetID. This is guaranteed to only be
|
||||
|
@ -366,8 +366,7 @@ nsINode::GetSelectionRootContent(nsIPresShell* aPresShell)
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
nsIFrame* frame = static_cast<nsIContent*>(this)->GetPrimaryFrame();
|
||||
if (frame && frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION) {
|
||||
if (static_cast<nsIContent*>(this)->HasIndependentSelection()) {
|
||||
// This node should be a descendant of input/textarea editor.
|
||||
nsIContent* content = GetTextEditorRootContent();
|
||||
if (content)
|
||||
@ -388,15 +387,9 @@ nsINode::GetSelectionRootContent(nsIPresShell* aPresShell)
|
||||
editorRoot :
|
||||
GetRootForContentSubtree(static_cast<nsIContent*>(this));
|
||||
}
|
||||
// If the current document is not editable, but current content is
|
||||
// editable, we should assume that the child of the nearest non-editable
|
||||
// ancestor is selection root.
|
||||
nsIContent* content = static_cast<nsIContent*>(this);
|
||||
for (nsIContent* parent = GetParent();
|
||||
parent && parent->HasFlag(NODE_IS_EDITABLE);
|
||||
parent = content->GetParent())
|
||||
content = parent;
|
||||
return content;
|
||||
// If the document isn't editable but this is editable, this is in
|
||||
// contenteditable. Use the editing host element for selection root.
|
||||
return static_cast<nsIContent*>(this)->GetEditingHost();
|
||||
}
|
||||
}
|
||||
|
||||
@ -830,14 +823,13 @@ nsIContent::GetDesiredIMEState()
|
||||
if (!IsEditableInternal()) {
|
||||
return IME_STATUS_DISABLE;
|
||||
}
|
||||
nsIContent *editableAncestor = nsnull;
|
||||
for (nsIContent* parent = GetParent();
|
||||
parent && parent->HasFlag(NODE_IS_EDITABLE);
|
||||
parent = parent->GetParent()) {
|
||||
editableAncestor = parent;
|
||||
}
|
||||
// NOTE: The content for independent editors (e.g., input[type=text],
|
||||
// textarea) must override this method, so, we don't need to worry about
|
||||
// that here.
|
||||
nsIContent *editableAncestor = GetEditingHost();
|
||||
|
||||
// This is in another editable content, use the result of it.
|
||||
if (editableAncestor) {
|
||||
if (editableAncestor && editableAncestor != this) {
|
||||
return editableAncestor->GetDesiredIMEState();
|
||||
}
|
||||
nsIDocument* doc = GetCurrentDoc();
|
||||
@ -865,6 +857,35 @@ nsIContent::GetDesiredIMEState()
|
||||
return state;
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsIContent::HasIndependentSelection()
|
||||
{
|
||||
nsIFrame* frame = GetPrimaryFrame();
|
||||
return (frame && frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION);
|
||||
}
|
||||
|
||||
nsIContent*
|
||||
nsIContent::GetEditingHost()
|
||||
{
|
||||
// If this isn't editable, return NULL.
|
||||
NS_ENSURE_TRUE(HasFlag(NODE_IS_EDITABLE), nsnull);
|
||||
|
||||
nsIDocument* doc = GetCurrentDoc();
|
||||
NS_ENSURE_TRUE(doc, nsnull);
|
||||
// If this is in designMode, we should return <body>
|
||||
if (doc->HasFlag(NODE_IS_EDITABLE)) {
|
||||
return doc->GetBodyElement();
|
||||
}
|
||||
|
||||
nsIContent* content = this;
|
||||
for (nsIContent* parent = GetParent();
|
||||
parent && parent->HasFlag(NODE_IS_EDITABLE);
|
||||
parent = content->GetParent()) {
|
||||
content = parent;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsIContent::LookupNamespaceURI(const nsAString& aNamespacePrefix,
|
||||
nsAString& aNamespaceURI) const
|
||||
|
@ -30,6 +30,12 @@ function onLoad() {
|
||||
}
|
||||
|
||||
function onReload() {
|
||||
var iframe = window.frames[0].frameElement;
|
||||
SimpleTest.waitForFocus(doTest, iframe.contentWindow);
|
||||
iframe.contentDocument.body.focus();
|
||||
}
|
||||
|
||||
function doTest() {
|
||||
var bodyElement = window.frames[0].frameElement.contentDocument.body;
|
||||
sendChar('S', bodyElement);
|
||||
sendChar('t', bodyElement);
|
||||
|
@ -43,6 +43,8 @@
|
||||
#include "nsIDOMDocument.h"
|
||||
#include "nsIDOMHTMLElement.h"
|
||||
#include "nsIDOMNSHTMLElement.h"
|
||||
#include "nsIDOMEventTarget.h"
|
||||
#include "nsIDOMNSEvent.h"
|
||||
#include "nsPIDOMEventTarget.h"
|
||||
#include "nsIMEStateManager.h"
|
||||
#include "nsFocusManager.h"
|
||||
@ -5166,3 +5168,41 @@ nsEditor::HasFocus()
|
||||
nsCOMPtr<nsIContent> content = fm->GetFocusedContent();
|
||||
return SameCOMIdentity(content, piTarget);
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsEditor::IsActiveInDOMWindow()
|
||||
{
|
||||
nsCOMPtr<nsPIDOMEventTarget> piTarget = GetPIDOMEventTarget();
|
||||
if (!piTarget) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
||||
NS_ENSURE_TRUE(fm, PR_FALSE);
|
||||
|
||||
nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
|
||||
nsPIDOMWindow* ourWindow = doc->GetWindow();
|
||||
nsCOMPtr<nsPIDOMWindow> win;
|
||||
nsIContent* content =
|
||||
nsFocusManager::GetFocusedDescendant(ourWindow, PR_FALSE,
|
||||
getter_AddRefs(win));
|
||||
return SameCOMIdentity(content, piTarget);
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent)
|
||||
{
|
||||
// If the event is trusted, the event should always cause input.
|
||||
nsCOMPtr<nsIDOMNSEvent> NSEvent = do_QueryInterface(aEvent);
|
||||
NS_ENSURE_TRUE(NSEvent, PR_FALSE);
|
||||
|
||||
PRBool isTrusted;
|
||||
nsresult rv = NSEvent->GetIsTrusted(&isTrusted);
|
||||
NS_ENSURE_SUCCESS(rv, PR_FALSE);
|
||||
if (isTrusted) {
|
||||
return PR_TRUE;
|
||||
}
|
||||
// Otherwise, we shouldn't handle any input events when we're not an active
|
||||
// element of the DOM window.
|
||||
return IsActiveInDOMWindow();
|
||||
}
|
||||
|
@ -659,9 +659,20 @@ public:
|
||||
IsInteractionAllowed();
|
||||
}
|
||||
|
||||
// Whether the editor has focus or not.
|
||||
// Whether the editor has application level focus or not.
|
||||
virtual PRBool HasFocus();
|
||||
|
||||
// Whether the editor is active on the DOM window. Note that when this
|
||||
// returns true but HasFocus() returns false, it means that this editor was
|
||||
// focused when the DOM window was active.
|
||||
virtual PRBool 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
|
||||
// elements.
|
||||
virtual PRBool IsAcceptableInputEvent(nsIDOMEvent* aEvent);
|
||||
|
||||
// FindSelectionRoot() returns a selection root of this editor when aNode
|
||||
// gets focus. aNode must be a content node or a document node. When the
|
||||
// target isn't a part of this editor, returns NULL. If this is for
|
||||
|
@ -316,6 +316,10 @@ nsEditorEventListener::KeyPress(nsIDOMEvent* aKeyEvent)
|
||||
{
|
||||
NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
if (!mEditor->IsAcceptableInputEvent(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.
|
||||
@ -483,6 +487,10 @@ nsEditorEventListener::HandleText(nsIDOMEvent* aTextEvent)
|
||||
{
|
||||
NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
if (!mEditor->IsAcceptableInputEvent(aTextEvent)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrivateTextEvent> textEvent = do_QueryInterface(aTextEvent);
|
||||
if (!textEvent) {
|
||||
//non-ui event passed in. bad things.
|
||||
@ -773,6 +781,9 @@ NS_IMETHODIMP
|
||||
nsEditorEventListener::HandleStartComposition(nsIDOMEvent* aCompositionEvent)
|
||||
{
|
||||
NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
|
||||
if (!mEditor->IsAcceptableInputEvent(aCompositionEvent)) {
|
||||
return NS_OK;
|
||||
}
|
||||
return mEditor->BeginIMEComposition();
|
||||
}
|
||||
|
||||
@ -780,6 +791,9 @@ NS_IMETHODIMP
|
||||
nsEditorEventListener::HandleEndComposition(nsIDOMEvent* aCompositionEvent)
|
||||
{
|
||||
NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
|
||||
if (!mEditor->IsAcceptableInputEvent(aCompositionEvent)) {
|
||||
return NS_OK;
|
||||
}
|
||||
return mEditor->EndIMEComposition();
|
||||
}
|
||||
|
||||
|
@ -426,10 +426,7 @@ nsHTMLEditor::FindSelectionRoot(nsINode *aNode)
|
||||
|
||||
// For non-readonly editors we want to find the root of the editable subtree
|
||||
// containing aContent.
|
||||
nsIContent *parent;
|
||||
while ((parent = content->GetParent()) && parent->HasFlag(NODE_IS_EDITABLE)) {
|
||||
content = parent;
|
||||
}
|
||||
content = content->GetEditingHost();
|
||||
return content.forget();
|
||||
}
|
||||
|
||||
@ -630,17 +627,6 @@ nsHTMLEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
|
||||
return nsEditor::HandleKeyPressEvent(aKeyEvent);
|
||||
}
|
||||
|
||||
// Don't handle events which do not belong to us (by making sure that the
|
||||
// target of the event is actually editable).
|
||||
// XXX we can remove this check after bug 389372
|
||||
nsCOMPtr<nsIDOMEventTarget> target;
|
||||
nsresult rv = aKeyEvent->GetTarget(getter_AddRefs(target));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(target);
|
||||
if (!IsModifiableNode(targetNode)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsKeyEvent* nativeKeyEvent = GetNativeKeyEvent(aKeyEvent);
|
||||
NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
|
||||
NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS,
|
||||
@ -4200,8 +4186,7 @@ nsHTMLEditor::SelectAll()
|
||||
|
||||
// If the anchor content has independent selection, we never need to explicitly
|
||||
// select its children.
|
||||
nsIFrame* frame = anchorContent->GetPrimaryFrame();
|
||||
if (frame && frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION) {
|
||||
if (anchorContent->HasIndependentSelection()) {
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection);
|
||||
NS_ENSURE_TRUE(selPriv, NS_ERROR_UNEXPECTED);
|
||||
rv = selPriv->SetAncestorLimiter(nsnull);
|
||||
@ -5804,13 +5789,49 @@ nsHTMLEditor::HasFocus()
|
||||
// If the focused content isn't editable, or it has independent selection,
|
||||
// we don't have focus.
|
||||
if (!focusedContent->HasFlag(NODE_IS_EDITABLE) ||
|
||||
IsIndependentSelectionContent(focusedContent)) {
|
||||
focusedContent->HasIndependentSelection()) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
// If our window is focused, we're focused.
|
||||
return OurWindowHasFocus();
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsHTMLEditor::IsActiveInDOMWindow()
|
||||
{
|
||||
NS_ENSURE_TRUE(mDocWeak, PR_FALSE);
|
||||
|
||||
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
||||
NS_ENSURE_TRUE(fm, PR_FALSE);
|
||||
|
||||
nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
|
||||
PRBool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE);
|
||||
|
||||
// If we're in designMode, we're always active in the DOM window.
|
||||
if (inDesignMode) {
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
nsPIDOMWindow* ourWindow = doc->GetWindow();
|
||||
nsCOMPtr<nsPIDOMWindow> win;
|
||||
nsIContent* content =
|
||||
nsFocusManager::GetFocusedDescendant(ourWindow, PR_FALSE,
|
||||
getter_AddRefs(win));
|
||||
if (!content) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
// We're HTML editor for contenteditable
|
||||
|
||||
// If the active content isn't editable, or it has independent selection,
|
||||
// we're not active).
|
||||
if (!content->HasFlag(NODE_IS_EDITABLE) ||
|
||||
content->HasIndependentSelection()) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
already_AddRefed<nsPIDOMEventTarget>
|
||||
nsHTMLEditor::GetPIDOMEventTarget()
|
||||
{
|
||||
@ -5920,11 +5941,46 @@ nsHTMLEditor::OurWindowHasFocus()
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsHTMLEditor::IsIndependentSelectionContent(nsIContent* aContent)
|
||||
nsHTMLEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent)
|
||||
{
|
||||
NS_PRECONDITION(aContent, "aContent must not be null");
|
||||
nsIFrame* frame = aContent->GetPrimaryFrame();
|
||||
return (frame && (frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION));
|
||||
if (!nsEditor::IsAcceptableInputEvent(aEvent)) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
NS_ENSURE_TRUE(mDocWeak, PR_FALSE);
|
||||
|
||||
nsCOMPtr<nsIDOMEventTarget> target;
|
||||
aEvent->GetTarget(getter_AddRefs(target));
|
||||
NS_ENSURE_TRUE(target, PR_FALSE);
|
||||
|
||||
nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocWeak);
|
||||
if (document->HasFlag(NODE_IS_EDITABLE)) {
|
||||
// If this editor is in designMode and the event target is the document,
|
||||
// the event is for this editor.
|
||||
nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(target);
|
||||
if (targetDocument) {
|
||||
return targetDocument == document;
|
||||
}
|
||||
// Otherwise, check whether the event target is in this document or not.
|
||||
nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
|
||||
NS_ENSURE_TRUE(targetContent, PR_FALSE);
|
||||
return document == targetContent->GetCurrentDoc();
|
||||
}
|
||||
|
||||
// If this is for contenteditable, we should check whether the target is
|
||||
// editable or not.
|
||||
nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
|
||||
NS_ENSURE_TRUE(targetContent, PR_FALSE);
|
||||
if (!targetContent->HasFlag(NODE_IS_EDITABLE) ||
|
||||
targetContent->HasIndependentSelection()) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
// Finally, check whether we're actually focused or not. When we're not
|
||||
// focused, we should ignore the dispatched event by script (or something)
|
||||
// because content editable element needs selection in itself for editing.
|
||||
// However, when we're not focused, it's not guaranteed.
|
||||
return IsActiveInDOMWindow();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -149,8 +149,10 @@ public:
|
||||
NS_IMETHODIMP BeginningOfDocument();
|
||||
virtual nsresult HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent);
|
||||
virtual PRBool HasFocus();
|
||||
virtual PRBool IsActiveInDOMWindow();
|
||||
virtual already_AddRefed<nsPIDOMEventTarget> GetPIDOMEventTarget();
|
||||
virtual already_AddRefed<nsIContent> FindSelectionRoot(nsINode *aNode);
|
||||
virtual PRBool IsAcceptableInputEvent(nsIDOMEvent* aEvent);
|
||||
|
||||
/* ------------ nsStubMutationObserver overrides --------- */
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
|
||||
@ -451,6 +453,9 @@ protected:
|
||||
PRBool ShouldReplaceRootElement();
|
||||
void ResetRootElementAndEventTarget();
|
||||
nsresult GetBodyElement(nsIDOMHTMLElement** aBody);
|
||||
// Get the focused node of this editor.
|
||||
// @return If the editor has focus, this returns the focused node.
|
||||
// Otherwise, returns null.
|
||||
already_AddRefed<nsINode> GetFocusedNode();
|
||||
|
||||
// Return TRUE if aElement is a table-related elemet and caret was set
|
||||
@ -745,9 +750,6 @@ protected:
|
||||
|
||||
// Whether the outer window of the DOM event target has focus or not.
|
||||
PRBool OurWindowHasFocus();
|
||||
// Whether the content has independent selection or not. E.g., input field,
|
||||
// password field and textarea element. At that time, this returns TRUE.
|
||||
PRBool IsIndependentSelectionContent(nsIContent* aContent);
|
||||
|
||||
// Data members
|
||||
protected:
|
||||
|
@ -61,6 +61,7 @@ _TEST_FILES = \
|
||||
test_bug550434.html \
|
||||
test_CF_HTML_clipboard.html \
|
||||
test_contenteditable_focus.html \
|
||||
test_contenteditable_text_input_handling.html \
|
||||
test_htmleditor_keyevent_handling.html \
|
||||
test_select_all_without_body.html \
|
||||
file_select_all_without_body.html \
|
||||
|
@ -0,0 +1,336 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for text input event handling on contenteditable editor</title>
|
||||
<script type="text/javascript"
|
||||
src="chrome://mochikit/content/MochiKit/packed.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="display">
|
||||
<p id="static">static content<input id="inputInStatic"><textarea id="textareaInStatic"></textarea></p>
|
||||
<p id="editor"contenteditable="true">content editable<input id="inputInEditor"><textarea id="textareaInEditor"></textarea></p>
|
||||
</div>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
|
||||
<script class="testbody" type="application/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(runTests);
|
||||
|
||||
function runTests()
|
||||
{
|
||||
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
||||
var fm = Components.classes["@mozilla.org/focus-manager;1"].
|
||||
getService(Components.interfaces.nsIFocusManager);
|
||||
|
||||
var listener = {
|
||||
handleEvent: function _hv(aEvent)
|
||||
{
|
||||
aEvent.preventDefault(); // prevent the browser default behavior
|
||||
}
|
||||
};
|
||||
var systemGroup =
|
||||
Components.classes["@mozilla.org/eventlistenerservice;1"].
|
||||
getService(Components.interfaces.nsIEventListenerService).
|
||||
systemEventGroup;
|
||||
window.QueryInterface(Components.interfaces.nsIDOM3EventTarget);
|
||||
window.addGroupedEventListener("keypress", listener, false, systemGroup);
|
||||
|
||||
var staticContent = document.getElementById("static");
|
||||
staticContent._defaultValue = getTextValue(staticContent);
|
||||
staticContent._isFocusable = false;
|
||||
staticContent._isEditable = false;
|
||||
staticContent._isContentEditable = false;
|
||||
staticContent._description = "non-editable p element";
|
||||
var inputInStatic = document.getElementById("inputInStatic");
|
||||
inputInStatic._defaultValue = getTextValue(inputInStatic);
|
||||
inputInStatic._isFocusable = true;
|
||||
inputInStatic._isEditable = true;
|
||||
inputInStatic._isContentEditable = false;
|
||||
inputInStatic._description = "input element in static content";
|
||||
var textareaInStatic = document.getElementById("textareaInStatic");
|
||||
textareaInStatic._defaultValue = getTextValue(textareaInStatic);
|
||||
textareaInStatic._isFocusable = true;
|
||||
textareaInStatic._isEditable = true;
|
||||
textareaInStatic._isContentEditable = false;
|
||||
textareaInStatic._description = "textarea element in static content";
|
||||
var editor = document.getElementById("editor");
|
||||
editor._defaultValue = getTextValue(editor);
|
||||
editor._isFocusable = true;
|
||||
editor._isEditable = true;
|
||||
editor._isContentEditable = true;
|
||||
editor._description = "contenteditable editor";
|
||||
var inputInEditor = document.getElementById("inputInEditor");
|
||||
inputInEditor._defaultValue = getTextValue(inputInEditor);
|
||||
inputInEditor._isFocusable = true;
|
||||
inputInEditor._isEditable = true;
|
||||
inputInEditor._isContentEditable = false;
|
||||
inputInEditor._description = "input element in contenteditable editor";
|
||||
var textareaInEditor = document.getElementById("textareaInEditor");
|
||||
textareaInEditor._defaultValue = getTextValue(textareaInEditor);
|
||||
textareaInEditor._isFocusable = true;
|
||||
textareaInEditor._isEditable = true;
|
||||
textareaInEditor._isContentEditable = false;
|
||||
textareaInEditor._description = "textarea element in contenteditable editor";
|
||||
|
||||
function getTextValue(aElement)
|
||||
{
|
||||
if (aElement == editor) {
|
||||
var value = "";
|
||||
for (var node = aElement.firstChild; node; node = node.nextSibling) {
|
||||
if (node.nodeType == 3) {
|
||||
value += node.data;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return aElement.value;
|
||||
}
|
||||
|
||||
function testTextInput(aFocus)
|
||||
{
|
||||
var when = " when " +
|
||||
((aFocus && aFocus._isFocusable) ? aFocus._description + " has focus" :
|
||||
"nobody has focus");
|
||||
|
||||
function checkValue(aElement, aInsertedText)
|
||||
{
|
||||
if (aElement == aFocus && aElement._isEditable) {
|
||||
is(getTextValue(aElement), aInsertedText + aElement._defaultValue,
|
||||
aElement._description +
|
||||
" wasn't edited by synthesized key events" + when);
|
||||
return;
|
||||
}
|
||||
is(getTextValue(aElement), aElement._defaultValue,
|
||||
aElement._description +
|
||||
" was edited by synthesized key events" + when);
|
||||
}
|
||||
|
||||
if (aFocus && aFocus._isFocusable) {
|
||||
aFocus.focus();
|
||||
is(fm.focusedElement, aFocus,
|
||||
aFocus._description + " didn't get focus at preparing tests" + when);
|
||||
} else {
|
||||
var focusedElement = fm.focusedElement;
|
||||
if (focusedElement) {
|
||||
focusedElement.blur();
|
||||
}
|
||||
ok(!fm.focusedElement,
|
||||
"Failed to blur at preparing tests" + when);
|
||||
}
|
||||
|
||||
if (aFocus && aFocus._isFocusable) {
|
||||
synthesizeKey("A", { });
|
||||
synthesizeKey("B", { });
|
||||
synthesizeKey("C", { });
|
||||
checkValue(staticContent, "ABC");
|
||||
checkValue(inputInStatic, "ABC");
|
||||
checkValue(textareaInStatic, "ABC");
|
||||
checkValue(editor, "ABC");
|
||||
checkValue(inputInEditor, "ABC");
|
||||
checkValue(textareaInEditor, "ABC");
|
||||
|
||||
if (aFocus._isEditable) {
|
||||
synthesizeKey("VK_BACK_SPACE", { });
|
||||
synthesizeKey("VK_BACK_SPACE", { });
|
||||
synthesizeKey("VK_BACK_SPACE", { });
|
||||
checkValue(staticContent, "");
|
||||
checkValue(inputInStatic, "");
|
||||
checkValue(textareaInStatic, "");
|
||||
checkValue(editor, "");
|
||||
checkValue(inputInEditor, "");
|
||||
checkValue(textareaInEditor, "");
|
||||
}
|
||||
}
|
||||
|
||||
// When key events are fired on unfocused editor.
|
||||
function testDispatchedKeyEvent(aTarget)
|
||||
{
|
||||
var targetDescription = " (dispatched to " + aTarget._description + ")";
|
||||
function dispatchKeyEvent(aKeyCode, aChar, aTarget)
|
||||
{
|
||||
var keyEvent = document.createEvent("KeyEvents");
|
||||
keyEvent.initKeyEvent("keypress", true, true, null, false, false,
|
||||
false, false, aKeyCode,
|
||||
aChar ? aChar.charCodeAt(0) : 0);
|
||||
aTarget.dispatchEvent(keyEvent);
|
||||
}
|
||||
|
||||
function checkValueForDispatchedKeyEvent(aElement, aInsertedText)
|
||||
{
|
||||
if (aElement == aTarget && aElement._isEditable &&
|
||||
(!aElement._isContentEditable || aElement == aFocus)) {
|
||||
is(getTextValue(aElement), aInsertedText + aElement._defaultValue,
|
||||
aElement._description +
|
||||
" wasn't edited by dispatched key events" +
|
||||
when + targetDescription);
|
||||
return;
|
||||
}
|
||||
if (aElement == aTarget) {
|
||||
is(getTextValue(aElement), aElement._defaultValue,
|
||||
aElement._description +
|
||||
" was edited by dispatched key events" +
|
||||
when + targetDescription);
|
||||
return;
|
||||
}
|
||||
is(getTextValue(aElement), aElement._defaultValue,
|
||||
aElement._description +
|
||||
" was edited by key events unexpectedly" +
|
||||
when + targetDescription);
|
||||
}
|
||||
|
||||
dispatchKeyEvent(0, "A", aTarget);
|
||||
dispatchKeyEvent(0, "B", aTarget);
|
||||
dispatchKeyEvent(0, "C", aTarget);
|
||||
|
||||
checkValueForDispatchedKeyEvent(staticContent, "ABC");
|
||||
checkValueForDispatchedKeyEvent(inputInStatic, "ABC");
|
||||
checkValueForDispatchedKeyEvent(textareaInStatic, "ABC");
|
||||
checkValueForDispatchedKeyEvent(editor, "ABC");
|
||||
checkValueForDispatchedKeyEvent(inputInEditor, "ABC");
|
||||
checkValueForDispatchedKeyEvent(textareaInEditor, "ABC");
|
||||
|
||||
const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
|
||||
dispatchKeyEvent(nsIDOMKeyEvent.DOM_VK_BACK_SPACE, 0, aTarget);
|
||||
dispatchKeyEvent(nsIDOMKeyEvent.DOM_VK_BACK_SPACE, 0, aTarget);
|
||||
dispatchKeyEvent(nsIDOMKeyEvent.DOM_VK_BACK_SPACE, 0, aTarget);
|
||||
|
||||
checkValueForDispatchedKeyEvent(staticContent, "");
|
||||
checkValueForDispatchedKeyEvent(inputInStatic, "");
|
||||
checkValueForDispatchedKeyEvent(textareaInStatic, "");
|
||||
checkValueForDispatchedKeyEvent(editor, "");
|
||||
checkValueForDispatchedKeyEvent(inputInEditor, "");
|
||||
checkValueForDispatchedKeyEvent(textareaInEditor, "");
|
||||
}
|
||||
|
||||
testDispatchedKeyEvent(staticContent);
|
||||
testDispatchedKeyEvent(inputInStatic);
|
||||
testDispatchedKeyEvent(textareaInStatic);
|
||||
testDispatchedKeyEvent(editor);
|
||||
testDispatchedKeyEvent(inputInEditor);
|
||||
testDispatchedKeyEvent(textareaInEditor);
|
||||
|
||||
if (!aFocus._isEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// IME
|
||||
const nsIDOMWindowUtils = Components.interfaces.nsIDOMWindowUtils;
|
||||
// start composition
|
||||
synthesizeComposition(true);
|
||||
// input first character
|
||||
synthesizeText(
|
||||
{ "composition":
|
||||
{ "string": "\u3089",
|
||||
"clauses":
|
||||
[
|
||||
{ "length": 1, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
|
||||
]
|
||||
},
|
||||
"caret": { "start": 1, "length": 0 }
|
||||
});
|
||||
var queryText = synthesizeQueryTextContent(0, 100);
|
||||
ok(queryText, "query text event result is null" + when);
|
||||
if (!queryText) {
|
||||
return;
|
||||
}
|
||||
ok(queryText.succeeded, "query text event failed" + when);
|
||||
if (!queryText.succeeded) {
|
||||
return;
|
||||
}
|
||||
is(queryText.text, "\u3089" + aFocus._defaultValue,
|
||||
"composing text is incorrect" + when);
|
||||
var querySelectedText = synthesizeQuerySelectedText();
|
||||
ok(querySelectedText, "query selected text event result is null" + when);
|
||||
if (!querySelectedText) {
|
||||
return;
|
||||
}
|
||||
ok(querySelectedText.succeeded, "query selected text event failed" + when);
|
||||
if (!querySelectedText.succeeded) {
|
||||
return;
|
||||
}
|
||||
is(querySelectedText.offset, 1,
|
||||
"query selected text event returns wrong offset" + when);
|
||||
is(querySelectedText.text, "",
|
||||
"query selected text event returns wrong selected text" + when);
|
||||
// commit composition
|
||||
synthesizeText(
|
||||
{ "composition":
|
||||
{ "string": "\u3089",
|
||||
"clauses":
|
||||
[
|
||||
{ "length": 0, "attr": 0 }
|
||||
]
|
||||
},
|
||||
"caret": { "start": 1, "length": 0 }
|
||||
});
|
||||
queryText = synthesizeQueryTextContent(0, 100);
|
||||
ok(queryText, "query text event result is null after commit" + when);
|
||||
if (!queryText) {
|
||||
return;
|
||||
}
|
||||
ok(queryText.succeeded, "query text event failed after commit" + when);
|
||||
if (!queryText.succeeded) {
|
||||
return;
|
||||
}
|
||||
is(queryText.text, "\u3089" + aFocus._defaultValue,
|
||||
"composing text is incorrect after commit" + when);
|
||||
querySelectedText = synthesizeQuerySelectedText();
|
||||
ok(querySelectedText,
|
||||
"query selected text event result is null after commit" + when);
|
||||
if (!querySelectedText) {
|
||||
return;
|
||||
}
|
||||
ok(querySelectedText.succeeded,
|
||||
"query selected text event failed after commit" + when);
|
||||
if (!querySelectedText.succeeded) {
|
||||
return;
|
||||
}
|
||||
is(querySelectedText.offset, 1,
|
||||
"query selected text event returns wrong offset after commit" + when);
|
||||
is(querySelectedText.text, "",
|
||||
"query selected text event returns wrong selected text after commit" +
|
||||
when);
|
||||
// end composition
|
||||
synthesizeComposition(false);
|
||||
|
||||
checkValue(staticContent, "\u3089");
|
||||
checkValue(inputInStatic, "\u3089");
|
||||
checkValue(textareaInStatic, "\u3089");
|
||||
checkValue(editor, "\u3089");
|
||||
checkValue(inputInEditor, "\u3089");
|
||||
checkValue(textareaInEditor, "\u3089");
|
||||
|
||||
synthesizeKey("VK_BACK_SPACE", { });
|
||||
checkValue(staticContent, "");
|
||||
checkValue(inputInStatic, "");
|
||||
checkValue(textareaInStatic, "");
|
||||
checkValue(editor, "");
|
||||
checkValue(inputInEditor, "");
|
||||
checkValue(textareaInEditor, "");
|
||||
}
|
||||
|
||||
testTextInput(inputInStatic);
|
||||
testTextInput(textareaInStatic);
|
||||
testTextInput(editor);
|
||||
testTextInput(inputInEditor);
|
||||
testTextInput(textareaInEditor);
|
||||
|
||||
window.removeGroupedEventListener("keypress", listener, false, systemGroup);
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=468167
|
||||
<title>Test for Bug 468167</title>
|
||||
<script type="application/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
@ -19,31 +20,35 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=468167
|
||||
|
||||
/** Test for Bug 468167 **/
|
||||
|
||||
var ta = document.getElementById("ta");
|
||||
is(ta.scrollTop, 0, "initially scrolled to top");
|
||||
var s = "";
|
||||
for (var i = 0; i < 40; ++i) {
|
||||
// three characters per line
|
||||
if (i < 10)
|
||||
s += "0";
|
||||
s += i + "\n";
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(runTests);
|
||||
|
||||
function runTests()
|
||||
{
|
||||
var ta = document.getElementById("ta");
|
||||
ta.focus();
|
||||
is(ta.scrollTop, 0, "initially scrolled to top");
|
||||
var s = "";
|
||||
for (var i = 0; i < 40; ++i) {
|
||||
// three characters per line
|
||||
if (i < 10)
|
||||
s += "0";
|
||||
s += i + "\n";
|
||||
}
|
||||
ta.value = s;
|
||||
is(ta.scrollTop, 0, "scrolled to top after adding content");
|
||||
ta.scrollTop = 9999; // scroll down as far as we can
|
||||
isnot(ta.scrollTop, 0, "scrolled down after scrolling down");
|
||||
|
||||
ta.setSelectionRange(0, 99); // 33 lines out of 40
|
||||
|
||||
// Send a backspace key event to delete the selection
|
||||
synthesizeKey("VK_BACK_SPACE", { });
|
||||
|
||||
is(ta.scrollTop, 0, "scrolled to top after deleting selection");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
ta.value = s;
|
||||
is(ta.scrollTop, 0, "scrolled to top after adding content");
|
||||
ta.scrollTop = 9999; // scroll down as far as we can
|
||||
isnot(ta.scrollTop, 0, "scrolled down after scrolling down");
|
||||
|
||||
ta.setSelectionRange(0, 99); // 33 lines out of 40
|
||||
|
||||
// Send a backspace key event to delete the selection
|
||||
var e = document.createEvent("KeyEvents");
|
||||
e.initKeyEvent("keypress", true, true, document.defaultView,
|
||||
false, false, false, false,
|
||||
KeyEvent.DOM_VK_BACK_SPACE, 0);
|
||||
ta.dispatchEvent(e);
|
||||
|
||||
is(ta.scrollTop, 0, "scrolled to top after deleting selection");
|
||||
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
Loading…
Reference in New Issue
Block a user