1 /* * ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
14 * The Original Code is MozMill Test code.
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.
21 * Henrik Skupin <hskupin@mozilla.com>
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.
35 * **** END LICENSE BLOCK ***** */
39 * The SoftwareUpdateAPI adds support for an easy access to the update process.
42 // Include required modules
43 var prefs = require("prefs");
44 var utils = require("utils");
46 const gTimeoutUpdateCheck = 10000;
47 const gTimeoutUpdateDownload = 360000;
49 const PREF_DISABLED_ADDONS = "extensions.disabledAddons";
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"})';
56 const WIZARD_PAGES = {
59 pluginUpdatesFound: 'pluginupdatesfound',
60 noUpdatesFound: 'noupdatesfound',
61 manualUpdate: 'manualUpdate',
62 incompatibleCheck: 'incompatibleCheck',
63 updatesFoundBasic: 'updatesfoundbasic',
64 updatesFoundBillboard: 'updatesfoundbillboard',
66 incompatibleList: 'incompatibleList',
67 downloading: 'downloading',
69 errorPatching: 'errorpatching',
71 finishedBackground: 'finishedBackground',
72 installed: 'installed'
75 // On Mac there is another DOM structure used as on Windows and Linux
77 var WIZARD_BUTTONS_BOX = WIZARD_BUTTONS +
78 '/anon({"flex":"1"})/{"class":"wizard-buttons-btm"}/';
80 back: '{"dlgtype":"back"}',
81 next: '{"dlgtype":"next"}',
82 cancel: '{"dlgtype":"cancel"}',
83 finish: '{"dlgtype":"finish"}',
84 extra1: '{"dlgtype":"extra1"}',
85 extra2: '{"dlgtype":"extra2"}'
88 var WIZARD_BUTTONS_BOX = WIZARD_BUTTONS +
89 '/anon({"flex":"1"})/{"class":"wizard-buttons-box-2"}/';
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"}'
101 * Constructor for software update class
103 function softwareUpdate() {
104 this._controller = null;
106 this._downloadDuration = -1;
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);
117 * Class for software updates
119 softwareUpdate.prototype = {
121 * Returns the active update
123 * @returns The currently selected update
127 return this._ums.activeUpdate;
131 * Check if the user has permissions to run the software update
133 * @returns Status if the user has the permissions.
137 return this._aus.canCheckForUpdates && this._aus.canApplyUpdates;
141 * Returns information of the current build version
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
154 * Returns the current update channel
157 return prefs.preferences.getPref('app.update.channel', '');
161 * Get the controller of the associated engine manager dialog
163 * @returns Controller of the browser window
164 * @type MozMillController
167 return this._controller;
171 * Returns the current step of the software update dialog wizard
174 return this._wizard.getNode().getAttribute('currentpageid');
178 * Returns true if the offered update is a complete update
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!");
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.");
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});
203 return (this.activeUpdate.selectedPatch.type == "complete");
207 * Returns information of the active update in the queue.
210 this._controller.assert(function() {
211 return !!this.activeUpdate;
212 }, "An active update is in the queue.", this);
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
227 * Returns the update type (minor or major)
229 * @returns The update type
232 return this.activeUpdate.type;
236 * Check if updates have been found
239 return this.currentPage.indexOf("updatesfound") == 0;
243 * Checks if an update has been applied correctly
245 * @param {object} updateData
246 * All the data collected during the update process
248 assertUpdateApplied : function softwareUpdate_assertUpdateApplied(updateData) {
249 // Get the information from the last update
250 var info = updateData.updates[updateData.updateIndex];
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 () {
257 }, "The version number of the upgraded build is higher or equal.");
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.");
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.");
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.");
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.");
284 * Close the software update dialog
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;
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
304 download : function softwareUpdate_download(channel, waitForFinish, timeout) {
305 waitForFinish = waitForFinish ? waitForFinish : true;
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);
312 // Retrieve the timestamp, so we can measure the duration of the download
313 var startTime = Date.now();
315 // Click the next button
316 var next = this.getElement({type: "button", subtype: "next"});
317 this._controller.click(next);
319 // Wait for the download page - if it fails the update was already cached
321 this.waitForWizardPage(WIZARD_PAGES.downloading);
324 this.waitforDownloadFinished(timeout);
326 this.waitForWizardPage(WIZARD_PAGES.finished);
329 // Calculate the duration in ms
330 this._downloadDuration = Date.now() - startTime;
334 * Update the update.status file and set the status to 'failed:6'
336 forceFallback : function softwareUpdate_forceFallback() {
337 var dirService = Cc["@mozilla.org/file/directory_service;1"].
338 getService(Ci.nsIProperties);
343 // Check the global update folder first
345 updateDir = dirService.get("UpdRootD", Ci.nsIFile);
346 updateDir.append("updates");
347 updateDir.append("0");
349 updateStatus = updateDir.clone();
350 updateStatus.append("update.status");
354 if (updateStatus == undefined || !updateStatus.exists()) {
355 updateDir = dirService.get("XCurProcD", Ci.nsIFile);
356 updateDir.append("updates");
357 updateDir.append("0");
359 updateStatus = updateDir.clone();
360 updateStatus.append("update.status");
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);
372 * Gets all the needed external DTD urls as an array
374 * @returns Array of external DTD urls
377 getDtds : function softwareUpdate_getDtds() {
378 var dtds = ["chrome://mozapps/locale/update/history.dtd",
379 "chrome://mozapps/locale/update/updates.dtd"]
384 * Retrieve an UI element based on the given spec
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
394 getElement : function softwareUpdate_getElement(spec) {
399 * subtype: subtype to match
400 * value: value to match
403 elem = new elementslib.Lookup(this._controller.window.document,
404 WIZARD_BUTTONS_BOX + WIZARD_BUTTON[spec.subtype]);
407 elem = new elementslib.Lookup(this._controller.window.document, WIZARD);
410 elem = new elementslib.Lookup(this._controller.window.document, WIZARD_DECK +
411 '/id(' + spec.subtype + ')');
413 case "download_progress":
414 elem = new elementslib.ID(this._controller.window.document, "downloadProgress");
417 throw new Error(arguments.callee.name + ": Unknown element type - " + spec.type);
424 * Open software update dialog
426 * @param {MozMillController} browserController
427 * Mozmill controller of the browser window
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
433 // With version >= 4.0b7pre the update dialog is reachable from within the
435 var appVersion = utils.appInfo.version;
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
441 // Open the about window and check the update button
442 //var aboutItem = new elementslib.Elem(browserController.menus.helpMenu.aboutName);
443 //browserController.click(aboutItem);
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);
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();
459 // For builds <4.0b7pre
460 updateItem = new elementslib.Elem(browserController.menus.helpMenu.checkForUpdates);
461 browserController.click(updateItem);
464 this.waitForDialogOpen(browserController);
468 * Wait that check for updates has been finished
469 * @param {number} timeout
471 waitForCheckFinished : function softwareUpdate_waitForCheckFinished(timeout) {
472 timeout = timeout ? timeout : gTimeoutUpdateCheck;
474 this._controller.waitFor(function() {
475 return this.currentPage != WIZARD_PAGES.checking;
476 }, "Check for updates has been completed.", timeout, null, this);
480 * Wait for the software update dialog
482 * @param {MozMillController} browserController
483 * Mozmill controller of the browser window
485 waitForDialogOpen : function softwareUpdate_waitForDialogOpen(browserController) {
486 this._controller = utils.handleWindow("type", "Update:Wizard",
488 this._wizard = this.getElement({type: "wizard"});
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);
495 this._controller.window.focus();
499 * Wait until the download has been finished
501 * @param {number} timeout
502 * Timeout the download has to stop
504 waitforDownloadFinished: function softwareUpdate_waitForDownloadFinished(timeout) {
505 timeout = timeout ? timeout : gTimeoutUpdateDownload;
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);
512 this.waitForWizardPage(WIZARD_PAGES.finished);
516 * Waits for the given page of the update dialog wizard
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);
526 // Export of variables
527 exports.WIZARD_PAGES = WIZARD_PAGES;
530 exports.softwareUpdate = softwareUpdate;