mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge fx-team to m-c
This commit is contained in:
commit
cc3b2a1e47
@ -32,7 +32,14 @@ libs::
|
||||
|
||||
TEST_DIRS += tests
|
||||
|
||||
TESTING_JS_MODULES := aitcserver.js storageserver.js
|
||||
testing_modules := \
|
||||
aitcserver.js \
|
||||
storageserver.js \
|
||||
utils.js \
|
||||
$(NULL)
|
||||
|
||||
TESTING_JS_MODULES := $(foreach file,$(testing_modules),modules-testing/$(file))
|
||||
|
||||
TESTING_JS_MODULE_DIR := services-common
|
||||
|
||||
# What follows is a helper to launch a standalone storage server instance.
|
||||
|
42
services/common/modules-testing/utils.js
Normal file
42
services/common/modules-testing/utils.js
Normal file
@ -0,0 +1,42 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EXPORTED_SYMBOLS = [
|
||||
"TestingUtils",
|
||||
];
|
||||
|
||||
let TestingUtils = {
|
||||
/**
|
||||
* Perform a deep copy of an Array or Object.
|
||||
*/
|
||||
deepCopy: function deepCopy(thing, noSort) {
|
||||
if (typeof(thing) != "object" || thing == null) {
|
||||
return thing;
|
||||
}
|
||||
|
||||
if (Array.isArray(thing)) {
|
||||
let ret = [];
|
||||
for (let element of thing) {
|
||||
ret.push(this.deepCopy(element, noSort));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
let ret = {};
|
||||
let props = [p for (p in thing)];
|
||||
|
||||
if (!noSort) {
|
||||
props = props.sort();
|
||||
}
|
||||
|
||||
for (let prop of props) {
|
||||
ret[prop] = this.deepCopy(thing[prop], noSort);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
@ -1,8 +1,11 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://testing-common/services-common/utils.js");
|
||||
|
||||
function run_test() {
|
||||
let thing = {o: {foo: "foo", bar: ["bar"]}, a: ["foo", {bar: "bar"}]};
|
||||
let ret = deepCopy(thing);
|
||||
let ret = TestingUtils.deepCopy(thing);
|
||||
do_check_neq(ret, thing)
|
||||
do_check_neq(ret.o, thing.o);
|
||||
do_check_neq(ret.o.bar, thing.o.bar);
|
@ -6,6 +6,7 @@ tail =
|
||||
[test_load_modules.js]
|
||||
|
||||
[test_utils_atob.js]
|
||||
[test_utils_deepCopy.js]
|
||||
[test_utils_encodeBase32.js]
|
||||
[test_utils_encodeBase64URL.js]
|
||||
[test_utils_json.js]
|
||||
|
474
services/sync/modules/addonutils.js
Normal file
474
services/sync/modules/addonutils.js
Normal file
@ -0,0 +1,474 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EXPORTED_SYMBOLS = ["AddonUtils"];
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
|
||||
"resource://gre/modules/AddonRepository.jsm");
|
||||
|
||||
function AddonUtilsInternal() {
|
||||
this._log = Log4Moz.repository.getLogger("Sync.AddonUtils");
|
||||
this._log.Level = Log4Moz.Level[Svc.Prefs.get("log.logger.addonutils")];
|
||||
}
|
||||
AddonUtilsInternal.prototype = {
|
||||
/**
|
||||
* Obtain an AddonInstall object from an AddonSearchResult instance.
|
||||
*
|
||||
* The callback will be invoked with the result of the operation. The
|
||||
* callback receives 2 arguments, error and result. Error will be falsy
|
||||
* on success or some kind of error value otherwise. The result argument
|
||||
* will be an AddonInstall on success or null on failure. It is possible
|
||||
* for the error to be falsy but result to be null. This could happen if
|
||||
* an install was not found.
|
||||
*
|
||||
* @param addon
|
||||
* AddonSearchResult to obtain install from.
|
||||
* @param cb
|
||||
* Function to be called with result of operation.
|
||||
*/
|
||||
getInstallFromSearchResult:
|
||||
function getInstallFromSearchResult(addon, cb, requireSecureURI=true) {
|
||||
|
||||
this._log.debug("Obtaining install for " + addon.id);
|
||||
|
||||
// Verify that the source URI uses TLS. We don't allow installs from
|
||||
// insecure sources for security reasons. The Addon Manager ensures that
|
||||
// cert validation, etc is performed.
|
||||
if (requireSecureURI) {
|
||||
let scheme = addon.sourceURI.scheme;
|
||||
if (scheme != "https") {
|
||||
cb(new Error("Insecure source URI scheme: " + scheme), addon.install);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We should theoretically be able to obtain (and use) addon.install if
|
||||
// it is available. However, the addon.sourceURI rewriting won't be
|
||||
// reflected in the AddonInstall, so we can't use it. If we ever get rid
|
||||
// of sourceURI rewriting, we can avoid having to reconstruct the
|
||||
// AddonInstall.
|
||||
AddonManager.getInstallForURL(
|
||||
addon.sourceURI.spec,
|
||||
function handleInstall(install) {
|
||||
cb(null, install);
|
||||
},
|
||||
"application/x-xpinstall",
|
||||
undefined,
|
||||
addon.name,
|
||||
addon.iconURL,
|
||||
addon.version
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Installs an add-on from an AddonSearchResult instance.
|
||||
*
|
||||
* The options argument defines extra options to control the install.
|
||||
* Recognized keys in this map are:
|
||||
*
|
||||
* syncGUID - Sync GUID to use for the new add-on.
|
||||
* enabled - Boolean indicating whether the add-on should be enabled upon
|
||||
* install.
|
||||
* requireSecureURI - Boolean indicating whether to require a secure
|
||||
* URI to install from. This defaults to true.
|
||||
*
|
||||
* When complete it calls a callback with 2 arguments, error and result.
|
||||
*
|
||||
* If error is falsy, result is an object. If error is truthy, result is
|
||||
* null.
|
||||
*
|
||||
* The result object has the following keys:
|
||||
*
|
||||
* id ID of add-on that was installed.
|
||||
* install AddonInstall that was installed.
|
||||
* addon Addon that was installed.
|
||||
*
|
||||
* @param addon
|
||||
* AddonSearchResult to install add-on from.
|
||||
* @param options
|
||||
* Object with additional metadata describing how to install add-on.
|
||||
* @param cb
|
||||
* Function to be invoked with result of operation.
|
||||
*/
|
||||
installAddonFromSearchResult:
|
||||
function installAddonFromSearchResult(addon, options, cb) {
|
||||
this._log.info("Trying to install add-on from search result: " + addon.id);
|
||||
|
||||
if (options.requireSecureURI === undefined) {
|
||||
options.requireSecureURI = true;
|
||||
}
|
||||
|
||||
this.getInstallFromSearchResult(addon, function onResult(error, install) {
|
||||
if (error) {
|
||||
cb(error, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!install) {
|
||||
cb(new Error("AddonInstall not available: " + addon.id), null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._log.info("Installing " + addon.id);
|
||||
let log = this._log;
|
||||
|
||||
let listener = {
|
||||
onInstallStarted: function onInstallStarted(install) {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.syncGUID) {
|
||||
log.info("Setting syncGUID of " + install.name +": " +
|
||||
options.syncGUID);
|
||||
install.addon.syncGUID = options.syncGUID;
|
||||
}
|
||||
|
||||
// We only need to change userDisabled if it is disabled because
|
||||
// enabled is the default.
|
||||
if ("enabled" in options && !options.enabled) {
|
||||
log.info("Marking add-on as disabled for install: " +
|
||||
install.name);
|
||||
install.addon.userDisabled = true;
|
||||
}
|
||||
},
|
||||
onInstallEnded: function(install, addon) {
|
||||
install.removeListener(listener);
|
||||
|
||||
cb(null, {id: addon.id, install: install, addon: addon});
|
||||
},
|
||||
onInstallFailed: function(install) {
|
||||
install.removeListener(listener);
|
||||
|
||||
cb(new Error("Install failed: " + install.error), null);
|
||||
},
|
||||
onDownloadFailed: function(install) {
|
||||
install.removeListener(listener);
|
||||
|
||||
cb(new Error("Download failed: " + install.error), null);
|
||||
}
|
||||
};
|
||||
install.addListener(listener);
|
||||
install.install();
|
||||
}
|
||||
catch (ex) {
|
||||
this._log.error("Error installing add-on: " + Utils.exceptionstr(ex));
|
||||
cb(ex, null);
|
||||
}
|
||||
}.bind(this), options.requireSecureURI);
|
||||
},
|
||||
|
||||
/**
|
||||
* Uninstalls the Addon instance and invoke a callback when it is done.
|
||||
*
|
||||
* @param addon
|
||||
* Addon instance to uninstall.
|
||||
* @param cb
|
||||
* Function to be invoked when uninstall has finished. It receives a
|
||||
* truthy value signifying error and the add-on which was uninstalled.
|
||||
*/
|
||||
uninstallAddon: function uninstallAddon(addon, cb) {
|
||||
let listener = {
|
||||
onUninstalling: function(uninstalling, needsRestart) {
|
||||
if (addon.id != uninstalling.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We assume restartless add-ons will send the onUninstalled event
|
||||
// soon.
|
||||
if (!needsRestart) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For non-restartless add-ons, we issue the callback on uninstalling
|
||||
// because we will likely never see the uninstalled event.
|
||||
AddonManager.removeAddonListener(listener);
|
||||
cb(null, addon);
|
||||
},
|
||||
onUninstalled: function(uninstalled) {
|
||||
if (addon.id != uninstalled.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
cb(null, addon);
|
||||
}
|
||||
};
|
||||
AddonManager.addAddonListener(listener);
|
||||
addon.uninstall();
|
||||
},
|
||||
|
||||
/**
|
||||
* Installs multiple add-ons specified by metadata.
|
||||
*
|
||||
* The first argument is an array of objects. Each object must have the
|
||||
* following keys:
|
||||
*
|
||||
* id - public ID of the add-on to install.
|
||||
* syncGUID - syncGUID for new add-on.
|
||||
* enabled - boolean indicating whether the add-on should be enabled.
|
||||
* requireSecureURI - Boolean indicating whether to require a secure
|
||||
* URI when installing from a remote location. This defaults to
|
||||
* true.
|
||||
*
|
||||
* The callback will be called when activity on all add-ons is complete. The
|
||||
* callback receives 2 arguments, error and result.
|
||||
*
|
||||
* If error is truthy, it contains a string describing the overall error.
|
||||
*
|
||||
* The 2nd argument to the callback is always an object with details on the
|
||||
* overall execution state. It contains the following keys:
|
||||
*
|
||||
* installedIDs Array of add-on IDs that were installed.
|
||||
* installs Array of AddonInstall instances that were installed.
|
||||
* addons Array of Addon instances that were installed.
|
||||
* errors Array of errors encountered. Only has elements if error is
|
||||
* truthy.
|
||||
*
|
||||
* @param installs
|
||||
* Array of objects describing add-ons to install.
|
||||
* @param cb
|
||||
* Function to be called when all actions are complete.
|
||||
*/
|
||||
installAddons: function installAddons(installs, cb) {
|
||||
if (!cb) {
|
||||
throw new Error("Invalid argument: cb is not defined.");
|
||||
}
|
||||
|
||||
let ids = [];
|
||||
for each (let addon in installs) {
|
||||
ids.push(addon.id);
|
||||
}
|
||||
|
||||
AddonRepository.getAddonsByIDs(ids, {
|
||||
searchSucceeded: function searchSucceeded(addons, addonsLength, total) {
|
||||
this._log.info("Found " + addonsLength + "/" + ids.length +
|
||||
" add-ons during repository search.");
|
||||
|
||||
let ourResult = {
|
||||
installedIDs: [],
|
||||
installs: [],
|
||||
addons: [],
|
||||
errors: []
|
||||
};
|
||||
|
||||
if (!addonsLength) {
|
||||
cb(null, ourResult);
|
||||
return;
|
||||
}
|
||||
|
||||
let expectedInstallCount = 0;
|
||||
let finishedCount = 0;
|
||||
let installCallback = function installCallback(error, result) {
|
||||
finishedCount++;
|
||||
|
||||
if (error) {
|
||||
ourResult.errors.push(error);
|
||||
} else {
|
||||
ourResult.installedIDs.push(result.id);
|
||||
ourResult.installs.push(result.install);
|
||||
ourResult.addons.push(result.addon);
|
||||
}
|
||||
|
||||
if (finishedCount >= expectedInstallCount) {
|
||||
if (ourResult.errors.length > 0) {
|
||||
cb(new Error("1 or more add-ons failed to install"), ourResult);
|
||||
} else {
|
||||
cb(null, ourResult);
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
let toInstall = [];
|
||||
|
||||
// Rewrite the "src" query string parameter of the source URI to note
|
||||
// that the add-on was installed by Sync and not something else so
|
||||
// server-side metrics aren't skewed (bug 708134). The server should
|
||||
// ideally send proper URLs, but this solution was deemed too
|
||||
// complicated at the time the functionality was implemented.
|
||||
for each (let addon in addons) {
|
||||
// sourceURI presence isn't enforced by AddonRepository. So, we skip
|
||||
// add-ons without a sourceURI.
|
||||
if (!addon.sourceURI) {
|
||||
this._log.info("Skipping install of add-on because missing " +
|
||||
"sourceURI: " + addon.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
toInstall.push(addon);
|
||||
|
||||
// We should always be able to QI the nsIURI to nsIURL. If not, we
|
||||
// still try to install the add-on, but we don't rewrite the URL,
|
||||
// potentially skewing metrics.
|
||||
try {
|
||||
addon.sourceURI.QueryInterface(Ci.nsIURL);
|
||||
} catch (ex) {
|
||||
this._log.warn("Unable to QI sourceURI to nsIURL: " +
|
||||
addon.sourceURI.spec);
|
||||
continue;
|
||||
}
|
||||
|
||||
let params = addon.sourceURI.query.split("&").map(
|
||||
function rewrite(param) {
|
||||
|
||||
if (param.indexOf("src=") == 0) {
|
||||
return "src=sync";
|
||||
} else {
|
||||
return param;
|
||||
}
|
||||
});
|
||||
|
||||
addon.sourceURI.query = params.join("&");
|
||||
}
|
||||
|
||||
expectedInstallCount = toInstall.length;
|
||||
|
||||
if (!expectedInstallCount) {
|
||||
cb(null, ourResult);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start all the installs asynchronously. They will report back to us
|
||||
// as they finish, eventually triggering the global callback.
|
||||
for each (let addon in toInstall) {
|
||||
let options = {};
|
||||
for each (let install in installs) {
|
||||
if (install.id == addon.id) {
|
||||
options = install;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.installAddonFromSearchResult(addon, options, installCallback);
|
||||
}
|
||||
|
||||
}.bind(this),
|
||||
|
||||
searchFailed: function searchFailed() {
|
||||
cb(new Error("AddonRepository search failed"), null);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the user disabled flag for an add-on.
|
||||
*
|
||||
* The supplied callback will ba called when the operation is
|
||||
* complete. If the new flag matches the existing or if the add-on
|
||||
* isn't currently active, the function will fire the callback
|
||||
* immediately. Else, the callback is invoked when the AddonManager
|
||||
* reports the change has taken effect or has been registered.
|
||||
*
|
||||
* The callback receives as arguments:
|
||||
*
|
||||
* (Error) Encountered error during operation or null on success.
|
||||
* (Addon) The add-on instance being operated on.
|
||||
*
|
||||
* @param addon
|
||||
* (Addon) Add-on instance to operate on.
|
||||
* @param value
|
||||
* (bool) New value for add-on's userDisabled property.
|
||||
* @param cb
|
||||
* (function) Callback to be invoked on completion.
|
||||
*/
|
||||
updateUserDisabled: function updateUserDisabled(addon, value, cb) {
|
||||
if (addon.userDisabled == value) {
|
||||
cb(null, addon);
|
||||
return;
|
||||
}
|
||||
|
||||
let listener = {
|
||||
onEnabling: function onEnabling(wrapper, needsRestart) {
|
||||
this._log.debug("onEnabling: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We ignore the restartless case because we'll get onEnabled shortly.
|
||||
if (!needsRestart) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
cb(null, wrapper);
|
||||
}.bind(this),
|
||||
|
||||
onEnabled: function onEnabled(wrapper) {
|
||||
this._log.debug("onEnabled: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
cb(null, wrapper);
|
||||
}.bind(this),
|
||||
|
||||
onDisabling: function onDisabling(wrapper, needsRestart) {
|
||||
this._log.debug("onDisabling: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!needsRestart) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
cb(null, wrapper);
|
||||
}.bind(this),
|
||||
|
||||
onDisabled: function onDisabled(wrapper) {
|
||||
this._log.debug("onDisabled: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
cb(null, wrapper);
|
||||
}.bind(this),
|
||||
|
||||
onOperationCancelled: function onOperationCancelled(wrapper) {
|
||||
this._log.debug("onOperationCancelled: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
cb(new Error("Operation cancelled"), wrapper);
|
||||
}.bind(this)
|
||||
};
|
||||
|
||||
// The add-on listeners are only fired if the add-on is active. If not, the
|
||||
// change is silently updated and made active when/if the add-on is active.
|
||||
|
||||
if (!addon.appDisabled) {
|
||||
AddonManager.addAddonListener(listener);
|
||||
}
|
||||
|
||||
this._log.info("Updating userDisabled flag: " + addon.id + " -> " + value);
|
||||
addon.userDisabled = !!value;
|
||||
|
||||
if (!addon.appDisabled) {
|
||||
cb(null, addon);
|
||||
return;
|
||||
}
|
||||
// Else the listener will handle invoking the callback.
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "AddonUtils", function() {
|
||||
return new AddonUtilsInternal();
|
||||
});
|
@ -35,6 +35,7 @@
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://services-sync/addonutils.js");
|
||||
Cu.import("resource://services-sync/addonsreconciler.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/record.js");
|
||||
@ -291,10 +292,11 @@ AddonsStore.prototype = {
|
||||
*/
|
||||
create: function create(record) {
|
||||
let cb = Async.makeSpinningCallback();
|
||||
this.installAddons([{
|
||||
id: record.addonID,
|
||||
syncGUID: record.id,
|
||||
enabled: record.enabled
|
||||
AddonUtils.installAddons([{
|
||||
id: record.addonID,
|
||||
syncGUID: record.id,
|
||||
enabled: record.enabled,
|
||||
requireSecureURI: !Svc.Prefs.get("addons.ignoreRepositoryChecking", false),
|
||||
}], cb);
|
||||
|
||||
// This will throw if there was an error. This will get caught by the sync
|
||||
@ -332,7 +334,7 @@ AddonsStore.prototype = {
|
||||
|
||||
this._log.info("Uninstalling add-on: " + addon.id);
|
||||
let cb = Async.makeSpinningCallback();
|
||||
this.uninstallAddon(addon, cb);
|
||||
AddonUtils.uninstallAddon(addon, cb);
|
||||
cb.wait();
|
||||
},
|
||||
|
||||
@ -607,185 +609,6 @@ AddonsStore.prototype = {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain an AddonInstall object from an AddonSearchResult instance.
|
||||
*
|
||||
* The callback will be invoked with the result of the operation. The
|
||||
* callback receives 2 arguments, error and result. Error will be falsy
|
||||
* on success or some kind of error value otherwise. The result argument
|
||||
* will be an AddonInstall on success or null on failure. It is possible
|
||||
* for the error to be falsy but result to be null. This could happen if
|
||||
* an install was not found.
|
||||
*
|
||||
* @param addon
|
||||
* AddonSearchResult to obtain install from.
|
||||
* @param cb
|
||||
* Function to be called with result of operation.
|
||||
*/
|
||||
getInstallFromSearchResult: function getInstallFromSearchResult(addon, cb) {
|
||||
// We should theoretically be able to obtain (and use) addon.install if
|
||||
// it is available. However, the addon.sourceURI rewriting won't be
|
||||
// reflected in the AddonInstall, so we can't use it. If we ever get rid
|
||||
// of sourceURI rewriting, we can avoid having to reconstruct the
|
||||
// AddonInstall.
|
||||
this._log.debug("Obtaining install for " + addon.id);
|
||||
|
||||
// Verify that the source URI uses TLS. We don't allow installs from
|
||||
// insecure sources for security reasons. The Addon Manager ensures that
|
||||
// cert validation, etc is performed.
|
||||
if (!Svc.Prefs.get("addons.ignoreRepositoryChecking", false)) {
|
||||
let scheme = addon.sourceURI.scheme;
|
||||
if (scheme != "https") {
|
||||
cb(new Error("Insecure source URI scheme: " + scheme), addon.install);
|
||||
}
|
||||
}
|
||||
|
||||
AddonManager.getInstallForURL(
|
||||
addon.sourceURI.spec,
|
||||
function handleInstall(install) {
|
||||
cb(null, install);
|
||||
},
|
||||
"application/x-xpinstall",
|
||||
undefined,
|
||||
addon.name,
|
||||
addon.iconURL,
|
||||
addon.version
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Installs an add-on from an AddonSearchResult instance.
|
||||
*
|
||||
* The options argument defines extra options to control the install.
|
||||
* Recognized keys in this map are:
|
||||
*
|
||||
* syncGUID - Sync GUID to use for the new add-on.
|
||||
* enabled - Boolean indicating whether the add-on should be enabled upon
|
||||
* install.
|
||||
*
|
||||
* When complete it calls a callback with 2 arguments, error and result.
|
||||
*
|
||||
* If error is falsy, result is an object. If error is truthy, result is
|
||||
* null.
|
||||
*
|
||||
* The result object has the following keys:
|
||||
*
|
||||
* id ID of add-on that was installed.
|
||||
* install AddonInstall that was installed.
|
||||
* addon Addon that was installed.
|
||||
*
|
||||
* @param addon
|
||||
* AddonSearchResult to install add-on from.
|
||||
* @param options
|
||||
* Object with additional metadata describing how to install add-on.
|
||||
* @param cb
|
||||
* Function to be invoked with result of operation.
|
||||
*/
|
||||
installAddonFromSearchResult:
|
||||
function installAddonFromSearchResult(addon, options, cb) {
|
||||
this._log.info("Trying to install add-on from search result: " + addon.id);
|
||||
|
||||
this.getInstallFromSearchResult(addon, function(error, install) {
|
||||
if (error) {
|
||||
cb(error, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!install) {
|
||||
cb(new Error("AddonInstall not available: " + addon.id), null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._log.info("Installing " + addon.id);
|
||||
let log = this._log;
|
||||
|
||||
let listener = {
|
||||
onInstallStarted: function(install) {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.syncGUID) {
|
||||
log.info("Setting syncGUID of " + install.name +": " +
|
||||
options.syncGUID);
|
||||
install.addon.syncGUID = options.syncGUID;
|
||||
}
|
||||
|
||||
// We only need to change userDisabled if it is disabled because
|
||||
// enabled is the default.
|
||||
if ("enabled" in options && !options.enabled) {
|
||||
log.info("Marking add-on as disabled for install: " +
|
||||
install.name);
|
||||
install.addon.userDisabled = true;
|
||||
}
|
||||
},
|
||||
onInstallEnded: function(install, addon) {
|
||||
install.removeListener(listener);
|
||||
|
||||
cb(null, {id: addon.id, install: install, addon: addon});
|
||||
},
|
||||
onInstallFailed: function(install) {
|
||||
install.removeListener(listener);
|
||||
|
||||
cb(new Error("Install failed: " + install.error), null);
|
||||
},
|
||||
onDownloadFailed: function(install) {
|
||||
install.removeListener(listener);
|
||||
|
||||
cb(new Error("Download failed: " + install.error), null);
|
||||
}
|
||||
};
|
||||
install.addListener(listener);
|
||||
install.install();
|
||||
}
|
||||
catch (ex) {
|
||||
this._log.error("Error installing add-on: " + Utils.exceptionstr(ex));
|
||||
cb(ex, null);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Uninstalls the Addon instance and invoke a callback when it is done.
|
||||
*
|
||||
* @param addon
|
||||
* Addon instance to uninstall.
|
||||
* @param callback
|
||||
* Function to be invoked when uninstall has finished. It receives a
|
||||
* truthy value signifying error and the add-on which was uninstalled.
|
||||
*/
|
||||
uninstallAddon: function uninstallAddon(addon, callback) {
|
||||
let listener = {
|
||||
onUninstalling: function(uninstalling, needsRestart) {
|
||||
if (addon.id != uninstalling.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We assume restartless add-ons will send the onUninstalled event
|
||||
// soon.
|
||||
if (!needsRestart) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For non-restartless add-ons, we issue the callback on uninstalling
|
||||
// because we will likely never see the uninstalled event.
|
||||
AddonManager.removeAddonListener(listener);
|
||||
callback(null, addon);
|
||||
},
|
||||
onUninstalled: function(uninstalled) {
|
||||
if (addon.id != uninstalled.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
callback(null, addon);
|
||||
}
|
||||
};
|
||||
AddonManager.addAddonListener(listener);
|
||||
addon.uninstall();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the userDisabled flag on an add-on.
|
||||
*
|
||||
@ -816,232 +639,8 @@ AddonsStore.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let listener = {
|
||||
onEnabling: function onEnabling(wrapper, needsRestart) {
|
||||
this._log.debug("onEnabling: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We ignore the restartless case because we'll get onEnabled shortly.
|
||||
if (!needsRestart) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
callback(null, wrapper);
|
||||
}.bind(this),
|
||||
|
||||
onEnabled: function onEnabled(wrapper) {
|
||||
this._log.debug("onEnabled: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
callback(null, wrapper);
|
||||
}.bind(this),
|
||||
|
||||
onDisabling: function onDisabling(wrapper, needsRestart) {
|
||||
this._log.debug("onDisabling: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!needsRestart) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
callback(null, wrapper);
|
||||
}.bind(this),
|
||||
|
||||
onDisabled: function onDisabled(wrapper) {
|
||||
this._log.debug("onDisabled: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
callback(null, wrapper);
|
||||
}.bind(this),
|
||||
|
||||
onOperationCancelled: function onOperationCancelled(wrapper) {
|
||||
this._log.debug("onOperationCancelled: " + wrapper.id);
|
||||
if (wrapper.id != addon.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
callback(new Error("Operation cancelled"), wrapper);
|
||||
}.bind(this)
|
||||
};
|
||||
|
||||
// The add-on listeners are only fired if the add-on is active. If not, the
|
||||
// change is silently updated and made active when/if the add-on is active.
|
||||
|
||||
if (!addon.appDisabled) {
|
||||
AddonManager.addAddonListener(listener);
|
||||
}
|
||||
|
||||
this._log.info("Updating userDisabled flag: " + addon.id + " -> " + value);
|
||||
addon.userDisabled = !!value;
|
||||
|
||||
if (!addon.appDisabled) {
|
||||
callback(null, addon);
|
||||
return;
|
||||
}
|
||||
// Else the listener will handle invoking the callback.
|
||||
AddonUtils.updateUserDisabled(addon, value, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Installs multiple add-ons specified by metadata.
|
||||
*
|
||||
* The first argument is an array of objects. Each object must have the
|
||||
* following keys:
|
||||
*
|
||||
* id - public ID of the add-on to install.
|
||||
* syncGUID - syncGUID for new add-on.
|
||||
* enabled - boolean indicating whether the add-on should be enabled.
|
||||
*
|
||||
* The callback will be called when activity on all add-ons is complete. The
|
||||
* callback receives 2 arguments, error and result.
|
||||
*
|
||||
* If error is truthy, it contains a string describing the overall error.
|
||||
*
|
||||
* The 2nd argument to the callback is always an object with details on the
|
||||
* overall execution state. It contains the following keys:
|
||||
*
|
||||
* installedIDs Array of add-on IDs that were installed.
|
||||
* installs Array of AddonInstall instances that were installed.
|
||||
* addons Array of Addon instances that were installed.
|
||||
* errors Array of errors encountered. Only has elements if error is
|
||||
* truthy.
|
||||
*
|
||||
* @param installs
|
||||
* Array of objects describing add-ons to install.
|
||||
* @param cb
|
||||
* Function to be called when all actions are complete.
|
||||
*/
|
||||
installAddons: function installAddons(installs, cb) {
|
||||
if (!cb) {
|
||||
throw new Error("Invalid argument: cb is not defined.");
|
||||
}
|
||||
|
||||
let ids = [];
|
||||
for each (let addon in installs) {
|
||||
ids.push(addon.id);
|
||||
}
|
||||
|
||||
AddonRepository.getAddonsByIDs(ids, {
|
||||
searchSucceeded: function searchSucceeded(addons, addonsLength, total) {
|
||||
this._log.info("Found " + addonsLength + "/" + ids.length +
|
||||
" add-ons during repository search.");
|
||||
|
||||
let ourResult = {
|
||||
installedIDs: [],
|
||||
installs: [],
|
||||
addons: [],
|
||||
errors: []
|
||||
};
|
||||
|
||||
if (!addonsLength) {
|
||||
cb(null, ourResult);
|
||||
return;
|
||||
}
|
||||
|
||||
let expectedInstallCount = 0;
|
||||
let finishedCount = 0;
|
||||
let installCallback = function installCallback(error, result) {
|
||||
finishedCount++;
|
||||
|
||||
if (error) {
|
||||
ourResult.errors.push(error);
|
||||
} else {
|
||||
ourResult.installedIDs.push(result.id);
|
||||
ourResult.installs.push(result.install);
|
||||
ourResult.addons.push(result.addon);
|
||||
}
|
||||
|
||||
if (finishedCount >= expectedInstallCount) {
|
||||
if (ourResult.errors.length > 0) {
|
||||
cb(new Error("1 or more add-ons failed to install"), ourResult);
|
||||
} else {
|
||||
cb(null, ourResult);
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
let toInstall = [];
|
||||
|
||||
// Rewrite the "src" query string parameter of the source URI to note
|
||||
// that the add-on was installed by Sync and not something else so
|
||||
// server-side metrics aren't skewed (bug 708134). The server should
|
||||
// ideally send proper URLs, but this solution was deemed too
|
||||
// complicated at the time the functionality was implemented.
|
||||
for each (let addon in addons) {
|
||||
// sourceURI presence isn't enforced by AddonRepository. So, we skip
|
||||
// add-ons without a sourceURI.
|
||||
if (!addon.sourceURI) {
|
||||
this._log.info("Skipping install of add-on because missing " +
|
||||
"sourceURI: " + addon.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
toInstall.push(addon);
|
||||
|
||||
// We should always be able to QI the nsIURI to nsIURL. If not, we
|
||||
// still try to install the add-on, but we don't rewrite the URL,
|
||||
// potentially skewing metrics.
|
||||
try {
|
||||
addon.sourceURI.QueryInterface(Ci.nsIURL);
|
||||
} catch (ex) {
|
||||
this._log.warn("Unable to QI sourceURI to nsIURL: " +
|
||||
addon.sourceURI.spec);
|
||||
continue;
|
||||
}
|
||||
|
||||
let params = addon.sourceURI.query.split("&").map(
|
||||
function rewrite(param) {
|
||||
|
||||
if (param.indexOf("src=") == 0) {
|
||||
return "src=sync";
|
||||
} else {
|
||||
return param;
|
||||
}
|
||||
});
|
||||
|
||||
addon.sourceURI.query = params.join("&");
|
||||
}
|
||||
|
||||
expectedInstallCount = toInstall.length;
|
||||
|
||||
if (!expectedInstallCount) {
|
||||
cb(null, ourResult);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start all the installs asynchronously. They will report back to us
|
||||
// as they finish, eventually triggering the global callback.
|
||||
for each (let addon in toInstall) {
|
||||
let options = {};
|
||||
for each (let install in installs) {
|
||||
if (install.id == addon.id) {
|
||||
options = install;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.installAddonFromSearchResult(addon, options, installCallback);
|
||||
}
|
||||
|
||||
}.bind(this),
|
||||
|
||||
searchFailed: function searchFailed() {
|
||||
cb(new Error("AddonRepository search failed"), null);
|
||||
}.bind(this)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -193,6 +193,7 @@ let SyncScheduler = {
|
||||
}
|
||||
break;
|
||||
case "weave:service:setup-complete":
|
||||
Services.prefs.savePrefFile(null);
|
||||
Svc.Idle.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
|
||||
break;
|
||||
case "weave:service:start-over":
|
||||
|
@ -54,6 +54,7 @@ pref("services.sync.log.appender.file.logOnError", true);
|
||||
pref("services.sync.log.appender.file.logOnSuccess", false);
|
||||
pref("services.sync.log.appender.file.maxErrorAge", 864000); // 10 days
|
||||
pref("services.sync.log.rootLogger", "Debug");
|
||||
pref("services.sync.log.logger.addonutils", "Debug");
|
||||
pref("services.sync.log.logger.service.main", "Debug");
|
||||
pref("services.sync.log.logger.status", "Debug");
|
||||
pref("services.sync.log.logger.authenticator", "Debug");
|
||||
|
@ -380,26 +380,3 @@ RotaryEngine.prototype = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
deepCopy: function deepCopy(thing, noSort) {
|
||||
if (typeof(thing) != "object" || thing == null){
|
||||
return thing;
|
||||
}
|
||||
let ret;
|
||||
|
||||
if (Array.isArray(thing)) {
|
||||
ret = [];
|
||||
for (let i = 0; i < thing.length; i++){
|
||||
ret.push(deepCopy(thing[i], noSort));
|
||||
}
|
||||
} else {
|
||||
ret = {};
|
||||
let props = [p for (p in thing)];
|
||||
if (!noSort){
|
||||
props = props.sort();
|
||||
}
|
||||
props.forEach(function(k) ret[k] = deepCopy(thing[k], noSort));
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
161
services/sync/tests/unit/test_addon_utils.js
Normal file
161
services/sync/tests/unit/test_addon_utils.js
Normal file
@ -0,0 +1,161 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://services-sync/addonutils.js");
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
|
||||
const HTTP_PORT = 8888;
|
||||
const SERVER_ADDRESS = "http://127.0.0.1:8888";
|
||||
|
||||
let prefs = new Preferences();
|
||||
|
||||
prefs.set("extensions.getAddons.get.url",
|
||||
SERVER_ADDRESS + "/search/guid:%IDS%");
|
||||
|
||||
loadAddonTestFunctions();
|
||||
startupManager();
|
||||
|
||||
function createAndStartHTTPServer(port=HTTP_PORT) {
|
||||
try {
|
||||
let server = new HttpServer();
|
||||
|
||||
let bootstrap1XPI = ExtensionsTestPath("/addons/test_bootstrap1_1.xpi");
|
||||
|
||||
server.registerFile("/search/guid:missing-sourceuri%40tests.mozilla.org",
|
||||
do_get_file("missing-sourceuri.xml"));
|
||||
|
||||
server.registerFile("/search/guid:rewrite%40tests.mozilla.org",
|
||||
do_get_file("rewrite-search.xml"));
|
||||
|
||||
server.start(port);
|
||||
|
||||
return server;
|
||||
} catch (ex) {
|
||||
_("Got exception starting HTTP server on port " + port);
|
||||
_("Error: " + Utils.exceptionStr(ex));
|
||||
do_throw(ex);
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
initTestLogging("Trace");
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_handle_empty_source_uri() {
|
||||
_("Ensure that search results without a sourceURI are properly ignored.");
|
||||
|
||||
let server = createAndStartHTTPServer();
|
||||
|
||||
const ID = "missing-sourceuri@tests.mozilla.org";
|
||||
|
||||
let cb = Async.makeSpinningCallback();
|
||||
AddonUtils.installAddons([{id: ID, requireSecureURI: false}], cb);
|
||||
let result = cb.wait();
|
||||
|
||||
do_check_true("installedIDs" in result);
|
||||
do_check_eq(0, result.installedIDs.length);
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
||||
add_test(function test_ignore_untrusted_source_uris() {
|
||||
_("Ensures that source URIs from insecure schemes are rejected.");
|
||||
|
||||
let ioService = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
|
||||
const bad = ["http://example.com/foo.xpi",
|
||||
"ftp://example.com/foo.xpi",
|
||||
"silly://example.com/foo.xpi"];
|
||||
|
||||
const good = ["https://example.com/foo.xpi"];
|
||||
|
||||
for (let s of bad) {
|
||||
let sourceURI = ioService.newURI(s, null, null);
|
||||
let addon = {sourceURI: sourceURI, name: "bad", id: "bad"};
|
||||
|
||||
try {
|
||||
let cb = Async.makeSpinningCallback();
|
||||
AddonUtils.getInstallFromSearchResult(addon, cb, true);
|
||||
cb.wait();
|
||||
} catch (ex) {
|
||||
do_check_neq(null, ex);
|
||||
do_check_eq(0, ex.message.indexOf("Insecure source URI"));
|
||||
continue;
|
||||
}
|
||||
|
||||
// We should never get here if an exception is thrown.
|
||||
do_check_true(false);
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
for (let s of good) {
|
||||
let sourceURI = ioService.newURI(s, null, null);
|
||||
let addon = {sourceURI: sourceURI, name: "good", id: "good"};
|
||||
|
||||
// Despite what you might think, we don't get an error in the callback.
|
||||
// The install won't work because the underlying Addon instance wasn't
|
||||
// proper. But, that just results in an AddonInstall that is missing
|
||||
// certain values. We really just care that the callback is being invoked
|
||||
// anyway.
|
||||
let callback = function onInstall(error, install) {
|
||||
do_check_null(error);
|
||||
do_check_neq(null, install);
|
||||
do_check_eq(sourceURI.spec, install.sourceURI.spec);
|
||||
|
||||
count += 1;
|
||||
|
||||
if (count >= good.length) {
|
||||
run_next_test();
|
||||
}
|
||||
};
|
||||
|
||||
AddonUtils.getInstallFromSearchResult(addon, callback, true);
|
||||
}
|
||||
});
|
||||
|
||||
add_test(function test_source_uri_rewrite() {
|
||||
_("Ensure that a 'src=api' query string is rewritten to 'src=sync'");
|
||||
|
||||
// This tests for conformance with bug 708134 so server-side metrics aren't
|
||||
// skewed.
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
|
||||
// We resort to monkeypatching because of the API design.
|
||||
let oldFunction = AddonUtils.__proto__.installAddonFromSearchResult;
|
||||
|
||||
let installCalled = false;
|
||||
AddonUtils.__proto__.installAddonFromSearchResult =
|
||||
function testInstallAddon(addon, metadata, cb) {
|
||||
|
||||
do_check_eq(SERVER_ADDRESS + "/require.xpi?src=sync",
|
||||
addon.sourceURI.spec);
|
||||
|
||||
installCalled = true;
|
||||
|
||||
AddonUtils.getInstallFromSearchResult(addon, function (error, install) {
|
||||
do_check_null(error);
|
||||
do_check_eq(SERVER_ADDRESS + "/require.xpi?src=sync",
|
||||
install.sourceURI.spec);
|
||||
|
||||
cb(null, {id: addon.id, addon: addon, install: install});
|
||||
}, false);
|
||||
};
|
||||
|
||||
let server = createAndStartHTTPServer();
|
||||
|
||||
let installCallback = Async.makeSpinningCallback();
|
||||
AddonUtils.installAddons([{id: "rewrite@tests.mozilla.org"}], installCallback);
|
||||
|
||||
installCallback.wait();
|
||||
do_check_true(installCalled);
|
||||
AddonUtils.__proto__.installAddonFromSearchResult = oldFunction;
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
server.stop(run_next_test);
|
||||
});
|
@ -3,8 +3,9 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://services-sync/engines/addons.js");
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
Cu.import("resource://services-sync/addonutils.js");
|
||||
Cu.import("resource://services-sync/engines/addons.js");
|
||||
|
||||
const HTTP_PORT = 8888;
|
||||
|
||||
@ -53,12 +54,6 @@ function createAndStartHTTPServer(port) {
|
||||
server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org",
|
||||
do_get_file("missing-xpi-search.xml"));
|
||||
|
||||
server.registerFile("/search/guid:rewrite%40tests.mozilla.org",
|
||||
do_get_file("rewrite-search.xml"));
|
||||
|
||||
server.registerFile("/search/guid:missing-sourceuri%40tests.mozilla.org",
|
||||
do_get_file("missing-sourceuri.xml"));
|
||||
|
||||
server.start(port);
|
||||
|
||||
return server;
|
||||
@ -409,64 +404,6 @@ add_test(function test_ignore_hotfixes() {
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_ignore_untrusted_source_uris() {
|
||||
_("Ensures that source URIs from insecure schemes are rejected.");
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", false);
|
||||
|
||||
let ioService = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
|
||||
const bad = ["http://example.com/foo.xpi",
|
||||
"ftp://example.com/foo.xpi",
|
||||
"silly://example.com/foo.xpi"];
|
||||
|
||||
const good = ["https://example.com/foo.xpi"];
|
||||
|
||||
for each (let s in bad) {
|
||||
let sourceURI = ioService.newURI(s, null, null);
|
||||
let addon = {sourceURI: sourceURI, name: "foo"};
|
||||
|
||||
try {
|
||||
let cb = Async.makeSpinningCallback();
|
||||
store.getInstallFromSearchResult(addon, cb);
|
||||
cb.wait();
|
||||
} catch (ex) {
|
||||
do_check_neq(null, ex);
|
||||
do_check_eq(0, ex.message.indexOf("Insecure source URI"));
|
||||
continue;
|
||||
}
|
||||
|
||||
// We should never get here if an exception is thrown.
|
||||
do_check_true(false);
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
for each (let s in good) {
|
||||
let sourceURI = ioService.newURI(s, null, null);
|
||||
let addon = {sourceURI: sourceURI, name: "foo", id: "foo"};
|
||||
|
||||
// Despite what you might think, we don't get an error in the callback.
|
||||
// The install won't work because the underlying Addon instance wasn't
|
||||
// proper. But, that just results in an AddonInstall that is missing
|
||||
// certain values. We really just care that the callback is being invoked
|
||||
// anyway.
|
||||
let callback = function(error, install) {
|
||||
do_check_eq(null, error);
|
||||
do_check_neq(null, install);
|
||||
do_check_eq(sourceURI.spec, install.sourceURI.spec);
|
||||
|
||||
count += 1;
|
||||
|
||||
if (count >= good.length) {
|
||||
run_next_test();
|
||||
}
|
||||
};
|
||||
|
||||
store.getInstallFromSearchResult(addon, callback);
|
||||
}
|
||||
});
|
||||
|
||||
add_test(function test_wipe() {
|
||||
_("Ensures that wiping causes add-ons to be uninstalled.");
|
||||
|
||||
@ -482,64 +419,3 @@ add_test(function test_wipe() {
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_source_uri_rewrite() {
|
||||
_("Ensure that a 'src=api' query string is rewritten to 'src=sync'");
|
||||
|
||||
// This tests for conformance with bug 708134 so server-side metrics aren't
|
||||
// skewed.
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
|
||||
// We resort to monkeypatching because of the API design.
|
||||
let oldFunction = store.__proto__.installAddonFromSearchResult;
|
||||
|
||||
let installCalled = false;
|
||||
store.__proto__.installAddonFromSearchResult =
|
||||
function testInstallAddon(addon, metadata, cb) {
|
||||
|
||||
do_check_eq("http://127.0.0.1:8888/require.xpi?src=sync",
|
||||
addon.sourceURI.spec);
|
||||
|
||||
installCalled = true;
|
||||
|
||||
store.getInstallFromSearchResult(addon, function (error, install) {
|
||||
do_check_eq("http://127.0.0.1:8888/require.xpi?src=sync",
|
||||
install.sourceURI.spec);
|
||||
|
||||
cb(null, {id: addon.id, addon: addon, install: install});
|
||||
});
|
||||
};
|
||||
|
||||
let server = createAndStartHTTPServer(HTTP_PORT);
|
||||
|
||||
let installCallback = Async.makeSpinningCallback();
|
||||
store.installAddons([{id: "rewrite@tests.mozilla.org"}], installCallback);
|
||||
|
||||
installCallback.wait();
|
||||
do_check_true(installCalled);
|
||||
store.__proto__.installAddonFromSearchResult = oldFunction;
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
||||
add_test(function test_handle_empty_source_uri() {
|
||||
_("Ensure that search results without a sourceURI are properly ignored.");
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
|
||||
let server = createAndStartHTTPServer(HTTP_PORT);
|
||||
|
||||
const ID = "missing-sourceuri@tests.mozilla.org";
|
||||
|
||||
let cb = Async.makeSpinningCallback();
|
||||
store.installAddons([{id: ID}], cb);
|
||||
let result = cb.wait();
|
||||
|
||||
do_check_true("installedIDs" in result);
|
||||
do_check_eq(0, result.installedIDs.length);
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
@ -1,11 +1,14 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-sync/record.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/engines/bookmarks.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
Cu.import("resource://gre/modules/PlacesUtils.jsm");
|
||||
Cu.import("resource://testing-common/services-common/utils.js");
|
||||
|
||||
const DESCRIPTION_ANNO = "bookmarkProperties/description";
|
||||
|
||||
@ -56,7 +59,7 @@ store.wipe();
|
||||
function makeLivemark(p, mintGUID) {
|
||||
let b = new Livemark("bookmarks", p.id);
|
||||
// Copy here, because tests mutate the contents.
|
||||
b.cleartext = deepCopy(p);
|
||||
b.cleartext = TestingUtils.deepCopy(p);
|
||||
|
||||
if (mintGUID)
|
||||
b.id = Utils.makeGUID();
|
||||
|
@ -1,27 +1,28 @@
|
||||
const modules = [
|
||||
"addonsreconciler.js",
|
||||
"constants.js",
|
||||
"engines/addons.js",
|
||||
"engines/bookmarks.js",
|
||||
"engines/clients.js",
|
||||
"engines/forms.js",
|
||||
"engines/history.js",
|
||||
"engines/passwords.js",
|
||||
"engines/prefs.js",
|
||||
"engines/tabs.js",
|
||||
"engines.js",
|
||||
"identity.js",
|
||||
"jpakeclient.js",
|
||||
"keys.js",
|
||||
"main.js",
|
||||
"notifications.js",
|
||||
"policies.js",
|
||||
"record.js",
|
||||
"resource.js",
|
||||
"rest.js",
|
||||
"service.js",
|
||||
"status.js",
|
||||
"util.js",
|
||||
"addonutils.js",
|
||||
"addonsreconciler.js",
|
||||
"constants.js",
|
||||
"engines/addons.js",
|
||||
"engines/bookmarks.js",
|
||||
"engines/clients.js",
|
||||
"engines/forms.js",
|
||||
"engines/history.js",
|
||||
"engines/passwords.js",
|
||||
"engines/prefs.js",
|
||||
"engines/tabs.js",
|
||||
"engines.js",
|
||||
"identity.js",
|
||||
"jpakeclient.js",
|
||||
"keys.js",
|
||||
"main.js",
|
||||
"notifications.js",
|
||||
"policies.js",
|
||||
"record.js",
|
||||
"resource.js",
|
||||
"rest.js",
|
||||
"service.js",
|
||||
"status.js",
|
||||
"util.js",
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
|
@ -155,12 +155,77 @@ let quotaValue;
|
||||
Observers.add("weave:service:quota:remaining",
|
||||
function (subject) { quotaValue = subject; });
|
||||
|
||||
let server;
|
||||
|
||||
function run_test() {
|
||||
logger = Log4Moz.repository.getLogger('Test');
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
Svc.Prefs.set("network.numRetries", 1); // speed up test
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// This apparently has to come first in order for our PAC URL to be hit.
|
||||
// Don't put any other HTTP requests earlier in the file!
|
||||
add_test(function test_proxy_auth_redirect() {
|
||||
_("Ensure that a proxy auth redirect (which switches out our channel) " +
|
||||
"doesn't break AsyncResource.");
|
||||
let server = httpd_setup({
|
||||
"/open": server_open,
|
||||
"/pac2": server_pac
|
||||
});
|
||||
|
||||
PACSystemSettings.PACURI = "http://localhost:8080/pac2";
|
||||
installFakePAC();
|
||||
let res = new AsyncResource("http://localhost:8080/open");
|
||||
res.get(function (error, result) {
|
||||
do_check_true(!error);
|
||||
do_check_true(pacFetched);
|
||||
do_check_true(fetched);
|
||||
do_check_eq("This path exists", result);
|
||||
pacFetched = fetched = false;
|
||||
uninstallFakePAC();
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_new_channel() {
|
||||
_("Ensure a redirect to a new channel is handled properly.");
|
||||
|
||||
let resourceRequested = false;
|
||||
function resourceHandler(metadata, response) {
|
||||
resourceRequested = true;
|
||||
|
||||
let body = "Test";
|
||||
response.setHeader("Content-Type", "text/plain");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function redirectHandler(metadata, response) {
|
||||
let body = "Redirecting";
|
||||
response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT");
|
||||
response.setHeader("Location", "http://localhost:8080/resource");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
let server = httpd_setup({"/resource": resourceHandler,
|
||||
"/redirect": redirectHandler},
|
||||
8080);
|
||||
|
||||
let request = new AsyncResource("http://localhost:8080/redirect");
|
||||
request.get(function onRequest(error, content) {
|
||||
do_check_null(error);
|
||||
do_check_true(resourceRequested);
|
||||
do_check_eq(200, content.status);
|
||||
do_check_true("content-type" in content.headers);
|
||||
do_check_eq("text/plain", content.headers["content-type"]);
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
let server;
|
||||
|
||||
add_test(function setup() {
|
||||
server = httpd_setup({
|
||||
"/open": server_open,
|
||||
"/protected": server_protected,
|
||||
@ -176,27 +241,7 @@ function run_test() {
|
||||
"/quota-error": server_quota_error
|
||||
});
|
||||
|
||||
Svc.Prefs.set("network.numRetries", 1); // speed up test
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// This apparently has to come first in order for our PAC URL to be hit.
|
||||
// Don't put any other HTTP requests earlier in the file!
|
||||
add_test(function test_proxy_auth_redirect() {
|
||||
_("Ensure that a proxy auth redirect (which switches out our channel) " +
|
||||
"doesn't break AsyncResource.");
|
||||
PACSystemSettings.PACURI = "http://localhost:8080/pac2";
|
||||
installFakePAC();
|
||||
let res = new AsyncResource("http://localhost:8080/open");
|
||||
res.get(function (error, result) {
|
||||
do_check_true(!error);
|
||||
do_check_true(pacFetched);
|
||||
do_check_true(fetched);
|
||||
do_check_eq("This path exists", result);
|
||||
pacFetched = fetched = false;
|
||||
uninstallFakePAC();
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_members() {
|
||||
@ -662,38 +707,3 @@ add_test(function test_uri_construction() {
|
||||
add_test(function eliminate_server() {
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
||||
add_test(function test_new_channel() {
|
||||
_("Ensure a redirect to a new channel is handled properly.");
|
||||
|
||||
let resourceRequested = false;
|
||||
function resourceHandler(metadata, response) {
|
||||
resourceRequested = true;
|
||||
|
||||
let body = "Test";
|
||||
response.setHeader("Content-Type", "text/plain");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function redirectHandler(metadata, response) {
|
||||
let body = "Redirecting";
|
||||
response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT");
|
||||
response.setHeader("Location", "http://localhost:8080/resource");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
let server = httpd_setup({"/resource": resourceHandler,
|
||||
"/redirect": redirectHandler},
|
||||
8080);
|
||||
|
||||
let request = new AsyncResource("http://localhost:8080/redirect");
|
||||
request.get(function onRequest(error, content) {
|
||||
do_check_null(error);
|
||||
do_check_true(resourceRequested);
|
||||
do_check_eq(200, content.status);
|
||||
do_check_true("content-type" in content.headers);
|
||||
do_check_eq("text/plain", content.headers["content-type"]);
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,9 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-sync/engines/tabs.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://testing-common/services-common/utils.js");
|
||||
|
||||
function test_lastUsed() {
|
||||
let store = new TabEngine()._store;
|
||||
@ -80,7 +84,7 @@ function fakeSessionSvc(url, numtabs) {
|
||||
if (numtabs) {
|
||||
let tabs = obj.windows[0].tabs;
|
||||
for (let i = 0; i < numtabs-1; i++)
|
||||
tabs.push(deepCopy(tabs[0]));
|
||||
tabs.push(TestingUtils.deepCopy(tabs[0]));
|
||||
}
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ tail =
|
||||
|
||||
# util contains a bunch of functionality used throughout.
|
||||
[test_utils_catch.js]
|
||||
[test_utils_deepCopy.js]
|
||||
[test_utils_deepEquals.js]
|
||||
[test_utils_deferGetSet.js]
|
||||
[test_utils_deriveKey.js]
|
||||
@ -25,6 +24,7 @@ tail =
|
||||
[test_utils_passphrase.js]
|
||||
|
||||
# We have a number of other libraries that are pretty much standalone.
|
||||
[test_addon_utils.js]
|
||||
[test_httpd_sync_server.js]
|
||||
[test_jpakeclient.js]
|
||||
# Bug 618233: this test produces random failures on Windows 7.
|
||||
|
@ -11,7 +11,7 @@ Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource://gre/modules/AddonRepository.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://services-common/async.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/addonutils.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://tps/logger.jsm");
|
||||
|
||||
@ -59,8 +59,7 @@ Addon.prototype = {
|
||||
Logger.AssertTrue(!!addon, 'could not find addon ' + this.id + ' to uninstall');
|
||||
|
||||
cb = Async.makeSpinningCallback();
|
||||
let store = Engines.get("addons")._store;
|
||||
store.uninstallAddon(addon, cb);
|
||||
AddonUtils.uninstallAddon(addon, cb);
|
||||
cb.wait();
|
||||
},
|
||||
|
||||
@ -97,11 +96,7 @@ Addon.prototype = {
|
||||
// for the addon's install .xml; we'll read the actual id from the .xml.
|
||||
|
||||
let cb = Async.makeSpinningCallback();
|
||||
// We call the store's APIs for installation because it is simpler. If that
|
||||
// API is broken, it should ideally be caught by an xpcshell test. But, if
|
||||
// TPS tests fail, it's all the same: a genuite reported error.
|
||||
let store = Engines.get("addons")._store;
|
||||
store.installAddons([{id: this.id}], cb);
|
||||
AddonUtils.installAddons([{id: this.id, requireSecureURI: false}], cb);
|
||||
let result = cb.wait();
|
||||
|
||||
Logger.AssertEqual(1, result.installedIDs.length, "Exactly 1 add-on was installed.");
|
||||
@ -121,9 +116,8 @@ Addon.prototype = {
|
||||
throw new Error("Unknown flag to setEnabled: " + flag);
|
||||
}
|
||||
|
||||
let store = Engines.get("addons")._store;
|
||||
let cb = Async.makeSpinningCallback();
|
||||
store.updateUserDisabled(this.addon, userDisabled, cb);
|
||||
AddonUtils.updateUserDisabled(this.addon, userDisabled, cb);
|
||||
cb.wait();
|
||||
|
||||
return true;
|
||||
|
Loading…
Reference in New Issue
Block a user