--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -858,21 +858,21 @@ pref("accessibility.typeaheadfind.timeou
pref("accessibility.typeaheadfind.enabletimeout", true);
pref("accessibility.typeaheadfind.soundURL", "beep");
pref("accessibility.typeaheadfind.enablesound", true);
#ifdef XP_MACOSX
pref("accessibility.typeaheadfind.prefillwithselection", false);
#else
pref("accessibility.typeaheadfind.prefillwithselection", true);
#endif
-pref("accessibility.typeaheadfind.matchesCountTimeout", 100);
pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
pref("findbar.highlightAll", false);
pref("findbar.modalHighlight", false);
pref("findbar.entireword", false);
+pref("findbar.iteratorTimeout", 100);
// use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
pref("gfx.use_text_smoothing_setting", false);
// Number of characters to consider emphasizing for rich autocomplete results
pref("toolkit.autocomplete.richBoundaryCutoff", 200);
// Variable controlling logging for osfile.
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -369,18 +369,16 @@
this._foundURL = null;
let prefsvc = this._prefsvc;
this._quickFindTimeoutLength =
prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
this._flashFindBar =
prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
- this._matchesCountTimeoutLength =
- prefsvc.getIntPref("accessibility.typeaheadfind.matchesCountTimeout");
this._matchesCountLimit =
prefsvc.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
this._useModalHighlight = prefsvc.getBoolPref("findbar.modalHighlight");
prefsvc.addObserver("accessibility.typeaheadfind",
this._observer, false);
prefsvc.addObserver("accessibility.typeaheadfind.linksonly",
this._observer, false);
@@ -451,28 +449,20 @@
if (this._flashFindBarTimeout) {
clearInterval(this._flashFindBarTimeout);
this._flashFindBarTimeout = null;
}
if (this._quickFindTimeout) {
clearTimeout(this._quickFindTimeout);
this._quickFindTimeout = null;
}
- if (this._highlightTimeout) {
- clearTimeout(this._highlightTimeout);
- this._highlightTimeout = null;
- }
if (this._findResetTimeout) {
clearTimeout(this._findResetTimeout);
this._findResetTimeout = null;
}
- if (this._updateMatchesCountTimeout) {
- clearTimeout(this._updateMatchesCountTimeout);
- this._updateMatchesCountTimeout = null;
- }
]]></body>
</method>
<method name="_setFindCloseTimeout">
<body><![CDATA[
if (this._quickFindTimeout)
clearTimeout(this._quickFindTimeout);
@@ -507,24 +497,18 @@
- @param aRes
- the result of the find operation
-->
<method name="_updateMatchesCount">
<body><![CDATA[
if (this._matchesCountLimit == 0 || !this._dispatchFindEvent("matchescount"))
return;
- if (this._updateMatchesCountTimeout) {
- window.clearTimeout(this._updateMatchesCountTimeout);
- }
- this._updateMatchesCountTimeout =
- window.setTimeout(() => {
- this.browser.finder.requestMatchesCount(this._findField.value, this._matchesCountLimit,
- this._findMode == this.FIND_LINKS);
- }, this._matchesCountTimeoutLength);
+ this.browser.finder.requestMatchesCount(this._findField.value,
+ this._matchesCountLimit, this._findMode == this.FIND_LINKS);
]]></body>
</method>
<!--
- Turns highlight on or off.
- @param aHighlight (boolean)
- Whether to turn the highlight on or off
- @param aFromPrefObserver (boolean)
@@ -580,30 +564,25 @@
this._prefsvc.setBoolPref("findbar.highlightAll", aHighlight);
}
this._highlightAll = aHighlight;
let checkbox = this.getElement("highlight");
checkbox.checked = this._highlightAll;
]]></body>
</method>
- <method name="_setHighlightTimeout">
+ <method name="_maybeHighlightAll">
<body><![CDATA[
- if (this._highlightTimeout)
- clearTimeout(this._highlightTimeout);
-
let word = this._findField.value;
// Bug 429723. Don't attempt to highlight ""
if (!this._highlightAll || !word)
return;
- this._highlightTimeout = setTimeout(() => {
- this.browser.finder.highlight(true, word,
- this._findMode == this.FIND_LINKS);
- }, 500);
+ this.browser.finder.highlight(true, word,
+ this._findMode == this.FIND_LINKS);
]]></body>
</method>
<!--
- Updates the case-sensitivity mode of the findbar and its UI.
- @param [optional] aString
- The string for which case sensitivity might be turned on.
- This only used when case-sensitivity is in auto mode,
@@ -644,18 +623,17 @@
-->
<method name="_setCaseSensitivity">
<parameter name="aCaseSensitivity"/>
<body><![CDATA[
this._typeAheadCaseSensitive = aCaseSensitivity;
this._updateCaseSensitivity();
this._findFailedString = null;
this._find();
- if (this.getElement("highlight").checked)
- this._setHighlightTimeout();
+ this._maybeHighlightAll();
this._dispatchFindEvent("casesensitivitychange");
]]></body>
</method>
<!--
- Updates the entire-word mode of the findbar and its UI.
-->
@@ -691,17 +669,17 @@
<body><![CDATA[
let prefsvc =
Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
// Just set the pref; our observer will change the find bar behavior.
prefsvc.setBoolPref("findbar.entireword", aEntireWord);
- this._setHighlightTimeout();
+ this._maybeHighlightAll();
]]></body>
</method>
<field name="_strBundle">null</field>
<property name="strBundle">
<getter><![CDATA[
if (!this._strBundle) {
this._strBundle =
@@ -1037,17 +1015,17 @@
// Getting here means the user commanded a find op. Make sure any
// initial prefilling is ignored if it hasn't happened yet.
if (this._startFindDeferred) {
this._startFindDeferred.resolve();
this._startFindDeferred = null;
}
this._enableFindButtons(val);
- this._setHighlightTimeout();
+ this._maybeHighlightAll();
this._updateCaseSensitivity(val);
this._updateEntireWord();
this.browser.finder.fastFind(val, this._findMode == this.FIND_LINKS,
this._findMode != this.FIND_NORMAL);
}
--- a/toolkit/modules/Finder.jsm
+++ b/toolkit/modules/Finder.jsm
@@ -221,28 +221,16 @@ Finder.prototype = {
this.clipboardSearchString = searchString;
return searchString;
},
highlight: Task.async(function* (aHighlight, aWord, aLinksOnly) {
let found = yield this.highlighter.highlight(aHighlight, aWord, null, aLinksOnly);
this.highlighter.notifyFinished(aHighlight);
- if (aHighlight) {
- let result = found ? Ci.nsITypeAheadFind.FIND_FOUND
- : Ci.nsITypeAheadFind.FIND_NOTFOUND;
- this._notify({
- searchString: aWord,
- result,
- findBackwards: false,
- findAgain: false,
- drawOutline: false,
- storeResult: false
- });
- }
}),
getInitialSelection: function() {
this._getWindow().setTimeout(() => {
let initialSelection = this.getActiveSelectionText();
for (let l of this._listeners) {
try {
l.onCurrentSelection(initialSelection, true);
@@ -385,18 +373,16 @@ Finder.prototype = {
break;
case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
controller.scrollLine(true);
break;
}
},
_notifyMatchesCount: function(result = this._currentMatchesCountResult) {
- if (!result)
- return;
// The `_currentFound` property is only used for internal bookkeeping.
delete result._currentFound;
if (result.total == this._currentMatchLimit)
result.total = -1;
for (let l of this._listeners) {
try {
l.onMatchesCountResult(result);
@@ -415,32 +401,32 @@ Finder.prototype = {
});
return;
}
let window = this._getWindow();
this._currentFoundRange = this._fastFind.getFoundRange();
this._currentMatchLimit = aMatchLimit;
- this._currentMatchesCountResult = {
- total: 0,
- current: 0,
- _currentFound: false
- };
-
this.iterator.start({
caseSensitive: this._fastFind.caseSensitive,
entireWord: this._fastFind.entireWord,
finder: this,
limit: aMatchLimit,
linksOnly: aLinksOnly,
listener: this,
useCache: true,
word: aWord
- }).then(this._notifyMatchesCount.bind(this));
+ }).then(() => {
+ // Without a valid result, there's nothing to notify about. This happens
+ // when the iterator was started before and won the race.
+ if (!this._currentMatchesCountResult || !this._currentMatchesCountResult.total)
+ return;
+ this._notifyMatchesCount();
+ });
},
// FinderIterator listener implementation
onIteratorRangeFound(range) {
let result = this._currentMatchesCountResult;
if (!result)
return;
@@ -455,19 +441,25 @@ Finder.prototype = {
range.endOffset == this._currentFoundRange.endOffset);
}
},
onIteratorReset() {},
onIteratorRestart({ word, linksOnly }) {
this.requestMatchesCount(word, this._currentMatchLimit, linksOnly);
- },
+ },
- onIteratorStart() {},
+ onIteratorStart() {
+ this._currentMatchesCountResult = {
+ total: 0,
+ current: 0,
+ _currentFound: false
+ };
+ },
_getWindow: function () {
return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
},
/**
* Get the bounding selection rect in CSS px relative to the origin of the
* top-level content document.
--- a/toolkit/modules/FinderHighlighter.jsm
+++ b/toolkit/modules/FinderHighlighter.jsm
@@ -14,17 +14,17 @@ Cu.import("resource://gre/modules/XPCOMU
XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
XPCOMUtils.defineLazyGetter(this, "kDebug", () => {
const kDebugPref = "findbar.modalHighlight.debug";
return Services.prefs.getPrefType(kDebugPref) && Services.prefs.getBoolPref(kDebugPref);
});
const kContentChangeThresholdPx = 5;
-const kModalHighlightRepaintFreqMs = 10;
+const kModalHighlightRepaintFreqMs = 100;
const kHighlightAllPref = "findbar.highlightAll";
const kModalHighlightPref = "findbar.modalHighlight";
const kFontPropsCSS = ["color", "font-family", "font-kerning", "font-size",
"font-size-adjust", "font-stretch", "font-variant", "font-weight", "line-height",
"letter-spacing", "text-emphasis", "text-orientation", "text-transform", "word-spacing"];
const kFontPropsCamelCase = kFontPropsCSS.map(prop => {
let parts = prop.split("-");
return parts.shift() + parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -214,16 +214,17 @@ FinderHighlighter.prototype = {
if (!controller || !doc || !doc.documentElement) {
// Without the selection controller,
// we are unable to (un)highlight any matches
return this._found;
}
if (highlight) {
let params = {
+ allowDistance: 1,
caseSensitive: this.finder._fastFind.caseSensitive,
entireWord: this.finder._fastFind.entireWord,
linksOnly, word,
finder: this.finder,
listener: this,
useCache: true
};
if (this.iterator._areParamsEqual(params, this._lastIteratorParams))
@@ -468,22 +469,24 @@ FinderHighlighter.prototype = {
* everything when the user starts to find in page again.
*/
onLocationChange() {
this.clear();
if (!this._modalHighlightOutline)
return;
- if (kDebug)
+ if (kDebug) {
this._modalHighlightOutline.remove();
- try {
- this.finder._getWindow().document
- .removeAnonymousContent(this._modalHighlightOutline);
- } catch (ex) {}
+ } else {
+ try {
+ this.finder._getWindow().document
+ .removeAnonymousContent(this._modalHighlightOutline);
+ } catch (ex) {}
+ }
this._modalHighlightOutline = null;
},
/**
* When `kModalHighlightPref` pref changed during a session, this callback is
* invoked. When modal highlighting is turned off, we hide the CanvasFrame
* contents.
@@ -791,26 +794,29 @@ FinderHighlighter.prototype = {
},
/**
* Safely remove the mask AnoymousContent node from the CanvasFrame.
*
* @param {nsIDOMWindow} window
*/
_removeHighlightAllMask(window) {
- if (this._modalHighlightAllMask) {
- // If the current window isn't the one the content was inserted into, this
- // will fail, but that's fine.
- if (kDebug)
- this._modalHighlightAllMask.remove();
+ if (!this._modalHighlightAllMask)
+ return;
+
+ // If the current window isn't the one the content was inserted into, this
+ // will fail, but that's fine.
+ if (kDebug) {
+ this._modalHighlightAllMask.remove();
+ } else {
try {
window.document.removeAnonymousContent(this._modalHighlightAllMask);
} catch (ex) {}
- this._modalHighlightAllMask = null;
}
+ this._modalHighlightAllMask = null;
},
/**
* Doing a full repaint each time a range is delivered by the highlight iterator
* is way too costly, thus we pipe the frequency down to every
* `kModalHighlightRepaintFreqMs` milliseconds.
*
* @param {nsIDOMWindow} window
--- a/toolkit/modules/FinderIterator.jsm
+++ b/toolkit/modules/FinderIterator.jsm
@@ -3,31 +3,37 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["FinderIterator"];
const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
+Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+const kDebug = false;
const kIterationSizeMax = 100;
+const kTimeoutPref = "findbar.iteratorTimeout";
/**
* FinderIterator singleton. See the documentation for the `start()` method to
* learn more.
*/
this.FinderIterator = {
_currentParams: null,
_listeners: new Map(),
_catchingUp: new Set(),
_previousParams: null,
_previousRanges: [],
_spawnId: 0,
+ _timeout: Services.prefs.getIntPref(kTimeoutPref),
+ _timer: null,
ranges: [],
running: false,
// Expose `kIterationSizeMax` to the outside world for unit tests to use.
get kIterationSizeMax() { return kIterationSizeMax },
get params() {
if (!this._currentParams && !this._previousParams)
@@ -45,40 +51,45 @@ this.FinderIterator = {
* Upon re-entrance after a break, we check if `stop()` was called during the
* break and if so, we stop iterating.
* Results are also passed to the `listener.onIteratorRangeFound` callback
* method, along with a flag that specifies if the result comes from the cache
* or is fresh. The callback also adheres to the `limit` flag.
* The returned promise is resolved when 1) the limit is reached, 2) when all
* the ranges have been found or 3) when `stop()` is called whilst iterating.
*
- * @param {Boolean} options.caseSensitive Whether to search in case sensitive
- * mode
- * @param {Boolean} options.entireWord Whether to search in entire-word mode
- * @param {Finder} options.finder Currently active Finder instance
- * @param {Number} [options.limit] Limit the amount of results to be
- * passed back. Optional, defaults to no
- * limit.
- * @param {Boolean} [options.linksOnly] Only yield ranges that are inside a
- * hyperlink (used by QuickFind).
- * Optional, defaults to `false`.
- * @param {Object} options.listener Listener object that implements the
- * following callback functions:
- * - onIteratorRangeFound({nsIDOMRange} range);
- * - onIteratorReset();
- * - onIteratorRestart({Object} iterParams);
- * - onIteratorStart({Object} iterParams);
- * @param {Boolean} [options.useCache] Whether to allow results already
- * present in the cache or demand fresh.
- * Optional, defaults to `false`.
- * @param {String} options.word Word to search for
+ * @param {Number} [options.allowDistance] Allowed edit distance between the
+ * current word and `options.word`
+ * when the iterator is already running
+ * @param {Boolean} options.caseSensitive Whether to search in case sensitive
+ * mode
+ * @param {Boolean} options.entireWord Whether to search in entire-word mode
+ * @param {Finder} options.finder Currently active Finder instance
+ * @param {Number} [options.limit] Limit the amount of results to be
+ * passed back. Optional, defaults to no
+ * limit.
+ * @param {Boolean} [options.linksOnly] Only yield ranges that are inside a
+ * hyperlink (used by QuickFind).
+ * Optional, defaults to `false`.
+ * @param {Object} options.listener Listener object that implements the
+ * following callback functions:
+ * - onIteratorRangeFound({nsIDOMRange} range);
+ * - onIteratorReset();
+ * - onIteratorRestart({Object} iterParams);
+ * - onIteratorStart({Object} iterParams);
+ * @param {Boolean} [options.useCache] Whether to allow results already
+ * present in the cache or demand fresh.
+ * Optional, defaults to `false`.
+ * @param {String} options.word Word to search for
* @return {Promise}
*/
- start({ caseSensitive, entireWord, finder, limit, linksOnly, listener, useCache, word }) {
+ start({ allowDistance, caseSensitive, entireWord, finder, limit, linksOnly, listener, useCache, word }) {
// Take care of default values for non-required options.
+ if (typeof allowDistance != "number")
+ allowDistance = 0;
if (typeof limit != "number")
limit = -1;
if (typeof linksOnly != "boolean")
linksOnly = false;
if (typeof useCache != "boolean")
useCache = false;
// Validate the options.
@@ -111,19 +122,26 @@ this.FinderIterator = {
// If we're not running anymore and we're requesting the previous result, use it.
if (!this.running && this._previousResultAvailable(iterParams)) {
this._yieldPreviousResult(listener, window);
return promise;
}
if (this.running) {
// Double-check if we're not running the iterator with a different set of
- // parameters, otherwise throw an error with the most common reason.
- if (!this._areParamsEqual(this._currentParams, iterParams))
- throw new Error(`We're currently iterating over '${this._currentParams.word}', not '${word}'`);
+ // parameters, otherwise report an error with the most common reason.
+ if (!this._areParamsEqual(this._currentParams, iterParams, allowDistance)) {
+ if (kDebug) {
+ Cu.reportError(`We're currently iterating over '${this._currentParams.word}', not '${word}'\n` +
+ new Error().stack);
+ }
+ this._listeners.delete(listener);
+ resolver();
+ return promise;
+ }
// if we're still running, yield the set we have built up this far.
this._yieldIntermediateResult(listener, window);
return promise;
}
// Start!
@@ -140,16 +158,21 @@ this.FinderIterator = {
*
* @param {Boolean} [cachePrevious] Whether to save the result for later.
* Optional.
*/
stop(cachePrevious = false) {
if (!this.running)
return;
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = null;
+ }
+
if (cachePrevious) {
this._previousRanges = [].concat(this.ranges);
this._previousParams = Object.assign({}, this._currentParams);
} else {
this._previousRanges = [];
this._previousParams = null;
}
@@ -185,16 +208,21 @@ this.FinderIterator = {
/**
* Reset the internal state of the iterator. Typically this would be called
* when the docShell is not active anymore, which makes the current and cached
* previous result invalid.
* If the iterator is running, it will be stopped as soon as possible.
*/
reset() {
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = null;
+ }
+
this._catchingUp.clear();
this._currentParams = this._previousParams = null;
this._previousRanges = [];
this.ranges = [];
this.running = false;
this._notifyListeners("reset");
for (let [, { onEnd }] of this._listeners)
@@ -261,26 +289,29 @@ this.FinderIterator = {
return !!(useCache &&
this._areParamsEqual(this._previousParams, { caseSensitive, entireWord, linksOnly, word }) &&
this._previousRanges.length);
},
/**
* Internal; compare if two sets of iterator parameters are equivalent.
*
- * @param {Object} paramSet1 First set of params (left hand side)
- * @param {Object} paramSet2 Second set of params (right hand side)
+ * @param {Object} paramSet1 First set of params (left hand side)
+ * @param {Object} paramSet2 Second set of params (right hand side)
+ * @param {Number} [allowDistance] Allowed edit distance between the two words.
+ * Optional, defaults to '0', which means 'no
+ * distance'.
* @return {Boolean}
*/
- _areParamsEqual(paramSet1, paramSet2) {
+ _areParamsEqual(paramSet1, paramSet2, allowDistance = 0) {
return (!!paramSet1 && !!paramSet2 &&
paramSet1.caseSensitive === paramSet2.caseSensitive &&
paramSet1.entireWord === paramSet2.entireWord &&
paramSet1.linksOnly === paramSet2.linksOnly &&
- paramSet1.word == paramSet2.word);
+ this._distance(paramSet1.word, paramSet2.word) <= allowDistance);
},
/**
* Internal; iterate over a predefined set of ranges that have been collected
* before.
* Also here, we make sure to pause every `kIterationSizeMax` iterations to
* make sure we don't block the host process too long. In the case of a break
* like this, we yield `undefined`, instead of a range.
@@ -379,16 +410,27 @@ this.FinderIterator = {
* @param {Finder} finder Currently active Finder instance
* @param {nsIDOMWindow} window The window to search in
* @param {Number} spawnId Since `stop()` is synchronous and this method
* is not, this identifier is used to learn if
* it's supposed to still continue after a pause.
* @yield {nsIDOMRange}
*/
_findAllRanges: Task.async(function* (finder, window, spawnId) {
+ if (this._timeout) {
+ if (this._timer)
+ clearTimeout(this._timer);
+ yield new Promise(resolve => this._timer = setTimeout(resolve, this._timeout));
+ this._timer = null;
+ // During the timeout, we could have gotten the signal to stop iterating.
+ // Make sure we do here.
+ if (!this.running || spawnId !== this._spawnId)
+ return;
+ }
+
this._notifyListeners("start", this.params);
// First we collect all frames we need to search through, whilst making sure
// that the parent window gets dibs.
let frames = [window].concat(this._collectFrames(window, finder));
let { linksOnly, word } = this._currentParams;
let iterCount = 0;
for (let frame of frames) {
@@ -564,10 +606,70 @@ this.FinderIterator = {
isInsideLink = (node.getAttributeNS(XLink_NS, "type") == "simple");
break;
}
node = node.parentNode;
} while (node);
return isInsideLink;
+ },
+
+ /**
+ * Calculate the Levenshtein distance between two words.
+ * The implementation of this method was heavily inspired by
+ * http://locutus.io/php/strings/levenshtein/index.html
+ * License: MIT.
+ *
+ * @param {String} word1 Word to compare against
+ * @param {String} word2 Word that may be different
+ * @param {Number} costIns The cost to insert a character
+ * @param {Number} costRep The cost to replace a character
+ * @param {Number} costDel The cost to delete a character
+ * @return {Number}
+ */
+ _distance(word1 = "", word2 = "", costIns = 1, costRep = 1, costDel = 1) {
+ if (word1 === word2)
+ return 0;
+
+ let l1 = word1.length;
+ let l2 = word2.length;
+ if (!l1)
+ return l2 * costIns;
+ if (!l2)
+ return l1 * costDel;
+
+ let p1 = new Array(l2 + 1)
+ let p2 = new Array(l2 + 1)
+
+ let i1, i2, c0, c1, c2, tmp;
+
+ for (i2 = 0; i2 <= l2; i2++)
+ p1[i2] = i2 * costIns;
+
+ for (i1 = 0; i1 < l1; i1++) {
+ p2[0] = p1[0] + costDel;
+
+ for (i2 = 0; i2 < l2; i2++) {
+ c0 = p1[i2] + ((word1[i1] === word2[i2]) ? 0 : costRep);
+ c1 = p1[i2 + 1] + costDel;
+
+ if (c1 < c0)
+ c0 = c1;
+
+ c2 = p2[i2] + costIns;
+
+ if (c2 < c0)
+ c0 = c2;
+
+ p2[i2 + 1] = c0;
+ }
+
+ tmp = p1;
+ p1 = p2;
+ p2 = tmp;
+ }
+
+ c0 = p1[l2];
+
+ return c0;
}
};
--- a/toolkit/modules/tests/xpcshell/test_FinderIterator.js
+++ b/toolkit/modules/tests/xpcshell/test_FinderIterator.js
@@ -136,17 +136,17 @@ add_task(function* test_stop() {
listener: { onIteratorRangeFound(range) { ++count; } },
word: findText
});
FinderIterator.stop();
yield whenDone;
- Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
+ Assert.equal(count, 0, "Number of ranges should be 0");
FinderIterator.reset();
});
add_task(function* test_reset() {
let findText = "tik";
let rangeCount = 142;
prepareIterator(findText, rangeCount);
@@ -156,29 +156,28 @@ add_task(function* test_reset() {
caseSensitive: false,
entireWord: false,
finder: gMockFinder,
listener: { onIteratorRangeFound(range) { ++count; } },
word: findText
});
Assert.ok(FinderIterator.running, "Yup, running we are");
- Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
- Assert.equal(FinderIterator.ranges.length, 100,
- "Number of ranges should match `kIterationSizeMax`");
+ Assert.equal(count, 0, "Number of ranges should match 0");
+ Assert.equal(FinderIterator.ranges.length, 0, "Number of ranges should match 0");
FinderIterator.reset();
Assert.ok(!FinderIterator.running, "Nope, running we are not");
Assert.equal(FinderIterator.ranges.length, 0, "No ranges after reset");
Assert.equal(FinderIterator._previousRanges.length, 0, "No ranges after reset");
yield whenDone;
- Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
+ Assert.equal(count, 0, "Number of ranges should match 0");
});
add_task(function* test_parallel_starts() {
let findText = "tak";
let rangeCount = 2143;
prepareIterator(findText, rangeCount);
// Start off the iterator.
@@ -187,17 +186,17 @@ add_task(function* test_parallel_starts(
caseSensitive: false,
entireWord: false,
finder: gMockFinder,
listener: { onIteratorRangeFound(range) { ++count; } },
word: findText
});
// Start again after a few milliseconds.
- yield new Promise(resolve => gMockWindow.setTimeout(resolve, 20));
+ yield new Promise(resolve => gMockWindow.setTimeout(resolve, 120));
Assert.ok(FinderIterator.running, "We ought to be running here");
let count2 = 0;
let whenDone2 = FinderIterator.start({
caseSensitive: false,
entireWord: false,
finder: gMockFinder,
listener: { onIteratorRangeFound(range) { ++count2; } },
@@ -214,9 +213,54 @@ add_task(function* test_parallel_starts(
yield whenDone2;
Assert.greater(count, FinderIterator.kIterationSizeMax, "At least one range should've been found");
Assert.less(count, rangeCount, "Not all ranges should've been found");
Assert.greater(count2, FinderIterator.kIterationSizeMax, "At least one range should've been found");
Assert.less(count2, rangeCount, "Not all ranges should've been found");
Assert.equal(count2, count, "The second start was later, but should have caught up");
+
+ FinderIterator.reset();
});
+
+add_task(function* test_allowDistance() {
+ let findText = "gup";
+ let rangeCount = 20;
+ prepareIterator(findText, rangeCount);
+
+ // Start off the iterator.
+ let count = 0;
+ let whenDone = FinderIterator.start({
+ caseSensitive: false,
+ entireWord: false,
+ finder: gMockFinder,
+ listener: { onIteratorRangeFound(range) { ++count; } },
+ word: findText
+ });
+
+ let count2 = 0;
+ let whenDone2 = FinderIterator.start({
+ caseSensitive: false,
+ entireWord: false,
+ finder: gMockFinder,
+ listener: { onIteratorRangeFound(range) { ++count2; } },
+ word: "gu"
+ });
+
+ let count3 = 0;
+ let whenDone3 = FinderIterator.start({
+ allowDistance: 1,
+ caseSensitive: false,
+ entireWord: false,
+ finder: gMockFinder,
+ listener: { onIteratorRangeFound(range) { ++count3; } },
+ word: "gu"
+ });
+
+ yield Promise.all([whenDone, whenDone2, whenDone3]);
+
+ Assert.equal(count, rangeCount, "The first iterator invocation should yield all results");
+ Assert.equal(count2, 0, "The second iterator invocation should yield _no_ results");
+ Assert.equal(count3, rangeCount, "The first iterator invocation should yield all results");
+
+ FinderIterator.reset();
+});