ErrorReporter.cpp (6970B)
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/Components.h" 12 #include "mozilla/Preferences.h" 13 #include "mozilla/SchedulerGroup.h" 14 #include "mozilla/StaticPrefs_layout.h" 15 #include "mozilla/StyleSheetInlines.h" 16 #include "mozilla/css/Loader.h" 17 #include "mozilla/dom/Document.h" 18 #include "nsComponentManagerUtils.h" 19 #include "nsIConsoleService.h" 20 #include "nsIDocShell.h" 21 #include "nsIFactory.h" 22 #include "nsINode.h" 23 #include "nsIScriptError.h" 24 #include "nsIStringBundle.h" 25 #include "nsNetUtil.h" 26 #include "nsServiceManagerUtils.h" 27 #include "nsStyleUtil.h" 28 #include "nsThreadUtils.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: 37 ShortTermURISpecCache() 38 : Runnable("ShortTermURISpecCache"), mPending(false) {} 39 40 nsCString 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 51 bool IsInUse() const { return mURI != nullptr; } 52 bool IsPending() const { return mPending; } 53 void SetPending() { mPending = true; } 54 55 // When invoked as a runnable, zap the cache. 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 nsCString 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 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 */ 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 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 137 ErrorReporter::ErrorReporter(uint64_t aInnerWindowId) 138 : mInnerWindowId(aInnerWindowId) { 139 EnsureGlobalsInitialized(); 140 } 141 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 = SchedulerGroup::Dispatch(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 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 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 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 203 void ErrorReporter::OutputError(const nsACString& aSelectors, 204 uint32_t aLineNumber, uint32_t aColNumber, 205 nsIURI* aURI) { 206 nsAutoString selectors; 207 if (!AppendUTF8toUTF16(aSelectors, selectors, fallible)) { 208 selectors.Truncate(); 209 } 210 211 if (mError.IsEmpty()) { 212 return; 213 } 214 215 nsAutoCString fileName; 216 if (aURI) { 217 if (!sSpecCache) { 218 sSpecCache = new ShortTermURISpecCache; 219 NS_ADDREF(sSpecCache); 220 } 221 fileName = sSpecCache->GetSpec(aURI); 222 } else { 223 fileName.AssignLiteral("from DOM"); 224 } 225 226 nsresult rv; 227 nsCOMPtr<nsIScriptError> errorObject = 228 do_CreateInstance(sScriptErrorFactory, &rv); 229 230 if (NS_SUCCEEDED(rv)) { 231 // It is safe to used InitWithSanitizedSource because fileName is 232 // an already anonymized uri spec. 233 rv = errorObject->InitWithSanitizedSource( 234 mError, fileName, aLineNumber, aColNumber, nsIScriptError::warningFlag, 235 "CSS Parser", mInnerWindowId); 236 237 if (NS_SUCCEEDED(rv)) { 238 errorObject->SetCssSelectors(selectors); 239 sConsoleService->LogMessage(errorObject); 240 } 241 } 242 243 mError.Truncate(); 244 } 245 246 void ErrorReporter::AddToError(const nsString& aErrorText) { 247 if (mError.IsEmpty()) { 248 mError = aErrorText; 249 } else { 250 mError.AppendLiteral(" "); 251 mError.Append(aErrorText); 252 } 253 } 254 255 void ErrorReporter::ReportUnexpected(const char* aMessage) { 256 nsAutoString str; 257 sStringBundle->GetStringFromName(aMessage, str); 258 AddToError(str); 259 } 260 261 void ErrorReporter::ReportUnexpectedUnescaped( 262 const char* aMessage, const nsTArray<nsString>& aParam) { 263 nsAutoString str; 264 sStringBundle->FormatStringFromName(aMessage, aParam, str); 265 AddToError(str); 266 } 267 268 } // namespace css 269 } // namespace mozilla