forked from mirrors/gecko-dev
		
	There's quite a few changes in here. At a high level, all we're trying to do is to replace the old update popup with a less intrusive and more modern doorhanger (set of doorhangers) for various update failure conditions. MozReview-Commit-ID: 24sESMTosNX --HG-- extra : rebase_source : ee0c1e00fe3f99e16388f0de17274ff97a3b9fcf
		
			
				
	
	
		
			360 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			360 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
Cu.import("resource://gre/modules/FileUtils.jsm");
 | 
						|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 | 
						|
 | 
						|
const IS_MACOSX = ("nsILocalFileMac" in Ci);
 | 
						|
const IS_WIN = ("@mozilla.org/windows-registry-key;1" in Cc);
 | 
						|
 | 
						|
const BIN_SUFFIX = (IS_WIN ? ".exe" : "");
 | 
						|
const FILE_UPDATER_BIN = "updater" + (IS_MACOSX ? ".app" : BIN_SUFFIX);
 | 
						|
const FILE_UPDATER_BIN_BAK = FILE_UPDATER_BIN + ".bak";
 | 
						|
 | 
						|
let gRembemberedPrefs = [];
 | 
						|
 | 
						|
const DATA_URI_SPEC =  "chrome://mochitests/content/browser/browser/base/content/test/appUpdate/";
 | 
						|
 | 
						|
var DEBUG_AUS_TEST = true;
 | 
						|
var gUseTestUpdater = false;
 | 
						|
 | 
						|
const LOG_FUNCTION = info;
 | 
						|
 | 
						|
/* import-globals-from testConstants.js */
 | 
						|
Services.scriptloader.loadSubScript(DATA_URI_SPEC + "testConstants.js", this);
 | 
						|
/* import-globals-from ../../../../../toolkit/mozapps/update/tests/data/shared.js */
 | 
						|
Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this);
 | 
						|
 | 
						|
var gURLData = URL_HOST + "/" + REL_PATH_DATA;
 | 
						|
const URL_MANUAL_UPDATE = gURLData + "downloadPage.html";
 | 
						|
 | 
						|
const NOTIFICATIONS = [
 | 
						|
  "update-available",
 | 
						|
  "update-manual",
 | 
						|
  "update-restart"
 | 
						|
];
 | 
						|
 | 
						|
/**
 | 
						|
 * Delay for a very short period. Useful for moving the code after this
 | 
						|
 * to the back of the event loop.
 | 
						|
 *
 | 
						|
 * @return A promise which will resolve after a very short period.
 | 
						|
 */
 | 
						|
