зеркало из https://github.com/mozilla/gecko-dev.git
Bug 429732 - Make Finder.jsm iterate over matches asynchronously in small batches so it does not block the UI thread. r=mikedeboer
This commit is contained in:
Родитель
eb47778dea
Коммит
63556e7c60
|
@ -392,24 +392,6 @@
|
|||
testClipboardSearchString(SEARCH_TEXT);
|
||||
}
|
||||
|
||||
// Perform an async function in serial on each of the list items.
|
||||
function asyncForEach(list, async, callback) {
|
||||
let i = 0;
|
||||
let len = list.length;
|
||||
|
||||
if (!len)
|
||||
return callback();
|
||||
|
||||
async(list[i], function handler() {
|
||||
i++;
|
||||
if (i < len) {
|
||||
async(list[i], handler, i);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}, i);
|
||||
}
|
||||
|
||||
function testFindCountUI(callback) {
|
||||
clearFocus();
|
||||
document.getElementById("cmd_find").doCommand();
|
||||
|
@ -444,46 +426,45 @@
|
|||
let timeout = gFindBar._matchesCountTimeoutLength + 20;
|
||||
|
||||
function assertMatches(aTest, aMatches) {
|
||||
window.opener.wrappedJSObject.SimpleTest.is(aTest.current, aMatches[1],
|
||||
window.opener.wrappedJSObject.SimpleTest.is(aMatches[1], aTest.current,
|
||||
"Currently highlighted match should be at " + aTest.current);
|
||||
window.opener.wrappedJSObject.SimpleTest.is(aTest.total, aMatches[2],
|
||||
window.opener.wrappedJSObject.SimpleTest.is(aMatches[2], aTest.total,
|
||||
"Total amount of matches should be " + aTest.total);
|
||||
}
|
||||
|
||||
function testString(aTest, aNext) {
|
||||
gFindBar.clear();
|
||||
enterStringIntoFindField(aTest.text);
|
||||
|
||||
setTimeout(function() {
|
||||
function* generatorTest() {
|
||||
for (let test of tests) {
|
||||
gFindBar.clear();
|
||||
yield;
|
||||
enterStringIntoFindField(test.text);
|
||||
yield;
|
||||
let matches = foundMatches.value.match(regex);
|
||||
if (!aTest.total) {
|
||||
if (!test.total) {
|
||||
ok(!matches, "No message should be shown when 0 matches are expected");
|
||||
aNext();
|
||||
} else {
|
||||
assertMatches(aTest, matches);
|
||||
let cycleTests = [];
|
||||
let cycles = aTest.total;
|
||||
while (--cycles) {
|
||||
aTest.current++;
|
||||
if (aTest.current > aTest.total)
|
||||
aTest.current = 1;
|
||||
cycleTests.push({
|
||||
current: aTest.current,
|
||||
total: aTest.total
|
||||
});
|
||||
}
|
||||
asyncForEach(cycleTests, function(aCycleTest, aNextCycle) {
|
||||
assertMatches(test, matches);
|
||||
for (let i = 1; i < test.total; i++) {
|
||||
gFindBar.onFindAgainCommand();
|
||||
setTimeout(function() {
|
||||
assertMatches(aCycleTest, foundMatches.value.match(regex));
|
||||
aNextCycle();
|
||||
}, timeout);
|
||||
}, aNext);
|
||||
yield;
|
||||
// test.current + 1, test.current + 2, ..., test.total, 1, ..., test.current
|
||||
let current = (test.current + i - 1) % test.total + 1;
|
||||
assertMatches({
|
||||
current: current,
|
||||
total: test.total
|
||||
}, foundMatches.value.match(regex));
|
||||
}
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
asyncForEach(tests, testString, callback);
|
||||
let test = generatorTest();
|
||||
let resultListener = {
|
||||
onMatchesCountResult: function() {
|
||||
test.next();
|
||||
}
|
||||
};
|
||||
gFindBar.browser.finder.addResultListener(resultListener);
|
||||
test.next();
|
||||
}
|
||||
|
||||
function testClipboardSearchString(aExpected) {
|
||||
|
|
|
@ -448,40 +448,24 @@
|
|||
]]></getter>
|
||||
</property>
|
||||
|
||||
<method name="_updateMatchesCountWorker">
|
||||
<parameter name="aRes"/>
|
||||
<body><![CDATA[
|
||||
let word = this._findField.value;
|
||||
if (aRes == this.nsITypeAheadFind.FIND_NOTFOUND || !word) {
|
||||
this._foundMatches.hidden = true;
|
||||
this._foundMatches.value = "";
|
||||
} else {
|
||||
let matchesCount = this.browser.finder.requestMatchesCount(
|
||||
word, this._matchesCountLimit, this._findMode == this.FIND_LINKS);
|
||||
window.clearTimeout(this._updateMatchesCountTimeout);
|
||||
this._updateMatchesCountTimeout = null;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
- Updates the search match count after each find operation on a new string.
|
||||
- @param aRes
|
||||
- the result of the find operation
|
||||
-->
|
||||
<method name="_updateMatchesCount">
|
||||
<parameter name="aRes"/>
|
||||
<body><![CDATA[
|
||||
if (this._matchesCountLimit == 0 || !this._dispatchFindEvent("matchescount"))
|
||||
return;
|
||||
|
||||
if (this._updateMatchesCountTimeout) {
|
||||
window.clearTimeout(this._updateMatchesCountTimeout);
|
||||
this._updateMatchesCountTimeout = null;
|
||||
}
|
||||
this._updateMatchesCountTimeout =
|
||||
window.setTimeout(() => this._updateMatchesCountWorker(aRes),
|
||||
this._matchesCountTimeoutLength);
|
||||
window.setTimeout(() => {
|
||||
this.browser.finder.requestMatchesCount(this._findField.value, this._matchesCountLimit,
|
||||
this._findMode == this.FIND_LINKS);
|
||||
}, this._matchesCountTimeoutLength);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Geometry.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "TextToSubURIService",
|
||||
"@mozilla.org/intl/texttosuburi;1",
|
||||
|
@ -22,6 +23,8 @@ XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
|
|||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
"nsIClipboardHelper");
|
||||
|
||||
const kHighlightIterationSizeMax = 100;
|
||||
|
||||
function Finder(docShell) {
|
||||
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
|
||||
this._fastFind.init(docShell);
|
||||
|
@ -127,6 +130,8 @@ Finder.prototype = {
|
|||
this._fastFind.caseSensitive = aSensitive;
|
||||
},
|
||||
|
||||
_lastFindResult: null,
|
||||
|
||||
/**
|
||||
* Used for normal search operations, highlights the first match.
|
||||
*
|
||||
|
@ -135,9 +140,9 @@ Finder.prototype = {
|
|||
* @param aDrawOutline Puts an outline around matched links.
|
||||
*/
|
||||
fastFind: function (aSearchString, aLinksOnly, aDrawOutline) {
|
||||
let result = this._fastFind.find(aSearchString, aLinksOnly);
|
||||
this._lastFindResult = this._fastFind.find(aSearchString, aLinksOnly);
|
||||
let searchString = this._fastFind.searchString;
|
||||
this._notify(searchString, result, false, aDrawOutline);
|
||||
this._notify(searchString, this._lastFindResult, false, aDrawOutline);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -150,9 +155,9 @@ Finder.prototype = {
|
|||
* @param aDrawOutline Puts an outline around matched links.
|
||||
*/
|
||||
findAgain: function (aFindBackwards, aLinksOnly, aDrawOutline) {
|
||||
let result = this._fastFind.findAgain(aFindBackwards, aLinksOnly);
|
||||
this._lastFindResult = this._fastFind.findAgain(aFindBackwards, aLinksOnly);
|
||||
let searchString = this._fastFind.searchString;
|
||||
this._notify(searchString, result, aFindBackwards, aDrawOutline);
|
||||
this._notify(searchString, this._lastFindResult, aFindBackwards, aDrawOutline);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -174,14 +179,18 @@ Finder.prototype = {
|
|||
return searchString;
|
||||
},
|
||||
|
||||
highlight: function (aHighlight, aWord) {
|
||||
let found = this._highlight(aHighlight, aWord, null);
|
||||
highlight: Task.async(function* (aHighlight, aWord) {
|
||||
if (this._abortHighlight) {
|
||||
this._abortHighlight();
|
||||
}
|
||||
|
||||
let found = yield this._highlight(aHighlight, aWord, null);
|
||||
if (aHighlight) {
|
||||
let result = found ? Ci.nsITypeAheadFind.FIND_FOUND
|
||||
: Ci.nsITypeAheadFind.FIND_NOTFOUND;
|
||||
this._notify(aWord, result, false, false, false);
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
enableSelection: function() {
|
||||
this._fastFind.setSelectionModeAndRepaint(Ci.nsISelectionController.SELECTION_ON);
|
||||
|
@ -262,7 +271,22 @@ Finder.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_notifyMatchesCount: function(result) {
|
||||
for (let l of this._listeners) {
|
||||
try {
|
||||
l.onMatchesCountResult(result);
|
||||
} catch (ex) {}
|
||||
}
|
||||
},
|
||||
|
||||
requestMatchesCount: function(aWord, aMatchLimit, aLinksOnly) {
|
||||
if (this._lastFindResult == Ci.nsITypeAheadFind.FIND_NOTFOUND ||
|
||||
this.searchString == "") {
|
||||
return this._notifyMatchesCount({
|
||||
total: 0,
|
||||
current: 0
|
||||
});
|
||||
}
|
||||
let window = this._getWindow();
|
||||
let result = this._countMatchesInWindow(aWord, aMatchLimit, aLinksOnly, window);
|
||||
|
||||
|
@ -279,11 +303,7 @@ Finder.prototype = {
|
|||
delete result._currentFound;
|
||||
delete result._framesToCount;
|
||||
|
||||
for (let l of this._listeners) {
|
||||
try {
|
||||
l.onMatchesCountResult(result);
|
||||
} catch (ex) {}
|
||||
}
|
||||
this._notifyMatchesCount(result);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -322,41 +342,37 @@ Finder.prototype = {
|
|||
|
||||
let foundRange = this._fastFind.getFoundRange();
|
||||
|
||||
this._findIterator(aWord, aWindow, aRange => {
|
||||
if (!aLinksOnly || this._rangeStartsInLink(aRange)) {
|
||||
for(let range of this._findIterator(aWord, aWindow)) {
|
||||
if (!aLinksOnly || this._rangeStartsInLink(range)) {
|
||||
++aStats.total;
|
||||
if (!aStats._currentFound) {
|
||||
++aStats.current;
|
||||
aStats._currentFound = (foundRange &&
|
||||
aRange.startContainer == foundRange.startContainer &&
|
||||
aRange.startOffset == foundRange.startOffset &&
|
||||
aRange.endContainer == foundRange.endContainer &&
|
||||
aRange.endOffset == foundRange.endOffset);
|
||||
range.startContainer == foundRange.startContainer &&
|
||||
range.startOffset == foundRange.startOffset &&
|
||||
range.endContainer == foundRange.endContainer &&
|
||||
range.endOffset == foundRange.endOffset);
|
||||
}
|
||||
}
|
||||
if (aStats.total == aMatchLimit) {
|
||||
aStats.total = -1;
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return aStats;
|
||||
},
|
||||
|
||||
/**
|
||||
* Basic wrapper around nsIFind that provides invoking a callback `aOnFind`
|
||||
* each time an occurence of `aWord` string is found.
|
||||
* Basic wrapper around nsIFind that provides a generator yielding
|
||||
* a range each time an occurence of `aWord` string is found.
|
||||
*
|
||||
* @param aWord
|
||||
* the word to search for.
|
||||
* @param aWindow
|
||||
* the window to search in.
|
||||
* @param aOnFind
|
||||
* the Function to invoke when a word is found. if Boolean `false` is
|
||||
* returned, the find operation will be stopped and the Function will
|
||||
* not be invoked again.
|
||||
*/
|
||||
_findIterator: function(aWord, aWindow, aOnFind) {
|
||||
_findIterator: function* (aWord, aWindow) {
|
||||
let doc = aWindow.document;
|
||||
let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
|
||||
doc.body : doc.documentElement;
|
||||
|
@ -381,13 +397,34 @@ Finder.prototype = {
|
|||
finder.caseSensitive = this._fastFind.caseSensitive;
|
||||
|
||||
while ((retRange = finder.Find(aWord, searchRange, startPt, endPt))) {
|
||||
if (aOnFind(retRange) === false)
|
||||
break;
|
||||
yield retRange;
|
||||
startPt = retRange.cloneRange();
|
||||
startPt.collapse(false);
|
||||
}
|
||||
},
|
||||
|
||||
_highlightIterator: Task.async(function* (aWord, aWindow, aOnFind) {
|
||||
let count = 0;
|
||||
for (let range of this._findIterator(aWord, aWindow)) {
|
||||
aOnFind(range);
|
||||
if (++count >= kHighlightIterationSizeMax) {
|
||||
count = 0;
|
||||
yield this._highlightSleep(0);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
_abortHighlight: null,
|
||||
_highlightSleep: function(delay) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._abortHighlight = () => {
|
||||
this._abortHighlight = null;
|
||||
reject();
|
||||
};
|
||||
this._getWindow().setTimeout(resolve, delay);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper method for `_countMatchesInWindow` that recursively collects all
|
||||
* visible (i)frames inside a window.
|
||||
|
@ -521,12 +558,12 @@ Finder.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_highlight: function (aHighlight, aWord, aWindow) {
|
||||
_highlight: Task.async(function* (aHighlight, aWord, aWindow) {
|
||||
let win = aWindow || this._getWindow();
|
||||
|
||||
let found = false;
|
||||
for (let i = 0; win.frames && i < win.frames.length; i++) {
|
||||
if (this._highlight(aHighlight, aWord, win.frames[i]))
|
||||
if (yield this._highlight(aHighlight, aWord, win.frames[i]))
|
||||
found = true;
|
||||
}
|
||||
|
||||
|
@ -539,7 +576,7 @@ Finder.prototype = {
|
|||
}
|
||||
|
||||
if (aHighlight) {
|
||||
this._findIterator(aWord, win, aRange => {
|
||||
yield this._highlightIterator(aWord, win, aRange => {
|
||||
this._highlightRange(aRange, controller);
|
||||
found = true;
|
||||
});
|
||||
|
@ -567,7 +604,7 @@ Finder.prototype = {
|
|||
}
|
||||
|
||||
return found;
|
||||
},
|
||||
}),
|
||||
|
||||
_highlightRange: function(aRange, aController) {
|
||||
let node = aRange.startContainer;
|
||||
|
|
Загрузка…
Ссылка в новой задаче