mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
Bug 257061: adding a counter of found matches to the find in page bar. r=Unfocused.
This commit is contained in:
parent
8f31d14de2
commit
3b54ae345d
@ -524,6 +524,8 @@ pref("accessibility.typeaheadfind.enabletimeout", true);
|
||||
pref("accessibility.typeaheadfind.soundURL", "beep");
|
||||
pref("accessibility.typeaheadfind.enablesound", true);
|
||||
pref("accessibility.typeaheadfind.prefillwithselection", true);
|
||||
pref("accessibility.typeaheadfind.matchesCountTimeout", 250);
|
||||
pref("accessibility.typeaheadfind.matchesCountLimit", 100);
|
||||
|
||||
// use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
|
||||
pref("gfx.use_text_smoothing_setting", false);
|
||||
|
@ -17,7 +17,7 @@ interface nsIDocShell;
|
||||
|
||||
/****************************** nsTypeAheadFind ******************************/
|
||||
|
||||
[scriptable, uuid(0749a445-19d3-4eb9-9d66-78eca8c6f604)]
|
||||
[scriptable, uuid(f4411c5b-761b-498c-8050-dcfc8311f69e)]
|
||||
interface nsITypeAheadFind : nsISupports
|
||||
{
|
||||
/****************************** Initializer ******************************/
|
||||
@ -37,6 +37,9 @@ interface nsITypeAheadFind : nsISupports
|
||||
/* Find another match in the page. */
|
||||
unsigned short findAgain(in boolean findBackwards, in boolean aLinksOnly);
|
||||
|
||||
/* Return the range of the most recent match. */
|
||||
nsIDOMRange getFoundRange();
|
||||
|
||||
|
||||
/**************************** Helper functions ***************************/
|
||||
|
||||
@ -53,6 +56,8 @@ interface nsITypeAheadFind : nsISupports
|
||||
* necessarily happen automatically. */
|
||||
void collapseSelection();
|
||||
|
||||
/* Check if a range is visible */
|
||||
boolean isRangeVisible(in nsIDOMRange aRange, in boolean aMustBeInViewPort);
|
||||
|
||||
/******************************* Attributes ******************************/
|
||||
|
||||
|
@ -66,7 +66,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTypeAheadFind)
|
||||
NS_IMPL_CYCLE_COLLECTION(nsTypeAheadFind, mFoundLink, mFoundEditable,
|
||||
mCurrentWindow, mStartFindRange, mSearchRange,
|
||||
mStartPointRange, mEndPointRange, mSoundInterface,
|
||||
mFind)
|
||||
mFind, mFoundRange)
|
||||
|
||||
static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
|
||||
|
||||
@ -176,6 +176,7 @@ nsTypeAheadFind::SetDocShell(nsIDocShell* aDocShell)
|
||||
|
||||
mFoundLink = nullptr;
|
||||
mFoundEditable = nullptr;
|
||||
mFoundRange = nullptr;
|
||||
mCurrentWindow = nullptr;
|
||||
|
||||
mSelectionController = nullptr;
|
||||
@ -275,6 +276,7 @@ nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly,
|
||||
*aResult = FIND_NOTFOUND;
|
||||
mFoundLink = nullptr;
|
||||
mFoundEditable = nullptr;
|
||||
mFoundRange = nullptr;
|
||||
mCurrentWindow = nullptr;
|
||||
nsCOMPtr<nsIPresShell> startingPresShell (GetPresShell());
|
||||
if (!startingPresShell) {
|
||||
@ -437,6 +439,8 @@ nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly,
|
||||
continue;
|
||||
}
|
||||
|
||||
mFoundRange = returnRange;
|
||||
|
||||
// ------ Success! -------
|
||||
// Hide old selection (new one may be on a different controller)
|
||||
if (selection) {
|
||||
@ -1091,6 +1095,44 @@ nsTypeAheadFind::GetSelection(nsIPresShell *aPresShell,
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTypeAheadFind::GetFoundRange(nsIDOMRange** aFoundRange)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aFoundRange);
|
||||
if (mFoundRange == nullptr) {
|
||||
*aFoundRange = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mFoundRange->CloneRange(aFoundRange);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTypeAheadFind::IsRangeVisible(nsIDOMRange *aRange,
|
||||
bool aMustBeInViewPort,
|
||||
bool *aResult)
|
||||
{
|
||||
// Jump through hoops to extract the docShell from the range.
|
||||
nsCOMPtr<nsIDOMNode> node;
|
||||
aRange->GetStartContainer(getter_AddRefs(node));
|
||||
nsCOMPtr<nsIDOMDocument> document;
|
||||
node->GetOwnerDocument(getter_AddRefs(document));
|
||||
nsCOMPtr<nsIDOMWindow> window;
|
||||
document->GetDefaultView(getter_AddRefs(window));
|
||||
nsCOMPtr<nsIWebNavigation> navNav (do_GetInterface(window));
|
||||
nsCOMPtr<nsIDocShell> docShell (do_GetInterface(navNav));
|
||||
|
||||
// Set up the arguments needed to check if a range is visible.
|
||||
nsCOMPtr<nsIPresShell> presShell (docShell->GetPresShell());
|
||||
nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
|
||||
nsCOMPtr<nsIDOMRange> startPointRange = new nsRange(presShell->GetDocument());
|
||||
*aResult = IsRangeVisible(presShell, presContext, aRange,
|
||||
aMustBeInViewPort, false,
|
||||
getter_AddRefs(startPointRange),
|
||||
nullptr);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
nsTypeAheadFind::IsRangeVisible(nsIPresShell *aPresShell,
|
||||
|
@ -80,6 +80,7 @@ protected:
|
||||
bool mCaretBrowsingOn;
|
||||
nsCOMPtr<nsIDOMElement> mFoundLink; // Most recent elem found, if a link
|
||||
nsCOMPtr<nsIDOMElement> mFoundEditable; // Most recent elem found, if editable
|
||||
nsCOMPtr<nsIDOMRange> mFoundRange; // Most recent range found
|
||||
nsCOMPtr<nsIDOMWindow> mCurrentWindow;
|
||||
// mLastFindLength is the character length of the last find string. It is used for
|
||||
// disabling the "not found" sound when using backspace or delete
|
||||
|
@ -101,8 +101,17 @@
|
||||
testDrop();
|
||||
testQuickFindLink();
|
||||
if (gHasFindClipboard)
|
||||
testStatusText();
|
||||
testQuickFindClose();
|
||||
testStatusText(afterStatusText);
|
||||
else
|
||||
afterStatusText();
|
||||
|
||||
function afterStatusText() {
|
||||
testFindCountUI(function() {
|
||||
gFindBar.close();
|
||||
ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
|
||||
testQuickFindClose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function testFindbarSelection() {
|
||||
@ -163,9 +172,10 @@
|
||||
setTimeout(_isClosedCallback, gFindBar._quickFindTimeoutLength + 100);
|
||||
}
|
||||
|
||||
function testStatusText() {
|
||||
function testStatusText(aCallback) {
|
||||
var _delayedCheckStatusText = function() {
|
||||
ok(gStatusText == SAMPLE_URL, "testStatusText: Failed to set status text of found link");
|
||||
aCallback();
|
||||
};
|
||||
setTimeout(_delayedCheckStatusText, 100);
|
||||
}
|
||||
@ -384,6 +394,100 @@
|
||||
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();
|
||||
|
||||
ok(!gFindBar.hidden, "testFindCountUI: failed to open findbar");
|
||||
ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
|
||||
"testFindCountUI: find field is not focused");
|
||||
|
||||
let matchCase = gFindBar.getElement("find-case-sensitive");
|
||||
if (matchCase.checked)
|
||||
matchCase.click();
|
||||
|
||||
let foundMatches = gFindBar._foundMatches;
|
||||
let tests = [{
|
||||
text: "t",
|
||||
current: 5,
|
||||
total: 10,
|
||||
}, {
|
||||
text: "te",
|
||||
current: 3,
|
||||
total: 5,
|
||||
}, {
|
||||
text: "tes",
|
||||
current: 1,
|
||||
total: 2,
|
||||
}, {
|
||||
text: "texxx",
|
||||
current: 0,
|
||||
total: 0
|
||||
}];
|
||||
let regex = /([\d]*)\sof\s([\d]*)/;
|
||||
let timeout = gFindBar._matchesCountTimeoutLength + 20;
|
||||
|
||||
function assertMatches(aTest, aMatches) {
|
||||
window.opener.wrappedJSObject.SimpleTest.is(aTest.current, aMatches[1],
|
||||
"Currently highlighted match should be at " + aTest.current);
|
||||
window.opener.wrappedJSObject.SimpleTest.is(aTest.total, aMatches[2],
|
||||
"Total amount of matches should be " + aTest.total);
|
||||
}
|
||||
|
||||
function testString(aTest, aNext) {
|
||||
gFindBar.clear();
|
||||
enterStringIntoFindField(aTest.text);
|
||||
|
||||
setTimeout(function() {
|
||||
let matches = foundMatches.value.match(regex);
|
||||
if (!aTest.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) {
|
||||
gFindBar.onFindAgainCommand();
|
||||
setTimeout(function() {
|
||||
assertMatches(aCycleTest, foundMatches.value.match(regex));
|
||||
aNextCycle();
|
||||
}, timeout);
|
||||
}, aNext);
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
asyncForEach(tests, testString, callback);
|
||||
}
|
||||
|
||||
function testClipboardSearchString(aExpected) {
|
||||
if (!gHasFindClipboard)
|
||||
return;
|
||||
|
@ -4,16 +4,19 @@
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=257061
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=288254
|
||||
-->
|
||||
<window title="Mozilla Bug 288254"
|
||||
<window title="Mozilla Bug 257061 and Bug 288254"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a target="_blank"
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=257061">Mozilla Bug 257061</a>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=288254">Mozilla Bug 288254</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
@ -26,7 +29,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=288254
|
||||
<script class="testbody" type="application/javascript">
|
||||
<![CDATA[
|
||||
|
||||
/** Test for Bug 288254 **/
|
||||
/** Test for Bug 257061 and Bug 288254 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
window.open("findbar_window.xul", "findbartest",
|
||||
"chrome,width=600,height=600");
|
||||
|
@ -191,6 +191,7 @@
|
||||
type="checkbox"
|
||||
xbl:inherits="accesskey=matchcaseaccesskey"/>
|
||||
<xul:label anonid="match-case-status" class="findbar-find-fast"/>
|
||||
<xul:label anonid="found-matches" class="findbar-find-fast found-matches" hidden="true"/>
|
||||
<xul:image anonid="find-status-icon" class="findbar-find-fast find-status-icon"/>
|
||||
<xul:description anonid="find-status"
|
||||
control="findbar-textbox"
|
||||
@ -318,6 +319,7 @@
|
||||
<constructor><![CDATA[
|
||||
// These elements are accessed frequently and are therefore cached
|
||||
this._findField = this.getElement("findbar-textbox");
|
||||
this._foundMatches = this.getElement("found-matches");
|
||||
this._findStatusIcon = this.getElement("find-status-icon");
|
||||
this._findStatusDesc = this.getElement("find-status");
|
||||
|
||||
@ -331,6 +333,10 @@
|
||||
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");
|
||||
|
||||
prefsvc.addObserver("accessibility.typeaheadfind",
|
||||
this._observer, false);
|
||||
@ -430,6 +436,54 @@
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<field name="_pluralForm">null</field>
|
||||
<property name="pluralForm">
|
||||
<getter><![CDATA[
|
||||
if (!this._pluralForm) {
|
||||
this._pluralForm = Components.utils.import(
|
||||
"resource://gre/modules/PluralForm.jsm", {}).PluralForm;
|
||||
}
|
||||
return this._pluralForm;
|
||||
]]></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);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
- Turns highlight on or off.
|
||||
- @param aHighlight (boolean)
|
||||
@ -448,6 +502,9 @@
|
||||
|
||||
this.browser._lastSearchHighlight = aHighlight;
|
||||
this.browser.finder.highlight(aHighlight, word);
|
||||
|
||||
// Update the matches count
|
||||
this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
@ -503,6 +560,19 @@
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<field name="_strBundle">null</field>
|
||||
<property name="strBundle">
|
||||
<getter><![CDATA[
|
||||
if (!this._strBundle) {
|
||||
this._strBundle =
|
||||
Components.classes["@mozilla.org/intl/stringbundle;1"]
|
||||
.getService(Components.interfaces.nsIStringBundleService)
|
||||
.createBundle("chrome://global/locale/findbar.properties");
|
||||
}
|
||||
return this._strBundle;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<!--
|
||||
- Opens and displays the find bar.
|
||||
-
|
||||
@ -519,10 +589,7 @@
|
||||
this._findMode = aMode;
|
||||
|
||||
if (!this._notFoundStr) {
|
||||
let stringsBundle =
|
||||
Components.classes["@mozilla.org/intl/stringbundle;1"]
|
||||
.getService(Components.interfaces.nsIStringBundleService)
|
||||
.createBundle("chrome://global/locale/findbar.properties");
|
||||
var stringsBundle = this.strBundle;
|
||||
this._notFoundStr = stringsBundle.GetStringFromName("NotFound");
|
||||
this._wrappedToTopStr =
|
||||
stringsBundle.GetStringFromName("WrappedToTop");
|
||||
@ -953,6 +1020,7 @@
|
||||
this._findField.removeAttribute("status");
|
||||
break;
|
||||
}
|
||||
this._updateMatchesCount(res);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
@ -1169,6 +1237,36 @@
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
- This handles all the result changes for matches counts.
|
||||
- @param aResult
|
||||
- Result Object, containing the total amount of matches and a vector
|
||||
- of the current result.
|
||||
-->
|
||||
<method name="onMatchesCountResult">
|
||||
<parameter name="aResult"/>
|
||||
<body><![CDATA[
|
||||
if (aResult.total !== 0) {
|
||||
if (aResult.total == -1) {
|
||||
this._foundMatches.value = this.pluralForm.get(
|
||||
this._matchesCountLimit,
|
||||
this.strBundle.GetStringFromName("FoundTooManyMatches")
|
||||
).replace("#1", this._matchesCountLimit);
|
||||
} else {
|
||||
this._foundMatches.value = this.pluralForm.get(
|
||||
aResult.total,
|
||||
this.strBundle.GetStringFromName("FoundMatches")
|
||||
).replace("#1", aResult.current)
|
||||
.replace("#2", aResult.total);
|
||||
}
|
||||
this._foundMatches.hidden = false;
|
||||
} else {
|
||||
this._foundMatches.hidden = true;
|
||||
this._foundMatches.value = "";
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
- This handler may cancel a request to focus content by returning |false|
|
||||
- explicitly.
|
||||
|
@ -10,6 +10,12 @@ NormalFind=Find in page
|
||||
FastFind=Quick find
|
||||
FastFindLinks=Quick find (links only)
|
||||
CaseSensitive=(Case sensitive)
|
||||
FoundMatchCount=%S match
|
||||
FoundMatchesCount=%S matches
|
||||
FoundTooManyMatches=More than %S matches
|
||||
# LOCALIZATION NOTE (FoundMatches): Semicolon-separated list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 is currently selected match and #2 the total amount of matches.
|
||||
FoundMatches=#1 of #2 match;#1 of #2 matches
|
||||
# LOCALIZATION NOTE (FoundTooManyMatches): Semicolon-separated list of plural
|
||||
# forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 is the total amount of matches allowed before counting stops.
|
||||
FoundTooManyMatches=More than #1 match;More than #1 matches
|
||||
|
@ -74,7 +74,9 @@ Finder.prototype = {
|
||||
};
|
||||
|
||||
for (let l of this._listeners) {
|
||||
l.onFindResult(data);
|
||||
try {
|
||||
l.onFindResult(data);
|
||||
} catch (ex) {}
|
||||
}
|
||||
},
|
||||
|
||||
@ -194,8 +196,10 @@ Finder.prototype = {
|
||||
focusContent: function() {
|
||||
// Allow Finder listeners to cancel focusing the content.
|
||||
for (let l of this._listeners) {
|
||||
if (!l.shouldFocusContent())
|
||||
return;
|
||||
try {
|
||||
if (!l.shouldFocusContent())
|
||||
return;
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
let fastFind = this._fastFind;
|
||||
@ -255,6 +259,180 @@ Finder.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
requestMatchesCount: function(aWord, aMatchLimit, aLinksOnly) {
|
||||
let window = this._getWindow();
|
||||
let result = this._countMatchesInWindow(aWord, aMatchLimit, aLinksOnly, window);
|
||||
|
||||
// Count matches in (i)frames AFTER searching through the main window.
|
||||
for (let frame of result._framesToCount) {
|
||||
// We've reached our limit; no need to do more work.
|
||||
if (result.total == -1 || result.total == aMatchLimit)
|
||||
break;
|
||||
this._countMatchesInWindow(aWord, aMatchLimit, aLinksOnly, frame, result);
|
||||
}
|
||||
|
||||
// The `_currentFound` and `_framesToCount` properties are only used for
|
||||
// internal bookkeeping between recursive calls.
|
||||
delete result._currentFound;
|
||||
delete result._framesToCount;
|
||||
|
||||
for (let l of this._listeners) {
|
||||
try {
|
||||
l.onMatchesCountResult(result);
|
||||
} catch (ex) {}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Counts the number of matches for the searched word in the passed window's
|
||||
* content.
|
||||
* @param aWord
|
||||
* the word to search for.
|
||||
* @param aMatchLimit
|
||||
* the maximum number of matches shown (for speed reasons).
|
||||
* @param aLinksOnly
|
||||
* whether we should only search through links.
|
||||
* @param aWindow
|
||||
* the window to search in. Passing undefined will search the
|
||||
* current content window. Optional.
|
||||
* @param aStats
|
||||
* the Object that is returned by this function. It may be passed as an
|
||||
* argument here in the case of a recursive call.
|
||||
* @returns an object stating the number of matches and a vector for the current match.
|
||||
*/
|
||||
_countMatchesInWindow: function(aWord, aMatchLimit, aLinksOnly, aWindow = null, aStats = null) {
|
||||
aWindow = aWindow || this._getWindow();
|
||||
aStats = aStats || {
|
||||
total: 0,
|
||||
current: 0,
|
||||
_framesToCount: new Set(),
|
||||
_currentFound: false
|
||||
};
|
||||
|
||||
// If we already reached our max, there's no need to do more work!
|
||||
if (aStats.total == -1 || aStats.total == aMatchLimit) {
|
||||
aStats.total = -1;
|
||||
return aStats;
|
||||
}
|
||||
|
||||
this._collectFrames(aWindow, aStats);
|
||||
|
||||
let foundRange = this._fastFind.getFoundRange();
|
||||
|
||||
this._findIterator(aWord, aWindow, aRange => {
|
||||
if (!aLinksOnly || this._rangeStartsInLink(aRange)) {
|
||||
++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);
|
||||
}
|
||||
}
|
||||
if (aStats.total == aMatchLimit) {
|
||||
aStats.total = -1;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return aStats;
|
||||
},
|
||||
|
||||
/**
|
||||
* Basic wrapper around nsIFind that provides invoking a callback `aOnFind`
|
||||
* 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) {
|
||||
let doc = aWindow.document;
|
||||
let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
|
||||
doc.body : doc.documentElement;
|
||||
|
||||
let searchRange = doc.createRange();
|
||||
searchRange.selectNodeContents(body);
|
||||
|
||||
let startPt = searchRange.cloneRange();
|
||||
startPt.collapse(true);
|
||||
|
||||
let endPt = searchRange.cloneRange();
|
||||
endPt.collapse(false);
|
||||
|
||||
let retRange = null;
|
||||
|
||||
let finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
|
||||
.createInstance()
|
||||
.QueryInterface(Ci.nsIFind);
|
||||
finder.caseSensitive = this._fastFind.caseSensitive;
|
||||
|
||||
while ((retRange = finder.Find(aWord, searchRange, startPt, endPt))) {
|
||||
if (aOnFind(retRange) === false)
|
||||
break;
|
||||
startPt = retRange.cloneRange();
|
||||
startPt.collapse(false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper method for `_countMatchesInWindow` that recursively collects all
|
||||
* visible (i)frames inside a window.
|
||||
*
|
||||
* @param aWindow
|
||||
* the window to extract the (i)frames from.
|
||||
* @param aStats
|
||||
* Object that contains a Set called '_framesToCount'
|
||||
*/
|
||||
_collectFrames: function(aWindow, aStats) {
|
||||
if (!aWindow.frames || !aWindow.frames.length)
|
||||
return;
|
||||
// Casting `aWindow.frames` to an Iterator doesn't work, so we're stuck with
|
||||
// a plain, old for-loop.
|
||||
for (let i = 0, l = aWindow.frames.length; i < l; ++i) {
|
||||
let frame = aWindow.frames[i];
|
||||
// Don't count matches in hidden frames.
|
||||
let frameEl = frame && frame.frameElement;
|
||||
if (!frameEl)
|
||||
continue;
|
||||
// Construct a range around the frame element to check its visiblity.
|
||||
let range = aWindow.document.createRange();
|
||||
range.setStart(frameEl, 0);
|
||||
range.setEnd(frameEl, 0);
|
||||
if (!this._fastFind.isRangeVisible(range, this._getDocShell(range), true))
|
||||
continue;
|
||||
// All good, so add it to the set to count later.
|
||||
if (!aStats._framesToCount.has(frame))
|
||||
aStats._framesToCount.add(frame);
|
||||
this._collectFrames(frame, aStats);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper method to extract the docShell reference from a Window or Range object.
|
||||
*
|
||||
* @param aWindowOrRange
|
||||
* Window object to query. May also be a Range, from which the owner
|
||||
* window will be queried.
|
||||
* @returns nsIDocShell
|
||||
*/
|
||||
_getDocShell: function(aWindowOrRange) {
|
||||
let window = aWindowOrRange;
|
||||
// Ranges may also be passed in, so fetch its window.
|
||||
if (aWindowOrRange instanceof Ci.nsIDOMRange)
|
||||
window = aWindowOrRange.startContainer.ownerDocument.defaultView;
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
},
|
||||
|
||||
_getWindow: function () {
|
||||
return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
|
||||
},
|
||||
@ -354,34 +532,11 @@ Finder.prototype = {
|
||||
return found;
|
||||
}
|
||||
|
||||
let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
|
||||
doc.body : doc.documentElement;
|
||||
|
||||
if (aHighlight) {
|
||||
let searchRange = doc.createRange();
|
||||
searchRange.selectNodeContents(body);
|
||||
|
||||
let startPt = searchRange.cloneRange();
|
||||
startPt.collapse(true);
|
||||
|
||||
let endPt = searchRange.cloneRange();
|
||||
endPt.collapse(false);
|
||||
|
||||
let retRange = null;
|
||||
let finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
|
||||
.createInstance()
|
||||
.QueryInterface(Ci.nsIFind);
|
||||
|
||||
finder.caseSensitive = this._fastFind.caseSensitive;
|
||||
|
||||
while ((retRange = finder.Find(aWord, searchRange,
|
||||
startPt, endPt))) {
|
||||
this._highlightRange(retRange, controller);
|
||||
startPt = retRange.cloneRange();
|
||||
startPt.collapse(false);
|
||||
|
||||
this._findIterator(aWord, win, aRange => {
|
||||
this._highlightRange(aRange, controller);
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// First, attempt to remove highlighting from main document
|
||||
let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
|
||||
@ -578,6 +733,41 @@ Finder.prototype = {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether a range is inside a link.
|
||||
* @param aRange
|
||||
* the range to check
|
||||
* @returns true if the range starts in a link
|
||||
*/
|
||||
_rangeStartsInLink: function(aRange) {
|
||||
let isInsideLink = false;
|
||||
let node = aRange.startContainer;
|
||||
|
||||
if (node.nodeType == node.ELEMENT_NODE) {
|
||||
if (node.hasChildNodes) {
|
||||
let childNode = node.item(aRange.startOffset);
|
||||
if (childNode)
|
||||
node = childNode;
|
||||
}
|
||||
}
|
||||
|
||||
const XLink_NS = "http://www.w3.org/1999/xlink";
|
||||
do {
|
||||
if (node instanceof HTMLAnchorElement) {
|
||||
isInsideLink = node.hasAttribute("href");
|
||||
break;
|
||||
} else if (typeof node.hasAttributeNS == "function" &&
|
||||
node.hasAttributeNS(XLink_NS, "href")) {
|
||||
isInsideLink = (node.getAttributeNS(XLink_NS, "type") == "simple");
|
||||
break;
|
||||
}
|
||||
|
||||
node = node.parentNode;
|
||||
} while (node);
|
||||
|
||||
return isInsideLink;
|
||||
},
|
||||
|
||||
// Start of nsIWebProgressListener implementation.
|
||||
|
||||
onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
|
||||
|
@ -150,7 +150,8 @@ findbar[hidden] {
|
||||
-moz-margin-start: 5px;
|
||||
}
|
||||
|
||||
.findbar-find-status {
|
||||
.findbar-find-status,
|
||||
.findbar-matches {
|
||||
color: GrayText;
|
||||
margin: 0 !important;
|
||||
-moz-margin-start: 12px !important;
|
||||
|
@ -210,7 +210,8 @@ label.findbar-find-fast:-moz-lwtheme,
|
||||
list-style-image: url("chrome://global/skin/icons/loading_16.png");
|
||||
}
|
||||
|
||||
.findbar-find-status {
|
||||
.findbar-find-status,
|
||||
.found-matches {
|
||||
color: rgba(0,0,0,.5);
|
||||
margin: 0 !important;
|
||||
-moz-margin-start: 12px !important;
|
||||
|
@ -143,7 +143,8 @@ findbar[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.findbar-find-status {
|
||||
.findbar-find-status,
|
||||
.found-matches {
|
||||
color: GrayText;
|
||||
margin: 0 !important;
|
||||
-moz-margin-start: 12px !important;
|
||||
|
Loading…
Reference in New Issue
Block a user