summaryrefslogtreecommitdiffstats
path: root/layout/style/GlobalStyleSheetCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/GlobalStyleSheetCache.cpp')
-rw-r--r--layout/style/GlobalStyleSheetCache.cpp715
1 files changed, 715 insertions, 0 deletions
diff --git a/layout/style/GlobalStyleSheetCache.cpp b/layout/style/GlobalStyleSheetCache.cpp
new file mode 100644
index 0000000000..07df709249
--- /dev/null
+++ b/layout/style/GlobalStyleSheetCache.cpp
@@ -0,0 +1,715 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GlobalStyleSheetCache.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "MainThreadUtils.h"
+#include "nsColor.h"
+#include "nsContentUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULAppAPI.h"
+
+#include <mozilla/ServoBindings.h>
+
+namespace mozilla {
+
+// The GlobalStyleSheetCache is responsible for sharing user agent style sheet
+// contents across processes using shared memory. Here is a high level view of
+// how that works:
+//
+// * In the parent process, in the GlobalStyleSheetCache constructor (which is
+// called early on in a process' lifetime), we parse all UA style sheets into
+// Gecko StyleSheet objects.
+//
+// * The constructor calls InitSharedSheetsInParent, which creates a shared
+// memory segment that we know ahead of time will be big enough to store the
+// UA sheets.
+//
+// * It then creates a Rust SharedMemoryBuilder object and passes it a pointer
+// to the start of the shared memory.
+//
+// * For each UA sheet, we call Servo_SharedMemoryBuilder_AddStylesheet, which
+// takes the StylesheetContents::rules (an Arc<Locked<CssRules>>), produces a
+// deep clone of it, and writes that clone into the shared memory:
+//
+// * The deep clone isn't a clone() call, but a call to ToShmem::to_shmem. The
+// ToShmem trait must be implemented on every type that is reachable under
+// the Arc<Locked<CssRules>>. The to_shmem call for each type will clone the
+// value, but any heap allocation will be cloned and placed into the shared
+// memory buffer, rather than heap allocated.
+//
+// * For most types, the ToShmem implementation is simple, and we just
+// #[derive(ToShmem)] it. For the types that need special handling due to
+// having heap allocations (Vec<T>, Box<T>, Arc<T>, etc.) we have impls that
+// call to_shmem on the heap allocated data, and then create a new container
+// (e.g. using Box::from_raw) that points into the shared memory.
+//
+// * Arc<T> and Locked<T> want to perform atomic writes on data that needs to
+// be in the shared memory buffer (the reference count for Arc<T>, and the
+// SharedRwLock's AtomicRefCell for Locked<T>), so we add special modes to
+// those objects that skip the writes. For Arc<T>, that means never
+// dropping the object since we don't track the reference count. That's
+// fine, since we want to just drop the entire shared memory buffer at
+// shutdown. For Locked<T>, we just panic on attempting to take the lock
+// for writing. That's also fine, since we don't want devtools being able
+// to modify UA sheets.
+//
+// * For Atoms in Rust, static atoms are represented by an index into the
+// static atom table. Then if we need to Deref the Atom we look up the
+// table. We panic if any Atom we encounter in the UA style sheets is
+// not a static atom.
+//
+// * For each UA sheet, we create a new C++ StyleSheet object using the shared
+// memory clone of the sheet contents, and throw away the original heap
+// allocated one. (We could avoid creating a new C++ StyleSheet object
+// wrapping the shared contents, and update the original StyleSheet object's
+// contents, but it's doubtful that matters.)
+//
+// * When we initially map the shared memory in the parent process in
+// InitSharedSheetsInParent, we choose an address which is far away from the
+// current extent of the heap. Although not too far, since we don't want to
+// unnecessarily fragment the virtual address space.
+//
+// * In the child process, as early as possible (in
+// ContentChild::InitSharedUASheets), we try to map the shared memory at that
+// same address, then pass the shared memory buffer to
+// GlobalStyleSheetCache::SetSharedMemory. Since we map at the same
+// address, this means any internal pointers in the UA sheets back into the
+// shared memory buffer that were written by the parent process are valid in
+// the child process too.
+//
+// * In practice, mapping at the address we need in the child process this works
+// nearly all the time. If we fail to map at the address we need, the child
+// process falls back to parsing and allocating its own copy of the UA sheets.
+
+using namespace mozilla;
+using namespace css;
+
+#define PREF_LEGACY_STYLESHEET_CUSTOMIZATION \
+ "toolkit.legacyUserProfileCustomizations.stylesheets"
+
+NS_IMPL_ISUPPORTS(GlobalStyleSheetCache, nsIObserver, nsIMemoryReporter)
+
+nsresult GlobalStyleSheetCache::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "profile-before-change")) {
+ mUserContentSheet = nullptr;
+ mUserChromeSheet = nullptr;
+ } else if (!strcmp(aTopic, "profile-do-change")) {
+ InitFromProfile();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected observer topic.");
+ }
+ return NS_OK;
+}
+
+#define STYLE_SHEET(identifier_, url_, shared_) \
+ NotNull<StyleSheet*> GlobalStyleSheetCache::identifier_##Sheet() { \
+ if (!m##identifier_##Sheet) { \
+ m##identifier_##Sheet = LoadSheetURL(url_, eAgentSheetFeatures, eCrash); \
+ } \
+ return WrapNotNull(m##identifier_##Sheet); \
+ }
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+
+StyleSheet* GlobalStyleSheetCache::GetUserContentSheet() {
+ return mUserContentSheet;
+}
+
+StyleSheet* GlobalStyleSheetCache::GetUserChromeSheet() {
+ return mUserChromeSheet;
+}
+
+StyleSheet* GlobalStyleSheetCache::ChromePreferenceSheet() {
+ if (!mChromePreferenceSheet) {
+ BuildPreferenceSheet(&mChromePreferenceSheet,
+ PreferenceSheet::ChromePrefs());
+ }
+
+ return mChromePreferenceSheet;
+}
+
+StyleSheet* GlobalStyleSheetCache::ContentPreferenceSheet() {
+ if (!mContentPreferenceSheet) {
+ BuildPreferenceSheet(&mContentPreferenceSheet,
+ PreferenceSheet::ContentPrefs());
+ }
+
+ return mContentPreferenceSheet;
+}
+
+void GlobalStyleSheetCache::Shutdown() {
+ gCSSLoader = nullptr;
+ NS_WARNING_ASSERTION(!gStyleCache || !gUserContentSheetURL,
+ "Got the URL but never used?");
+ gStyleCache = nullptr;
+ gUserContentSheetURL = nullptr;
+ for (auto& r : URLExtraData::sShared) {
+ r = nullptr;
+ }
+ // We want to leak the shared memory forever, rather than cleaning up all
+ // potential DOM references and such that chrome code may have created.
+}
+
+void GlobalStyleSheetCache::SetUserContentCSSURL(nsIURI* aURI) {
+ MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
+ gUserContentSheetURL = aURI;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(LayoutStylesheetCacheMallocSizeOf)
+
+NS_IMETHODIMP
+GlobalStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/unshared", KIND_HEAP,
+ UNITS_BYTES,
+ SizeOfIncludingThis(LayoutStylesheetCacheMallocSizeOf),
+ "Memory used for built-in style sheets that are not "
+ "shared between processes.");
+
+ if (XRE_IsParentProcess()) {
+ MOZ_COLLECT_REPORT(
+ "explicit/layout/style-sheet-cache/shared", KIND_NONHEAP, UNITS_BYTES,
+ sSharedMemory ? sUsedSharedMemory : 0,
+ "Memory used for built-in style sheets that are shared to "
+ "child processes.");
+ }
+
+ return NS_OK;
+}
+
+size_t GlobalStyleSheetCache::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+#define MEASURE(s) n += s ? s->SizeOfIncludingThis(aMallocSizeOf) : 0;
+
+#define STYLE_SHEET(identifier_, url_, shared_) MEASURE(m##identifier_##Sheet);
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+
+ MEASURE(mChromePreferenceSheet);
+ MEASURE(mContentPreferenceSheet);
+ MEASURE(mUserChromeSheet);
+ MEASURE(mUserContentSheet);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - gCSSLoader
+
+ return n;
+}
+
+GlobalStyleSheetCache::GlobalStyleSheetCache() {
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ NS_ASSERTION(obsSvc, "No global observer service?");
+
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "profile-before-change", false);
+ obsSvc->AddObserver(this, "profile-do-change", false);
+ }
+
+ // Load user style sheets.
+ InitFromProfile();
+
+ if (XRE_IsParentProcess()) {
+ // We know we need xul.css for the UI, so load that now too:
+ XULSheet();
+ }
+
+ if (gUserContentSheetURL) {
+ MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
+ mUserContentSheet =
+ LoadSheet(gUserContentSheetURL, eUserSheetFeatures, eLogToConsole);
+ gUserContentSheetURL = nullptr;
+ }
+
+ // If we are the in the parent process, then we load all of the UA sheets that
+ // are shareable and store them into shared memory. In both the parent and
+ // the content process, we load these sheets out of shared memory.
+ //
+ // The shared memory buffer's format is a Header object, which contains
+ // internal pointers to each of the shared style sheets, followed by the style
+ // sheets themselves.
+ if (StaticPrefs::layout_css_shared_memory_ua_sheets_enabled()) {
+ if (XRE_IsParentProcess()) {
+ // Load the style sheets and store them in a new shared memory buffer.
+ InitSharedSheetsInParent();
+ } else if (sSharedMemory) {
+ // Use the shared memory handle that was given to us by a SetSharedMemory
+ // call under ContentChild::InitXPCOM.
+ MOZ_ASSERT(sSharedMemory->memory(),
+ "GlobalStyleSheetCache::SetSharedMemory should have mapped "
+ "the shared memory");
+ }
+ }
+
+ // If we get here and we don't have a shared memory handle, then it means
+ // either we failed to create the shared memory buffer in the parent process
+ // (unexpected), or we failed to map the shared memory buffer at the address
+ // we needed in the content process (might happen).
+ //
+ // If sSharedMemory is non-null, but it is not currently mapped, then it means
+ // we are in the parent process, and we failed to re-map the memory after
+ // freezing it. (We keep sSharedMemory around so that we can still share it
+ // to content processes.)
+ //
+ // In the parent process, this means we'll just leave our eagerly loaded
+ // non-shared sheets in the mFooSheet fields. In a content process, we'll
+ // lazily load our own copies of the sheets later.
+ if (sSharedMemory) {
+ if (auto header = static_cast<Header*>(sSharedMemory->memory())) {
+ MOZ_RELEASE_ASSERT(header->mMagic == Header::kMagic);
+
+#define STYLE_SHEET(identifier_, url_, shared_) \
+ if (shared_) { \
+ LoadSheetFromSharedMemory(url_, &m##identifier_##Sheet, \
+ eAgentSheetFeatures, header, \
+ UserAgentStyleSheetID::identifier_); \
+ }
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+ }
+ }
+}
+
+void GlobalStyleSheetCache::LoadSheetFromSharedMemory(
+ const char* aURL, RefPtr<StyleSheet>* aSheet, SheetParsingMode aParsingMode,
+ Header* aHeader, UserAgentStyleSheetID aSheetID) {
+ auto i = size_t(aSheetID);
+
+ auto sheet =
+ MakeRefPtr<StyleSheet>(aParsingMode, CORS_NONE, dom::SRIMetadata());
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aURL));
+
+ sheet->SetPrincipal(nsContentUtils::GetSystemPrincipal());
+ sheet->SetURIs(uri, uri, uri);
+ sheet->SetSharedContents(aHeader->mSheets[i]);
+ sheet->SetComplete();
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ dom::ReferrerInfo::CreateForExternalCSSResources(sheet);
+ sheet->SetReferrerInfo(referrerInfo);
+ URLExtraData::sShared[i] = sheet->URLData();
+
+ *aSheet = std::move(sheet);
+}
+
+void GlobalStyleSheetCache::InitSharedSheetsInParent() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_RELEASE_ASSERT(!sSharedMemory);
+
+ auto shm = MakeUnique<base::SharedMemory>();
+ if (NS_WARN_IF(!shm->CreateFreezeable(kSharedMemorySize))) {
+ return;
+ }
+
+ // We need to choose an address to map the shared memory in the parent process
+ // that we'll also be able to use in content processes. There's no way to
+ // pick an address that is guaranteed to be free in future content processes,
+ // so instead we pick an address that is some distance away from current heap
+ // allocations and hope that by the time the content process maps the shared
+ // memory, that address will be free.
+ //
+ // On 64 bit, we have a large amount of address space, so we pick an address
+ // half way through the next 8 GiB of free space, and this has a very good
+ // chance of succeeding. On 32 bit, address space is more constrained. We
+ // only have 3 GiB of space to work with, and we don't want to pick a location
+ // right in the middle, since that could cause future large allocations to
+ // fail. So we pick an address half way through the next 512 MiB of free
+ // space. Experimentally this seems to work 9 times out of 10; this is good
+ // enough, as it means only 1 in 10 content processes will have its own unique
+ // copies of the UA style sheets, and we're still getting a significant
+ // overall memory saving.
+ //
+ // In theory ASLR could reduce the likelihood of the mapping succeeding in
+ // content processes, due to our expectations of where the heap is being
+ // wrong, but in practice this isn't an issue.
+#ifdef HAVE_64BIT_BUILD
+ constexpr size_t kOffset = 0x200000000ULL; // 8 GiB
+#else
+ constexpr size_t kOffset = 0x20000000; // 512 MiB
+#endif
+
+ void* address = nullptr;
+ if (void* p = base::SharedMemory::FindFreeAddressSpace(2 * kOffset)) {
+ address = reinterpret_cast<void*>(uintptr_t(p) + kOffset);
+ }
+
+ bool parentMapped = shm->Map(kSharedMemorySize, address);
+ Telemetry::Accumulate(Telemetry::SHARED_MEMORY_UA_SHEETS_MAPPED_PARENT,
+ parentMapped);
+
+ if (!parentMapped) {
+ // Failed to map at the address we computed for some reason. Fall back
+ // to just allocating at a location of the OS's choosing, and hope that
+ // it works in the content process.
+ if (NS_WARN_IF(!shm->Map(kSharedMemorySize))) {
+ return;
+ }
+ }
+ address = shm->memory();
+
+ auto header = static_cast<Header*>(address);
+ header->mMagic = Header::kMagic;
+#ifdef DEBUG
+ for (auto ptr : header->mSheets) {
+ MOZ_RELEASE_ASSERT(!ptr, "expected shared memory to have been zeroed");
+ }
+#endif
+
+ UniquePtr<RawServoSharedMemoryBuilder> builder(
+ Servo_SharedMemoryBuilder_Create(
+ header->mBuffer, kSharedMemorySize - offsetof(Header, mBuffer)));
+
+ nsCString message;
+
+ // Copy each one into the shared memory, and record its pointer.
+ //
+ // Normally calling ToShared on UA sheets should not fail. It happens
+ // in practice in odd cases that seem like corrupted installations; see bug
+ // 1621773. On failure, return early and fall back to non-shared sheets.
+#define STYLE_SHEET(identifier_, url_, shared_) \
+ if (shared_) { \
+ StyleSheet* sheet = identifier_##Sheet(); \
+ size_t i = size_t(UserAgentStyleSheetID::identifier_); \
+ URLExtraData::sShared[i] = sheet->URLData(); \
+ header->mSheets[i] = sheet->ToShared(builder.get(), message); \
+ if (!header->mSheets[i]) { \
+ CrashReporter::AppendAppNotesToCrashReport("\n"_ns + message); \
+ Telemetry::Accumulate( \
+ Telemetry::SHARED_MEMORY_UA_SHEETS_TOSHMEM_SUCCEEDED, false); \
+ return; \
+ } \
+ }
+#include "mozilla/UserAgentStyleSheetList.h"
+#undef STYLE_SHEET
+
+ Telemetry::Accumulate(Telemetry::SHARED_MEMORY_UA_SHEETS_TOSHMEM_SUCCEEDED,
+ true);
+
+ // Finished writing into the shared memory. Freeze it, so that a process
+ // can't confuse other processes by changing the UA style sheet contents.
+ if (NS_WARN_IF(!shm->Freeze())) {
+ return;
+ }
+
+ // The Freeze() call unmaps the shared memory. Re-map it again as read only.
+ // If this fails, due to something else being mapped into the same place
+ // between the Freeze() and Map() call, we can just fall back to keeping our
+ // own copy of the UA style sheets in the parent, and still try sending the
+ // shared memory to the content processes.
+ bool parentRemapped = shm->Map(kSharedMemorySize, address);
+ Telemetry::Accumulate(
+ Telemetry::SHARED_MEMORY_UA_SHEETS_MAPPED_PARENT_AFTER_FREEZE,
+ parentRemapped);
+
+ // Record how must of the shared memory we have used, for memory reporting
+ // later. We round up to the nearest page since the free space at the end
+ // of the page isn't really usable for anything else.
+ //
+ // TODO(heycam): This won't be true on Windows unless we allow creating the
+ // shared memory with SEC_RESERVE so that the pages are reserved but not
+ // committed.
+ size_t pageSize = ipc::SharedMemory::SystemPageSize();
+ sUsedSharedMemory =
+ (Servo_SharedMemoryBuilder_GetLength(builder.get()) + pageSize - 1) &
+ ~(pageSize - 1);
+
+ sSharedMemory = shm.release();
+}
+
+GlobalStyleSheetCache::~GlobalStyleSheetCache() {
+ UnregisterWeakMemoryReporter(this);
+}
+
+void GlobalStyleSheetCache::InitMemoryReporter() {
+ RegisterWeakMemoryReporter(this);
+}
+
+/* static */
+GlobalStyleSheetCache* GlobalStyleSheetCache::Singleton() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gStyleCache) {
+ gStyleCache = new GlobalStyleSheetCache;
+ gStyleCache->InitMemoryReporter();
+
+ // For each pref that controls a CSS feature that a UA style sheet depends
+ // on (such as a pref that enables a property that a UA style sheet uses),
+ // register DependentPrefChanged as a callback to ensure that the relevant
+ // style sheets will be re-parsed.
+ // Preferences::RegisterCallback(&DependentPrefChanged,
+ // "layout.css.example-pref.enabled");
+ }
+
+ return gStyleCache;
+}
+
+void GlobalStyleSheetCache::InitFromProfile() {
+ if (!Preferences::GetBool(PREF_LEGACY_STYLESHEET_CUSTOMIZATION)) {
+ return;
+ }
+
+ nsCOMPtr<nsIXULRuntime> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ if (appInfo) {
+ bool inSafeMode = false;
+ appInfo->GetInSafeMode(&inSafeMode);
+ if (inSafeMode) return;
+ }
+ nsCOMPtr<nsIFile> contentFile;
+ nsCOMPtr<nsIFile> chromeFile;
+
+ NS_GetSpecialDirectory(NS_APP_USER_CHROME_DIR, getter_AddRefs(contentFile));
+ if (!contentFile) {
+ // if we don't have a profile yet, that's OK!
+ return;
+ }
+
+ contentFile->Clone(getter_AddRefs(chromeFile));
+ if (!chromeFile) return;
+
+ contentFile->Append(u"userContent.css"_ns);
+ chromeFile->Append(u"userChrome.css"_ns);
+
+ mUserContentSheet = LoadSheetFile(contentFile, eUserSheetFeatures);
+ mUserChromeSheet = LoadSheetFile(chromeFile, eUserSheetFeatures);
+}
+
+RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheetURL(
+ const char* aURL, SheetParsingMode aParsingMode,
+ FailureAction aFailureAction) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aURL);
+ return LoadSheet(uri, aParsingMode, aFailureAction);
+}
+
+RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheetFile(
+ nsIFile* aFile, SheetParsingMode aParsingMode) {
+ bool exists = false;
+ aFile->Exists(&exists);
+ if (!exists) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewFileURI(getter_AddRefs(uri), aFile);
+ return LoadSheet(uri, aParsingMode, eLogToConsole);
+}
+
+static void ErrorLoadingSheet(nsIURI* aURI, const char* aMsg,
+ FailureAction aFailureAction) {
+ nsPrintfCString errorMessage("%s loading built-in stylesheet '%s'", aMsg,
+ aURI ? aURI->GetSpecOrDefault().get() : "");
+ if (aFailureAction == eLogToConsole) {
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(NS_ConvertUTF8toUTF16(errorMessage).get());
+ return;
+ }
+ }
+
+ MOZ_CRASH_UNSAFE(errorMessage.get());
+}
+
+RefPtr<StyleSheet> GlobalStyleSheetCache::LoadSheet(
+ nsIURI* aURI, SheetParsingMode aParsingMode, FailureAction aFailureAction) {
+ if (!aURI) {
+ ErrorLoadingSheet(aURI, "null URI", eCrash);
+ return nullptr;
+ }
+
+ if (!gCSSLoader) {
+ gCSSLoader = new Loader;
+ if (!gCSSLoader) {
+ ErrorLoadingSheet(aURI, "no Loader", eCrash);
+ return nullptr;
+ }
+ }
+
+ // Note: The parallel parsing code assume that UA sheets are always loaded
+ // synchronously like they are here, and thus that we'll never attempt
+ // parallel parsing on them. If that ever changes, we'll either need to find a
+ // different way to prohibit parallel parsing for UA sheets, or handle
+ // -moz-bool-pref and various other things in the parallel parsing code.
+ auto result = gCSSLoader->LoadSheetSync(aURI, aParsingMode,
+ css::Loader::UseSystemPrincipal::Yes);
+ if (MOZ_UNLIKELY(result.isErr())) {
+ ErrorLoadingSheet(
+ aURI,
+ nsPrintfCString("LoadSheetSync failed with error %" PRIx32,
+ static_cast<uint32_t>(result.unwrapErr()))
+ .get(),
+ aFailureAction);
+ }
+ return result.unwrapOr(nullptr);
+}
+
+/* static */
+void GlobalStyleSheetCache::InvalidatePreferenceSheets() {
+ if (gStyleCache) {
+ gStyleCache->mContentPreferenceSheet = nullptr;
+ gStyleCache->mChromePreferenceSheet = nullptr;
+ }
+}
+
+void GlobalStyleSheetCache::BuildPreferenceSheet(
+ RefPtr<StyleSheet>* aSheet, const PreferenceSheet::Prefs& aPrefs) {
+ *aSheet = new StyleSheet(eAgentSheetFeatures, CORS_NONE, dom::SRIMetadata());
+
+ StyleSheet* sheet = *aSheet;
+
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:PreferenceStyleSheet");
+ MOZ_ASSERT(uri, "URI creation shouldn't fail");
+
+ sheet->SetURIs(uri, uri, uri);
+ sheet->SetComplete();
+
+ static const uint32_t kPreallocSize = 1024;
+
+ nsCString sheetText;
+ sheetText.SetCapacity(kPreallocSize);
+
+#define NS_GET_R_G_B(color_) \
+ NS_GET_R(color_), NS_GET_G(color_), NS_GET_B(color_)
+
+ sheetText.AppendLiteral(
+ "@namespace url(http://www.w3.org/1999/xhtml);\n"
+ "@namespace svg url(http://www.w3.org/2000/svg);\n");
+
+ // Rules for link styling.
+ nscolor linkColor = aPrefs.mLinkColor;
+ nscolor activeColor = aPrefs.mActiveLinkColor;
+ nscolor visitedColor = aPrefs.mVisitedLinkColor;
+
+ sheetText.AppendPrintf(
+ "*|*:link { color: #%02x%02x%02x; }\n"
+ "*|*:any-link:active { color: #%02x%02x%02x; }\n"
+ "*|*:visited { color: #%02x%02x%02x; }\n",
+ NS_GET_R_G_B(linkColor), NS_GET_R_G_B(activeColor),
+ NS_GET_R_G_B(visitedColor));
+
+ bool underlineLinks = aPrefs.mUnderlineLinks;
+ sheetText.AppendPrintf("*|*:any-link%s { text-decoration: %s; }\n",
+ underlineLinks ? ":not(svg|a)" : "",
+ underlineLinks ? "underline" : "none");
+
+ // Rules for focus styling.
+
+ bool focusRingOnAnything = aPrefs.mFocusRingOnAnything;
+ uint8_t focusRingWidth = aPrefs.mFocusRingWidth;
+ uint8_t focusRingStyle = aPrefs.mFocusRingStyle;
+
+ if ((focusRingWidth != 1 && focusRingWidth <= 4) || focusRingOnAnything) {
+ if (focusRingWidth != 1) {
+ // If the focus ring width is different from the default, fix buttons
+ // with rings.
+ sheetText.AppendPrintf(
+ "button::-moz-focus-inner, input[type=\"reset\"]::-moz-focus-inner, "
+ "input[type=\"button\"]::-moz-focus-inner, "
+ "input[type=\"submit\"]::-moz-focus-inner { "
+ "border: %dpx %s transparent !important; }\n",
+ focusRingWidth, focusRingStyle == 0 ? "solid" : "dotted");
+
+ sheetText.AppendLiteral(
+ "button:focus::-moz-focus-inner, "
+ "input[type=\"reset\"]:focus::-moz-focus-inner, "
+ "input[type=\"button\"]:focus::-moz-focus-inner, "
+ "input[type=\"submit\"]:focus::-moz-focus-inner { "
+ "border-color: ButtonText !important; }\n");
+ }
+
+ sheetText.AppendPrintf(
+ "%s { outline: %dpx %s !important; %s}\n",
+ focusRingOnAnything ? ":focus" : "*|*:link:focus, *|*:visited:focus",
+ focusRingWidth,
+ focusRingStyle == 0 ? // solid
+ "solid -moz-mac-focusring"
+ : "dotted WindowText",
+ focusRingStyle == 0 ? // solid
+ "-moz-outline-radius: 3px; outline-offset: 1px; "
+ : "");
+ }
+
+ if (aPrefs.mUseFocusColors) {
+ nscolor focusText = aPrefs.mFocusTextColor;
+ nscolor focusBG = aPrefs.mFocusBackgroundColor;
+ sheetText.AppendPrintf(
+ "*:focus, *:focus > font { color: #%02x%02x%02x !important; "
+ "background-color: #%02x%02x%02x !important; }\n",
+ NS_GET_R_G_B(focusText), NS_GET_R_G_B(focusBG));
+ }
+
+ NS_ASSERTION(sheetText.Length() <= kPreallocSize,
+ "kPreallocSize should be big enough to build preference style "
+ "sheet without reallocation");
+
+ // NB: The pref sheet never has @import rules, thus no loader.
+ sheet->ParseSheetSync(nullptr, sheetText,
+ /* aLoadData = */ nullptr,
+ /* aLineNumber = */ 0);
+
+#undef NS_GET_R_G_B
+}
+
+/* static */ void GlobalStyleSheetCache::SetSharedMemory(
+ const base::SharedMemoryHandle& aHandle, uintptr_t aAddress) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(!gStyleCache, "Too late, GlobalStyleSheetCache already created!");
+ MOZ_ASSERT(!sSharedMemory, "Shouldn't call this more than once");
+
+ auto shm = MakeUnique<base::SharedMemory>();
+ if (!shm->SetHandle(aHandle, /* read_only */ true)) {
+ return;
+ }
+
+ bool contentMapped =
+ shm->Map(kSharedMemorySize, reinterpret_cast<void*>(aAddress));
+ Telemetry::Accumulate(Telemetry::SHARED_MEMORY_UA_SHEETS_MAPPED_CHILD,
+ contentMapped);
+ if (contentMapped) {
+ sSharedMemory = shm.release();
+ }
+}
+
+bool GlobalStyleSheetCache::ShareToProcess(base::ProcessId aProcessId,
+ base::SharedMemoryHandle* aHandle) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return sSharedMemory && sSharedMemory->ShareToProcess(aProcessId, aHandle);
+}
+
+StaticRefPtr<GlobalStyleSheetCache> GlobalStyleSheetCache::gStyleCache;
+StaticRefPtr<css::Loader> GlobalStyleSheetCache::gCSSLoader;
+StaticRefPtr<nsIURI> GlobalStyleSheetCache::gUserContentSheetURL;
+
+StaticAutoPtr<base::SharedMemory> GlobalStyleSheetCache::sSharedMemory;
+size_t GlobalStyleSheetCache::sUsedSharedMemory;
+
+} // namespace mozilla