Bug 202251 - Add an option to ignore diacritics when searching. r=fluent-reviewers,mikedeboer,jfkthame,flod

Differential Revision: https://phabricator.services.mozilla.com/D51841

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Alex Henrie 2019-12-09 19:26:40 +00:00
parent fc08c93083
commit 00867c4809
32 changed files with 419 additions and 11 deletions

View File

@ -162,6 +162,32 @@ void ToFoldedCase(const char16_t* aIn, char16_t* aOut, uint32_t aLen) {
}
}
uint32_t ToNaked(uint32_t aChar) {
if (IS_ASCII(aChar)) {
return aChar;
}
return mozilla::unicode::GetNaked(aChar);
}
void ToNaked(nsAString& aString) {
char16_t* buf = aString.BeginWriting();
ToNaked(buf, buf, aString.Length());
}
void ToNaked(const char16_t* aIn, char16_t* aOut, uint32_t aLen) {
for (uint32_t i = 0; i < aLen; i++) {
uint32_t ch = aIn[i];
if (i < aLen - 1 && NS_IS_SURROGATE_PAIR(ch, aIn[i + 1])) {
ch = mozilla::unicode::GetNaked(SURROGATE_TO_UCS4(ch, aIn[i + 1]));
NS_ASSERTION(!IS_IN_BMP(ch), "stripping crossed BMP/SMP boundary!");
aOut[i++] = H_SURROGATE(ch);
aOut[i] = L_SURROGATE(ch);
continue;
}
aOut[i] = ToNaked(ch);
}
}
int32_t nsCaseInsensitiveStringComparator::operator()(const char16_t* lhs,
const char16_t* rhs,
uint32_t lLength,

View File

@ -56,6 +56,10 @@ uint32_t ToFoldedCase(uint32_t aChar);
void ToFoldedCase(nsAString& aString);
void ToFoldedCase(const char16_t* aIn, char16_t* aOut, uint32_t aLen);
uint32_t ToNaked(uint32_t aChar);
void ToNaked(nsAString& aString);
void ToNaked(const char16_t* aIn, char16_t* aOut, uint32_t aLen);
class nsCaseInsensitiveStringComparator : public nsStringComparator {
public:
nsCaseInsensitiveStringComparator() = default;

View File

@ -8,8 +8,12 @@
#include "nsUnicodePropertyData.cpp"
#include "mozilla/ArrayUtils.h"
#include "mozilla/HashTable.h"
#include "nsCharTraits.h"
#include "unicode/uchar.h"
#include "unicode/unorm2.h"
#define UNICODE_BMP_LIMIT 0x10000
#define UNICODE_LIMIT 0x110000
@ -305,6 +309,80 @@ uint32_t CountGraphemeClusters(const char16_t* aText, uint32_t aLength) {
return result;
}
uint32_t GetNaked(uint32_t aCh) {
using namespace mozilla;
static const UNormalizer2* normalizer;
static HashMap<uint32_t, uint32_t> nakedCharCache;
HashMap<uint32_t, uint32_t>::Ptr entry = nakedCharCache.lookup(aCh);
if (entry.found()) {
return entry->value();
}
UErrorCode error = U_ZERO_ERROR;
if (!normalizer) {
normalizer = unorm2_getNFDInstance(&error);
if (U_FAILURE(error)) {
return aCh;
}
}
static const size_t MAX_DECOMPOSITION_SIZE = 16;
UChar decomposition[MAX_DECOMPOSITION_SIZE];
UChar* combiners;
int32_t decompositionLen;
uint32_t baseChar, nextChar;
decompositionLen = unorm2_getDecomposition(normalizer, aCh, decomposition,
MAX_DECOMPOSITION_SIZE, &error);
if (decompositionLen < 1) {
// The character does not decompose.
return aCh;
}
if (u_getIntPropertyValue(aCh, UCHAR_GENERAL_CATEGORY) & U_GC_M_MASK) {
// The character is itself a combining character, and we don't want to use
// its decomposition into multiple combining characters.
baseChar = aCh;
goto cache;
}
if (NS_IS_HIGH_SURROGATE(decomposition[0])) {
baseChar = SURROGATE_TO_UCS4(decomposition[0], decomposition[1]);
combiners = decomposition + 2;
} else {
baseChar = decomposition[0];
combiners = decomposition + 1;
}
if (IS_IN_BMP(baseChar) != IS_IN_BMP(aCh)) {
// Mappings that would change the length of a UTF-16 string are not
// currently supported.
baseChar = aCh;
goto cache;
}
if (decompositionLen > 1) {
if (NS_IS_HIGH_SURROGATE(combiners[0])) {
nextChar = SURROGATE_TO_UCS4(combiners[0], combiners[1]);
} else {
nextChar = combiners[0];
}
if (u_getCombiningClass(nextChar) == 0) {
// Hangul syllables decompose but do not actually have diacritics.
baseChar = aCh;
}
}
cache:
if (!nakedCharCache.putNew(aCh, baseChar)) {
// We're out of memory, so delete the cache to free some up.
nakedCharCache.clearAndCompact();
}
return baseChar;
}
} // end namespace unicode
} // end namespace mozilla

