Skip to content
This repository has been archived by the owner on Aug 4, 2022. It is now read-only.

Commit

Permalink
Bug 338427 - Spellchecker should respect the langi attribute; r=ehsan
Browse files Browse the repository at this point in the history
  • Loading branch information
arenevier committed Aug 12, 2011
1 parent e1f9d00 commit 0d3f7a0
Show file tree
Hide file tree
Showing 26 changed files with 411 additions and 106 deletions.
1 change: 1 addition & 0 deletions browser/base/content/test/test_contextmenu.html
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@
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");

Expand Down
25 changes: 25 additions & 0 deletions content/base/public/nsIContent.h
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,31 @@ class nsIContent : public nsINode {
*/
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;

Expand Down
192 changes: 129 additions & 63 deletions editor/composer/src/nsEditorSpellCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,23 @@

#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"
#include "nsIChromeRegistry.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsITextServicesFilter.h"
#include "nsUnicharUtils.h"
#include "mozilla/Services.h"
#include "mozilla/Preferences.h"

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
8 changes: 7 additions & 1 deletion editor/idl/nsIEditorSpellCheck.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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
{

Expand Down Expand Up @@ -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);

};
9 changes: 8 additions & 1 deletion editor/idl/nsIHTMLEditor.idl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "domstubs.idl"

interface nsIAtom;
interface nsIContent;
interface nsISupportsArray;
interface nsISelection;
interface nsIContentFilter;
Expand All @@ -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
{
Expand Down Expand Up @@ -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();
};

9 changes: 9 additions & 0 deletions editor/libeditor/base/nsEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5311,3 +5311,12 @@ nsEditor::BeginKeypressHandling(nsIDOMNSEvent* aEvent)
mLastKeypressEventWasTrusted = isTrusted ? eTriTrue : eTriFalse;
}
}

void
nsEditor::OnFocus(nsIDOMEventTarget* aFocusEventTarget)
{
InitializeSelection(aFocusEventTarget);
if (mInlineSpellChecker) {
mInlineSpellChecker->UpdateCurrentDictionary();
}
}
5 changes: 5 additions & 0 deletions editor/libeditor/base/nsEditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,11 @@ class nsEditor : public nsIEditor,
// 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)
Expand Down
2 changes: 1 addition & 1 deletion editor/libeditor/base/nsEditorEventListener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ nsEditorEventListener::Focus(nsIDOMEvent* aEvent)
}
}

mEditor->InitializeSelection(target);
mEditor->OnFocus(target);
return NS_OK;
}

Expand Down
3 changes: 0 additions & 3 deletions editor/libeditor/html/nsHTMLEditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,6 @@ class nsHTMLEditor : public nsPlaintextEditor,
// @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);
Expand Down
18 changes: 10 additions & 8 deletions editor/libeditor/html/tests/test_bug484181.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");

var edit = document.getElementById("edit");
edit.focus();
var editor = getEditor();
var sel = editor.selection;
sel.selectAllChildren(edit);
Expand All @@ -59,16 +58,19 @@

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.");

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();
SimpleTest.finish();
});
});
}

Expand Down
Loading

0 comments on commit 0d3f7a0

Please sign in to comment.