]> git.donarmstrong.com Git - dactyl.git/blob - common/tests/functional/shared-modules/software-update.js
Initial import of 1.0~b6
[dactyl.git] / common / tests / functional / shared-modules / software-update.js
1 /* * ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Mozilla Public License Version
5  * 1.1 (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  * http://www.mozilla.org/MPL/
8  *
9  * Software distributed under the License is distributed on an "AS IS" basis,
10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11  * for the specific language governing rights and limitations under the
12  * License.
13  *
14  * The Original Code is MozMill Test code.
15  *
16  * The Initial Developer of the Original Code is Mozilla Foundation.
17  * Portions created by the Initial Developer are Copyright (C) 2009
18  * the Initial Developer. All Rights Reserved.
19  *
20  * Contributor(s):
21  *   Henrik Skupin <hskupin@mozilla.com>
22  *
23  * Alternatively, the contents of this file may be used under the terms of
24  * either the GNU General Public License Version 2 or later (the "GPL"), or
25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26  * in which case the provisions of the GPL or the LGPL are applicable instead
27  * of those above. If you wish to allow use of your version of this file only
28  * under the terms of either the GPL or the LGPL, and not to allow others to
29  * use your version of this file under the terms of the MPL, indicate your
30  * decision by deleting the provisions above and replace them with the notice
31  * and other provisions required by the GPL or the LGPL. If you do not delete
32  * the provisions above, a recipient may use your version of this file under
33  * the terms of any one of the MPL, the GPL or the LGPL.
34  *
35  * **** END LICENSE BLOCK ***** */
36
37 /**
38  * @fileoverview
39  * The SoftwareUpdateAPI adds support for an easy access to the update process.
40  */
41
42 // Include required modules
43 var prefs = require("prefs");
44 var utils = require("utils");
45
46 const gTimeoutUpdateCheck     = 10000;
47 const gTimeoutUpdateDownload  = 360000;
48
49 const PREF_DISABLED_ADDONS = "extensions.disabledAddons";
50
51 // Helper lookup constants for elements of the software update dialog
52 const WIZARD = '/id("updates")';
53 const WIZARD_BUTTONS = WIZARD + '/anon({"anonid":"Buttons"})';
54 const WIZARD_DECK = WIZARD  + '/anon({"anonid":"Deck"})';
55
56 const WIZARD_PAGES = {
57   dummy: 'dummy',
58   checking: 'checking',
59   pluginUpdatesFound: 'pluginupdatesfound',
60   noUpdatesFound: 'noupdatesfound',
61   manualUpdate: 'manualUpdate',
62   incompatibleCheck: 'incompatibleCheck',
63   updatesFoundBasic: 'updatesfoundbasic',
64   updatesFoundBillboard: 'updatesfoundbillboard',
65   license: 'license',
66   incompatibleList: 'incompatibleList',
67   downloading: 'downloading',
68   errors: 'errors',
69   errorPatching: 'errorpatching',
70   finished: 'finished',
71   finishedBackground: 'finishedBackground',
72   installed: 'installed'
73 }
74
75 // On Mac there is another DOM structure used as on Windows and Linux
76 if (mozmill.isMac) {
77   var WIZARD_BUTTONS_BOX = WIZARD_BUTTONS +
78                              '/anon({"flex":"1"})/{"class":"wizard-buttons-btm"}/';
79   var WIZARD_BUTTON = {
80           back: '{"dlgtype":"back"}',
81           next: '{"dlgtype":"next"}',
82           cancel: '{"dlgtype":"cancel"}',
83           finish: '{"dlgtype":"finish"}',
84           extra1: '{"dlgtype":"extra1"}',
85           extra2: '{"dlgtype":"extra2"}'
86         }
87 } else {
88   var WIZARD_BUTTONS_BOX = WIZARD_BUTTONS +
89                        '/anon({"flex":"1"})/{"class":"wizard-buttons-box-2"}/';
90   var WIZARD_BUTTON = {
91     back: '{"dlgtype":"back"}',
92     next: 'anon({"anonid":"WizardButtonDeck"})/[1]/{"dlgtype":"next"}',
93     cancel: '{"dlgtype":"cancel"}',
94     finish: 'anon({"anonid":"WizardButtonDeck"})/[0]/{"dlgtype":"finish"}',
95     extra1: '{"dlgtype":"extra1"}',
96     extra2: '{"dlgtype":"extra2"}'
97   }
98 }
99
100 /**
101  * Constructor for software update class
102  */
103 function softwareUpdate() {
104   this._controller = null;
105   this._wizard = null;
106   this._downloadDuration = -1;
107
108   this._aus = Cc["@mozilla.org/updates/update-service;1"].
109               getService(Ci.nsIApplicationUpdateService);
110   this._ums = Cc["@mozilla.org/updates/update-manager;1"].
111               getService(Ci.nsIUpdateManager);
112   this._vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
113              getService(Ci.nsIVersionComparator);
114 }
115
116 /**
117  * Class for software updates
118  */
119 softwareUpdate.prototype = {
120   /**
121    * Returns the active update
122    *
123    * @returns The currently selected update
124    * @type nsIUpdate
125    */
126   get activeUpdate() {
127     return this._ums.activeUpdate;
128   },
129
130   /**
131    * Check if the user has permissions to run the software update
132    *
133    * @returns Status if the user has the permissions.
134    * @type {boolean}
135    */
136   get allowed() {
137     return this._aus.canCheckForUpdates && this._aus.canApplyUpdates;
138   },
139
140   /**
141    * Returns information of the current build version
142    */
143   get buildInfo() {
144     return {
145       buildid : utils.appInfo.buildID,
146       disabled_addons : prefs.preferences.getPref(PREF_DISABLED_ADDONS, ''),
147       locale : utils.appInfo.locale,
148       user_agent : utils.appInfo.userAgent,
149       version : utils.appInfo.version
150     };
151   },
152
153   /**
154    * Returns the current update channel
155    */
156   get channel() {
157     return prefs.preferences.getPref('app.update.channel', '');
158   },
159
160   /**
161    * Get the controller of the associated engine manager dialog
162    *
163    * @returns Controller of the browser window
164    * @type MozMillController
165    */
166   get controller() {
167     return this._controller;
168   },
169
170   /**
171    * Returns the current step of the software update dialog wizard
172    */
173   get currentPage() {
174     return this._wizard.getNode().getAttribute('currentpageid');
175   },
176
177   /**
178    * Returns true if the offered update is a complete update
179    */
180   get isCompleteUpdate() {
181     // Throw when isCompleteUpdate is called without an update. This should
182     // never happen except if the test is incorrectly written.
183     if (!this.activeUpdate)
184       throw new Error(arguments.callee.name + ": isCompleteUpdate called " +
185                       "when activeUpdate is null!");
186
187     var patchCount = this.activeUpdate.patchCount;
188     if ((patchCount < 1) || (patchCount > 2)) {
189       throw new Error("An update must have one or two patches included.");
190     }
191
192     // XXX: After Firefox 4 has been released and we do not have to test any
193     // beta release anymore uncomment out the following code
194 //    if (this.activeUpdate.patchCount == 2) {
195 //      var patch0URL = this.activeUpdate.getPatchAt(0).URL;
196 //      var patch1URL = this.activeUpdate.getPatchAt(1).URL;
197        // Test that the update snippet created by releng doesn't have the same
198        // url for both patches (bug 514040).
199 //      controller.assertJS("subject.patch0URL != subject.patch1URL",
200 //                          {patch0URL: patch0URL, patch1URL: patch1URL});
201 //    }
202
203     return (this.activeUpdate.selectedPatch.type  == "complete");
204   },
205
206    /**
207    * Returns information of the active update in the queue.
208    */
209   get patchInfo() {
210     this._controller.assert(function() {
211       return !!this.activeUpdate;
212     }, "An active update is in the queue.", this);
213
214     return {
215       buildid : this.activeUpdate.buildID,
216       channel : this.channel,
217       is_complete : this.isCompleteUpdate,
218       size : this.activeUpdate.selectedPatch.size,
219       type : this.activeUpdate.type,
220       url : this.activeUpdate.selectedPatch.finalURL || "n/a",
221       download_duration : this._downloadDuration,
222       version : this.activeUpdate.version
223     };
224   },
225
226   /**
227    * Returns the update type (minor or major)
228    *
229    * @returns The update type
230    */
231   get updateType() {
232     return this.activeUpdate.type;
233   },
234
235   /**
236    * Check if updates have been found
237    */
238   get updatesFound() {
239     return this.currentPage.indexOf("updatesfound") == 0;
240   },
241
242   /**
243    * Checks if an update has been applied correctly
244    *
245    * @param {object} updateData
246    *        All the data collected during the update process
247    */
248   assertUpdateApplied : function softwareUpdate_assertUpdateApplied(updateData) {
249     // Get the information from the last update
250     var info = updateData.updates[updateData.updateIndex];
251
252     // The upgraded version should be identical with the version given by
253     // the update and we shouldn't have run a downgrade
254     var check = this._vc.compare(info.build_post.version, info.build_pre.version);
255     this._controller.assert(function () {
256       return check >= 0;
257     }, "The version number of the upgraded build is higher or equal.");
258
259     // The build id should be identical with the one from the update
260     this._controller.assert(function () {
261       return info.build_post.buildid === info.patch.buildid;
262     }, "The build id is equal to the build id of the update.");
263
264     // If a target build id has been given, check if it matches the updated build
265     info.target_buildid = updateData.targetBuildID;
266     if (info.target_buildid) {
267       this._controller.assert(function () {
268         return info.build_post.buildid === info.target_buildid;
269       }, "Target build id matches id of updated build.");
270     }
271
272     // An upgrade should not change the builds locale
273     this._controller.assert(function () {
274       return info.build_post.locale === info.build_pre.locale;
275     }, "The locale of the updated build is identical to the original locale.");
276
277     // Check that no application-wide add-ons have been disabled
278     this._controller.assert(function () {
279       return info.build_post.disabled_addons === info.build_pre.disabled_addons;
280     }, "No application-wide add-ons have been disabled by the update.");
281   },
282
283   /**
284    * Close the software update dialog
285    */
286   closeDialog: function softwareUpdate_closeDialog() {
287     if (this._controller) {
288       this._controller.keypress(null, "VK_ESCAPE", {});
289       this._controller.sleep(500);
290       this._controller = null;
291       this._wizard = null;
292     }
293   },
294
295   /**
296    * Download the update of the given channel and type
297    * @param {string} channel
298    *        Update channel to use
299    * @param {boolean} waitForFinish
300    *        Sets if the function should wait until the download has been finished
301    * @param {number} timeout
302    *        Timeout the download has to stop
303    */
304   download : function softwareUpdate_download(channel, waitForFinish, timeout) {
305     waitForFinish = waitForFinish ? waitForFinish : true;
306
307     // Check that the correct channel has been set
308     this._controller.assert(function() {
309       return channel == this.channel;
310     }, "The current update channel is identical to the specified one.", this);
311
312     // Retrieve the timestamp, so we can measure the duration of the download
313     var startTime = Date.now();
314
315     // Click the next button
316     var next = this.getElement({type: "button", subtype: "next"});
317     this._controller.click(next);
318
319     // Wait for the download page - if it fails the update was already cached
320     try {
321       this.waitForWizardPage(WIZARD_PAGES.downloading);
322
323       if (waitForFinish)
324         this.waitforDownloadFinished(timeout);
325     } catch (ex) {
326       this.waitForWizardPage(WIZARD_PAGES.finished);
327     }
328
329     // Calculate the duration in ms
330     this._downloadDuration = Date.now() - startTime;
331   },
332
333   /**
334    * Update the update.status file and set the status to 'failed:6'
335    */
336   forceFallback : function softwareUpdate_forceFallback() {
337     var dirService = Cc["@mozilla.org/file/directory_service;1"].
338                      getService(Ci.nsIProperties);
339
340     var updateDir;
341     var updateStatus;
342
343     // Check the global update folder first
344     try {
345       updateDir = dirService.get("UpdRootD", Ci.nsIFile);
346       updateDir.append("updates");
347       updateDir.append("0");
348
349       updateStatus = updateDir.clone();
350       updateStatus.append("update.status");
351     } catch (ex) {
352     }
353
354     if (updateStatus == undefined || !updateStatus.exists()) {
355       updateDir = dirService.get("XCurProcD", Ci.nsIFile);
356       updateDir.append("updates");
357       updateDir.append("0");
358
359       updateStatus = updateDir.clone();
360       updateStatus.append("update.status");
361     }
362
363     var foStream = Cc["@mozilla.org/network/file-output-stream;1"].
364                    createInstance(Ci.nsIFileOutputStream);
365     var status = "failed: 6\n";
366     foStream.init(updateStatus, 0x02 | 0x08 | 0x20, -1, 0);
367     foStream.write(status, status.length);
368     foStream.close();
369   },
370
371   /**
372    * Gets all the needed external DTD urls as an array
373    *
374    * @returns Array of external DTD urls
375    * @type [string]
376    */
377   getDtds : function softwareUpdate_getDtds() {
378     var dtds = ["chrome://mozapps/locale/update/history.dtd",
379                 "chrome://mozapps/locale/update/updates.dtd"]
380     return dtds;
381   },
382
383   /**
384    * Retrieve an UI element based on the given spec
385    *
386    * @param {object} spec
387    *        Information of the UI element which should be retrieved
388    *        type: General type information
389    *        subtype: Specific element or property
390    *        value: Value of the element or property
391    * @returns Element which has been created
392    * @type {ElemBase}
393    */
394   getElement : function softwareUpdate_getElement(spec) {
395     var elem = null;
396
397     switch(spec.type) {
398       /**
399        * subtype: subtype to match
400        * value: value to match
401        */
402       case "button":
403         elem = new elementslib.Lookup(this._controller.window.document,
404                                       WIZARD_BUTTONS_BOX + WIZARD_BUTTON[spec.subtype]);
405         break;
406       case "wizard":
407         elem = new elementslib.Lookup(this._controller.window.document, WIZARD);
408         break;
409       case "wizard_page":
410         elem = new elementslib.Lookup(this._controller.window.document, WIZARD_DECK +
411                                       '/id(' + spec.subtype + ')');
412         break;
413       case "download_progress":
414         elem = new elementslib.ID(this._controller.window.document, "downloadProgress");
415         break;
416       default:
417         throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
418     }
419
420     return elem;
421   },
422
423   /**
424    * Open software update dialog
425    *
426    * @param {MozMillController} browserController
427    *        Mozmill controller of the browser window
428    */
429   openDialog: function softwareUpdate_openDialog(browserController) {
430     // XXX: After Firefox 4 has been released and we do not have to test any
431     // beta release anymore uncomment out the following code
432
433     // With version >= 4.0b7pre the update dialog is reachable from within the
434     // about window now.
435     var appVersion = utils.appInfo.version;
436
437     if (this._vc.compare(appVersion, "4.0b7pre") >= 0) {
438       // XXX: We can't open the about window, otherwise a parallel download of
439       // the update will let us fallback to a complete one all the time
440
441       // Open the about window and check the update button
442       //var aboutItem = new elementslib.Elem(browserController.menus.helpMenu.aboutName);
443       //browserController.click(aboutItem);
444       //
445       //utils.handleWindow("type", "Browser:About", function(controller) {
446       //  // XXX: Bug 599290 - Check for updates has been completely relocated
447       //  // into the about window. We can't check the in-about ui yet.
448       //  var updateButton = new elementslib.ID(controller.window.document,
449       //                                        "checkForUpdatesButton");
450       //  //controller.click(updateButton);
451       //  controller.waitForElement(updateButton, gTimeout);
452       //});
453
454       // For now just call the old ui until we have support for the about window.
455       var updatePrompt = Cc["@mozilla.org/updates/update-prompt;1"].
456                          createInstance(Ci.nsIUpdatePrompt);
457       updatePrompt.checkForUpdates();
458     } else {
459       // For builds <4.0b7pre
460       updateItem = new elementslib.Elem(browserController.menus.helpMenu.checkForUpdates);
461       browserController.click(updateItem);
462     }
463
464     this.waitForDialogOpen(browserController);
465   },
466
467   /**
468    * Wait that check for updates has been finished
469    * @param {number} timeout
470    */
471   waitForCheckFinished : function softwareUpdate_waitForCheckFinished(timeout) {
472     timeout = timeout ? timeout : gTimeoutUpdateCheck;
473
474     this._controller.waitFor(function() {
475       return this.currentPage != WIZARD_PAGES.checking;
476     }, "Check for updates has been completed.", timeout, null, this);
477   },
478
479   /**
480    * Wait for the software update dialog
481    *
482    * @param {MozMillController} browserController
483    *        Mozmill controller of the browser window
484    */
485   waitForDialogOpen : function softwareUpdate_waitForDialogOpen(browserController) {
486     this._controller = utils.handleWindow("type", "Update:Wizard",
487                                           undefined, false);
488     this._wizard = this.getElement({type: "wizard"});
489
490     this._controller.waitFor(function () {
491       return this.currentPage !== WIZARD_PAGES.dummy;
492     }, "Dummy wizard page has been made invisible - got " + this.currentPage,
493       undefined, undefined, this);
494
495     this._controller.window.focus();
496   },
497
498   /**
499    * Wait until the download has been finished
500    *
501    * @param {number} timeout
502    *        Timeout the download has to stop
503    */
504   waitforDownloadFinished: function softwareUpdate_waitForDownloadFinished(timeout) {
505     timeout = timeout ? timeout : gTimeoutUpdateDownload;
506
507     var progress =  this.getElement({type: "download_progress"});
508     this._controller.waitFor(function () {
509       return progress.getNode().value === '100';
510     }, "Update has been finished downloading.", timeout);
511
512     this.waitForWizardPage(WIZARD_PAGES.finished);
513   },
514
515   /**
516    * Waits for the given page of the update dialog wizard
517    */
518   waitForWizardPage : function softwareUpdate_waitForWizardPage(step) {
519     this._controller.waitFor(function () {
520       return this.currentPage === step;
521     }, "New wizard page has been selected - got " + this.currentPage +
522       ", expected " + step, undefined, undefined, this);
523   }
524 }
525
526 // Export of variables
527 exports.WIZARD_PAGES = WIZARD_PAGES;
528
529 // Export of classes
530 exports.softwareUpdate = softwareUpdate;