Line data Source code
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.h"
12 : #include "mozilla/StyleSheetInlines.h"
13 : #include "mozilla/css/Loader.h"
14 : #include "mozilla/Preferences.h"
15 : #include "mozilla/Services.h"
16 : #include "mozilla/SystemGroup.h"
17 : #include "nsCSSScanner.h"
18 : #include "nsIConsoleService.h"
19 : #include "nsIDocument.h"
20 : #include "nsIDocShell.h"
21 : #include "nsIFactory.h"
22 : #include "nsINode.h"
23 : #include "nsIScriptError.h"
24 : #include "nsISensitiveInfoHiddenURI.h"
25 : #include "nsIStringBundle.h"
26 : #include "nsServiceManagerUtils.h"
27 : #include "nsStyleUtil.h"
28 : #include "nsThreadUtils.h"
29 : #include "nsNetUtil.h"
30 :
31 : using namespace mozilla;
32 : using namespace mozilla::css;
33 :
34 : namespace {
35 0 : class ShortTermURISpecCache : public Runnable {
36 : public:
37 0 : ShortTermURISpecCache()
38 0 : : Runnable("ShortTermURISpecCache")
39 0 : , mPending(false) {}
40 :
41 0 : nsString const& GetSpec(nsIURI* aURI) {
42 0 : if (mURI != aURI) {
43 0 : mURI = aURI;
44 :
45 0 : if (NS_FAILED(NS_GetSanitizedURIStringFromURI(mURI, mSpec))) {
46 0 : mSpec.AssignLiteral("[nsIURI::GetSpec failed]");
47 : }
48 : }
49 0 : return mSpec;
50 : }
51 :
52 0 : bool IsInUse() const { return mURI != nullptr; }
53 : bool IsPending() const { return mPending; }
54 0 : void SetPending() { mPending = true; }
55 :
56 : // When invoked as a runnable, zap the cache.
57 0 : NS_IMETHOD Run() override {
58 0 : mURI = nullptr;
59 0 : mSpec.Truncate();
60 0 : mPending = false;
61 0 : return NS_OK;
62 : }
63 :
64 : private:
65 : nsCOMPtr<nsIURI> mURI;
66 : nsString mSpec;
67 : bool mPending;
68 : };
69 :
70 : } // namespace
71 :
72 : bool ErrorReporter::sInitialized = false;
73 :
74 : static nsIConsoleService *sConsoleService;
75 : static nsIFactory *sScriptErrorFactory;
76 : static nsIStringBundle *sStringBundle;
77 : static ShortTermURISpecCache *sSpecCache;
78 :
79 : void
80 0 : ErrorReporter::InitGlobals()
81 : {
82 0 : MOZ_RELEASE_ASSERT(NS_IsMainThread());
83 0 : MOZ_ASSERT(!sInitialized, "should not have been called");
84 :
85 0 : sInitialized = true;
86 :
87 0 : nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
88 0 : if (!cs) {
89 0 : return;
90 : }
91 :
92 0 : nsCOMPtr<nsIFactory> sf = do_GetClassObject(NS_SCRIPTERROR_CONTRACTID);
93 0 : if (!sf) {
94 0 : return;
95 : }
96 :
97 0 : nsCOMPtr<nsIStringBundleService> sbs = services::GetStringBundleService();
98 0 : if (!sbs) {
99 0 : return;
100 : }
101 :
102 0 : nsCOMPtr<nsIStringBundle> sb;
103 0 : nsresult rv = sbs->CreateBundle("chrome://global/locale/css.properties",
104 0 : getter_AddRefs(sb));
105 0 : if (NS_FAILED(rv) || !sb) {
106 0 : return;
107 : }
108 :
109 0 : cs.forget(&sConsoleService);
110 0 : sf.forget(&sScriptErrorFactory);
111 0 : sb.forget(&sStringBundle);
112 : }
113 :
114 : namespace mozilla {
115 : namespace css {
116 :
117 : /* static */ void
118 0 : ErrorReporter::ReleaseGlobals()
119 : {
120 0 : NS_IF_RELEASE(sConsoleService);
121 0 : NS_IF_RELEASE(sScriptErrorFactory);
122 0 : NS_IF_RELEASE(sStringBundle);
123 0 : NS_IF_RELEASE(sSpecCache);
124 0 : }
125 :
126 : static uint64_t
127 0 : FindInnerWindowID(const StyleSheet* aSheet, const Loader* aLoader)
128 : {
129 0 : uint64_t innerWindowID = 0;
130 0 : if (aSheet) {
131 0 : innerWindowID = aSheet->FindOwningWindowInnerID();
132 : }
133 0 : if (innerWindowID == 0 && aLoader) {
134 0 : if (nsIDocument* doc = aLoader->GetDocument()) {
135 0 : innerWindowID = doc->InnerWindowID();
136 : }
137 : }
138 0 : return innerWindowID;
139 : }
140 :
141 0 : ErrorReporter::ErrorReporter(const StyleSheet* aSheet,
142 : const Loader* aLoader,
143 0 : nsIURI* aURI)
144 : : mSheet(aSheet)
145 : , mLoader(aLoader)
146 : , mURI(aURI)
147 : , mErrorLineNumber(0)
148 : , mPrevErrorLineNumber(0)
149 0 : , mErrorColNumber(0)
150 : {
151 0 : MOZ_ASSERT(ShouldReportErrors(mSheet, mLoader));
152 0 : EnsureGlobalsInitialized();
153 0 : }
154 :
155 0 : ErrorReporter::~ErrorReporter()
156 : {
157 0 : MOZ_ASSERT(NS_IsMainThread());
158 : // Schedule deferred cleanup for cached data. We want to strike a
159 : // balance between performance and memory usage, so we only allow
160 : // short-term caching.
161 0 : if (sSpecCache && sSpecCache->IsInUse() && !sSpecCache->IsPending()) {
162 0 : nsCOMPtr<nsIRunnable> runnable(sSpecCache);
163 : nsresult rv =
164 0 : SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
165 0 : if (NS_FAILED(rv)) {
166 : // Peform the "deferred" cleanup immediately if the dispatch fails.
167 0 : sSpecCache->Run();
168 : } else {
169 0 : sSpecCache->SetPending();
170 : }
171 : }
172 0 : }
173 :
174 : bool
175 0 : ErrorReporter::ShouldReportErrors(const nsIDocument& aDoc)
176 : {
177 0 : MOZ_ASSERT(NS_IsMainThread());
178 15 : nsIDocShell* shell = aDoc.GetDocShell();
179 15 : if (!shell) {
180 : return false;
181 : }
182 :
183 0 : bool report = false;
184 15 : shell->GetCssErrorReportingEnabled(&report);
185 15 : return report;
186 : }
187 :
188 : static nsINode*
189 0 : SheetOwner(const StyleSheet& aSheet)
190 : {
191 0 : if (nsINode* owner = aSheet.GetOwnerNode()) {
192 : return owner;
193 : }
194 :
195 0 : auto* associated = aSheet.GetAssociatedDocumentOrShadowRoot();
196 0 : return associated ? &associated->AsNode() : nullptr;
197 : }
198 :
199 : bool
200 0 : ErrorReporter::ShouldReportErrors(const StyleSheet* aSheet,
201 : const Loader* aLoader)
202 : {
203 0 : MOZ_ASSERT(NS_IsMainThread());
204 :
205 0 : if (!StaticPrefs::layout_css_report_errors()) {
206 : return false;
207 : }
208 :
209 0 : if (aSheet) {
210 0 : nsINode* owner = SheetOwner(*aSheet);
211 0 : if (owner && ShouldReportErrors(*owner->OwnerDoc())) {
212 : return true;
213 : }
214 : }
215 :
216 0 : if (aLoader && aLoader->GetDocument() &&
217 0 : ShouldReportErrors(*aLoader->GetDocument())) {
218 : return true;
219 : }
220 :
221 0 : return false;
222 : }
223 :
224 : void
225 0 : ErrorReporter::OutputError()
226 : {
227 0 : MOZ_ASSERT(NS_IsMainThread());
228 0 : MOZ_ASSERT(ShouldReportErrors(mSheet, mLoader));
229 :
230 0 : if (mError.IsEmpty()) {
231 0 : return;
232 : }
233 :
234 0 : if (mFileName.IsEmpty()) {
235 0 : if (mURI) {
236 0 : if (!sSpecCache) {
237 0 : sSpecCache = new ShortTermURISpecCache;
238 0 : NS_ADDREF(sSpecCache);
239 : }
240 0 : mFileName = sSpecCache->GetSpec(mURI);
241 0 : mURI = nullptr;
242 : } else {
243 0 : mFileName.AssignLiteral("from DOM");
244 : }
245 : }
246 :
247 : nsresult rv;
248 : nsCOMPtr<nsIScriptError> errorObject =
249 0 : do_CreateInstance(sScriptErrorFactory, &rv);
250 :
251 0 : if (NS_SUCCEEDED(rv)) {
252 : // It is safe to used InitWithSanitizedSource because mFileName is
253 : // an already anonymized uri spec.
254 0 : rv = errorObject->InitWithSanitizedSource(mError,
255 : mFileName,
256 : mErrorLine,
257 : mErrorLineNumber,
258 : mErrorColNumber,
259 : nsIScriptError::warningFlag,
260 : "CSS Parser",
261 : FindInnerWindowID(mSheet, mLoader));
262 0 : if (NS_SUCCEEDED(rv)) {
263 0 : sConsoleService->LogMessage(errorObject);
264 : }
265 : }
266 :
267 0 : ClearError();
268 : }
269 :
270 : // When Stylo's CSS parser is in use, this reporter does not have access to the CSS parser's
271 : // state. The users of ErrorReporter need to provide:
272 : // - the line number of the error
273 : // - the column number of the error
274 : // - the complete source line containing the invalid CSS
275 :
276 : void
277 0 : ErrorReporter::OutputError(uint32_t aLineNumber,
278 : uint32_t aColNumber,
279 : const nsACString& aSourceLine)
280 : {
281 0 : mErrorLineNumber = aLineNumber;
282 0 : mErrorColNumber = aColNumber;
283 :
284 : // Retrieve the error line once per line, and reuse the same nsString
285 : // for all errors on that line. That causes the text of the line to
286 : // be shared among all the nsIScriptError objects.
287 0 : if (mErrorLine.IsEmpty() || mErrorLineNumber != mPrevErrorLineNumber) {
288 0 : mErrorLine.Truncate();
289 : // This could be a really long string for minified CSS; just leave it empty if we OOM.
290 0 : if (!AppendUTF8toUTF16(aSourceLine, mErrorLine, fallible)) {
291 0 : mErrorLine.Truncate();
292 : }
293 :
294 0 : mPrevErrorLineNumber = aLineNumber;
295 : }
296 :
297 0 : OutputError();
298 0 : }
299 :
300 : void
301 0 : ErrorReporter::ClearError()
302 : {
303 0 : mError.Truncate();
304 0 : }
305 :
306 : void
307 0 : ErrorReporter::AddToError(const nsString &aErrorText)
308 : {
309 0 : MOZ_ASSERT(ShouldReportErrors(mSheet, mLoader));
310 :
311 0 : if (mError.IsEmpty()) {
312 0 : mError = aErrorText;
313 : } else {
314 0 : mError.AppendLiteral(" ");
315 0 : mError.Append(aErrorText);
316 : }
317 0 : }
318 :
319 : void
320 0 : ErrorReporter::ReportUnexpected(const char *aMessage)
321 : {
322 0 : MOZ_ASSERT(ShouldReportErrors(mSheet, mLoader));
323 :
324 0 : nsAutoString str;
325 0 : sStringBundle->GetStringFromName(aMessage, str);
326 0 : AddToError(str);
327 0 : }
328 :
329 : void
330 0 : ErrorReporter::ReportUnexpectedUnescaped(const char *aMessage,
331 : const nsAutoString& aParam)
332 : {
333 0 : MOZ_ASSERT(ShouldReportErrors(mSheet, mLoader));
334 :
335 0 : const char16_t *params[1] = { aParam.get() };
336 :
337 0 : nsAutoString str;
338 : sStringBundle->FormatStringFromName(aMessage, params, ArrayLength(params),
339 0 : str);
340 0 : AddToError(str);
341 0 : }
342 :
343 : } // namespace css
344 : } // namespace mozilla
|