michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* diagnostic reporting for CSS style sheet parser */ michael@0: michael@0: #include "mozilla/css/ErrorReporter.h" michael@0: #include "mozilla/css/Loader.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsCSSScanner.h" michael@0: #include "nsCSSStyleSheet.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIFactory.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsStyleUtil.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #ifdef CSS_REPORT_PARSE_ERRORS michael@0: michael@0: using mozilla::Preferences; michael@0: namespace services = mozilla::services; michael@0: michael@0: namespace { michael@0: class ShortTermURISpecCache : public nsRunnable { michael@0: public: michael@0: ShortTermURISpecCache() : mPending(false) {} michael@0: michael@0: nsString const& GetSpec(nsIURI* aURI) { michael@0: if (mURI != aURI) { michael@0: mURI = aURI; michael@0: michael@0: nsAutoCString cSpec; michael@0: mURI->GetSpec(cSpec); michael@0: CopyUTF8toUTF16(cSpec, mSpec); michael@0: } michael@0: return mSpec; michael@0: } michael@0: michael@0: bool IsInUse() const { return mURI != nullptr; } michael@0: bool IsPending() const { return mPending; } michael@0: void SetPending() { mPending = true; } michael@0: michael@0: // When invoked as a runnable, zap the cache. michael@0: NS_IMETHOD Run() { michael@0: mURI = nullptr; michael@0: mSpec.Truncate(); michael@0: mPending = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mURI; michael@0: nsString mSpec; michael@0: bool mPending; michael@0: }; michael@0: } michael@0: michael@0: static bool sReportErrors; michael@0: static nsIConsoleService *sConsoleService; michael@0: static nsIFactory *sScriptErrorFactory; michael@0: static nsIStringBundle *sStringBundle; michael@0: static ShortTermURISpecCache *sSpecCache; michael@0: michael@0: #define CSS_ERRORS_PREF "layout.css.report_errors" michael@0: michael@0: static bool michael@0: InitGlobals() michael@0: { michael@0: NS_ABORT_IF_FALSE(!sConsoleService && !sScriptErrorFactory && !sStringBundle, michael@0: "should not have been called"); michael@0: michael@0: if (NS_FAILED(Preferences::AddBoolVarCache(&sReportErrors, CSS_ERRORS_PREF, michael@0: true))) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: if (!cs) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr sf = do_GetClassObject(NS_SCRIPTERROR_CONTRACTID); michael@0: if (!sf) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr sbs = services::GetStringBundleService(); michael@0: if (!sbs) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr sb; michael@0: nsresult rv = sbs->CreateBundle("chrome://global/locale/css.properties", michael@0: getter_AddRefs(sb)); michael@0: if (NS_FAILED(rv) || !sb) { michael@0: return false; michael@0: } michael@0: michael@0: cs.forget(&sConsoleService); michael@0: sf.forget(&sScriptErrorFactory); michael@0: sb.forget(&sStringBundle); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static inline bool michael@0: ShouldReportErrors() michael@0: { michael@0: if (!sConsoleService) { michael@0: if (!InitGlobals()) { michael@0: return false; michael@0: } michael@0: } michael@0: return sReportErrors; michael@0: } michael@0: michael@0: namespace mozilla { michael@0: namespace css { michael@0: michael@0: /* static */ void michael@0: ErrorReporter::ReleaseGlobals() michael@0: { michael@0: NS_IF_RELEASE(sConsoleService); michael@0: NS_IF_RELEASE(sScriptErrorFactory); michael@0: NS_IF_RELEASE(sStringBundle); michael@0: NS_IF_RELEASE(sSpecCache); michael@0: } michael@0: michael@0: ErrorReporter::ErrorReporter(const nsCSSScanner& aScanner, michael@0: const nsCSSStyleSheet* aSheet, michael@0: const Loader* aLoader, michael@0: nsIURI* aURI) michael@0: : mScanner(&aScanner), mSheet(aSheet), mLoader(aLoader), mURI(aURI), michael@0: mInnerWindowID(0), mErrorLineNumber(0), mPrevErrorLineNumber(0), michael@0: mErrorColNumber(0) michael@0: { michael@0: } michael@0: michael@0: ErrorReporter::~ErrorReporter() michael@0: { michael@0: // Schedule deferred cleanup for cached data. We want to strike a michael@0: // balance between performance and memory usage, so we only allow michael@0: // short-term caching. michael@0: if (sSpecCache && sSpecCache->IsInUse() && !sSpecCache->IsPending()) { michael@0: if (NS_FAILED(NS_DispatchToCurrentThread(sSpecCache))) { michael@0: // Peform the "deferred" cleanup immediately if the dispatch fails. michael@0: sSpecCache->Run(); michael@0: } else { michael@0: sSpecCache->SetPending(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::OutputError() michael@0: { michael@0: if (mError.IsEmpty()) { michael@0: return; michael@0: } michael@0: if (!ShouldReportErrors()) { michael@0: ClearError(); michael@0: return; michael@0: } michael@0: michael@0: if (mInnerWindowID == 0 && (mSheet || mLoader)) { michael@0: if (mSheet) { michael@0: mInnerWindowID = mSheet->FindOwningWindowInnerID(); michael@0: } michael@0: if (mInnerWindowID == 0 && mLoader) { michael@0: nsIDocument* doc = mLoader->GetDocument(); michael@0: if (doc) { michael@0: mInnerWindowID = doc->InnerWindowID(); michael@0: } michael@0: } michael@0: // don't attempt this again, even if we failed michael@0: mSheet = nullptr; michael@0: mLoader = nullptr; michael@0: } michael@0: michael@0: if (mFileName.IsEmpty()) { michael@0: if (mURI) { michael@0: if (!sSpecCache) { michael@0: sSpecCache = new ShortTermURISpecCache; michael@0: NS_ADDREF(sSpecCache); michael@0: } michael@0: mFileName = sSpecCache->GetSpec(mURI); michael@0: mURI = nullptr; michael@0: } else { michael@0: mFileName.AssignLiteral("from DOM"); michael@0: } michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr errorObject = michael@0: do_CreateInstance(sScriptErrorFactory, &rv); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = errorObject->InitWithWindowID(mError, michael@0: mFileName, michael@0: mErrorLine, michael@0: mErrorLineNumber, michael@0: mErrorColNumber, michael@0: nsIScriptError::warningFlag, michael@0: "CSS Parser", michael@0: mInnerWindowID); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: sConsoleService->LogMessage(errorObject); michael@0: } michael@0: } michael@0: michael@0: ClearError(); michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::OutputError(uint32_t aLineNumber, uint32_t aLineOffset) michael@0: { michael@0: mErrorLineNumber = aLineNumber; michael@0: mErrorColNumber = aLineOffset; michael@0: OutputError(); michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::ClearError() michael@0: { michael@0: mError.Truncate(); michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::AddToError(const nsString &aErrorText) michael@0: { michael@0: if (!ShouldReportErrors()) return; michael@0: michael@0: if (mError.IsEmpty()) { michael@0: mError = aErrorText; michael@0: mErrorLineNumber = mScanner->GetLineNumber(); michael@0: mErrorColNumber = mScanner->GetColumnNumber(); michael@0: // Retrieve the error line once per line, and reuse the same nsString michael@0: // for all errors on that line. That causes the text of the line to michael@0: // be shared among all the nsIScriptError objects. michael@0: if (mErrorLine.IsEmpty() || mErrorLineNumber != mPrevErrorLineNumber) { michael@0: mErrorLine = mScanner->GetCurrentLine(); michael@0: mPrevErrorLineNumber = mErrorLineNumber; michael@0: } michael@0: } else { michael@0: mError.AppendLiteral(" "); michael@0: mError.Append(aErrorText); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::ReportUnexpected(const char *aMessage) michael@0: { michael@0: if (!ShouldReportErrors()) return; michael@0: michael@0: nsAutoString str; michael@0: sStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(), michael@0: getter_Copies(str)); michael@0: AddToError(str); michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::ReportUnexpected(const char *aMessage, michael@0: const nsString &aParam) michael@0: { michael@0: if (!ShouldReportErrors()) return; michael@0: michael@0: nsAutoString qparam; michael@0: nsStyleUtil::AppendEscapedCSSIdent(aParam, qparam); michael@0: const char16_t *params[1] = { qparam.get() }; michael@0: michael@0: nsAutoString str; michael@0: sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(), michael@0: params, ArrayLength(params), michael@0: getter_Copies(str)); michael@0: AddToError(str); michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::ReportUnexpected(const char *aMessage, michael@0: const nsCSSToken &aToken) michael@0: { michael@0: if (!ShouldReportErrors()) return; michael@0: michael@0: nsAutoString tokenString; michael@0: aToken.AppendToString(tokenString); michael@0: const char16_t *params[1] = { tokenString.get() }; michael@0: michael@0: nsAutoString str; michael@0: sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(), michael@0: params, ArrayLength(params), michael@0: getter_Copies(str)); michael@0: AddToError(str); michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::ReportUnexpected(const char *aMessage, michael@0: const nsCSSToken &aToken, michael@0: char16_t aChar) michael@0: { michael@0: if (!ShouldReportErrors()) return; michael@0: michael@0: nsAutoString tokenString; michael@0: aToken.AppendToString(tokenString); michael@0: const char16_t charStr[2] = { aChar, 0 }; michael@0: const char16_t *params[2] = { tokenString.get(), charStr }; michael@0: michael@0: nsAutoString str; michael@0: sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(), michael@0: params, ArrayLength(params), michael@0: getter_Copies(str)); michael@0: AddToError(str); michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::ReportUnexpectedEOF(const char *aMessage) michael@0: { michael@0: if (!ShouldReportErrors()) return; michael@0: michael@0: nsAutoString innerStr; michael@0: sStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(), michael@0: getter_Copies(innerStr)); michael@0: const char16_t *params[1] = { innerStr.get() }; michael@0: michael@0: nsAutoString str; michael@0: sStringBundle->FormatStringFromName(MOZ_UTF16("PEUnexpEOF2"), michael@0: params, ArrayLength(params), michael@0: getter_Copies(str)); michael@0: AddToError(str); michael@0: } michael@0: michael@0: void michael@0: ErrorReporter::ReportUnexpectedEOF(char16_t aExpected) michael@0: { michael@0: if (!ShouldReportErrors()) return; michael@0: michael@0: const char16_t expectedStr[] = { michael@0: char16_t('\''), aExpected, char16_t('\''), char16_t(0) michael@0: }; michael@0: const char16_t *params[1] = { expectedStr }; michael@0: michael@0: nsAutoString str; michael@0: sStringBundle->FormatStringFromName(MOZ_UTF16("PEUnexpEOF2"), michael@0: params, ArrayLength(params), michael@0: getter_Copies(str)); michael@0: AddToError(str); michael@0: } michael@0: michael@0: } // namespace css michael@0: } // namespace mozilla michael@0: michael@0: #endif