Bug 269442 - whole word matching support for nsFind. r=ehsan,dao. ui-r=shorlander

MozReview-Commit-ID: KIDWHyjOSYL
This commit is contained in:
Mike de Boer 2016-06-28 15:13:53 +02:00
Родитель 9e552a2542
Коммит fe68c53f4b
16 изменённых файлов: 276 добавлений и 31 удалений

Просмотреть файл

@ -557,18 +557,21 @@ nsFind::SetCaseSensitive(bool aCaseSensitive)
return NS_OK;
}
/* attribute boolean entireWord; */
NS_IMETHODIMP
nsFind::GetWordBreaker(nsIWordBreaker** aWordBreaker)
nsFind::GetEntireWord(bool *aEntireWord)
{
*aWordBreaker = mWordBreaker;
NS_IF_ADDREF(*aWordBreaker);
if (!aEntireWord)
return NS_ERROR_NULL_POINTER;
*aEntireWord = !!mWordBreaker;
return NS_OK;
}
NS_IMETHODIMP
nsFind::SetWordBreaker(nsIWordBreaker* aWordBreaker)
nsFind::SetEntireWord(bool aEntireWord)
{
mWordBreaker = aWordBreaker;
mWordBreaker = aEntireWord ? nsContentUtils::WordBreaker() : nullptr;
return NS_OK;
}
@ -730,6 +733,82 @@ nsFind::NextNode(nsIDOMRange* aSearchRange,
return NS_OK;
}
class MOZ_STACK_CLASS PeekNextCharRestoreState final
{
public:
explicit PeekNextCharRestoreState(nsFind* aFind)
: mIterOffset(aFind->mIterOffset),
mIterNode(aFind->mIterNode),
mCurrNode(aFind->mIterator->GetCurrentNode()),
mFind(aFind)
{
}
~PeekNextCharRestoreState()
{
mFind->mIterOffset = mIterOffset;
mFind->mIterNode = mIterNode;
mFind->mIterator->PositionAt(mCurrNode);
}
private:
int32_t mIterOffset;
nsCOMPtr<nsIDOMNode> mIterNode;
nsCOMPtr<nsINode> mCurrNode;
RefPtr<nsFind> mFind;
};
char16_t
nsFind::PeekNextChar(nsIDOMRange* aSearchRange,
nsIDOMRange* aStartPoint,
nsIDOMRange* aEndPoint)
{
// We need to restore the necessary member variables before this function
// returns.
PeekNextCharRestoreState restoreState(this);
nsCOMPtr<nsIContent> tc;
nsresult rv;
const nsTextFragment *frag;
int32_t fragLen;
// Loop through non-block nodes until we find one that's not empty.
do {
tc = nullptr;
NextNode(aSearchRange, aStartPoint, aEndPoint, false);
// Get the text content:
tc = do_QueryInterface(mIterNode);
// Get the block parent.
nsCOMPtr<nsIDOMNode> blockParent;
rv = GetBlockParent(mIterNode, getter_AddRefs(blockParent));
if (NS_FAILED(rv))
return L'\0';
// If out of nodes or in new parent.
if (!mIterNode || !tc || (blockParent != mLastBlockParent))
return L'\0';
frag = tc->GetText();
fragLen = frag->GetLength();
} while (fragLen <= 0);
const char16_t *t2b = nullptr;
const char *t1b = nullptr;
if (frag->Is2b()) {
t2b = frag->Get2b();
} else {
t1b = frag->Get1b();
}
// Index of char to return.
int32_t index = mFindBackward ? fragLen - 1 : 0;
return t1b ? CHAR_TO_UNICHAR(t1b[index]) : t2b[index];
}
bool
nsFind::IsBlockNode(nsIContent* aContent)
{
@ -901,6 +980,8 @@ nsFind::Find(const char16_t* aPatText, nsIDOMRange* aSearchRange,
// Keep track of when we're in whitespace:
// (only matters when we're matching)
bool inWhitespace = false;
// Keep track of whether the previous char was a word-breaking one.
bool wordBreakPrev = false;
// Place to save the range start point in case we find a match:
nsCOMPtr<nsIDOMNode> matchAnchorNode;
@ -912,7 +993,10 @@ nsFind::Find(const char16_t* aPatText, nsIDOMRange* aSearchRange,
aEndPoint->GetEndContainer(getter_AddRefs(endNode));
aEndPoint->GetEndOffset(&endOffset);
char16_t c = 0;
char16_t patc = 0;
char16_t prevChar = 0;
char16_t prevCharInMatch = 0;
while (1) {
#ifdef DEBUG_FIND
printf("Loop ...\n");
@ -1046,9 +1130,11 @@ nsFind::Find(const char16_t* aPatText, nsIDOMRange* aSearchRange,
return NS_OK;
}
// Save the previous character for word boundary detection
prevChar = c;
// The two characters we'll be comparing:
char16_t c = (t2b ? t2b[findex] : CHAR_TO_UNICHAR(t1b[findex]));
char16_t patc = patStr[pindex];
c = (t2b ? t2b[findex] : CHAR_TO_UNICHAR(t1b[findex]));
patc = patStr[pindex];
#ifdef DEBUG_FIND
printf("Comparing '%c'=%x to '%c' (%d of %d), findex=%d%s\n",
@ -1111,7 +1197,7 @@ nsFind::Find(const char16_t* aPatText, nsIDOMRange* aSearchRange,
// a '\n' between CJ characters is ignored
if (pindex != (mFindBackward ? patLen : 0) && c != patc && !inWhitespace) {
if (c == '\n' && t2b && IS_CJ_CHAR(prevChar)) {
if (c == '\n' && t2b && IS_CJ_CHAR(prevCharInMatch)) {
int32_t nindex = findex + incr;
if (mFindBackward ? (nindex >= 0) : (nindex < fragLen)) {
if (IS_CJ_CHAR(t2b[nindex])) {
@ -1121,9 +1207,22 @@ nsFind::Find(const char16_t* aPatText, nsIDOMRange* aSearchRange,
}
}
// Compare
if (c == patc || (inWhitespace && IsSpace(c))) {
prevChar = c;
wordBreakPrev = false;
if (mWordBreaker) {
if (prevChar == NBSP_CHARCODE)
prevChar = CHAR_TO_UNICHAR(' ');
wordBreakPrev = mWordBreaker->BreakInBetween(&prevChar, 1, &c, 1);
}
// Compare. Match if we're in whitespace and c is whitespace, or if the
// characters match and at least one of the following is true:
// a) we're not matching the entire word
// b) a match has already been stored
// c) the previous character is a different "class" than the current character.
if ((c == patc && (!mWordBreaker || matchAnchorNode || wordBreakPrev)) ||
(inWhitespace && IsSpace(c)))
{
prevCharInMatch = c;
#ifdef DEBUG_FIND
if (inWhitespace) {
printf("YES (whitespace)(%d of %d)\n", pindex, patLen);
@ -1148,6 +1247,29 @@ nsFind::Find(const char16_t* aPatText, nsIDOMRange* aSearchRange,
// Make the range:
nsCOMPtr<nsIDOMNode> startParent;
nsCOMPtr<nsIDOMNode> endParent;
// Check for word break (if necessary)
if (mWordBreaker) {
int32_t nextfindex = findex + incr;
char16_t nextChar;
// If still in array boundaries, get nextChar.
if (mFindBackward ? (nextfindex >= 0) : (nextfindex < fragLen))
nextChar = (t2b ? t2b[nextfindex] : CHAR_TO_UNICHAR(t1b[nextfindex]));
// Get next character from the next node.
else
nextChar = PeekNextChar(aSearchRange, aStartPoint, aEndPoint);
if (nextChar == NBSP_CHARCODE)
nextChar = CHAR_TO_UNICHAR(' ');
// If a word break isn't there when it needs to be, reset search.
if (!mWordBreaker->BreakInBetween(&c, 1, &nextChar, 1)) {
matchAnchorNode = nullptr;
continue;
}
}
nsCOMPtr<nsIDOMRange> range = CreateRange(tc);
if (range) {
int32_t matchStartOffset, matchEndOffset;

Просмотреть файл

@ -44,6 +44,8 @@ protected:
bool mFindBackward;
bool mCaseSensitive;
// Use "find entire words" mode by setting to a word breaker or null, to
// disable "entire words" mode.
nsCOMPtr<nsIWordBreaker> mWordBreaker;
int32_t mIterOffset;
@ -64,6 +66,11 @@ protected:
nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint,
bool aContinueOk);
// Get the first character from the next node (last if mFindBackward).
char16_t PeekNextChar(nsIDOMRange* aSearchRange,
nsIDOMRange* aStartPoint,
nsIDOMRange* aEndPoint);
// Reset variables before returning -- don't hold any references.
void ResetAll();
@ -71,6 +78,8 @@ protected:
nsresult InitIterator(nsIDOMNode* aStartNode, int32_t aStartOffset,
nsIDOMNode* aEndNode, int32_t aEndOffset);
RefPtr<nsFindContentIterator> mIterator;
friend class PeekNextCharRestoreState;
};
#endif // nsFind_h__

Просмотреть файл

@ -8,17 +8,12 @@
interface nsIDOMRange;
interface nsIWordBreaker;
[scriptable, uuid(75125d55-37ee-4575-b9b5-f33bfa68c2a1)]
[scriptable, uuid(40aba110-2a56-4678-be90-e2c17a9ae7d7)]
interface nsIFind : nsISupports
{
attribute boolean findBackwards;
attribute boolean caseSensitive;
/**
* Use "find entire words" mode by setting to a word breaker
* or null, to disable "entire words" mode.
*/
[noscript] attribute nsIWordBreaker wordBreaker;
attribute boolean entireWord;
/**
* Find some text in the current context. The implementation is

Просмотреть файл

@ -719,8 +719,7 @@ nsWebBrowserFind::SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping,
(void)find->SetCaseSensitive(mMatchCase);
(void)find->SetFindBackwards(mFindBackwards);
// XXX Make and set a line breaker here, once that's implemented.
(void)find->SetWordBreaker(nullptr);
(void)find->SetEntireWord(mEntireWord);
// Now make sure the content (for actual finding) and frame (for
// selection) models are up to date.

Просмотреть файл

@ -864,6 +864,7 @@ pref("accessibility.typeaheadfind.matchesCountTimeout", 100);
pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
pref("findbar.highlightAll", false);
pref("findbar.modalHighlight", false);
pref("findbar.entireword", false);
// use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
pref("gfx.use_text_smoothing_setting", false);

Просмотреть файл

@ -18,7 +18,7 @@ interface nsIDocShell;
/****************************** nsTypeAheadFind ******************************/
[scriptable, uuid(c8ca2c38-7030-4453-ae63-a16eeb10e096)]
[scriptable, uuid(ae501e28-c57f-4692-ac74-410e1bed98b7)]
interface nsITypeAheadFind : nsISupports
{
/****************************** Initializer ******************************/
@ -65,6 +65,7 @@ interface nsITypeAheadFind : nsISupports
readonly attribute AString searchString;
// Most recent search string
attribute boolean caseSensitive; // Searches are case sensitive
attribute boolean entireWord; // Search for whole words only
readonly attribute nsIDOMElement foundLink;
// Most recent elem found, if a link
readonly attribute nsIDOMElement foundEditable;

Просмотреть файл

@ -80,7 +80,8 @@ nsTypeAheadFind::nsTypeAheadFind():
mDidAddObservers(false),
mLastFindLength(0),
mIsSoundInitialized(false),
mCaseSensitive(false)
mCaseSensitive(false),
mEntireWord(false)
{
}
@ -167,6 +168,26 @@ nsTypeAheadFind::GetCaseSensitive(bool* isCaseSensitive)
return NS_OK;
}
NS_IMETHODIMP
nsTypeAheadFind::SetEntireWord(bool isEntireWord)
{
mEntireWord = isEntireWord;
if (mFind) {
mFind->SetEntireWord(mEntireWord);
}
return NS_OK;
}
NS_IMETHODIMP
nsTypeAheadFind::GetEntireWord(bool* isEntireWord)
{
*isEntireWord = mEntireWord;
return NS_OK;
}
NS_IMETHODIMP
nsTypeAheadFind::SetDocShell(nsIDocShell* aDocShell)
{

Просмотреть файл

@ -106,6 +106,7 @@ protected:
nsCOMPtr<nsIFind> mFind;
bool mCaseSensitive;
bool mEntireWord;
bool EnsureFind() {
if (mFind) {
@ -118,7 +119,7 @@ protected:
}
mFind->SetCaseSensitive(mCaseSensitive);
mFind->SetWordBreaker(nullptr);
mFind->SetEntireWord(mEntireWord);
return true;
}

Просмотреть файл

@ -196,7 +196,16 @@
oncommand="_setCaseSensitivity(this.checked ? 1 : 0);"
type="checkbox"
xbl:inherits="accesskey=matchcaseaccesskey"/>
<xul:toolbarbutton anonid="find-entire-word"
class="findbar-entire-word findbar-button tabbable"
label="&entireWord.label;"
accesskey="&entireWord.accesskey;"
tooltiptext="&entireWord.tooltiptext;"
oncommand="_setEntireWord(this.checked);"
type="checkbox"
xbl:inherits="accesskey=entirewordaccesskey"/>
<xul:label anonid="match-case-status" class="findbar-find-fast"/>
<xul:label anonid="entire-word-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"
@ -334,6 +343,10 @@
case "accessibility.typeaheadfind.casesensitive":
this._self._setCaseSensitivity(prefsvc.getIntPref(aPrefName));
break;
case "findbar.entireword":
this._self._entireWord = prefsvc.getBoolPref(aPrefName);
this._self._updateEntireWord();
break;
case "findbar.highlightAll":
this._self.toggleHighlight(prefsvc.getBoolPref(aPrefName), true);
break;
@ -375,6 +388,7 @@
this._observer, false);
prefsvc.addObserver("accessibility.typeaheadfind.casesensitive",
this._observer, false);
prefsvc.addObserver("findbar.entireword", this._observer, false);
prefsvc.addObserver("findbar.highlightAll", this._observer, false);
prefsvc.addObserver("findbar.modalHighlight", this._observer, false);
@ -384,6 +398,7 @@
prefsvc.getBoolPref("accessibility.typeaheadfind.linksonly");
this._typeAheadCaseSensitive =
prefsvc.getIntPref("accessibility.typeaheadfind.casesensitive");
this._entireWord = prefsvc.getBoolPref("findbar.entireword");
this._highlightAll = prefsvc.getBoolPref("findbar.highlightAll");
// Convenience
@ -424,6 +439,7 @@
this._observer);
prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
this._observer);
prefsvc.removeObserver("findbar.entireword", this._observer);
prefsvc.removeObserver("findbar.highlightAll", this._observer);
prefsvc.removeObserver("findbar.modalHighlight", this._observer);
@ -622,6 +638,51 @@
]]></body>
</method>
<!--
- Updates the entire-word mode of the findbar and its UI.
-->
<method name="_updateEntireWord">
<body><![CDATA[
let entireWord = this._entireWord;
let checkbox = this.getElement("find-entire-word");
let statusLabel = this.getElement("entire-word-status");
checkbox.checked = entireWord;
statusLabel.value = entireWord ? this._entireWordStr : "";
// Show the checkbox on the full Find bar in non-auto mode.
// Show the label in all other cases.
let hideCheckbox = this._findMode != this.FIND_NORMAL;
checkbox.hidden = hideCheckbox;
statusLabel.hidden = !hideCheckbox;
this.browser.finder.entireWord = entireWord;
// Update the matches count
this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
]]></body>
</method>
<!--
- Sets the findbar entire-word mode
- @param aEntireWord (boolean)
- Whether or not entire-word mode should be turned on.
-->
<method name="_setEntireWord">
<parameter name="aEntireWord"/>
<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);
if (this.getElement("highlight").checked)
this._setHighlightTimeout();
]]></body>
</method>
<field name="_strBundle">null</field>
<property name="strBundle">
<getter><![CDATA[
@ -665,6 +726,8 @@
stringsBundle.GetStringFromName("FastFindLinks");
this._caseSensitiveStr =
stringsBundle.GetStringFromName("CaseSensitive");
this._entireWordStr =
stringsBundle.GetStringFromName("EntireWord");
}
this._findFailedString = null;
@ -910,6 +973,7 @@
this.getElement("find-previous").hidden = showMinimalUI;
foundMatches.hidden = showMinimalUI || !foundMatches.value;
this._updateCaseSensitivity();
this._updateEntireWord();
this._setHighlightAll();
if (showMinimalUI)
@ -941,9 +1005,13 @@
// Only search on input if we don't have a last-failed string,
// or if the current search string doesn't start with it.
// In entire-word mode we always attemp a find; since sequential matching
// is not guaranteed, the first character typed may not be a word (no
// match), but the with the second character it may well be a word,
// thus a match.
if (!this._findFailedString ||
!val.startsWith(this._findFailedString))
{
!val.startsWith(this._findFailedString) ||
this._entireWord) {
// 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) {
@ -956,6 +1024,7 @@
this._setHighlightTimeout();
this._updateCaseSensitivity(val);
this._updateEntireWord();
this.browser.finder.fastFind(val, this._findMode == this.FIND_LINKS,
this._findMode != this.FIND_NORMAL);
@ -1065,6 +1134,7 @@
event.initCustomEvent("find" + aType, true, true, {
query: this._findField.value,
caseSensitive: !!this._typeAheadCaseSensitive,
entireWord: this._entireWord,
highlightAll: this._highlightAll,
findPrevious: aFindPrevious
});

Просмотреть файл

@ -14,3 +14,6 @@
<!ENTITY caseSensitive.label "Match Case">
<!ENTITY caseSensitive.accesskey "c">
<!ENTITY caseSensitive.tooltiptext "Search with case sensitivity">
<!ENTITY entireWord.label "Whole Words">
<!ENTITY entireWord.accesskey "w">
<!ENTITY entireWord.tooltiptext "Search whole words only">

Просмотреть файл

@ -10,6 +10,7 @@ NormalFind=Find in page
FastFind=Quick find
FastFindLinks=Quick find (links only)
CaseSensitive=(Case sensitive)
EntireWord=(Whole words only)
# 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.

Просмотреть файл

@ -121,6 +121,10 @@ Finder.prototype = {
this._fastFind.caseSensitive = aSensitive;
},
set entireWord(aEntireWord) {
this._fastFind.entireWord = aEntireWord;
},
get highlighter() {
if (this._highlighter)
return this._highlighter;
@ -468,6 +472,7 @@ Finder.prototype = {
.createInstance()
.QueryInterface(Ci.nsIFind);
finder.caseSensitive = this._fastFind.caseSensitive;
finder.entireWord = this._fastFind.entireWord;
while ((retRange = finder.Find(aWord, searchRange, startPt, endPt))) {
yield retRange;

Просмотреть файл

@ -127,6 +127,11 @@ RemoteFinder.prototype = {
{ caseSensitive: aSensitive });
},
set entireWord(aEntireWord) {
this._browser.messageManager.sendAsyncMessage("Finder:EntireWord",
{ entireWord: aEntireWord });
},
getInitialSelection: function() {
this._browser.messageManager.sendAsyncMessage("Finder:GetInitialSelection", {});
},
@ -217,6 +222,7 @@ RemoteFinderListener.prototype = {
MESSAGES: [
"Finder:CaseSensitive",
"Finder:Destroy",
"Finder:EntireWord",
"Finder:FastFind",
"Finder:FindAgain",
"Finder:SetSearchStringToSelection",
@ -257,6 +263,10 @@ RemoteFinderListener.prototype = {
this._finder.caseSensitive = data.caseSensitive;
break;
case "Finder:EntireWord":
this._finder.entireWord = data.entireWord;
break;
case "Finder:SetSearchStringToSelection": {
let selection = this._finder.setSearchStringToSelection();
this._global.sendAsyncMessage("Finder:CurrentSelectionResult",

Просмотреть файл

@ -143,7 +143,8 @@ findbar[noanim] {
}
.findbar-highlight,
.findbar-case-sensitive {
.findbar-case-sensitive,
.findbar-entire-word {
margin-inline-start: 5px;
}

Просмотреть файл

@ -71,7 +71,8 @@ label.findbar-find-fast:-moz-lwtheme,
.findbar-find-next,
.findbar-find-previous,
.findbar-highlight,
.findbar-case-sensitive {
.findbar-case-sensitive,
.findbar-entire-word {
-moz-appearance: none;
border-radius: 10000px;
border: @roundButtonBorder@;
@ -90,7 +91,8 @@ label.findbar-find-fast:-moz-lwtheme,
}
.findbar-highlight,
.findbar-case-sensitive {
.findbar-case-sensitive,
.findbar-entire-word {
margin-inline-end: 5px;
padding: 2px 9px;
}
@ -114,8 +116,10 @@ label.findbar-find-fast:-moz-lwtheme,
.findbar-find-previous:not([disabled]):hover:active,
.findbar-highlight:not([disabled]):hover:active,
.findbar-case-sensitive:not([disabled]):hover:active,
.findbar-entire-word:not([disabled]):hover:active,
.findbar-highlight:not([disabled])[checked="true"],
.findbar-case-sensitive:not([disabled])[checked="true"] {
.findbar-case-sensitive:not([disabled])[checked="true"],
.findbar-entire-word:not([disabled])[checked="true"] {
text-shadow: @loweredShadow@;
background: @roundButtonPressedBackground@;
box-shadow: @roundButtonPressedShadow@;

Просмотреть файл

@ -131,12 +131,14 @@ findbar[noanim] {
}
.findbar-highlight,
.findbar-case-sensitive {
.findbar-case-sensitive,
.findbar-entire-word {
margin-inline-start: 5px;
}
.findbar-highlight > .toolbarbutton-icon,
.findbar-case-sensitive > .toolbarbutton-icon {
.findbar-case-sensitive > .toolbarbutton-icon,
.findbar-entire-word > .toolbarbutton-icon {
display: none;
}