function delay() {
 | 
						|
  return new Promise(resolve => executeSoon(resolve));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Gets the update version info for the update url parameters to send to
 | 
						|
 * update.sjs.
 | 
						|
 *
 | 
						|
 * @param  aAppVersion (optional)
 | 
						|
 *         The application version for the update snippet. If not specified the
 | 
						|
 *         current application version will be used.
 | 
						|
 * @return The url parameters for the application and platform version to send
 | 
						|
 *         to update.sjs.
 | 
						|
 */
 | 
						|
function getVersionParams(aAppVersion) {
 | 
						|
  let appInfo = Services.appinfo;
 | 
						|
  return "&appVersion=" + (aAppVersion ? aAppVersion : appInfo.version);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Clean up updates list and the updates directory.
 | 
						|
 */
 | 
						|
function cleanUpUpdates() {
 | 
						|
  gUpdateManager.activeUpdate = null;
 | 
						|
  gUpdateManager.saveUpdates();
 | 
						|
 | 
						|
  removeUpdateDirsAndFiles();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Runs a typical update test. Will set various common prefs for using the
 | 
						|
 * updater doorhanger, runs the provided list of steps, and makes sure
 | 
						|
 * everything is cleaned up afterwards.
 | 
						|
 *
 | 
						|
 * @param  updateParams
 | 
						|
 *         URL-encoded params which will be sent to update.sjs.
 | 
						|
 * @param  checkAttempts
 | 
						|
 *         How many times to check for updates. Useful for testing the UI
 | 
						|
 *         for check failures.
 | 
						|
 * @param  steps
 | 
						|
 *         A list of test steps to perform, specifying expected doorhangers
 | 
						|
 *         and additional validation/cleanup callbacks.
 | 
						|
 * @return A promise which will resolve once all of the steps have been run
 | 
						|
 *         and cleanup has been performed.
 | 
						|
 */
 | 
						|
function runUpdateTest(updateParams, checkAttempts, steps) {
 | 
						|
  return Task.spawn(function*() {
 | 
						|
    registerCleanupFunction(() => {
 | 
						|
      gMenuButtonUpdateBadge.uninit();
 | 
						|
      gMenuButtonUpdateBadge.init();
 | 
						|
      cleanUpUpdates();
 | 
						|
    });
 | 
						|
    yield SpecialPowers.pushPrefEnv({
 | 
						|
      set: [
 | 
						|
        [PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
 | 
						|
        [PREF_APP_UPDATE_ENABLED, true],
 | 
						|
        [PREF_APP_UPDATE_IDLETIME, 0],
 | 
						|
        [PREF_APP_UPDATE_URL_MANUAL, URL_MANUAL_UPDATE],
 | 
						|
        [PREF_APP_UPDATE_LOG, DEBUG_AUS_TEST],
 | 
						|
      ]});
 | 
						|
 | 
						|
    yield setupTestUpdater();
 | 
						|
 | 
						|
    let url = URL_HTTP_UPDATE_SJS +
 | 
						|
              "?" + updateParams +
 | 
						|
              getVersionParams();
 | 
						|
 | 
						|
    setUpdateURL(url);
 | 
						|
 | 
						|
    executeSoon(() => {
 | 
						|
      Task.spawn(function*() {
 | 
						|
        gAUS.checkForBackgroundUpdates();
 | 
						|
        for (var i = 0; i < checkAttempts - 1; i++) {
 | 
						|
          yield waitForEvent("update-error", "check-attempt-failed");
 | 
						|
          gAUS.checkForBackgroundUpdates();
 | 
						|
        }
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    for (let step of steps) {
 | 
						|
      yield processStep(step);
 | 
						|
    }
 | 
						|
 | 
						|
    yield finishTestRestoreUpdaterBackup();
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Runs a test which processes an update. Similar to runUpdateTest.
 | 
						|
 *
 | 
						|
 * @param  updates
 | 
						|
 *         A list of updates to process.
 | 
						|
 * @param  steps
 | 
						|
 *         A list of test steps to perform, specifying expected doorhangers
 | 
						|
 *         and additional validation/cleanup callbacks.
 | 
						|
 * @return A promise which will resolve once all of the steps have been run
 | 
						|
 *         and cleanup has been performed.
 | 
						|
 */
 | 
						|
function runUpdateProcessingTest(updates, steps) {
 | 
						|
  return Task.spawn(function*() {
 | 
						|
    registerCleanupFunction(() => {
 | 
						|
      gMenuButtonUpdateBadge.reset();
 | 
						|
      cleanUpUpdates();
 | 
						|
    });
 | 
						|
 | 
						|
    SpecialPowers.pushPrefEnv({
 | 
						|
      set: [
 | 
						|
        [PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
 | 
						|
        [PREF_APP_UPDATE_ENABLED, true],
 | 
						|
        [PREF_APP_UPDATE_IDLETIME, 0],
 | 
						|
        [PREF_APP_UPDATE_URL_MANUAL, URL_MANUAL_UPDATE],
 | 
						|
        [PREF_APP_UPDATE_LOG, DEBUG_AUS_TEST],
 | 
						|
      ]});
 | 
						|
 | 
						|
    yield setupTestUpdater();
 | 
						|
 | 
						|
    writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
 | 
						|
 | 
						|
    writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
 | 
						|
    writeStatusFile(STATE_FAILED_CRC_ERROR);
 | 
						|
    reloadUpdateManagerData();
 | 
						|
 | 
						|
    testPostUpdateProcessing();
 | 
						|
 | 
						|
    for (let step of steps) {
 | 
						|
      yield processStep(step);
 | 
						|
    }
 | 
						|
 | 
						|
    yield finishTestRestoreUpdaterBackup();
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function processStep({notificationId, button, beforeClick, cleanup}) {
 | 
						|
  return Task.spawn(function*() {
 | 
						|
 | 
						|
    yield BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popupshown");
 | 
						|
    const shownNotification = PanelUI.activeNotification.id;
 | 
						|
 | 
						|
    is(shownNotification, notificationId, "The right notification showed up.");
 | 
						|
    if (shownNotification != notificationId) {
 | 
						|
      if (cleanup) {
 | 
						|
        yield cleanup();
 | 
						|
      }
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let notification = document.getElementById(`PanelUI-${notificationId}-notification`);
 | 
						|
    is(notification.hidden, false, `${notificationId} notification is showing`);
 | 
						|
    if (beforeClick) {
 | 
						|
      yield Task.spawn(beforeClick);
 | 
						|
    }
 | 
						|
 | 
						|
    let buttonEl = document.getAnonymousElementByAttribute(notification, "anonid", button);
 | 
						|
 | 
						|
    buttonEl.click();
 | 
						|
 | 
						|
    if (cleanup) {
 | 
						|
      yield cleanup();
 | 
						|
    }
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Waits for the specified topic and (optionally) status.
 | 
						|
 * @param  topic
 | 
						|
 *         String representing the topic to wait for.
 | 
						|
 * @param  status
 | 
						|
 *         Optional String representing the status on said topic to wait for.
 | 
						|
 * @return A promise which will resolve the first time an event occurs on the
 | 
						|
 *         specified topic, and (optionally) with the specified status.
 | 
						|
 */
 | 
						|
function waitForEvent(topic, status = null) {
 | 
						|
  return new Promise(resolve => Services.obs.addObserver({
 | 
						|
    observe(subject, innerTopic, innerStatus) {
 | 
						|
      if (!status || status == innerStatus) {
 | 
						|
        Services.obs.removeObserver(this, topic);
 | 
						|
        resolve(innerStatus);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }, topic, false))
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Ensures that the "What's new" link with the provided ID is displayed and
 | 
						|
 * matches the url parameter provided. If no URL is provided, it will instead
 | 
						|
 * ensure that the link matches the default link URL.
 | 
						|
 *
 | 
						|
 * @param  id
 | 
						|
 *         The ID of the "What's new" link element.
 | 
						|
 * @param  url (optional)
 | 
						|
 *         The URL to check against. If none is provided, a default will be used.
 | 
						|
 */
 | 
						|
function checkWhatsNewLink(id, url) {
 | 
						|
  let whatsNewLink = document.getElementById(id);
 | 
						|
  is(whatsNewLink.href,
 | 
						|
     url || URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS",
 | 
						|
     "What's new link points to the test_details URL");
 | 
						|
  is(whatsNewLink.hidden, false, "What's new link is not hidden.");
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * For tests that use the test updater restores the backed up real updater if
 | 
						|
 * it exists and tries again on failure since Windows debug builds at times
 | 
						|
 * leave the file in use. After success moveRealUpdater is called to continue
 | 
						|
 * the setup of the test updater. For tests that don't use the test updater
 | 
						|
 * runTest will be called.
 | 
						|
 */
 | 
						|
function setupTestUpdater() {
 | 
						|
  return Task.spawn(function*() {
 | 
						|
    if (gUseTestUpdater) {
 | 
						|
      try {
 | 
						|
        restoreUpdaterBackup();
 | 
						|
      } catch (e) {
 | 
						|
        logTestInfo("Attempt to restore the backed up updater failed... " +
 | 
						|
                    "will try again, Exception: " + e);
 | 
						|
        yield delay();
 | 
						|
        yield setupTestUpdater();
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      yield moveRealUpdater();
 | 
						|
    }
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Backs up the real updater and tries again on failure since Windows debug
 | 
						|
 * builds at times leave the file in use. After success it will call
 | 
						|
 * copyTestUpdater to continue the setup of the test updater.
 | 
						|
 */
 | 
						|
function moveRealUpdater() {
 | 
						|
  return Task.spawn(function*() {
 | 
						|
    try {
 | 
						|
      // Move away the real updater
 | 
						|
      let baseAppDir = getAppBaseDir();
 | 
						|
      let updater = baseAppDir.clone();
 | 
						|
      updater.append(FILE_UPDATER_BIN);
 | 
						|
      updater.moveTo(baseAppDir, FILE_UPDATER_BIN_BAK);
 | 
						|
    } catch (e) {
 | 
						|
      logTestInfo("Attempt to move the real updater out of the way failed... " +
 | 
						|
                  "will try again, Exception: " + e);
 | 
						|
      yield delay();
 | 
						|
      yield moveRealUpdater();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    yield copyTestUpdater();
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Copies the test updater so it can be used by tests and tries again on failure
 | 
						|
 * since Windows debug builds at times leave the file in use. After success it
 | 
						|
 * will call runTest to continue the test.
 | 
						|
 */
 | 
						|
function copyTestUpdater() {
 | 
						|
  return Task.spawn(function*() {
 | 
						|
    try {
 | 
						|
      // Copy the test updater
 | 
						|
      let baseAppDir = getAppBaseDir();
 | 
						|
      let testUpdaterDir = Services.dirsvc.get("CurWorkD", Ci.nsILocalFile);
 | 
						|
      let relPath = REL_PATH_DATA;
 | 
						|
      let pathParts = relPath.split("/");
 | 
						|
      for (let i = 0; i < pathParts.length; ++i) {
 | 
						|
        testUpdaterDir.append(pathParts[i]);
 | 
						|
      }
 | 
						|
 | 
						|
      let testUpdater = testUpdaterDir.clone();
 | 
						|
      testUpdater.append(FILE_UPDATER_BIN);
 | 
						|
      testUpdater.copyToFollowingLinks(baseAppDir, FILE_UPDATER_BIN);
 | 
						|
    } catch (e) {
 | 
						|
      logTestInfo("Attempt to copy the test updater failed... " +
 | 
						|
                  "will try again, Exception: " + e);
 | 
						|
      yield delay();
 | 
						|
      yield copyTestUpdater();
 | 
						|
    }
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Restores the updater that was backed up. This is called in setupTestUpdater
 | 
						|
 * before the backup of the real updater is done in case the previous test
 | 
						|
 * failed to restore the updater, in finishTestDefaultWaitForWindowClosed when
 | 
						|
 * the test has finished, and in test_9999_cleanup.xul after all tests have
 | 
						|
 * finished.
 | 
						|
 */
 | 
						|
function restoreUpdaterBackup() {
 | 
						|
  let baseAppDir = getAppBaseDir();
 | 
						|
  let updater = baseAppDir.clone();
 | 
						|
  let updaterBackup = baseAppDir.clone();
 | 
						|
  updater.append(FILE_UPDATER_BIN);
 | 
						|
  updaterBackup.append(FILE_UPDATER_BIN_BAK);
 | 
						|
  if (updaterBackup.exists()) {
 | 
						|
    if (updater.exists()) {
 | 
						|
      updater.remove(true);
 | 
						|
    }
 | 
						|
    updaterBackup.moveTo(baseAppDir, FILE_UPDATER_BIN);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * When a test finishes this will repeatedly attempt to restore the real updater
 | 
						|
 * for tests that use the test updater and then call
 | 
						|
 * finishTestDefaultWaitForWindowClosed after the restore is successful.
 | 
						|
 */
 | 
						|
function finishTestRestoreUpdaterBackup() {
 | 
						|
  return Task.spawn(function*() {
 | 
						|
    if (gUseTestUpdater) {
 | 
						|
      try {
 | 
						|
        // Windows debug builds keep the updater file in use for a short period of
 | 
						|
        // time after the updater process exits.
 | 
						|
        restoreUpdaterBackup();
 | 
						|
      } catch (e) {
 | 
						|
        logTestInfo("Attempt to restore the backed up updater failed... " +
 | 
						|
                    "will try again, Exception: " + e);
 | 
						|
 | 
						|
        yield delay();
 | 
						|
        yield finishTestRestoreUpdaterBackup();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  });
 | 
						|
}
 |