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