Bug 338427 - Spellchecker should respect the langi attribute; r=ehsan

This commit is contained in:
arno renevier 2011-08-12 15:12:45 -04:00
parent 6c668d27a8
commit 56a37f401e
26 changed files with 411 additions and 106 deletions

View File

@ -589,6 +589,7 @@ function startTest() {
iframe = subwindow.document.getElementById("test-iframe");
textarea = subwindow.document.getElementById("test-textarea");
contenteditable = subwindow.document.getElementById("test-contenteditable");
contenteditable.focus(); // content editable needs to be focused to enable spellcheck
inputspell = subwindow.document.getElementById("test-input-spellcheck");
pagemenu = subwindow.document.getElementById("test-pagemenu");

View File

@ -944,6 +944,31 @@ public:
*/
nsIContent* GetEditingHost();
/**
* Determing language. Look at the nearest ancestor element that has a lang
* attribute in the XML namespace or is an HTML element and has a lang in
* no namespace attribute.
*/
void GetLang(nsAString& aResult) const {
for (const nsIContent* content = this; content; content = content->GetParent()) {
if (content->GetAttrCount() > 0) {
// xml:lang has precedence over lang on HTML elements (see
// XHTML1 section C.7).
PRBool hasAttr = content->GetAttr(kNameSpaceID_XML, nsGkAtoms::lang,
aResult);
if (!hasAttr && content->IsHTML()) {
hasAttr = content->GetAttr(kNameSpaceID_None, nsGkAtoms::lang,
aResult);
}
NS_ASSERTION(hasAttr || aResult.IsEmpty(),
"GetAttr that returns false should not make string non-empty");
if (hasAttr) {
return;
}
}
}
}
// Overloaded from nsINode
virtual already_AddRefed<nsIURI> GetBaseURI() const;

View File

@ -42,11 +42,15 @@
#include "nsEditorSpellCheck.h"
#include "nsStyleUtil.h"
#include "nsIContent.h"
#include "nsIDOMElement.h"
#include "nsITextServicesDocument.h"
#include "nsISpellChecker.h"
#include "nsISelection.h"
#include "nsIDOMRange.h"
#include "nsIEditor.h"
#include "nsIHTMLEditor.h"
#include "nsIComponentManager.h"
#include "nsServiceManagerUtils.h"
@ -54,6 +58,7 @@
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsITextServicesFilter.h"
#include "nsUnicharUtils.h"
#include "mozilla/Services.h"
#include "mozilla/Preferences.h"
@ -183,61 +188,9 @@ nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, PRBool aEnableSelection
rv = mSpellChecker->SetDocument(tsDoc, PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
// Tell the spellchecker what dictionary to use:
nsAdoptingString dictName =
Preferences::GetLocalizedString("spellchecker.dictionary");
if (dictName.IsEmpty())
{
// Prefs didn't give us a dictionary name, so just get the current
// locale and use that as the default dictionary name!
nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
mozilla::services::GetXULChromeRegistryService();
if (packageRegistry) {
nsCAutoString utf8DictName;
rv = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"),
utf8DictName);
AppendUTF8toUTF16(utf8DictName, dictName);
}
}
PRBool setDictionary = PR_FALSE;
if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) {
rv = SetCurrentDictionary(dictName.get());
// fall back to "en-US" if the current locale doesn't have a dictionary.
if (NS_FAILED(rv)) {
rv = SetCurrentDictionary(NS_LITERAL_STRING("en-US").get());
}
if (NS_SUCCEEDED(rv))
setDictionary = PR_TRUE;
}
// If there was no dictionary specified by spellchecker.dictionary and setting it to the
// locale dictionary didn't work, try to use the first dictionary we find. This helps when
// the first dictionary is installed
if (! setDictionary) {
nsTArray<nsString> dictList;
rv = mSpellChecker->GetDictionaryList(&dictList);
NS_ENSURE_SUCCESS(rv, rv);
if (dictList.Length() > 0) {
rv = SetCurrentDictionary(dictList[0].get());
if (NS_SUCCEEDED(rv))
SaveDefaultDictionary();
}
}
// If an error was thrown while checking the dictionary pref, just
// fail silently so that the spellchecker dialog is allowed to come
// up. The user can manually reset the language to their choice on
// the dialog if it is wrong.
DeleteSuggestedWordList();
// do not fail if UpdateCurrentDictionary fails because this method may
// succeed later.
UpdateCurrentDictionary(aEditor);
return NS_OK;
}
@ -439,14 +392,6 @@ nsEditorSpellCheck::UninitSpellChecker()
{
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
// we preserve the last selected language, but ignore errors so we continue
// to uninitialize
#ifdef DEBUG
nsresult rv =
#endif
SaveDefaultDictionary();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to set default dictionary");
// Cleanup - kill the spell checker
DeleteSuggestedWordList();
mDictionaryList.Clear();
@ -489,3 +434,124 @@ nsEditorSpellCheck::DeleteSuggestedWordList()
mSuggestedWordIndex = 0;
return NS_OK;
}
NS_IMETHODIMP
nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditor* aEditor)
{
nsresult rv;
// Tell the spellchecker what dictionary to use:
nsAutoString dictName;
// First, try to get language with html5 algorithm
nsAutoString editorLang;
nsCOMPtr<nsIContent> rootContent;
nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
if (htmlEditor) {
rootContent = htmlEditor->GetActiveEditingHost();
} else {
nsCOMPtr<nsIDOMElement> rootElement;
rv = aEditor->GetRootElement(getter_AddRefs(rootElement));
NS_ENSURE_SUCCESS(rv, rv);
rootContent = do_QueryInterface(rootElement);
}
NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE);
rootContent->GetLang(editorLang);
if (editorLang.IsEmpty()) {
nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
doc->GetContentLanguage(editorLang);
}
if (!editorLang.IsEmpty()) {
dictName.Assign(editorLang);
}
// otherwise, get language from preferences
if (dictName.IsEmpty()) {
dictName.Assign(Preferences::GetLocalizedString("spellchecker.dictionary"));
}
if (dictName.IsEmpty())
{
// Prefs didn't give us a dictionary name, so just get the current
// locale and use that as the default dictionary name!
nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
mozilla::services::GetXULChromeRegistryService();
if (packageRegistry) {
nsCAutoString utf8DictName;
rv = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"),
utf8DictName);
AppendUTF8toUTF16(utf8DictName, dictName);
}
}
SetCurrentDictionary(NS_LITERAL_STRING("").get());
if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) {
rv = SetCurrentDictionary(dictName.get());
if (NS_FAILED(rv)) {
// required dictionary was not available. Try to get a dictionary
// matching at least language part of dictName: If required dictionary is
// "aa-bb", we try "aa", then we try any available dictionary aa-XX
nsAutoString langCode;
PRInt32 dashIdx = dictName.FindChar('-');
if (dashIdx != -1) {
langCode.Assign(Substring(dictName, 0, dashIdx));
// try to use langCode
rv = SetCurrentDictionary(langCode.get());
} else {
langCode.Assign(dictName);
}
if (NS_FAILED(rv)) {
// loop over avaible dictionaries; if we find one with required
// language, use it
nsTArray<nsString> dictList;
rv = mSpellChecker->GetDictionaryList(&dictList);
NS_ENSURE_SUCCESS(rv, rv);
nsDefaultStringComparator comparator;
PRInt32 i, count = dictList.Length();
for (i = 0; i < count; i++) {
nsAutoString dictStr(dictList.ElementAt(i));
if (nsStyleUtil::DashMatchCompare(dictStr, langCode, comparator) &&
NS_SUCCEEDED(SetCurrentDictionary(dictStr.get()))) {
break;
}
}
}
}
}
// If we have not set dictionary, and the editable element doesn't have a
// lang attribute, we try to get a dictionary. First try, en-US. If it does
// not work, pick the first one.
if (editorLang.IsEmpty()) {
nsAutoString currentDictonary;
rv = mSpellChecker->GetCurrentDictionary(currentDictonary);
if (NS_FAILED(rv) || currentDictonary.IsEmpty()) {
rv = SetCurrentDictionary(NS_LITERAL_STRING("en-US").get());
if (NS_FAILED(rv)) {
nsTArray<nsString> dictList;
rv = mSpellChecker->GetDictionaryList(&dictList);
if (NS_SUCCEEDED(rv) && dictList.Length() > 0) {
SetCurrentDictionary(dictList[0].get());
}
}
}
}
// If an error was thrown while setting the dictionary, just
// fail silently so that the spellchecker dialog is allowed to come
// up. The user can manually reset the language to their choice on
// the dialog if it is wrong.
DeleteSuggestedWordList();
return NS_OK;
}

