Bug 467890 - Support automatic updates for app-shipped search plugins. r=gavin

This commit is contained in:
Ryan Flint 2009-01-29 00:32:10 -05:00
parent b6f260e8f7
commit 76601bc779
2 changed files with 112 additions and 34 deletions

View File

@ -276,9 +276,6 @@ pref("browser.search.defaultenginename", "chrome://browser-region/locale/re
// disable logging for the search service by default
pref("browser.search.log", false);
// serialize search plugin XML
pref("browser.search.cache.enabled", true);
// Ordering of Search Engines in the Engine list.
pref("browser.search.order.1", "chrome://browser-region/locale/region.properties");
pref("browser.search.order.2", "chrome://browser-region/locale/region.properties");

View File

@ -131,6 +131,7 @@ const MOZSEARCH_LOCALNAME = "SearchPlugin";
const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
const URLTYPE_SEARCH_HTML = "text/html";
const URLTYPE_OPENSEARCH = "application/opensearchdescription+xml";
// Empty base document used to serialize engines to file.
const EMPTY_DOC = "<?xml version=\"1.0\"?>\n" +
@ -825,6 +826,7 @@ function EngineURL(aType, aMethod, aTemplate) {
this.type = type;
this.method = method;
this.params = [];
this.rels = [];
// Don't serialize expanded mozparams
this.mozparams = {};
@ -892,10 +894,15 @@ EngineURL.prototype = {
return new Submission(makeURI(url), postData);
},
_hasRelation: function SRC_EURL__hasRelation(aRel)
this.rels.some(function(e) e == aRel.toLowerCase()),
_initWithJSON: function SRC_EURL__initWithJSON(aJson, aEngine) {
if (!aJson.params)
return;
this.rels = aJson.rels;
for (let i = 0; i < aJson.params.length; ++i) {
let param = aJson.params[i];
if (param.mozparam) {
@ -922,6 +929,7 @@ EngineURL.prototype = {
_serializeToJSON: function SRCH_EURL__serializeToJSON() {
var json = {
template: this.template,
rels: this.rels
};
if (this.type != URLTYPE_SEARCH_HTML)
@ -950,6 +958,8 @@ EngineURL.prototype = {
url.setAttribute("type", this.type);
url.setAttribute("method", this.method);
url.setAttribute("template", this.template);
if (this.rels.length)
url.setAttribute("rel", this.rels.join(" "));
for (var i = 0; i < this.params.length; ++i) {
var param = aDoc.createElementNS(OPENSEARCH_NS_11, "Param");
@ -1066,6 +1076,17 @@ Engine.prototype = {
_iconUpdateURL: null,
// A reference to the timer used for lazily serializing the engine to file
_serializeTimer: null,
// Whether this engine has been used since the cache was last recreated.
__used: null,
get _used() {
if (!this.__used)
this.__used = !!engineMetadataService.getAttr(this, "used");
return this.__used;
},
set _used(aValue) {
this.__used = aValue
engineMetadataService.setAttr(this, "used", aValue);
},
/**
* Retrieves the data from the engine's file. If the engine's dataType is
@ -1299,6 +1320,29 @@ Engine.prototype = {
engineMetadataService.setAttr(aEngine, "updatelastmodified",
(new Date()).toUTCString());
// If we're updating an app-shipped engine, ensure that the updateURLs
// are the same.
if (engineToUpdate._isInAppDir) {
let oldUpdateURL = engineToUpdate._updateURL;
let newUpdateURL = aEngine._updateURL;
let oldSelfURL = engineToUpdate._getURLOfType(URLTYPE_OPENSEARCH);
if (oldSelfURL && oldSelfURL._hasRelation("self")) {
oldUpdateURL = oldSelfURL.template;
let newSelfURL = aEngine._getURLOfType(URLTYPE_OPENSEARCH);
if (!newSelfURL || !newSelfURL._hasRelation("self")) {
LOG("_onLoad: updateURL missing in updated engine for " +
aEngine.name + " aborted");
return;
}
newUpdateURL = newSelfURL.template;
}
if (oldUpdateURL != newUpdateURL) {
LOG("_onLoad: updateURLs do not match! Update of " + aEngine.name + " aborted");
return;
}
}
// Set the new engine's icon, if it doesn't yet have one.
if (!aEngine._iconURI && engineToUpdate._iconURI)
aEngine._iconURI = engineToUpdate._iconURI;
@ -1308,8 +1352,10 @@ Engine.prototype = {
aEngine._useNow = false;
}
// Write the engine to file
aEngine._serializeToFile();
// Write the engine to file. For readOnly engines, they'll be stored in the
// cache following the notification below.
if (!aEngine._readOnly)
aEngine._serializeToFile();
// Notify the search service of the successful load. It will deal with
// updates by checking aEngine._engineToUpdate.
@ -1375,8 +1421,9 @@ Engine.prototype = {
// The engine might not have a file yet, if it's being downloaded,
// because the request for the engine file itself (_onLoad) may not
// yet be complete. In that case, this change will be written to
// file when _onLoad is called.
if (aEngine._file)
// file when _onLoad is called. For readonly engines, we'll store
// the changes in the cache once notified below.
if (aEngine._file && !aEngine._readOnly)
aEngine._serializeToFile();
notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
@ -1483,6 +1530,9 @@ Engine.prototype = {
Cr.NS_ERROR_FAILURE);
}
if (aElement.hasAttribute("rel"))
url.rels = aElement.getAttribute("rel").toLowerCase().split(/\s+/);
for (var i = 0; i < aElement.childNodes.length; ++i) {
var param = aElement.childNodes[i];
if (param.localName == "Param") {
@ -2216,7 +2266,9 @@ Engine.prototype = {
get _hasUpdates() {
// Whether or not the engine has an update URL
return !!(this._updateURL || this._iconUpdateURL);
let selfURL = this._getURLOfType(URLTYPE_OPENSEARCH);
return !!(this._updateURL || this._iconUpdateURL || (selfURL &&
selfURL._hasRelation("self")));
},
get name() {
@ -2271,6 +2323,12 @@ Engine.prototype = {
if (!aResponseType)
aResponseType = URLTYPE_SEARCH_HTML;
// Check for updates on the first use of an app-shipped engine
if (this._isInAppDir && aResponseType == URLTYPE_SEARCH_HTML && !this._used) {
this._used = true;
engineUpdateService.update(this);
}
var url = this._getURLOfType(aResponseType);
if (!url)
@ -2604,6 +2662,8 @@ SearchService.prototype = {
try {
addedEngine = new Engine(file, dataType, !isWritable);
addedEngine._initFromFile();
if (addedEngine._used)
addedEngine._used = false;
} catch (ex) {
LOG("_loadEnginesFromDir: Failed to load " + file.path + "!\n" + ex);
continue;
@ -3322,6 +3382,49 @@ var engineUpdateService = {
Date.now() + milliseconds);
},
update: function eus_Update(aEngine) {
let engine = aEngine.wrappedJSObject;
ULOG("update called for " + aEngine._name);
if (!getBoolPref(BROWSER_SEARCH_PREF + "update", true) || !engine._hasUpdates)
return;
// We use the cache to store updated app engines, so refuse to update if the
// cache is disabled.
if (engine._readOnly &&
!getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true))
return;
let testEngine = null;
let updateURL = engine._getURLOfType(URLTYPE_OPENSEARCH);
let updateURI = (updateURL && updateURL._hasRelation("self")) ?
updateURL.getSubmission("", engine).uri :
makeURI(engine._updateURL);
if (updateURI) {
if (engine._isDefault && !updateURI.schemeIs("https")) {
ULOG("Invalid scheme for default engine update");
return;
}
let dataType = engineMetadataService.getAttr(engine, "updatedatatype");
if (!dataType) {
ULOG("No loadtype to update engine!");
return;
}
ULOG("updating " + engine.name + " from " + updateURI.spec);
testEngine = new Engine(updateURI, dataType, false);
testEngine._engineToUpdate = engine;
testEngine._initFromURI();
} else
ULOG("invalid updateURI");
if (engine._iconUpdateURL) {
// If we're updating the engine too, use the new engine object,
// otherwise use the existing engine object.
(testEngine || engine)._setIcon(engine._iconUpdateURL, true);
}
},
notify: function eus_Notify(aTimer) {
ULOG("notify called");
@ -3336,16 +3439,14 @@ var engineUpdateService = {
ULOG("currentTime: " + currentTime);
for each (engine in searchService.getEngines({})) {
engine = engine.wrappedJSObject;
if (!engine._hasUpdates || engine._readOnly)
if (!engine._hasUpdates)
continue;
ULOG("checking " + engine.name);
var expirTime = engineMetadataService.getAttr(engine, "updateexpir");
var updateURL = engine._updateURL;
var iconUpdateURL = engine._iconUpdateURL;
ULOG("expirTime: " + expirTime + "\nupdateURL: " + updateURL +
"\niconUpdateURL: " + iconUpdateURL);
ULOG("expirTime: " + expirTime + "\nupdateURL: " + engine._updateURL +
"\niconUpdateURL: " + engine._iconUpdateURL);
var engineExpired = expirTime <= currentTime;
@ -3356,27 +3457,7 @@ var engineUpdateService = {
ULOG(engine.name + " has expired");
var testEngine = null;
var updateURI = makeURI(updateURL);
if (updateURI) {
var dataType = engineMetadataService.getAttr(engine, "updatedatatype")
if (!dataType) {
ULOG("No loadtype to update engine!");
continue;
}
testEngine = new Engine(updateURI, dataType, false);
testEngine._engineToUpdate = engine;
testEngine._initFromURI();
} else
ULOG("invalid updateURI");
if (iconUpdateURL) {
// If we're updating the engine too, use the new engine object,
// otherwise use the existing engine object.
(testEngine || engine)._setIcon(iconUpdateURL, true);
}
this.update(engine);
// Schedule the next update
this.scheduleNextUpdate(engine);