Bug 1272808 - Expose WebGL context creation errors to telemetry. r=jgilbert draft
authorBenoit Girard <b56girard@gmail.com>
Fri, 13 May 2016 18:57:19 -0400
changeset 367028 5df88530353c4c757db4c5fb965a12d3f9aca4c9
parent 366801 1f1a8b96d5167153d1f750439ba6a1063155a4bc
child 520897 12c0553f885416cc9f57e08ab5083ca50a57633c
push id18120
push userb56girard@gmail.com
push dateFri, 13 May 2016 22:57:32 +0000
reviewersjgilbert
bugs1272808
milestone49.0a1
Bug 1272808 - Expose WebGL context creation errors to telemetry. r=jgilbert MozReview-Commit-ID: Ks4i3sG9SH9
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
gfx/config/gfxConfig.cpp
gfx/config/gfxConfig.h
gfx/config/gfxFeature.cpp
gfx/config/gfxFeature.h
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPrefs.h
toolkit/components/telemetry/TelemetryEnvironment.jsm
widget/GfxInfoBase.cpp
widget/GfxInfoBase.h
widget/windows/GfxInfo.cpp
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -3,31 +3,34 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLContext.h"
 
 #include <queue>
 
 #include "AccessCheck.h"
+#include "gfxConfig.h"
 #include "gfxContext.h"
 #include "gfxCrashReporterUtils.h"
+#include "gfxFeature.h"
 #include "gfxPattern.h"
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "GLBlitHelper.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 #include "GLReadTexImageHelper.h"
 #include "GLScreenBuffer.h"
 #include "ImageContainer.h"
 #include "ImageEncoder.h"
 #include "Layers.h"
 #include "LayerUserData.h"
 #include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/WebGLContextEvent.h"
 #include "mozilla/EnumeratedArrayCycleCollection.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessPriorityManager.h"
 #include "mozilla/Services.h"
@@ -683,17 +686,17 @@ WebGLContext::CreateAndInitGLWith(FnCrea
         gl = nullptr;
         return false;
     }
 
     return true;
 }
 
 bool