View File

@ -41,7 +41,7 @@
interface nsIEditor;
interface nsITextServicesFilter;
[scriptable, uuid(90c93610-c116-44ab-9793-62dccb9f43ce)]
[scriptable, uuid(803ff0dd-07f2-4438-b3a6-ab9c2fe4e1dd)]
interface nsIEditorSpellCheck : nsISupports
{
@ -188,4 +188,10 @@ interface nsIEditorSpellCheck : nsISupports
*/
boolean CheckCurrentWordNoSuggest(in wstring suggestedWord);
/**
* Update the dictionary in use to be sure it corresponds to what the editor
* needs.
*/
void UpdateCurrentDictionary(in nsIEditor editor);
};

View File

@ -42,6 +42,7 @@
#include "domstubs.idl"
interface nsIAtom;
interface nsIContent;
interface nsISupportsArray;
interface nsISelection;
interface nsIContentFilter;
@ -51,7 +52,7 @@ interface nsIContentFilter;
NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_EDITOR, 1)
%}
[scriptable, uuid(c964b8b0-e9e8-11df-9492-0800200c9a66)]
[scriptable, uuid(d58f35a7-c269-4292-b9aa-a79e200a7c99)]
interface nsIHTMLEditor : nsISupports
{
@ -613,5 +614,11 @@ interface nsIHTMLEditor : nsISupports
* Checks whether a BR node is visible to the user.
*/
boolean breakIsVisible(in nsIDOMNode aNode);
/**
* Get an active editor's editing host in DOM window. If this editor isn't
* active in the DOM window, this returns NULL.
*/
[noscript, notxpcom] nsIContent GetActiveEditingHost();
};

