bug 678842: remember spell check setting per site; r=ehsan
☠☠ backed out by 562229c22e97 ☠ ☠
authorarno renevier <arno@renevier.net>
Mon, 22 Aug 2011 19:25:29 -0400
changeset 75687 e57b659ee5dd47e38976ab1a200fe466fd539b30
parent 75686 ff19c78bded4fe275efdd9eb008619b8ee46f6cb
child 75688 713be47aa3f9fc9e5027645bfb3eebac281d586e
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersehsan
bugs678842
milestone9.0a1
bug 678842: remember spell check setting per site; r=ehsan
editor/composer/src/nsEditorSpellCheck.cpp
editor/composer/src/nsEditorSpellCheck.h
editor/composer/test/Makefile.in
editor/composer/test/bug678842_subframe.html
editor/composer/test/test_bug678842.html
editor/idl/nsIEditorSpellCheck.idl
extensions/spellcheck/src/mozInlineSpellChecker.cpp
layout/build/nsLayoutStatics.cpp
toolkit/components/contentprefs/nsContentPrefService.js
toolkit/content/InlineSpellChecker.jsm
--- a/editor/composer/src/nsEditorSpellCheck.cpp
+++ b/editor/composer/src/nsEditorSpellCheck.cpp
@@ -48,18 +48,23 @@
 #include "nsITextServicesDocument.h"
 #include "nsISpellChecker.h"
 #include "nsISelection.h"
 #include "nsIDOMRange.h"
 #include "nsIEditor.h"
 #include "nsIHTMLEditor.h"
 
 #include "nsIComponentManager.h"
+#include "nsIContentPrefService.h"
+#include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIChromeRegistry.h"
+#include "nsIPrivateBrowsingService.h"
+#include "nsIContentURIGrouper.h"
+#include "nsNetCID.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsITextServicesFilter.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
@@ -75,34 +80,245 @@ class UpdateDictionnaryHolder {
     }
     ~UpdateDictionnaryHolder() {
       if (mSpellCheck) {
         mSpellCheck->EndUpdateDictionary();
       }
     }
 };
 
