Bug 626660, part2 - fix text diff algorithm, r=davidb, sr=neil, a=betaN

This commit is contained in:
Alexander Surkov 2011-02-08 12:48:41 +08:00
parent 0678b1c95d
commit e4cf999060
7 changed files with 606 additions and 285 deletions

View File

@ -77,6 +77,7 @@ CPPSRCS = \
nsTextAccessible.cpp \
nsTextEquivUtils.cpp \
nsTextAttrs.cpp \
TextUpdater.cpp \
$(NULL)
EXPORTS = \

View File

@ -44,6 +44,7 @@
#include "nsDocAccessible.h"
#include "nsEventShell.h"
#include "nsTextAccessible.h"
#include "TextUpdater.h"
////////////////////////////////////////////////////////////////////////////////
@ -574,287 +575,6 @@ NotificationController::CreateTextChangeEventFor(AccMutationEvent* aEvent)
////////////////////////////////////////////////////////////////////////////////
// Notification controller: text leaf accessible text update
/**
* Used to find a difference between old and new text and fire text change
* events.
*/
class TextUpdater
{
public:
TextUpdater(nsDocAccessible* aDocument, nsTextAccessible* aTextLeaf) :
mDocument(aDocument), mTextLeaf(aTextLeaf) { }
~TextUpdater() { mDocument = nsnull; mTextLeaf = nsnull; }
/**
* Update text of the text leaf accessible, fire text change events for its
* container hypertext accessible.
*/
void Run(const nsAString& aNewText);
private:
TextUpdater();
TextUpdater(const TextUpdater&);
TextUpdater& operator = (const TextUpdater&);
/**
* Fire text change events based on difference between strings.
*/
void FindDiffNFireEvents(const nsDependentSubstring& aStr1,
const nsDependentSubstring& aStr2,
PRUint32** aMatrix,
PRUint32 aStartOffset);
/**
* Change type used to describe the diff between strings.
*/
enum ChangeType {
eNoChange,
eInsertion,
eRemoval,
eSubstitution
};
/**
* Helper to fire text change events.
*/
inline void MayFireEvent(nsAString* aInsertedText, nsAString* aRemovedText,
PRUint32 aOffset, ChangeType* aChange)
{
if (*aChange == eNoChange)
return;
if (*aChange == eRemoval || *aChange == eSubstitution) {
FireEvent(*aRemovedText, aOffset, PR_FALSE);
aRemovedText->Truncate();
}
if (*aChange == eInsertion || *aChange == eSubstitution) {
FireEvent(*aInsertedText, aOffset, PR_TRUE);
aInsertedText->Truncate();
}
*aChange = eNoChange;
}
/**
* Fire text change event.
*/
void FireEvent(const nsAString& aModText, PRUint32 aOffset, PRBool aType);
private:
nsDocAccessible* mDocument;
nsTextAccessible* mTextLeaf;
};
void
TextUpdater::Run(const nsAString& aNewText)
{
NS_ASSERTION(mTextLeaf, "No text leaf accessible?");
const nsString& oldText = mTextLeaf->Text();
PRUint32 oldLen = oldText.Length(), newLen = aNewText.Length();
PRUint32 minLen = oldLen < newLen ? oldLen : newLen;
// Skip coinciding begin substrings.
PRUint32 skipIdx = 0;
for (; skipIdx < minLen; skipIdx++) {
if (aNewText[skipIdx] != oldText[skipIdx])
break;
}
// No change, text append or removal to/from the end.
if (skipIdx == minLen) {
if (oldLen == newLen)
return;
// If text has been appended to the end, fire text inserted event.
if (oldLen < newLen) {
FireEvent(Substring(aNewText, oldLen), oldLen, PR_TRUE);
mTextLeaf->SetText(aNewText);
return;
}
// Text has been removed from the end, fire text removed event.
FireEvent(Substring(oldText, newLen), newLen, PR_FALSE);
mTextLeaf->SetText(aNewText);
return;
}
// Trim coinciding substrings from the end.
PRUint32 endIdx = minLen;
if (oldLen < newLen) {
PRUint32 delta = newLen - oldLen;
for (; endIdx > skipIdx; endIdx--) {
if (aNewText[endIdx + delta] != oldText[endIdx])
break;
}
} else {
PRUint32 delta = oldLen - newLen;
for (; endIdx > skipIdx; endIdx--) {
if (aNewText[endIdx] != oldText[endIdx + delta])
break;
}
}
PRUint32 oldEndIdx = oldLen - minLen + endIdx;
PRUint32 newEndIdx = newLen - minLen + endIdx;
// Find the difference starting from start character, we can skip initial and
// final coinciding characters since they don't affect on the Levenshtein
// distance.
const nsDependentSubstring& str1 =
Substring(oldText, skipIdx, oldEndIdx - skipIdx);
const nsDependentSubstring& str2 =
Substring(aNewText, skipIdx, newEndIdx - skipIdx);
// Compute the matrix.
PRUint32 len1 = str1.Length() + 1, len2 = str2.Length() + 1;
PRUint32** matrix = new PRUint32*[len1];
for (PRUint32 i = 0; i < len1; i++)
matrix[i] = new PRUint32[len2];
matrix[0][0] = 0;
for (PRUint32 i = 1; i < len1; i++)
matrix[i][0] = i;
for (PRUint32 j = 1; j < len2; j++)
matrix[0][j] = j;
for (PRUint32 i = 1; i < len1; i++) {
for (PRUint32 j = 1; j < len2; j++) {
if (str1[i - 1] != str2[j - 1]) {
PRUint32 left = matrix[i - 1][j];
PRUint32 up = matrix[i][j - 1];
PRUint32 upleft = matrix[i - 1][j - 1];
matrix[i][j] =
(left < up ? (upleft < left ? upleft : left) :
(upleft < up ? upleft : up)) + 1;
} else {
matrix[i][j] = matrix[i - 1][j - 1];
}
}
}
FindDiffNFireEvents(str1, str2, matrix, skipIdx);
for (PRUint32 i = 0; i < len1; i++)
delete[] matrix[i];
delete[] matrix;
mTextLeaf->SetText(aNewText);
}
void
TextUpdater::FindDiffNFireEvents(const nsDependentSubstring& aStr1,
const nsDependentSubstring& aStr2,
PRUint32** aMatrix,
PRUint32 aStartOffset)
{
// Find the difference.
ChangeType change = eNoChange;
nsAutoString insertedText;
nsAutoString removedText;
PRUint32 offset = 0;
PRInt32 i = aStr1.Length(), j = aStr2.Length();
while (i >= 0 && j >= 0) {
if (aMatrix[i][j] == 0) {
MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
return;
}
// move up left
if (i >= 1 && j >= 1) {
// no change
if (aStr1[i - 1] == aStr2[j - 1]) {
MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
i--; j--;
continue;
}
// substitution
if (aMatrix[i][j] == aMatrix[i - 1][j - 1] + 1) {
if (change != eSubstitution)
MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
offset = j - 1;
insertedText.Append(aStr1[i - 1]);
removedText.Append(aStr2[offset]);
change = eSubstitution;
i--; j--;
continue;
}
}
// move up, insertion
if (j >= 1 && aMatrix[i][j] == aMatrix[i][j - 1] + 1) {
if (change != eInsertion)
MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
offset = j - 1;
insertedText.Insert(aStr2[offset], 0);
change = eInsertion;
j--;
continue;
}
// move left, removal
if (i >= 1 && aMatrix[i][j] == aMatrix[i - 1][j] + 1) {
if (change != eRemoval) {
MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
offset = j;
}
removedText.Insert(aStr1[i - 1], 0);
change = eRemoval;
i--;
continue;
}
NS_NOTREACHED("Huh?");
return;
}
MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
}
void
TextUpdater::FireEvent(const nsAString& aModText, PRUint32 aOffset,
PRBool aIsInserted)
{
nsAccessible* parent = mTextLeaf->GetParent();
NS_ASSERTION(parent, "No parent for text leaf!");
nsHyperTextAccessible* hyperText = parent->AsHyperText();
NS_ASSERTION(hyperText, "Text leaf parnet is not hyper text!");
PRInt32 textLeafOffset = hyperText->GetChildOffset(mTextLeaf, PR_TRUE);
NS_ASSERTION(textLeafOffset != -1,
"Text leaf hasn't offset within hyper text!");
// Fire text change event.
nsRefPtr<AccEvent> textChangeEvent =
new AccTextChangeEvent(hyperText, textLeafOffset + aOffset, aModText,
aIsInserted);
mDocument->FireDelayedAccessibleEvent(textChangeEvent);
// Fire value change event.
if (hyperText->Role() == nsIAccessibleRole::ROLE_ENTRY) {
nsRefPtr<AccEvent> valueChangeEvent =
new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, hyperText,
eAutoDetect, AccEvent::eRemoveDupes);
mDocument->FireDelayedAccessibleEvent(valueChangeEvent);
}
}
PLDHashOperator
NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry,
void* aUserArg)
@ -927,9 +647,7 @@ NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry,
NS_ConvertUTF16toUTF8(text).get());
#endif
TextUpdater updater(document, textAcc->AsTextLeaf());
updater.Run(text);
TextUpdater::Run(document, textAcc->AsTextLeaf(), text);
return PL_DHASH_NEXT;
}

