Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/. */
5 :
6 : #include <string>
7 : #include <vector>
8 :
9 : #include "base/basictypes.h"
10 :
11 : #include "prefapi.h"
12 : #include "prefapi_private_data.h"
13 : #include "prefread.h"
14 : #include "MainThreadUtils.h"
15 : #include "nsReadableUtils.h"
16 : #include "nsCRT.h"
17 :
18 : #ifdef _WIN32
19 : #include "windows.h"
20 : #endif /* _WIN32 */
21 :
22 : #include "plstr.h"
23 : #include "PLDHashTable.h"
24 : #include "plbase64.h"
25 : #include "mozilla/ArenaAllocator.h"
26 : #include "mozilla/ArenaAllocatorExtensions.h"
27 : #include "mozilla/Logging.h"
28 : #include "mozilla/MemoryReporting.h"
29 : #include "mozilla/ServoStyleSet.h"
30 : #include "mozilla/dom/PContent.h"
31 : #include "mozilla/dom/ContentPrefs.h"
32 : #include "nsQuickSort.h"
33 : #include "nsString.h"
34 : #include "nsPrintfCString.h"
35 : #include "prlink.h"
36 :
37 : using namespace mozilla;
38 :
39 : static void
40 0 : clearPrefEntry(PLDHashTable *table, PLDHashEntryHdr *entry)
41 : {
42 0 : PrefHashEntry *pref = static_cast<PrefHashEntry *>(entry);
43 0 : if (pref->prefFlags.IsTypeString())
44 : {
45 0 : if (pref->defaultPref.stringVal)
46 0 : PL_strfree(pref->defaultPref.stringVal);
47 0 : if (pref->userPref.stringVal)
48 0 : PL_strfree(pref->userPref.stringVal);
49 : }
50 : // don't need to free this as it's allocated in memory owned by
51 : // gPrefNameArena
52 0 : pref->key = nullptr;
53 0 : memset(entry, 0, table->EntrySize());
54 0 : }
55 :
56 : static bool
57 17511 : matchPrefEntry(const PLDHashEntryHdr* entry, const void* key)
58 : {
59 : const PrefHashEntry *prefEntry =
60 17511 : static_cast<const PrefHashEntry*>(entry);
61 :
62 17511 : if (prefEntry->key == key) return true;
63 :
64 17511 : if (!prefEntry->key || !key) return false;
65 :
66 17511 : const char *otherKey = reinterpret_cast<const char*>(key);
67 17511 : return (strcmp(prefEntry->key, otherKey) == 0);
68 : }
69 :
70 : PLDHashTable* gHashTable;
71 3 : static ArenaAllocator<8192,4> gPrefNameArena;
72 :
73 : static struct CallbackNode* gFirstCallback = nullptr;
74 : static struct CallbackNode* gLastPriorityNode = nullptr;
75 : static bool gIsAnyPrefLocked = false;
76 : // These are only used during the call to pref_DoCallback
77 : static bool gCallbacksInProgress = false;
78 : static bool gShouldCleanupDeadNodes = false;
79 :
80 :
81 : static PLDHashTableOps pref_HashTableOps = {
82 : PLDHashTable::HashStringKey,
83 : matchPrefEntry,
84 : PLDHashTable::MoveEntryStub,
85 : clearPrefEntry,
86 : nullptr,
87 : };
88 :
89 : // PR_ALIGN_OF_WORD is only defined on some platforms. ALIGN_OF_WORD has
90 : // already been defined to PR_ALIGN_OF_WORD everywhere
91 : #ifndef PR_ALIGN_OF_WORD
92 : #define PR_ALIGN_OF_WORD PR_ALIGN_OF_POINTER
93 : #endif
94 :
95 : #define WORD_ALIGN_MASK (PR_ALIGN_OF_WORD - 1)
96 :
97 : // sanity checking
98 : #if (PR_ALIGN_OF_WORD & WORD_ALIGN_MASK) != 0
99 : #error "PR_ALIGN_OF_WORD must be a power of 2!"
100 : #endif
101 :
102 : static PrefsDirtyFunc gDirtyCallback = nullptr;
103 :
104 521 : inline void MakeDirtyCallback()
105 : {
106 : // Right now the callback function is always set, so we don't need
107 : // to complicate the code to cover the scenario where we set the callback
108 : // after we've already tried to make it dirty. If this assert triggers
109 : // we will add that code.
110 521 : MOZ_ASSERT(gDirtyCallback);
111 521 : if (gDirtyCallback) {
112 521 : gDirtyCallback();
113 : }
114 521 : }
115 :
116 3 : void PREF_SetDirtyCallback(PrefsDirtyFunc aFunc)
117 : {
118 3 : gDirtyCallback = aFunc;
119 3 : }
120 :
121 : /*---------------------------------------------------------------------------*/
122 :
123 : static bool pref_ValueChanged(PrefValue oldValue, PrefValue newValue, PrefType type);
124 : /* -- Privates */
125 : struct CallbackNode {
126 : char* domain;
127 : // If someone attempts to remove the node from the callback list while
128 : // pref_DoCallback is running, |func| is set to nullptr. Such nodes will
129 : // be removed at the end of pref_DoCallback.
130 : PrefChangedFunc func;
131 : void* data;
132 : struct CallbackNode* next;
133 : };
134 :
135 : /* -- Prototypes */
136 : static nsresult pref_DoCallback(const char* changed_pref);
137 :
138 : enum {
139 : kPrefSetDefault = 1,
140 : kPrefForceSet = 2,
141 : kPrefStickyDefault = 4,
142 : };
143 : static nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags);
144 :
145 : #define PREF_HASHTABLE_INITIAL_LENGTH 1024
146 :
147 3 : void PREF_Init()
148 : {
149 3 : if (!gHashTable) {
150 3 : gHashTable = new PLDHashTable(&pref_HashTableOps,
151 : sizeof(PrefHashEntry),
152 3 : PREF_HASHTABLE_INITIAL_LENGTH);
153 : }
154 3 : }
155 :
156 : /* Frees the callback list. */
157 0 : void PREF_Cleanup()
158 : {
159 0 : NS_ASSERTION(!gCallbacksInProgress,
160 : "PREF_Cleanup was called while gCallbacksInProgress is true!");
161 0 : struct CallbackNode* node = gFirstCallback;
162 : struct CallbackNode* next_node;
163 :
164 0 : while (node)
165 : {
166 0 : next_node = node->next;
167 0 : PL_strfree(node->domain);
168 0 : free(node);
169 0 : node = next_node;
170 : }
171 0 : gLastPriorityNode = gFirstCallback = nullptr;
172 :
173 0 : PREF_CleanupPrefs();
174 0 : }
175 :
176 : /* Frees up all the objects except the callback list. */
177 0 : void PREF_CleanupPrefs()
178 : {
179 0 : if (gHashTable) {
180 0 : delete gHashTable;
181 0 : gHashTable = nullptr;
182 0 : gPrefNameArena.Clear();
183 : }
184 0 : }
185 :
186 : // note that this appends to aResult, and does not assign!
187 0 : static void str_escape(const char * original, nsCString& aResult)
188 : {
189 : /* JavaScript does not allow quotes, slashes, or line terminators inside
190 : * strings so we must escape them. ECMAScript defines four line
191 : * terminators, but we're only worrying about \r and \n here. We currently
192 : * feed our pref script to the JS interpreter as Latin-1 so we won't
193 : * encounter \u2028 (line separator) or \u2029 (paragraph separator).
194 : *
195 : * WARNING: There are hints that we may be moving to storing prefs
196 : * as utf8. If we ever feed them to the JS compiler as UTF8 then
197 : * we'll have to worry about the multibyte sequences that would be
198 : * interpreted as \u2028 and \u2029
199 : */
200 : const char *p;
201 :
202 0 : if (original == nullptr)
203 0 : return;
204 :
205 : /* Paranoid worst case all slashes will free quickly */
206 0 : for (p=original; *p; ++p)
207 : {
208 0 : switch (*p)
209 : {
210 : case '\n':
211 0 : aResult.AppendLiteral("\\n");
212 0 : break;
213 :
214 : case '\r':
215 0 : aResult.AppendLiteral("\\r");
216 0 : break;
217 :
218 : case '\\':
219 0 : aResult.AppendLiteral("\\\\");
220 0 : break;
221 :
222 : case '\"':
223 0 : aResult.AppendLiteral("\\\"");
224 0 : break;
225 :
226 : default:
227 0 : aResult.Append(*p);
228 0 : break;
229 : }
230 : }
231 : }
232 :
233 : /*
234 : ** External calls
235 : */
236 : nsresult
237 1667 : PREF_SetCharPref(const char *pref_name, const char *value, bool set_default)
238 : {
239 1667 : if ((uint32_t)strlen(value) > MAX_PREF_LENGTH) {
240 0 : return NS_ERROR_ILLEGAL_VALUE;
241 : }
242 :
243 : PrefValue pref;
244 1667 : pref.stringVal = (char*)value;
245 :
246 1667 : return pref_HashPref(pref_name, pref, PrefType::String, set_default ? kPrefSetDefault : 0);
247 : }
248 :
249 : nsresult
250 1694 : PREF_SetIntPref(const char *pref_name, int32_t value, bool set_default)
251 : {
252 : PrefValue pref;
253 1694 : pref.intVal = value;
254 :
255 1694 : return pref_HashPref(pref_name, pref, PrefType::Int, set_default ? kPrefSetDefault : 0);
256 : }
257 :
258 : nsresult
259 3213 : PREF_SetBoolPref(const char *pref_name, bool value, bool set_default)
260 : {
261 : PrefValue pref;
262 3213 : pref.boolVal = value;
263 :
264 3213 : return pref_HashPref(pref_name, pref, PrefType::Bool, set_default ? kPrefSetDefault : 0);
265 : }
266 :
267 : enum WhichValue { DEFAULT_VALUE, USER_VALUE };
268 : static nsresult
269 6533 : SetPrefValue(const char* aPrefName, const dom::PrefValue& aValue,
270 : WhichValue aWhich)
271 : {
272 6533 : bool setDefault = (aWhich == DEFAULT_VALUE);
273 6533 : switch (aValue.type()) {
274 : case dom::PrefValue::TnsCString:
275 1658 : return PREF_SetCharPref(aPrefName, aValue.get_nsCString().get(),
276 1658 : setDefault);
277 : case dom::PrefValue::Tint32_t:
278 1681 : return PREF_SetIntPref(aPrefName, aValue.get_int32_t(),
279 1681 : setDefault);
280 : case dom::PrefValue::Tbool:
281 3194 : return PREF_SetBoolPref(aPrefName, aValue.get_bool(),
282 3194 : setDefault);
283 : default:
284 0 : MOZ_CRASH();
285 : }
286 : }
287 :
288 : nsresult
289 6348 : pref_SetPref(const dom::PrefSetting& aPref)
290 : {
291 6348 : const char* prefName = aPref.name().get();
292 6348 : const dom::MaybePrefValue& defaultValue = aPref.defaultValue();
293 6348 : const dom::MaybePrefValue& userValue = aPref.userValue();
294 :
295 : nsresult rv;
296 6348 : if (defaultValue.type() == dom::MaybePrefValue::TPrefValue) {
297 6177 : rv = SetPrefValue(prefName, defaultValue.get_PrefValue(), DEFAULT_VALUE);
298 6177 : if (NS_FAILED(rv)) {
299 0 : return rv;
300 : }
301 : }
302 :
303 6348 : if (userValue.type() == dom::MaybePrefValue::TPrefValue) {
304 356 : rv = SetPrefValue(prefName, userValue.get_PrefValue(), USER_VALUE);
305 : } else {
306 5992 : rv = PREF_ClearUserPref(prefName);
307 : }
308 :
309 : // NB: we should never try to clear a default value, that doesn't
310 : // make sense
311 :
312 6348 : return rv;
313 : }
314 :
315 : PrefSaveData
316 0 : pref_savePrefs(PLDHashTable* aTable)
317 : {
318 0 : PrefSaveData savedPrefs(aTable->EntryCount());
319 :
320 0 : for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
321 0 : auto pref = static_cast<PrefHashEntry*>(iter.Get());
322 :
323 0 : nsAutoCString prefValue;
324 0 : nsAutoCString prefPrefix;
325 0 : prefPrefix.AssignLiteral("user_pref(\"");
326 :
327 : // where we're getting our pref from
328 : PrefValue* sourcePref;
329 :
330 0 : if (pref->prefFlags.HasUserValue() &&
331 0 : (pref_ValueChanged(pref->defaultPref,
332 : pref->userPref,
333 0 : pref->prefFlags.GetPrefType()) ||
334 0 : !(pref->prefFlags.HasDefault()) ||
335 0 : pref->prefFlags.HasStickyDefault())) {
336 0 : sourcePref = &pref->userPref;
337 : } else {
338 : // do not save default prefs that haven't changed
339 0 : continue;
340 : }
341 :
342 : // strings are in quotes!
343 0 : if (pref->prefFlags.IsTypeString()) {
344 0 : prefValue = '\"';
345 0 : str_escape(sourcePref->stringVal, prefValue);
346 0 : prefValue += '\"';
347 :
348 0 : } else if (pref->prefFlags.IsTypeInt()) {
349 0 : prefValue.AppendInt(sourcePref->intVal);
350 :
351 0 : } else if (pref->prefFlags.IsTypeBool()) {
352 0 : prefValue = (sourcePref->boolVal) ? "true" : "false";
353 : }
354 :
355 0 : nsAutoCString prefName;
356 0 : str_escape(pref->key, prefName);
357 :
358 : savedPrefs.AppendElement()->
359 0 : reset(ToNewCString(prefPrefix +
360 0 : prefName +
361 0 : NS_LITERAL_CSTRING("\", ") +
362 0 : prefValue +
363 0 : NS_LITERAL_CSTRING(");")));
364 : }
365 0 : return savedPrefs;
366 : }
367 :
368 : bool
369 6088 : pref_EntryHasAdvisablySizedValues(PrefHashEntry* aHashEntry)
370 : {
371 6088 : if (aHashEntry->prefFlags.GetPrefType() != PrefType::String) {
372 4507 : return true;
373 : }
374 :
375 : char* stringVal;
376 1581 : if (aHashEntry->prefFlags.HasDefault()) {
377 1533 : stringVal = aHashEntry->defaultPref.stringVal;
378 1533 : if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
379 0 : return false;
380 : }
381 : }
382 :
383 1581 : if (aHashEntry->prefFlags.HasUserValue()) {
384 117 : stringVal = aHashEntry->userPref.stringVal;
385 117 : if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
386 0 : return false;
387 : }
388 : }
389 :
390 1581 : return true;
391 : }
392 :
393 : static void
394 6273 : GetPrefValueFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref,
395 : WhichValue aWhich)
396 : {
397 : PrefValue* value;
398 : dom::PrefValue* settingValue;
399 6273 : if (aWhich == USER_VALUE) {
400 356 : value = &aHashEntry->userPref;
401 356 : aPref->userValue() = dom::PrefValue();
402 356 : settingValue = &aPref->userValue().get_PrefValue();
403 : } else {
404 5917 : value = &aHashEntry->defaultPref;
405 5917 : aPref->defaultValue() = dom::PrefValue();
406 5917 : settingValue = &aPref->defaultValue().get_PrefValue();
407 : }
408 :
409 6273 : switch (aHashEntry->prefFlags.GetPrefType()) {
410 : case PrefType::String:
411 1650 : *settingValue = nsDependentCString(value->stringVal);
412 1650 : return;
413 : case PrefType::Int:
414 1613 : *settingValue = value->intVal;
415 1613 : return;
416 : case PrefType::Bool:
417 3010 : *settingValue = !!value->boolVal;
418 3010 : return;
419 : default:
420 0 : MOZ_CRASH();
421 : }
422 : }
423 :
424 : void
425 6088 : pref_GetPrefFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref)
426 : {
427 6088 : aPref->name() = aHashEntry->key;
428 6088 : if (aHashEntry->prefFlags.HasDefault()) {
429 5917 : GetPrefValueFromEntry(aHashEntry, aPref, DEFAULT_VALUE);
430 : } else {
431 171 : aPref->defaultValue() = null_t();
432 : }
433 6088 : if (aHashEntry->prefFlags.HasUserValue()) {
434 356 : GetPrefValueFromEntry(aHashEntry, aPref, USER_VALUE);
435 : } else {
436 5732 : aPref->userValue() = null_t();
437 : }
438 :
439 6088 : MOZ_ASSERT(aPref->defaultValue().type() == dom::MaybePrefValue::Tnull_t ||
440 : aPref->userValue().type() == dom::MaybePrefValue::Tnull_t ||
441 : (aPref->defaultValue().get_PrefValue().type() ==
442 : aPref->userValue().get_PrefValue().type()));
443 6088 : }
444 :
445 133 : bool PREF_HasUserPref(const char *pref_name)
446 : {
447 133 : if (!gHashTable)
448 0 : return false;
449 :
450 133 : PrefHashEntry *pref = pref_HashTableLookup(pref_name);
451 133 : return pref && pref->prefFlags.HasUserValue();
452 : }
453 :
454 : nsresult
455 952 : PREF_CopyCharPref(const char *pref_name, char ** return_buffer, bool get_default)
456 : {
457 952 : if (!gHashTable)
458 0 : return NS_ERROR_NOT_INITIALIZED;
459 :
460 952 : nsresult rv = NS_ERROR_UNEXPECTED;
461 : char* stringVal;
462 952 : PrefHashEntry* pref = pref_HashTableLookup(pref_name);
463 :
464 952 : if (pref && (pref->prefFlags.IsTypeString())) {
465 607 : if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) {
466 564 : stringVal = pref->defaultPref.stringVal;
467 : } else {
468 43 : stringVal = pref->userPref.stringVal;
469 : }
470 :
471 607 : if (stringVal) {
472 607 : *return_buffer = NS_strdup(stringVal);
473 607 : rv = NS_OK;
474 : }
475 : }
476 952 : return rv;
477 : }
478 :
479 1752 : nsresult PREF_GetIntPref(const char *pref_name,int32_t * return_int, bool get_default)
480 : {
481 1752 : if (!gHashTable)
482 0 : return NS_ERROR_NOT_INITIALIZED;
483 :
484 1752 : nsresult rv = NS_ERROR_UNEXPECTED;
485 1752 : PrefHashEntry* pref = pref_HashTableLookup(pref_name);
486 1752 : if (pref && (pref->prefFlags.IsTypeInt())) {
487 1230 : if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) {
488 1155 : int32_t tempInt = pref->defaultPref.intVal;
489 : /* check to see if we even had a default */
490 1155 : if (!pref->prefFlags.HasDefault()) {
491 0 : return NS_ERROR_UNEXPECTED;
492 : }
493 1155 : *return_int = tempInt;
494 : } else {
495 75 : *return_int = pref->userPref.intVal;
496 : }
497 1230 : rv = NS_OK;
498 : }
499 1752 : return rv;
500 : }
501 :
502 4100 : nsresult PREF_GetBoolPref(const char *pref_name, bool * return_value, bool get_default)
503 : {
504 4100 : if (!gHashTable)
505 0 : return NS_ERROR_NOT_INITIALIZED;
506 :
507 4100 : nsresult rv = NS_ERROR_UNEXPECTED;
508 4100 : PrefHashEntry* pref = pref_HashTableLookup(pref_name);
509 : //NS_ASSERTION(pref, pref_name);
510 4100 : if (pref && (pref->prefFlags.IsTypeBool())) {
511 2343 : if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) {
512 2235 : bool tempBool = pref->defaultPref.boolVal;
513 : /* check to see if we even had a default */
514 2235 : if (pref->prefFlags.HasDefault()) {
515 2235 : *return_value = tempBool;
516 2235 : rv = NS_OK;
517 : }
518 : } else {
519 108 : *return_value = pref->userPref.boolVal;
520 108 : rv = NS_OK;
521 : }
522 : }
523 4100 : return rv;
524 : }
525 :
526 : nsresult
527 0 : PREF_DeleteBranch(const char *branch_name)
528 : {
529 : #ifndef MOZ_B2G
530 0 : MOZ_ASSERT(NS_IsMainThread());
531 : #endif
532 :
533 0 : int len = (int)strlen(branch_name);
534 :
535 0 : if (!gHashTable)
536 0 : return NS_ERROR_NOT_INITIALIZED;
537 :
538 : /* The following check insures that if the branch name already has a "."
539 : * at the end, we don't end up with a "..". This fixes an incompatibility
540 : * between nsIPref, which needs the period added, and nsIPrefBranch which
541 : * does not. When nsIPref goes away this function should be fixed to
542 : * never add the period at all.
543 : */
544 0 : nsAutoCString branch_dot(branch_name);
545 0 : if ((len > 1) && branch_name[len - 1] != '.')
546 0 : branch_dot += '.';
547 :
548 : /* Delete a branch. Used for deleting mime types */
549 0 : const char *to_delete = branch_dot.get();
550 0 : MOZ_ASSERT(to_delete);
551 0 : len = strlen(to_delete);
552 0 : for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
553 0 : auto entry = static_cast<PrefHashEntry*>(iter.Get());
554 :
555 : /* note if we're deleting "ldap" then we want to delete "ldap.xxx"
556 : and "ldap" (if such a leaf node exists) but not "ldap_1.xxx" */
557 0 : if (PL_strncmp(entry->key, to_delete, (uint32_t) len) == 0 ||
558 0 : (len-1 == (int)strlen(entry->key) &&
559 0 : PL_strncmp(entry->key, to_delete, (uint32_t)(len-1)) == 0)) {
560 0 : iter.Remove();
561 : }
562 : }
563 :
564 0 : MakeDirtyCallback();
565 0 : return NS_OK;
566 : }
567 :
568 :
569 : nsresult
570 5998 : PREF_ClearUserPref(const char *pref_name)
571 : {
572 5998 : if (!gHashTable)
573 0 : return NS_ERROR_NOT_INITIALIZED;
574 :
575 5998 : PrefHashEntry* pref = pref_HashTableLookup(pref_name);
576 5998 : if (pref && pref->prefFlags.HasUserValue()) {
577 0 : pref->prefFlags.SetHasUserValue(false);
578 :
579 0 : if (!pref->prefFlags.HasDefault()) {
580 0 : gHashTable->RemoveEntry(pref);
581 : }
582 :
583 0 : pref_DoCallback(pref_name);
584 0 : MakeDirtyCallback();
585 : }
586 5998 : return NS_OK;
587 : }
588 :
589 : nsresult
590 1 : PREF_ClearAllUserPrefs()
591 : {
592 : #ifndef MOZ_B2G
593 1 : MOZ_ASSERT(NS_IsMainThread());
594 : #endif
595 :
596 1 : if (!gHashTable)
597 0 : return NS_ERROR_NOT_INITIALIZED;
598 :
599 2 : std::vector<std::string> prefStrings;
600 2936 : for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
601 2935 : auto pref = static_cast<PrefHashEntry*>(iter.Get());
602 :
603 2935 : if (pref->prefFlags.HasUserValue()) {
604 0 : prefStrings.push_back(std::string(pref->key));
605 :
606 0 : pref->prefFlags.SetHasUserValue(false);
607 0 : if (!pref->prefFlags.HasDefault()) {
608 0 : iter.Remove();
609 : }
610 : }
611 : }
612 :
613 1 : for (std::string& prefString : prefStrings) {
614 0 : pref_DoCallback(prefString.c_str());
615 : }
616 :
617 1 : MakeDirtyCallback();
618 1 : return NS_OK;
619 : }
620 :
621 0 : nsresult PREF_LockPref(const char *key, bool lockit)
622 : {
623 0 : if (!gHashTable)
624 0 : return NS_ERROR_NOT_INITIALIZED;
625 :
626 0 : PrefHashEntry* pref = pref_HashTableLookup(key);
627 0 : if (!pref)
628 0 : return NS_ERROR_UNEXPECTED;
629 :
630 0 : if (lockit) {
631 0 : if (!pref->prefFlags.IsLocked()) {
632 0 : pref->prefFlags.SetLocked(true);
633 0 : gIsAnyPrefLocked = true;
634 0 : pref_DoCallback(key);
635 : }
636 : } else {
637 0 : if (pref->prefFlags.IsLocked()) {
638 0 : pref->prefFlags.SetLocked(false);
639 0 : pref_DoCallback(key);
640 : }
641 : }
642 0 : return NS_OK;
643 : }
644 :
645 : /*
646 : * Hash table functions
647 : */
648 15687 : static bool pref_ValueChanged(PrefValue oldValue, PrefValue newValue, PrefType type)
649 : {
650 15687 : bool changed = true;
651 15687 : switch(type) {
652 : case PrefType::String:
653 4024 : if (oldValue.stringVal && newValue.stringVal) {
654 1726 : changed = (strcmp(oldValue.stringVal, newValue.stringVal) != 0);
655 : }
656 4024 : break;
657 : case PrefType::Int:
658 4008 : changed = oldValue.intVal != newValue.intVal;
659 4008 : break;
660 : case PrefType::Bool:
661 7655 : changed = oldValue.boolVal != newValue.boolVal;
662 7655 : break;
663 : case PrefType::Invalid:
664 : default:
665 0 : changed = false;
666 0 : break;
667 : }
668 15687 : return changed;
669 : }
670 :
671 : /*
672 : * Overwrite the type and value of an existing preference. Caller must
673 : * ensure that they are not changing the type of a preference that has
674 : * a default value.
675 : */
676 9603 : static PrefTypeFlags pref_SetValue(PrefValue* existingValue, PrefTypeFlags flags,
677 : PrefValue newValue, PrefType newType)
678 : {
679 9603 : if (flags.IsTypeString() && existingValue->stringVal) {
680 19 : PL_strfree(existingValue->stringVal);
681 : }
682 9603 : flags.SetPrefType(newType);
683 9603 : if (flags.IsTypeString()) {
684 2491 : MOZ_ASSERT(newValue.stringVal);
685 2491 : existingValue->stringVal = newValue.stringVal ? PL_strdup(newValue.stringVal) : nullptr;
686 : }
687 : else {
688 7112 : *existingValue = newValue;
689 : }
690 9603 : return flags;
691 : }
692 : #ifdef DEBUG
693 : static pref_initPhase gPhase = START;
694 :
695 : static bool gWatchingPref = false;
696 :
697 : void
698 16 : pref_SetInitPhase(pref_initPhase phase)
699 : {
700 16 : gPhase = phase;
701 16 : }
702 :
703 : pref_initPhase
704 2504 : pref_GetInitPhase()
705 : {
706 2504 : return gPhase;
707 : }
708 :
709 : void
710 4248 : pref_SetWatchingPref(bool watching)
711 : {
712 4248 : gWatchingPref = watching;
713 4248 : }
714 :
715 :
716 : struct StringComparator
717 : {
718 : const char* mKey;
719 494 : explicit StringComparator(const char* aKey) : mKey(aKey) {}
720 3038 : int operator()(const char* string) const {
721 3038 : return strcmp(mKey, string);
722 : }
723 : };
724 :
725 : bool
726 494 : inInitArray(const char* key)
727 : {
728 : size_t prefsLen;
729 : size_t found;
730 494 : const char** list = mozilla::dom::ContentPrefs::GetContentPrefs(&prefsLen);
731 494 : return BinarySearchIf(list, 0, prefsLen,
732 988 : StringComparator(key), &found);
733 : }
734 : #endif
735 :
736 13668 : PrefHashEntry* pref_HashTableLookup(const char *key)
737 : {
738 : #ifndef MOZ_B2G
739 13668 : MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal());
740 : #endif
741 13668 : MOZ_ASSERT((!XRE_IsContentProcess() || gPhase != START),
742 : "pref access before commandline prefs set");
743 : /* If you're hitting this assertion, you've added a pref access to start up.
744 : * Consider moving it later or add it to the whitelist in ContentPrefs.cpp
745 : * and get review from a DOM peer
746 : */
747 : #ifdef DEBUG
748 36973 : if (XRE_IsContentProcess() && gPhase <= END_INIT_PREFS &&
749 15260 : !gWatchingPref && !inInitArray(key)) {
750 0 : MOZ_CRASH_UNSAFE_PRINTF("accessing non-init pref %s before the rest of the prefs are sent",
751 : key);
752 : }
753 : #endif
754 13668 : return static_cast<PrefHashEntry*>(gHashTable->Search(key));
755 : }
756 :
757 15877 : nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags)
758 : {
759 : #ifndef MOZ_B2G
760 15877 : MOZ_ASSERT(NS_IsMainThread());
761 : #endif
762 :
763 15877 : if (!gHashTable)
764 0 : return NS_ERROR_OUT_OF_MEMORY;
765 :
766 15877 : auto pref = static_cast<PrefHashEntry*>(gHashTable->Add(key, fallible));
767 15877 : if (!pref)
768 0 : return NS_ERROR_OUT_OF_MEMORY;
769 :
770 : // new entry, better initialize
771 15877 : if (!pref->key) {
772 :
773 : // initialize the pref entry
774 9126 : pref->prefFlags.Reset().SetPrefType(type);
775 9126 : pref->key = ArenaStrdup(key, gPrefNameArena);
776 9126 : memset(&pref->defaultPref, 0, sizeof(pref->defaultPref));
777 9126 : memset(&pref->userPref, 0, sizeof(pref->userPref));
778 6751 : } else if (pref->prefFlags.HasDefault() && !pref->prefFlags.IsPrefType(type)) {
779 0 : NS_WARNING(nsPrintfCString("Trying to overwrite value of default pref %s with the wrong type!", key).get());
780 0 : return NS_ERROR_UNEXPECTED;
781 : }
782 :
783 15877 : bool valueChanged = false;
784 15877 : if (flags & kPrefSetDefault) {
785 15239 : if (!pref->prefFlags.IsLocked()) {
786 : /* ?? change of semantics? */
787 23648 : if (pref_ValueChanged(pref->defaultPref, value, type) ||
788 8409 : !pref->prefFlags.HasDefault()) {
789 9083 : pref->prefFlags = pref_SetValue(&pref->defaultPref, pref->prefFlags, value, type).SetHasDefault(true);
790 9083 : if (flags & kPrefStickyDefault) {
791 9 : pref->prefFlags.SetHasStickyDefault(true);
792 : }
793 9083 : if (!pref->prefFlags.HasUserValue()) {
794 9083 : valueChanged = true;
795 : }
796 : }
797 : // What if we change the default to be the same as the user value?
798 : // Should we clear the user value?
799 : }
800 : } else {
801 : /* If new value is same as the default value and it's not a "sticky"
802 : pref, then un-set the user value.
803 : Otherwise, set the user value only if it has changed */
804 1626 : if ((pref->prefFlags.HasDefault()) &&
805 689 : !(pref->prefFlags.HasStickyDefault()) &&
806 1005 : !pref_ValueChanged(pref->defaultPref, value, type) &&
807 28 : !(flags & kPrefForceSet)) {
808 19 : if (pref->prefFlags.HasUserValue()) {
809 : /* XXX should we free a user-set string value if there is one? */
810 0 : pref->prefFlags.SetHasUserValue(false);
811 0 : if (!pref->prefFlags.IsLocked()) {
812 0 : MakeDirtyCallback();
813 0 : valueChanged = true;
814 : }
815 : }
816 1347 : } else if (!pref->prefFlags.HasUserValue() ||
817 728 : !pref->prefFlags.IsPrefType(type) ||
818 109 : pref_ValueChanged(pref->userPref, value, type) ) {
819 520 : pref->prefFlags = pref_SetValue(&pref->userPref, pref->prefFlags, value, type).SetHasUserValue(true);
820 520 : if (!pref->prefFlags.IsLocked()) {
821 520 : MakeDirtyCallback();
822 520 : valueChanged = true;
823 : }
824 : }
825 : }
826 :
827 15877 : if (valueChanged) {
828 9603 : return pref_DoCallback(key);
829 : }
830 6274 : return NS_OK;
831 : }
832 :
833 : size_t
834 0 : pref_SizeOfPrivateData(MallocSizeOf aMallocSizeOf)
835 : {
836 0 : size_t n = gPrefNameArena.SizeOfExcludingThis(aMallocSizeOf);
837 0 : for (struct CallbackNode* node = gFirstCallback; node; node = node->next) {
838 0 : n += aMallocSizeOf(node);
839 0 : n += aMallocSizeOf(node->domain);
840 : }
841 0 : return n;
842 : }
843 :
844 : PrefType
845 710 : PREF_GetPrefType(const char *pref_name)
846 : {
847 710 : if (gHashTable) {
848 710 : PrefHashEntry* pref = pref_HashTableLookup(pref_name);
849 710 : if (pref) {
850 443 : return pref->prefFlags.GetPrefType();
851 : }
852 : }
853 267 : return PrefType::Invalid;
854 : }
855 :
856 : /* -- */
857 :
858 : bool
859 20 : PREF_PrefIsLocked(const char *pref_name)
860 : {
861 20 : bool result = false;
862 20 : if (gIsAnyPrefLocked && gHashTable) {
863 0 : PrefHashEntry* pref = pref_HashTableLookup(pref_name);
864 0 : if (pref && pref->prefFlags.IsLocked()) {
865 0 : result = true;
866 : }
867 : }
868 :
869 20 : return result;
870 : }
871 :
872 : /* Adds a node to the beginning of the callback list. */
873 : void
874 1606 : PREF_RegisterPriorityCallback(const char *pref_node,
875 : PrefChangedFunc callback,
876 : void * instance_data)
877 : {
878 1606 : NS_PRECONDITION(pref_node, "pref_node must not be nullptr");
879 1606 : NS_PRECONDITION(callback, "callback must not be nullptr");
880 :
881 1606 : struct CallbackNode* node = (struct CallbackNode*) malloc(sizeof(struct CallbackNode));
882 1606 : if (node)
883 : {
884 1606 : node->domain = PL_strdup(pref_node);
885 1606 : node->func = callback;
886 1606 : node->data = instance_data;
887 1606 : node->next = gFirstCallback;
888 1606 : gFirstCallback = node;
889 1606 : if (!gLastPriorityNode) {
890 3 : gLastPriorityNode = node;
891 : }
892 : }
893 1606 : }
894 :
895 : /* Adds a node to the end of the callback list. */
896 : void
897 925 : PREF_RegisterCallback(const char *pref_node,
898 : PrefChangedFunc callback,
899 : void * instance_data)
900 : {
901 925 : NS_PRECONDITION(pref_node, "pref_node must not be nullptr");
902 925 : NS_PRECONDITION(callback, "callback must not be nullptr");
903 :
904 925 : struct CallbackNode* node = (struct CallbackNode*) malloc(sizeof(struct CallbackNode));
905 925 : if (node)
906 : {
907 925 : node->domain = PL_strdup(pref_node);
908 925 : node->func = callback;
909 925 : node->data = instance_data;
910 925 : if (gLastPriorityNode) {
911 879 : node->next = gLastPriorityNode->next;
912 879 : gLastPriorityNode->next = node;
913 : } else {
914 46 : node->next = gFirstCallback;
915 46 : gFirstCallback = node;
916 : }
917 : }
918 925 : }
919 :
920 : /* Removes |node| from callback list.
921 : Returns the node after the deleted one. */
922 : struct CallbackNode*
923 3 : pref_RemoveCallbackNode(struct CallbackNode* node,
924 : struct CallbackNode* prev_node)
925 : {
926 3 : NS_PRECONDITION(!prev_node || prev_node->next == node, "invalid params");
927 3 : NS_PRECONDITION(prev_node || gFirstCallback == node, "invalid params");
928 :
929 3 : NS_ASSERTION(!gCallbacksInProgress,
930 : "modifying the callback list while gCallbacksInProgress is true");
931 :
932 3 : struct CallbackNode* next_node = node->next;
933 3 : if (prev_node) {
934 3 : prev_node->next = next_node;
935 : } else {
936 0 : gFirstCallback = next_node;
937 : }
938 3 : if (gLastPriorityNode == node) {
939 0 : gLastPriorityNode = prev_node;
940 : }
941 3 : PL_strfree(node->domain);
942 3 : free(node);
943 3 : return next_node;
944 : }
945 :
946 : /* Deletes a node from the callback list or marks it for deletion. */
947 : nsresult
948 3 : PREF_UnregisterCallback(const char *pref_node,
949 : PrefChangedFunc callback,
950 : void * instance_data)
951 : {
952 3 : nsresult rv = NS_ERROR_FAILURE;
953 3 : struct CallbackNode* node = gFirstCallback;
954 3 : struct CallbackNode* prev_node = nullptr;
955 :
956 5761 : while (node != nullptr)
957 : {
958 4097 : if ( node->func == callback &&
959 1221 : node->data == instance_data &&
960 3 : strcmp(node->domain, pref_node) == 0)
961 : {
962 3 : if (gCallbacksInProgress)
963 : {
964 : // postpone the node removal until after
965 : // callbacks enumeration is finished.
966 0 : node->func = nullptr;
967 0 : gShouldCleanupDeadNodes = true;
968 0 : prev_node = node;
969 0 : node = node->next;
970 : }
971 : else
972 : {
973 3 : node = pref_RemoveCallbackNode(node, prev_node);
974 : }
975 3 : rv = NS_OK;
976 : }
977 : else
978 : {
979 2876 : prev_node = node;
980 2876 : node = node->next;
981 : }
982 : }
983 3 : return rv;
984 : }
985 :
986 9603 : static nsresult pref_DoCallback(const char* changed_pref)
987 : {
988 9603 : nsresult rv = NS_OK;
989 : struct CallbackNode* node;
990 :
991 9603 : bool reentered = gCallbacksInProgress;
992 9603 : gCallbacksInProgress = true;
993 : // Nodes must not be deleted while gCallbacksInProgress is true.
994 : // Nodes that need to be deleted are marked for deletion by nulling
995 : // out the |func| pointer. We release them at the end of this function
996 : // if we haven't reentered.
997 :
998 201682 : for (node = gFirstCallback; node != nullptr; node = node->next)
999 : {
1000 384158 : if ( node->func &&
1001 384158 : PL_strncmp(changed_pref,
1002 192079 : node->domain,
1003 192079 : strlen(node->domain)) == 0 )
1004 : {
1005 59 : (*node->func) (changed_pref, node->data);
1006 : }
1007 : }
1008 :
1009 9603 : gCallbacksInProgress = reentered;
1010 :
1011 9603 : if (gShouldCleanupDeadNodes && !gCallbacksInProgress)
1012 : {
1013 0 : struct CallbackNode* prev_node = nullptr;
1014 0 : node = gFirstCallback;
1015 :
1016 0 : while (node != nullptr)
1017 : {
1018 0 : if (!node->func)
1019 : {
1020 0 : node = pref_RemoveCallbackNode(node, prev_node);
1021 : }
1022 : else
1023 : {
1024 0 : prev_node = node;
1025 0 : node = node->next;
1026 : }
1027 : }
1028 0 : gShouldCleanupDeadNodes = false;
1029 : }
1030 :
1031 9603 : return rv;
1032 : }
1033 :
1034 9303 : void PREF_ReaderCallback(void *closure,
1035 : const char *pref,
1036 : PrefValue value,
1037 : PrefType type,
1038 : bool isDefault,
1039 : bool isStickyDefault)
1040 :
1041 : {
1042 9303 : uint32_t flags = 0;
1043 9303 : if (isDefault) {
1044 9036 : flags |= kPrefSetDefault;
1045 9036 : if (isStickyDefault) {
1046 9 : flags |= kPrefStickyDefault;
1047 : }
1048 : } else {
1049 267 : flags |= kPrefForceSet;
1050 : }
1051 9303 : pref_HashPref(pref, value, type, flags);
1052 9303 : }
|