+#define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
+
+class LastDictionary : public nsIObserver, public nsSupportsWeakReference {
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  LastDictionary();
+
+  /**
+   * Store current dictionary for editor document url. Use content pref
+   * service. Or, if in private mode, store this information in memory.
+   */
+  NS_IMETHOD StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary);
+
+  /**
+   * Get last stored current dictionary for editor document url.
+   */
+  NS_IMETHOD FetchLastDictionary(nsIEditor* aEditor, nsAString& aDictionary);
+
+  /**
+   * Forget last current dictionary stored for editor document url.
+   */
+  NS_IMETHOD ClearCurrentDictionary(nsIEditor* aEditor);
+
+  /**
+   * get uri of editor's document.
+   *
+   */
+  static nsresult GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI);
+
+  PRBool mInPrivateBrowsing;
+
+  // During private browsing, dictionaries are stored in memory
+  nsDataHashtable<nsStringHashKey, nsString> mMemoryStorage;
+};
+
+NS_IMPL_ISUPPORTS2(LastDictionary, nsIObserver, nsISupportsWeakReference)
+
+LastDictionary::LastDictionary():
+  mInPrivateBrowsing(PR_FALSE)
+{  
+  nsCOMPtr<nsIPrivateBrowsingService> pbService =
+    do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
+  if (pbService) {
+    pbService->GetPrivateBrowsingEnabled(&mInPrivateBrowsing);
+    mMemoryStorage.Init();
+  }
+}
+
+// static
+nsresult
+LastDictionary::GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
+{
+  NS_ENSURE_ARG_POINTER(aEditor);
+  NS_ENSURE_ARG_POINTER(aURI);
+
+  nsCOMPtr<nsIDOMDocument> domDoc;
+  aEditor->GetDocument(getter_AddRefs(domDoc));
+  NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
+  NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
+
+  *aURI = docUri;
+  NS_ADDREF(*aURI);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LastDictionary::FetchLastDictionary(nsIEditor* aEditor, nsAString& aDictionary)
+{
+  NS_ENSURE_ARG_POINTER(aEditor);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIURI> docUri;
+  rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mInPrivateBrowsing) {
+    nsCOMPtr<nsIContentURIGrouper> hostnameGrouperService =
+      do_GetService(NS_HOSTNAME_GROUPER_SERVICE_CONTRACTID);
+    NS_ENSURE_TRUE(hostnameGrouperService, NS_ERROR_NOT_AVAILABLE);
+    nsString group;
+    hostnameGrouperService->Group(docUri, group);
+    nsAutoString lastDict;
+    if (mMemoryStorage.Get(group, &lastDict)) {
+      aDictionary.Assign(lastDict);
+    } else {
+      aDictionary.Truncate();
+    }
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIContentPrefService> contentPrefService =
+    do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
+
+  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
+  NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
+  uri->SetAsISupports(docUri);
+
+  PRBool hasPref;
+  if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, &hasPref)) && hasPref) {
+    nsCOMPtr<nsIVariant> pref;
+    contentPrefService->GetPref(uri, CPS_PREF_NAME, nsnull, getter_AddRefs(pref));
+    pref->GetAsAString(aDictionary);
+  } else {
+    aDictionary.Truncate();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LastDictionary::StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
+{
+  NS_ENSURE_ARG_POINTER(aEditor);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIURI> docUri;
+  rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mInPrivateBrowsing) {
+    nsCOMPtr<nsIContentURIGrouper> hostnameGrouperService =
+      do_GetService(NS_HOSTNAME_GROUPER_SERVICE_CONTRACTID);
+    NS_ENSURE_TRUE(hostnameGrouperService, NS_ERROR_NOT_AVAILABLE);
+    nsString group;
+    hostnameGrouperService->Group(docUri, group);
+
+    if (mMemoryStorage.Put(group, nsString(aDictionary))) {
+      return NS_OK;
+    } else {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
+  NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
+  uri->SetAsISupports(docUri);
+
+  nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
+  NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY);
+  prefValue->SetAsAString(aDictionary);
+
+  nsCOMPtr<nsIContentPrefService> contentPrefService =
+    do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
+
+  return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue);
+}
+
+NS_IMETHODIMP
+LastDictionary::ClearCurrentDictionary(nsIEditor* aEditor)
+{
+  NS_ENSURE_ARG_POINTER(aEditor);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIURI> docUri;
+  rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIContentURIGrouper> hostnameGrouperService =
+      do_GetService(NS_HOSTNAME_GROUPER_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(hostnameGrouperService, NS_ERROR_NOT_AVAILABLE);
+
+  nsString group;
+  hostnameGrouperService->Group(docUri, group);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mMemoryStorage.IsInitialized()) {
+    mMemoryStorage.Remove(group);
+  }
+
+  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
+  NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
+  uri->SetAsISupports(docUri);
+
+  nsCOMPtr<nsIContentPrefService> contentPrefService =
+    do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
+
+  return contentPrefService->RemovePref(uri, CPS_PREF_NAME);
+}
+
+NS_IMETHODIMP
+LastDictionary::Observe(nsISupports *aSubject, char const *aTopic, PRUnichar const *aData)
+{
+  if (strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC) == 0) {
+    if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).Equals(aData)) {
+      mInPrivateBrowsing = PR_TRUE;
+    } else if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(aData)) {
+      mInPrivateBrowsing = PR_FALSE;
+      if (mMemoryStorage.IsInitialized()) {
+        mMemoryStorage.Clear();
+      }
+    }
+  } 
+  return NS_OK;
+}
+
+LastDictionary* nsEditorSpellCheck::gDictionaryStore = nsnull;
+
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck)
 
 NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTION_2(nsEditorSpellCheck,
+NS_IMPL_CYCLE_COLLECTION_3(nsEditorSpellCheck,
+                           mEditor,
                            mSpellChecker,
                            mTxtSrvFilter)
 
 nsEditorSpellCheck::nsEditorSpellCheck()
   : mSuggestedWordIndex(0)
   , mDictionaryIndex(0)
+  , mEditor(nsnull)
   , mUpdateDictionaryRunning(PR_FALSE)
-  , mDictWasSetManually(PR_FALSE)
 {
 }
 
 nsEditorSpellCheck::~nsEditorSpellCheck()
 {
   // Make sure we blow the spellchecker away, just in
   // case it hasn't been destroyed already.
   mSpellChecker = nsnull;
@@ -130,18 +346,34 @@ nsEditorSpellCheck::CanSpellCheck(PRBool
 
   *_retval = (dictList.Length() > 0);
   return NS_OK;
 }
 
 NS_IMETHODIMP    
 nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, PRBool aEnableSelectionChecking)
 {
+  NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
+  mEditor = aEditor;
+
   nsresult rv;
 
+  if (!gDictionaryStore) {
+    gDictionaryStore = new LastDictionary();
+    if (gDictionaryStore) {
+      NS_ADDREF(gDictionaryStore);
+      nsCOMPtr<nsIObserverService> observerService =
+        mozilla::services::GetObserverService();
+      if (observerService) {
+        observerService->AddObserver(gDictionaryStore, NS_PRIVATE_BROWSING_SWITCH_TOPIC, PR_TRUE);
+      }
+    }
+  }
+
+
   // We can spell check with any editor type
   nsCOMPtr<nsITextServicesDocument>tsDoc =
      do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER);
 
   tsDoc->SetFilter(mTxtSrvFilter);
@@ -203,17 +435,17 @@ nsEditorSpellCheck::InitSpellChecker(nsI
 
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
 
   rv = mSpellChecker->SetDocument(tsDoc, PR_TRUE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // do not fail if UpdateCurrentDictionary fails because this method may
   // succeed later.
-  UpdateCurrentDictionary(aEditor);
+  UpdateCurrentDictionary();
   return NS_OK;
 }
 
 NS_IMETHODIMP    
 nsEditorSpellCheck::GetNextMisspelledWord(PRUnichar **aNextMisspelledWord)
 {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
 
@@ -386,17 +618,39 @@ nsEditorSpellCheck::GetCurrentDictionary
 }
 
 NS_IMETHODIMP    
 nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary)
 {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
 
   if (!mUpdateDictionaryRunning) {
-    mDictWasSetManually = PR_TRUE;
+
+    nsDefaultStringComparator comparator;
+    nsAutoString langCode;
+    PRInt32 dashIdx = aDictionary.FindChar('-');
+    if (dashIdx != -1) {
+      langCode.Assign(Substring(aDictionary, 0, dashIdx));
+    } else {
+      langCode.Assign(aDictionary);
+    }
+
+    if (mPreferredLang.IsEmpty() || !nsStyleUtil::DashMatchCompare(mPreferredLang, langCode, comparator)) {
+      // When user sets dictionary manually, we store this value associated
+      // with editor url.
+      gDictionaryStore->StoreCurrentDictionary(mEditor, aDictionary);
+    } else {
+      // If user sets a dictionary matching (even partially), lang defined by
+      // document, we consider content pref has been canceled, and we clear it.
+      gDictionaryStore->ClearCurrentDictionary(mEditor);
+    }
+
+    // Also store it in as a preference. It will be used as a default value
+    // when everything else fails.
+    Preferences::SetString("spellchecker.dictionary", aDictionary);
   }
   return mSpellChecker->SetCurrentDictionary(aDictionary);
 }
 
 NS_IMETHODIMP    
 nsEditorSpellCheck::UninitSpellChecker()
 {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
@@ -404,31 +658,16 @@ nsEditorSpellCheck::UninitSpellChecker()
   // Cleanup - kill the spell checker
   DeleteSuggestedWordList();
   mDictionaryList.Clear();
   mDictionaryIndex = 0;
   mSpellChecker = 0;
   return NS_OK;
 }
 
-// Save the last set dictionary to the user's preferences.
-NS_IMETHODIMP
-nsEditorSpellCheck::SaveDefaultDictionary()
-{
-  if (!mDictWasSetManually) {
-    return NS_OK;
-  }
-
-  nsAutoString dictName;
-  nsresult rv = GetCurrentDictionary(dictName);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return Preferences::SetString("spellchecker.dictionary", dictName);
-}
-
 
 /* void setFilter (in nsITextServicesFilter filter); */
 NS_IMETHODIMP 
 nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter)
 {
   mTxtSrvFilter = filter;
   return NS_OK;
 }
