Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : /* diagnostic reporting for CSS style sheet parser */
7 :
8 : #include "mozilla/css/ErrorReporter.h"
9 :
10 : #include "mozilla/StyleSheetInlines.h"
11 : #include "mozilla/css/Loader.h"
12 : #include "mozilla/Preferences.h"
13 : #include "mozilla/Services.h"
14 : #include "mozilla/SystemGroup.h"
15 : #include "nsCSSScanner.h"
16 : #include "nsIConsoleService.h"
17 : #include "nsIDocument.h"
18 : #include "nsIFactory.h"
19 : #include "nsIScriptError.h"
20 : #include "nsIStringBundle.h"
21 : #include "nsServiceManagerUtils.h"
22 : #include "nsStyleUtil.h"
23 : #include "nsThreadUtils.h"
24 :
25 : #ifdef CSS_REPORT_PARSE_ERRORS
26 :
27 : using namespace mozilla;
28 :
29 : namespace {
30 0 : class ShortTermURISpecCache : public Runnable {
31 : public:
32 0 : ShortTermURISpecCache()
33 0 : : Runnable("ShortTermURISpecCache")
34 0 : , mPending(false) {}
35 :
36 0 : nsString const& GetSpec(nsIURI* aURI) {
37 0 : if (mURI != aURI) {
38 0 : mURI = aURI;
39 :
40 0 : nsAutoCString cSpec;
41 0 : nsresult rv = mURI->GetSpec(cSpec);
42 0 : if (NS_FAILED(rv)) {
43 0 : cSpec.AssignLiteral("[nsIURI::GetSpec failed]");
44 : }
45 0 : CopyUTF8toUTF16(cSpec, mSpec);
46 : }
47 0 : return mSpec;
48 : }
49 :
50 0 : bool IsInUse() const { return mURI != nullptr; }
51 0 : bool IsPending() const { return mPending; }
52 0 : void SetPending() { mPending = true; }
53 :
54 : // When invoked as a runnable, zap the cache.
55 0 : NS_IMETHOD Run() override {
56 0 : mURI = nullptr;
57 0 : mSpec.Truncate();
58 0 : mPending = false;
59 0 : return NS_OK;
60 : }
61 :
62 : private:
63 : nsCOMPtr<nsIURI> mURI;
64 : nsString mSpec;
65 : bool mPending;
66 : };
67 :
68 : } // namespace
69 :
70 : static bool sReportErrors;
71 : static nsIConsoleService *sConsoleService;
72 : static nsIFactory *sScriptErrorFactory;
73 : static nsIStringBundle *sStringBundle;
74 : static ShortTermURISpecCache *sSpecCache;
75 :
76 : #define CSS_ERRORS_PREF "layout.css.report_errors"
77 :
78 : static bool
79 2 : InitGlobals()
80 : {
81 2 : MOZ_ASSERT(!sConsoleService && !sScriptErrorFactory && !sStringBundle,
82 : "should not have been called");
83 :
84 2 : if (NS_FAILED(Preferences::AddBoolVarCache(&sReportErrors, CSS_ERRORS_PREF,
85 : true))) {
86 0 : return false;
87 : }
88 :
89 4 : nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
90 2 : if (!cs) {
91 0 : return false;
92 : }
93 :
94 4 : nsCOMPtr<nsIFactory> sf = do_GetClassObject(NS_SCRIPTERROR_CONTRACTID);
95 2 : if (!sf) {
96 0 : return false;
97 : }
98 :
99 4 : nsCOMPtr<nsIStringBundleService> sbs = services::GetStringBundleService();
100 2 : if (!sbs) {
101 0 : return false;
102 : }
103 :
104 4 : nsCOMPtr<nsIStringBundle> sb;
105 4 : nsresult rv = sbs->CreateBundle("chrome://global/locale/css.properties",
106 4 : getter_AddRefs(sb));
107 2 : if (NS_FAILED(rv) || !sb) {
108 0 : return false;
109 : }
110 :
111 2 : cs.forget(&sConsoleService);
112 2 : sf.forget(&sScriptErrorFactory);
113 2 : sb.forget(&sStringBundle);
114 :
115 2 : return true;
116 : }
117 :
118 : static inline bool
119 198 : ShouldReportErrors()
120 : {
121 198 : if (!sConsoleService) {
122 2 : if (!InitGlobals()) {
123 0 : return false;
124 : }
125 : }
126 198 : return sReportErrors;
127 : }
128 :
129 : namespace mozilla {
130 : namespace css {
131 :
132 : /* static */ void
133 0 : ErrorReporter::ReleaseGlobals()
134 : {
135 0 : NS_IF_RELEASE(sConsoleService);
136 0 : NS_IF_RELEASE(sScriptErrorFactory);
137 0 : NS_IF_RELEASE(sStringBundle);
138 0 : NS_IF_RELEASE(sSpecCache);
139 0 : }
140 :
141 2380 : ErrorReporter::ErrorReporter(const nsCSSScanner& aScanner,
142 : const StyleSheet* aSheet,
143 : const Loader* aLoader,
144 2380 : nsIURI* aURI)
145 : : mScanner(&aScanner), mSheet(aSheet), mLoader(aLoader), mURI(aURI),
146 : mInnerWindowID(0), mErrorLineNumber(0), mPrevErrorLineNumber(0),
147 2380 : mErrorColNumber(0)
148 : {
149 2380 : }
150 :
151 0 : ErrorReporter::ErrorReporter(const StyleSheet* aSheet,
152 : const Loader* aLoader,
153 0 : nsIURI* aURI)
154 : : mScanner(nullptr), mSheet(aSheet), mLoader(aLoader), mURI(aURI),
155 : mInnerWindowID(0), mErrorLineNumber(0), mPrevErrorLineNumber(0),
156 0 : mErrorColNumber(0)
157 : {
158 0 : }
159 :
160 4760 : ErrorReporter::~ErrorReporter()
161 : {
162 : // Schedule deferred cleanup for cached data. We want to strike a
163 : // balance between performance and memory usage, so we only allow
164 : // short-term caching.
165 2380 : if (sSpecCache && sSpecCache->IsInUse() && !sSpecCache->IsPending()) {
166 0 : nsCOMPtr<nsIRunnable> runnable(sSpecCache);
167 : nsresult rv =
168 : SystemGroup::Dispatch("ShortTermURISpecCache", TaskCategory::Other,
169 0 : runnable.forget());
170 0 : if (NS_FAILED(rv)) {
171 : // Peform the "deferred" cleanup immediately if the dispatch fails.
172 0 : sSpecCache->Run();
173 : } else {
174 0 : sSpecCache->SetPending();
175 : }
176 : }
177 2380 : }
178 :
179 : void
180 212 : ErrorReporter::OutputError()
181 : {
182 212 : if (mError.IsEmpty()) {
183 424 : return;
184 : }
185 0 : if (!ShouldReportErrors()) {
186 0 : ClearError();
187 0 : return;
188 : }
189 :
190 0 : if (mInnerWindowID == 0 && (mSheet || mLoader)) {
191 0 : if (mSheet) {
192 0 : mInnerWindowID = mSheet->FindOwningWindowInnerID();
193 : }
194 0 : if (mInnerWindowID == 0 && mLoader) {
195 0 : nsIDocument* doc = mLoader->GetDocument();
196 0 : if (doc) {
197 0 : mInnerWindowID = doc->InnerWindowID();
198 : }
199 : }
200 : // don't attempt this again, even if we failed
201 0 : mSheet = nullptr;
202 0 : mLoader = nullptr;
203 : }
204 :
205 0 : if (mFileName.IsEmpty()) {
206 0 : if (mURI) {
207 0 : if (!sSpecCache) {
208 0 : sSpecCache = new ShortTermURISpecCache;
209 0 : NS_ADDREF(sSpecCache);
210 : }
211 0 : mFileName = sSpecCache->GetSpec(mURI);
212 0 : mURI = nullptr;
213 : } else {
214 0 : mFileName.AssignLiteral("from DOM");
215 : }
216 : }
217 :
218 : nsresult rv;
219 : nsCOMPtr<nsIScriptError> errorObject =
220 0 : do_CreateInstance(sScriptErrorFactory, &rv);
221 :
222 0 : if (NS_SUCCEEDED(rv)) {
223 0 : rv = errorObject->InitWithWindowID(mError,
224 : mFileName,
225 : mErrorLine,
226 : mErrorLineNumber,
227 : mErrorColNumber,
228 : nsIScriptError::warningFlag,
229 : "CSS Parser",
230 : mInnerWindowID);
231 0 : if (NS_SUCCEEDED(rv)) {
232 0 : sConsoleService->LogMessage(errorObject);
233 : }
234 : }
235 :
236 0 : ClearError();
237 : }
238 :
239 : void
240 0 : ErrorReporter::OutputError(uint32_t aLineNumber, uint32_t aColNumber)
241 : {
242 0 : mErrorLineNumber = aLineNumber;
243 0 : mErrorColNumber = aColNumber;
244 0 : OutputError();
245 0 : }
246 :
247 : // When Stylo's CSS parser is in use, this reporter does not have access to the CSS parser's
248 : // state. The users of ErrorReporter need to provide:
249 : // - the line number of the error
250 : // - the column number of the error
251 : // - the complete source line containing the invalid CSS
252 :
253 : void
254 0 : ErrorReporter::OutputError(uint32_t aLineNumber,
255 : uint32_t aColNumber,
256 : const nsACString& aSourceLine)
257 : {
258 0 : mErrorLine.Truncate();
259 : // This could be a really long string for minified CSS; just leave it empty if we OOM.
260 0 : if (!AppendUTF8toUTF16(aSourceLine, mErrorLine, fallible)) {
261 0 : mErrorLine.Truncate();
262 : }
263 0 : mPrevErrorLineNumber = aLineNumber;
264 0 : OutputError(aLineNumber, aColNumber);
265 0 : }
266 :
267 : void
268 9576 : ErrorReporter::ClearError()
269 : {
270 9576 : mError.Truncate();
271 9576 : }
272 :
273 : void
274 66 : ErrorReporter::AddToError(const nsString &aErrorText)
275 : {
276 66 : if (!ShouldReportErrors()) return;
277 :
278 66 : if (mError.IsEmpty()) {
279 64 : mError = aErrorText;
280 64 : mErrorLineNumber = mScanner ? mScanner->GetLineNumber() : 0;
281 64 : mErrorColNumber = mScanner ? mScanner->GetColumnNumber() : 0;
282 : // Retrieve the error line once per line, and reuse the same nsString
283 : // for all errors on that line. That causes the text of the line to
284 : // be shared among all the nsIScriptError objects.
285 64 : if (mErrorLine.IsEmpty() || mErrorLineNumber != mPrevErrorLineNumber) {
286 : // Be careful here: the error line might be really long and OOM
287 : // when we try to make a copy here. If so, just leave it empty.
288 64 : if (!mScanner || !mErrorLine.Assign(mScanner->GetCurrentLine(), fallible)) {
289 0 : mErrorLine.Truncate();
290 : }
291 64 : mPrevErrorLineNumber = mErrorLineNumber;
292 : }
293 : } else {
294 2 : mError.AppendLiteral(" ");
295 2 : mError.Append(aErrorText);
296 : }
297 : }
298 :
299 : void
300 0 : ErrorReporter::ReportUnexpected(const char *aMessage)
301 : {
302 0 : if (!ShouldReportErrors()) return;
303 :
304 0 : nsAutoString str;
305 0 : sStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
306 0 : getter_Copies(str));
307 0 : AddToError(str);
308 : }
309 :
310 : void
311 0 : ErrorReporter::ReportUnexpected(const char *aMessage,
312 : const nsString &aParam)
313 : {
314 0 : if (!ShouldReportErrors()) return;
315 :
316 0 : nsAutoString qparam;
317 0 : nsStyleUtil::AppendEscapedCSSIdent(aParam, qparam);
318 0 : const char16_t *params[1] = { qparam.get() };
319 :
320 0 : nsAutoString str;
321 0 : sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
322 0 : params, ArrayLength(params),
323 0 : getter_Copies(str));
324 0 : AddToError(str);
325 : }
326 :
327 : void
328 66 : ErrorReporter::ReportUnexpectedUnescaped(const char *aMessage,
329 : const nsAutoString& aParam)
330 : {
331 66 : if (!ShouldReportErrors()) return;
332 :
333 66 : const char16_t *params[1] = { aParam.get() };
334 :
335 132 : nsAutoString str;
336 198 : sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
337 66 : params, ArrayLength(params),
338 198 : getter_Copies(str));
339 66 : AddToError(str);
340 : }
341 :
342 : void
343 66 : ErrorReporter::ReportUnexpected(const char *aMessage,
344 : const nsCSSToken &aToken)
345 : {
346 66 : if (!ShouldReportErrors()) return;
347 :
348 132 : nsAutoString tokenString;
349 66 : aToken.AppendToString(tokenString);
350 66 : ReportUnexpectedUnescaped(aMessage, tokenString);
351 : }
352 :
353 : void
354 0 : ErrorReporter::ReportUnexpected(const char *aMessage,
355 : const nsCSSToken &aToken,
356 : char16_t aChar)
357 : {
358 0 : if (!ShouldReportErrors()) return;
359 :
360 0 : nsAutoString tokenString;
361 0 : aToken.AppendToString(tokenString);
362 0 : const char16_t charStr[2] = { aChar, 0 };
363 0 : const char16_t *params[2] = { tokenString.get(), charStr };
364 :
365 0 : nsAutoString str;
366 0 : sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
367 0 : params, ArrayLength(params),
368 0 : getter_Copies(str));
369 0 : AddToError(str);
370 : }
371 :
372 : void
373 0 : ErrorReporter::ReportUnexpected(const char *aMessage,
374 : const nsString &aParam,
375 : const nsString &aValue)
376 : {
377 0 : if (!ShouldReportErrors()) return;
378 :
379 0 : nsAutoString qparam;
380 0 : nsStyleUtil::AppendEscapedCSSIdent(aParam, qparam);
381 0 : const char16_t *params[2] = { qparam.get(), aValue.get() };
382 :
383 0 : nsAutoString str;
384 0 : sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
385 0 : params, ArrayLength(params),
386 0 : getter_Copies(str));
387 0 : AddToError(str);
388 : }
389 :
390 : void
391 0 : ErrorReporter::ReportUnexpectedEOF(const char *aMessage)
392 : {
393 0 : if (!ShouldReportErrors()) return;
394 :
395 0 : nsAutoString innerStr;
396 0 : sStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
397 0 : getter_Copies(innerStr));
398 0 : const char16_t *params[1] = { innerStr.get() };
399 :
400 0 : nsAutoString str;
401 0 : sStringBundle->FormatStringFromName(u"PEUnexpEOF2",
402 0 : params, ArrayLength(params),
403 0 : getter_Copies(str));
404 0 : AddToError(str);
405 : }
406 :
407 : void
408 0 : ErrorReporter::ReportUnexpectedEOF(char16_t aExpected)
409 : {
410 0 : if (!ShouldReportErrors()) return;
411 :
412 : const char16_t expectedStr[] = {
413 : char16_t('\''), aExpected, char16_t('\''), char16_t(0)
414 0 : };
415 0 : const char16_t *params[1] = { expectedStr };
416 :
417 0 : nsAutoString str;
418 0 : sStringBundle->FormatStringFromName(u"PEUnexpEOF2",
419 0 : params, ArrayLength(params),
420 0 : getter_Copies(str));
421 0 : AddToError(str);
422 : }
423 :
424 : } // namespace css
425 : } // namespace mozilla
426 :
427 : #endif
|