Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/Components.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 = components::StringBundle::Service();
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(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