diff options
Diffstat (limited to 'layout/style/ErrorReporter.cpp')
-rw-r--r-- | layout/style/ErrorReporter.cpp | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/layout/style/ErrorReporter.cpp b/layout/style/ErrorReporter.cpp new file mode 100644 index 0000000000..75921d0d8c --- /dev/null +++ b/layout/style/ErrorReporter.cpp @@ -0,0 +1,278 @@ +/* -*- 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/. */ + +/* diagnostic reporting for CSS style sheet parser */ + +#include "mozilla/css/ErrorReporter.h" + +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/css/Loader.h" +#include "mozilla/Preferences.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" +#include "nsIConsoleService.h" +#include "mozilla/dom/Document.h" +#include "nsComponentManagerUtils.h" +#include "nsIDocShell.h" +#include "nsIFactory.h" +#include "nsINode.h" +#include "nsIScriptError.h" +#include "nsIStringBundle.h" +#include "nsServiceManagerUtils.h" +#include "nsStyleUtil.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" + +using namespace mozilla; +using namespace mozilla::css; +using namespace mozilla::dom; + +namespace { +class ShortTermURISpecCache : public Runnable { + public: + ShortTermURISpecCache() + : Runnable("ShortTermURISpecCache"), mPending(false) {} + + nsString const& GetSpec(nsIURI* aURI) { + if (mURI != aURI) { + mURI = aURI; + + if (NS_FAILED(NS_GetSanitizedURIStringFromURI(mURI, mSpec))) { + mSpec.AssignLiteral("[nsIURI::GetSpec failed]"); + } + } + return mSpec; + } + + bool IsInUse() const { return mURI != nullptr; } + bool IsPending() const { return mPending; } + void SetPending() { mPending = true; } + + // When invoked as a runnable, zap the cache. + NS_IMETHOD Run() override { + mURI = nullptr; + mSpec.Truncate(); + mPending = false; + return NS_OK; + } + + private: + nsCOMPtr<nsIURI> mURI; + nsString mSpec; + bool mPending; +}; + +} // namespace + +bool ErrorReporter::sInitialized = false; + +static nsIConsoleService* sConsoleService; +static nsIFactory* sScriptErrorFactory; +static nsIStringBundle* sStringBundle; +static ShortTermURISpecCache* sSpecCache; + +void ErrorReporter::InitGlobals() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sInitialized, "should not have been called"); + + sInitialized = true; + + nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!cs) { + return; + } + + nsCOMPtr<nsIFactory> sf = do_GetClassObject(NS_SCRIPTERROR_CONTRACTID); + if (!sf) { + return; + } + + nsCOMPtr<nsIStringBundleService> sbs = services::GetStringBundleService(); + if (!sbs) { + return; + } + + nsCOMPtr<nsIStringBundle> sb; + nsresult rv = sbs->CreateBundle("chrome://global/locale/css.properties", + getter_AddRefs(sb)); + if (NS_FAILED(rv) || !sb) { + return; + } + + cs.forget(&sConsoleService); + sf.forget(&sScriptErrorFactory); + sb.forget(&sStringBundle); +} + +namespace mozilla { +namespace css { + +/* static */ +void ErrorReporter::ReleaseGlobals() { + NS_IF_RELEASE(sConsoleService); + NS_IF_RELEASE(sScriptErrorFactory); + NS_IF_RELEASE(sStringBundle); + NS_IF_RELEASE(sSpecCache); +} + +uint64_t ErrorReporter::FindInnerWindowId(const StyleSheet* aSheet, + const Loader* aLoader) { + if (aSheet) { + if (uint64_t id = aSheet->FindOwningWindowInnerID()) { + return id; + } + } + if (aLoader) { + if (Document* doc = aLoader->GetDocument()) { + return doc->InnerWindowID(); + } + } + return 0; +} + +ErrorReporter::ErrorReporter(uint64_t aInnerWindowId) + : mInnerWindowId(aInnerWindowId) { + EnsureGlobalsInitialized(); +} + +ErrorReporter::~ErrorReporter() { + MOZ_ASSERT(NS_IsMainThread()); + // Schedule deferred cleanup for cached data. We want to strike a + // balance between performance and memory usage, so we only allow + // short-term caching. + if (sSpecCache && sSpecCache->IsInUse() && !sSpecCache->IsPending()) { + nsCOMPtr<nsIRunnable> runnable(sSpecCache); + nsresult rv = + SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget()); + if (NS_FAILED(rv)) { + // Peform the "deferred" cleanup immediately if the dispatch fails. + sSpecCache->Run(); + } else { + sSpecCache->SetPending(); + } + } +} + +bool ErrorReporter::ShouldReportErrors(const Document& aDoc) { + MOZ_ASSERT(NS_IsMainThread()); + nsIDocShell* shell = aDoc.GetDocShell(); + if (!shell) { + return false; + } + + bool report = false; + shell->GetCssErrorReportingEnabled(&report); + return report; +} + +static nsINode* SheetOwner(const StyleSheet& aSheet) { + if (nsINode* owner = aSheet.GetOwnerNode()) { + return owner; + } + + auto* associated = aSheet.GetAssociatedDocumentOrShadowRoot(); + return associated ? &associated->AsNode() : nullptr; +} + +bool ErrorReporter::ShouldReportErrors(const StyleSheet* aSheet, + const Loader* aLoader) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!StaticPrefs::layout_css_report_errors()) { + return false; + } + + if (aSheet) { + nsINode* owner = SheetOwner(*aSheet); + if (owner && ShouldReportErrors(*owner->OwnerDoc())) { + return true; + } + } + + if (aLoader && aLoader->GetDocument() && + ShouldReportErrors(*aLoader->GetDocument())) { + return true; + } + + return false; +} + +void ErrorReporter::OutputError(const nsACString& aSourceLine, + const nsACString& aSelectors, + uint32_t aLineNumber, uint32_t aColNumber, + nsIURI* aURI) { + nsAutoString errorLine; + // This could be a really long string for minified CSS; just leave it empty + // if we OOM. + if (!AppendUTF8toUTF16(aSourceLine, errorLine, fallible)) { + errorLine.Truncate(); + } + + nsAutoString selectors; + if (!AppendUTF8toUTF16(aSelectors, selectors, fallible)) { + selectors.Truncate(); + } + + if (mError.IsEmpty()) { + return; + } + + nsAutoString fileName; + if (aURI) { + if (!sSpecCache) { + sSpecCache = new ShortTermURISpecCache; + NS_ADDREF(sSpecCache); + } + fileName = sSpecCache->GetSpec(aURI); + } else { + fileName.AssignLiteral("from DOM"); + } + + nsresult rv; + nsCOMPtr<nsIScriptError> errorObject = + do_CreateInstance(sScriptErrorFactory, &rv); + + if (NS_SUCCEEDED(rv)) { + // It is safe to used InitWithSanitizedSource because fileName is + // an already anonymized uri spec. + rv = errorObject->InitWithSanitizedSource( + mError, fileName, errorLine, aLineNumber, aColNumber, + nsIScriptError::warningFlag, "CSS Parser", mInnerWindowId); + + if (NS_SUCCEEDED(rv)) { + errorObject->SetCssSelectors(selectors); + sConsoleService->LogMessage(errorObject); + } + } + + mError.Truncate(); +} + +void ErrorReporter::AddToError(const nsString& aErrorText) { + if (mError.IsEmpty()) { + mError = aErrorText; + } else { + mError.AppendLiteral(" "); + mError.Append(aErrorText); + } +} + +void ErrorReporter::ReportUnexpected(const char* aMessage) { + nsAutoString str; + sStringBundle->GetStringFromName(aMessage, str); + AddToError(str); +} + +void ErrorReporter::ReportUnexpectedUnescaped( + const char* aMessage, const nsTArray<nsString>& aParam) { + nsAutoString str; + sStringBundle->FormatStringFromName(aMessage, aParam, str); + AddToError(str); +} + +} // namespace css +} // namespace mozilla |