View File

@ -0,0 +1,263 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Alexander Surkov <surkov.alexander@gmail.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "TextUpdater.h"
#include "nsDocAccessible.h"
#include "nsTextAccessible.h"
void
TextUpdater::Run(nsDocAccessible* aDocument, nsTextAccessible* aTextLeaf,
const nsAString& aNewText)
{
NS_ASSERTION(aTextLeaf, "No text leaf accessible?");
const nsString& oldText = aTextLeaf->Text();
PRUint32 oldLen = oldText.Length(), newLen = aNewText.Length();
PRUint32 minLen = NS_MIN(oldLen, newLen);
// Skip coinciding begin substrings.
PRUint32 skipStart = 0;
for (; skipStart < minLen; skipStart++) {
if (aNewText[skipStart] != oldText[skipStart])
break;
}
// The text was changed. Do update.
if (skipStart != minLen || oldLen != newLen) {
TextUpdater updater(aDocument, aTextLeaf);
updater.DoUpdate(aNewText, oldText, skipStart);
}
}
void
TextUpdater::DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
PRUint32 aSkipStart)
{
nsAccessible* parent = mTextLeaf->GetParent();
NS_ASSERTION(parent, "No parent for text leaf!");
mHyperText = parent->AsHyperText();
if (!mHyperText) {
NS_ERROR("Text leaf parent is not hypertext!");
return;
}
// Get the text leaf accessible offset and invalidate cached offsets after it.
mTextOffset = mHyperText->GetChildOffset(mTextLeaf, PR_TRUE);
NS_ASSERTION(mTextOffset != -1,
"Text leaf hasn't offset within hyper text!");
PRUint32 oldLen = aOldText.Length(), newLen = aNewText.Length();
PRUint32 minLen = NS_MIN(oldLen, newLen);
// Text was appended or removed to/from the end.
if (aSkipStart == minLen) {
// If text has been appended to the end, fire text inserted event.
if (oldLen < newLen) {
UpdateTextNFireEvent(aNewText, Substring(aNewText, oldLen),
oldLen, PR_TRUE);
return;
}
// Text has been removed from the end, fire text removed event.
UpdateTextNFireEvent(aNewText, Substring(aOldText, newLen),
newLen, PR_FALSE);
return;
}
// Trim coinciding substrings from the end.
PRUint32 skipEnd = 0;
while (minLen - skipEnd > aSkipStart &&
aNewText[newLen - skipEnd - 1] == aOldText[oldLen - skipEnd - 1]) {
skipEnd++;
}
// Text was appended or removed to/from the start.
if (skipEnd == minLen) {
// If text has been appended to the start, fire text inserted event.
if (oldLen < newLen) {
UpdateTextNFireEvent(aNewText, Substring(aNewText, 0, newLen - skipEnd),
0, PR_TRUE);
return;
}
// Text has been removed from the start, fire text removed event.
UpdateTextNFireEvent(aNewText, Substring(aOldText, 0, oldLen - skipEnd),
0, PR_FALSE);
return;
}
// Find the difference between strings and fire events.
// Note: we can skip initial and final coinciding characters since they don't
// affect the Levenshtein distance.
PRInt32 strLen1 = oldLen - aSkipStart - skipEnd;
PRInt32 strLen2 = newLen - aSkipStart - skipEnd;
const nsAString& str1 = Substring(aOldText, aSkipStart, strLen1);
const nsAString& str2 = Substring(aNewText, aSkipStart, strLen2);
// Increase offset of the text leaf on skipped characters amount.
mTextOffset += aSkipStart;
// Compute the flat structured matrix need to compute the difference.
PRUint32 len1 = strLen1 + 1, len2 = strLen2 + 1;
PRUint32* entries = new PRUint32[len1 * len2];
for (PRUint32 colIdx = 0; colIdx < len1; colIdx++)
entries[colIdx] = colIdx;
PRUint32* row = entries;
for (PRUint32 rowIdx = 1; rowIdx < len2; rowIdx++) {
PRUint32* prevRow = row;
row += len1;
row[0] = rowIdx;
for (PRUint32 colIdx = 1; colIdx < len1; colIdx++) {
if (str1[colIdx - 1] != str2[rowIdx - 1]) {
PRUint32 left = row[colIdx - 1];
PRUint32 up = prevRow[colIdx];
PRUint32 upleft = prevRow[colIdx - 1];
row[colIdx] = NS_MIN(upleft, NS_MIN(left, up)) + 1;
} else {
row[colIdx] = prevRow[colIdx - 1];
}
}
}
// Compute events based on the difference.
nsTArray<nsRefPtr<AccEvent> > events;
ComputeTextChangeEvents(str1, str2, entries, events);
delete [] entries;
// Fire events.
for (PRInt32 idx = events.Length() - 1; idx >= 0; idx--)
mDocument->FireDelayedAccessibleEvent(events[idx]);
if (mHyperText->Role() == nsIAccessibleRole::ROLE_ENTRY) {
nsRefPtr<AccEvent> valueChangeEvent =
new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, mHyperText,
eAutoDetect, AccEvent::eRemoveDupes);
mDocument->FireDelayedAccessibleEvent(valueChangeEvent);
}
// Update the text.
mTextLeaf->SetText(aNewText);
}
void
TextUpdater::ComputeTextChangeEvents(const nsAString& aStr1,
const nsAString& aStr2,
PRUint32* aEntries,
nsTArray<nsRefPtr<AccEvent> >& aEvents)
{
PRInt32 colIdx = aStr1.Length(), rowIdx = aStr2.Length();
// Point at which strings last matched.
PRInt32 colEnd = colIdx;
PRInt32 rowEnd = rowIdx;
PRInt32 colLen = colEnd + 1;
PRUint32* row = aEntries + rowIdx * colLen;
PRInt32 dist = row[colIdx]; // current Levenshtein distance
while (rowIdx && colIdx) { // stop when we can't move diagonally
if (aStr1[colIdx - 1] == aStr2[rowIdx - 1]) { // match
if (rowIdx < rowEnd) { // deal with any pending insertion
FireInsertEvent(Substring(aStr2, rowIdx, rowEnd - rowIdx),
rowIdx, aEvents);
}
if (colIdx < colEnd) { // deal with any pending deletion
FireDeleteEvent(Substring(aStr1, colIdx, colEnd - colIdx),
rowIdx, aEvents);
}
colEnd = --colIdx; // reset the match point
rowEnd = --rowIdx;
row -= colLen;
continue;
}
--dist;
if (dist == row[colIdx - 1 - colLen]) { // substitution
--colIdx;
--rowIdx;
row -= colLen;
continue;
}
if (dist == row[colIdx - colLen]) { // insertion
--rowIdx;
row -= colLen;
continue;
}
if (dist == row[colIdx - 1]) { // deletion
--colIdx;
continue;
}
NS_NOTREACHED("huh?");
return;
}
if (rowEnd)
FireInsertEvent(Substring(aStr2, 0, rowEnd), 0, aEvents);
if (colEnd)
FireDeleteEvent(Substring(aStr1, 0, colEnd), 0, aEvents);
}
void
TextUpdater::UpdateTextNFireEvent(const nsAString& aNewText,
const nsAString& aChangeText,
PRUint32 aAddlOffset,
PRBool aIsInserted)
{
// Fire text change event.
nsRefPtr<AccEvent> textChangeEvent =
new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset, aChangeText,
aIsInserted);
mDocument->FireDelayedAccessibleEvent(textChangeEvent);
// Fire value change event.
if (mHyperText->Role() == nsIAccessibleRole::ROLE_ENTRY) {
nsRefPtr<AccEvent> valueChangeEvent =
new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, mHyperText,
eAutoDetect, AccEvent::eRemoveDupes);
mDocument->FireDelayedAccessibleEvent(valueChangeEvent);
}
// Update the text.
mTextLeaf->SetText(aNewText);
}