@@ -437,55 +676,61 @@ nsresult
 nsEditorSpellCheck::DeleteSuggestedWordList()
 {
   mSuggestedWordList.Clear();
   mSuggestedWordIndex = 0;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditor* aEditor)
+nsEditorSpellCheck::UpdateCurrentDictionary()
 {
-  if (mDictWasSetManually) { // user has set dictionary manually; we better not change it.
-    return NS_OK;
-  }
-
   nsresult rv;
 
   UpdateDictionnaryHolder holder(this);
 
-  // Tell the spellchecker what dictionary to use:
-  nsAutoString dictName;
-
-  // First, try to get language with html5 algorithm
-  nsAutoString editorLang;
-
+  // Get language with html5 algorithm
   nsCOMPtr<nsIContent> rootContent;
-
-  nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(aEditor);
+  nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
   if (htmlEditor) {
     rootContent = htmlEditor->GetActiveEditingHost();
   } else {
     nsCOMPtr<nsIDOMElement> rootElement;
-    rv = aEditor->GetRootElement(getter_AddRefs(rootElement));
+    rv = mEditor->GetRootElement(getter_AddRefs(rootElement));
     NS_ENSURE_SUCCESS(rv, rv);
     rootContent = do_QueryInterface(rootElement);
   }
   NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE);
 