-WebGLContext::CreateAndInitGL(bool forceEnabled, nsACString* const out_failReason)
+WebGLContext::CreateAndInitGL(bool forceEnabled, nsACString* const out_failReason, gfx::Feature& featureOut)
 {
     const bool useEGL = PR_GetEnv("MOZ_WEBGL_PREFER_EGL");
 
     bool useANGLE = false;
 #ifdef XP_WIN
     const bool disableANGLE = (gfxPrefs::WebGLDisableANGLE() ||
                                PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL"));
     useANGLE = !disableANGLE;
@@ -701,22 +704,27 @@ WebGLContext::CreateAndInitGL(bool force
 
     gl::CreateContextFlags flags = gl::CreateContextFlags::NONE;
     if (forceEnabled) flags |= gl::CreateContextFlags::FORCE_ENABLE_HARDWARE;
     if (!IsWebGL2())  flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
     if (IsWebGL2())   flags |= gl::CreateContextFlags::PREFER_ES3;
 
     const gl::SurfaceCaps baseCaps = BaseCaps(mOptions, this);
 
-    if (useEGL)
+    if (useEGL) {
+        featureOut = Feature::WEBGL_OPENGL;
         return CreateAndInitGLWith(CreateGLWithEGL, baseCaps, flags, out_failReason);
+    }
 
-    if (useANGLE)
+    if (useANGLE) {
+        featureOut = Feature::WEBGL_ANGLE;
         return CreateAndInitGLWith(CreateGLWithANGLE, baseCaps, flags, out_failReason);
+    }
 
+    featureOut = Feature::WEBGL_OPENGL;
     return CreateAndInitGLWith(CreateGLWithDefault, baseCaps, flags, out_failReason);
 }
 
 // Fallback for resizes:
 bool
 WebGLContext::ResizeBackbuffer(uint32_t requestedWidth,
                                uint32_t requestedHeight)
 {
@@ -779,16 +787,33 @@ WebGLContext::ThrowEvent_WebGLContextCre
     bool didPreventDefault;
     target->DispatchEvent(event, &didPreventDefault);
 
     //////
 
     GenerateWarning("Failed to create WebGL context: %s", text.BeginReading());
 }
 
+static void
+SetRuntimeGraphicsConfigResult(gfx::Feature feature, FeatureStatus aStatus, const nsCString& failReason)
+{
+  mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+  if (cc) {
+    cc->SendRuntimeGraphicsConfigResult(static_cast<int32_t>(feature),
+                                        static_cast<int32_t>(aStatus),
+                                        failReason);
+  } else {
+    if (aStatus == gfx::FeatureStatus::Available) {
+      gfxConfig::SetUsed(feature);
+    } else {
+      gfxConfig::SetFailed(feature, aStatus, failReason.get());
+    }
+  }
+}
+
 NS_IMETHODIMP
 WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight)
 {
     if (signedWidth < 0 || signedHeight < 0) {
         GenerateWarning("Canvas size is too large (seems like a negative value wrapped)");
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
@@ -900,20 +925,23 @@ WebGLContext::SetDimensions(int32_t sign
     }
 
     // Alright, now let's start trying.
     bool forceEnabled = gfxPrefs::WebGLForceEnabled();
     ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
 
     MOZ_ASSERT(!gl);
     nsCString failReason;
-    if (!CreateAndInitGL(forceEnabled, &failReason)) {
+    gfx::Feature feature;
+    if (!CreateAndInitGL(forceEnabled, &failReason, feature)) {
         const nsPrintfCString text("WebGL creation failed: %s",
                                    failReason.BeginReading());
         ThrowEvent_WebGLContextCreationError(text);
+        SetRuntimeGraphicsConfigResult(feature, FeatureStatus::Blacklisted,
+                                       failReason);
         return NS_ERROR_FAILURE;
     }
     MOZ_ASSERT(gl);
 
     MOZ_ASSERT_IF(mOptions.alpha, gl->Caps().alpha);
 
     if (!ResizeBackbuffer(width, height)) {
         const nsLiteralCString text("Initializing WebGL backbuffer failed.");
@@ -993,16 +1021,19 @@ WebGLContext::SetDimensions(int32_t sign
 
     MOZ_ASSERT(gl->Caps().antialias == mOptions.antialias);
     MOZ_ASSERT(gl->Caps().preserve == mOptions.preserveDrawingBuffer);
 
     AssertCachedBindings();
     AssertCachedState();
 
     reporter.SetSuccessful();
+
+    SetRuntimeGraphicsConfigResult(feature, FeatureStatus::Available, failReason);
+
     return NS_OK;
 }
 
 void
 WebGLContext::ClearBackbufferIfNeeded()
 {
     if (!mBackbufferNeedsClear)
         return;
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -5,26 +5,27 @@
 
 #ifndef WEBGLCONTEXT_H_
 #define WEBGLCONTEXT_H_
 
 #include <stdarg.h>
 
 #include "GLContextTypes.h"
 #include "GLDefs.h"
+#include "gfxFeature.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/CheckedInt.h"
-#include "mozilla/dom/HTMLCanvasElement.h"
-#include "mozilla/dom/TypedArray.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/ErrorResult.h"
-#include "mozilla/gfx/2D.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/gfx/2D.h"
 #include "nsCycleCollectionNoteChild.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "nsLayoutUtils.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 #include "SurfaceTypes.h"
 #include "ScopedGLHelpers.h"
 #include "TexUnpackBlob.h"
@@ -1198,17 +1199,17 @@ protected:
     // -------------------------------------------------------------------------
     // WebGL 2 specifics (implemented in WebGL2Context.cpp)
 public:
     virtual bool IsWebGL2() const = 0;
 
 protected:
     bool InitWebGL2(nsACString* const out_failReason);
 
-    bool CreateAndInitGL(bool forceEnabled, nsACString* const out_failReason);
+    bool CreateAndInitGL(bool forceEnabled, nsACString* const out_failReason, gfx::Feature& featureOut);
     bool ResizeBackbuffer(uint32_t width, uint32_t height);
 
     typedef already_AddRefed<gl::GLContext> FnCreateGL_T(const gl::SurfaceCaps& caps,
                                                          gl::CreateContextFlags flags,
                                                          WebGLContext* webgl,
                                                          nsACString* const out_failReason);
 
     bool CreateAndInitGLWith(FnCreateGL_T fnCreateGL, const gl::SurfaceCaps& baseCaps,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -172,16 +172,17 @@
 #include "SourceSurfaceRawData.h"
 #include "TabParent.h"
 #include "URIUtils.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsIDocShell.h"
 #include "nsDocShell.h"
 #include "nsOpenURIInFrameParams.h"
 #include "mozilla/net/NeckoMessageUtils.h"
+#include "gfxConfig.h"
 #include "gfxPrefs.h"
 #include "prio.h"
 #include "private/pprio.h"
 #include "ContentProcessManager.h"
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/psm/PSMContentListener.h"
 #include "nsPluginHost.h"
 #include "nsPluginTags.h"
@@ -4937,16 +4938,38 @@ ContentParent::RecvRecordingDeviceEvents
                          aRecordingStatus.get());
   } else {
     NS_WARNING("Could not get the Observer service for ContentParent::RecvRecordingDeviceEvents.");
   }
   return true;
 }
 
 bool
+ContentParent::RecvRuntimeGraphicsConfigResult(const int32_t& aFeature,
+                                               const int32_t& aStatus,
+                                               const nsCString& aFailureId)
+{
+  gfx::FeatureStatus featureStatus = static_cast<gfx::FeatureStatus>(aStatus);
+  if (featureStatus == gfx::FeatureStatus::Available) {
+    gfx::gfxConfig::SetUsed(static_cast<gfx::Feature>(aFeature));
+  } else {
+    gfx::gfxConfig::SetFailed(static_cast<gfx::Feature>(aFeature),
+                              featureStatus,
+                              aFailureId.get());
+  }
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "gfx-features-changed", nullptr);
+  }
+
+  return true;
+}
+
+bool
 ContentParent::RecvGetGraphicsFeatureStatus(const int32_t& aFeature,
                                             int32_t* aStatus,
                                             nsCString* aFailureId,
                                             bool* aSuccess)
 {
   nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
   if (!gfxInfo) {
     *aSuccess = false;
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1014,16 +1014,20 @@ private:
   virtual void ProcessingError(Result aCode, const char* aMsgName) override;
 
   virtual bool RecvAllocateLayerTreeId(const ContentParentId& aCpId,
                                        const TabId& aTabId,
                                        uint64_t* aId) override;
 
   virtual bool RecvDeallocateLayerTreeId(const uint64_t& aId) override;
 
+  virtual bool RecvRuntimeGraphicsConfigResult(const int32_t& aFeature,
+                                               const int32_t& aStatus,
+                                               const nsCString& aFailureId) override;
+
   virtual bool RecvGetGraphicsFeatureStatus(const int32_t& aFeature,
                                             int32_t* aStatus,
                                             nsCString* aFailureId,
                                             bool* aSuccess) override;
 
   virtual bool RecvGraphicsError(const nsCString& aError) override;
 
   virtual bool
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -979,19 +979,25 @@ parent:
      * @param isAudio recording start with microphone
      * @param isVideo recording start with camera
      */
     async RecordingDeviceEvents(nsString recordingStatus,
                                 nsString pageURL,
                                 bool isAudio,
                                 bool isVideo);
 
-    sync GetGraphicsFeatureStatus(int32_t aFeature) returns (int32_t aStatus, nsCString aFailureCode,
+    sync GetGraphicsFeatureStatus(int32_t aFeature) returns (int32_t aStatus, nsCString aFailureId,
                                                              bool aSuccess);
 
+    /**
+     * Some graphics feature, like WebGL, need to run before we can report
+     * the exact feature state. When we do so we notify the parent process.
+     */
+    async RuntimeGraphicsConfigResult(int32_t aFeature, int32_t aStatus,
+                                      nsCString aFailureId);
     // Graphics errors
     async GraphicsError(nsCString aError);
 
     // Driver crash guards. aGuardType must be a member of CrashGuardType.
     sync BeginDriverCrashGuard(uint32_t aGuardType) returns (bool crashDetected);
     sync EndDriverCrashGuard(uint32_t aGuardType);
 
     async AddIdleObserver(uint64_t observerId, uint32_t idleTimeInS);
--- a/gfx/config/gfxConfig.cpp
+++ b/gfx/config/gfxConfig.cpp
@@ -214,10 +214,17 @@ void
 gfxConfig::ForEachFallbackImpl(const FallbackIterCallback& aCallback)
 {
   for (size_t i = 0; i < mNumFallbackLogEntries; i++) {
     const FallbackLogEntry& entry = mFallbackLog[i];
     aCallback(sFallbackNames[size_t(entry.mFallback)], entry.mMessage);
   }
 }
 
+void
+gfxConfig::SetUsed(Feature aFeature)
+{
+  FeatureState& state = sConfig.GetState(aFeature);
+  state.SetUsed();
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/config/gfxConfig.h
+++ b/gfx/config/gfxConfig.h
@@ -158,16 +158,20 @@ public:
                                  FeatureState& aFeature)> FeatureIterCallback;
   static void ForEachFeature(const FeatureIterCallback& aCallback);
 
   // Run a callback for each enabled fallback.
   typedef mozilla::function<void(const char* aName, const char* aMsg)> 
     FallbackIterCallback;
   static void ForEachFallback(const FallbackIterCallback& aCallback);
 
+  // This feature has been successfully initialized without running into
+  // any static blocking or runtime errors.
+  static void SetUsed(Feature aFeature);
+
 private:
   void ForEachFallbackImpl(const FallbackIterCallback& aCallback);
 
 private:
   FeatureState& GetState(Feature aFeature) {
     MOZ_ASSERT(size_t(aFeature) < kNumFeatures);
     return mFeatures[size_t(aFeature)];
   }
--- a/gfx/config/gfxFeature.cpp
+++ b/gfx/config/gfxFeature.cpp
@@ -207,18 +207,21 @@ FeatureState::SetRuntime(FeatureStatus a
   AssertInitialized();
 
   mRuntime.Set(aStatus, aMessage);
 }
 
 const char*
 FeatureState::GetRuntimeMessage() const
 {
-  MOZ_ASSERT(IsFeatureStatusFailure(mRuntime.mStatus));
-  return mRuntime.mMessage;
+  if (mRuntime.IsInitialized()) {
+    return mRuntime.mMessage;
+  } else {
+    return nullptr;
+  }
 }
 
 void
 FeatureState::ForEachStatusChange(const StatusIterCallback& aCallback) const
 {
   AssertInitialized();
 
   aCallback("default", mDefault.mStatus, mDefault.MessageOrNull());
--- a/gfx/config/gfxFeature.h
+++ b/gfx/config/gfxFeature.h
@@ -16,16 +16,18 @@ namespace gfx {
 
 #define GFX_FEATURE_MAP(_)                                                        \
   /* Name,                        Type,         Description */                    \
   _(HW_COMPOSITING,               Feature,      "Compositing")                    \
   _(D3D11_COMPOSITING,            Feature,      "Direct3D11 Compositing")         \
   _(D3D9_COMPOSITING,             Feature,      "Direct3D9 Compositing")          \
   _(DIRECT2D,                     Feature,      "Direct2D")                       \
   _(D3D11_ANGLE,                  Feature,      "Direct3D11 ANGLE")               \
+  _(WEBGL_ANGLE,                  Feature,      "WebGL ANGLE")                    \
+  _(WEBGL_OPENGL,                 Feature,      "WebGL OpenGL")                   \
   /* Add new entries above this comment */
 
 enum class Feature : uint32_t {
 #define MAKE_ENUM(name, type, desc) name,
   GFX_FEATURE_MAP(MAKE_ENUM)
 #undef MAKE_ENUM
   NumValues
 };
@@ -60,36 +62,53 @@ class FeatureState
 
   // aType is "base", "user", "env", or "runtime".
   // aMessage may be null.
   typedef mozilla::function<void(const char* aType,
                                  FeatureStatus aStatus,
                                  const char* aMessage)> StatusIterCallback;
   void ForEachStatusChange(const StatusIterCallback& aCallback) const;
 
+  // Get the last message set for this feature.
+  const char* GetRuntimeMessage() const;
+
+  // This feature has been successfully initialized without running into
+  // any static blocking or runtime errors.
+  void SetUsed() {
+    mUsed = true;
+  }
+
+  bool HasBeenUsed() const {
+    return mUsed;
+  }
  private:
   void SetUser(FeatureStatus aStatus, const char* aMessage);
   void SetEnvironment(FeatureStatus aStatus, const char* aMessage);
   void SetRuntime(FeatureStatus aStatus, const char* aMessage);
   bool IsForcedOnByUser() const;
   bool DisabledByDefault() const;
-  const char* GetRuntimeMessage() const;
   bool IsInitialized() const {
     return mDefault.IsInitialized();
   }
 
   void AssertInitialized() const {
     MOZ_ASSERT(IsInitialized());
   }
 
  private:
   struct Instance {
     char mMessage[64];
     FeatureStatus mStatus;
 
+    Instance()
+      : mStatus(FeatureStatus::Unused)
+    {
+      mMessage[0] = '\0';
+    }
+
     void Set(FeatureStatus aStatus, const char* aMessage = nullptr);
     bool IsInitialized() const {
       return mStatus != FeatureStatus::Unused;
     }
     const char* MessageOrNull() const {
       return mMessage[0] != '\0' ? mMessage : nullptr;
     }
     const char* Message() const {
@@ -106,14 +125,16 @@ class FeatureState
   // The environment state factors in any additional decisions made, such as
   // availability or blacklisting.
   //
   // The runtime state factors in any problems discovered at runtime.
   Instance mDefault;
   Instance mUser;
   Instance mEnvironment;
   Instance mRuntime;
+  // Track if this feature was ever successfully used
+  bool mUsed;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif // mozilla_gfx_config_gfxFeature_h
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -753,28 +753,36 @@ gfxPlatform::Init()
     if (XRE_IsParentProcess()) {
       if (gfxPlatform::ForceSoftwareVsync()) {
         gPlatform->mVsyncSource = (gPlatform)->gfxPlatform::CreateHardwareVsyncSource();
       } else {
         gPlatform->mVsyncSource = gPlatform->CreateHardwareVsyncSource();
       }
     }
 
+    FeatureState& webglOpenGLFeature = gfxConfig::GetFeature(Feature::WEBGL_OPENGL);
+    webglOpenGLFeature.SetDefaultFromPref(gfxPrefs::GetWebGLDisabledPrefName(), false,
+                                          gfxPrefs::GetWebGLDisabledPrefDefault());
+
+    FeatureState& webglAngleFeature = gfxConfig::GetFeature(Feature::WEBGL_ANGLE);
+    webglAngleFeature.SetDefaultFromPref(gfxPrefs::GetWebGLDisableANGLEPrefName(),
+                                         false, gfxPrefs::GetWebGLDisableANGLEPrefDefault());
+
 #ifdef USE_SKIA
     uint32_t skiaCacheSize = GetSkiaGlyphCacheSize();
     if (skiaCacheSize != kDefaultGlyphCacheSize) {
       SkGraphics::SetFontCacheLimit(skiaCacheSize);
     }
 #endif
 
     ScrollMetadata::sNullMetadata = new ScrollMetadata();
     ClearOnShutdown(&ScrollMetadata::sNullMetadata);
 
     if (obs) {
-      obs->NotifyObservers(nullptr, "gfx-features-ready", nullptr);
+      obs->NotifyObservers(nullptr, "gfx-features-changed", nullptr);
     }
 }
 
 static bool sLayersIPCIsUp = false;
 
 void
 gfxPlatform::Shutdown()
 {
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -67,16 +67,17 @@ static const char* Get##Name##PrefName()
 static Type Get##Name##PrefDefault() { return Default; }                      \
 PrefTemplate<UpdatePolicy::Update, Type, Get##Name##PrefDefault, Get##Name##PrefName> mPref##Name
 
 class PreferenceAccessImpl;
 class gfxPrefs;
 class gfxPrefs final
 {
   friend class gfxWindowsPlatform;
+  friend class gfxPlatform;
 
 private:
   /// See Logging.h.  This lets Moz2D access preference values it owns.
   PreferenceAccessImpl* mMoz2DPrefAccess;
 
 private:
   // Enums for the update policy.
   enum class UpdatePolicy {
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -179,17 +179,17 @@ const PREF_PARTNER_ID = "mozilla.partner
 const PREF_UPDATE_ENABLED = "app.update.enabled";
 const PREF_UPDATE_AUTODOWNLOAD = "app.update.auto";
 const PREF_SEARCH_COHORT = "browser.search.cohort";
 const PREF_E10S_COHORT = "e10s.rollout.cohort";
 
 const COMPOSITOR_CREATED_TOPIC = "compositor:created";
 const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = "distribution-customization-complete";
 const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed";
-const GFX_FEATURES_READY_TOPIC = "gfx-features-ready";
+const GFX_FEATURES_CHANGED_TOPIC = "gfx-features-changed";
 const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified";
 const SEARCH_SERVICE_TOPIC = "browser-search-service";
 
 /**
  * Enforces the parameter to a boolean value.
  * @param aValue The input value.
  * @return {Boolean|Object} If aValue is a boolean or a number, returns its truthfulness
  *         value. Otherwise, return null.
@@ -900,27 +900,27 @@ EnvironmentCache.prototype = {
       }
     }
   },
 
   _addObservers: function () {
     // Watch the search engine change and service topics.
     Services.obs.addObserver(this, COMPOSITOR_CREATED_TOPIC, false);
     Services.obs.addObserver(this, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, false);
-    Services.obs.addObserver(this, GFX_FEATURES_READY_TOPIC, false);
+    Services.obs.addObserver(this, GFX_FEATURES_CHANGED_TOPIC, false);
     Services.obs.addObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC, false);
     Services.obs.addObserver(this, SEARCH_SERVICE_TOPIC, false);
   },
 
   _removeObservers: function () {
     Services.obs.removeObserver(this, COMPOSITOR_CREATED_TOPIC);
     try {
       Services.obs.removeObserver(this, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC);
     } catch(ex) {}
-    Services.obs.removeObserver(this, GFX_FEATURES_READY_TOPIC);
+    Services.obs.removeObserver(this, GFX_FEATURES_CHANGED_TOPIC);
     Services.obs.removeObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC);
     Services.obs.removeObserver(this, SEARCH_SERVICE_TOPIC);
   },
 
   observe: function (aSubject, aTopic, aData) {
     this._log.trace("observe - aTopic: " + aTopic + ", aData: " + aData);
     switch (aTopic) {
       case SEARCH_ENGINE_MODIFIED_TOPIC:
@@ -932,17 +932,17 @@ EnvironmentCache.prototype = {
         break;
       case SEARCH_SERVICE_TOPIC:
         if (aData != "init-complete") {
           return;
         }
         // Now that the search engine init is complete, record the default search choice.
         this._updateSearchEngine();
         break;
-      case GFX_FEATURES_READY_TOPIC:
+      case GFX_FEATURES_CHANGED_TOPIC:
       case COMPOSITOR_CREATED_TOPIC:
         // Full graphics information is not available until we have created at
         // least one off-main-thread-composited window. Thus we wait for the
         // first compositor to be created and then query nsIGfxInfo again.
         this._updateGraphicsFeatures();
         break;
       case DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC:
         // Distribution customizations are applied after final-ui-startup. query
--- a/widget/GfxInfoBase.cpp
+++ b/widget/GfxInfoBase.cpp
@@ -165,16 +165,46 @@ GetPrefNameForFeature(int32_t aFeature)
     default:
       MOZ_ASSERT_UNREACHABLE("Unexpected nsIGfxInfo feature?!");
       break;
   }
 
   return name;
 }
 
+static Maybe<Feature>
+GetConfigFeatureForFeature(int32_t aFeature)
+{
+  Maybe<Feature> feature;
+  switch(aFeature) {
+    case nsIGfxInfo::FEATURE_DIRECT2D:
+      feature = Some(Feature::DIRECT2D);
+      break;
+    case nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS:
+      feature = Some(Feature::D3D9_COMPOSITING);
+      break;
+    case nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS:
+      feature = Some(Feature::D3D11_COMPOSITING);
+      break;
+    case nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE:
+      feature = Some(Feature::D3D11_ANGLE);
+      break;
+    case nsIGfxInfo::FEATURE_WEBGL_OPENGL:
+      feature = Some(Feature::WEBGL_OPENGL);
+      break;
+    case nsIGfxInfo::FEATURE_WEBGL_ANGLE:
+      feature = Some(Feature::WEBGL_ANGLE);
+      break;
+    default:
+      break;
+  }
+
+  return feature;
+}
+
 // Returns the value of the pref for the relevant feature in aValue.
 // If the pref doesn't exist, aValue is not touched, and returns false.
 static bool
 GetPrefValueForFeature(int32_t aFeature, int32_t& aValue, nsACString& aFailureId)
 {
   const char *prefname = GetPrefNameForFeature(aFeature);
   if (!prefname)
     return false;
@@ -1297,45 +1327,63 @@ GfxInfoBase::DescribeFeatures(JSContext*
   InitFeatureObject(aCx, aObj, "webgl", nsIGfxInfo::FEATURE_WEBGL_OPENGL, Nothing(), &obj);
 }
 
 bool
 GfxInfoBase::InitFeatureObject(JSContext* aCx,
                                JS::Handle<JSObject*> aContainer,
                                const char* aName,
                                int32_t aFeature,
-                               Maybe<mozilla::gfx::FeatureStatus> aFeatureStatus,
+                               Maybe<mozilla::gfx::FeatureState> aFeatureState,
                                JS::MutableHandle<JSObject*> aOutObj)
 {
   JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
   if (!obj) {
     return false;
   }
 
-  nsCString failureId = NS_LITERAL_CSTRING("OK");
+
+  nsCString failureId;
   int32_t unused;
   if (!NS_SUCCEEDED(GetFeatureStatus(aFeature, failureId, &unused))) {
     return false;
   }
 
+  if (!aFeatureState) {
+    Maybe<Feature> feature = GetConfigFeatureForFeature(aFeature);
+    if (feature) {
+      aFeatureState = Some(gfxConfig::GetFeature(feature.value()));
+    }
+  }
+
   // Set "status".
-  if (aFeatureStatus) {
-    const char* status = FeatureStatusToString(aFeatureStatus.value());
+  if (aFeatureState) {
+    const char* used = aFeatureState.value().HasBeenUsed() ? "true" : "false";
+    JS::Rooted<JSString*> usedStr(aCx, JS_NewStringCopyZ(aCx, used));
+    JS::Rooted<JS::Value> usedVal(aCx, JS::StringValue(usedStr));
+    JS_SetProperty(aCx, obj, "used", usedVal);
 
+    const char* status = FeatureStatusToString(aFeatureState.value().GetValue());
     JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, status));
     JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));
     JS_SetProperty(aCx, obj, "status", val);
+
+    if (failureId.IsEmpty()) {
+      failureId = nsCString(aFeatureState.value().GetRuntimeMessage());
+    }
   }
 
   if (!failureId.IsEmpty()) {
-    JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, failureId.get()));
-    JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));
-    JS_SetProperty(aCx, obj, "failureId", val);
+    failureId = NS_LITERAL_CSTRING("OK");
   }
 
+  JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, failureId.get()));
+  JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));
+  JS_SetProperty(aCx, obj, "failureId", val);
+
   // Add the feature object to the container.
   {
     JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*obj));
     JS_SetProperty(aCx, aContainer, aName, val);
   }
 
   aOutObj.set(obj);
   return true;
--- a/widget/GfxInfoBase.h
+++ b/widget/GfxInfoBase.h
@@ -105,17 +105,17 @@ protected:
   virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() = 0;
 
   virtual void DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> obj);
   bool InitFeatureObject(
     JSContext* aCx,
     JS::Handle<JSObject*> aContainer,
     const char* aName,
     int32_t aFeature,
-    Maybe<mozilla::gfx::FeatureStatus> aKnownStatus,
+    Maybe<mozilla::gfx::FeatureState> aKnownState,
     JS::MutableHandle<JSObject*> aOutObj);
 
 private:
   virtual int32_t FindBlocklistedDeviceInList(const nsTArray<GfxDriverInfo>& aDriverInfo,
                                               nsAString& aSuggestedVersion,
                                               int32_t aFeature,
                                               nsACString &aFailureId,
                                               OperatingSystem os);
--- a/widget/windows/GfxInfo.cpp
+++ b/widget/windows/GfxInfo.cpp
@@ -1262,17 +1262,17 @@ GfxInfo::DescribeFeatures(JSContext* aCx
 {
   // Add the platform neutral features
   GfxInfoBase::DescribeFeatures(aCx, aObj);
 
   JS::Rooted<JSObject*> obj(aCx);
 
   gfxWindowsPlatform* platform = gfxWindowsPlatform::GetPlatform();
 
-  gfx::FeatureStatus d3d11 = gfxConfig::GetValue(Feature::D3D11_COMPOSITING);
+  gfx::FeatureState& d3d11 = gfxConfig::GetState(Feature::D3D11_COMPOSITING);
   if (!InitFeatureObject(aCx, aObj, "d3d11", FEATURE_DIRECT3D_11_ANGLE,
                          Some(d3d11), &obj)) {
     return;
   }
   if (d3d11 == gfx::FeatureStatus::Available) {
     JS::Rooted<JS::Value> val(aCx, JS::Int32Value(platform->GetD3D11Version()));
     JS_SetProperty(aCx, obj, "version", val);
 
@@ -1290,17 +1290,17 @@ GfxInfo::DescribeFeatures(JSContext* aCx
         blacklisted = (status != nsIGfxInfo::FEATURE_STATUS_OK);
       }
     }
 
     val = JS::BooleanValue(blacklisted);
     JS_SetProperty(aCx, obj, "blacklisted", val);
   }
 
-  gfx::FeatureStatus d2d = gfxConfig::GetValue(Feature::DIRECT2D);
+  gfx::FeatureState& d2d = gfxConfig::GetState(Feature::DIRECT2D);
   if (!InitFeatureObject(aCx, aObj, "d2d", nsIGfxInfo::FEATURE_DIRECT2D,
                          Some(d2d), &obj)) {
     return;
   }
   {
     const char* version = "1.1";
     JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, version));
     JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));