1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /* diagnostic reporting for CSS style sheet parser */
8
9 #include "mozilla/css/ErrorReporter.h"
10
11 #include "mozilla/StaticPrefs_layout.h"
12 #include "mozilla/StyleSheetInlines.h"
13 #include "mozilla/css/Loader.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/SchedulerGroup.h"
16 #include "mozilla/Components.h"
17 #include "nsIConsoleService.h"
18 #include "mozilla/dom/Document.h"
19 #include "nsComponentManagerUtils.h"
20 #include "nsIDocShell.h"
21 #include "nsIFactory.h"
22 #include "nsINode.h"
23 #include "nsIScriptError.h"
24 #include "nsIStringBundle.h"
25 #include "nsServiceManagerUtils.h"
26 #include "nsStyleUtil.h"
27 #include "nsThreadUtils.h"
28 #include "nsNetUtil.h"
29
30 using namespace mozilla;
31 using namespace mozilla::css;
32 using namespace mozilla::dom;
33
34 namespace {
35 class ShortTermURISpecCache : public Runnable {
36 public:
ShortTermURISpecCache()37 ShortTermURISpecCache()
38 : Runnable("ShortTermURISpecCache"), mPending(false) {}
39
GetSpec(nsIURI * aURI)40 nsString const& GetSpec(nsIURI* aURI) {
41 if (mURI != aURI) {
42 mURI = aURI;
43
44 if (NS_FAILED(NS_GetSanitizedURIStringFromURI(mURI, mSpec))) {
45 mSpec.AssignLiteral("[nsIURI::GetSpec failed]");
46 }
47 }
48 return mSpec;
49 }
50
IsInUse() const51 bool IsInUse() const { return mURI != nullptr; }
IsPending() const52 bool IsPending() const { return mPending; }
SetPending()53 void SetPending() { mPending = true; }
54
55 // When invoked as a runnable, zap the cache.
Run()56 NS_IMETHOD Run() override {
57 mURI = nullptr;
58 mSpec.Truncate();
59 mPending = false;
60 return NS_OK;
61 }
62
63 private:
64 nsCOMPtr<nsIURI> mURI;
65 nsString mSpec;
66 bool mPending;
67 };
68
69 } // namespace
70
71 bool ErrorReporter::sInitialized = false;
72
73 static nsIConsoleService* sConsoleService;
74 static nsIFactory* sScriptErrorFactory;
75 static nsIStringBundle* sStringBundle;
76 static ShortTermURISpecCache* sSpecCache;
77
InitGlobals()78 void ErrorReporter::InitGlobals() {
79 MOZ_RELEASE_ASSERT(NS_IsMainThread());
80 MOZ_ASSERT(!sInitialized, "should not have been called");
81
82 sInitialized = true;
83
84 nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
85 if (!cs) {
86 return;
87 }
88
89 nsCOMPtr<nsIFactory> sf = do_GetClassObject(NS_SCRIPTERROR_CONTRACTID);
90 if (!sf) {
91 return;
92 }
93
94 nsCOMPtr<nsIStringBundleService> sbs = components::StringBundle::Service();
95 if (!sbs) {
96 return;
97 }
98
99 nsCOMPtr<nsIStringBundle> sb;
100 nsresult rv = sbs->CreateBundle("chrome://global/locale/css.properties",
101 getter_AddRefs(sb));
102 if (NS_FAILED(rv) || !sb) {
103 return;
104 }
105
106 cs.forget(&sConsoleService);
107 sf.forget(&sScriptErrorFactory);
108 sb.forget(&sStringBundle);
109 }
110
111 namespace mozilla {
112 namespace css {
113
114 /* static */
ReleaseGlobals()115 void ErrorReporter::ReleaseGlobals() {
116 NS_IF_RELEASE(sConsoleService);
117 NS_IF_RELEASE(sScriptErrorFactory);
118 NS_IF_RELEASE(sStringBundle);
119 NS_IF_RELEASE(sSpecCache);
120 }
121
FindInnerWindowId(const StyleSheet * aSheet,const Loader * aLoader)122 uint64_t ErrorReporter::FindInnerWindowId(const StyleSheet* aSheet,
123 const Loader* aLoader) {
124 if (aSheet) {
125 if (uint64_t id = aSheet->FindOwningWindowInnerID()) {
126 return id;
127 }
128 }
129 if (aLoader) {
130 if (Document* doc = aLoader->GetDocument()) {
131 return doc->InnerWindowID();
132 }
133 }
134 return 0;
135 }
136
ErrorReporter(uint64_t aInnerWindowId)137 ErrorReporter::ErrorReporter(uint64_t aInnerWindowId)
138 : mInnerWindowId(aInnerWindowId) {
139 EnsureGlobalsInitialized();
140 }
141
~ErrorReporter()142 ErrorReporter::~ErrorReporter() {
143 MOZ_ASSERT(NS_IsMainThread());
144 // Schedule deferred cleanup for cached data. We want to strike a
145 // balance between performance and memory usage, so we only allow
146 // short-term caching.
147 if (sSpecCache && sSpecCache->IsInUse() && !sSpecCache->IsPending()) {
148 nsCOMPtr<nsIRunnable> runnable(sSpecCache);
149 nsresult rv =
150 SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
151 if (NS_FAILED(rv)) {
152 // Peform the "deferred" cleanup immediately if the dispatch fails.
153 sSpecCache->Run();
154 } else {
155 sSpecCache->SetPending();
156 }
157 }
158 }
159
ShouldReportErrors(const Document & aDoc)160 bool ErrorReporter::ShouldReportErrors(const Document& aDoc) {
161 MOZ_ASSERT(NS_IsMainThread());
162 nsIDocShell* shell = aDoc.GetDocShell();
163 if (!shell) {
164 return false;
165 }
166
167 bool report = false;
168 shell->GetCssErrorReportingEnabled(&report);
169 return report;
170 }
171
SheetOwner(const StyleSheet & aSheet)172 static nsINode* SheetOwner(const StyleSheet& aSheet) {
173 if (nsINode* owner = aSheet.GetOwnerNode()) {
174 return owner;
175 }
176
177 auto* associated = aSheet.GetAssociatedDocumentOrShadowRoot();
178 return associated ? &associated->AsNode() : nullptr;
179 }
180
ShouldReportErrors(const StyleSheet * aSheet,const Loader * aLoader)181 bool ErrorReporter::ShouldReportErrors(const StyleSheet* aSheet,
182 const Loader* aLoader) {
183 MOZ_ASSERT(NS_IsMainThread());
184
185 if (!StaticPrefs::layout_css_report_errors()) {
186 return false;
187 }
188
189 if (aSheet) {
190 nsINode* owner = SheetOwner(*aSheet);
191 if (owner && ShouldReportErrors(*owner->OwnerDoc())) {
192 return true;
193 }
194 }
195
196 if (aLoader && aLoader->GetDocument() &&
197 ShouldReportErrors(*aLoader->GetDocument())) {
198 return true;
199 }
200
201 return false;
202 }
203
OutputError(const nsACString & aSourceLine,const nsACString & aSelectors,uint32_t aLineNumber,uint32_t aColNumber,nsIURI * aURI)204 void ErrorReporter::OutputError(const nsACString& aSourceLine,
205 const nsACString& aSelectors,
206 uint32_t aLineNumber, uint32_t aColNumber,
207 nsIURI* aURI) {
208 nsAutoString errorLine;
209 // This could be a really long string for minified CSS; just leave it empty
210 // if we OOM.
211 if (!AppendUTF8toUTF16(aSourceLine, errorLine, fallible)) {
212 errorLine.Truncate();
213 }
214
215 nsAutoString selectors;
216 if (!AppendUTF8toUTF16(aSelectors, selectors, fallible)) {
217 selectors.Truncate();
218 }
219
220 if (mError.IsEmpty()) {
221 return;
222 }
223
224 nsAutoString fileName;
225 if (aURI) {
226 if (!sSpecCache) {
227 sSpecCache = new ShortTermURISpecCache;
228 NS_ADDREF(sSpecCache);
229 }
230 fileName = sSpecCache->GetSpec(aURI);
231 } else {
232 fileName.AssignLiteral("from DOM");
233 }
234
235 nsresult rv;
236 nsCOMPtr<nsIScriptError> errorObject =
237 do_CreateInstance(sScriptErrorFactory, &rv);
238
239 if (NS_SUCCEEDED(rv)) {
240 // It is safe to used InitWithSanitizedSource because fileName is
241 // an already anonymized uri spec.
242 rv = errorObject->InitWithSanitizedSource(
243 mError, fileName, errorLine, aLineNumber, aColNumber,
244 nsIScriptError::warningFlag, "CSS Parser", mInnerWindowId);
245
246 if (NS_SUCCEEDED(rv)) {
247 errorObject->SetCssSelectors(selectors);
248 sConsoleService->LogMessage(errorObject);
249 }
250 }
251
252 mError.Truncate();
253 }
254
AddToError(const nsString & aErrorText)255 void ErrorReporter::AddToError(const nsString& aErrorText) {
256 if (mError.IsEmpty()) {
257 mError = aErrorText;
258 } else {
259 mError.AppendLiteral(" ");
260 mError.Append(aErrorText);
261 }
262 }
263
ReportUnexpected(const char * aMessage)264 void ErrorReporter::ReportUnexpected(const char* aMessage) {
265 nsAutoString str;
266 sStringBundle->GetStringFromName(aMessage, str);
267 AddToError(str);
268 }
269
ReportUnexpectedUnescaped(const char * aMessage,const nsTArray<nsString> & aParam)270 void ErrorReporter::ReportUnexpectedUnescaped(
271 const char* aMessage, const nsTArray<nsString>& aParam) {
272 nsAutoString str;
273 sStringBundle->FormatStringFromName(aMessage, aParam, str);
274 AddToError(str);
275 }
276
277 } // namespace css
278 } // namespace mozilla
279