View File

@ -5311,3 +5311,12 @@ nsEditor::BeginKeypressHandling(nsIDOMNSEvent* aEvent)
mLastKeypressEventWasTrusted = isTrusted ? eTriTrue : eTriFalse;
}
}
void
nsEditor::OnFocus(nsIDOMEventTarget* aFocusEventTarget)
{
InitializeSelection(aFocusEventTarget);
if (mInlineSpellChecker) {
mInlineSpellChecker->UpdateCurrentDictionary();
}
}

View File

@ -719,6 +719,11 @@ public:
// nothing.
nsresult InitializeSelection(nsIDOMEventTarget* aFocusEventTarget);
// This method has to be called by nsEditorEventListener::Focus.
// All actions that have to be done when the editor is focused needs to be
// added here.
void OnFocus(nsIDOMEventTarget* aFocusEventTarget);
protected:
PRUint32 mModCount; // number of modifications (for undo/redo stack)

View File

@ -818,7 +818,7 @@ nsEditorEventListener::Focus(nsIDOMEvent* aEvent)
}
}
mEditor->InitializeSelection(target);
mEditor->OnFocus(target);
return NS_OK;
}

View File

@ -458,9 +458,6 @@ protected:
// @return If the editor has focus, this returns the focused node.
// Otherwise, returns null.
already_AddRefed<nsINode> GetFocusedNode();
// Get an active editor's editing host in DOM window. If this editor isn't
// active in the DOM window, this returns NULL.
nsIContent* GetActiveEditingHost();
// Return TRUE if aElement is a table-related elemet and caret was set
PRBool SetCaretInTableCell(nsIDOMElement* aElement);

View File

@ -46,7 +46,6 @@ function append(str) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var edit = document.getElementById("edit");
edit.focus();
var editor = getEditor();
var sel = editor.selection;
sel.selectAllChildren(edit);
@ -59,16 +58,19 @@ function append(str) {
function runTest() {
gMisspeltWords = ["haz", "cheezburger"];
is(isSpellingCheckOk(), true, "All misspellings before editing are accounted for.");
var edit = document.getElementById("edit");
append(" becaz I'm a lolcat!");
edit.focus();
SimpleTest.executeSoon(function() {
gMisspeltWords.push("becaz");
gMisspeltWords.push("lolcat");
is(isSpellingCheckOk(), true, "All misspellings after typing are accounted for.");
is(isSpellingCheckOk(), true, "All misspellings before editing are accounted for.");
SimpleTest.finish();
append(" becaz I'm a lolcat!");
SimpleTest.executeSoon(function() {
gMisspeltWords.push("becaz");
gMisspeltWords.push("lolcat");
is(isSpellingCheckOk(), true, "All misspellings after typing are accounted for.");
SimpleTest.finish();
});
});
}

View File

@ -43,7 +43,7 @@ interface nsISelection;
interface nsIEditor;
interface nsIEditorSpellCheck;
[scriptable, uuid(07be036a-2355-4a92-b150-5c9b7e9fdf2f)]
[scriptable, uuid(f456dda1-965d-470c-8c55-e51b38e45212)]
interface nsIInlineSpellChecker : nsISupports
{
@ -71,6 +71,7 @@ interface nsIInlineSpellChecker : nsISupports
void ignoreWord(in AString aWord);
void ignoreWords([array, size_is(aCount)] in wstring aWordsToIgnore, in unsigned long aCount);
void updateCurrentDictionary();
};
%{C++

View File

@ -142,7 +142,8 @@ public:
/**
* Tells the spellchecker to use a specific dictionary.
* @param aDictionary a string that is in the list returned
* by GetDictionaryList().
* by GetDictionaryList() or an empty string. If aDictionary is
* empty string, spellchecker will be disabled.
*/
NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary) = 0;
};

