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