gecko-dev/widget/GfxInfoBase.cpp
Andrew Osmond 759c48f75e Bug 1614377 - Switch the WebRender rollout to use allowlist rules. r=jrmuizel
Now that GfxInfo supports allowlisting, we can port our existing
configuration in gfxPlatform to using allowlist rules. This will
greatly increase maintainability and certainty that the expected
devices are getting WebRender.

Differential Revision: https://phabricator.services.mozilla.com/D62325

--HG--
extra : moz-landing-system : lando
2020-02-18 13:56:10 +00:00

1792 lines
60 KiB
C++

/* vim: se cin sw=2 ts=2 et : */
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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 "mozilla/ArrayUtils.h"
#include "GfxInfoBase.h"
#include "GfxDriverInfo.h"
#include "js/Array.h" // JS::GetArrayLength, JS::NewArrayObject
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "nsString.h"
#include "nsUnicharUtils.h"
#include "nsVersionComparator.h"
#include "mozilla/Services.h"
#include "mozilla/Observer.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIScreenManager.h"
#include "nsTArray.h"
#include "nsXULAppAPI.h"
#include "nsIXULAppInfo.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/layers/PaintThread.h"
#include "gfxPlatform.h"
#include "gfxConfig.h"
#include "DriverCrashGuard.h"
using namespace mozilla::widget;
using namespace mozilla;
using mozilla::MutexAutoLock;
nsTArray<GfxDriverInfo>* GfxInfoBase::sDriverInfo;
nsTArray<dom::GfxInfoFeatureStatus>* GfxInfoBase::sFeatureStatus;
bool GfxInfoBase::sDriverInfoObserverInitialized;
bool GfxInfoBase::sShutdownOccurred;
// Observes for shutdown so that the child GfxDriverInfo list is freed.
class ShutdownObserver : public nsIObserver {
virtual ~ShutdownObserver() {}
public:
ShutdownObserver() {}
NS_DECL_ISUPPORTS
NS_IMETHOD Observe(nsISupports* subject, const char* aTopic,
const char16_t* aData) override {
MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
delete GfxInfoBase::sDriverInfo;
GfxInfoBase::sDriverInfo = nullptr;
delete GfxInfoBase::sFeatureStatus;
GfxInfoBase::sFeatureStatus = nullptr;
for (auto& deviceFamily : GfxDriverInfo::sDeviceFamilies) {
delete deviceFamily;
deviceFamily = nullptr;
}
for (auto& desktop : GfxDriverInfo::sDesktopEnvironment) {
delete desktop;
desktop = nullptr;
}
for (auto& windowProtocol : GfxDriverInfo::sWindowProtocol) {
delete windowProtocol;
windowProtocol = nullptr;
}
for (auto& deviceVendor : GfxDriverInfo::sDeviceVendors) {
delete deviceVendor;
deviceVendor = nullptr;
}
for (auto& driverVendor : GfxDriverInfo::sDriverVendors) {
delete driverVendor;
driverVendor = nullptr;
}
GfxInfoBase::sShutdownOccurred = true;
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver)
static void InitGfxDriverInfoShutdownObserver() {
if (GfxInfoBase::sDriverInfoObserverInitialized) return;
GfxInfoBase::sDriverInfoObserverInitialized = true;
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
if (!observerService) {
NS_WARNING("Could not get observer service!");
return;
}
ShutdownObserver* obs = new ShutdownObserver();
observerService->AddObserver(obs, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
}
using namespace mozilla::widget;
using namespace mozilla::gfx;
using namespace mozilla;
NS_IMPL_ISUPPORTS(GfxInfoBase, nsIGfxInfo, nsIObserver,
nsISupportsWeakReference)
#define BLACKLIST_PREF_BRANCH "gfx.blacklist."
#define SUGGESTED_VERSION_PREF BLACKLIST_PREF_BRANCH "suggested-driver-version"
#define BLACKLIST_ENTRY_TAG_NAME "gfxBlacklistEntry"
static const char* GetPrefNameForFeature(int32_t aFeature) {
const char* name = nullptr;
switch (aFeature) {
case nsIGfxInfo::FEATURE_DIRECT2D:
name = BLACKLIST_PREF_BRANCH "direct2d";
break;
case nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS:
name = BLACKLIST_PREF_BRANCH "layers.direct3d9";
break;
case nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS:
name = BLACKLIST_PREF_BRANCH "layers.direct3d10";
break;
case nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS:
name = BLACKLIST_PREF_BRANCH "layers.direct3d10-1";
break;
case nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS:
name = BLACKLIST_PREF_BRANCH "layers.direct3d11";
break;
case nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE:
name = BLACKLIST_PREF_BRANCH "direct3d11angle";
break;
case nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING:
name = BLACKLIST_PREF_BRANCH "hardwarevideodecoding";
break;
case nsIGfxInfo::FEATURE_OPENGL_LAYERS:
name = BLACKLIST_PREF_BRANCH "layers.opengl";
break;
case nsIGfxInfo::FEATURE_WEBGL_OPENGL:
name = BLACKLIST_PREF_BRANCH "webgl.opengl";
break;
case nsIGfxInfo::FEATURE_WEBGL_ANGLE:
name = BLACKLIST_PREF_BRANCH "webgl.angle";
break;
case nsIGfxInfo::FEATURE_WEBGL_MSAA:
name = BLACKLIST_PREF_BRANCH "webgl.msaa";
break;
case nsIGfxInfo::FEATURE_STAGEFRIGHT:
name = BLACKLIST_PREF_BRANCH "stagefright";
break;
case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_H264:
name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration.h264";
break;
case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE:
name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration.encode";
break;
case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE:
name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration.decode";
break;
case nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION:
name = BLACKLIST_PREF_BRANCH "canvas2d.acceleration";
break;
case nsIGfxInfo::FEATURE_DX_INTEROP2:
name = BLACKLIST_PREF_BRANCH "dx.interop2";
break;
case nsIGfxInfo::FEATURE_GPU_PROCESS:
name = BLACKLIST_PREF_BRANCH "gpu.process";
break;
case nsIGfxInfo::FEATURE_WEBGL2:
name = BLACKLIST_PREF_BRANCH "webgl2";
break;
case nsIGfxInfo::FEATURE_ADVANCED_LAYERS:
name = BLACKLIST_PREF_BRANCH "layers.advanced";
break;
case nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX:
name = BLACKLIST_PREF_BRANCH "d3d11.keyed.mutex";
break;
case nsIGfxInfo::FEATURE_WEBRENDER:
name = BLACKLIST_PREF_BRANCH "webrender";
break;
case nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR:
name = BLACKLIST_PREF_BRANCH "webrender.compositor";
break;
case nsIGfxInfo::FEATURE_DX_NV12:
name = BLACKLIST_PREF_BRANCH "dx.nv12";
break;
case nsIGfxInfo::FEATURE_DX_P010:
name = BLACKLIST_PREF_BRANCH "dx.p010";
break;
case nsIGfxInfo::FEATURE_DX_P016:
name = BLACKLIST_PREF_BRANCH "dx.p016";
break;
case nsIGfxInfo::FEATURE_VP8_HW_DECODE:
case nsIGfxInfo::FEATURE_VP9_HW_DECODE:
// We don't provide prefs for these features as these are
// not handling downloadable blocklist.
break;
case nsIGfxInfo::FEATURE_GL_SWIZZLE:
name = BLACKLIST_PREF_BRANCH "gl.swizzle";
break;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected nsIGfxInfo feature?!");
break;
}
return name;
}
// 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;
aValue = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
if (!NS_SUCCEEDED(Preferences::GetInt(prefname, &aValue))) {
return false;
}
nsCString failureprefname(prefname);
failureprefname += ".failureid";
nsAutoCString failureValue;
nsresult rv = Preferences::GetCString(failureprefname.get(), failureValue);
if (NS_SUCCEEDED(rv)) {
aFailureId = failureValue.get();
} else {
aFailureId = "FEATURE_FAILURE_BLACKLIST_PREF";
}
return true;
}
static void SetPrefValueForFeature(int32_t aFeature, int32_t aValue,
const nsACString& aFailureId) {
const char* prefname = GetPrefNameForFeature(aFeature);
if (!prefname) return;
if (XRE_IsParentProcess()) {
delete GfxInfoBase::sFeatureStatus;
GfxInfoBase::sFeatureStatus = nullptr;
}
Preferences::SetInt(prefname, aValue);
if (!aFailureId.IsEmpty()) {
nsCString failureprefname(prefname);
failureprefname += ".failureid";
Preferences::SetCString(failureprefname.get(), aFailureId);
}
}
static void RemovePrefForFeature(int32_t aFeature) {
const char* prefname = GetPrefNameForFeature(aFeature);
if (!prefname) return;
if (XRE_IsParentProcess()) {
delete GfxInfoBase::sFeatureStatus;
GfxInfoBase::sFeatureStatus = nullptr;
}
Preferences::ClearUser(prefname);
}
static bool GetPrefValueForDriverVersion(nsCString& aVersion) {
return NS_SUCCEEDED(
Preferences::GetCString(SUGGESTED_VERSION_PREF, aVersion));
}
static void SetPrefValueForDriverVersion(const nsAString& aVersion) {
Preferences::SetString(SUGGESTED_VERSION_PREF, aVersion);
}
static void RemovePrefForDriverVersion() {
Preferences::ClearUser(SUGGESTED_VERSION_PREF);
}
static OperatingSystem BlacklistOSToOperatingSystem(const nsAString& os) {
if (os.EqualsLiteral("WINNT 6.1"))
return OperatingSystem::Windows7;
else if (os.EqualsLiteral("WINNT 6.2"))
return OperatingSystem::Windows8;
else if (os.EqualsLiteral("WINNT 6.3"))
return OperatingSystem::Windows8_1;
else if (os.EqualsLiteral("WINNT 10.0"))
return OperatingSystem::Windows10;
else if (os.EqualsLiteral("Linux"))
return OperatingSystem::Linux;
else if (os.EqualsLiteral("Darwin 9"))
return OperatingSystem::OSX10_5;
else if (os.EqualsLiteral("Darwin 10"))
return OperatingSystem::OSX10_6;
else if (os.EqualsLiteral("Darwin 11"))
return OperatingSystem::OSX10_7;
else if (os.EqualsLiteral("Darwin 12"))
return OperatingSystem::OSX10_8;
else if (os.EqualsLiteral("Darwin 13"))
return OperatingSystem::OSX10_9;
else if (os.EqualsLiteral("Darwin 14"))
return OperatingSystem::OSX10_10;
else if (os.EqualsLiteral("Darwin 15"))
return OperatingSystem::OSX10_11;
else if (os.EqualsLiteral("Darwin 16"))
return OperatingSystem::OSX10_12;
else if (os.EqualsLiteral("Darwin 17"))
return OperatingSystem::OSX10_13;
else if (os.EqualsLiteral("Darwin 18"))
return OperatingSystem::OSX10_14;
else if (os.EqualsLiteral("Darwin 19"))
return OperatingSystem::OSX10_15;
else if (os.EqualsLiteral("Android"))
return OperatingSystem::Android;
// For historical reasons, "All" in blocklist means "All Windows"
else if (os.EqualsLiteral("All"))
return OperatingSystem::Windows;
return OperatingSystem::Unknown;
}
static GfxDeviceFamily* BlacklistDevicesToDeviceFamily(
nsTArray<nsCString>& devices) {
if (devices.Length() == 0) return nullptr;
// For each device, get its device ID, and return a freshly-allocated
// GfxDeviceFamily with the contents of that array.
GfxDeviceFamily* deviceIds = new GfxDeviceFamily;
for (uint32_t i = 0; i < devices.Length(); ++i) {
// We make sure we don't add any "empty" device entries to the array, so
// we don't need to check if devices[i] is empty.
deviceIds->Append(NS_ConvertUTF8toUTF16(devices[i]));
}
return deviceIds;
}
static int32_t BlacklistFeatureToGfxFeature(const nsAString& aFeature) {
MOZ_ASSERT(!aFeature.IsEmpty());
if (aFeature.EqualsLiteral("DIRECT2D"))
return nsIGfxInfo::FEATURE_DIRECT2D;
else if (aFeature.EqualsLiteral("DIRECT3D_9_LAYERS"))
return nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS;
else if (aFeature.EqualsLiteral("DIRECT3D_10_LAYERS"))
return nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS;
else if (aFeature.EqualsLiteral("DIRECT3D_10_1_LAYERS"))
return nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS;
else if (aFeature.EqualsLiteral("DIRECT3D_11_LAYERS"))
return nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS;
else if (aFeature.EqualsLiteral("DIRECT3D_11_ANGLE"))
return nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE;
else if (aFeature.EqualsLiteral("HARDWARE_VIDEO_DECODING"))
return nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING;
else if (aFeature.EqualsLiteral("OPENGL_LAYERS"))
return nsIGfxInfo::FEATURE_OPENGL_LAYERS;
else if (aFeature.EqualsLiteral("WEBGL_OPENGL"))
return nsIGfxInfo::FEATURE_WEBGL_OPENGL;
else if (aFeature.EqualsLiteral("WEBGL_ANGLE"))
return nsIGfxInfo::FEATURE_WEBGL_ANGLE;
else if (aFeature.EqualsLiteral("WEBGL_MSAA"))
return nsIGfxInfo::FEATURE_WEBGL_MSAA;
else if (aFeature.EqualsLiteral("STAGEFRIGHT"))
return nsIGfxInfo::FEATURE_STAGEFRIGHT;
else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION_ENCODE"))
return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE;
else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION_DECODE"))
return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE;
else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION_H264"))
return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_H264;
else if (aFeature.EqualsLiteral("CANVAS2D_ACCELERATION"))
return nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION;
else if (aFeature.EqualsLiteral("DX_INTEROP2"))
return nsIGfxInfo::FEATURE_DX_INTEROP2;
else if (aFeature.EqualsLiteral("GPU_PROCESS"))
return nsIGfxInfo::FEATURE_GPU_PROCESS;
else if (aFeature.EqualsLiteral("WEBGL2"))
return nsIGfxInfo::FEATURE_WEBGL2;
else if (aFeature.EqualsLiteral("ADVANCED_LAYERS"))
return nsIGfxInfo::FEATURE_ADVANCED_LAYERS;
else if (aFeature.EqualsLiteral("D3D11_KEYED_MUTEX"))
return nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX;
else if (aFeature.EqualsLiteral("WEBRENDER"))
return nsIGfxInfo::FEATURE_WEBRENDER;
else if (aFeature.EqualsLiteral("WEBRENDER_COMPOSITOR"))
return nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR;
else if (aFeature.EqualsLiteral("DX_NV12"))
return nsIGfxInfo::FEATURE_DX_NV12;
// We do not support FEATURE_VP8_HW_DECODE and FEATURE_VP9_HW_DECODE
// in downloadable blocklist.
else if (aFeature.EqualsLiteral("GL_SWIZZLE"))
return nsIGfxInfo::FEATURE_GL_SWIZZLE;
// If we don't recognize the feature, it may be new, and something
// this version doesn't understand. So, nothing to do. This is
// different from feature not being specified at all, in which case
// this method should not get called and we should continue with the
// "all features" blocklisting.
return -1;
}
static int32_t BlacklistFeatureStatusToGfxFeatureStatus(
const nsAString& aStatus) {
if (aStatus.EqualsLiteral("STATUS_OK"))
return nsIGfxInfo::FEATURE_STATUS_OK;
else if (aStatus.EqualsLiteral("BLOCKED_DRIVER_VERSION"))
return nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
else if (aStatus.EqualsLiteral("BLOCKED_DEVICE"))
return nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
else if (aStatus.EqualsLiteral("DISCOURAGED"))
return nsIGfxInfo::FEATURE_DISCOURAGED;
else if (aStatus.EqualsLiteral("BLOCKED_OS_VERSION"))
return nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
else if (aStatus.EqualsLiteral("DENIED"))
return nsIGfxInfo::FEATURE_DENIED;
else if (aStatus.EqualsLiteral("ALLOW_QUALIFIED"))
return nsIGfxInfo::FEATURE_ALLOW_QUALIFIED;
else if (aStatus.EqualsLiteral("ALLOW_ALWAYS"))
return nsIGfxInfo::FEATURE_ALLOW_ALWAYS;
// Do not allow it to set STATUS_UNKNOWN. Also, we are not
// expecting the "mismatch" status showing up here.
return nsIGfxInfo::FEATURE_STATUS_OK;
}
static VersionComparisonOp BlacklistComparatorToComparisonOp(
const nsAString& op) {
if (op.EqualsLiteral("LESS_THAN"))
return DRIVER_LESS_THAN;
else if (op.EqualsLiteral("BUILD_ID_LESS_THAN"))
return DRIVER_BUILD_ID_LESS_THAN;
else if (op.EqualsLiteral("LESS_THAN_OR_EQUAL"))
return DRIVER_LESS_THAN_OR_EQUAL;
else if (op.EqualsLiteral("BUILD_ID_LESS_THAN_OR_EQUAL"))
return DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL;
else if (op.EqualsLiteral("GREATER_THAN"))
return DRIVER_GREATER_THAN;
else if (op.EqualsLiteral("GREATER_THAN_OR_EQUAL"))
return DRIVER_GREATER_THAN_OR_EQUAL;
else if (op.EqualsLiteral("EQUAL"))
return DRIVER_EQUAL;
else if (op.EqualsLiteral("NOT_EQUAL"))
return DRIVER_NOT_EQUAL;
else if (op.EqualsLiteral("BETWEEN_EXCLUSIVE"))
return DRIVER_BETWEEN_EXCLUSIVE;
else if (op.EqualsLiteral("BETWEEN_INCLUSIVE"))
return DRIVER_BETWEEN_INCLUSIVE;
else if (op.EqualsLiteral("BETWEEN_INCLUSIVE_START"))
return DRIVER_BETWEEN_INCLUSIVE_START;
return DRIVER_COMPARISON_IGNORED;
}
/*
Deserialize Blacklist entries from string.
e.g:
os:WINNT 6.0\tvendor:0x8086\tdevices:0x2582,0x2782\tfeature:DIRECT3D_10_LAYERS\tfeatureStatus:BLOCKED_DRIVER_VERSION\tdriverVersion:8.52.322.2202\tdriverVersionComparator:LESS_THAN_OR_EQUAL
*/
static bool BlacklistEntryToDriverInfo(nsCString& aBlacklistEntry,
GfxDriverInfo& aDriverInfo) {
// If we get an application version to be zero, something is not working
// and we are not going to bother checking the blocklist versions.
// See TestGfxWidgets.cpp for how version comparison works.
// <versionRange minVersion="42.0a1" maxVersion="45.0"></versionRange>
static mozilla::Version zeroV("0");
static mozilla::Version appV(GfxInfoBase::GetApplicationVersion().get());
if (appV <= zeroV) {
gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false))
<< "Invalid application version "
<< GfxInfoBase::GetApplicationVersion().get();
}
nsTArray<nsCString> keyValues;
ParseString(aBlacklistEntry, '\t', keyValues);
aDriverInfo.mRuleId =
NS_LITERAL_CSTRING("FEATURE_FAILURE_DL_BLACKLIST_NO_ID");
for (uint32_t i = 0; i < keyValues.Length(); ++i) {
nsCString keyValue = keyValues[i];
nsTArray<nsCString> splitted;
ParseString(keyValue, ':', splitted);
if (splitted.Length() != 2) {
// If we don't recognize the input data, we do not want to proceed.
gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false))
<< "Unrecognized data " << keyValue.get();
return false;
}
nsCString key = splitted[0];
nsCString value = splitted[1];
NS_ConvertUTF8toUTF16 dataValue(value);
if (value.Length() == 0) {
// Safety check for empty values.
gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false))
<< "Empty value for " << key.get();
return false;
}
if (key.EqualsLiteral("blockID")) {
nsCString blockIdStr =
NS_LITERAL_CSTRING("FEATURE_FAILURE_DL_BLACKLIST_") + value;
aDriverInfo.mRuleId = blockIdStr.get();
} else if (key.EqualsLiteral("os")) {
aDriverInfo.mOperatingSystem = BlacklistOSToOperatingSystem(dataValue);
} else if (key.EqualsLiteral("osversion")) {
aDriverInfo.mOperatingSystemVersion = strtoul(value.get(), nullptr, 10);
} else if (key.EqualsLiteral("desktopEnvironment")) {
aDriverInfo.mDesktopEnvironment = dataValue;
} else if (key.EqualsLiteral("windowProtocol")) {
aDriverInfo.mWindowProtocol = dataValue;
} else if (key.EqualsLiteral("vendor")) {
aDriverInfo.mAdapterVendor = dataValue;
} else if (key.EqualsLiteral("driverVendor")) {
aDriverInfo.mDriverVendor = dataValue;
} else if (key.EqualsLiteral("feature")) {
aDriverInfo.mFeature = BlacklistFeatureToGfxFeature(dataValue);
if (aDriverInfo.mFeature < 0) {
// If we don't recognize the feature, we do not want to proceed.
gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false))
<< "Unrecognized feature " << value.get();
return false;
}
} else if (key.EqualsLiteral("featureStatus")) {
aDriverInfo.mFeatureStatus =
BlacklistFeatureStatusToGfxFeatureStatus(dataValue);
} else if (key.EqualsLiteral("driverVersion")) {
uint64_t version;
if (ParseDriverVersion(dataValue, &version))
aDriverInfo.mDriverVersion = version;
} else if (key.EqualsLiteral("driverVersionMax")) {
uint64_t version;
if (ParseDriverVersion(dataValue, &version))
aDriverInfo.mDriverVersionMax = version;
} else if (key.EqualsLiteral("driverVersionComparator")) {
aDriverInfo.mComparisonOp = BlacklistComparatorToComparisonOp(dataValue);
} else if (key.EqualsLiteral("model")) {
aDriverInfo.mModel = dataValue;
} else if (key.EqualsLiteral("product")) {
aDriverInfo.mProduct = dataValue;
} else if (key.EqualsLiteral("manufacturer")) {
aDriverInfo.mManufacturer = dataValue;
} else if (key.EqualsLiteral("hardware")) {
aDriverInfo.mHardware = dataValue;
} else if (key.EqualsLiteral("versionRange")) {
nsTArray<nsCString> versionRange;
ParseString(value, ',', versionRange);
if (versionRange.Length() != 2) {
gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false))
<< "Unrecognized versionRange " << value.get();
return false;
}
nsCString minValue = versionRange[0];
nsCString maxValue = versionRange[1];
mozilla::Version minV(minValue.get());
mozilla::Version maxV(maxValue.get());
if (minV > zeroV && !(appV >= minV)) {
// The version of the application is less than the minimal version
// this blocklist entry applies to, so we can just ignore it by
// returning false and letting the caller deal with it.
return false;
}
if (maxV > zeroV && !(appV <= maxV)) {
// The version of the application is more than the maximal version
// this blocklist entry applies to, so we can just ignore it by
// returning false and letting the caller deal with it.
return false;
}
} else if (key.EqualsLiteral("devices")) {
nsTArray<nsCString> devices;
ParseString(value, ',', devices);
GfxDeviceFamily* deviceIds = BlacklistDevicesToDeviceFamily(devices);
if (deviceIds) {
// Get GfxDriverInfo to adopt the devices array we created.
aDriverInfo.mDeleteDevices = true;
aDriverInfo.mDevices = deviceIds;
}
}
// We explicitly ignore unknown elements.
}
return true;
}
static void BlacklistEntriesToDriverInfo(nsTArray<nsCString>& aBlacklistEntries,
nsTArray<GfxDriverInfo>& aDriverInfo) {
aDriverInfo.Clear();
aDriverInfo.SetLength(aBlacklistEntries.Length());
for (uint32_t i = 0; i < aBlacklistEntries.Length(); ++i) {
nsCString blacklistEntry = aBlacklistEntries[i];
GfxDriverInfo di;
if (BlacklistEntryToDriverInfo(blacklistEntry, di)) {
aDriverInfo[i] = di;
// Prevent di falling out of scope from destroying the devices.
di.mDeleteDevices = false;
}
}
}
NS_IMETHODIMP
GfxInfoBase::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (strcmp(aTopic, "blocklist-data-gfxItems") == 0) {
nsTArray<GfxDriverInfo> driverInfo;
nsTArray<nsCString> blacklistEntries;
nsCString utf8Data = NS_ConvertUTF16toUTF8(aData);
if (utf8Data.Length() > 0) {
ParseString(utf8Data, '\n', blacklistEntries);
}
BlacklistEntriesToDriverInfo(blacklistEntries, driverInfo);
EvaluateDownloadedBlacklist(driverInfo);
}
return NS_OK;
}
GfxInfoBase::GfxInfoBase() : mScreenPixels(INT64_MAX), mMutex("GfxInfoBase") {}
GfxInfoBase::~GfxInfoBase() {}
nsresult GfxInfoBase::Init() {
InitGfxDriverInfoShutdownObserver();
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->AddObserver(this, "blocklist-data-gfxItems", true);
}
return NS_OK;
}
void GfxInfoBase::GetData() {
if (mScreenPixels != INT64_MAX) {
// Already initialized.
return;
}
nsCOMPtr<nsIScreenManager> manager =
do_GetService("@mozilla.org/gfx/screenmanager;1");
if (!manager) {
MOZ_ASSERT_UNREACHABLE("failed to get nsIScreenManager");
return;
}
manager->GetTotalScreenPixels(&mScreenPixels);
}
NS_IMETHODIMP
GfxInfoBase::GetFeatureStatus(int32_t aFeature, nsACString& aFailureId,
int32_t* aStatus) {
// Ignore the gfx.blocklist.all pref on release and beta.
#if defined(RELEASE_OR_BETA)
int32_t blocklistAll = 0;
#else
int32_t blocklistAll = StaticPrefs::gfx_blocklist_all_AtStartup();
#endif
if (blocklistAll > 0) {
gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false))
<< "Forcing blocklisting all features";
*aStatus = FEATURE_BLOCKED_DEVICE;
aFailureId = "FEATURE_FAILURE_BLOCK_ALL";
return NS_OK;
} else if (blocklistAll < 0) {
gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false))
<< "Ignoring any feature blocklisting.";
*aStatus = FEATURE_STATUS_OK;
return NS_OK;
}
if (GetPrefValueForFeature(aFeature, *aStatus, aFailureId)) {
return NS_OK;
}
if (XRE_IsContentProcess()) {
// Use the cached data received from the parent process.
MOZ_ASSERT(sFeatureStatus);
bool success = false;
for (const auto& fs : *sFeatureStatus) {
if (fs.feature() == aFeature) {
aFailureId = fs.failureId();
*aStatus = fs.status();
success = true;
break;
}
}
return success ? NS_OK : NS_ERROR_FAILURE;
}
nsString version;
nsTArray<GfxDriverInfo> driverInfo;
nsresult rv =
GetFeatureStatusImpl(aFeature, aStatus, version, driverInfo, aFailureId);
return rv;
}
void GfxInfoBase::GetAllFeatures(dom::XPCOMInitData& xpcomInit) {
MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
if (!sFeatureStatus) {
sFeatureStatus = new nsTArray<dom::GfxInfoFeatureStatus>();
for (int32_t i = 1; i <= nsIGfxInfo::FEATURE_MAX_VALUE; ++i) {
int32_t status = 0;
nsAutoCString failureId;
GetFeatureStatus(i, failureId, &status);
dom::GfxInfoFeatureStatus gfxFeatureStatus;
gfxFeatureStatus.feature() = i;
gfxFeatureStatus.status() = status;
gfxFeatureStatus.failureId() = failureId;
sFeatureStatus->AppendElement(gfxFeatureStatus);
}
}
for (const auto& status : *sFeatureStatus) {
dom::GfxInfoFeatureStatus copy = status;
xpcomInit.gfxFeatureStatus().AppendElement(copy);
}
}
inline bool MatchingAllowStatus(int32_t aStatus) {
switch (aStatus) {
case nsIGfxInfo::FEATURE_ALLOW_ALWAYS:
case nsIGfxInfo::FEATURE_ALLOW_QUALIFIED:
return true;
default:
return false;
}
}
// Matching OS go somewhat beyond the simple equality check because of the
// "All Windows" and "All OS X" variations.
//
// aBlockedOS is describing the system(s) we are trying to block.
// aSystemOS is describing the system we are running on.
//
// aSystemOS should not be "Windows" or "OSX" - it should be set to
// a particular version instead.
// However, it is valid for aBlockedOS to be one of those generic values,
// as we could be blocking all of the versions.
inline bool MatchingOperatingSystems(OperatingSystem aBlockedOS,
OperatingSystem aSystemOS,
uint32_t aSystemOSBuild) {
MOZ_ASSERT(aSystemOS != OperatingSystem::Windows &&
aSystemOS != OperatingSystem::OSX);
// If the block entry OS is unknown, it doesn't match
if (aBlockedOS == OperatingSystem::Unknown) {
return false;
}
#if defined(XP_WIN)
if (aBlockedOS == OperatingSystem::Windows) {
// We do want even "unknown" aSystemOS to fall under "all windows"
return true;
}
constexpr uint32_t kMinWin10BuildNumber = 18362;
if (aSystemOSBuild && aBlockedOS == OperatingSystem::RecentWindows10 &&
aSystemOS == OperatingSystem::Windows10) {
// For allowlist purposes, we sometimes want to restrict to only recent
// versions of Windows 10. This is a bit of a kludge but easier than adding
// complicated blocklist infrastructure for build ID comparisons like driver
// versions.
return aSystemOSBuild >= kMinWin10BuildNumber;
}
#endif
#if defined(XP_MACOSX)
if (aBlockedOS == OperatingSystem::OSX) {
// We do want even "unknown" aSystemOS to fall under "all OS X"
return true;
}
#endif
return aSystemOS == aBlockedOS;
}
inline bool MatchingBattery(BatteryStatus aBatteryStatus, bool aHasBattery) {
switch (aBatteryStatus) {
case BatteryStatus::All:
return true;
case BatteryStatus::None:
return !aHasBattery;
case BatteryStatus::Present:
return aHasBattery;
}
MOZ_ASSERT_UNREACHABLE("bad battery status");
return false;
}
inline bool MatchingScreenSize(ScreenSizeStatus aScreenStatus,
int64_t aScreenPixels) {
constexpr int64_t kMaxSmallPixels = 2304000; // 1920x1200
constexpr int64_t kMaxMediumPixels = 4953600; // 3440x1440
switch (aScreenStatus) {
case ScreenSizeStatus::All:
return true;
case ScreenSizeStatus::Small:
return aScreenPixels <= kMaxSmallPixels;
case ScreenSizeStatus::SmallAndMedium:
return aScreenPixels <= kMaxMediumPixels;
case ScreenSizeStatus::Medium:
return aScreenPixels > kMaxSmallPixels &&
aScreenPixels <= kMaxMediumPixels;
case ScreenSizeStatus::MediumAndLarge:
return aScreenPixels > kMaxSmallPixels;
case ScreenSizeStatus::Large:
return aScreenPixels > kMaxMediumPixels;
}
MOZ_ASSERT_UNREACHABLE("bad screen status");
return false;
}
int32_t GfxInfoBase::FindBlocklistedDeviceInList(
const nsTArray<GfxDriverInfo>& info, nsAString& aSuggestedVersion,
int32_t aFeature, nsACString& aFailureId, OperatingSystem os,
bool aForAllowing) {
int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
// Some properties are not available on all platforms.
nsAutoString desktopEnvironment;
nsresult rv = GetDesktopEnvironment(desktopEnvironment);
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
return 0;
}
nsAutoString windowProtocol;
rv = GetWindowProtocol(windowProtocol);
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
return 0;
}
bool hasBattery = false;
rv = GetHasBattery(&hasBattery);
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
return 0;
}
// OS build number is only used for the allowlist.
uint32_t osBuild = aForAllowing ? OperatingSystemBuild() : 0;
// Get the adapters once then reuse below
nsAutoString adapterVendorID[2];
nsAutoString adapterDeviceID[2];
nsAutoString adapterDriverVendor[2];
nsAutoString adapterDriverVersionString[2];
bool adapterInfoFailed[2];
adapterInfoFailed[0] =
(NS_FAILED(GetAdapterVendorID(adapterVendorID[0])) ||
NS_FAILED(GetAdapterDeviceID(adapterDeviceID[0])) ||
NS_FAILED(GetAdapterDriverVendor(adapterDriverVendor[0])) ||
NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString[0])));
adapterInfoFailed[1] =
(NS_FAILED(GetAdapterVendorID2(adapterVendorID[1])) ||
NS_FAILED(GetAdapterDeviceID2(adapterDeviceID[1])) ||
NS_FAILED(GetAdapterDriverVendor2(adapterDriverVendor[1])) ||
NS_FAILED(GetAdapterDriverVersion2(adapterDriverVersionString[1])));
// No point in going on if we don't have adapter info
if (adapterInfoFailed[0] && adapterInfoFailed[1]) {
return 0;
}
#if defined(XP_WIN) || defined(ANDROID) || defined(MOZ_X11)
uint64_t driverVersion[2] = {0, 0};
if (!adapterInfoFailed[0]) {
ParseDriverVersion(adapterDriverVersionString[0], &driverVersion[0]);
}
if (!adapterInfoFailed[1]) {
ParseDriverVersion(adapterDriverVersionString[1], &driverVersion[1]);
}
#endif
uint32_t i = 0;
for (; i < info.Length(); i++) {
// If the status is FEATURE_ALLOW_*, then it is for the allowlist, not
// blocklisting. Only consider entries for our search mode.
if (MatchingAllowStatus(info[i].mFeatureStatus) != aForAllowing) {
continue;
}
// If we don't have the info for this GPU, no need to check further.
// It is unclear that we would ever have a mixture of 1st and 2nd
// GPU, but leaving the code in for that possibility for now.
// (Actually, currently mGpu2 will never be true, so this can
// be optimized out.)
uint32_t infoIndex = info[i].mGpu2 ? 1 : 0;
if (adapterInfoFailed[infoIndex]) {
continue;
}
// Do the operating system check first, no point in getting the driver
// info if we won't need to use it.
if (!MatchingOperatingSystems(info[i].mOperatingSystem, os, osBuild)) {
continue;
}
if (info[i].mOperatingSystemVersion &&
info[i].mOperatingSystemVersion != OperatingSystemVersion()) {
continue;
}
if (!MatchingBattery(info[i].mBattery, hasBattery)) {
continue;
}
if (!MatchingScreenSize(info[i].mScreen, mScreenPixels)) {
continue;
}
if (!DoesDesktopEnvironmentMatch(info[i].mDesktopEnvironment,
desktopEnvironment)) {
continue;
}
if (!DoesWindowProtocolMatch(info[i].mWindowProtocol, windowProtocol)) {
continue;
}
if (!DoesVendorMatch(info[i].mAdapterVendor, adapterVendorID[infoIndex])) {
continue;
}
if (!DoesDriverVendorMatch(info[i].mDriverVendor,
adapterDriverVendor[infoIndex])) {
continue;
}
if (info[i].mDevices && !info[i].mDevices->IsEmpty()) {
nsresult rv = info[i].mDevices->Contains(adapterDeviceID[infoIndex]);
if (rv == NS_ERROR_NOT_AVAILABLE) {
// Not found
continue;
}
if (rv != NS_OK) {
// Failed to search, allowlist should not match, blocklist should match
// for safety reasons
if (aForAllowing) {
continue;
}
break;
}
}
bool match = false;
if (!info[i].mHardware.IsEmpty() && !info[i].mHardware.Equals(Hardware())) {
continue;
}
if (!info[i].mModel.IsEmpty() && !info[i].mModel.Equals(Model())) {
continue;
}
if (!info[i].mProduct.IsEmpty() && !info[i].mProduct.Equals(Product())) {
continue;
}
if (!info[i].mManufacturer.IsEmpty() &&
!info[i].mManufacturer.Equals(Manufacturer())) {
continue;
}
#if defined(XP_WIN) || defined(ANDROID) || defined(MOZ_X11)
switch (info[i].mComparisonOp) {
case DRIVER_LESS_THAN:
match = driverVersion[infoIndex] < info[i].mDriverVersion;
break;
case DRIVER_BUILD_ID_LESS_THAN:
match = (driverVersion[infoIndex] & 0xFFFF) < info[i].mDriverVersion;
break;
case DRIVER_LESS_THAN_OR_EQUAL:
match = driverVersion[infoIndex] <= info[i].mDriverVersion;
break;
case DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL:
match = (driverVersion[infoIndex] & 0xFFFF) <= info[i].mDriverVersion;
break;
case DRIVER_GREATER_THAN:
match = driverVersion[infoIndex] > info[i].mDriverVersion;
break;
case DRIVER_GREATER_THAN_OR_EQUAL:
match = driverVersion[infoIndex] >= info[i].mDriverVersion;
break;
case DRIVER_EQUAL:
match = driverVersion[infoIndex] == info[i].mDriverVersion;
break;
case DRIVER_NOT_EQUAL:
match = driverVersion[infoIndex] != info[i].mDriverVersion;
break;
case DRIVER_BETWEEN_EXCLUSIVE:
match = driverVersion[infoIndex] > info[i].mDriverVersion &&
driverVersion[infoIndex] < info[i].mDriverVersionMax;
break;
case DRIVER_BETWEEN_INCLUSIVE:
match = driverVersion[infoIndex] >= info[i].mDriverVersion &&
driverVersion[infoIndex] <= info[i].mDriverVersionMax;
break;
case DRIVER_BETWEEN_INCLUSIVE_START:
match = driverVersion[infoIndex] >= info[i].mDriverVersion &&
driverVersion[infoIndex] < info[i].mDriverVersionMax;
break;
case DRIVER_COMPARISON_IGNORED:
// We don't have a comparison op, so we match everything.
match = true;
break;
default:
NS_WARNING("Bogus op in GfxDriverInfo");
break;
}
#else
// We don't care what driver version it was. We only check OS version and if
// the device matches.
match = true;
#endif
if (match || info[i].mDriverVersion == GfxDriverInfo::allDriverVersions) {
if (info[i].mFeature == GfxDriverInfo::allFeatures ||
info[i].mFeature == aFeature) {
status = info[i].mFeatureStatus;
if (!info[i].mRuleId.IsEmpty()) {
aFailureId = info[i].mRuleId.get();
} else {
aFailureId = "FEATURE_FAILURE_DL_BLACKLIST_NO_ID";
}
break;
}
}
}
#if defined(XP_WIN)
// As a very special case, we block D2D on machines with an NVidia 310M GPU
// as either the primary or secondary adapter. D2D is also blocked when the
// NV 310M is the primary adapter (using the standard blocklisting mechanism).
// If the primary GPU already matched something in the blocklist then we
// ignore this special rule. See bug 1008759.
if (status == nsIGfxInfo::FEATURE_STATUS_UNKNOWN &&
(aFeature == nsIGfxInfo::FEATURE_DIRECT2D)) {
if (!adapterInfoFailed[1]) {
nsAString& nvVendorID =
(nsAString&)GfxDriverInfo::GetDeviceVendor(DeviceVendor::NVIDIA);
const nsString nv310mDeviceId = NS_LITERAL_STRING("0x0A70");
if (nvVendorID.Equals(adapterVendorID[1],
nsCaseInsensitiveStringComparator()) &&
nv310mDeviceId.Equals(adapterDeviceID[1],
nsCaseInsensitiveStringComparator())) {
status = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
aFailureId = "FEATURE_FAILURE_D2D_NV310M_BLOCK";
}
}
}
// Depends on Windows driver versioning. We don't pass a GfxDriverInfo object
// back to the Windows handler, so we must handle this here.
if (status == FEATURE_BLOCKED_DRIVER_VERSION) {
if (info[i].mSuggestedVersion) {
aSuggestedVersion.AppendPrintf("%s", info[i].mSuggestedVersion);
} else if (info[i].mComparisonOp == DRIVER_LESS_THAN &&
info[i].mDriverVersion != GfxDriverInfo::allDriverVersions) {
aSuggestedVersion.AppendPrintf(
"%lld.%lld.%lld.%lld",
(info[i].mDriverVersion & 0xffff000000000000) >> 48,
(info[i].mDriverVersion & 0x0000ffff00000000) >> 32,
(info[i].mDriverVersion & 0x00000000ffff0000) >> 16,
(info[i].mDriverVersion & 0x000000000000ffff));
}
}
#endif
return status;
}
void GfxInfoBase::SetFeatureStatus(
const nsTArray<dom::GfxInfoFeatureStatus>& aFS) {
MOZ_ASSERT(!sFeatureStatus);
sFeatureStatus = new nsTArray<dom::GfxInfoFeatureStatus>(aFS);
}
bool GfxInfoBase::DoesDesktopEnvironmentMatch(
const nsAString& aBlocklistDesktop, const nsAString& aDesktopEnv) {
return aBlocklistDesktop.Equals(aDesktopEnv,
nsCaseInsensitiveStringComparator()) ||
aBlocklistDesktop.Equals(
GfxDriverInfo::GetDesktopEnvironment(DesktopEnvironment::All),
nsCaseInsensitiveStringComparator());
}
bool GfxInfoBase::DoesWindowProtocolMatch(
const nsAString& aBlocklistWindowProtocol,
const nsAString& aWindowProtocol) {
return aBlocklistWindowProtocol.Equals(aWindowProtocol,
nsCaseInsensitiveStringComparator()) ||
aBlocklistWindowProtocol.Equals(
GfxDriverInfo::GetWindowProtocol(WindowProtocol::All),
nsCaseInsensitiveStringComparator());
}
bool GfxInfoBase::DoesVendorMatch(const nsAString& aBlocklistVendor,
const nsAString& aAdapterVendor) {
return aBlocklistVendor.Equals(aAdapterVendor,
nsCaseInsensitiveStringComparator()) ||
aBlocklistVendor.Equals(
GfxDriverInfo::GetDeviceVendor(DeviceVendor::All),
nsCaseInsensitiveStringComparator());
}
bool GfxInfoBase::DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
const nsAString& aDriverVendor) {
return aBlocklistVendor.Equals(aDriverVendor,
nsCaseInsensitiveStringComparator()) ||
aBlocklistVendor.Equals(
GfxDriverInfo::GetDriverVendor(DriverVendor::All),
nsCaseInsensitiveStringComparator());
}
bool GfxInfoBase::IsFeatureAllowlisted(int32_t aFeature) const {
return aFeature == nsIGfxInfo::FEATURE_WEBRENDER;
}
nsresult GfxInfoBase::GetFeatureStatusImpl(
int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedVersion,
const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
OperatingSystem* aOS /* = nullptr */) {
if (aFeature <= 0) {
gfxWarning() << "Invalid feature <= 0";
return NS_OK;
}
if (*aStatus != nsIGfxInfo::FEATURE_STATUS_UNKNOWN) {
// Terminate now with the status determined by the derived type (OS-specific
// code).
return NS_OK;
}
if (sShutdownOccurred) {
// This is futile; we've already commenced shutdown and our blocklists have
// been deleted. We may want to look into resurrecting the blocklist instead
// but for now, just don't even go there.
return NS_OK;
}
// Ensure any additional initialization required is complete.
GetData();
// If an operating system was provided by the derived GetFeatureStatusImpl,
// grab it here. Otherwise, the OS is unknown.
OperatingSystem os = (aOS ? *aOS : OperatingSystem::Unknown);
nsAutoString adapterVendorID;
nsAutoString adapterDeviceID;
nsAutoString adapterDriverVersionString;
if (NS_FAILED(GetAdapterVendorID(adapterVendorID)) ||
NS_FAILED(GetAdapterDeviceID(adapterDeviceID)) ||
NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString))) {
aFailureId = "FEATURE_FAILURE_CANT_RESOLVE_ADAPTER";
*aStatus = FEATURE_BLOCKED_DEVICE;
return NS_OK;
}
// Check if the device is blocked from the downloaded blocklist. If not, check
// the static list after that. This order is used so that we can later escape
// out of static blocks (i.e. if we were wrong or something was patched, we
// can back out our static block without doing a release).
int32_t status;
if (aDriverInfo.Length()) {
status =
FindBlocklistedDeviceInList(aDriverInfo, aSuggestedVersion, aFeature,
aFailureId, os, /* aForAllowing */ false);
} else {
if (!sDriverInfo) {
sDriverInfo = new nsTArray<GfxDriverInfo>();
}
status = FindBlocklistedDeviceInList(GetGfxDriverInfo(), aSuggestedVersion,
aFeature, aFailureId, os,
/* aForAllowing */ false);
}
if (status == nsIGfxInfo::FEATURE_STATUS_UNKNOWN) {
if (IsFeatureAllowlisted(aFeature)) {
// This feature is actually using the allowlist; that means after we pass
// the blocklist to prevent us explicitly from getting the feature, we now
// need to check the allowlist to ensure we are allowed to get it in the
// first place.
if (aDriverInfo.Length()) {
status = FindBlocklistedDeviceInList(aDriverInfo, aSuggestedVersion,
aFeature, aFailureId, os,
/* aForAllowing */ true);
} else {
status = FindBlocklistedDeviceInList(
GetGfxDriverInfo(), aSuggestedVersion, aFeature, aFailureId, os,
/* aForAllowing */ true);
}
if (status == nsIGfxInfo::FEATURE_STATUS_UNKNOWN) {
status = nsIGfxInfo::FEATURE_DENIED;
}
} else {
// It's now done being processed. It's safe to set the status to
// STATUS_OK.
status = nsIGfxInfo::FEATURE_STATUS_OK;
}
}
*aStatus = status;
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetFeatureSuggestedDriverVersion(int32_t aFeature,
nsAString& aVersion) {
nsCString version;
if (GetPrefValueForDriverVersion(version)) {
aVersion = NS_ConvertASCIItoUTF16(version);
return NS_OK;
}
int32_t status;
nsCString discardFailureId;
nsTArray<GfxDriverInfo> driverInfo;
return GetFeatureStatusImpl(aFeature, &status, aVersion, driverInfo,
discardFailureId);
}
void GfxInfoBase::EvaluateDownloadedBlacklist(
nsTArray<GfxDriverInfo>& aDriverInfo) {
int32_t features[] = {nsIGfxInfo::FEATURE_DIRECT2D,
nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS,
nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS,
nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE,
nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
nsIGfxInfo::FEATURE_OPENGL_LAYERS,
nsIGfxInfo::FEATURE_WEBGL_OPENGL,
nsIGfxInfo::FEATURE_WEBGL_ANGLE,
nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE,
nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE,
nsIGfxInfo::FEATURE_WEBGL_MSAA,
nsIGfxInfo::FEATURE_STAGEFRIGHT,
nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_H264,
nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION,
nsIGfxInfo::FEATURE_VP8_HW_DECODE,
nsIGfxInfo::FEATURE_VP9_HW_DECODE,
nsIGfxInfo::FEATURE_DX_INTEROP2,
nsIGfxInfo::FEATURE_GPU_PROCESS,
nsIGfxInfo::FEATURE_WEBGL2,
nsIGfxInfo::FEATURE_ADVANCED_LAYERS,
nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX,
nsIGfxInfo::FEATURE_WEBRENDER,
nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR,
nsIGfxInfo::FEATURE_DX_NV12,
nsIGfxInfo::FEATURE_DX_P010,
nsIGfxInfo::FEATURE_DX_P016,
nsIGfxInfo::FEATURE_GL_SWIZZLE,
0};
// For every feature we know about, we evaluate whether this blacklist has a
// non-STATUS_OK status. If it does, we set the pref we evaluate in
// GetFeatureStatus above, so we don't need to hold on to this blacklist
// anywhere permanent.
int i = 0;
while (features[i]) {
int32_t status;
nsCString failureId;
nsAutoString suggestedVersion;
if (NS_SUCCEEDED(GetFeatureStatusImpl(
features[i], &status, suggestedVersion, aDriverInfo, failureId))) {
switch (status) {
default:
case nsIGfxInfo::FEATURE_STATUS_OK:
RemovePrefForFeature(features[i]);
break;
case nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION:
if (!suggestedVersion.IsEmpty()) {
SetPrefValueForDriverVersion(suggestedVersion);
} else {
RemovePrefForDriverVersion();
}
[[fallthrough]];
case nsIGfxInfo::FEATURE_BLOCKED_MISMATCHED_VERSION:
case nsIGfxInfo::FEATURE_BLOCKED_DEVICE:
case nsIGfxInfo::FEATURE_DISCOURAGED:
case nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION:
SetPrefValueForFeature(features[i], status, failureId);
break;
}
}
++i;
}
}
NS_IMETHODIMP_(void)
GfxInfoBase::LogFailure(const nsACString& failure) {
// gfxCriticalError has a mutex lock of its own, so we may not actually
// need this lock. ::GetFailures() accesses the data but the LogForwarder
// will not return the copy of the logs unless it can get the same lock
// that gfxCriticalError uses. Still, that is so much of an implementation
// detail that it's nicer to just add an extra lock here and in
// ::GetFailures()
MutexAutoLock lock(mMutex);
// By default, gfxCriticalError asserts; make it not assert in this case.
gfxCriticalError(CriticalLog::DefaultOptions(false))
<< "(LF) " << failure.BeginReading();
}
NS_IMETHODIMP GfxInfoBase::GetFailures(nsTArray<int32_t>& indices,
nsTArray<nsCString>& failures) {
MutexAutoLock lock(mMutex);
LogForwarder* logForwarder = Factory::GetLogForwarder();
if (!logForwarder) {
return NS_ERROR_UNEXPECTED;
}
// There are two string copies in this method, starting with this one. We are
// assuming this is not a big deal, as the size of the array should be small
// and the strings in it should be small as well (the error messages in the
// code.) The second copy happens with the AppendElement() calls.
// Technically, we don't need the mutex lock after the StringVectorCopy()
// call.
LoggingRecord loggedStrings = logForwarder->LoggingRecordCopy();
LoggingRecord::const_iterator it;
for (it = loggedStrings.begin(); it != loggedStrings.end(); ++it) {
failures.AppendElement(
nsDependentCSubstring(Get<1>(*it).c_str(), Get<1>(*it).size()));
indices.AppendElement(Get<0>(*it));
}
return NS_OK;
}
nsTArray<GfxInfoCollectorBase*>* sCollectors;
static void InitCollectors() {
if (!sCollectors) sCollectors = new nsTArray<GfxInfoCollectorBase*>;
}
nsresult GfxInfoBase::GetInfo(JSContext* aCx,
JS::MutableHandle<JS::Value> aResult) {
InitCollectors();
InfoObject obj(aCx);
for (uint32_t i = 0; i < sCollectors->Length(); i++) {
(*sCollectors)[i]->GetInfo(obj);
}
// Some example property definitions
// obj.DefineProperty("wordCacheSize", gfxTextRunWordCache::Count());
// obj.DefineProperty("renderer", mRendererIDsString);
// obj.DefineProperty("five", 5);
if (!obj.mOk) {
return NS_ERROR_FAILURE;
}
aResult.setObject(*obj.mObj);
return NS_OK;
}
nsAutoCString gBaseAppVersion;
const nsCString& GfxInfoBase::GetApplicationVersion() {
static bool versionInitialized = false;
if (!versionInitialized) {
// If we fail to get the version, we will not try again.
versionInitialized = true;
// Get the version from xpcom/system/nsIXULAppInfo.idl
nsCOMPtr<nsIXULAppInfo> app = do_GetService("@mozilla.org/xre/app-info;1");
if (app) {
app->GetVersion(gBaseAppVersion);
}
}
return gBaseAppVersion;
}
void GfxInfoBase::AddCollector(GfxInfoCollectorBase* collector) {
InitCollectors();
sCollectors->AppendElement(collector);
}
void GfxInfoBase::RemoveCollector(GfxInfoCollectorBase* collector) {
InitCollectors();
for (uint32_t i = 0; i < sCollectors->Length(); i++) {
if ((*sCollectors)[i] == collector) {
sCollectors->RemoveElementAt(i);
break;
}
}
if (sCollectors->IsEmpty()) {
delete sCollectors;
sCollectors = nullptr;
}
}
nsresult GfxInfoBase::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) {
// If we have no platform specific implementation for detecting monitors, we
// can just get the screen size from gfxPlatform as the best guess.
if (!gfxPlatform::Initialized()) {
return NS_OK;
}
// If the screen size is empty, we are probably in xpcshell.
gfx::IntSize screenSize = gfxPlatform::GetPlatform()->GetScreenSize();
JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
JS::Rooted<JS::Value> screenWidth(aCx, JS::Int32Value(screenSize.width));
JS_SetProperty(aCx, obj, "screenWidth", screenWidth);
JS::Rooted<JS::Value> screenHeight(aCx, JS::Int32Value(screenSize.height));
JS_SetProperty(aCx, obj, "screenHeight", screenHeight);
JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
JS_SetElement(aCx, aOutArray, 0, element);
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetMonitors(JSContext* aCx, JS::MutableHandleValue aResult) {
JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0));
nsresult rv = FindMonitors(aCx, array);
if (NS_FAILED(rv)) {
return rv;
}
aResult.setObject(*array);
return NS_OK;
}
static inline bool SetJSPropertyString(JSContext* aCx,
JS::Handle<JSObject*> aObj,
const char* aProp, const char* aString) {
JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, aString));
if (!str) {
return false;
}
JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));
return JS_SetProperty(aCx, aObj, aProp, val);
}
template <typename T>
static inline bool AppendJSElement(JSContext* aCx, JS::Handle<JSObject*> aObj,
const T& aValue) {
uint32_t index;
if (!JS::GetArrayLength(aCx, aObj, &index)) {
return false;
}
return JS_SetElement(aCx, aObj, index, aValue);
}
nsresult GfxInfoBase::GetFeatures(JSContext* aCx,
JS::MutableHandle<JS::Value> aOut) {
JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
if (!obj) {
return NS_ERROR_OUT_OF_MEMORY;
}
aOut.setObject(*obj);
layers::LayersBackend backend =
gfxPlatform::Initialized()
? gfxPlatform::GetPlatform()->GetCompositorBackend()
: layers::LayersBackend::LAYERS_NONE;
const char* backendName = layers::GetLayersBackendName(backend);
SetJSPropertyString(aCx, obj, "compositor", backendName);
// If graphics isn't initialized yet, just stop now.
if (!gfxPlatform::Initialized()) {
return NS_OK;
}
DescribeFeatures(aCx, obj);
return NS_OK;
}
nsresult GfxInfoBase::GetFeatureLog(JSContext* aCx,
JS::MutableHandle<JS::Value> aOut) {
JS::Rooted<JSObject*> containerObj(aCx, JS_NewPlainObject(aCx));
if (!containerObj) {
return NS_ERROR_OUT_OF_MEMORY;
}
aOut.setObject(*containerObj);
JS::Rooted<JSObject*> featureArray(aCx, JS::NewArrayObject(aCx, 0));
if (!featureArray) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Collect features.
gfxConfig::ForEachFeature([&](const char* aName, const char* aDescription,
FeatureState& aFeature) -> void {
JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
if (!obj) {
return;
}
if (!SetJSPropertyString(aCx, obj, "name", aName) ||
!SetJSPropertyString(aCx, obj, "description", aDescription) ||
!SetJSPropertyString(aCx, obj, "status",
FeatureStatusToString(aFeature.GetValue()))) {
return;
}
JS::Rooted<JS::Value> log(aCx);
if (!BuildFeatureStateLog(aCx, aFeature, &log)) {
return;
}
if (!JS_SetProperty(aCx, obj, "log", log)) {
return;
}
if (!AppendJSElement(aCx, featureArray, obj)) {
return;
}
});
JS::Rooted<JSObject*> fallbackArray(aCx, JS::NewArrayObject(aCx, 0));
if (!fallbackArray) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Collect fallbacks.
gfxConfig::ForEachFallback(
[&](const char* aName, const char* aMessage) -> void {
JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
if (!obj) {
return;
}
if (!SetJSPropertyString(aCx, obj, "name", aName) ||
!SetJSPropertyString(aCx, obj, "message", aMessage)) {
return;
}
if (!AppendJSElement(aCx, fallbackArray, obj)) {
return;
}
});
JS::Rooted<JS::Value> val(aCx);
val = JS::ObjectValue(*featureArray);
JS_SetProperty(aCx, containerObj, "features", val);
val = JS::ObjectValue(*fallbackArray);
JS_SetProperty(aCx, containerObj, "fallbacks", val);
return NS_OK;
}
bool GfxInfoBase::BuildFeatureStateLog(JSContext* aCx,
const FeatureState& aFeature,
JS::MutableHandle<JS::Value> aOut) {
JS::Rooted<JSObject*> log(aCx, JS::NewArrayObject(aCx, 0));
if (!log) {
return false;
}
aOut.setObject(*log);
aFeature.ForEachStatusChange([&](const char* aType, FeatureStatus aStatus,
const char* aMessage) -> void {
JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
if (!obj) {
return;
}
if (!SetJSPropertyString(aCx, obj, "type", aType) ||
!SetJSPropertyString(aCx, obj, "status",
FeatureStatusToString(aStatus)) ||
(aMessage && !SetJSPropertyString(aCx, obj, "message", aMessage))) {
return;
}
if (!AppendJSElement(aCx, log, obj)) {
return;
}
});
return true;
}
void GfxInfoBase::DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> aObj) {
JS::Rooted<JSObject*> obj(aCx);
gfx::FeatureStatus gpuProcess =
gfxConfig::GetValue(gfx::Feature::GPU_PROCESS);
InitFeatureObject(aCx, aObj, "gpuProcess", gpuProcess, &obj);
gfx::FeatureStatus wrQualified =
gfxConfig::GetValue(gfx::Feature::WEBRENDER_QUALIFIED);
InitFeatureObject(aCx, aObj, "wrQualified", wrQualified, &obj);
gfx::FeatureStatus webrender = gfxConfig::GetValue(gfx::Feature::WEBRENDER);
InitFeatureObject(aCx, aObj, "webrender", webrender, &obj);
// Only include AL if the platform attempted to use it.
gfx::FeatureStatus advancedLayers =
gfxConfig::GetValue(gfx::Feature::ADVANCED_LAYERS);
if (advancedLayers != FeatureStatus::Unused) {
InitFeatureObject(aCx, aObj, "advancedLayers", advancedLayers, &obj);
if (gfxConfig::UseFallback(Fallback::NO_CONSTANT_BUFFER_OFFSETTING)) {
JS::Rooted<JS::Value> trueVal(aCx, JS::BooleanValue(true));
JS_SetProperty(aCx, obj, "noConstantBufferOffsetting", trueVal);
}
}
}
bool GfxInfoBase::InitFeatureObject(JSContext* aCx,
JS::Handle<JSObject*> aContainer,
const char* aName,
mozilla::gfx::FeatureStatus& aFeatureStatus,
JS::MutableHandle<JSObject*> aOutObj) {
JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
if (!obj) {
return false;
}
// Set "status".
const char* status = FeatureStatusToString(aFeatureStatus);
JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, status));
JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));
JS_SetProperty(aCx, obj, "status", 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;
}
nsresult GfxInfoBase::GetActiveCrashGuards(JSContext* aCx,
JS::MutableHandle<JS::Value> aOut) {
JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0));
if (!array) {
return NS_ERROR_OUT_OF_MEMORY;
}
aOut.setObject(*array);
DriverCrashGuard::ForEachActiveCrashGuard(
[&](const char* aName, const char* aPrefName) -> void {
JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
if (!obj) {
return;
}
if (!SetJSPropertyString(aCx, obj, "type", aName)) {
return;
}
if (!SetJSPropertyString(aCx, obj, "prefName", aPrefName)) {
return;
}
if (!AppendJSElement(aCx, array, obj)) {
return;
}
});
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetWebRenderEnabled(bool* aWebRenderEnabled) {
*aWebRenderEnabled = gfxVars::UseWebRender();
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetUsesTiling(bool* aUsesTiling) {
*aUsesTiling = gfxPlatform::GetPlatform()->UsesTiling();
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetContentUsesTiling(bool* aUsesTiling) {
*aUsesTiling = gfxPlatform::GetPlatform()->ContentUsesTiling();
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetOffMainThreadPaintEnabled(bool* aOffMainThreadPaintEnabled) {
*aOffMainThreadPaintEnabled = gfxConfig::IsEnabled(gfx::Feature::OMTP);
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetOffMainThreadPaintWorkerCount(
int32_t* aOffMainThreadPaintWorkerCount) {
if (gfxConfig::IsEnabled(gfx::Feature::OMTP)) {
*aOffMainThreadPaintWorkerCount =
layers::PaintThread::CalculatePaintWorkerCount();
} else {
*aOffMainThreadPaintWorkerCount = 0;
}
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetTargetFrameRate(uint32_t* aTargetFrameRate) {
*aTargetFrameRate = gfxPlatform::TargetFrameRate();
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetIsHeadless(bool* aIsHeadless) {
*aIsHeadless = gfxPlatform::IsHeadless();
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetContentBackend(nsAString& aContentBackend) {
BackendType backend = gfxPlatform::GetPlatform()->GetDefaultContentBackend();
nsString outStr;
switch (backend) {
case BackendType::DIRECT2D1_1: {
outStr.AppendPrintf("Direct2D 1.1");
break;
}
case BackendType::SKIA: {
outStr.AppendPrintf("Skia");
break;
}
case BackendType::CAIRO: {
outStr.AppendPrintf("Cairo");
break;
}
default:
return NS_ERROR_FAILURE;
}
aContentBackend.Assign(outStr);
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::GetUsingGPUProcess(bool* aOutValue) {
GPUProcessManager* gpu = GPUProcessManager::Get();
if (!gpu) {
// Not supported in content processes.
return NS_ERROR_FAILURE;
}
*aOutValue = !!gpu->GetGPUChild();
return NS_OK;
}
NS_IMETHODIMP
GfxInfoBase::ControlGPUProcessForXPCShell(bool aEnable, bool* _retval) {
gfxPlatform::GetPlatform();
GPUProcessManager* gpm = GPUProcessManager::Get();
if (aEnable) {
if (!gfxConfig::IsEnabled(gfx::Feature::GPU_PROCESS)) {
gfxConfig::UserForceEnable(gfx::Feature::GPU_PROCESS, "xpcshell-test");
}
gpm->LaunchGPUProcess();
gpm->EnsureGPUReady();
} else {
gfxConfig::UserDisable(gfx::Feature::GPU_PROCESS, "xpcshell-test");
gpm->KillProcess();
}
*_retval = true;
return NS_OK;
}
GfxInfoCollectorBase::GfxInfoCollectorBase() {
GfxInfoBase::AddCollector(this);
}
GfxInfoCollectorBase::~GfxInfoCollectorBase() {
GfxInfoBase::RemoveCollector(this);
}