mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 574003 - coalesce text evetns on nodes removal, r=davidb, marcoz
This commit is contained in:
parent
344791f1a9
commit
a90254e830
@ -300,6 +300,7 @@ nsAccReorderEvent::HasAccessibleInReasonSubtree()
|
||||
return accessible || nsAccUtils::HasAccessibleChildren(mReasonNode);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsAccStateChangeEvent
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -387,14 +388,12 @@ NS_IMPL_ISUPPORTS_INHERITED1(nsAccTextChangeEvent, nsAccEvent,
|
||||
// a defunct accessible so the behaviour should be equivalent.
|
||||
// XXX revisit this when coalescence is faster (eCoalesceFromSameSubtree)
|
||||
nsAccTextChangeEvent::
|
||||
nsAccTextChangeEvent(nsAccessible *aAccessible,
|
||||
PRInt32 aStart, PRUint32 aLength,
|
||||
nsAccTextChangeEvent(nsAccessible *aAccessible, PRInt32 aStart,
|
||||
nsAString& aModifiedText, PRBool aIsInserted,
|
||||
PRBool aIsAsynch, EIsFromUserInput aIsFromUserInput) :
|
||||
nsAccEvent(aIsInserted ? nsIAccessibleEvent::EVENT_TEXT_INSERTED : nsIAccessibleEvent::EVENT_TEXT_REMOVED,
|
||||
aAccessible, aIsAsynch, aIsFromUserInput, eAllowDupes),
|
||||
mStart(aStart), mLength(aLength), mIsInserted(aIsInserted),
|
||||
mModifiedText(aModifiedText)
|
||||
mStart(aStart), mIsInserted(aIsInserted), mModifiedText(aModifiedText)
|
||||
{
|
||||
}
|
||||
|
||||
@ -410,7 +409,7 @@ NS_IMETHODIMP
|
||||
nsAccTextChangeEvent::GetLength(PRUint32 *aLength)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aLength);
|
||||
*aLength = mLength;
|
||||
*aLength = GetLength();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -429,6 +428,24 @@ nsAccTextChangeEvent::GetModifiedText(nsAString& aModifiedText)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// AccHideEvent
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AccHideEvent::
|
||||
AccHideEvent(nsAccessible* aTarget, nsINode* aTargetNode,
|
||||
PRBool aIsAsynch, EIsFromUserInput aIsFromUserInput) :
|
||||
nsAccEvent(nsIAccessibleEvent::EVENT_HIDE, aTarget, aIsAsynch,
|
||||
aIsFromUserInput, eCoalesceFromSameSubtree)
|
||||
{
|
||||
mNode = aTargetNode;
|
||||
mParent = mAccessible->GetCachedParent();
|
||||
mNextSibling = mAccessible->GetCachedNextSibling();
|
||||
mPrevSibling = mAccessible->GetCachedPrevSibling();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsAccCaretMoveEvent
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -112,14 +112,15 @@ public:
|
||||
PRBool IsFromUserInput() const { return mIsFromUserInput; }
|
||||
|
||||
nsAccessible *GetAccessible();
|
||||
nsINode* GetNode();
|
||||
nsDocAccessible* GetDocAccessible();
|
||||
nsINode* GetNode();
|
||||
|
||||
enum EventGroup {
|
||||
eGenericEvent,
|
||||
eReorderEvent,
|
||||
eStateChangeEvent,
|
||||
eTextChangeEvent,
|
||||
eHideEvent,
|
||||
eCaretMoveEvent,
|
||||
eTableChangeEvent
|
||||
};
|
||||
@ -236,7 +237,7 @@ class nsAccTextChangeEvent: public nsAccEvent,
|
||||
{
|
||||
public:
|
||||
nsAccTextChangeEvent(nsAccessible *aAccessible, PRInt32 aStart,
|
||||
PRUint32 aLength, nsAString& aModifiedText,
|
||||
nsAString& aModifiedText,
|
||||
PRBool aIsInserted, PRBool aIsAsynch = PR_FALSE,
|
||||
EIsFromUserInput aIsFromUserInput = eAutoDetect);
|
||||
|
||||
@ -252,14 +253,41 @@ public:
|
||||
|
||||
// nsAccTextChangeEvent
|
||||
PRInt32 GetStartOffset() const { return mStart; }
|
||||
PRUint32 GetLength() const { return mLength; }
|
||||
PRUint32 GetLength() const { return mModifiedText.Length(); }
|
||||
PRBool IsTextInserted() const { return mIsInserted; }
|
||||
|
||||
private:
|
||||
PRInt32 mStart;
|
||||
PRUint32 mLength;
|
||||
PRBool mIsInserted;
|
||||
nsString mModifiedText;
|
||||
|
||||
friend class nsAccEventQueue;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Accessible hide events.
|
||||
*/
|
||||
class AccHideEvent : public nsAccEvent
|
||||
{
|
||||
public:
|
||||
AccHideEvent(nsAccessible* aTarget, nsINode* aTargetNode,
|
||||
PRBool aIsAsynch, EIsFromUserInput aIsFromUserInput);
|
||||
|
||||
// nsAccEvent
|
||||
static const EventGroup kEventGroup = eHideEvent;
|
||||
virtual unsigned int GetEventGroups() const
|
||||
{
|
||||
return nsAccEvent::GetEventGroups() | (1U << eHideEvent);
|
||||
}
|
||||
|
||||
protected:
|
||||
nsRefPtr<nsAccessible> mParent;
|
||||
nsRefPtr<nsAccessible> mNextSibling;
|
||||
nsRefPtr<nsAccessible> mPrevSibling;
|
||||
nsRefPtr<nsAccTextChangeEvent> mTextChangeEvent;
|
||||
|
||||
friend class nsAccEventQueue;
|
||||
};
|
||||
|
||||
|
||||
|
@ -259,8 +259,17 @@ public:
|
||||
* Return cached accessible of parent-child relatives.
|
||||
*/
|
||||
nsAccessible* GetCachedParent() const { return mParent; }
|
||||
nsAccessible* GetCachedNextSibling() const
|
||||
{
|
||||
return mParent ?
|
||||
mParent->mChildren.SafeElementAt(mIndexInParent + 1, nsnull) : nsnull;
|
||||
}
|
||||
nsAccessible* GetCachedPrevSibling() const
|
||||
{
|
||||
return mParent ?
|
||||
mParent->mChildren.SafeElementAt(mIndexInParent - 1, nsnull) : nsnull;
|
||||
}
|
||||
PRUint32 GetCachedChildCount() const { return mChildren.Length(); }
|
||||
|
||||
PRBool AreChildrenCached() const { return mAreChildrenInitialized; }
|
||||
|
||||
#ifdef DEBUG
|
||||
|
@ -1344,17 +1344,15 @@ nsDocAccessible::FireTextChangeEventForText(nsIContent *aContent,
|
||||
if (NS_FAILED(rv))
|
||||
return;
|
||||
|
||||
// Get text length.
|
||||
PRUint32 length = text.Length();
|
||||
if (length == 0)
|
||||
if (text.IsEmpty())
|
||||
return;
|
||||
|
||||
// Normally we only fire delayed events created from the node, not an
|
||||
// accessible object. See the nsAccTextChangeEvent constructor for details
|
||||
// about this exceptional case.
|
||||
nsRefPtr<nsAccEvent> event =
|
||||
new nsAccTextChangeEvent(textAccessible, offset, length, text,
|
||||
aIsInserted, PR_FALSE);
|
||||
new nsAccTextChangeEvent(textAccessible, offset, text, aIsInserted,
|
||||
PR_FALSE);
|
||||
FireDelayedAccessibleEvent(event);
|
||||
|
||||
FireValueChangeForTextFields(textAccessible);
|
||||
@ -1425,13 +1423,12 @@ nsDocAccessible::CreateTextChangeEventForNode(nsAccessible *aContainerAccessible
|
||||
aAccessibleForChangeNode->AppendTextTo(text, 0, PR_UINT32_MAX);
|
||||
}
|
||||
|
||||
PRUint32 length = text.Length();
|
||||
if (length == 0)
|
||||
if (text.IsEmpty())
|
||||
return nsnull;
|
||||
|
||||
nsAccEvent *event =
|
||||
new nsAccTextChangeEvent(aContainerAccessible, offset, length, text,
|
||||
aIsInserting, aIsAsynch, aIsFromUserInput);
|
||||
new nsAccTextChangeEvent(aContainerAccessible, offset, text,
|
||||
aIsInserting, aIsAsynch, aIsFromUserInput);
|
||||
NS_IF_ADDREF(event);
|
||||
|
||||
return event;
|
||||
@ -1494,7 +1491,7 @@ nsDocAccessible::ProcessPendingEvent(nsAccEvent *aEvent)
|
||||
// Don't need to invalidate this current accessible, but can
|
||||
// just invalidate the children instead
|
||||
FireShowHideEvents(node, PR_TRUE, eventType, eNormalEvent,
|
||||
isAsync, isFromUserInput);
|
||||
isAsync, isFromUserInput);
|
||||
return;
|
||||
}
|
||||
gLastFocusedFrameType = newFrameType;
|
||||
@ -1828,24 +1825,6 @@ nsDocAccessible::InvalidateCacheSubtree(nsIContent *aChild,
|
||||
eDelayedEvent, isAsynch);
|
||||
if (NS_FAILED(rv))
|
||||
return;
|
||||
|
||||
if (aChild) {
|
||||
// Fire text change unless the node being removed is for this doc.
|
||||
// When a node is hidden or removed, the text in an ancestor hyper text will lose characters
|
||||
// At this point we still have the frame and accessible for this node if there was one
|
||||
// XXX Collate events when a range is deleted
|
||||
// XXX We need a way to ignore SplitNode and JoinNode() when they
|
||||
// do not affect the text within the hypertext
|
||||
// Normally we only fire delayed events created from the node, not an
|
||||
// accessible object. See the nsAccTextChangeEvent constructor for details
|
||||
// about this exceptional case.
|
||||
nsRefPtr<nsAccEvent> textChangeEvent =
|
||||
CreateTextChangeEventForNode(containerAccessible, aChild, childAccessible,
|
||||
PR_FALSE, isAsynch);
|
||||
if (textChangeEvent) {
|
||||
FireDelayedAccessibleEvent(textChangeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to get an accessible for the mutation event's container node
|
||||
@ -1948,9 +1927,19 @@ nsDocAccessible::FireShowHideEvents(nsINode *aNode,
|
||||
if (accessible) {
|
||||
// Found an accessible, so fire the show/hide on it and don't look further
|
||||
// into this subtree.
|
||||
nsRefPtr<nsAccEvent> event =
|
||||
new nsAccEvent(aEventType, accessible, aIsAsyncChange, aIsFromUserInput,
|
||||
nsAccEvent::eCoalesceFromSameSubtree);
|
||||
nsRefPtr<nsAccEvent> event;
|
||||
if (aDelayedOrNormal == eDelayedEvent &&
|
||||
aEventType == nsIAccessibleEvent::EVENT_HIDE) {
|
||||
// Use AccHideEvent for delayed hide events to coalesce text change events
|
||||
// caused by these hide events.
|
||||
event = new AccHideEvent(accessible, accessible->GetNode(),
|
||||
aIsAsyncChange, aIsFromUserInput);
|
||||
|
||||
} else {
|
||||
event = new nsAccEvent(aEventType, accessible, aIsAsyncChange,
|
||||
aIsFromUserInput,
|
||||
nsAccEvent::eCoalesceFromSameSubtree);
|
||||
}
|
||||
NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
if (aDelayedOrNormal == eDelayedEvent)
|
||||
|
@ -146,10 +146,16 @@ void
|
||||
nsAccEventQueue::Push(nsAccEvent *aEvent)
|
||||
{
|
||||
mEvents.AppendElement(aEvent);
|
||||
|
||||
|
||||
// Filter events.
|
||||
CoalesceEvents();
|
||||
|
||||
|
||||
// Associate text change with hide event if it wasn't stolen from hiding
|
||||
// siblings during coalescence.
|
||||
AccHideEvent* hideEvent = downcast_accEvent(aEvent);
|
||||
if (hideEvent && !hideEvent->mTextChangeEvent)
|
||||
CreateTextChangeEventFor(hideEvent);
|
||||
|
||||
// Process events.
|
||||
PrepareFlush();
|
||||
}
|
||||
@ -205,9 +211,16 @@ nsAccEventQueue::WillRefresh(mozilla::TimeStamp aTime)
|
||||
for (PRUint32 index = 0; index < length; index ++) {
|
||||
|
||||
nsAccEvent *accEvent = events[index];
|
||||
if (accEvent->mEventRule != nsAccEvent::eDoNotEmit)
|
||||
if (accEvent->mEventRule != nsAccEvent::eDoNotEmit) {
|
||||
mDocument->ProcessPendingEvent(accEvent);
|
||||
|
||||
AccHideEvent* hideEvent = downcast_accEvent(accEvent);
|
||||
if (hideEvent) {
|
||||
if (hideEvent->mTextChangeEvent)
|
||||
mDocument->ProcessPendingEvent(hideEvent->mTextChangeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// No document means it was shut down during event handling by AT
|
||||
if (!mDocument)
|
||||
return;
|
||||
@ -244,15 +257,41 @@ nsAccEventQueue::CoalesceEvents()
|
||||
continue; // Different type
|
||||
|
||||
// Skip event for application accessible since no coalescence for it
|
||||
// is supported. Ignore events unattached from DOM and events from
|
||||
// different documents since we can't coalesce them.
|
||||
if (!thisEvent->mNode || !thisEvent->mNode->IsInDoc() ||
|
||||
// is supported. Ignore events from different documents since we don't
|
||||
// coalesce them.
|
||||
if (!thisEvent->mNode ||
|
||||
thisEvent->mNode->GetOwnerDoc() != tailEvent->mNode->GetOwnerDoc())
|
||||
continue;
|
||||
|
||||
// If event queue contains an event of the same type and having target
|
||||
// that is sibling of target of newly appended event then apply its
|
||||
// event rule to the newly appended event.
|
||||
|
||||
// XXX: deal with show events separately because they can't be
|
||||
// coalesced by accessible tree the same as hide events since target
|
||||
// accessibles can't be created at this point because of lazy frame
|
||||
// construction (bug 570275).
|
||||
|
||||
// Coalesce hide events for sibling targets.
|
||||
if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_HIDE) {
|
||||
AccHideEvent* tailHideEvent = downcast_accEvent(tailEvent);
|
||||
AccHideEvent* thisHideEvent = downcast_accEvent(thisEvent);
|
||||
if (thisHideEvent->mParent == tailHideEvent->mParent) {
|
||||
tailEvent->mEventRule = thisEvent->mEventRule;
|
||||
|
||||
// Coalesce text change events for hide events.
|
||||
if (tailEvent->mEventRule != nsAccEvent::eDoNotEmit)
|
||||
CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore events unattached from DOM since we don't coalesce them.
|
||||
if (!thisEvent->mNode->IsInDoc())
|
||||
continue;
|
||||
|
||||
// Coalesce show and reorder events by sibling targets.
|
||||
if (thisEvent->mNode->GetNodeParent() ==
|
||||
tailEvent->mNode->GetNodeParent()) {
|
||||
tailEvent->mEventRule = thisEvent->mEventRule;
|
||||
@ -462,3 +501,67 @@ nsAccEventQueue::CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent,
|
||||
if (reorderEvent->IsUnconditionalEvent())
|
||||
aDescendantAccEvent->mEventRule = nsAccEvent::eDoNotEmit;
|
||||
}
|
||||
|
||||
void
|
||||
nsAccEventQueue::CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
|
||||
AccHideEvent* aThisEvent)
|
||||
{
|
||||
// XXX: we need a way to ignore SplitNode and JoinNode() when they do not
|
||||
// affect the text within the hypertext.
|
||||
|
||||
nsAccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent;
|
||||
if (!textEvent)
|
||||
return;
|
||||
|
||||
if (aThisEvent->mNextSibling == aTailEvent->mAccessible) {
|
||||
aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText,
|
||||
0, PR_UINT32_MAX);
|
||||
|
||||
} else if (aThisEvent->mPrevSibling == aTailEvent->mAccessible) {
|
||||
PRUint32 oldLen = textEvent->GetLength();
|
||||
aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText,
|
||||
0, PR_UINT32_MAX);
|
||||
textEvent->mStart -= textEvent->GetLength() - oldLen;
|
||||
}
|
||||
|
||||
aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent);
|
||||
}
|
||||
|
||||
void
|
||||
nsAccEventQueue::CreateTextChangeEventFor(AccHideEvent* aEvent)
|
||||
{
|
||||
nsRefPtr<nsHyperTextAccessible> textAccessible = do_QueryObject(
|
||||
GetAccService()->GetContainerAccessible(aEvent->mNode,
|
||||
aEvent->mAccessible->GetWeakShell()));
|
||||
if (!textAccessible)
|
||||
return;
|
||||
|
||||
PRInt32 offset = 0;
|
||||
nsAccessible *changeAcc =
|
||||
textAccessible->DOMPointToHypertextOffset(aEvent->mNode, -1, &offset);
|
||||
NS_ASSERTION(!changeAcc || changeAcc == aEvent->mAccessible,
|
||||
"Hypertext is reporting a different accessible for this node");
|
||||
|
||||
// Don't fire event for the first html:br in an editor.
|
||||
if (nsAccUtils::Role(aEvent->mAccessible) ==
|
||||
nsIAccessibleRole::ROLE_WHITESPACE) {
|
||||
nsCOMPtr<nsIEditor> editor;
|
||||
textAccessible->GetAssociatedEditor(getter_AddRefs(editor));
|
||||
if (editor) {
|
||||
PRBool isEmpty = PR_FALSE;
|
||||
editor->GetDocumentIsEmpty(&isEmpty);
|
||||
if (isEmpty)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoString text;
|
||||
aEvent->mAccessible->AppendTextTo(text, 0, PR_UINT32_MAX);
|
||||
if (text.IsEmpty())
|
||||
return;
|
||||
|
||||
aEvent->mTextChangeEvent =
|
||||
new nsAccTextChangeEvent(textAccessible, offset, text, PR_FALSE,
|
||||
aEvent->mIsAsync,
|
||||
aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
|
||||
}
|
||||
|
@ -157,6 +157,20 @@ private:
|
||||
void CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent,
|
||||
nsAccEvent *aDescendantAccEvent);
|
||||
|
||||
/**
|
||||
* Coalesce text change events caused by sibling hide events.
|
||||
*/
|
||||
void CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
|
||||
AccHideEvent* aThisEvent);
|
||||
|
||||
/**
|
||||
* Create text change event caused by hide event. When a node is hidden or
|
||||
* removed, the text in an ancestor hyper text will lose characters. Create
|
||||
* text change event unless the node is being removed or frame is being
|
||||
* destroyed.
|
||||
*/
|
||||
void CreateTextChangeEventFor(AccHideEvent* aEvent);
|
||||
|
||||
/**
|
||||
* Indicates whether we're waiting on a refresh notification from our
|
||||
* presshell to flush events
|
||||
|
@ -96,6 +96,8 @@ const WIN = (navigator.platform.indexOf("Win") != -1)? true : false;
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Accessible general
|
||||
|
||||
const kEmbedChar = String.fromCharCode(0xfffc);
|
||||
|
||||
/**
|
||||
* nsIAccessibleRetrieval, initialized when test is loaded.
|
||||
*/
|
||||
|
@ -76,8 +76,7 @@
|
||||
*/
|
||||
function removeChildDiv(aID, aChildId)
|
||||
{
|
||||
this.__proto__ = new textRemoveInvoker(aID, 5, 6,
|
||||
String.fromCharCode(0xfffc));
|
||||
this.__proto__ = new textRemoveInvoker(aID, 5, 6, kEmbedChar);
|
||||
|
||||
this.invoke = function removeChildDiv_invoke()
|
||||
{
|
||||
@ -97,6 +96,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove children from text container from first to last child or vice
|
||||
* versa.
|
||||
*/
|
||||
function removeChildren(aID, aLastToFirst, aStart, aEnd, aText)
|
||||
{
|
||||
this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText);
|
||||
|
||||
this.invoke = function removeChildren_invoke()
|
||||
{
|
||||
ensureAccessibleTree(this.DOMNode);
|
||||
|
||||
if (aLastToFirst) {
|
||||
while (this.DOMNode.firstChild)
|
||||
this.DOMNode.removeChild(this.DOMNode.lastChild);
|
||||
} else {
|
||||
while (this.DOMNode.firstChild)
|
||||
this.DOMNode.removeChild(this.DOMNode.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
this.getID = function removeChildren_getID()
|
||||
{
|
||||
return "remove children of " + prettyName(aID) +
|
||||
(aLastToFirst ? " from last to first" : " from first to last");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove text from HTML input.
|
||||
*/
|
||||
@ -156,7 +183,7 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Do tests
|
||||
var gQueue = null;
|
||||
// gA11yEventDumpID = "eventdump"; // debug stuff
|
||||
//gA11yEventDumpID = "eventdump"; // debug stuff
|
||||
|
||||
function doTests()
|
||||
{
|
||||
@ -169,6 +196,11 @@
|
||||
// Remove embedded character.
|
||||
gQueue.push(new removeChildDiv("div", 1));
|
||||
|
||||
// Remove all children.
|
||||
var text = kEmbedChar + "txt" + kEmbedChar;
|
||||
gQueue.push(new removeChildren("div2", true, 0, 5, text));
|
||||
gQueue.push(new removeChildren("div3", false, 0, 5, text));
|
||||
|
||||
// Text remove from text node within hypertext accessible.
|
||||
gQueue.push(new removeTextFromInput("input", 1, 3, "al"));
|
||||
|
||||
@ -193,11 +225,16 @@
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=566293"
|
||||
title=" wrong length of text remove event when inaccessible node containing accessible nodes is removed">
|
||||
Mozilla Bug 566293
|
||||
</a>
|
||||
</a><br>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=570710"
|
||||
title="Avoid extra array traversal during text event creation">
|
||||
Mozilla Bug 570710
|
||||
</a><br>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=574003"
|
||||
title="Coalesce text events on nodes removal">
|
||||
Mozilla Bug 574003
|
||||
</a>
|
||||
|
||||
<p id="display"></p>
|
||||
@ -208,6 +245,8 @@
|
||||
|
||||
<p id="p"><span><span>333</span><span>22</span></span>1111</p>
|
||||
<div id="div">hello<div>hello</div>hello</div>
|
||||
<div id="div2"><div>txt</div>txt<div>txt</div></div>
|
||||
<div id="div3"><div>txt</div>txt<div>txt</div></div>
|
||||
<input id="input" value="value">
|
||||
<div contentEditable="true" id="editable">value</div>
|
||||
<div contentEditable="true" id="editable2"><span>value</span></div>
|
||||
|
Loading…
Reference in New Issue
Block a user