|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 // Ideally this would be an xpcshell test, but Troubleshoot relies on things |
|
6 // that aren't initialized outside of a XUL app environment like AddonManager |
|
7 // and the "@mozilla.org/xre/app-info;1" component. |
|
8 |
|
9 Components.utils.import("resource://gre/modules/Services.jsm"); |
|
10 Components.utils.import("resource://gre/modules/Troubleshoot.jsm"); |
|
11 |
|
12 function test() { |
|
13 waitForExplicitFinish(); |
|
14 function doNextTest() { |
|
15 if (!tests.length) { |
|
16 finish(); |
|
17 return; |
|
18 } |
|
19 tests.shift()(doNextTest); |
|
20 } |
|
21 doNextTest(); |
|
22 } |
|
23 |
|
24 registerCleanupFunction(function () { |
|
25 // Troubleshoot.jsm is imported into the global scope -- the window -- above. |
|
26 // If it's not deleted, it outlives the test and is reported as a leak. |
|
27 delete window.Troubleshoot; |
|
28 }); |
|
29 |
|
30 let tests = [ |
|
31 |
|
32 function snapshotSchema(done) { |
|
33 Troubleshoot.snapshot(function (snapshot) { |
|
34 try { |
|
35 validateObject(snapshot, SNAPSHOT_SCHEMA); |
|
36 ok(true, "The snapshot should conform to the schema."); |
|
37 } |
|
38 catch (err) { |
|
39 ok(false, err); |
|
40 } |
|
41 done(); |
|
42 }); |
|
43 }, |
|
44 |
|
45 function modifiedPreferences(done) { |
|
46 let prefs = [ |
|
47 "javascript.troubleshoot", |
|
48 "troubleshoot.foo", |
|
49 "javascript.print_to_filename", |
|
50 "network.proxy.troubleshoot", |
|
51 ]; |
|
52 prefs.forEach(function (p) { |
|
53 Services.prefs.setBoolPref(p, true); |
|
54 is(Services.prefs.getBoolPref(p), true, "The pref should be set: " + p); |
|
55 }); |
|
56 Troubleshoot.snapshot(function (snapshot) { |
|
57 let p = snapshot.modifiedPreferences; |
|
58 is(p["javascript.troubleshoot"], true, |
|
59 "The pref should be present because it's whitelisted " + |
|
60 "but not blacklisted."); |
|
61 ok(!("troubleshoot.foo" in p), |
|
62 "The pref should be absent because it's not in the whitelist."); |
|
63 ok(!("javascript.print_to_filename" in p), |
|
64 "The pref should be absent because it's blacklisted."); |
|
65 ok(!("network.proxy.troubleshoot" in p), |
|
66 "The pref should be absent because it's blacklisted."); |
|
67 prefs.forEach(function (p) Services.prefs.deleteBranch(p)); |
|
68 done(); |
|
69 }); |
|
70 }, |
|
71 ]; |
|
72 |
|
73 // This is inspired by JSON Schema, or by the example on its Wikipedia page |
|
74 // anyway. |
|
75 const SNAPSHOT_SCHEMA = { |
|
76 type: "object", |
|
77 required: true, |
|
78 properties: { |
|
79 application: { |
|
80 required: true, |
|
81 type: "object", |
|
82 properties: { |
|
83 name: { |
|
84 required: true, |
|
85 type: "string", |
|
86 }, |
|
87 version: { |
|
88 required: true, |
|
89 type: "string", |
|
90 }, |
|
91 userAgent: { |
|
92 required: true, |
|
93 type: "string", |
|
94 }, |
|
95 vendor: { |
|
96 type: "string", |
|
97 }, |
|
98 supportURL: { |
|
99 type: "string", |
|
100 }, |
|
101 }, |
|
102 }, |
|
103 crashes: { |
|
104 required: false, |
|
105 type: "object", |
|
106 properties: { |
|
107 pending: { |
|
108 required: true, |
|
109 type: "number", |
|
110 }, |
|
111 submitted: { |
|
112 required: true, |
|
113 type: "array", |
|
114 items: { |
|
115 type: "object", |
|
116 properties: { |
|
117 id: { |
|
118 required: true, |
|
119 type: "string", |
|
120 }, |
|
121 date: { |
|
122 required: true, |
|
123 type: "number", |
|
124 }, |
|
125 pending: { |
|
126 required: true, |
|
127 type: "boolean", |
|
128 }, |
|
129 }, |
|
130 }, |
|
131 }, |
|
132 }, |
|
133 }, |
|
134 extensions: { |
|
135 required: true, |
|
136 type: "array", |
|
137 items: { |
|
138 type: "object", |
|
139 properties: { |
|
140 name: { |
|
141 required: true, |
|
142 type: "string", |
|
143 }, |
|
144 version: { |
|
145 required: true, |
|
146 type: "string", |
|
147 }, |
|
148 id: { |
|
149 required: true, |
|
150 type: "string", |
|
151 }, |
|
152 isActive: { |
|
153 required: true, |
|
154 type: "boolean", |
|
155 }, |
|
156 }, |
|
157 }, |
|
158 }, |
|
159 modifiedPreferences: { |
|
160 required: true, |
|
161 type: "object", |
|
162 }, |
|
163 graphics: { |
|
164 required: true, |
|
165 type: "object", |
|
166 properties: { |
|
167 numTotalWindows: { |
|
168 required: true, |
|
169 type: "number", |
|
170 }, |
|
171 numAcceleratedWindows: { |
|
172 required: true, |
|
173 type: "number", |
|
174 }, |
|
175 windowLayerManagerType: { |
|
176 type: "string", |
|
177 }, |
|
178 windowLayerManagerRemote: { |
|
179 type: "boolean", |
|
180 }, |
|
181 numAcceleratedWindowsMessage: { |
|
182 type: "array", |
|
183 }, |
|
184 adapterDescription: { |
|
185 type: "string", |
|
186 }, |
|
187 adapterVendorID: { |
|
188 type: "string", |
|
189 }, |
|
190 adapterDeviceID: { |
|
191 type: "string", |
|
192 }, |
|
193 adapterRAM: { |
|
194 type: "string", |
|
195 }, |
|
196 adapterDrivers: { |
|
197 type: "string", |
|
198 }, |
|
199 driverVersion: { |
|
200 type: "string", |
|
201 }, |
|
202 driverDate: { |
|
203 type: "string", |
|
204 }, |
|
205 adapterDescription2: { |
|
206 type: "string", |
|
207 }, |
|
208 adapterVendorID2: { |
|
209 type: "string", |
|
210 }, |
|
211 adapterDeviceID2: { |
|
212 type: "string", |
|
213 }, |
|
214 adapterRAM2: { |
|
215 type: "string", |
|
216 }, |
|
217 adapterDrivers2: { |
|
218 type: "string", |
|
219 }, |
|
220 driverVersion2: { |
|
221 type: "string", |
|
222 }, |
|
223 driverDate2: { |
|
224 type: "string", |
|
225 }, |
|
226 isGPU2Active: { |
|
227 type: "boolean", |
|
228 }, |
|
229 direct2DEnabled: { |
|
230 type: "boolean", |
|
231 }, |
|
232 directWriteEnabled: { |
|
233 type: "boolean", |
|
234 }, |
|
235 directWriteVersion: { |
|
236 type: "string", |
|
237 }, |
|
238 clearTypeParameters: { |
|
239 type: "string", |
|
240 }, |
|
241 webglRenderer: { |
|
242 type: "string", |
|
243 }, |
|
244 info: { |
|
245 type: "object", |
|
246 }, |
|
247 failures: { |
|
248 type: "array", |
|
249 items: { |
|
250 type: "string", |
|
251 }, |
|
252 }, |
|
253 direct2DEnabledMessage: { |
|
254 type: "array", |
|
255 }, |
|
256 webglRendererMessage: { |
|
257 type: "array", |
|
258 }, |
|
259 }, |
|
260 }, |
|
261 javaScript: { |
|
262 required: true, |
|
263 type: "object", |
|
264 properties: { |
|
265 incrementalGCEnabled: { |
|
266 type: "boolean", |
|
267 }, |
|
268 }, |
|
269 }, |
|
270 accessibility: { |
|
271 required: true, |
|
272 type: "object", |
|
273 properties: { |
|
274 isActive: { |
|
275 required: true, |
|
276 type: "boolean", |
|
277 }, |
|
278 forceDisabled: { |
|
279 type: "number", |
|
280 }, |
|
281 }, |
|
282 }, |
|
283 libraryVersions: { |
|
284 required: true, |
|
285 type: "object", |
|
286 properties: { |
|
287 NSPR: { |
|
288 required: true, |
|
289 type: "object", |
|
290 properties: { |
|
291 minVersion: { |
|
292 required: true, |
|
293 type: "string", |
|
294 }, |
|
295 version: { |
|
296 required: true, |
|
297 type: "string", |
|
298 }, |
|
299 }, |
|
300 }, |
|
301 NSS: { |
|
302 required: true, |
|
303 type: "object", |
|
304 properties: { |
|
305 minVersion: { |
|
306 required: true, |
|
307 type: "string", |
|
308 }, |
|
309 version: { |
|
310 required: true, |
|
311 type: "string", |
|
312 }, |
|
313 }, |
|
314 }, |
|
315 NSSUTIL: { |
|
316 required: true, |
|
317 type: "object", |
|
318 properties: { |
|
319 minVersion: { |
|
320 required: true, |
|
321 type: "string", |
|
322 }, |
|
323 version: { |
|
324 required: true, |
|
325 type: "string", |
|
326 }, |
|
327 }, |
|
328 }, |
|
329 NSSSSL: { |
|
330 required: true, |
|
331 type: "object", |
|
332 properties: { |
|
333 minVersion: { |
|
334 required: true, |
|
335 type: "string", |
|
336 }, |
|
337 version: { |
|
338 required: true, |
|
339 type: "string", |
|
340 }, |
|
341 }, |
|
342 }, |
|
343 NSSSMIME: { |
|
344 required: true, |
|
345 type: "object", |
|
346 properties: { |
|
347 minVersion: { |
|
348 required: true, |
|
349 type: "string", |
|
350 }, |
|
351 version: { |
|
352 required: true, |
|
353 type: "string", |
|
354 }, |
|
355 }, |
|
356 }, |
|
357 }, |
|
358 }, |
|
359 userJS: { |
|
360 required: true, |
|
361 type: "object", |
|
362 properties: { |
|
363 exists: { |
|
364 required: true, |
|
365 type: "boolean", |
|
366 }, |
|
367 }, |
|
368 }, |
|
369 experiments: { |
|
370 type: "array", |
|
371 }, |
|
372 }, |
|
373 }; |
|
374 |
|
375 /** |
|
376 * Throws an Error if obj doesn't conform to schema. That way you get a nice |
|
377 * error message and a stack to help you figure out what went wrong, which you |
|
378 * wouldn't get if this just returned true or false instead. There's still |
|
379 * room for improvement in communicating validation failures, however. |
|
380 * |
|
381 * @param obj The object to validate. |
|
382 * @param schema The schema that obj should conform to. |
|
383 */ |
|
384 function validateObject(obj, schema) { |
|
385 if (obj === undefined && !schema.required) |
|
386 return; |
|
387 if (typeof(schema.type) != "string") |
|
388 throw schemaErr("'type' must be a string", schema); |
|
389 if (objType(obj) != schema.type) |
|
390 throw validationErr("Object is not of the expected type", obj, schema); |
|
391 let validatorFnName = "validateObject_" + schema.type; |
|
392 if (!(validatorFnName in this)) |
|
393 throw schemaErr("Validator function not defined for type", schema); |
|
394 this[validatorFnName](obj, schema); |
|
395 } |
|
396 |
|
397 function validateObject_object(obj, schema) { |
|
398 if (typeof(schema.properties) != "object") |
|
399 // Don't care what obj's properties are. |
|
400 return; |
|
401 // First check that all the schema's properties match the object. |
|
402 for (let prop in schema.properties) |
|
403 validateObject(obj[prop], schema.properties[prop]); |
|
404 // Now check that the object doesn't have any properties not in the schema. |
|
405 for (let prop in obj) |
|
406 if (!(prop in schema.properties)) |
|
407 throw validationErr("Object has property not in schema", obj, schema); |
|
408 } |
|
409 |
|
410 function validateObject_array(array, schema) { |
|
411 if (typeof(schema.items) != "object") |
|
412 // Don't care what the array's elements are. |
|
413 return; |
|
414 array.forEach(function (elt) validateObject(elt, schema.items)); |
|
415 } |
|
416 |
|
417 function validateObject_string(str, schema) {} |
|
418 function validateObject_boolean(bool, schema) {} |
|
419 function validateObject_number(num, schema) {} |
|
420 |
|
421 function validationErr(msg, obj, schema) { |
|
422 return new Error("Validation error: " + msg + |
|
423 ": object=" + JSON.stringify(obj) + |
|
424 ", schema=" + JSON.stringify(schema)); |
|
425 } |
|
426 |
|
427 function schemaErr(msg, schema) { |
|
428 return new Error("Schema error: " + msg + ": " + JSON.stringify(schema)); |
|
429 } |
|
430 |
|
431 function objType(obj) { |
|
432 let type = typeof(obj); |
|
433 if (type != "object") |
|
434 return type; |
|
435 if (Array.isArray(obj)) |
|
436 return "array"; |
|
437 if (obj === null) |
|
438 return "null"; |
|
439 return type; |
|
440 } |