-  rootContent->GetLang(editorLang);
+  mPreferredLang.Truncate();
+  rootContent->GetLang(mPreferredLang);
+
+  // Tell the spellchecker what dictionary to use:
 
-  if (editorLang.IsEmpty()) {
+  // First try to get dictionary from content prefs. If we have one, do not got
+  // further. Use this exact dictionary.
+  nsAutoString dictName;
+  rv = gDictionaryStore->FetchLastDictionary(mEditor, dictName);
+  if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) {
+    if (NS_FAILED(SetCurrentDictionary(dictName))) { 
+      // may be dictionary was uninstalled ?
+      gDictionaryStore->ClearCurrentDictionary(mEditor);
+    }
+    return NS_OK;
+  }
+
+  if (mPreferredLang.IsEmpty()) {
     nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
     NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
-    doc->GetContentLanguage(editorLang);
+    doc->GetContentLanguage(mPreferredLang);
   }
 
-  if (!editorLang.IsEmpty()) {
-    dictName.Assign(editorLang);
+  // Then, try to use language computed from element
+  if (!mPreferredLang.IsEmpty()) {
+    dictName.Assign(mPreferredLang);
   }
 
   // otherwise, get language from preferences
   if (dictName.IsEmpty()) {
     dictName.Assign(Preferences::GetLocalizedString("spellchecker.dictionary"));
   }
 
   if (dictName.IsEmpty())
@@ -538,17 +783,17 @@ nsEditorSpellCheck::UpdateCurrentDiction
         }
       }
     }
   }
 
   // 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()) {
