Bug 1888590 - Mark some subtests on trusted-types-event-handlers.html as failing...
[gecko.git] / extensions / spellcheck / src / mozPersonalDictionary.cpp
blob752369c034aa3b3956362c0eed230a04a6355775
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozPersonalDictionary.h"
8 #include <utility>
10 #include "nsAppDirectoryServiceDefs.h"
11 #include "nsCRT.h"
12 #include "nsIFile.h"
13 #include "nsIInputStream.h"
14 #include "nsIObserverService.h"
15 #include "nsIOutputStream.h"
16 #include "nsIRunnable.h"
17 #include "nsISafeOutputStream.h"
18 #include "nsIUnicharInputStream.h"
19 #include "nsIWeakReference.h"
20 #include "nsNetCID.h"
21 #include "nsNetUtil.h"
22 #include "nsProxyRelease.h"
23 #include "nsReadableUtils.h"
24 #include "nsStringEnumerator.h"
25 #include "nsTArray.h"
26 #include "nsThreadUtils.h"
27 #include "nsUnicharInputStream.h"
28 #include "prio.h"
30 #define MOZ_PERSONAL_DICT_NAME u"persdict.dat"
32 /**
33 * This is the most braindead implementation of a personal dictionary possible.
34 * There is not much complexity needed, though. It could be made much faster,
35 * and probably should, but I don't see much need for more in terms of
36 * interface.
38 * Allowing personal words to be associated with only certain dictionaries
39 * maybe.
41 * TODO:
42 * Implement the suggestion record.
45 NS_IMPL_ADDREF(mozPersonalDictionary)
46 NS_IMPL_RELEASE(mozPersonalDictionary)
48 NS_INTERFACE_MAP_BEGIN(mozPersonalDictionary)
49 NS_INTERFACE_MAP_ENTRY(mozIPersonalDictionary)
50 NS_INTERFACE_MAP_ENTRY(nsIObserver)
51 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
52 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIPersonalDictionary)
53 NS_INTERFACE_MAP_END
55 class mozPersonalDictionaryLoader final : public mozilla::Runnable {
56 public:
57 explicit mozPersonalDictionaryLoader(mozPersonalDictionary* dict)
58 : mozilla::Runnable("mozPersonalDictionaryLoader"), mDict(dict) {}
60 NS_IMETHOD Run() override {
61 mDict->SyncLoad();
63 // Release the dictionary on the main thread
64 NS_ReleaseOnMainThread("mozPersonalDictionaryLoader::mDict",
65 mDict.forget().downcast<mozIPersonalDictionary>());
67 return NS_OK;
70 private:
71 RefPtr<mozPersonalDictionary> mDict;
74 class mozPersonalDictionarySave final : public mozilla::Runnable {
75 public:
76 explicit mozPersonalDictionarySave(mozPersonalDictionary* aDict,
77 nsCOMPtr<nsIFile> aFile,
78 nsTArray<nsString>&& aDictWords)
79 : mozilla::Runnable("mozPersonalDictionarySave"),
80 mDictWords(std::move(aDictWords)),
81 mFile(aFile),
82 mDict(aDict) {}
84 NS_IMETHOD Run() override {
85 nsresult res;
87 MOZ_ASSERT(!NS_IsMainThread());
90 mozilla::MonitorAutoLock mon(mDict->mMonitorSave);
92 nsCOMPtr<nsIOutputStream> outStream;
93 NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStream), mFile,
94 PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
95 0664);
97 // Get a buffered output stream 4096 bytes big, to optimize writes.
98 nsCOMPtr<nsIOutputStream> bufferedOutputStream;
99 res = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream),
100 outStream.forget(), 4096);
101 if (NS_FAILED(res)) {
102 return res;
105 uint32_t bytesWritten;
106 nsAutoCString utf8Key;
107 for (uint32_t i = 0; i < mDictWords.Length(); ++i) {
108 CopyUTF16toUTF8(mDictWords[i], utf8Key);
110 bufferedOutputStream->Write(utf8Key.get(), utf8Key.Length(),
111 &bytesWritten);
112 bufferedOutputStream->Write("\n", 1, &bytesWritten);
114 nsCOMPtr<nsISafeOutputStream> safeStream =
115 do_QueryInterface(bufferedOutputStream);
116 NS_ASSERTION(safeStream, "expected a safe output stream!");
117 if (safeStream) {
118 res = safeStream->Finish();
119 if (NS_FAILED(res)) {
120 NS_WARNING(
121 "failed to save personal dictionary file! possible data loss");
125 // Save is done, reset the state variable and notify those who are
126 // waiting.
127 mDict->mSavePending = false;
128 mon.Notify();
130 // Leaving the block where 'mon' was declared will call the destructor
131 // and unlock.
134 // Release the dictionary on the main thread.
135 NS_ReleaseOnMainThread("mozPersonalDictionarySave::mDict",
136 mDict.forget().downcast<mozIPersonalDictionary>());
138 return NS_OK;
141 private:
142 nsTArray<nsString> mDictWords;
143 nsCOMPtr<nsIFile> mFile;
144 RefPtr<mozPersonalDictionary> mDict;
147 mozPersonalDictionary::mozPersonalDictionary()
148 : mIsLoaded(false),
149 mSavePending(false),
150 mMonitor("mozPersonalDictionary::mMonitor"),
151 mMonitorSave("mozPersonalDictionary::mMonitorSave") {}
153 mozPersonalDictionary::~mozPersonalDictionary() {}
155 nsresult mozPersonalDictionary::Init() {
156 nsCOMPtr<nsIObserverService> svc =
157 do_GetService("@mozilla.org/observer-service;1");
159 NS_ENSURE_STATE(svc);
160 // we want to reload the dictionary if the profile switches
161 nsresult rv = svc->AddObserver(this, "profile-do-change", true);
162 if (NS_WARN_IF(NS_FAILED(rv))) {
163 return rv;
166 rv = svc->AddObserver(this, "profile-before-change", true);
167 if (NS_WARN_IF(NS_FAILED(rv))) {
168 return rv;
171 Load();
173 return NS_OK;
176 void mozPersonalDictionary::WaitForLoad() {
177 // If the dictionary is already loaded, we return straight away.
178 if (mIsLoaded) {
179 return;
182 // If the dictionary hasn't been loaded, we try to lock the same monitor
183 // that the thread uses that does the load. This way the main thread will
184 // be suspended until the monitor becomes available.
185 mozilla::MonitorAutoLock mon(mMonitor);
187 // The monitor has become available. This can have two reasons:
188 // 1: The thread that does the load has finished.
189 // 2: The thread that does the load hasn't even started.
190 // In this case we need to wait.
191 if (!mIsLoaded) {
192 mon.Wait();
196 nsresult mozPersonalDictionary::LoadInternal() {
197 nsresult rv;
198 mozilla::MonitorAutoLock mon(mMonitor);
200 if (mIsLoaded) {
201 return NS_OK;
204 rv =
205 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mFile));
206 if (NS_WARN_IF(NS_FAILED(rv))) {
207 return rv;
210 if (!mFile) {
211 return NS_ERROR_FAILURE;
214 rv = mFile->Append(nsLiteralString(MOZ_PERSONAL_DICT_NAME));
215 if (NS_WARN_IF(NS_FAILED(rv))) {
216 return rv;
219 nsCOMPtr<nsIEventTarget> target =
220 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
221 if (NS_WARN_IF(NS_FAILED(rv))) {
222 return rv;
225 nsCOMPtr<nsIRunnable> runnable = new mozPersonalDictionaryLoader(this);
226 rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
227 if (NS_WARN_IF(NS_FAILED(rv))) {
228 return rv;
231 return NS_OK;
234 NS_IMETHODIMP mozPersonalDictionary::Load() {
235 nsresult rv = LoadInternal();
237 if (NS_FAILED(rv)) {
238 mIsLoaded = true;
241 return rv;
244 void mozPersonalDictionary::SyncLoad() {
245 MOZ_ASSERT(!NS_IsMainThread());
247 mozilla::MonitorAutoLock mon(mMonitor);
249 if (mIsLoaded) {
250 return;
253 SyncLoadInternal();
254 mIsLoaded = true;
255 mon.Notify();
258 void mozPersonalDictionary::SyncLoadInternal() {
259 MOZ_ASSERT(!NS_IsMainThread());
261 // FIXME Deinst -- get dictionary name from prefs;
262 nsresult rv;
263 bool dictExists;
265 rv = mFile->Exists(&dictExists);
266 if (NS_FAILED(rv)) {
267 return;
270 if (!dictExists) {
271 // Nothing is really wrong...
272 return;
275 nsCOMPtr<nsIInputStream> inStream;
276 NS_NewLocalFileInputStream(getter_AddRefs(inStream), mFile);
278 nsCOMPtr<nsIUnicharInputStream> convStream;
279 rv = NS_NewUnicharInputStream(inStream, getter_AddRefs(convStream));
280 if (NS_FAILED(rv)) {
281 return;
284 // we're rereading to get rid of the old data -- we shouldn't have any,
285 // but...
286 mDictionaryTable.Clear();
288 char16_t c;
289 uint32_t nRead;
290 bool done = false;
291 do { // read each line of text into the string array.
292 if ((NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) break;
293 while (!done && ((c == '\n') || (c == '\r'))) {
294 if ((NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1))
295 done = true;
297 if (!done) {
298 nsAutoString word;
299 while ((c != '\n') && (c != '\r') && !done) {
300 word.Append(c);
301 if ((NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1))
302 done = true;
304 mDictionaryTable.Insert(word);
306 } while (!done);
309 void mozPersonalDictionary::WaitForSave() {
310 // If no save is pending, we return straight away.
311 if (!mSavePending) {
312 return;
315 // If a save is pending, we try to lock the same monitor that the thread uses
316 // that does the save. This way the main thread will be suspended until the
317 // monitor becomes available.
318 mozilla::MonitorAutoLock mon(mMonitorSave);
320 // The monitor has become available. This can have two reasons:
321 // 1: The thread that does the save has finished.
322 // 2: The thread that does the save hasn't even started.
323 // In this case we need to wait.
324 if (mSavePending) {
325 mon.Wait();
329 NS_IMETHODIMP mozPersonalDictionary::Save() {
330 nsCOMPtr<nsIFile> theFile;
331 nsresult res;
333 WaitForSave();
335 mSavePending = true;
337 // FIXME Deinst -- get dictionary name from prefs;
338 res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
339 getter_AddRefs(theFile));
340 if (NS_FAILED(res)) return res;
341 if (!theFile) return NS_ERROR_FAILURE;
342 res = theFile->Append(nsLiteralString(MOZ_PERSONAL_DICT_NAME));
343 if (NS_FAILED(res)) return res;
345 nsCOMPtr<nsIEventTarget> target =
346 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &res);
347 if (NS_WARN_IF(NS_FAILED(res))) {
348 return res;
351 nsCOMPtr<nsIRunnable> runnable = new mozPersonalDictionarySave(
352 this, theFile, mozilla::ToTArray<nsTArray<nsString>>(mDictionaryTable));
353 res = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
354 if (NS_WARN_IF(NS_FAILED(res))) {
355 return res;
357 return res;
360 NS_IMETHODIMP mozPersonalDictionary::GetWordList(nsIStringEnumerator** aWords) {
361 NS_ENSURE_ARG_POINTER(aWords);
362 *aWords = nullptr;
364 WaitForLoad();
366 nsTArray<nsString>* array = new nsTArray<nsString>(
367 mozilla::ToTArray<nsTArray<nsString>>(mDictionaryTable));
369 array->Sort();
371 return NS_NewAdoptingStringEnumerator(aWords, array);
374 NS_IMETHODIMP
375 mozPersonalDictionary::Check(const nsAString& aWord, bool* aResult) {
376 NS_ENSURE_ARG_POINTER(aResult);
378 WaitForLoad();
380 *aResult = (mDictionaryTable.Contains(aWord) || mIgnoreTable.Contains(aWord));
381 return NS_OK;
384 NS_IMETHODIMP
385 mozPersonalDictionary::AddWord(const nsAString& aWord) {
386 nsresult res;
387 WaitForLoad();
389 mDictionaryTable.Insert(aWord);
390 res = Save();
391 return res;
394 NS_IMETHODIMP
395 mozPersonalDictionary::RemoveWord(const nsAString& aWord) {
396 nsresult res;
397 WaitForLoad();
399 mDictionaryTable.Remove(aWord);
400 res = Save();
401 return res;
404 NS_IMETHODIMP
405 mozPersonalDictionary::IgnoreWord(const nsAString& aWord) {
406 // avoid adding duplicate words to the ignore list
407 mIgnoreTable.EnsureInserted(aWord);
408 return NS_OK;
411 NS_IMETHODIMP mozPersonalDictionary::EndSession() {
412 WaitForLoad();
414 WaitForSave();
415 mIgnoreTable.Clear();
416 return NS_OK;
419 NS_IMETHODIMP mozPersonalDictionary::Observe(nsISupports* aSubject,
420 const char* aTopic,
421 const char16_t* aData) {
422 if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
423 // The observer is registered in Init() which calls Load and in turn
424 // LoadInternal(); i.e. Observe() can't be called before Load().
425 WaitForLoad();
426 mIsLoaded = false;
427 Load(); // load automatically clears out the existing dictionary table
428 } else if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
429 WaitForSave();
432 return NS_OK;