View File

@ -1384,6 +1384,9 @@ nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil,
PRBool isMisspelled;
aWordUtil.NormalizeWord(wordText);
rv = mSpellCheck->CheckCurrentWordNoSuggest(wordText.get(), &isMisspelled);
if (NS_FAILED(rv))
continue;
if (isMisspelled) {
// misspelled words count extra toward the max
wordsSinceTimeCheck += MISSPELLED_WORD_COUNT_PENALTY;
@ -1441,6 +1444,23 @@ mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus* aStatus)
nsCOMPtr<nsISelection> spellCheckSelection;
rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection));
NS_ENSURE_SUCCESS(rv, rv);
PRUnichar *currentDictionary = nsnull;
rv = mSpellCheck->GetCurrentDictionary(&currentDictionary);
if (NS_FAILED(rv)) {
// no active dictionary
PRInt32 count;
spellCheckSelection->GetRangeCount(&count);
for (PRInt32 index = count - 1; index >= 0; index--) {
nsCOMPtr<nsIDOMRange> checkRange;
spellCheckSelection->GetRangeAt(index, getter_AddRefs(checkRange));
if (checkRange) {
RemoveRange(spellCheckSelection, checkRange);
}
}
return NS_OK;
}
CleanupRangesInSelection(spellCheckSelection);
rv = aStatus->FinishInitOnEvent(wordUtil);
@ -1733,3 +1753,38 @@ nsresult mozInlineSpellChecker::KeyPress(nsIDOMEvent* aKeyEvent)
return NS_OK;
}
NS_IMETHODIMP mozInlineSpellChecker::UpdateCurrentDictionary()
{
if (!mSpellCheck) {
return NS_OK;
}
PRUnichar *previousDictionary = nsnull;
nsDependentString previousDictionaryStr;
if (NS_SUCCEEDED(mSpellCheck->GetCurrentDictionary(&previousDictionary))) {
previousDictionaryStr.Assign(previousDictionary);
}
nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
nsresult rv = mSpellCheck->UpdateCurrentDictionary(editor);
PRUnichar *currentDictionary = nsnull;
nsDependentString currentDictionaryStr;
if (NS_SUCCEEDED(mSpellCheck->GetCurrentDictionary(&currentDictionary))) {
currentDictionaryStr.Assign(currentDictionary);
}
if (!previousDictionary || !currentDictionary || !previousDictionaryStr.Equals(currentDictionaryStr)) {
rv = SpellCheckRange(nsnull);
}
if (previousDictionary) {
nsMemory::Free(previousDictionary);
}
if (currentDictionary) {
nsMemory::Free(currentDictionary);
}
return rv;
}

View File