+  if (mPreferredLang.IsEmpty()) {
     nsAutoString currentDictionary;
     rv = GetCurrentDictionary(currentDictionary);
     if (NS_FAILED(rv) || currentDictionary.IsEmpty()) {
       rv = SetCurrentDictionary(NS_LITERAL_STRING("en-US"));
       if (NS_FAILED(rv)) {
         nsTArray<nsString> dictList;
         rv = mSpellChecker->GetDictionaryList(&dictList);
         if (NS_SUCCEEDED(rv) && dictList.Length() > 0) {
@@ -562,8 +807,13 @@ nsEditorSpellCheck::UpdateCurrentDiction
   // 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;
 }
+
+void 
+nsEditorSpellCheck::ShutDown() {
+  NS_IF_RELEASE(gDictionaryStore);
+}
--- a/editor/composer/src/nsEditorSpellCheck.h
+++ b/editor/composer/src/nsEditorSpellCheck.h
@@ -38,54 +38,66 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsEditorSpellCheck_h___
 #define nsEditorSpellCheck_h___
 
 
 #include "nsIEditorSpellCheck.h"
 #include "nsISpellChecker.h"
+#include "nsIObserver.h"
+#include "nsIURI.h"
+#include "nsWeakReference.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
+#include "nsDataHashtable.h"
 
 #define NS_EDITORSPELLCHECK_CID                     \
 { /* {75656ad9-bd13-4c5d-939a-ec6351eea0cc} */        \
     0x75656ad9, 0xbd13, 0x4c5d,                       \
     { 0x93, 0x9a, 0xec, 0x63, 0x51, 0xee, 0xa0, 0xcc }\
 }
 
+class LastDictionary;
+
 class nsEditorSpellCheck : public nsIEditorSpellCheck
 {
 public:
   nsEditorSpellCheck();
   virtual ~nsEditorSpellCheck();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsEditorSpellCheck)
 
   /* Declare all methods in the nsIEditorSpellCheck interface */
   NS_DECL_NSIEDITORSPELLCHECK
 
+  static LastDictionary* gDictionaryStore;
+
+  static void ShutDown();
+
 protected:
   nsCOMPtr<nsISpellChecker> mSpellChecker;
 
   nsTArray<nsString>  mSuggestedWordList;
   PRInt32        mSuggestedWordIndex;
 
   // these are the words in the current personal dictionary,
   // GetPersonalDictionary must be called to load them.
   nsTArray<nsString>  mDictionaryList;
   PRInt32        mDictionaryIndex;
 
   nsresult       DeleteSuggestedWordList();
 
   nsCOMPtr<nsITextServicesFilter> mTxtSrvFilter;
+  nsCOMPtr<nsIEditor> mEditor;
+
+  nsString mPreferredLang;
 
   PRPackedBool mUpdateDictionaryRunning;
-  PRPackedBool mDictWasSetManually;
 
 public:
   void BeginUpdateDictionary() { mUpdateDictionaryRunning = PR_TRUE ;}
   void EndUpdateDictionary() { mUpdateDictionaryRunning = PR_FALSE ;}
 };
 
 #endif // nsEditorSpellCheck_h___
 
--- a/editor/composer/test/Makefile.in
+++ b/editor/composer/test/Makefile.in
@@ -44,20 +44,22 @@ relativesrcdir  = editor/composer/test
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		test_bug348497.html \
 		test_bug384147.html \
 		test_bug389350.html \
 		test_bug519928.html \
+		bug678842_subframe.html \
 		$(NULL)
 
 _CHROME_TEST_FILES = \
 		test_bug434998.xul \
 		test_bug338427.html \
+		test_bug678842.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 
 libs:: $(_CHROME_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/editor/composer/test/bug678842_subframe.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<textarea id="textarea" lang="testing-XXX"></textarea>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/editor/composer/test/test_bug678842.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=678842
+-->
+<head>
+  <title>Test for Bug 678842</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=678842">Mozilla Bug 678842</a>
+<p id="display"></p>
+<iframe id="content"></iframe>
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 678842 **/
+// just a fake test
+SimpleTest.waitForExplicitFinish();
+var content = document.getElementById('content');
+// load a subframe containing an editor with a defined unknown lang. At first
+// load, it will set dictionary to en-US. At second load, it will return current
+// dictionary. So, we can check, dictionary is correctly remembered between
+// loads.
+
+var firstLoad = true;
+
+var loadListener = function(evt) {
+  var doc = evt.target.contentDocument;
+  var elem = doc.getElementById('textarea');
+  var editor = elem.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor;
+  editor.setSpellcheckUserOverride(true);
+  var inlineSpellChecker = editor.getInlineSpellChecker(true);
+  var spellchecker = inlineSpellChecker.spellChecker;
+  var currentDictonary = "";
+  try {
+    currentDictonary = spellchecker.GetCurrentDictionary();
+  } catch(e) {}
+
+  if (!currentDictonary) {
+    spellchecker.SetCurrentDictionary('en-US');
+  }
+
+  if (firstLoad) {
+    firstLoad = false;
+    is (currentDictonary, "", "unexpected lang " + currentDictonary);
+    content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug678842_subframe.html?firstload=false';
+  } else {
+    is (currentDictonary, "en-US", "unexpected lang " + currentDictonary + " instead of en-US");
+    content.removeEventListener('load', loadListener, false);
+  }
+}
+
+content.addEventListener('load', loadListener, false);
+
+content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug678842_subframe.html?firstload=true';
+
+</script>
+</pre>
+</body>
+</html>
--- a/editor/idl/nsIEditorSpellCheck.idl
+++ b/editor/idl/nsIEditorSpellCheck.idl
@@ -36,17 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
  
 #include "nsISupports.idl"
 
 interface nsIEditor;
 interface nsITextServicesFilter;
 
-[scriptable, uuid(3da0ce96-7d3a-48d0-80b7-2d90dab09747)]
+[scriptable, uuid(af84da62-588f-409f-847d-feecc991bd93)]
 interface nsIEditorSpellCheck : nsISupports
 {
 
  /**
    * Returns true if we can enable spellchecking. If there are no available
    * dictionaries, this will return false.
    */
   boolean       canSpellCheck();
@@ -146,24 +146,16 @@ interface nsIEditorSpellCheck : nsISuppo
   AString       GetCurrentDictionary();
 
   /**
    * @see nsISpellChecker::SetCurrentDictionary
    */
   void          SetCurrentDictionary(in AString dictionary);
 
   /**
-   * Call to save the currently selected dictionary as the default. The
-   * function UninitSpellChecker will also do this, but that function may not
-   * be called in some situations. This function allows the caller to force the
-   * default right now.
-   */
-  void          saveDefaultDictionary();
-
-  /**
    * Call this to free up the spell checking object. It will also save the
    * current selected language as the default for future use.
    *
    * If you have called CanSpellCheck but not InitSpellChecker, you can still
    * call this function to clear the cached spell check object, and no
    * preference saving will happen.
    */
   void          UninitSpellChecker();
@@ -187,11 +179,11 @@ interface nsIEditorSpellCheck : nsISuppo
    * invalid.
    */
   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);
+  void          UpdateCurrentDictionary();
 
 };
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -1760,18 +1760,17 @@ NS_IMETHODIMP mozInlineSpellChecker::Upd
     return NS_OK;
   }
 
   nsAutoString previousDictionary;
   if (NS_FAILED(mSpellCheck->GetCurrentDictionary(previousDictionary))) {
     previousDictionary.Truncate();
   }
 
-  nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
-  nsresult rv = mSpellCheck->UpdateCurrentDictionary(editor);
+  nsresult rv = mSpellCheck->UpdateCurrentDictionary();
 
   nsAutoString currentDictionary;
   if (NS_FAILED(mSpellCheck->GetCurrentDictionary(currentDictionary))) {
     currentDictionary.Truncate();
   }
 
   if (!previousDictionary.Equals(currentDictionary)) {
       rv = SpellCheckRange(nsnull);
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -117,16 +117,17 @@
 
 #include "nsCycleCollector.h"
 #include "nsJSEnvironment.h"
 #include "nsContentSink.h"
 #include "nsFrameMessageManager.h"
 #include "nsRefreshDriver.h"
 
 #include "nsHyphenationManager.h"
+#include "nsEditorSpellCheck.h"
 #include "nsDOMMemoryReporter.h"
 
 extern void NS_ShutdownChainItemPool();
 
 using namespace mozilla;
 
 nsrefcnt nsLayoutStatics::sLayoutStaticRefcnt = 0;
 
@@ -353,9 +354,10 @@ nsLayoutStatics::Shutdown()
 
   nsFrameList::Shutdown();
 
   nsHTMLInputElement::DestroyUploadLastDir();
 
   nsLayoutUtils::Shutdown();
 
   nsHyphenationManager::Shutdown();
+  nsEditorSpellCheck::ShutDown();
 }
--- a/toolkit/components/contentprefs/nsContentPrefService.js
+++ b/toolkit/components/contentprefs/nsContentPrefService.js
@@ -70,18 +70,19 @@ function electrolify(service) {
       // and using that to discover all the websites visited, etc.
       // Also there are both potential race conditions (if two processes
       // set more than one value in succession, and the values
       // only make sense together), as well as security issues, if
       // a compromised content process can send arbitrary setPref
       // messages. The whitelist contains only those settings that
       // are not at risk for either.
       // We currently whitelist saving/reading the last directory of file
-      // uploads, which is so far the only need we have identified.
-      const NAME_WHITELIST = ["browser.upload.lastDir"];
+      // uploads, and the last current spellchecker dictionary which are so far
+      // the only need we have identified.
+      const NAME_WHITELIST = ["browser.upload.lastDir", "spellcheck.lang"];
       if (NAME_WHITELIST.indexOf(json.name) == -1)
         return { succeeded: false };
 
       switch (aMessage.name) {
         case "ContentPref:getPref":
           return { succeeded: true,
                    value: service.getPref(json.group, json.name, json.value) };
 
--- a/toolkit/content/InlineSpellChecker.jsm
+++ b/toolkit/content/InlineSpellChecker.jsm
@@ -255,17 +255,16 @@ InlineSpellChecker.prototype = {
 
   // callback for selecting a dictionary
   selectDictionary: function(index)
   {
     if (! this.mInlineSpellChecker || index < 0 || index >= this.mDictionaryNames.length)
       return;
     var spellchecker = this.mInlineSpellChecker.spellChecker;
     spellchecker.SetCurrentDictionary(this.mDictionaryNames[index]);
-    spellchecker.saveDefaultDictionary();
     this.mInlineSpellChecker.spellCheckRange(null); // causes recheck
   },
 
   // callback for selecting a suggesteed replacement
   replaceMisspelling: function(index)
   {
     if (! this.mInlineSpellChecker || ! this.mOverMisspelling)
       return;