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