Backed out changeset 1b14354719c0 (bug 1895254) for causing bustages on NavigationTra...
[gecko.git] / layout / style / GlobalStyleSheetCache.cpp
blob6a8507af7b7bd5734294185ab1b81de5559b1135
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/. */
7 #include "GlobalStyleSheetCache.h"
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/css/Loader.h"
17 #include "mozilla/dom/ReferrerInfo.h"
18 #include "mozilla/dom/SRIMetadata.h"
19 #include "mozilla/ipc/SharedMemory.h"
20 #include "MainThreadUtils.h"
21 #include "nsContentUtils.h"
22 #include "nsIConsoleService.h"
23 #include "nsIFile.h"
24 #include "nsIObserverService.h"
25 #include "nsIXULRuntime.h"
26 #include "nsNetUtil.h"
27 #include "nsPrintfCString.h"
28 #include "nsServiceManagerUtils.h"
29 #include "nsXULAppAPI.h"
31 #include <mozilla/ServoBindings.h>
33 namespace mozilla {
35 // The GlobalStyleSheetCache is responsible for sharing user agent style sheet
36 // contents across processes using shared memory. Here is a high level view of
37 // how that works:
39 // * In the parent process, in the GlobalStyleSheetCache constructor (which is
40 // called early on in a process' lifetime), we parse all UA style sheets into
41 // Gecko StyleSheet objects.
43 // * The constructor calls InitSharedSheetsInParent, which creates a shared
44 // memory segment that we know ahead of time will be big enough to store the
45 // UA sheets.
47 // * It then creates a Rust SharedMemoryBuilder object and passes it a pointer
48 // to the start of the shared memory.
50 // * For each UA sheet, we call Servo_SharedMemoryBuilder_AddStylesheet, which
51 // takes the StylesheetContents::rules (an Arc<Locked<CssRules>>), produces a
52 // deep clone of it, and writes that clone into the shared memory:
54 // * The deep clone isn't a clone() call, but a call to ToShmem::to_shmem. The
55 // ToShmem trait must be implemented on every type that is reachable under
56 // the Arc<Locked<CssRules>>. The to_shmem call for each type will clone the
57 // value, but any heap allocation will be cloned and placed into the shared
58 // memory buffer, rather than heap allocated.
60 // * For most types, the ToShmem implementation is simple, and we just
61 // #[derive(ToShmem)] it. For the types that need special handling due to
62 // having heap allocations (Vec<T>, Box<T>, Arc<T>, etc.) we have impls that
63 // call to_shmem on the heap allocated data, and then create a new container
64 // (e.g. using Box::from_raw) that points into the shared memory.
66 // * Arc<T> and Locked<T> want to perform atomic writes on data that needs to
67 // be in the shared memory buffer (the reference count for Arc<T>, and the
68 // SharedRwLock's AtomicRefCell for Locked<T>), so we add special modes to
69 // those objects that skip the writes. For Arc<T>, that means never
70 // dropping the object since we don't track the reference count. That's
71 // fine, since we want to just drop the entire shared memory buffer at
72 // shutdown. For Locked<T>, we just panic on attempting to take the lock
73 // for writing. That's also fine, since we don't want devtools being able
74 // to modify UA sheets.
76 // * For Atoms in Rust, static atoms are represented by an index into the
77 // static atom table. Then if we need to Deref the Atom we look up the
78 // table. We panic if any Atom we encounter in the UA style sheets is
79 // not a static atom.
81 // * For each UA sheet, we create a new C++ StyleSheet object using the shared
82 // memory clone of the sheet contents, and throw away the original heap
83 // allocated one. (We could avoid creating a new C++ StyleSheet object
84 // wrapping the shared contents, and update the original StyleSheet object's
85 // contents, but it's doubtful that matters.)
87 // * When we initially map the shared memory in the parent process in
88 // InitSharedSheetsInParent, we choose an address which is far away from the
89 // current extent of the heap. Although not too far, since we don't want to
90 // unnecessarily fragment the virtual address space.
92 // * In the child process, as early as possible (in
93 // ContentChild::InitSharedUASheets), we try to map the shared memory at that
94 // same address, then pass the shared memory buffer to
95 // GlobalStyleSheetCache::SetSharedMemory. Since we map at the same
96 // address, this means any internal pointers in the UA sheets back into the
97 // shared memory buffer that were written by the parent process are valid in
98 // the child process too.
100 // * In practice, mapping at the address we need in the child process this works
101 // nearly all the time. If we fail to map at the address we need, the child
102 // process falls back to parsing and allocating its own copy of the UA sheets.
104 using namespace mozilla;
105 using namespace css;
107 #define PREF_LEGACY_STYLESHEET_CUSTOMIZATION \
108 "toolkit.legacyUserProfileCustomizations.stylesheets"
110 NS_IMPL_ISUPPORTS(GlobalStyleSheetCache, nsIObserver, nsIMemoryReporter)
112 nsresult GlobalStyleSheetCache::Observe(nsISupports* aSubject,
113 const char* aTopic,
114 const char16_t* aData) {
115 if (!strcmp(aTopic, "profile-before-change")) {
116 mUserContentSheet = nullptr;
117 mUserChromeSheet = nullptr;
118 } else if (!strcmp(aTopic, "profile-do-change")) {
119 InitFromProfile();
120 } else {
121 MOZ_ASSERT_UNREACHABLE("Unexpected observer topic.");
123 return NS_OK;
126 #define STYLE_SHEET(identifier_, url_, shared_) \
127 NotNull<StyleSheet*> GlobalStyleSheetCache::identifier_##Sheet() { \
128 if (!m##identifier_##Sheet) { \
129 m##identifier_##Sheet = LoadSheetURL(url_, eAgentSheetFeatures, eCrash); \
131 return WrapNotNull(m##identifier_##Sheet); \
133 #include "mozilla/UserAgentStyleSheetList.h"
134 #undef STYLE_SHEET
136 StyleSheet* GlobalStyleSheetCache::GetUserContentSheet() {
137 return mUserContentSheet;
140 StyleSheet* GlobalStyleSheetCache::GetUserChromeSheet() {
141 return mUserChromeSheet;
144 void GlobalStyleSheetCache::Shutdown() {
145 gCSSLoader = nullptr;
146 NS_WARNING_ASSERTION(!gStyleCache || !gUserContentSheetURL,
147 "Got the URL but never used?");
148 gStyleCache = nullptr;
149 gUserContentSheetURL = nullptr;
150 for (auto& r : URLExtraData::sShared) {
151 r = nullptr;
153 // We want to leak the shared memory forever, rather than cleaning up all
154 // potential DOM references and such that chrome code may have created.
157 void GlobalStyleSheetCache::SetUserContentCSSURL(nsIURI* aURI) {
158 MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
159 gUserContentSheetURL = aURI;
162 MOZ_DEFINE_MALLOC_SIZE_OF(LayoutStylesheetCacheMallocSizeOf)
164 NS_IMETHODIMP
165 GlobalStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
166 nsISupports* aData, bool aAnonymize) {
167 MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/unshared", KIND_HEAP,
168 UNITS_BYTES,
169 SizeOfIncludingThis(LayoutStylesheetCacheMallocSizeOf),
170 "Memory used for built-in style sheets that are not "
171 "shared between processes.");
173 if (XRE_IsParentProcess()) {
174 MOZ_COLLECT_REPORT(
175 "explicit/layout/style-sheet-cache/shared", KIND_NONHEAP, UNITS_BYTES,
176 sSharedMemory ? sUsedSharedMemory : 0,
177 "Memory used for built-in style sheets that are shared to "
178 "child processes.");
181 return NS_OK;
184 size_t GlobalStyleSheetCache::SizeOfIncludingThis(
185 MallocSizeOf aMallocSizeOf) const {
186 size_t n = aMallocSizeOf(this);
188 #define MEASURE(s) n += s ? s->SizeOfIncludingThis(aMallocSizeOf) : 0;
190 #define STYLE_SHEET(identifier_, url_, shared_) MEASURE(m##identifier_##Sheet);
191 #include "mozilla/UserAgentStyleSheetList.h"
192 #undef STYLE_SHEET
194 MEASURE(mUserChromeSheet);
195 MEASURE(mUserContentSheet);
197 // Measurement of the following members may be added later if DMD finds it is
198 // worthwhile:
199 // - gCSSLoader
201 return n;
204 GlobalStyleSheetCache::GlobalStyleSheetCache() {
205 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
206 NS_ASSERTION(obsSvc, "No global observer service?");
208 if (obsSvc) {
209 obsSvc->AddObserver(this, "profile-before-change", false);
210 obsSvc->AddObserver(this, "profile-do-change", false);
213 // Load user style sheets.
214 InitFromProfile();
216 if (XRE_IsParentProcess()) {
217 // We know we need xul.css for the UI, so load that now too:
218 XULSheet();
221 if (gUserContentSheetURL) {
222 MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
223 mUserContentSheet =
224 LoadSheet(gUserContentSheetURL, eUserSheetFeatures, eLogToConsole);
225 gUserContentSheetURL = nullptr;
228 // If we are the in the parent process, then we load all of the UA sheets that
229 // are shareable and store them into shared memory. In both the parent and
230 // the content process, we load these sheets out of shared memory.
232 // The shared memory buffer's format is a Header object, which contains
233 // internal pointers to each of the shared style sheets, followed by the style
234 // sheets themselves.
235 if (StaticPrefs::layout_css_shared_memory_ua_sheets_enabled()) {
236 if (XRE_IsParentProcess()) {
237 // Load the style sheets and store them in a new shared memory buffer.
238 InitSharedSheetsInParent();
239 } else if (sSharedMemory) {
240 // Use the shared memory handle that was given to us by a SetSharedMemory
241 // call under ContentChild::InitXPCOM.
242 MOZ_ASSERT(sSharedMemory->memory(),
243 "GlobalStyleSheetCache::SetSharedMemory should have mapped "
244 "the shared memory");
248 // If we get here and we don't have a shared memory handle, then it means
249 // either we failed to create the shared memory buffer in the parent process
250 // (unexpected), or we failed to map the shared memory buffer at the address
251 // we needed in the content process (might happen).
253 // If sSharedMemory is non-null, but it is not currently mapped, then it means
254 // we are in the parent process, and we failed to re-map the memory after
255 // freezing it. (We keep sSharedMemory around so that we can still share it
256 // to content processes.)
258 // In the parent process, this means we'll just leave our eagerly loaded
259 // non-shared sheets in the mFooSheet fields. In a content process, we'll
260 // lazily load our own copies of the sheets later.
261 if (sSharedMemory) {
262 if (auto* header = static_cast<Header*>(sSharedMemory->memory())) {
263 MOZ_RELEASE_ASSERT(header->mMagic == Header::kMagic);
265 #define STYLE_SHEET(identifier_, url_, shared_) \
266 if (shared_) { \
267 LoadSheetFromSharedMemory(url_, &m##identifier_##Sheet, \
268 eAgentSheetFeatures, header, \
269 UserAgentStyleSheetID::identifier_); \
271 #include "mozilla/UserAgentStyleSheetList.h"
272 #undef STYLE_SHEET
277 void GlobalStyleSheetCache::LoadSheetFromSharedMemory(
278 const char* aURL, RefPtr<StyleSheet>* aSheet, SheetParsingMode aParsingMode,
279 Header* aHeader, UserAgentStyleSheetID aSheetID) {
280 auto i = size_t(aSheetID);
282 auto sheet =
283 MakeRefPtr<StyleSheet>(aParsingMode, CORS_NONE, dom::SRIMetadata());
285 nsCOMPtr<nsIURI> uri;
286 MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aURL));
288 sheet->SetPrincipal(nsContentUtils::GetSystemPrincipal());
289 sheet->SetURIs(uri, uri, uri);
290 nsCOMPtr<nsIReferrerInfo> referrerInfo =
291 dom::ReferrerInfo::CreateForExternalCSSResources(sheet);
292 sheet->SetReferrerInfo(referrerInfo);
293 sheet->SetSharedContents(aHeader->mSheets[i]);
294 sheet->SetComplete();
295 URLExtraData::sShared[i] = sheet->URLData();
297 *aSheet = std::move(sheet);
300 void GlobalStyleSheetCache::InitSharedSheetsInParent() {
301 MOZ_ASSERT(XRE_IsParentProcess());
302 MOZ_RELEASE_ASSERT(!sSharedMemory);
304 auto shm = MakeUnique<base::SharedMemory>();
305 if (NS_WARN_IF(!shm->CreateFreezeable(kSharedMemorySize))) {
306 return;
309 // We need to choose an address to map the shared memory in the parent process
310 // that we'll also be able to use in content processes. There's no way to
311 // pick an address that is guaranteed to be free in future content processes,
312 // so instead we pick an address that is some distance away from current heap
313 // allocations and hope that by the time the content process maps the shared
314 // memory, that address will be free.
316 // On 64 bit, we have a large amount of address space, so we pick an address
317 // half way through the next 8 GiB of free space, and this has a very good
318 // chance of succeeding. On 32 bit, address space is more constrained. We
319 // only have 3 GiB of space to work with, and we don't want to pick a location
320 // right in the middle, since that could cause future large allocations to
321 // fail. So we pick an address half way through the next 512 MiB of free
322 // space. Experimentally this seems to work 9 times out of 10; this is good
323 // enough, as it means only 1 in 10 content processes will have its own unique
324 // copies of the UA style sheets, and we're still getting a significant
325 // overall memory saving.
327 // In theory ASLR could reduce the likelihood of the mapping succeeding in
328 // content processes, due to our expectations of where the heap is being
329 // wrong, but in practice this isn't an issue.
330 #ifdef HAVE_64BIT_BUILD
331 constexpr size_t kOffset = 0x200000000ULL; // 8 GiB
332 #else
333 constexpr size_t kOffset = 0x20000000; // 512 MiB
334 #endif
336 void* address = nullptr;
337 if (void* p = base::SharedMemory::FindFreeAddressSpace(2 * kOffset)) {
338 address = reinterpret_cast<void*>(uintptr_t(p) + kOffset);
341 if (!shm->Map(kSharedMemorySize, address)) {
342 // Failed to map at the address we computed for some reason. Fall back
343 // to just allocating at a location of the OS's choosing, and hope that
344 // it works in the content process.
345 if (NS_WARN_IF(!shm->Map(kSharedMemorySize))) {
346 return;
349 address = shm->memory();
351 auto* header = static_cast<Header*>(address);
352 header->mMagic = Header::kMagic;
353 #ifdef DEBUG
354 for (const auto* ptr : header->mSheets) {
355 MOZ_RELEASE_ASSERT(!ptr, "expected shared memory to have been zeroed");
357 #endif
359 UniquePtr<StyleSharedMemoryBuilder> builder(Servo_SharedMemoryBuilder_Create(
360 header->mBuffer, kSharedMemorySize - offsetof(Header, mBuffer)));
362 nsCString message;
364 // Copy each one into the shared memory, and record its pointer.
366 // Normally calling ToShared on UA sheets should not fail. It happens
367 // in practice in odd cases that seem like corrupted installations; see bug
368 // 1621773. On failure, return early and fall back to non-shared sheets.
369 #define STYLE_SHEET(identifier_, url_, shared_) \
370 if (shared_) { \
371 StyleSheet* sheet = identifier_##Sheet(); \
372 size_t i = size_t(UserAgentStyleSheetID::identifier_); \
373 URLExtraData::sShared[i] = sheet->URLData(); \
374 header->mSheets[i] = sheet->ToShared(builder.get(), message); \
375 if (!header->mSheets[i]) { \
376 CrashReporter::AppendAppNotesToCrashReport("\n"_ns + message); \
377 return; \
380 #include "mozilla/UserAgentStyleSheetList.h"
381 #undef STYLE_SHEET
383 // Finished writing into the shared memory. Freeze it, so that a process
384 // can't confuse other processes by changing the UA style sheet contents.
385 if (NS_WARN_IF(!shm->Freeze())) {
386 return;
389 // The Freeze() call unmaps the shared memory. Re-map it again as read only.
390 // If this fails, due to something else being mapped into the same place
391 // between the Freeze() and Map() call, we can just fall back to keeping our
392 // own copy of the UA style sheets in the parent, and still try sending the
393 // shared memory to the content processes.
394 shm->Map(kSharedMemorySize, address);
396 // Record how must of the shared memory we have used, for memory reporting
397 // later. We round up to the nearest page since the free space at the end
398 // of the page isn't really usable for anything else.
400 // TODO(heycam): This won't be true on Windows unless we allow creating the
401 // shared memory with SEC_RESERVE so that the pages are reserved but not
402 // committed.
403 size_t pageSize = ipc::SharedMemory::SystemPageSize();
404 sUsedSharedMemory =
405 (Servo_SharedMemoryBuilder_GetLength(builder.get()) + pageSize - 1) &
406 ~(pageSize - 1);
408 sSharedMemory = shm.release();
411 GlobalStyleSheetCache::~GlobalStyleSheetCache() {
412 UnregisterWeakMemoryReporter(this);
415 void GlobalStyleSheetCache::InitMemoryReporter() {
416 RegisterWeakMemoryReporter(this);
419 /* static */
420 GlobalStyleSheetCache* GlobalStyleSheetCache::Singleton() {
421 MOZ_ASSERT(NS_IsMainThread());
423 if (!gStyleCache) {
424 gStyleCache = new GlobalStyleSheetCache;
425 gStyleCache->InitMemoryReporter();
427 // For each pref that controls a CSS feature that a UA style sheet depends
428 // on (such as a pref that enables a property that a UA style sheet uses),
429 // register DependentPrefChanged as a callback to ensure that the relevant
430 // style sheets will be re-parsed.
431 // Preferences::RegisterCallback(&DependentPrefChanged,
432 // "layout.css.example-pref.enabled");
435 return gStyleCache;
438 void GlobalStyleSheetCache::InitFromProfile() {
439 if (!Preferences::GetBool(PREF_LEGACY_STYLESHEET_CUSTOMIZATION)) {
440 return;
443 nsCOMPtr<nsIXULRuntime> appInfo =
444 do_GetService("@mozilla.org/xre/app-info;1");
445 if (appInfo) {
446 bool inSafeMode = false;
447 appInfo->GetInSafeMode(&inSafeMode);
448 if (inSafeMode) return;
450 nsCOMPtr<nsIFile> contentFile;
451 nsCOMPtr<nsIFile> chromeFile;
453 NS_GetSpecialDirectory(NS_APP_USER_CHROME_DIR, getter_AddRefs(contentFile));
454 if (!contentFile) {
455 // if we don't have a profile yet, that's OK!
456 return;
459 contentFile->Clone(getter_AddRefs(chromeFile));
460 if (!chromeFile) return;
462 contentFile->Append(u"userContent.css"_ns);
463 chromeFile->Append(u"userChrome.css"_ns);
465 mUserContentSheet = LoadSheetFile(contentFile, eUserSheetFeatures);
466 mUserChromeSheet = LoadSheetFile(chromeFile, eUserSheetFeatures);
469 RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheetURL(
470 const char* aURL, SheetParsingMode aParsingMode,
471 FailureAction aFailureAction) {
472 nsCOMPtr<nsIURI> uri;
473 NS_NewURI(getter_AddRefs(uri), aURL);
474 return LoadSheet(uri, aParsingMode, aFailureAction);
477 RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheetFile(
478 nsIFile* aFile, SheetParsingMode aParsingMode) {
479 bool exists = false;
480 aFile->Exists(&exists);
481 if (!exists) {
482 return nullptr;
485 nsCOMPtr<nsIURI> uri;
486 NS_NewFileURI(getter_AddRefs(uri), aFile);
487 return LoadSheet(uri, aParsingMode, eLogToConsole);
490 static void ErrorLoadingSheet(nsIURI* aURI, const char* aMsg,
491 FailureAction aFailureAction) {
492 nsPrintfCString errorMessage("%s loading built-in stylesheet '%s'", aMsg,
493 aURI ? aURI->GetSpecOrDefault().get() : "");
494 if (aFailureAction == eLogToConsole) {
495 nsCOMPtr<nsIConsoleService> cs =
496 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
497 if (cs) {
498 cs->LogStringMessage(NS_ConvertUTF8toUTF16(errorMessage).get());
499 return;
503 MOZ_CRASH_UNSAFE(errorMessage.get());
506 RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheet(
507 nsIURI* aURI, SheetParsingMode aParsingMode, FailureAction aFailureAction) {
508 if (!aURI) {
509 ErrorLoadingSheet(aURI, "null URI", eCrash);
510 return nullptr;
513 if (!gCSSLoader) {
514 gCSSLoader = new Loader;
517 auto result = gCSSLoader->LoadSheetSync(aURI, aParsingMode,
518 css::Loader::UseSystemPrincipal::Yes);
519 if (MOZ_UNLIKELY(result.isErr())) {
520 ErrorLoadingSheet(
521 aURI,
522 nsPrintfCString("LoadSheetSync failed with error %" PRIx32,
523 static_cast<uint32_t>(result.unwrapErr()))
524 .get(),
525 aFailureAction);
527 return result.unwrapOr(nullptr);
530 /* static */ void GlobalStyleSheetCache::SetSharedMemory(
531 base::SharedMemoryHandle aHandle, uintptr_t aAddress) {
532 MOZ_ASSERT(!XRE_IsParentProcess());
533 MOZ_ASSERT(!gStyleCache, "Too late, GlobalStyleSheetCache already created!");
534 MOZ_ASSERT(!sSharedMemory, "Shouldn't call this more than once");
536 auto shm = MakeUnique<base::SharedMemory>();
537 if (!shm->SetHandle(std::move(aHandle), /* read_only */ true)) {
538 return;
541 if (shm->Map(kSharedMemorySize, reinterpret_cast<void*>(aAddress))) {
542 sSharedMemory = shm.release();
546 base::SharedMemoryHandle GlobalStyleSheetCache::CloneHandle() {
547 MOZ_ASSERT(XRE_IsParentProcess());
548 if (sSharedMemory) {
549 return sSharedMemory->CloneHandle();
551 return nullptr;
554 StaticRefPtr<GlobalStyleSheetCache> GlobalStyleSheetCache::gStyleCache;
555 StaticRefPtr<css::Loader> GlobalStyleSheetCache::gCSSLoader;
556 StaticRefPtr<nsIURI> GlobalStyleSheetCache::gUserContentSheetURL;
558 StaticAutoPtr<base::SharedMemory> GlobalStyleSheetCache::sSharedMemory;
559 size_t GlobalStyleSheetCache::sUsedSharedMemory;
561 } // namespace mozilla