michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Ideally this would be an xpcshell test, but Troubleshoot relies on things michael@0: // that aren't initialized outside of a XUL app environment like AddonManager michael@0: // and the "@mozilla.org/xre/app-info;1" component. michael@0: michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: Components.utils.import("resource://gre/modules/Troubleshoot.jsm"); michael@0: michael@0: function test() { michael@0: waitForExplicitFinish(); michael@0: function doNextTest() { michael@0: if (!tests.length) { michael@0: finish(); michael@0: return; michael@0: } michael@0: tests.shift()(doNextTest); michael@0: } michael@0: doNextTest(); michael@0: } michael@0: michael@0: registerCleanupFunction(function () { michael@0: // Troubleshoot.jsm is imported into the global scope -- the window -- above. michael@0: // If it's not deleted, it outlives the test and is reported as a leak. michael@0: delete window.Troubleshoot; michael@0: }); michael@0: michael@0: let tests = [ michael@0: michael@0: function snapshotSchema(done) { michael@0: Troubleshoot.snapshot(function (snapshot) { michael@0: try { michael@0: validateObject(snapshot, SNAPSHOT_SCHEMA); michael@0: ok(true, "The snapshot should conform to the schema."); michael@0: } michael@0: catch (err) { michael@0: ok(false, err); michael@0: } michael@0: done(); michael@0: }); michael@0: }, michael@0: michael@0: function modifiedPreferences(done) { michael@0: let prefs = [ michael@0: "javascript.troubleshoot", michael@0: "troubleshoot.foo", michael@0: "javascript.print_to_filename", michael@0: "network.proxy.troubleshoot", michael@0: ]; michael@0: prefs.forEach(function (p) { michael@0: Services.prefs.setBoolPref(p, true); michael@0: is(Services.prefs.getBoolPref(p), true, "The pref should be set: " + p); michael@0: }); michael@0: Troubleshoot.snapshot(function (snapshot) { michael@0: let p = snapshot.modifiedPreferences; michael@0: is(p["javascript.troubleshoot"], true, michael@0: "The pref should be present because it's whitelisted " + michael@0: "but not blacklisted."); michael@0: ok(!("troubleshoot.foo" in p), michael@0: "The pref should be absent because it's not in the whitelist."); michael@0: ok(!("javascript.print_to_filename" in p), michael@0: "The pref should be absent because it's blacklisted."); michael@0: ok(!("network.proxy.troubleshoot" in p), michael@0: "The pref should be absent because it's blacklisted."); michael@0: prefs.forEach(function (p) Services.prefs.deleteBranch(p)); michael@0: done(); michael@0: }); michael@0: }, michael@0: ]; michael@0: michael@0: // This is inspired by JSON Schema, or by the example on its Wikipedia page michael@0: // anyway. michael@0: const SNAPSHOT_SCHEMA = { michael@0: type: "object", michael@0: required: true, michael@0: properties: { michael@0: application: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: name: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: version: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: userAgent: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: vendor: { michael@0: type: "string", michael@0: }, michael@0: supportURL: { michael@0: type: "string", michael@0: }, michael@0: }, michael@0: }, michael@0: crashes: { michael@0: required: false, michael@0: type: "object", michael@0: properties: { michael@0: pending: { michael@0: required: true, michael@0: type: "number", michael@0: }, michael@0: submitted: { michael@0: required: true, michael@0: type: "array", michael@0: items: { michael@0: type: "object", michael@0: properties: { michael@0: id: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: date: { michael@0: required: true, michael@0: type: "number", michael@0: }, michael@0: pending: { michael@0: required: true, michael@0: type: "boolean", michael@0: }, michael@0: }, michael@0: }, michael@0: }, michael@0: }, michael@0: }, michael@0: extensions: { michael@0: required: true, michael@0: type: "array", michael@0: items: { michael@0: type: "object", michael@0: properties: { michael@0: name: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: version: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: id: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: isActive: { michael@0: required: true, michael@0: type: "boolean", michael@0: }, michael@0: }, michael@0: }, michael@0: }, michael@0: modifiedPreferences: { michael@0: required: true, michael@0: type: "object", michael@0: }, michael@0: graphics: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: numTotalWindows: { michael@0: required: true, michael@0: type: "number", michael@0: }, michael@0: numAcceleratedWindows: { michael@0: required: true, michael@0: type: "number", michael@0: }, michael@0: windowLayerManagerType: { michael@0: type: "string", michael@0: }, michael@0: windowLayerManagerRemote: { michael@0: type: "boolean", michael@0: }, michael@0: numAcceleratedWindowsMessage: { michael@0: type: "array", michael@0: }, michael@0: adapterDescription: { michael@0: type: "string", michael@0: }, michael@0: adapterVendorID: { michael@0: type: "string", michael@0: }, michael@0: adapterDeviceID: { michael@0: type: "string", michael@0: }, michael@0: adapterRAM: { michael@0: type: "string", michael@0: }, michael@0: adapterDrivers: { michael@0: type: "string", michael@0: }, michael@0: driverVersion: { michael@0: type: "string", michael@0: }, michael@0: driverDate: { michael@0: type: "string", michael@0: }, michael@0: adapterDescription2: { michael@0: type: "string", michael@0: }, michael@0: adapterVendorID2: { michael@0: type: "string", michael@0: }, michael@0: adapterDeviceID2: { michael@0: type: "string", michael@0: }, michael@0: adapterRAM2: { michael@0: type: "string", michael@0: }, michael@0: adapterDrivers2: { michael@0: type: "string", michael@0: }, michael@0: driverVersion2: { michael@0: type: "string", michael@0: }, michael@0: driverDate2: { michael@0: type: "string", michael@0: }, michael@0: isGPU2Active: { michael@0: type: "boolean", michael@0: }, michael@0: direct2DEnabled: { michael@0: type: "boolean", michael@0: }, michael@0: directWriteEnabled: { michael@0: type: "boolean", michael@0: }, michael@0: directWriteVersion: { michael@0: type: "string", michael@0: }, michael@0: clearTypeParameters: { michael@0: type: "string", michael@0: }, michael@0: webglRenderer: { michael@0: type: "string", michael@0: }, michael@0: info: { michael@0: type: "object", michael@0: }, michael@0: failures: { michael@0: type: "array", michael@0: items: { michael@0: type: "string", michael@0: }, michael@0: }, michael@0: direct2DEnabledMessage: { michael@0: type: "array", michael@0: }, michael@0: webglRendererMessage: { michael@0: type: "array", michael@0: }, michael@0: }, michael@0: }, michael@0: javaScript: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: incrementalGCEnabled: { michael@0: type: "boolean", michael@0: }, michael@0: }, michael@0: }, michael@0: accessibility: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: isActive: { michael@0: required: true, michael@0: type: "boolean", michael@0: }, michael@0: forceDisabled: { michael@0: type: "number", michael@0: }, michael@0: }, michael@0: }, michael@0: libraryVersions: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: NSPR: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: minVersion: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: version: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: }, michael@0: }, michael@0: NSS: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: minVersion: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: version: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: }, michael@0: }, michael@0: NSSUTIL: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: minVersion: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: version: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: }, michael@0: }, michael@0: NSSSSL: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: minVersion: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: version: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: }, michael@0: }, michael@0: NSSSMIME: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: minVersion: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: version: { michael@0: required: true, michael@0: type: "string", michael@0: }, michael@0: }, michael@0: }, michael@0: }, michael@0: }, michael@0: userJS: { michael@0: required: true, michael@0: type: "object", michael@0: properties: { michael@0: exists: { michael@0: required: true, michael@0: type: "boolean", michael@0: }, michael@0: }, michael@0: }, michael@0: experiments: { michael@0: type: "array", michael@0: }, michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Throws an Error if obj doesn't conform to schema. That way you get a nice michael@0: * error message and a stack to help you figure out what went wrong, which you michael@0: * wouldn't get if this just returned true or false instead. There's still michael@0: * room for improvement in communicating validation failures, however. michael@0: * michael@0: * @param obj The object to validate. michael@0: * @param schema The schema that obj should conform to. michael@0: */ michael@0: function validateObject(obj, schema) { michael@0: if (obj === undefined && !schema.required) michael@0: return; michael@0: if (typeof(schema.type) != "string") michael@0: throw schemaErr("'type' must be a string", schema); michael@0: if (objType(obj) != schema.type) michael@0: throw validationErr("Object is not of the expected type", obj, schema); michael@0: let validatorFnName = "validateObject_" + schema.type; michael@0: if (!(validatorFnName in this)) michael@0: throw schemaErr("Validator function not defined for type", schema); michael@0: this[validatorFnName](obj, schema); michael@0: } michael@0: michael@0: function validateObject_object(obj, schema) { michael@0: if (typeof(schema.properties) != "object") michael@0: // Don't care what obj's properties are. michael@0: return; michael@0: // First check that all the schema's properties match the object. michael@0: for (let prop in schema.properties) michael@0: validateObject(obj[prop], schema.properties[prop]); michael@0: // Now check that the object doesn't have any properties not in the schema. michael@0: for (let prop in obj) michael@0: if (!(prop in schema.properties)) michael@0: throw validationErr("Object has property not in schema", obj, schema); michael@0: } michael@0: michael@0: function validateObject_array(array, schema) { michael@0: if (typeof(schema.items) != "object") michael@0: // Don't care what the array's elements are. michael@0: return; michael@0: array.forEach(function (elt) validateObject(elt, schema.items)); michael@0: } michael@0: michael@0: function validateObject_string(str, schema) {} michael@0: function validateObject_boolean(bool, schema) {} michael@0: function validateObject_number(num, schema) {} michael@0: michael@0: function validationErr(msg, obj, schema) { michael@0: return new Error("Validation error: " + msg + michael@0: ": object=" + JSON.stringify(obj) + michael@0: ", schema=" + JSON.stringify(schema)); michael@0: } michael@0: michael@0: function schemaErr(msg, schema) { michael@0: return new Error("Schema error: " + msg + ": " + JSON.stringify(schema)); michael@0: } michael@0: michael@0: function objType(obj) { michael@0: let type = typeof(obj); michael@0: if (type != "object") michael@0: return type; michael@0: if (Array.isArray(obj)) michael@0: return "array"; michael@0: if (obj === null) michael@0: return "null"; michael@0: return type; michael@0: }