View File

@ -229,6 +229,9 @@ class ClusterIterator {
// Count the number of grapheme clusters in the given string
uint32_t CountGraphemeClusters(const char16_t* aText, uint32_t aLength);
// Remove diacritics from a character
uint32_t GetNaked(uint32_t aCh);
// A simple reverse iterator for a string of char16_t codepoints that
// advances by Unicode grapheme clusters
class ClusterReverseIterator {

View File

@ -236,6 +236,7 @@ pref("accessibility.typeaheadfind.flashBar", 1);
pref("accessibility.typeaheadfind.linksonly", false);
pref("accessibility.typeaheadfind.casesensitive", 0);
pref("accessibility.browsewithcaret_shortcut.enabled", false);
pref("findbar.matchdiacritics", 0);
// Whether the character encoding menu is under the main Firefox button. This
// preference is a string so that localizers can alter it.

View File

@ -332,6 +332,7 @@ class GeckoViewContent extends GeckoViewModule {
finder.caseSensitive = !!aData.matchCase;
finder.entireWord = !!aData.wholeWord;
finder.matchDiacritics = !!aData.matchDiacritics;
finder.addResultListener(this._finderListener);
const drawOutline =

View File

@ -773,6 +773,11 @@ pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
pref("findbar.highlightAll", false);
pref("findbar.entireword", false);
pref("findbar.iteratorTimeout", 100);
// matchdiacritics: controls the find bar's diacritic matching
// 0 - "never" (ignore diacritics)
// 1 - "always" (match diacritics)
// other - "auto" (match diacritics if input has diacritics, ignore otherwise)
pref("findbar.matchdiacritics", 0);
// use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
pref("gfx.use_text_smoothing_setting", false);

View File

@ -28,6 +28,10 @@ class FinderChild extends JSWindowActorChild {
this.finder.caseSensitive = data.caseSensitive;
break;
case "Finder:MatchDiacritics":
this.finder.matchDiacritics = data.matchDiacritics;
break;
case "Finder:EntireWord":
this.finder.entireWord = data.entireWord;
break;

View File

@ -51,6 +51,7 @@ class FindContent {
* @param {string} queryphrase - the text to search for.
* @param {boolean} caseSensitive - whether to use case sensitive matches.
* @param {boolean} includeRangeData - whether to collect and return range data.
* @param {boolean} matchDiacritics - whether diacritics must match.
* @param {boolean} searchString - whether to collect and return rect data.
*
* @returns {object} that includes:
@ -66,6 +67,7 @@ class FindContent {
entireWord,
includeRangeData,
includeRectData,
matchDiacritics,
} = params;
this.iterator.reset();
@ -77,6 +79,7 @@ class FindContent {
entireWord: !!entireWord,
finder: this.finder,
listener: this.finder,
matchDiacritics: !!matchDiacritics,
useSubFrames: false,
});

View File

@ -60,7 +60,10 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFind)
NS_IMPL_CYCLE_COLLECTION(nsFind)
nsFind::nsFind()
: mFindBackward(false), mCaseSensitive(false), mWordBreaker(nullptr) {}
: mFindBackward(false),
mCaseSensitive(false),
mMatchDiacritics(false),
mWordBreaker(nullptr) {}
nsFind::~nsFind() = default;
@ -396,6 +399,22 @@ nsFind::SetEntireWord(bool aEntireWord) {
return NS_OK;
}
NS_IMETHODIMP
nsFind::GetMatchDiacritics(bool* aMatchDiacritics) {
if (!aMatchDiacritics) {
return NS_ERROR_NULL_POINTER;
}
*aMatchDiacritics = mMatchDiacritics;
return NS_OK;
}
NS_IMETHODIMP
nsFind::SetMatchDiacritics(bool aMatchDiacritics) {
mMatchDiacritics = aMatchDiacritics;
return NS_OK;
}
// Here begins the find code. A ten-thousand-foot view of how it works: Find
// needs to be able to compare across inline (but not block) nodes, e.g. find
// for "abc" should match a<b>b</b>c. So after we've searched a node, we're not
@ -506,6 +525,9 @@ nsFind::Find(const nsAString& aPatText, nsRange* aSearchRange,
if (!mCaseSensitive) {
ToFoldedCase(patAutoStr);
}
if (!mMatchDiacritics) {
ToNaked(patAutoStr);
}
// Ignore soft hyphens in the pattern
static const char kShy[] = {char(CH_SHY), 0};
@ -684,8 +706,13 @@ nsFind::Find(const nsAString& aPatText, nsRange* aSearchRange,
}
if (!inWhitespace && IsSpace(patc)) {
inWhitespace = true;
} else if (!inWhitespace && !mCaseSensitive) {
c = ToFoldedCase(c);
} else if (!inWhitespace) {
if (!mCaseSensitive) {
c = ToFoldedCase(c);
}
if (!mMatchDiacritics) {
c = ToNaked(c);
}
}
if (c == CH_SHY) {

View File

@ -42,6 +42,7 @@ class nsFind : public nsIFind {
// Parameters set from the interface:
bool mFindBackward;
bool mCaseSensitive;
bool mMatchDiacritics;
// Use "find entire words" mode by setting to a word breaker or null, to
// disable "entire words" mode.

View File

@ -79,3 +79,13 @@ NS_IMETHODIMP nsFindService::SetMatchCase(bool aMatchCase) {
mMatchCase = aMatchCase;
return NS_OK;
}
NS_IMETHODIMP nsFindService::GetMatchDiacritics(bool* aMatchDiacritics) {
NS_ENSURE_ARG_POINTER(aMatchDiacritics);
*aMatchDiacritics = mMatchDiacritics;
return NS_OK;
}
NS_IMETHODIMP nsFindService::SetMatchDiacritics(bool aMatchDiacritics) {
mMatchDiacritics = aMatchDiacritics;
return NS_OK;
}

View File

@ -40,4 +40,5 @@ class nsFindService : public nsIFindService {
bool mWrapFind;
bool mEntireWord;
bool mMatchCase;
bool mMatchDiacritics;
};

View File

@ -15,6 +15,7 @@ interface nsIFind : nsISupports
attribute boolean findBackwards;
attribute boolean caseSensitive;
attribute boolean entireWord;
attribute boolean matchDiacritics;
/**
* Find some text in the current context. The implementation is

View File

@ -22,5 +22,6 @@ interface nsIFindService : nsISupports
attribute boolean wrapFind;
attribute boolean entireWord;
attribute boolean matchCase;
attribute boolean matchDiacritics;
};

View File

@ -79,6 +79,13 @@ interface nsIWebBrowserFind : nsISupports
*/
attribute boolean matchCase;
/**
* matchDiacritics
*
* Whether to match diacritics when searching. Default is false.
*/
attribute boolean matchDiacritics;
/**
* searchFrames
*

View File

@ -50,6 +50,7 @@ nsWebBrowserFind::nsWebBrowserFind()
mWrapFind(false),
mEntireWord(false),
mMatchCase(false),
mMatchDiacritics(false),
mSearchSubFrames(true),
mSearchParentFrames(true) {}
@ -286,6 +287,19 @@ nsWebBrowserFind::SetMatchCase(bool aMatchCase) {
return NS_OK;
}
NS_IMETHODIMP
nsWebBrowserFind::GetMatchDiacritics(bool* aMatchDiacritics) {
NS_ENSURE_ARG_POINTER(aMatchDiacritics);
*aMatchDiacritics = mMatchDiacritics;
return NS_OK;
}
NS_IMETHODIMP
nsWebBrowserFind::SetMatchDiacritics(bool aMatchDiacritics) {
mMatchDiacritics = aMatchDiacritics;
return NS_OK;
}
void nsWebBrowserFind::SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow,
nsRange* aRange) {
RefPtr<Document> doc = aWindow->GetDoc();
@ -622,6 +636,7 @@ nsresult nsWebBrowserFind::SearchInFrame(nsPIDOMWindowOuter* aWindow,
NS_ENSURE_SUCCESS(rv, rv);
(void)find->SetCaseSensitive(mMatchCase);
(void)find->SetMatchDiacritics(mMatchDiacritics);
(void)find->SetFindBackwards(mFindBackwards);
(void)find->SetEntireWord(mEntireWord);

View File

@ -79,6 +79,7 @@ class nsWebBrowserFind : public nsIWebBrowserFind,
bool mWrapFind;
bool mEntireWord;
bool mMatchCase;
bool mMatchDiacritics;
bool mSearchSubFrames;
bool mSearchParentFrames;

View File

@ -70,6 +70,7 @@ interface nsITypeAheadFind : nsISupports
readonly attribute AString searchString;
// Most recent search string
attribute boolean caseSensitive; // Searches are case sensitive
attribute boolean matchDiacritics; // Searches preserve diacritics
attribute boolean entireWord; // Search for whole words only
readonly attribute Element foundLink;
// Most recent elem found, if a link

View File

@ -79,7 +79,8 @@ nsTypeAheadFind::nsTypeAheadFind()
mLastFindLength(0),
mIsSoundInitialized(false),
mCaseSensitive(false),
mEntireWord(false) {}
mEntireWord(false),
mMatchDiacritics(false) {}
nsTypeAheadFind::~nsTypeAheadFind() {
nsCOMPtr<nsIPrefBranch> prefInternal(
@ -204,6 +205,24 @@ nsTypeAheadFind::GetEntireWord(bool* isEntireWord) {
return NS_OK;
}
NS_IMETHODIMP
nsTypeAheadFind::SetMatchDiacritics(bool matchDiacritics) {
mMatchDiacritics = matchDiacritics;
if (mFind) {
mFind->SetMatchDiacritics(mMatchDiacritics);
}
return NS_OK;
}
NS_IMETHODIMP
nsTypeAheadFind::GetMatchDiacritics(bool* matchDiacritics) {
*matchDiacritics = mMatchDiacritics;
return NS_OK;
}
NS_IMETHODIMP
nsTypeAheadFind::SetDocShell(nsIDocShell* aDocShell) {
mDocShell = do_GetWeakReference(aDocShell);

View File

@ -118,6 +118,7 @@ class nsTypeAheadFind : public nsITypeAheadFind,
bool mCaseSensitive;
bool mEntireWord;
bool mMatchDiacritics;
bool EnsureFind() {
if (mFind) {
@ -131,6 +132,7 @@ class nsTypeAheadFind : public nsITypeAheadFind,
mFind->SetCaseSensitive(mCaseSensitive);
mFind->SetEntireWord(mEntireWord);
mFind->SetMatchDiacritics(mMatchDiacritics);
return true;
}

View File

@ -1,6 +1,7 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=202251
https://bugzilla.mozilla.org/show_bug.cgi?id=450048
https://bugzilla.mozilla.org/show_bug.cgi?id=969980
https://bugzilla.mozilla.org/show_bug.cgi?id=1589786
@ -13,7 +14,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1589786
<script type="application/javascript">
/** Test for Bug 450048 **/
SimpleTest.waitForExplicitFinish();
async function runTests() {
@ -47,15 +47,20 @@ async function runTests() {
rf.findBackwards = false;
rf.caseSensitive = false;
rf.matchDiacritics = false;
searchValue = "TexT";
retRange = rf.Find(searchValue, searchRange, startPt, endPt);
ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");
searchValue = "λόγος";
searchValue = "λογος";
retRange = rf.Find(searchValue, searchRange, startPt, endPt);
ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");
searchValue = "유";
retRange = rf.Find(searchValue, searchRange, startPt, endPt);
ok(!retRange, "\"" + searchValue + "\" found (not caseSensitive)");
searchValue = "degrees k";
retRange = rf.Find(searchValue, searchRange, startPt, endPt);
ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");
@ -64,9 +69,15 @@ async function runTests() {
retRange = rf.Find(searchValue, searchRange, startPt, endPt);
ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)");
rf.matchDiacritics = true;
searchValue = "λογος";
retRange = rf.Find(searchValue, searchRange, startPt, endPt);
ok(!retRange, "\"" + searchValue + "\" found (not caseSensitive)");
rf.caseSensitive = true;
// searchValue = "TexT";
searchValue = "TexT";
retRange = rf.Find(searchValue, searchRange, startPt, endPt);
ok(!retRange, "\"" + searchValue + "\" found (caseSensitive)");
@ -267,6 +278,7 @@ async function runTests() {
<p id="nullcharsnative">native null&#0;</p>
<p id="nullcharsinjected"></p>
<p id="greek">ΛΌΓΟΣ</p>
<p id="korean"></p>
<p id="kelvin">degrees &#x212A;</p>
<p id="deseret">𐐐𐐯𐑊𐐬 𐐶𐐯𐑉𐑊𐐼!</p>
<div id="content" style="display: none">

View File

@ -60,6 +60,7 @@
await testFind();
await testFindAgain();
await testCaseSensitivity();
await testDiacriticMatching();
await testHighlight();
}
@ -143,6 +144,24 @@
await ContentTask.spawn(gBrowser, null, () => content.getSelection().removeAllRanges());
}
async function testDiacriticMatching() {
info("Testing normal diacritic matching.");
let promise = once(gFindBar, "finddiacriticmatchingchange", false);
let matchDiacriticsCheckbox = gFindBar.getElement("find-match-diacritics");
matchDiacriticsCheckbox.click();
let e = await promise;
ok(e.detail.matchDiacritics, "find should match diacritics");
// Toggle it back to the original setting.
matchDiacriticsCheckbox.click();
// Changing diacritic matching does the search so clear the selected text
// before the next test.
await ContentTask.spawn(gBrowser, null, () => content.getSelection().removeAllRanges());
}
async function testHighlight() {
info("Testing find with highlight all.");
// Update the find state so the highlight button is clickable.

View File

@ -28,7 +28,7 @@
const SAMPLE_URL = "http://www.mozilla.org/";
const SAMPLE_TEXT = "Some text in a text field.";
const SEARCH_TEXT = "Text Test";
const SEARCH_TEXT = "Text Test (δοκιμή)";
const NOT_FOUND_TEXT = "This text is not on the page."
const ITERATOR_TIMEOUT = gPrefsvc.getIntPref("findbar.iteratorTimeout");
@ -88,7 +88,7 @@
await new Promise(resolve => setTimeout(resolve, 1000));
let promise = BrowserTestUtils.browserLoaded(gBrowser);
BrowserTestUtils.loadURI(gBrowser, "data:text/html,<h2 id='h2'>" + SEARCH_TEXT +
BrowserTestUtils.loadURI(gBrowser, "data:text/html;charset=utf-8,<h2 id='h2'>" + SEARCH_TEXT +
"</h2><h2><a href='" + SAMPLE_URL + "'>Link Test</a></h2><input id='text' type='text' value='" +
SAMPLE_TEXT + "'></input><input id='button' type='button'></input><img id='img' width='50' height='50'/>",
{ triggeringPrincipal: window.document.nodePrincipal });
@ -735,6 +735,24 @@
ok(gFindBar.hidden, "Successful Find Again leaves the find bar closed.");
}
async function testToggleDiacriticMatching() {
await openFindbar();
let promise = promiseFindResult();
await enterStringIntoFindField("δοκιμη", false);
let result = await promise;
is(result.result, Ci.nsITypeAheadFind.FIND_FOUND, "Text should be found");
await new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT + 20));
promise = promiseFindResult();
let check = gFindBar.getElement("find-match-diacritics");
check.click();
result = await promise;
is(result.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "Text should NOT be found");
check.click();
gFindBar.close(true);
}
async function testToggleEntireWord() {
await openFindbar();
let promise = promiseFindResult();

View File

@ -30,6 +30,7 @@
]);
const PREFS_TO_OBSERVE_INT = new Map([
["typeAheadCaseSensitive", "accessibility.typeaheadfind.casesensitive"],
["matchDiacritics", "findbar.matchdiacritics"],
]);
const PREFS_TO_OBSERVE_ALL = new Map([
...PREFS_TO_OBSERVE_BOOL,
@ -72,9 +73,12 @@
data-l10n-id="findbar-highlight-all2" oncommand="toggleHighlight(this.checked);" type="checkbox" />
<toolbarbutton anonid="find-case-sensitive" class="findbar-case-sensitive findbar-button tabbable"
data-l10n-id="findbar-case-sensitive" oncommand="_setCaseSensitivity(this.checked ? 1 : 0);" type="checkbox" />
<toolbarbutton anonid="find-match-diacritics" class="findbar-match-diacritics findbar-button tabbable"
data-l10n-id="findbar-match-diacritics" oncommand="_setDiacriticMatching(this.checked ? 1 : 0);" type="checkbox" />
<toolbarbutton anonid="find-entire-word" class="findbar-entire-word findbar-button tabbable"
data-l10n-id="findbar-entire-word" oncommand="toggleEntireWord(this.checked);" type="checkbox" />
<label anonid="match-case-status" class="findbar-find-fast" />
<label anonid="match-diacritics-status" class="findbar-find-fast" />
<label anonid="entire-word-status" class="findbar-find-fast" />
<label anonid="found-matches" class="findbar-find-fast found-matches" hidden="true" />
<image anonid="find-status-icon" class="findbar-find-fast find-status-icon" />
@ -390,6 +394,9 @@
case "findbar.highlightAll":
this.toggleHighlight(prefsvc.getBoolPref(prefName), true);
break;
case "findbar.matchdiacritics":
this._setDiacriticMatching(prefsvc.getIntPref(prefName));
break;
case "findbar.modalHighlight":
this._useModalHighlight = prefsvc.getBoolPref(prefName);
if (this.browser.finder) {
@ -598,6 +605,55 @@
this._dispatchFindEvent("casesensitivitychange");
}
/**
* Updates the diacritic-matching mode of the findbar and its UI.
*
* @param {String} [str] The string for which diacritic matching might be
* turned on. This is only used when diacritic
* matching is in auto mode, see
* `_shouldMatchDiacritics`. The default value for
* this parameter is the find-field value.
* @see _shouldMatchDiacritics.
*/
_updateDiacriticMatching(str) {
let val = str || this._findField.value;
let matchDiacritics = this._shouldMatchDiacritics(val);
let checkbox = this.getElement("find-match-diacritics");
let statusLabel = this.getElement("match-diacritics-status");
checkbox.checked = matchDiacritics;
statusLabel.value = matchDiacritics ? this._matchDiacriticsStr : "";
// 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 ||
(this._matchDiacritics != 0 && this._matchDiacritics != 1);
checkbox.hidden = hideCheckbox;
statusLabel.hidden = !hideCheckbox;
this.browser.finder.matchDiacritics = matchDiacritics;
}
/**
* Sets the findbar diacritic-matching mode
* @param {Number} diacriticMatching 0 - ignore diacritics,
* 1 - match diacritics,
* 2 - auto = match diacritics if the
* matching string contains
* diacritics.
* @see _shouldMatchDiacritics
*/
_setDiacriticMatching(diacriticMatching) {
this._matchDiacritics = diacriticMatching;
this._updateDiacriticMatching();
this._findFailedString = null;
this._find();
this._dispatchFindEvent("diacriticmatchingchange");
}
/**
* Updates the entire-word mode of the findbar and its UI.
*/
@ -659,6 +715,7 @@
this._fastFindStr = bundle.GetStringFromName("FastFind");
this._fastFindLinksStr = bundle.GetStringFromName("FastFindLinks");
this._caseSensitiveStr = bundle.GetStringFromName("CaseSensitive");
this._matchDiacriticsStr = bundle.GetStringFromName("MatchDiacritics");
this._entireWordStr = bundle.GetStringFromName("EntireWord");
}
@ -783,6 +840,17 @@
return str != str.toLowerCase();
}
_shouldMatchDiacritics(str) {
if (this._matchDiacritics == 0) {
return false;
}
if (this._matchDiacritics == 1) {
return true;
}
return str != str.normalize("NFD");
}
onMouseUp() {
if (!this.hidden && this.findMode != this.FIND_NORMAL) {
this.close();
@ -884,6 +952,7 @@
).hidden = showMinimalUI;
foundMatches.hidden = showMinimalUI || !foundMatches.value;
this._updateCaseSensitivity();
this._updateDiacriticMatching();
this._setEntireWord();
this._setHighlightAll();
@ -933,6 +1002,7 @@
this._enableFindButtons(val);
this._updateCaseSensitivity(val);
this._updateDiacriticMatching(val);
this._setEntireWord();
this.browser.finder.fastFind(
@ -1026,6 +1096,7 @@
event.initCustomEvent("find" + type, true, true, {
query: this._findField.value,
caseSensitive: !!this._typeAheadCaseSensitive,
matchDiacritics: !!this._matchDiacritics,
entireWord: this._entireWord,
highlightAll: this._highlightAll,
findPrevious,

View File

@ -10,6 +10,7 @@ NormalFind=Find in page
FastFind=Quick find
FastFindLinks=Quick find (links only)
CaseSensitive=(Case sensitive)
MatchDiacritics=(Matching diacritics)
EntireWord=(Whole words only)
# LOCALIZATION NOTE (FoundMatches): Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals

View File

@ -25,6 +25,11 @@ findbar-case-sensitive =
.accesskey = C
.tooltiptext = Search with case sensitivity
findbar-match-diacritics =
.label = Match Diacritics
.accesskey = D
.tooltiptext = Distinguish between accented letters and their base letters (for example, when searching for “resume”, “résumé” will not be matched)
findbar-entire-word =
.label = Whole Words
.accesskey = W

View File

@ -170,6 +170,14 @@ Finder.prototype = {
this.iterator.reset();
},
set matchDiacritics(aMatchDiacritics) {
if (this._fastFind.matchDiacritics === aMatchDiacritics) {
return;
}
this._fastFind.matchDiacritics = aMatchDiacritics;
this.iterator.reset();
},
set entireWord(aEntireWord) {
if (this._fastFind.entireWord === aEntireWord) {
return;
@ -341,6 +349,7 @@ Finder.prototype = {
caseSensitive: this._fastFind.caseSensitive,
entireWord: this._fastFind.entireWord,
linksOnly: aArgs.linksOnly,
matchDiacritics: this._fastFind.matchDiacritics,
word: aArgs.searchString,
useSubFrames: aArgs.useSubFrames,
})
@ -589,6 +598,7 @@ Finder.prototype = {
caseSensitive: this._fastFind.caseSensitive,
entireWord: this._fastFind.entireWord,
linksOnly: aLinksOnly,
matchDiacritics: this._fastFind.matchDiacritics,
word: aWord,
useSubFrames: aUseSubFrames,
};

View File

@ -284,6 +284,7 @@ FinderHighlighter.prototype = {
word,
finder: this.finder,
listener: this,
matchDiacritics: this.finder._fastFind.matchDiacritics,
useCache: true,
useSubFrames,
window,

View File

@ -88,6 +88,8 @@ FinderIterator.prototype = {
* - onIteratorReset();
* - onIteratorRestart({Object} iterParams);
* - onIteratorStart({Object} iterParams);
* @param {Boolean} options.matchDiacritics Whether to search in
* diacritic-matching mode
* @param {Boolean} [options.useCache] Whether to allow results already
* present in the cache or demand fresh.
* Optional, defaults to `false`.
@ -104,6 +106,7 @@ FinderIterator.prototype = {
limit,
linksOnly,
listener,
matchDiacritics,
useCache,
word,
useSubFrames,
@ -132,6 +135,9 @@ FinderIterator.prototype = {
if (typeof entireWord != "boolean") {
throw new Error("Missing required option 'entireWord'");
}
if (typeof matchDiacritics != "boolean") {
throw new Error("Missing required option 'matchDiacritics'");
}
if (!finder) {
throw new Error("Missing required option 'finder'");
}
@ -158,6 +164,7 @@ FinderIterator.prototype = {
caseSensitive,
entireWord,
linksOnly,
matchDiacritics,
useCache,
window,
word,
@ -304,6 +311,8 @@ FinderIterator.prototype = {
* @param {Boolean} options.entireWord Whether to search in entire-word mode
* @param {Boolean} options.linksOnly Whether to search for the word to be
* present in links only
* @param {Boolean} options.matchDiacritics Whether to search in
* diacritic-matching mode
* @param {String} options.word The word being searched for
* @param (Boolean) options.useSubFrames Whether to search subframes
* @return {Boolean}
@ -312,6 +321,7 @@ FinderIterator.prototype = {
caseSensitive,
entireWord,
linksOnly,
matchDiacritics,
word,
useSubFrames,
}) {
@ -320,6 +330,7 @@ FinderIterator.prototype = {
this._currentParams.caseSensitive === caseSensitive &&
this._currentParams.entireWord === entireWord &&
this._currentParams.linksOnly === linksOnly &&
this._currentParams.matchDiacritics === matchDiacritics &&
this._currentParams.word == word &&
this._currentParams.useSubFrames == useSubFrames
);
@ -374,6 +385,8 @@ FinderIterator.prototype = {
* @param {Boolean} options.entireWord Whether to search in entire-word mode
* @param {Boolean} options.linksOnly Whether to search for the word to be
* present in links only
* @param {Boolean} options.matchDiacritics Whether to search in
* diacritic-matching mode
* @param {Boolean} options.useCache Whether the consumer wants to use the
* cached previous result at all
* @param {String} options.word The word being searched for
@ -383,6 +396,7 @@ FinderIterator.prototype = {
caseSensitive,
entireWord,
linksOnly,
matchDiacritics,
useCache,
word,
}) {
@ -392,6 +406,7 @@ FinderIterator.prototype = {
caseSensitive,
entireWord,
linksOnly,
matchDiacritics,
word,
}) &&
this._previousRanges.length
@ -415,6 +430,7 @@ FinderIterator.prototype = {
paramSet1.caseSensitive === paramSet2.caseSensitive &&
paramSet1.entireWord === paramSet2.entireWord &&
paramSet1.linksOnly === paramSet2.linksOnly &&
paramSet1.matchDiacritics === paramSet2.matchDiacritics &&
paramSet1.window === paramSet2.window &&
paramSet1.useSubFrames === paramSet2.useSubFrames &&
NLP.levenshtein(paramSet1.word, paramSet2.word) <= allowDistance
@ -624,11 +640,16 @@ FinderIterator.prototype = {
* sensitive mode
* @param {Boolean} options.entireWord Whether to search in entire-word
* mode
* @param {Boolean} options.matchDiacritics Whether to search in
* diacritic-matching mode
* @param {String} options.word The word to search for
* @param {nsIDOMWindow} window The window to search in
* @yield {Range}
*/
*_iterateDocument({ caseSensitive, entireWord, word }, window) {
*_iterateDocument(
{ caseSensitive, entireWord, matchDiacritics, word },
window
) {
let doc = window.document;
let body = doc.body || doc.documentElement;
@ -652,6 +673,7 @@ FinderIterator.prototype = {
.QueryInterface(Ci.nsIFind);
nsIFind.caseSensitive = caseSensitive;
nsIFind.entireWord = entireWord;
nsIFind.matchDiacritics = matchDiacritics;
while ((retRange = nsIFind.Find(word, searchRange, startPt, endPt))) {
yield retRange;

View File

@ -217,6 +217,12 @@ FinderParent.prototype = {
});
},
set matchDiacritics(aMatchDiacritics) {
this.sendMessageToAllContexts("Finder:MatchDiacritics", {
matchDiacritics: aMatchDiacritics,
});
},
async setSearchStringToSelection() {
return this.setToSelection("Finder:SetSearchStringToSelection", false);
},

View File

@ -58,6 +58,7 @@ add_task(async function test_start() {
Assert.equal(range.toString(), findText, "Text content should match");
},
},
matchDiacritics: false,
word: findText,
});
@ -88,6 +89,7 @@ add_task(async function test_subframes() {
Assert.equal(range.toString(), findText, "Text content should match");
},
},
matchDiacritics: false,
word: findText,
useSubFrames: true,
});
@ -119,6 +121,7 @@ add_task(async function test_valid_arguments() {
++count;
},
},
matchDiacritics: false,
word: findText,
});
@ -137,6 +140,7 @@ add_task(async function test_valid_arguments() {
++count;
},
},
matchDiacritics: false,
word: findText,
}),
/Missing required option 'caseSensitive'/,
@ -144,6 +148,23 @@ add_task(async function test_valid_arguments() {
);
finderIterator.reset();
Assert.throws(
() =>
finderIterator.start({
caseSensitive: false,
entireWord: false,
listener: {
onIteratorRangeFound(range) {
++count;
},
},
word: findText,
}),
/Missing required option 'matchDiacritics'/,
"Should throw when missing an argument"
);
finderIterator.reset();
Assert.throws(
() =>
finderIterator.start({
@ -153,6 +174,7 @@ add_task(async function test_valid_arguments() {
++count;
},
},
matchDiacritics: false,
word: findText,
}),
/Missing required option 'entireWord'/,
@ -170,6 +192,7 @@ add_task(async function test_valid_arguments() {
++count;
},
},
matchDiacritics: false,
word: findText,
}),
/Missing required option 'finder'/,
@ -183,6 +206,7 @@ add_task(async function test_valid_arguments() {
caseSensitive: true,
entireWord: false,
finder: gMockFinder,
matchDiacritics: false,
word: findText,
}),
/Missing valid, required option 'listener'/,
@ -201,6 +225,7 @@ add_task(async function test_valid_arguments() {
++count;
},
},
matchDiacritics: false,
}),
/Missing required option 'word'/,
"Should throw when missing an argument"
@ -225,6 +250,7 @@ add_task(async function test_stop() {
++count;
},
},
matchDiacritics: false,
word: findText,
});
@ -252,6 +278,7 @@ add_task(async function test_reset() {
++count;
},
},
matchDiacritics: false,
word: findText,
});
@ -294,10 +321,11 @@ add_task(async function test_parallel_starts() {
++count;
},
},
matchDiacritics: false,
word: findText,
});
await new Promise(resolve => gMockWindow.setTimeout(resolve, 120));
await new Promise(resolve => gMockWindow.setTimeout(resolve, 100));
Assert.ok(finderIterator.running, "We ought to be running here");
let count2 = 0;
@ -310,6 +338,7 @@ add_task(async function test_parallel_starts() {
++count2;
},
},
matchDiacritics: false,
word: findText,
});
@ -360,6 +389,7 @@ add_task(async function test_allowDistance() {
++count;
},
},
matchDiacritics: false,
word: findText,
});
@ -373,6 +403,7 @@ add_task(async function test_allowDistance() {
++count2;
},
},
matchDiacritics: false,
word: "gu",
});
@ -387,6 +418,7 @@ add_task(async function test_allowDistance() {
++count3;
},
},
matchDiacritics: false,
word: "gu",
});