summaryrefslogtreecommitdiffstats
path: root/layout/style/ErrorReporter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/ErrorReporter.cpp')
-rw-r--r--layout/style/ErrorReporter.cpp382
1 files changed, 382 insertions, 0 deletions
diff --git a/layout/style/ErrorReporter.cpp b/layout/style/ErrorReporter.cpp
new file mode 100644
index 000000000..56a84da3e
--- /dev/null
+++ b/layout/style/ErrorReporter.cpp
@@ -0,0 +1,382 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsCSSScanner.h"
+#include "nsIConsoleService.h"
+#include "nsIDocument.h"
+#include "nsIFactory.h"
+#include "nsIScriptError.h"
+#include "nsIStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStyleUtil.h"
+#include "nsThreadUtils.h"
+
+#ifdef CSS_REPORT_PARSE_ERRORS
+
+using namespace mozilla;
+
+namespace {
+class ShortTermURISpecCache : public Runnable {
+public:
+ ShortTermURISpecCache() : mPending(false) {}
+
+ nsString const& GetSpec(nsIURI* aURI) {
+ if (mURI != aURI) {
+ mURI = aURI;
+
+ nsAutoCString cSpec;
+ nsresult rv = mURI->GetSpec(cSpec);
+ if (NS_FAILED(rv)) {
+ cSpec.AssignLiteral("[nsIURI::GetSpec failed]");
+ }
+ CopyUTF8toUTF16(cSpec, mSpec);
+ }
+ 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
+
+static bool sReportErrors;
+static nsIConsoleService *sConsoleService;
+static nsIFactory *sScriptErrorFactory;
+static nsIStringBundle *sStringBundle;
+static ShortTermURISpecCache *sSpecCache;
+
+#define CSS_ERRORS_PREF "layout.css.report_errors"
+
+static bool
+InitGlobals()
+{
+ MOZ_ASSERT(!sConsoleService && !sScriptErrorFactory && !sStringBundle,
+ "should not have been called");
+
+ if (NS_FAILED(Preferences::AddBoolVarCache(&sReportErrors, CSS_ERRORS_PREF,
+ true))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!cs) {
+ return false;
+ }
+
+ nsCOMPtr<nsIFactory> sf = do_GetClassObject(NS_SCRIPTERROR_CONTRACTID);
+ if (!sf) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStringBundleService> sbs = services::GetStringBundleService();
+ if (!sbs) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStringBundle> sb;
+ nsresult rv = sbs->CreateBundle("chrome://global/locale/css.properties",
+ getter_AddRefs(sb));
+ if (NS_FAILED(rv) || !sb) {
+ return false;
+ }
+
+ cs.forget(&sConsoleService);
+ sf.forget(&sScriptErrorFactory);
+ sb.forget(&sStringBundle);
+
+ return true;
+}
+
+static inline bool
+ShouldReportErrors()
+{
+ if (!sConsoleService) {
+ if (!InitGlobals()) {
+ return false;
+ }
+ }
+ return sReportErrors;
+}
+
+namespace mozilla {
+namespace css {
+
+/* static */ void
+ErrorReporter::ReleaseGlobals()
+{
+ NS_IF_RELEASE(sConsoleService);
+ NS_IF_RELEASE(sScriptErrorFactory);
+ NS_IF_RELEASE(sStringBundle);
+ NS_IF_RELEASE(sSpecCache);
+}
+
+ErrorReporter::ErrorReporter(const nsCSSScanner& aScanner,
+ const CSSStyleSheet* aSheet,
+ const Loader* aLoader,
+ nsIURI* aURI)
+ : mScanner(&aScanner), mSheet(aSheet), mLoader(aLoader), mURI(aURI),
+ mInnerWindowID(0), mErrorLineNumber(0), mPrevErrorLineNumber(0),
+ mErrorColNumber(0)
+{
+}
+
+ErrorReporter::~ErrorReporter()
+{
+ // 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()) {
+ if (NS_FAILED(NS_DispatchToCurrentThread(sSpecCache))) {
+ // Peform the "deferred" cleanup immediately if the dispatch fails.
+ sSpecCache->Run();
+ } else {
+ sSpecCache->SetPending();
+ }
+ }
+}
+
+void
+ErrorReporter::OutputError()
+{
+ if (mError.IsEmpty()) {
+ return;
+ }
+ if (!ShouldReportErrors()) {
+ ClearError();
+ return;
+ }
+
+ if (mInnerWindowID == 0 && (mSheet || mLoader)) {
+ if (mSheet) {
+ mInnerWindowID = mSheet->FindOwningWindowInnerID();
+ }
+ if (mInnerWindowID == 0 && mLoader) {
+ nsIDocument* doc = mLoader->GetDocument();
+ if (doc) {
+ mInnerWindowID = doc->InnerWindowID();
+ }
+ }
+ // don't attempt this again, even if we failed
+ mSheet = nullptr;
+ mLoader = nullptr;
+ }
+
+ if (mFileName.IsEmpty()) {
+ if (mURI) {
+ if (!sSpecCache) {
+ sSpecCache = new ShortTermURISpecCache;
+ NS_ADDREF(sSpecCache);
+ }
+ mFileName = sSpecCache->GetSpec(mURI);
+ mURI = nullptr;
+ } else {
+ mFileName.AssignLiteral("from DOM");
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIScriptError> errorObject =
+ do_CreateInstance(sScriptErrorFactory, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = errorObject->InitWithWindowID(mError,
+ mFileName,
+ mErrorLine,
+ mErrorLineNumber,
+ mErrorColNumber,
+ nsIScriptError::warningFlag,
+ "CSS Parser",
+ mInnerWindowID);
+ if (NS_SUCCEEDED(rv)) {
+ sConsoleService->LogMessage(errorObject);
+ }
+ }
+
+ ClearError();
+}
+
+void
+ErrorReporter::OutputError(uint32_t aLineNumber, uint32_t aLineOffset)
+{
+ mErrorLineNumber = aLineNumber;
+ mErrorColNumber = aLineOffset;
+ OutputError();
+}
+
+void
+ErrorReporter::ClearError()
+{
+ mError.Truncate();
+}
+
+void
+ErrorReporter::AddToError(const nsString &aErrorText)
+{
+ if (!ShouldReportErrors()) return;
+
+ if (mError.IsEmpty()) {
+ mError = aErrorText;
+ mErrorLineNumber = mScanner->GetLineNumber();
+ mErrorColNumber = mScanner->GetColumnNumber();
+ // Retrieve the error line once per line, and reuse the same nsString
+ // for all errors on that line. That causes the text of the line to
+ // be shared among all the nsIScriptError objects.
+ if (mErrorLine.IsEmpty() || mErrorLineNumber != mPrevErrorLineNumber) {
+ // Be careful here: the error line might be really long and OOM
+ // when we try to make a copy here. If so, just leave it empty.
+ if (!mErrorLine.Assign(mScanner->GetCurrentLine(), fallible)) {
+ mErrorLine.Truncate();
+ }
+ mPrevErrorLineNumber = mErrorLineNumber;
+ }
+ } else {
+ mError.AppendLiteral(" ");
+ mError.Append(aErrorText);
+ }
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString str;
+ sStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage,
+ const nsString &aParam)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString qparam;
+ nsStyleUtil::AppendEscapedCSSIdent(aParam, qparam);
+ const char16_t *params[1] = { qparam.get() };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage,
+ const nsCSSToken &aToken)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString tokenString;
+ aToken.AppendToString(tokenString);
+ const char16_t *params[1] = { tokenString.get() };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage,
+ const nsCSSToken &aToken,
+ char16_t aChar)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString tokenString;
+ aToken.AppendToString(tokenString);
+ const char16_t charStr[2] = { aChar, 0 };
+ const char16_t *params[2] = { tokenString.get(), charStr };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpected(const char *aMessage,
+ const nsString &aParam,
+ const nsString &aValue)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString qparam;
+ nsStyleUtil::AppendEscapedCSSIdent(aParam, qparam);
+ const char16_t *params[2] = { qparam.get(), aValue.get() };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpectedEOF(const char *aMessage)
+{
+ if (!ShouldReportErrors()) return;
+
+ nsAutoString innerStr;
+ sStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
+ getter_Copies(innerStr));
+ const char16_t *params[1] = { innerStr.get() };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(u"PEUnexpEOF2",
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+void
+ErrorReporter::ReportUnexpectedEOF(char16_t aExpected)
+{
+ if (!ShouldReportErrors()) return;
+
+ const char16_t expectedStr[] = {
+ char16_t('\''), aExpected, char16_t('\''), char16_t(0)
+ };
+ const char16_t *params[1] = { expectedStr };
+
+ nsAutoString str;
+ sStringBundle->FormatStringFromName(u"PEUnexpEOF2",
+ params, ArrayLength(params),
+ getter_Copies(str));
+ AddToError(str);
+}
+
+} // namespace css
+} // namespace mozilla
+
+#endif