Bug 1891710: part 2) Enable <Element-outerHTML.html> WPT for Trusted Types. r=smaug
[gecko.git] / gfx / src / DriverCrashGuard.cpp
blobc54a07b9ec9c1cc84b757f2b67a4ce932020cace
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 #include "DriverCrashGuard.h"
7 #include "gfxEnv.h"
8 #include "gfxConfig.h"
9 #include "nsAppDirectoryServiceDefs.h"
10 #include "nsDirectoryServiceUtils.h"
11 #include "nsExceptionHandler.h"
12 #include "nsServiceManagerUtils.h"
13 #include "nsString.h"
14 #include "nsXULAppAPI.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/StaticPrefs_gfx.h"
17 #include "mozilla/StaticPrefs_webgl.h"
18 #include "mozilla/Telemetry.h"
19 #include "mozilla/Components.h"
20 #include "mozilla/gfx/Logging.h"
21 #include "mozilla/dom/ContentChild.h"
23 namespace mozilla {
24 namespace gfx {
26 static const size_t NUM_CRASH_GUARD_TYPES = size_t(CrashGuardType::NUM_TYPES);
27 static const char* sCrashGuardNames[] = {
28 "d3d11layers",
29 "glcontext",
30 "wmfvpxvideo",
32 static_assert(MOZ_ARRAY_LENGTH(sCrashGuardNames) == NUM_CRASH_GUARD_TYPES,
33 "CrashGuardType updated without a name string");
35 static inline void BuildCrashGuardPrefName(CrashGuardType aType,
36 nsCString& aOutPrefName) {
37 MOZ_ASSERT(aType < CrashGuardType::NUM_TYPES);
38 MOZ_ASSERT(sCrashGuardNames[size_t(aType)]);
40 aOutPrefName.AssignLiteral("gfx.crash-guard.status.");
41 aOutPrefName.Append(sCrashGuardNames[size_t(aType)]);
44 DriverCrashGuard::DriverCrashGuard(CrashGuardType aType,
45 dom::ContentParent* aContentParent)
46 : mType(aType),
47 mMode(aContentParent ? Mode::Proxy : Mode::Normal),
48 mInitialized(false),
49 mGuardActivated(false),
50 mCrashDetected(false) {
51 BuildCrashGuardPrefName(aType, mStatusPref);
54 void DriverCrashGuard::InitializeIfNeeded() {
55 if (mInitialized) {
56 return;
59 mInitialized = true;
60 Initialize();
63 static inline bool AreCrashGuardsEnabled(CrashGuardType aType) {
64 // Crash guard isn't supported in the GPU or RDD process since the entire
65 // process is basically a crash guard.
66 if (XRE_IsGPUProcess() || XRE_IsRDDProcess()) {
67 return false;
69 #ifdef NIGHTLY_BUILD
70 // We only use the crash guard on non-nightly channels, since the nightly
71 // channel is for development and having graphics features perma-disabled
72 // is rather annoying. Unless the user forces is with an environment
73 // variable, which comes in handy for testing.
74 // We handle the WMFVPXVideo crash guard differently to the other and always
75 // enable it as it completely breaks playback and there's no way around it.
76 if (aType != CrashGuardType::WMFVPXVideo) {
77 return gfxEnv::MOZ_FORCE_CRASH_GUARD_NIGHTLY();
79 #endif
80 // Check to see if all guards have been disabled through the environment.
81 return !gfxEnv::MOZ_DISABLE_CRASH_GUARD();
84 void DriverCrashGuard::Initialize() {
85 if (!AreCrashGuardsEnabled(mType)) {
86 return;
89 // Using DriverCrashGuard off the main thread currently does not work. Under
90 // e10s it could conceivably work by dispatching the IPC calls via the main
91 // thread. In the parent process this would be harder. For now, we simply
92 // exit early instead.
93 if (!NS_IsMainThread()) {
94 return;
97 mGfxInfo = components::GfxInfo::Service();
99 if (XRE_IsContentProcess()) {
100 // Ask the parent whether or not activating the guard is okay. The parent
101 // won't bother if it detected a crash.
102 dom::ContentChild* cc = dom::ContentChild::GetSingleton();
103 cc->SendBeginDriverCrashGuard(uint32_t(mType), &mCrashDetected);
104 if (mCrashDetected) {
105 LogFeatureDisabled();
106 return;
109 ActivateGuard();
110 return;
113 // Always check whether or not the lock file exists. For example, we could
114 // have crashed creating a D3D9 device in the parent process, and on restart
115 // are now requesting one in the child process. We catch everything here.
116 if (RecoverFromCrash()) {
117 mCrashDetected = true;
118 return;
121 // If the environment has changed, we always activate the guard. In the
122 // parent process this performs main-thread disk I/O. Child process guards
123 // only incur an IPC cost, so if we're proxying for a child process, we
124 // play it safe and activate the guard as long as we don't expect it to
125 // crash.
126 if (CheckOrRefreshEnvironment() ||
127 (mMode == Mode::Proxy && GetStatus() != DriverInitStatus::Crashed)) {
128 ActivateGuard();
129 return;
132 // If we got here and our status is "crashed", then the environment has not
133 // updated and we do not want to attempt to use the driver again.
134 if (GetStatus() == DriverInitStatus::Crashed) {
135 mCrashDetected = true;
136 LogFeatureDisabled();
140 DriverCrashGuard::~DriverCrashGuard() {
141 if (!mGuardActivated) {
142 return;
145 if (XRE_IsParentProcess()) {
146 if (mGuardFile) {
147 mGuardFile->Remove(false);
150 // If during our initialization, no other process encountered a crash, we
151 // proceed to mark the status as okay.
152 if (GetStatus() != DriverInitStatus::Crashed) {
153 SetStatus(DriverInitStatus::Okay);
155 } else {
156 dom::ContentChild::GetSingleton()->SendEndDriverCrashGuard(uint32_t(mType));
159 CrashReporter::UnrecordAnnotation(
160 CrashReporter::Annotation::GraphicsStartupTest);
163 bool DriverCrashGuard::Crashed() {
164 InitializeIfNeeded();
166 // Note, we read mCrashDetected instead of GetStatus(), since in child
167 // processes we're not guaranteed that the prefs have been synced in
168 // time.
169 return mCrashDetected;
172 nsCOMPtr<nsIFile> DriverCrashGuard::GetGuardFile() {
173 MOZ_ASSERT(XRE_IsParentProcess());
175 nsCString filename;
176 filename.Assign(sCrashGuardNames[size_t(mType)]);
177 filename.AppendLiteral(".guard");
179 nsCOMPtr<nsIFile> file;
180 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
181 getter_AddRefs(file));
182 if (!file) {
183 return nullptr;
185 if (!NS_SUCCEEDED(file->AppendNative(filename))) {
186 return nullptr;
188 return file;
191 void DriverCrashGuard::ActivateGuard() {
192 mGuardActivated = true;
194 // Anotate crash reports only if we're a real guard. Otherwise, we could
195 // attribute a random parent process crash to a graphics problem in a child
196 // process.
197 if (mMode != Mode::Proxy) {
198 CrashReporter::RecordAnnotationBool(
199 CrashReporter::Annotation::GraphicsStartupTest, true);
202 // If we're in the content process, the rest of the guarding is handled
203 // in the parent.
204 if (XRE_IsContentProcess()) {
205 return;
208 SetStatus(DriverInitStatus::Attempting);
210 if (mMode != Mode::Proxy) {
211 // In parent process guards, we use two tombstones to detect crashes: a
212 // preferences and a zero-byte file on the filesystem.
213 FlushPreferences();
215 // Create a temporary tombstone/lockfile.
216 FILE* fp = nullptr;
217 mGuardFile = GetGuardFile();
218 if (!mGuardFile || !NS_SUCCEEDED(mGuardFile->OpenANSIFileDesc("w", &fp))) {
219 return;
221 fclose(fp);
225 void DriverCrashGuard::NotifyCrashed() {
226 SetStatus(DriverInitStatus::Crashed);
227 FlushPreferences();
228 LogCrashRecovery();
231 bool DriverCrashGuard::RecoverFromCrash() {
232 MOZ_ASSERT(XRE_IsParentProcess());
234 nsCOMPtr<nsIFile> file = GetGuardFile();
235 bool exists;
236 if ((file && NS_SUCCEEDED(file->Exists(&exists)) && exists) ||
237 (GetStatus() == DriverInitStatus::Attempting)) {
238 // If we get here, we've just recovered from a crash. Disable acceleration
239 // until the environment changes.
240 if (file) {
241 file->Remove(false);
243 NotifyCrashed();
244 return true;
246 return false;
249 // Return true if the caller should proceed to guard for crashes. False if
250 // the environment has not changed. We persist the "changed" status across
251 // calls, so that after an environment changes, all guards for the new
252 // session are activated rather than just the first.
253 bool DriverCrashGuard::CheckOrRefreshEnvironment() {
254 // Our result can be cached statically since we don't check live prefs.
255 // We need to cache once per crash guard type.
256 // The first call to CheckOrRefrechEnvironment will always return true should
257 // the configuration had changed, following calls will return false.
258 static bool sBaseInfoChanged[NUM_CRASH_GUARD_TYPES];
259 static bool sBaseInfoChecked[NUM_CRASH_GUARD_TYPES];
261 const uint32_t type = uint32_t(mType);
262 if (!sBaseInfoChecked[type]) {
263 // None of the prefs we care about, so we cache the result statically.
264 sBaseInfoChecked[type] = true;
265 sBaseInfoChanged[type] = UpdateBaseEnvironment();
268 // Always update the full environment, even if the base info didn't change.
269 bool result = UpdateEnvironment() || sBaseInfoChanged[type] ||
270 GetStatus() == DriverInitStatus::Unknown;
271 sBaseInfoChanged[type] = false;
272 return result;
275 bool DriverCrashGuard::UpdateBaseEnvironment() {
276 bool changed = false;
277 if (mGfxInfo) {
278 nsString value;
280 // Driver properties.
281 mGfxInfo->GetAdapterDriverVersion(value);
282 changed |= CheckAndUpdatePref("driverVersion", value);
283 mGfxInfo->GetAdapterDeviceID(value);
284 changed |= CheckAndUpdatePref("deviceID", value);
287 // Firefox properties.
288 changed |= CheckAndUpdatePref(
289 "appVersion", NS_LITERAL_STRING_FROM_CSTRING(MOZ_APP_VERSION));
291 return changed;
294 bool DriverCrashGuard::FeatureEnabled(int aFeature, bool aDefault) {
295 if (!mGfxInfo) {
296 return aDefault;
298 int32_t status;
299 nsCString discardFailureId;
300 if (!NS_SUCCEEDED(
301 mGfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) {
302 return false;
304 return status == nsIGfxInfo::FEATURE_STATUS_OK;
307 bool DriverCrashGuard::CheckAndUpdateBoolPref(const char* aPrefName,
308 bool aCurrentValue) {
309 std::string pref = GetFullPrefName(aPrefName);
311 bool oldValue;
312 if (NS_SUCCEEDED(Preferences::GetBool(pref.c_str(), &oldValue)) &&
313 oldValue == aCurrentValue) {
314 return false;
316 Preferences::SetBool(pref.c_str(), aCurrentValue);
317 return true;
320 bool DriverCrashGuard::CheckAndUpdatePref(const char* aPrefName,
321 const nsAString& aCurrentValue) {
322 std::string pref = GetFullPrefName(aPrefName);
324 nsAutoString oldValue;
325 Preferences::GetString(pref.c_str(), oldValue);
326 if (oldValue == aCurrentValue) {
327 return false;
329 Preferences::SetString(pref.c_str(), aCurrentValue);
330 return true;
333 std::string DriverCrashGuard::GetFullPrefName(const char* aPref) {
334 return std::string("gfx.crash-guard.") +
335 std::string(sCrashGuardNames[uint32_t(mType)]) + std::string(".") +
336 std::string(aPref);
339 DriverInitStatus DriverCrashGuard::GetStatus() const {
340 return (DriverInitStatus)Preferences::GetInt(mStatusPref.get(), 0);
343 void DriverCrashGuard::SetStatus(DriverInitStatus aStatus) {
344 MOZ_ASSERT(XRE_IsParentProcess());
346 Preferences::SetInt(mStatusPref.get(), int32_t(aStatus));
349 void DriverCrashGuard::FlushPreferences() {
350 MOZ_ASSERT(XRE_IsParentProcess());
352 if (nsIPrefService* prefService = Preferences::GetService()) {
353 static_cast<Preferences*>(prefService)->SavePrefFileBlocking();
357 void DriverCrashGuard::ForEachActiveCrashGuard(
358 const CrashGuardCallback& aCallback) {
359 for (size_t i = 0; i < NUM_CRASH_GUARD_TYPES; i++) {
360 CrashGuardType type = static_cast<CrashGuardType>(i);
362 if (!AreCrashGuardsEnabled(type)) {
363 // Even if guards look active (via prefs), they can be ignored if globally
364 // disabled.
365 continue;
368 nsCString prefName;
369 BuildCrashGuardPrefName(type, prefName);
371 auto status =
372 static_cast<DriverInitStatus>(Preferences::GetInt(prefName.get(), 0));
373 if (status != DriverInitStatus::Crashed) {
374 continue;
377 aCallback(sCrashGuardNames[i], prefName.get());
381 D3D11LayersCrashGuard::D3D11LayersCrashGuard(dom::ContentParent* aContentParent)
382 : DriverCrashGuard(CrashGuardType::D3D11Layers, aContentParent) {}
384 void D3D11LayersCrashGuard::Initialize() {
385 if (!XRE_IsParentProcess()) {
386 // We assume the parent process already performed crash detection for
387 // graphics devices.
388 return;
391 DriverCrashGuard::Initialize();
393 // If no telemetry states have been recorded, this will set the state to okay.
394 // Otherwise, it will have no effect.
395 RecordTelemetry(TelemetryState::Okay);
398 bool D3D11LayersCrashGuard::UpdateEnvironment() {
399 // Our result can be cached statically since we don't check live prefs.
400 static bool checked = false;
402 if (checked) {
403 // We no longer need to bypass the crash guard.
404 return false;
407 checked = true;
409 bool changed = false;
410 // Feature status.
411 #if defined(XP_WIN)
412 bool d2dEnabled = StaticPrefs::gfx_direct2d_force_enabled_AtStartup() ||
413 (!StaticPrefs::gfx_direct2d_disabled_AtStartup() &&
414 FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT2D));
415 changed |= CheckAndUpdateBoolPref("feature-d2d", d2dEnabled);
417 bool d3d11Enabled = gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING);
418 changed |= CheckAndUpdateBoolPref("feature-d3d11", d3d11Enabled);
419 if (changed) {
420 RecordTelemetry(TelemetryState::EnvironmentChanged);
422 #endif
424 return changed;
427 void D3D11LayersCrashGuard::LogCrashRecovery() {
428 RecordTelemetry(TelemetryState::RecoveredFromCrash);
429 gfxCriticalNote << "D3D11 layers just crashed; D3D11 will be disabled.";
432 void D3D11LayersCrashGuard::LogFeatureDisabled() {
433 RecordTelemetry(TelemetryState::FeatureDisabled);
434 gfxCriticalNote << "D3D11 layers disabled due to a prior crash.";
437 void D3D11LayersCrashGuard::RecordTelemetry(TelemetryState aState) {
438 // D3D11LayersCrashGuard is a no-op in the child process.
439 if (!XRE_IsParentProcess()) {
440 return;
443 // Since we instantiate this class more than once, make sure we only record
444 // the first state (since that is really all we care about).
445 static bool sTelemetryStateRecorded = false;
446 if (sTelemetryStateRecorded) {
447 return;
450 Telemetry::Accumulate(Telemetry::GRAPHICS_DRIVER_STARTUP_TEST,
451 int32_t(aState));
452 sTelemetryStateRecorded = true;
455 GLContextCrashGuard::GLContextCrashGuard(dom::ContentParent* aContentParent)
456 : DriverCrashGuard(CrashGuardType::GLContext, aContentParent) {}
458 void GLContextCrashGuard::Initialize() {
459 if (XRE_IsContentProcess()) {
460 // Disable the GL crash guard in content processes, since we're not going
461 // to lose the entire browser and we don't want to hinder WebGL
462 // availability.
463 return;
466 #if defined(MOZ_WIDGET_ANDROID)
467 // Disable the WebGL crash guard on Android - it doesn't use E10S, and
468 // its drivers will essentially never change, so the crash guard could
469 // permanently disable WebGL.
470 return;
471 #endif
473 DriverCrashGuard::Initialize();
476 bool GLContextCrashGuard::UpdateEnvironment() {
477 static bool checked = false;
479 if (checked) {
480 // We no longer need to bypass the crash guard.
481 return false;
484 checked = true;
486 bool changed = false;
488 #if defined(XP_WIN)
489 changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-d3d11",
490 StaticPrefs::webgl_angle_force_d3d11());
491 changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-try-d3d11",
492 StaticPrefs::webgl_angle_try_d3d11());
493 changed |= CheckAndUpdateBoolPref("gfx.driver-init.webgl-angle-force-warp",
494 StaticPrefs::webgl_angle_force_warp());
495 changed |= CheckAndUpdateBoolPref(
496 "gfx.driver-init.webgl-angle",
497 FeatureEnabled(nsIGfxInfo::FEATURE_WEBGL_ANGLE, false));
498 changed |= CheckAndUpdateBoolPref(
499 "gfx.driver-init.direct3d11-angle",
500 FeatureEnabled(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, false));
501 #endif
503 return changed;
506 void GLContextCrashGuard::LogCrashRecovery() {
507 gfxCriticalNote << "GLContext just crashed.";
510 void GLContextCrashGuard::LogFeatureDisabled() {
511 gfxCriticalNote << "GLContext remains enabled despite a previous crash.";
514 WMFVPXVideoCrashGuard::WMFVPXVideoCrashGuard(dom::ContentParent* aContentParent)
515 : DriverCrashGuard(CrashGuardType::WMFVPXVideo, aContentParent) {}
517 void WMFVPXVideoCrashGuard::LogCrashRecovery() {
518 gfxCriticalNote
519 << "WMF VPX decoder just crashed; hardware video will be disabled.";
522 void WMFVPXVideoCrashGuard::LogFeatureDisabled() {
523 gfxCriticalNote
524 << "WMF VPX video decoding is disabled due to a previous crash.";
527 } // namespace gfx
528 } // namespace mozilla