View File

@ -0,0 +1,124 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Alexander Surkov <surkov.alexander@gmail.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef TextUpdater_h_
#define TextUpdater_h_
#include "AccEvent.h"
#include "nsHyperTextAccessible.h"
/**
* Used to find a difference between old and new text and fire text change
* events.
*/
class TextUpdater
{
public:
/**
* Start text of the text leaf update.
*/
static void Run(nsDocAccessible* aDocument, nsTextAccessible* aTextLeaf,
const nsAString& aNewText);
private:
TextUpdater(nsDocAccessible* aDocument, nsTextAccessible* aTextLeaf) :
mDocument(aDocument), mTextLeaf(aTextLeaf), mHyperText(nsnull),
mTextOffset(-1) { }
~TextUpdater()
{ mDocument = nsnull; mTextLeaf = nsnull; mHyperText = nsnull; }
/**
* Update text of the text leaf accessible, fire text change and value change
* (if applicable) events for its container hypertext accessible.
*/
void DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
PRUint32 aSkipStart);
private:
TextUpdater();
TextUpdater(const TextUpdater&);
TextUpdater& operator=(const TextUpdater&);
/**
* Fire text change events based on difference between strings.
*/
void ComputeTextChangeEvents(const nsAString& aStr1,
const nsAString& aStr2,
PRUint32* aEntries,
nsTArray<nsRefPtr<AccEvent> >& aEvents);
/**
* Helper to create text change events for inserted text.
*/
inline void FireInsertEvent(const nsAString& aText, PRUint32 aAddlOffset,
nsTArray<nsRefPtr<AccEvent> >& aEvents)
{
nsRefPtr<AccEvent> event =
new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset,
aText, PR_TRUE);
aEvents.AppendElement(event);
}
/**
* Helper to create text change events for removed text.
*/
inline void FireDeleteEvent(const nsAString& aText, PRUint32 aAddlOffset,
nsTArray<nsRefPtr<AccEvent> >& aEvents)
{
nsRefPtr<AccEvent> event =
new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset,
aText, PR_FALSE);
aEvents.AppendElement(event);
}
/**
* Update the text and fire text change/value change events.
*/
void UpdateTextNFireEvent(const nsAString& aNewText,
const nsAString& aChangeText, PRUint32 aAddlOffset,
PRBool aIsInserted);
private:
nsDocAccessible* mDocument;
nsTextAccessible* mTextLeaf;
nsHyperTextAccessible* mHyperText;
PRInt32 mTextOffset;
};
#endif

