1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "GlobalStyleSheetCache.h"
8 
9 #include "nsAppDirectoryServiceDefs.h"
10 #include "nsExceptionHandler.h"
11 #include "mozilla/MemoryReporting.h"
12 #include "mozilla/Preferences.h"
13 #include "mozilla/StaticPrefs_layout.h"
14 #include "mozilla/StyleSheet.h"
15 #include "mozilla/StyleSheetInlines.h"
16 #include "mozilla/Telemetry.h"
17 #include "mozilla/css/Loader.h"
18 #include "mozilla/StaticPrefs_browser.h"
19 #include "mozilla/dom/SRIMetadata.h"
20 #include "mozilla/ipc/SharedMemory.h"
21 #include "MainThreadUtils.h"
22 #include "nsColor.h"
23 #include "nsContentUtils.h"
24 #include "nsIConsoleService.h"
25 #include "nsIFile.h"
26 #include "nsIObserverService.h"
27 #include "nsIXULRuntime.h"
28 #include "nsNetUtil.h"
29 #include "nsPresContext.h"
30 #include "nsPrintfCString.h"
31 #include "nsServiceManagerUtils.h"
32 #include "nsXULAppAPI.h"
33 
34 #include <mozilla/ServoBindings.h>
35 
36 namespace mozilla {
37 
38 // The GlobalStyleSheetCache is responsible for sharing user agent style sheet
39 // contents across processes using shared memory.  Here is a high level view of
40 // how that works:
41 //
42 // * In the parent process, in the GlobalStyleSheetCache constructor (which is
43 //   called early on in a process' lifetime), we parse all UA style sheets into
44 //   Gecko StyleSheet objects.
45 //
46 // * The constructor calls InitSharedSheetsInParent, which creates a shared
47 //   memory segment that we know ahead of time will be big enough to store the
48 //   UA sheets.
49 //
50 // * It then creates a Rust SharedMemoryBuilder object and passes it a pointer
51 //   to the start of the shared memory.
52 //
53 // * For each UA sheet, we call Servo_SharedMemoryBuilder_AddStylesheet, which
54 //   takes the StylesheetContents::rules (an Arc<Locked<CssRules>>), produces a
55 //   deep clone of it, and writes that clone into the shared memory:
56 //
57 //   * The deep clone isn't a clone() call, but a call to ToShmem::to_shmem. The
58 //     ToShmem trait must be implemented on every type that is reachable under
59 //     the Arc<Locked<CssRules>>. The to_shmem call for each type will clone the
60 //     value, but any heap allocation will be cloned and placed into the shared
61 //     memory buffer, rather than heap allocated.
62 //
63 //   * For most types, the ToShmem implementation is simple, and we just
64 //     #[derive(ToShmem)] it. For the types that need special handling due to
65 //     having heap allocations (Vec<T>, Box<T>, Arc<T>, etc.) we have impls that
66 //     call to_shmem on the heap allocated data, and then create a new container
67 //     (e.g. using Box::from_raw) that points into the shared memory.
68 //
69 //   * Arc<T> and Locked<T> want to perform atomic writes on data that needs to
70 //     be in the shared memory buffer (the reference count for Arc<T>, and the
71 //     SharedRwLock's AtomicRefCell for Locked<T>), so we add special modes to
72 //     those objects that skip the writes.  For Arc<T>, that means never
73 //     dropping the object since we don't track the reference count.  That's
74 //     fine, since we want to just drop the entire shared memory buffer at
75 //     shutdown.  For Locked<T>, we just panic on attempting to take the lock
76 //     for writing.  That's also fine, since we don't want devtools being able
77 //     to modify UA sheets.
78 //
79 //   * For Atoms in Rust, static atoms are represented by an index into the
80 //     static atom table.  Then if we need to Deref the Atom we look up the
81 //     table.  We panic if any Atom we encounter in the UA style sheets is
82 //     not a static atom.
83 //
84 // * For each UA sheet, we create a new C++ StyleSheet object using the shared
85 //   memory clone of the sheet contents, and throw away the original heap
86 //   allocated one.  (We could avoid creating a new C++ StyleSheet object
87 //   wrapping the shared contents, and update the original StyleSheet object's
88 //   contents, but it's doubtful that matters.)
89 //
90 // * When we initially map the shared memory in the parent process in
91 //   InitSharedSheetsInParent, we choose an address which is far away from the
92 //   current extent of the heap.  Although not too far, since we don't want to
93 //   unnecessarily fragment the virtual address space.
94 //
95 // * In the child process, as early as possible (in
96 //   ContentChild::InitSharedUASheets), we try to map the shared memory at that
97 //   same address, then pass the shared memory buffer to
98 //   GlobalStyleSheetCache::SetSharedMemory.  Since we map at the same
99 //   address, this means any internal pointers in the UA sheets back into the
100 //   shared memory buffer that were written by the parent process are valid in
101 //   the child process too.
102 //
103 // * In practice, mapping at the address we need in the child process this works
104 //   nearly all the time.  If we fail to map at the address we need, the child
105 //   process falls back to parsing and allocating its own copy of the UA sheets.
106 
107 using namespace mozilla;
108 using namespace css;
109 
110 #define PREF_LEGACY_STYLESHEET_CUSTOMIZATION \
111   "toolkit.legacyUserProfileCustomizations.stylesheets"
112 
NS_IMPL_ISUPPORTS(GlobalStyleSheetCache,nsIObserver,nsIMemoryReporter)113 NS_IMPL_ISUPPORTS(GlobalStyleSheetCache, nsIObserver, nsIMemoryReporter)
114 
115 nsresult GlobalStyleSheetCache::Observe(nsISupports* aSubject,
116                                         const char* aTopic,
117                                         const char16_t* aData) {
118   if (!strcmp(aTopic, "profile-before-change")) {
119     mUserContentSheet = nullptr;
120     mUserChromeSheet = nullptr;
121   } else if (!strcmp(aTopic, "profile-do-change")) {
122     InitFromProfile();
123   } else {
124     MOZ_ASSERT_UNREACHABLE("Unexpected observer topic.");
125   }
126   return NS_OK;
127 }
128 
129 #define STYLE_SHEET(identifier_, url_, shared_)                                \
130   NotNull<StyleSheet*> GlobalStyleSheetCache::identifier_##Sheet() {           \
131     if (!m##identifier_##Sheet) {                                              \
132       m##identifier_##Sheet = LoadSheetURL(url_, eAgentSheetFeatures, eCrash); \
133     }                                                                          \
134     return WrapNotNull(m##identifier_##Sheet);                                 \
135   }
136 #include "mozilla/UserAgentStyleSheetList.h"
137 #undef STYLE_SHEET
138 
GetUserContentSheet()139 StyleSheet* GlobalStyleSheetCache::GetUserContentSheet() {
140   return mUserContentSheet;
141 }
142 
GetUserChromeSheet()143 StyleSheet* GlobalStyleSheetCache::GetUserChromeSheet() {
144   return mUserChromeSheet;
145 }
146 
ChromePreferenceSheet()147 StyleSheet* GlobalStyleSheetCache::ChromePreferenceSheet() {
148   if (!mChromePreferenceSheet) {
149     BuildPreferenceSheet(&mChromePreferenceSheet,
150                          PreferenceSheet::ChromePrefs());
151   }
152 
153   return mChromePreferenceSheet;
154 }
155 
ContentPreferenceSheet()156 StyleSheet* GlobalStyleSheetCache::ContentPreferenceSheet() {
157   if (!mContentPreferenceSheet) {
158     BuildPreferenceSheet(&mContentPreferenceSheet,
159                          PreferenceSheet::ContentPrefs());
160   }
161 
162   return mContentPreferenceSheet;
163 }
164 
Shutdown()165 void GlobalStyleSheetCache::Shutdown() {
166   gCSSLoader = nullptr;
167   NS_WARNING_ASSERTION(!gStyleCache || !gUserContentSheetURL,
168                        "Got the URL but never used?");
169   gStyleCache = nullptr;
170   gUserContentSheetURL = nullptr;
171   for (auto& r : URLExtraData::sShared) {
172     r = nullptr;
173   }
174   // We want to leak the shared memory forever, rather than cleaning up all
175   // potential DOM references and such that chrome code may have created.
176 }
177 
SetUserContentCSSURL(nsIURI * aURI)178 void GlobalStyleSheetCache::SetUserContentCSSURL(nsIURI* aURI) {
179   MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
180   gUserContentSheetURL = aURI;
181 }
182 
MOZ_DEFINE_MALLOC_SIZE_OF(LayoutStylesheetCacheMallocSizeOf)183 MOZ_DEFINE_MALLOC_SIZE_OF(LayoutStylesheetCacheMallocSizeOf)
184 
185 NS_IMETHODIMP
186 GlobalStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
187                                       nsISupports* aData, bool aAnonymize) {
188   MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/unshared", KIND_HEAP,
189                      UNITS_BYTES,
190                      SizeOfIncludingThis(LayoutStylesheetCacheMallocSizeOf),
191                      "Memory used for built-in style sheets that are not "
192                      "shared between processes.");
193 
194   if (XRE_IsParentProcess()) {
195     MOZ_COLLECT_REPORT(
196         "explicit/layout/style-sheet-cache/shared", KIND_NONHEAP, UNITS_BYTES,
197         sSharedMemory ? sUsedSharedMemory : 0,
198         "Memory used for built-in style sheets that are shared to "
199         "child processes.");
200   }
201 
202   return NS_OK;
203 }
204 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const205 size_t GlobalStyleSheetCache::SizeOfIncludingThis(
206     MallocSizeOf aMallocSizeOf) const {
207   size_t n = aMallocSizeOf(this);
208 
209 #define MEASURE(s) n += s ? s->SizeOfIncludingThis(aMallocSizeOf) : 0;
210 
211 #define STYLE_SHEET(identifier_, url_, shared_) MEASURE(m##identifier_##Sheet);
212 #include "mozilla/UserAgentStyleSheetList.h"
213 #undef STYLE_SHEET
214 
215   MEASURE(mChromePreferenceSheet);
216   MEASURE(mContentPreferenceSheet);
217   MEASURE(mUserChromeSheet);
218   MEASURE(mUserContentSheet);
219 
220   // Measurement of the following members may be added later if DMD finds it is
221   // worthwhile:
222   // - gCSSLoader
223 
224   return n;
225 }
226 
GlobalStyleSheetCache()227 GlobalStyleSheetCache::GlobalStyleSheetCache() {
228   nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
229   NS_ASSERTION(obsSvc, "No global observer service?");
230 
231   if (obsSvc) {
232     obsSvc->AddObserver(this, "profile-before-change", false);
233     obsSvc->AddObserver(this, "profile-do-change", false);
234   }
235 
236   // Load user style sheets.
237   InitFromProfile();
238 
239   if (XRE_IsParentProcess()) {
240     // We know we need xul.css for the UI, so load that now too:
241     XULSheet();
242   }
243 
244   if (gUserContentSheetURL) {
245     MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
246     mUserContentSheet =
247         LoadSheet(gUserContentSheetURL, eUserSheetFeatures, eLogToConsole);
248     gUserContentSheetURL = nullptr;
249   }
250 
251   // If we are the in the parent process, then we load all of the UA sheets that
252   // are shareable and store them into shared memory.  In both the parent and
253   // the content process, we load these sheets out of shared memory.
254   //
255   // The shared memory buffer's format is a Header object, which contains
256   // internal pointers to each of the shared style sheets, followed by the style
257   // sheets themselves.
258   if (StaticPrefs::layout_css_shared_memory_ua_sheets_enabled()) {
259     if (XRE_IsParentProcess()) {
260       // Load the style sheets and store them in a new shared memory buffer.
261       InitSharedSheetsInParent();
262     } else if (sSharedMemory) {
263       // Use the shared memory handle that was given to us by a SetSharedMemory
264       // call under ContentChild::InitXPCOM.
265       MOZ_ASSERT(sSharedMemory->memory(),
266                  "GlobalStyleSheetCache::SetSharedMemory should have mapped "
267                  "the shared memory");
268     }
269   }
270 
271   // If we get here and we don't have a shared memory handle, then it means
272   // either we failed to create the shared memory buffer in the parent process
273   // (unexpected), or we failed to map the shared memory buffer at the address
274   // we needed in the content process (might happen).
275   //
276   // If sSharedMemory is non-null, but it is not currently mapped, then it means
277   // we are in the parent process, and we failed to re-map the memory after
278   // freezing it.  (We keep sSharedMemory around so that we can still share it
279   // to content processes.)
280   //
281   // In the parent process, this means we'll just leave our eagerly loaded
282   // non-shared sheets in the mFooSheet fields.  In a content process, we'll
283   // lazily load our own copies of the sheets later.
284   if (sSharedMemory) {
285     if (auto header = static_cast<Header*>(sSharedMemory->memory())) {
286       MOZ_RELEASE_ASSERT(header->mMagic == Header::kMagic);
287 
288 #define STYLE_SHEET(identifier_, url_, shared_)                    \
289   if (shared_) {                                                   \
290     LoadSheetFromSharedMemory(url_, &m##identifier_##Sheet,        \
291                               eAgentSheetFeatures, header,         \
292                               UserAgentStyleSheetID::identifier_); \
293   }
294 #include "mozilla/UserAgentStyleSheetList.h"
295 #undef STYLE_SHEET
296     }
297   }
298 }
299 
LoadSheetFromSharedMemory(const char * aURL,RefPtr<StyleSheet> * aSheet,SheetParsingMode aParsingMode,Header * aHeader,UserAgentStyleSheetID aSheetID)300 void GlobalStyleSheetCache::LoadSheetFromSharedMemory(
301     const char* aURL, RefPtr<StyleSheet>* aSheet, SheetParsingMode aParsingMode,
302     Header* aHeader, UserAgentStyleSheetID aSheetID) {
303   auto i = size_t(aSheetID);
304 
305   auto sheet =
306       MakeRefPtr<StyleSheet>(aParsingMode, CORS_NONE, dom::SRIMetadata());
307 
308   nsCOMPtr<nsIURI> uri;
309   MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aURL));
310 
311   sheet->SetPrincipal(nsContentUtils::GetSystemPrincipal());
312   sheet->SetURIs(uri, uri, uri);
313   sheet->SetSharedContents(aHeader->mSheets[i]);
314   sheet->SetComplete();
315 
316   nsCOMPtr<nsIReferrerInfo> referrerInfo =
317       dom::ReferrerInfo::CreateForExternalCSSResources(sheet);
318   sheet->SetReferrerInfo(referrerInfo);
319   URLExtraData::sShared[i] = sheet->URLData();
320 
321   *aSheet = std::move(sheet);
322 }
323 
InitSharedSheetsInParent()324 void GlobalStyleSheetCache::InitSharedSheetsInParent() {
325   MOZ_ASSERT(XRE_IsParentProcess());
326   MOZ_RELEASE_ASSERT(!sSharedMemory);
327 
328   auto shm = MakeUnique<base::SharedMemory>();
329   if (NS_WARN_IF(!shm->CreateFreezeable(kSharedMemorySize))) {
330     return;
331   }
332 
333   // We need to choose an address to map the shared memory in the parent process
334   // that we'll also be able to use in content processes.  There's no way to
335   // pick an address that is guaranteed to be free in future content processes,
336   // so instead we pick an address that is some distance away from current heap
337   // allocations and hope that by the time the content process maps the shared
338   // memory, that address will be free.
339   //
340   // On 64 bit, we have a large amount of address space, so we pick an address
341   // half way through the next 8 GiB of free space, and this has a very good
342   // chance of succeeding.  On 32 bit, address space is more constrained.  We
343   // only have 3 GiB of space to work with, and we don't want to pick a location
344   // right in the middle, since that could cause future large allocations to
345   // fail.  So we pick an address half way through the next 512 MiB of free
346   // space.  Experimentally this seems to work 9 times out of 10; this is good
347   // enough, as it means only 1 in 10 content processes will have its own unique
348   // copies of the UA style sheets, and we're still getting a significant
349   // overall memory saving.
350   //
351   // In theory ASLR could reduce the likelihood of the mapping succeeding in
352   // content processes, due to our expectations of where the heap is being
353   // wrong, but in practice this isn't an issue.
354 #ifdef HAVE_64BIT_BUILD
355   constexpr size_t kOffset = 0x200000000ULL;  // 8 GiB
356 #else
357   constexpr size_t kOffset = 0x20000000;  // 512 MiB
358 #endif
359 
360   void* address = nullptr;
361   if (void* p = base::SharedMemory::FindFreeAddressSpace(2 * kOffset)) {
362     address = reinterpret_cast<void*>(uintptr_t(p) + kOffset);
363   }
364 
365   if (!shm->Map(kSharedMemorySize, address)) {
366     // Failed to map at the address we computed for some reason.  Fall back
367     // to just allocating at a location of the OS's choosing, and hope that
368     // it works in the content process.
369     if (NS_WARN_IF(!shm->Map(kSharedMemorySize))) {
370       return;
371     }
372   }
373   address = shm->memory();
374 
375   auto header = static_cast<Header*>(address);
376   header->mMagic = Header::kMagic;
377 #ifdef DEBUG
378   for (auto ptr : header->mSheets) {
379     MOZ_RELEASE_ASSERT(!ptr, "expected shared memory to have been zeroed");
380   }
381 #endif
382 
383   UniquePtr<RawServoSharedMemoryBuilder> builder(
384       Servo_SharedMemoryBuilder_Create(
385           header->mBuffer, kSharedMemorySize - offsetof(Header, mBuffer)));
386 
387   nsCString message;
388 
389   // Copy each one into the shared memory, and record its pointer.
390   //
391   // Normally calling ToShared on UA sheets should not fail.  It happens
392   // in practice in odd cases that seem like corrupted installations; see bug
393   // 1621773.  On failure, return early and fall back to non-shared sheets.
394 #define STYLE_SHEET(identifier_, url_, shared_)                      \
395   if (shared_) {                                                     \
396     StyleSheet* sheet = identifier_##Sheet();                        \
397     size_t i = size_t(UserAgentStyleSheetID::identifier_);           \
398     URLExtraData::sShared[i] = sheet->URLData();                     \
399     header->mSheets[i] = sheet->ToShared(builder.get(), message);    \
400     if (!header->mSheets[i]) {                                       \
401       CrashReporter::AppendAppNotesToCrashReport("\n"_ns + message); \
402       return;                                                        \
403     }                                                                \
404   }
405 #include "mozilla/UserAgentStyleSheetList.h"
406 #undef STYLE_SHEET
407 
408   // Finished writing into the shared memory.  Freeze it, so that a process
409   // can't confuse other processes by changing the UA style sheet contents.
410   if (NS_WARN_IF(!shm->Freeze())) {
411     return;
412   }
413 
414   // The Freeze() call unmaps the shared memory.  Re-map it again as read only.
415   // If this fails, due to something else being mapped into the same place
416   // between the Freeze() and Map() call, we can just fall back to keeping our
417   // own copy of the UA style sheets in the parent, and still try sending the
418   // shared memory to the content processes.
419   shm->Map(kSharedMemorySize, address);
420 
421   // Record how must of the shared memory we have used, for memory reporting
422   // later.  We round up to the nearest page since the free space at the end
423   // of the page isn't really usable for anything else.
424   //
425   // TODO(heycam): This won't be true on Windows unless we allow creating the
426   // shared memory with SEC_RESERVE so that the pages are reserved but not
427   // committed.
428   size_t pageSize = ipc::SharedMemory::SystemPageSize();
429   sUsedSharedMemory =
430       (Servo_SharedMemoryBuilder_GetLength(builder.get()) + pageSize - 1) &
431       ~(pageSize - 1);
432 
433   sSharedMemory = shm.release();
434 }
435 
~GlobalStyleSheetCache()436 GlobalStyleSheetCache::~GlobalStyleSheetCache() {
437   UnregisterWeakMemoryReporter(this);
438 }
439 
InitMemoryReporter()440 void GlobalStyleSheetCache::InitMemoryReporter() {
441   RegisterWeakMemoryReporter(this);
442 }
443 
444 /* static */
Singleton()445 GlobalStyleSheetCache* GlobalStyleSheetCache::Singleton() {
446   MOZ_ASSERT(NS_IsMainThread());
447 
448   if (!gStyleCache) {
449     gStyleCache = new GlobalStyleSheetCache;
450     gStyleCache->InitMemoryReporter();
451 
452     // For each pref that controls a CSS feature that a UA style sheet depends
453     // on (such as a pref that enables a property that a UA style sheet uses),
454     // register DependentPrefChanged as a callback to ensure that the relevant
455     // style sheets will be re-parsed.
456     // Preferences::RegisterCallback(&DependentPrefChanged,
457     //                               "layout.css.example-pref.enabled");
458   }
459 
460   return gStyleCache;
461 }
462 
InitFromProfile()463 void GlobalStyleSheetCache::InitFromProfile() {
464   if (!Preferences::GetBool(PREF_LEGACY_STYLESHEET_CUSTOMIZATION)) {
465     return;
466   }
467 
468   nsCOMPtr<nsIXULRuntime> appInfo =
469       do_GetService("@mozilla.org/xre/app-info;1");
470   if (appInfo) {
471     bool inSafeMode = false;
472     appInfo->GetInSafeMode(&inSafeMode);
473     if (inSafeMode) return;
474   }
475   nsCOMPtr<nsIFile> contentFile;
476   nsCOMPtr<nsIFile> chromeFile;
477 
478   NS_GetSpecialDirectory(NS_APP_USER_CHROME_DIR, getter_AddRefs(contentFile));
479   if (!contentFile) {
480     // if we don't have a profile yet, that's OK!
481     return;
482   }
483 
484   contentFile->Clone(getter_AddRefs(chromeFile));
485   if (!chromeFile) return;
486 
487   contentFile->Append(u"userContent.css"_ns);
488   chromeFile->Append(u"userChrome.css"_ns);
489 
490   mUserContentSheet = LoadSheetFile(contentFile, eUserSheetFeatures);
491   mUserChromeSheet = LoadSheetFile(chromeFile, eUserSheetFeatures);
492 }
493 
LoadSheetURL(const char * aURL,SheetParsingMode aParsingMode,FailureAction aFailureAction)494 RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheetURL(
495     const char* aURL, SheetParsingMode aParsingMode,
496     FailureAction aFailureAction) {
497   nsCOMPtr<nsIURI> uri;
498   NS_NewURI(getter_AddRefs(uri), aURL);
499   return LoadSheet(uri, aParsingMode, aFailureAction);
500 }
501 
LoadSheetFile(nsIFile * aFile,SheetParsingMode aParsingMode)502 RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheetFile(
503     nsIFile* aFile, SheetParsingMode aParsingMode) {
504   bool exists = false;
505   aFile->Exists(&exists);
506   if (!exists) {
507     return nullptr;
508   }
509 
510   nsCOMPtr<nsIURI> uri;
511   NS_NewFileURI(getter_AddRefs(uri), aFile);
512   return LoadSheet(uri, aParsingMode, eLogToConsole);
513 }
514 
ErrorLoadingSheet(nsIURI * aURI,const char * aMsg,FailureAction aFailureAction)515 static void ErrorLoadingSheet(nsIURI* aURI, const char* aMsg,
516                               FailureAction aFailureAction) {
517   nsPrintfCString errorMessage("%s loading built-in stylesheet '%s'", aMsg,
518                                aURI ? aURI->GetSpecOrDefault().get() : "");
519   if (aFailureAction == eLogToConsole) {
520     nsCOMPtr<nsIConsoleService> cs =
521         do_GetService(NS_CONSOLESERVICE_CONTRACTID);
522     if (cs) {
523       cs->LogStringMessage(NS_ConvertUTF8toUTF16(errorMessage).get());
524       return;
525     }
526   }
527 
528   MOZ_CRASH_UNSAFE(errorMessage.get());
529 }
530 
LoadSheet(nsIURI * aURI,SheetParsingMode aParsingMode,FailureAction aFailureAction)531 RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheet(
532     nsIURI* aURI, SheetParsingMode aParsingMode, FailureAction aFailureAction) {
533   if (!aURI) {
534     ErrorLoadingSheet(aURI, "null URI", eCrash);
535     return nullptr;
536   }
537 
538   if (!gCSSLoader) {
539     gCSSLoader = new Loader;
540   }
541 
542   // Note: The parallel parsing code assume that UA sheets are always loaded
543   // synchronously like they are here, and thus that we'll never attempt
544   // parallel parsing on them. If that ever changes, we'll either need to find a
545   // different way to prohibit parallel parsing for UA sheets, or handle
546   // -moz-bool-pref and various other things in the parallel parsing code.
547   auto result = gCSSLoader->LoadSheetSync(aURI, aParsingMode,
548                                           css::Loader::UseSystemPrincipal::Yes);
549   if (MOZ_UNLIKELY(result.isErr())) {
550     ErrorLoadingSheet(
551         aURI,
552         nsPrintfCString("LoadSheetSync failed with error %" PRIx32,
553                         static_cast<uint32_t>(result.unwrapErr()))
554             .get(),
555         aFailureAction);
556   }
557   return result.unwrapOr(nullptr);
558 }
559 
560 /* static */
InvalidatePreferenceSheets()561 void GlobalStyleSheetCache::InvalidatePreferenceSheets() {
562   if (gStyleCache) {
563     gStyleCache->mContentPreferenceSheet = nullptr;
564     gStyleCache->mChromePreferenceSheet = nullptr;
565   }
566 }
567 
BuildPreferenceSheet(RefPtr<StyleSheet> * aSheet,const PreferenceSheet::Prefs & aPrefs)568 void GlobalStyleSheetCache::BuildPreferenceSheet(
569     RefPtr<StyleSheet>* aSheet, const PreferenceSheet::Prefs& aPrefs) {
570   *aSheet = new StyleSheet(eAgentSheetFeatures, CORS_NONE, dom::SRIMetadata());
571 
572   StyleSheet* sheet = *aSheet;
573 
574   nsCOMPtr<nsIURI> uri;
575   NS_NewURI(getter_AddRefs(uri), "about:PreferenceStyleSheet");
576   MOZ_ASSERT(uri, "URI creation shouldn't fail");
577 
578   sheet->SetURIs(uri, uri, uri);
579   sheet->SetComplete();
580 
581   static const uint32_t kPreallocSize = 1024;
582 
583   nsCString sheetText;
584   sheetText.SetCapacity(kPreallocSize);
585 
586 #define NS_GET_R_G_B(color_) \
587   NS_GET_R(color_), NS_GET_G(color_), NS_GET_B(color_)
588 
589   sheetText.AppendLiteral(
590       "@namespace url(http://www.w3.org/1999/xhtml);\n"
591       "@namespace svg url(http://www.w3.org/2000/svg);\n");
592 
593   // Rules for link styling.
594   const bool underlineLinks = StaticPrefs::browser_underline_anchors();
595   sheetText.AppendPrintf("*|*:any-link%s { text-decoration: %s; }\n",
596                          underlineLinks ? ":not(svg|a)" : "",
597                          underlineLinks ? "underline" : "none");
598 
599   // Rules for focus styling.
600 
601   const bool focusRingOnAnything =
602       StaticPrefs::browser_display_focus_ring_on_anything();
603   uint8_t focusRingWidth = StaticPrefs::browser_display_focus_ring_width();
604   uint8_t focusRingStyle = StaticPrefs::browser_display_focus_ring_style();
605 
606   if ((focusRingWidth != 1 && focusRingWidth <= 4) || focusRingOnAnything) {
607     if (focusRingWidth != 1) {
608       // If the focus ring width is different from the default, fix buttons
609       // with rings.
610       sheetText.AppendPrintf(
611           "button::-moz-focus-inner, input[type=\"reset\"]::-moz-focus-inner, "
612           "input[type=\"button\"]::-moz-focus-inner, "
613           "input[type=\"submit\"]::-moz-focus-inner { "
614           "border: %dpx %s transparent !important; }\n",
615           focusRingWidth, focusRingStyle == 0 ? "solid" : "dotted");
616 
617       sheetText.AppendLiteral(
618           "button:focus::-moz-focus-inner, "
619           "input[type=\"reset\"]:focus::-moz-focus-inner, "
620           "input[type=\"button\"]:focus::-moz-focus-inner, "
621           "input[type=\"submit\"]:focus::-moz-focus-inner { "
622           "border-color: ButtonText !important; }\n");
623     }
624 
625     sheetText.AppendPrintf(
626         "%s { outline: %dpx %s !important; }\n",
627         focusRingOnAnything ? ":focus" : "*|*:link:focus, *|*:visited:focus",
628         focusRingWidth,
629         focusRingStyle == 0 ? "solid -moz-mac-focusring" : "dotted WindowText");
630   }
631 
632   if (StaticPrefs::browser_display_use_focus_colors()) {
633     const auto& colors = aPrefs.mLightColors;
634     nscolor focusText = colors.mFocusText;
635     nscolor focusBG = colors.mFocusBackground;
636     sheetText.AppendPrintf(
637         "*:focus, *:focus > font { color: #%02x%02x%02x !important; "
638         "background-color: #%02x%02x%02x !important; }\n",
639         NS_GET_R_G_B(focusText), NS_GET_R_G_B(focusBG));
640   }
641 
642   NS_ASSERTION(sheetText.Length() <= kPreallocSize,
643                "kPreallocSize should be big enough to build preference style "
644                "sheet without reallocation");
645 
646   // NB: The pref sheet never has @import rules, thus no loader.
647   sheet->ParseSheetSync(nullptr, sheetText,
648                         /* aLoadData = */ nullptr,
649                         /* aLineNumber = */ 0);
650 
651 #undef NS_GET_R_G_B
652 }
653 
AffectedByPref(const nsACString & aPref)654 bool GlobalStyleSheetCache::AffectedByPref(const nsACString& aPref) {
655   const char* prefs[] = {
656       StaticPrefs::GetPrefName_browser_display_show_focus_rings(),
657       StaticPrefs::GetPrefName_browser_display_focus_ring_style(),
658       StaticPrefs::GetPrefName_browser_display_focus_ring_width(),
659       StaticPrefs::GetPrefName_browser_display_focus_ring_on_anything(),
660       StaticPrefs::GetPrefName_browser_display_use_focus_colors(),
661       StaticPrefs::GetPrefName_browser_underline_anchors(),
662   };
663 
664   for (const char* pref : prefs) {
665     if (aPref.Equals(pref)) {
666       return true;
667     }
668   }
669 
670   return false;
671 }
672 
SetSharedMemory(base::SharedMemoryHandle aHandle,uintptr_t aAddress)673 /* static */ void GlobalStyleSheetCache::SetSharedMemory(
674     base::SharedMemoryHandle aHandle, uintptr_t aAddress) {
675   MOZ_ASSERT(!XRE_IsParentProcess());
676   MOZ_ASSERT(!gStyleCache, "Too late, GlobalStyleSheetCache already created!");
677   MOZ_ASSERT(!sSharedMemory, "Shouldn't call this more than once");
678 
679   auto shm = MakeUnique<base::SharedMemory>();
680   if (!shm->SetHandle(std::move(aHandle), /* read_only */ true)) {
681     return;
682   }
683 
684   if (shm->Map(kSharedMemorySize, reinterpret_cast<void*>(aAddress))) {
685     sSharedMemory = shm.release();
686   }
687 }
688 
CloneHandle()689 base::SharedMemoryHandle GlobalStyleSheetCache::CloneHandle() {
690   MOZ_ASSERT(XRE_IsParentProcess());
691   if (sSharedMemory) {
692     return sSharedMemory->CloneHandle();
693   }
694   return nullptr;
695 }
696 
697 StaticRefPtr<GlobalStyleSheetCache> GlobalStyleSheetCache::gStyleCache;
698 StaticRefPtr<css::Loader> GlobalStyleSheetCache::gCSSLoader;
699 StaticRefPtr<nsIURI> GlobalStyleSheetCache::gUserContentSheetURL;
700 
701 StaticAutoPtr<base::SharedMemory> GlobalStyleSheetCache::sSharedMemory;
702 size_t GlobalStyleSheetCache::sUsedSharedMemory;
703 
704 }  // namespace mozilla
705