diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /widget/GfxInfoBase.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'widget/GfxInfoBase.cpp')
-rw-r--r-- | widget/GfxInfoBase.cpp | 1438 |
1 files changed, 1438 insertions, 0 deletions
diff --git a/widget/GfxInfoBase.cpp b/widget/GfxInfoBase.cpp new file mode 100644 index 000000000..e53db69c5 --- /dev/null +++ b/widget/GfxInfoBase.cpp @@ -0,0 +1,1438 @@ +/* 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 "GfxInfoWebGL.h" +#include "GfxDriverInfo.h" +#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 "nsIDOMElement.h" +#include "nsIDOMHTMLCollection.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsTArray.h" +#include "nsXULAppAPI.h" +#include "nsIXULAppInfo.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "MediaPrefs.h" +#include "gfxPrefs.h" +#include "gfxPlatform.h" +#include "gfxConfig.h" +#include "DriverCrashGuard.h" + +#if defined(MOZ_CRASHREPORTER) +#include "nsExceptionHandler.h" +#endif + +using namespace mozilla::widget; +using namespace mozilla; +using mozilla::MutexAutoLock; + +nsTArray<GfxDriverInfo>* GfxInfoBase::mDriverInfo; +bool GfxInfoBase::mDriverInfoObserverInitialized; + +// 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::mDriverInfo; + GfxInfoBase::mDriverInfo = nullptr; + + for (uint32_t i = 0; i < DeviceFamilyMax; i++) + delete GfxDriverInfo::mDeviceFamilies[i]; + + for (uint32_t i = 0; i < DeviceVendorMax; i++) + delete GfxDriverInfo::mDeviceVendors[i]; + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver) + +void InitGfxDriverInfoShutdownObserver() +{ + if (GfxInfoBase::mDriverInfoObserverInitialized) + return; + + GfxInfoBase::mDriverInfoObserverInitialized = 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: + name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration"; + 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_VP8_HW_DECODE: + case nsIGfxInfo::FEATURE_VP9_HW_DECODE: + case nsIGfxInfo::FEATURE_DX_INTEROP2: + // We don't provide prefs for these features. + 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"; + nsAdoptingCString failureValue = Preferences::GetCString(failureprefname.get()); + if (failureValue) { + 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; + + 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; + + 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 5.1")) + return OperatingSystem::WindowsXP; + else if (os.EqualsLiteral("WINNT 5.2")) + return OperatingSystem::WindowsServer2003; + else if (os.EqualsLiteral("WINNT 6.0")) + return OperatingSystem::WindowsVista; + else 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("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->AppendElement(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")) + return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION; + else if (aFeature.EqualsLiteral("CANVAS2D_ACCELERATION")) + return nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION; + + // 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; + + // 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("vendor")) { + aDriverInfo.mAdapterVendor = 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() + : mMutex("GfxInfoBase") +{ +} + +GfxInfoBase::~GfxInfoBase() +{ +} + +nsresult +GfxInfoBase::Init() +{ + InitGfxDriverInfoShutdownObserver(); + gfxPrefs::GetSingleton(); + MediaPrefs::GetSingleton(); + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->AddObserver(this, "blocklist-data-gfxItems", true); + } + + return NS_OK; +} + +NS_IMETHODIMP +GfxInfoBase::GetFeatureStatus(int32_t aFeature, nsACString& aFailureId, int32_t* aStatus) +{ + int32_t blocklistAll = gfxPrefs::BlocklistAll(); + 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()) { + // Delegate to the parent process. + mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); + bool success; + nsCString remoteFailureId; + cc->SendGetGraphicsFeatureStatus(aFeature, aStatus, &remoteFailureId, &success); + aFailureId = remoteFailureId; + return success ? NS_OK : NS_ERROR_FAILURE; + } + + nsString version; + nsTArray<GfxDriverInfo> driverInfo; + nsresult rv = GetFeatureStatusImpl(aFeature, aStatus, version, driverInfo, aFailureId); + return rv; +} + +// 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) +{ + 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; + } +#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; +} + +int32_t +GfxInfoBase::FindBlocklistedDeviceInList(const nsTArray<GfxDriverInfo>& info, + nsAString& aSuggestedVersion, + int32_t aFeature, + nsACString& aFailureId, + OperatingSystem os) +{ + int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; + + uint32_t i = 0; + for (; i < info.Length(); i++) { + // 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)) { + continue; + } + + if (info[i].mOperatingSystemVersion && info[i].mOperatingSystemVersion != OperatingSystemVersion()) { + continue; + } + + // XXX: it would be better not to do this everytime round the loop + nsAutoString adapterVendorID; + nsAutoString adapterDeviceID; + nsAutoString adapterDriverVersionString; + if (info[i].mGpu2) { + if (NS_FAILED(GetAdapterVendorID2(adapterVendorID)) || + NS_FAILED(GetAdapterDeviceID2(adapterDeviceID)) || + NS_FAILED(GetAdapterDriverVersion2(adapterDriverVersionString))) + { + return 0; + } + } else { + if (NS_FAILED(GetAdapterVendorID(adapterVendorID)) || + NS_FAILED(GetAdapterDeviceID(adapterDeviceID)) || + NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString))) + { + return 0; + } + } + +#if defined(XP_WIN) || defined(ANDROID) + uint64_t driverVersion; + ParseDriverVersion(adapterDriverVersionString, &driverVersion); +#endif + + if (!info[i].mAdapterVendor.Equals(GfxDriverInfo::GetDeviceVendor(VendorAll), nsCaseInsensitiveStringComparator()) && + !info[i].mAdapterVendor.Equals(adapterVendorID, nsCaseInsensitiveStringComparator())) { + continue; + } + + if (info[i].mDevices != GfxDriverInfo::allDevices && info[i].mDevices->Length()) { + bool deviceMatches = false; + for (uint32_t j = 0; j < info[i].mDevices->Length(); j++) { + if ((*info[i].mDevices)[j].Equals(adapterDeviceID, nsCaseInsensitiveStringComparator())) { + deviceMatches = true; + break; + } + } + + if (!deviceMatches) { + continue; + } + } + + 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) + switch (info[i].mComparisonOp) { + case DRIVER_LESS_THAN: + match = driverVersion < info[i].mDriverVersion; + break; + case DRIVER_BUILD_ID_LESS_THAN: + match = (driverVersion & 0xFFFF) < info[i].mDriverVersion; + break; + case DRIVER_LESS_THAN_OR_EQUAL: + match = driverVersion <= info[i].mDriverVersion; + break; + case DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL: + match = (driverVersion & 0xFFFF) <= info[i].mDriverVersion; + break; + case DRIVER_GREATER_THAN: + match = driverVersion > info[i].mDriverVersion; + break; + case DRIVER_GREATER_THAN_OR_EQUAL: + match = driverVersion >= info[i].mDriverVersion; + break; + case DRIVER_EQUAL: + match = driverVersion == info[i].mDriverVersion; + break; + case DRIVER_NOT_EQUAL: + match = driverVersion != info[i].mDriverVersion; + break; + case DRIVER_BETWEEN_EXCLUSIVE: + match = driverVersion > info[i].mDriverVersion && driverVersion < info[i].mDriverVersionMax; + break; + case DRIVER_BETWEEN_INCLUSIVE: + match = driverVersion >= info[i].mDriverVersion && driverVersion <= info[i].mDriverVersionMax; + break; + case DRIVER_BETWEEN_INCLUSIVE_START: + match = driverVersion >= info[i].mDriverVersion && driverVersion < 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)) { + nsAutoString adapterVendorID2; + nsAutoString adapterDeviceID2; + if ((!NS_FAILED(GetAdapterVendorID2(adapterVendorID2))) && + (!NS_FAILED(GetAdapterDeviceID2(adapterDeviceID2)))) + { + nsAString &nvVendorID = (nsAString &)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA); + const nsString nv310mDeviceId = NS_LITERAL_STRING("0x0A70"); + if (nvVendorID.Equals(adapterVendorID2, nsCaseInsensitiveStringComparator()) && + nv310mDeviceId.Equals(adapterDeviceID2, 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; +} + +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 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); + } else { + if (!mDriverInfo) { + mDriverInfo = new nsTArray<GfxDriverInfo>(); + } + status = FindBlocklistedDeviceInList(GetGfxDriverInfo(), aSuggestedVersion, aFeature, aFailureId, os); + } + + // It's now done being processed. It's safe to set the status to STATUS_OK. + if (status == nsIGfxInfo::FEATURE_STATUS_UNKNOWN) { + *aStatus = nsIGfxInfo::FEATURE_STATUS_OK; + } else { + *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); +} + + +NS_IMETHODIMP +GfxInfoBase::GetWebGLParameter(const nsAString& aParam, + nsAString& aResult) +{ + return GfxInfoWebGL::GetWebGLParameter(aParam, aResult); +} + +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, + nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION, + 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(); + } + MOZ_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(); +} + +/* XPConnect method of returning arrays is very ugly. Would not recommend. */ +NS_IMETHODIMP GfxInfoBase::GetFailures(uint32_t* failureCount, + int32_t** indices, + char ***failures) +{ + MutexAutoLock lock(mMutex); + + NS_ENSURE_ARG_POINTER(failureCount); + NS_ENSURE_ARG_POINTER(failures); + + *failures = nullptr; + *failureCount = 0; + + // indices is "allowed" to be null, the caller may not care about them, + // although calling from JS doesn't seem to get us there. + if (indices) *indices = nullptr; + + LogForwarder* logForwarder = Factory::GetLogForwarder(); + if (!logForwarder) { + return NS_ERROR_UNEXPECTED; + } + + // There are two stirng 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 Clone() calls. Technically, + // we don't need the mutex lock after the StringVectorCopy() call. + LoggingRecord loggedStrings = logForwarder->LoggingRecordCopy(); + *failureCount = loggedStrings.size(); + + if (*failureCount != 0) { + *failures = (char**)moz_xmalloc(*failureCount * sizeof(char*)); + if (!(*failures)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (indices) { + *indices = (int32_t*)moz_xmalloc(*failureCount * sizeof(int32_t)); + if (!(*indices)) { + free(*failures); + *failures = nullptr; + return NS_ERROR_OUT_OF_MEMORY; + } + } + + /* copy over the failure messages into the array we just allocated */ + LoggingRecord::const_iterator it; + uint32_t i=0; + for(it = loggedStrings.begin() ; it != loggedStrings.end(); ++it, i++) { + (*failures)[i] = (char*)nsMemory::Clone(Get<1>(*it).c_str(), Get<1>(*it).size() + 1); + if (indices) (*indices)[i] = Get<0>(*it); + + if (!(*failures)[i]) { + /* <sarcasm> I'm too afraid to use an inline function... </sarcasm> */ + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, (*failures)); + *failureCount = i; + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + 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; + } +} + +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 const char* +GetLayersBackendName(layers::LayersBackend aBackend) +{ + switch (aBackend) { + case layers::LayersBackend::LAYERS_NONE: + return "none"; + case layers::LayersBackend::LAYERS_OPENGL: + return "opengl"; + case layers::LayersBackend::LAYERS_D3D9: + return "d3d9"; + case layers::LayersBackend::LAYERS_D3D11: + return "d3d11"; + case layers::LayersBackend::LAYERS_CLIENT: + return "client"; + case layers::LayersBackend::LAYERS_BASIC: + return "basic"; + default: + MOZ_ASSERT_UNREACHABLE("unknown layers backend"); + return "unknown"; + } +} + +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 = 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) +{ +} + +bool +GfxInfoBase::InitFeatureObject(JSContext* aCx, + JS::Handle<JSObject*> aContainer, + const char* aName, + int32_t aFeature, + Maybe<mozilla::gfx::FeatureStatus> aFeatureStatus, + JS::MutableHandle<JSObject*> aOutObj) +{ + JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); + if (!obj) { + return false; + } + + nsCString failureId = NS_LITERAL_CSTRING("OK"); + int32_t unused; + if (!NS_SUCCEEDED(GetFeatureStatus(aFeature, failureId, &unused))) { + return false; + } + + // Set "status". + if (aFeatureStatus) { + const char* status = FeatureStatusToString(aFeatureStatus.value()); + + 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::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; +} + +GfxInfoCollectorBase::GfxInfoCollectorBase() +{ + GfxInfoBase::AddCollector(this); +} + +GfxInfoCollectorBase::~GfxInfoCollectorBase() +{ + GfxInfoBase::RemoveCollector(this); +} |