View File

@ -331,7 +331,7 @@ function eventQueue(aEventType)
var idx = 0;
for (; idx < this.mEventSeq.length; idx++) {
if (!this.isEventUnexpected(idx) && (invoker.wasCaught[idx] == true) &&
this.compareEvents(idx, aEvent)) {
this.isAlreadyCaught(idx, aEvent)) {
var msg = "Doubled event { event type: " +
this.getEventTypeAsString(idx) + ", target: " +
@ -543,6 +543,15 @@ function eventQueue(aEventType)
return target1 == target2;
}
this.isAlreadyCaught = function eventQueue_isAlreadyCaught(aIdx, aEvent)
{
// We don't have stored info about handled event other than its type and
// target, thus we should filter text change events since they may occur
// on the same element because of complex changes.
return this.compareEvents(aIdx, aEvent) &&
!(aEvent instanceof nsIAccessibleTextChangeEvent);
}
this.checkEvent = function eventQueue_checkEvent(aIdx, aEvent)
{
var eventItem = this.mEventSeq[aIdx];

View File

@ -71,6 +71,7 @@ _TEST_FILES =\
test_scroll.xul \
test_selection.html \
test_statechange.html \
test_text_alg.html \
test_text.html \
test_textattrchange.html \
test_tree.xul \

View File

@ -0,0 +1,205 @@
<html>
<head>
<title>Accessible text update algorithm testing</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript">
////////////////////////////////////////////////////////////////////////////
// Invokers
const kRemoval = 0;
const kInsertion = 1;
function changeText(aContainerID, aValue, aEventList)
{
this.containerNode = getNode(aContainerID);
this.textNode = this.containerNode.firstChild;
this.textData = this.textNode.data;
this.eventSeq = [ ];
for (var i = 0; i < aEventList.length; i++) {
var isInserted = aEventList[i][0];
var str = aEventList[i][1];
var offset = aEventList[i][2];
var checker = new textChangeChecker(this.containerNode, offset,
offset + str.length, str,
isInserted);
this.eventSeq.push(checker);
}
this.invoke = function changeText_invoke()
{
this.textNode.data = aValue;
}
this.getID = function changeText_getID()
{
return "change text '" + this.textData + "' -> " + this.textNode.data +
"for " + prettyName(this.containerNode);
}
}
////////////////////////////////////////////////////////////////////////////
// Do tests
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
var gQueue = null;
function doTests()
{
gQueue = new eventQueue();
//////////////////////////////////////////////////////////////////////////
// wqrema -> tqb: substitution coalesced with removal
var events = [
[ kRemoval, "w", 0 ], // wqrema -> qrema
[ kInsertion, "t", 0], // qrema -> tqrema
[ kRemoval, "rema", 2 ], // tqrema -> tq
[ kInsertion, "b", 2] // tq -> tqb
];
gQueue.push(new changeText("p1", "tqb", events));
//////////////////////////////////////////////////////////////////////////
// b -> insa: substitution coalesced with insertion (complex substitution)
events = [
[ kRemoval, "b", 0 ], // b ->
[ kInsertion, "insa", 0] // -> insa
];
gQueue.push(new changeText("p2", "insa", events));
//////////////////////////////////////////////////////////////////////////
// abc -> def: coalesced substitutions
events = [
[ kRemoval, "abc", 0 ], // abc ->
[ kInsertion, "def", 0] // -> def
];
gQueue.push(new changeText("p3", "def", events));
//////////////////////////////////////////////////////////////////////////
// abcabc -> abcDEFabc: coalesced insertions
events = [
[ kInsertion, "DEF", 3] // abcabc -> abcDEFabc
];
gQueue.push(new changeText("p4", "abcDEFabc", events));
//////////////////////////////////////////////////////////////////////////
// abc -> defabc: insertion into begin
events = [
[ kInsertion, "def", 0] // abc -> defabc
];
gQueue.push(new changeText("p5", "defabc", events));
//////////////////////////////////////////////////////////////////////////
// abc -> abcdef: insertion into end
events = [
[ kInsertion, "def", 3] // abc -> abcdef
];
gQueue.push(new changeText("p6", "abcdef", events));
//////////////////////////////////////////////////////////////////////////
// defabc -> abc: removal from begin
events = [
[ kRemoval, "def", 0] // defabc -> abc
];
gQueue.push(new changeText("p7", "abc", events));
//////////////////////////////////////////////////////////////////////////
// abcdef -> abc: removal from the end
events = [
[ kRemoval, "def", 3] // abcdef -> abc
];
gQueue.push(new changeText("p8", "abc", events));
//////////////////////////////////////////////////////////////////////////
// abcDEFabc -> abcabc: coalesced removals
events = [
[ kRemoval, "DEF", 3] // abcDEFabc -> abcabc
];
gQueue.push(new changeText("p9", "abcabc", events));
//////////////////////////////////////////////////////////////////////////
// !abcdef@ -> @axbcef!: insertion, deletion and substitutions
events = [
[ kRemoval, "!", 0 ], // !abcdef@ -> abcdef@
[ kInsertion, "@", 0], // abcdef@ -> @abcdef@
[ kInsertion, "x", 2 ], // @abcdef@ -> @axbcdef@
[ kRemoval, "d", 5], // @axbcdef@ -> @axbcef@
[ kRemoval, "@", 7 ], // @axbcef@ -> @axbcef
[ kInsertion, "!", 7 ], // @axbcef -> @axbcef!
];
gQueue.push(new changeText("p10", "@axbcef!", events));
//////////////////////////////////////////////////////////////////////////
// meilenstein -> levenshtein: insertion, complex and simple substitutions
events = [
[ kRemoval, "m", 0 ], // meilenstein -> eilenstein
[ kInsertion, "l", 0], // eilenstein -> leilenstein
[ kRemoval, "il", 2 ], // leilenstein -> leenstein
[ kInsertion, "v", 2], // leenstein -> levenstein
[ kInsertion, "h", 6 ], // levenstein -> levenshtein
];
gQueue.push(new changeText("p11", "levenshtein", events));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660"
title="Cache rendered text on a11y side">
Mozilla Bug 626660
</a>
<br>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="eventdump"></div>
<p id="p1">wqrema</p>
<p id="p2">b</p>
<p id="p3">abc</p>
<p id="p4">abcabc</p>
<p id="p5">abc</p>
<p id="p6">abc</p>
<p id="p7">defabc</p>
<p id="p8">abcdef</p>
<p id="p9">abcDEFabc</p>
<p id="p10">!abcdef@</p>
<p id="p11">meilenstein</p>
</body>
</html>