@ -369,6 +369,12 @@ mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
nsresult rv;
nsCString *contractId;
if (aDictionary.IsEmpty()) {
mCurrentEngineContractId = nsnull;
mSpellCheckingEngine = nsnull;
return NS_OK;
}
if (!mDictionariesMap.Get(aDictionary, &contractId)){
NS_WARNING("Dictionary not found");
return NS_ERROR_NOT_AVAILABLE;

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<body>
<textarea spellcheck="false" lang="testing-XX">strangeimpossibleword</textarea>
</body>
</html>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<textarea lang="testing-XX">strangeimpossibleword</textarea>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html class="reftest-wait">
<script>
function init() {
var editor = document.getElementById('editor');
editor.addEventListener("focus", function() {
window.setTimeout(function() {
document.documentElement.className = '';
}, 0);
}, false);
editor.focus();
}
</script>
<body onload="init()">
<div id="editor" lang="testing-XX" contenteditable="true" spellcheck="false">strangeimpossibleword</div>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html class="reftest-wait">
<script>
function init() {
var editor = document.getElementById('editor');
editor.addEventListener("focus", function() {
window.setTimeout(function() {
document.documentElement.className = '';
}, 0);
}, false);
editor.focus();
}
</script>
<body onload="init()">
<div id="editor" lang="testing-XX" contenteditable="true">strangeimpossibleword</div>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html class="reftest-wait">
<script>
function init() {
var editor = document.getElementById('editor');
editor.setAttribute('lang', 'testing-XX');
editor.addEventListener("focus", function() {
window.setTimeout(function() {
document.documentElement.className = '';
}, 0);
}, false);
editor.focus();
}
</script>
<body onload="init()">
<textarea id="editor" spellcheck="false" lang="en-US">strangeimpossibleword</textarea>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html class="reftest-wait">
<script>
function init() {
var editor = document.getElementById('editor');
editor.setAttribute('lang', 'testing-XX');
editor.addEventListener("focus", function() {
window.setTimeout(function() {
document.documentElement.className = '';
}, 0);
}, false);
editor.focus();
}
</script>
<body onload="init()">
<textarea id="editor" lang="en-US">strangeimpossibleword</textarea>
</body>
</html>

View File

@ -64,3 +64,6 @@ fails-if(Android) != spellcheck-hyphen-multiple-invalid.html spellcheck-hyphen-m
!= selection_visibility_after_reframe-2.html selection_visibility_after_reframe-ref.html
!= selection_visibility_after_reframe-3.html selection_visibility_after_reframe-ref.html
== 672709.html 672709-ref.html
== 338427-1.html 338427-1-ref.html
skip-if(Android) == 338427-2.html 338427-2-ref.html
skip-if(Android) == 338427-3.html 338427-3-ref.html

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="dk">
<head>
<title>testing lang attribute</title>
<style>
div[lang="fr"] {
color: green;
}
div[lang="de"] > input {
color: blue;
}
div:not([lang]) {
color: yellow;
}
</style>
</head>
<body>
<div lang="fr">fr language</div>
<div lang="de">
<input value="de language">
</div>
<div>
dk language
</div>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="dk">
<head>
<title>testing lang attribute</title>
<style>
div:lang(fr) {
color: green;
}
input:lang(de) {
color: blue;
}
div:lang(dk) {
color: yellow;
}
</style>
</head>
<body>
<div lang="fr">fr language</div>
<div lang="de">
<input value="de language">
</div>
<div>
dk language
</div>
</body>
</html>

View File

@ -4,3 +4,4 @@
== unicode-media-query-media-type.html unicode-ref-print.html
== unicode-media-query-query.html unicode-ref-print.html
== unicode-pseudo-selector.html unicode-ref.html
== langattribute.html langattribute-ref.html

View File

@ -1195,29 +1195,6 @@ nsCSSRuleProcessor::GetWindowsThemeIdentifier()
}
#endif
// If we have a useful @lang, then aLang will end up nonempty.
static void GetLang(nsIContent* aContent, nsString& aLang)
{
for (nsIContent* content = aContent; content;
content = content->GetParent()) {
if (content->GetAttrCount() > 0) {
// xml:lang has precedence over lang on HTML elements (see
// XHTML1 section C.7).
PRBool hasAttr = content->GetAttr(kNameSpaceID_XML, nsGkAtoms::lang,
aLang);
if (!hasAttr && content->IsHTML()) {
hasAttr = content->GetAttr(kNameSpaceID_None, nsGkAtoms::lang,
aLang);
}
NS_ASSERTION(hasAttr || aLang.IsEmpty(),
"GetAttr that returns false should not make string non-empty");
if (hasAttr) {
return;
}
}
}
}
/* static */
nsEventStates
nsCSSRuleProcessor::GetContentState(Element* aElement)
@ -1697,7 +1674,7 @@ static PRBool SelectorMatches(Element* aElement,
// from the parent we have to be prepared to look at all parent
// nodes. The language itself is encoded in the LANG attribute.
nsAutoString language;
GetLang(aElement, language);
aElement->GetLang(language);
if (!language.IsEmpty()) {
if (!nsStyleUtil::DashMatchCompare(language,
nsDependentString(pseudoClass->u.mString),

View File

@ -131,8 +131,12 @@ InlineSpellChecker.prototype = {
return 0; // nothing to do
var spellchecker = this.mInlineSpellChecker.spellChecker;
if (! spellchecker.CheckCurrentWord(this.mMisspelling))
return 0; // word seems not misspelled after all (?)
try {
if (! spellchecker.CheckCurrentWord(this.mMisspelling))
return 0; // word seems not misspelled after all (?)
} catch(e) {
return 0;
}
this.mMenu = menu;
this.mSpellSuggestions = [];
@ -192,7 +196,10 @@ InlineSpellChecker.prototype = {
spellchecker.GetDictionaryList(o1, o2);
var list = o1.value;
var listcount = o2.value;
var curlang = spellchecker.GetCurrentDictionary();
var curlang = "";
try {
curlang = spellchecker.GetCurrentDictionary();
} catch(e) {}
var isoStrArray;
for (var i = 0; i < list.length; i ++) {