mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
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:
parent
fc08c93083
commit
00867c4809
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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 =
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -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,9 +706,14 @@ nsFind::Find(const nsAString& aPatText, nsRange* aSearchRange,
|
||||
}
|
||||
if (!inWhitespace && IsSpace(patc)) {
|
||||
inWhitespace = true;
|
||||
} else if (!inWhitespace && !mCaseSensitive) {
|
||||
} else if (!inWhitespace) {
|
||||
if (!mCaseSensitive) {
|
||||
c = ToFoldedCase(c);
|
||||
}
|
||||
if (!mMatchDiacritics) {
|
||||
c = ToNaked(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == CH_SHY) {
|
||||
// ignore soft hyphens in the document
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -40,4 +40,5 @@ class nsFindService : public nsIFindService {
|
||||
bool mWrapFind;
|
||||
bool mEntireWord;
|
||||
bool mMatchCase;
|
||||
bool mMatchDiacritics;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -22,5 +22,6 @@ interface nsIFindService : nsISupports
|
||||
attribute boolean wrapFind;
|
||||
attribute boolean entireWord;
|
||||
attribute boolean matchCase;
|
||||
attribute boolean matchDiacritics;
|
||||
|
||||
};
|
||||
|
@ -79,6 +79,13 @@ interface nsIWebBrowserFind : nsISupports
|
||||
*/
|
||||
attribute boolean matchCase;
|
||||
|
||||
/**
|
||||
* matchDiacritics
|
||||
*
|
||||
* Whether to match diacritics when searching. Default is false.
|
||||
*/
|
||||
attribute boolean matchDiacritics;
|
||||
|
||||
/**
|
||||
* searchFrames
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -79,6 +79,7 @@ class nsWebBrowserFind : public nsIWebBrowserFind,
|
||||
bool mWrapFind;
|
||||
bool mEntireWord;
|
||||
bool mMatchCase;
|
||||
bool mMatchDiacritics;
|
||||
|
||||
bool mSearchSubFrames;
|
||||
bool mSearchParentFrames;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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�</p>
|
||||
<p id="nullcharsinjected"></p>
|
||||
<p id="greek">ΛΌΓΟΣ</p>
|
||||
<p id="korean">위</p>
|
||||
<p id="kelvin">degrees K</p>
|
||||
<p id="deseret">𐐐𐐯𐑊𐐬 𐐶𐐯𐑉𐑊𐐼!</p>
|
||||
<div id="content" style="display: none">
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -284,6 +284,7 @@ FinderHighlighter.prototype = {
|
||||
word,
|
||||
finder: this.finder,
|
||||
listener: this,
|
||||
matchDiacritics: this.finder._fastFind.matchDiacritics,
|
||||
useCache: true,
|
||||
useSubFrames,
|
||||
window,
|
||||
|
@ -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;
|
||||
|
@ -217,6 +217,12 @@ FinderParent.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
set matchDiacritics(aMatchDiacritics) {
|
||||
this.sendMessageToAllContexts("Finder:MatchDiacritics", {
|
||||
matchDiacritics: aMatchDiacritics,
|
||||
});
|
||||
},
|
||||
|
||||
async setSearchStringToSelection() {
|
||||
return this.setToSelection("Finder:SetSearchStringToSelection", false);
|
||||
},
|
||||
|
@ -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",
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user