2010-03-23 06:55:17 -07:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License
|
|
|
|
* Version 1.1 (the "License"); you may not use this file except in
|
|
|
|
* compliance with the License. You may obtain a copy of the License
|
|
|
|
* at http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS"
|
|
|
|
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
|
|
|
* the License for the specific language governing rights and
|
|
|
|
* limitations under the License.
|
|
|
|
*
|
|
|
|
* The Original Code is Microsummarizer.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is the Mozilla Foundation.
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2006
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
* Myk Melez <myk@mozilla.org> (Original Author)
|
|
|
|
* Simon Bünzli <zeniko@gmail.com>
|
|
|
|
* Asaf Romano <mano@mozilla.com>
|
|
|
|
* Dan Mills <thunder@mozilla.com>
|
|
|
|
* Ryan Flint <rflint@dslr.net>
|
|
|
|
* Dietrich Ayala <dietrich@mozilla.com>
|
|
|
|
* Marco Bonardo <mak77@bonardo.net>
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the
|
|
|
|
* terms of either the GNU General Public License Version 2 or later
|
|
|
|
* (the "GPL"), or the GNU Lesser General Public License Version 2.1
|
|
|
|
* or later (the "LGPL"), in which case the provisions of the GPL or
|
|
|
|
* the LGPL are applicable instead of those above. If you wish to
|
|
|
|
* allow use of your version of this file only under the terms of
|
|
|
|
* either the GPL or the LGPL, and not to allow others to use your
|
|
|
|
* version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the
|
|
|
|
* notice and other provisions required by the GPL or the LGPL. If you
|
|
|
|
* do not delete the provisions above, a recipient may use your
|
|
|
|
* version of this file under the terms of any one of the MPL, the GPL
|
|
|
|
* or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
const Cr = Components.results;
|
2007-06-30 20:35:19 -07:00
|
|
|
const Cu = Components.utils;
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
const PERMS_FILE = 0644;
|
|
|
|
const MODE_WRONLY = 0x02;
|
|
|
|
const MODE_CREATE = 0x08;
|
|
|
|
const MODE_TRUNCATE = 0x20;
|
|
|
|
|
|
|
|
const NS_ERROR_MODULE_DOM = 2152923136;
|
|
|
|
const NS_ERROR_DOM_BAD_URI = NS_ERROR_MODULE_DOM + 1012;
|
|
|
|
|
|
|
|
// How often to check for microsummaries that need updating, in milliseconds.
|
|
|
|
const CHECK_INTERVAL = 15 * 1000; // 15 seconds
|
|
|
|
|
2010-03-23 06:55:22 -07:00
|
|
|
// The default value of the update interval, used if there is no user's pref.
|
|
|
|
const DEFAULT_UPDATE_INTERVAL_MINUTES = 30;
|
|
|
|
|
2007-09-28 23:06:18 -07:00
|
|
|
const MICSUM_NS = "http://www.mozilla.org/microsummaries/0.1";
|
|
|
|
const XSLT_NS = "http://www.w3.org/1999/XSL/Transform";
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2007-09-17 14:20:14 -07:00
|
|
|
const ANNO_MICSUM_GEN_URI = "microsummary/generatorURI";
|
|
|
|
const ANNO_MICSUM_EXPIRATION = "microsummary/expiration";
|
|
|
|
const ANNO_STATIC_TITLE = "bookmarks/staticTitle";
|
|
|
|
const ANNO_CONTENT_TYPE = "bookmarks/contentType";
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
const MAX_SUMMARY_LENGTH = 4096;
|
2010-03-23 06:55:22 -07:00
|
|
|
const MAX_GENERATOR_NAME_LENGTH = 60;
|
|
|
|
const MIN_GENERATOR_NAME_LENGTH = 6;
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2010-03-23 06:55:19 -07:00
|
|
|
const USER_MICROSUMMARY_GENS_DIR = "microsummary-generators";
|
|
|
|
|
2010-04-22 05:53:54 -07:00
|
|
|
const TOPIC_SHUTDOWN = "places-shutdown";
|
|
|
|
|
2007-06-30 20:35:19 -07:00
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
|
2010-03-23 06:55:22 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
|
2009-10-03 14:48:43 -07:00
|
|
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
|
|
|
return NetUtil;
|
|
|
|
});
|
|
|
|
|
2010-04-16 14:51:33 -07:00
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
2009-10-03 14:48:43 -07:00
|
|
|
|
2007-11-14 23:33:00 -08:00
|
|
|
function MicrosummaryService() {
|
2010-04-22 05:53:54 -07:00
|
|
|
Services.obs.addObserver(this, TOPIC_SHUTDOWN, true);
|
2007-11-14 23:33:00 -08:00
|
|
|
|
2010-03-23 06:55:22 -07:00
|
|
|
this._ans = Cc["@mozilla.org/browser/annotation-service;1"].
|
|
|
|
getService(Ci.nsIAnnotationService);
|
|
|
|
this._ans.addObserver(this, false);
|
|
|
|
Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
|
|
|
|
getBranch("browser.microsummary.").
|
|
|
|
QueryInterface(Ci.nsIPrefBranch2).
|
|
|
|
addObserver("", this, true);
|
2007-11-14 23:33:00 -08:00
|
|
|
this._initTimers();
|
|
|
|
this._cacheLocalGenerators();
|
|
|
|
}
|
2010-04-16 14:51:33 -07:00
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
MicrosummaryService.prototype = {
|
|
|
|
get _bms() {
|
2009-06-12 15:26:04 -07:00
|
|
|
var svc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
|
|
|
|
getService(Ci.nsINavBookmarksService);
|
|
|
|
this.__defineGetter__("_bms", function() svc);
|
|
|
|
return this._bms;
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
2010-04-16 14:51:33 -07:00
|
|
|
|
2010-03-23 06:55:22 -07:00
|
|
|
// The update interval as specified by the user.
|
2007-03-22 10:30:00 -07:00
|
|
|
get _updateInterval() {
|
2010-03-23 06:55:22 -07:00
|
|
|
var updateInterval = getPref("browser.microsummary.updateInterval",
|
|
|
|
DEFAULT_UPDATE_INTERVAL_MINUTES);
|
2007-03-22 10:30:00 -07:00
|
|
|
// the minimum update interval is 1 minute
|
|
|
|
return Math.max(updateInterval, 1) * 60 * 1000;
|
|
|
|
},
|
|
|
|
|
|
|
|
// A cache of local microsummary generators. This gets built on startup
|
|
|
|
// by the _cacheLocalGenerators() method.
|
|
|
|
_localGenerators: {},
|
|
|
|
|
|
|
|
// The timer that periodically checks for microsummaries needing updating.
|
|
|
|
_timer: null,
|
|
|
|
|
2007-06-30 20:35:19 -07:00
|
|
|
// XPCOM registration
|
|
|
|
classID: Components.ID("{460a9792-b154-4f26-a922-0f653e2c8f91}"),
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummaryService,
|
2009-09-18 11:03:27 -07:00
|
|
|
Ci.nsITimerCallback,
|
2007-06-30 20:35:19 -07:00
|
|
|
Ci.nsISupportsWeakReference,
|
2009-06-12 15:26:04 -07:00
|
|
|
Ci.nsIAnnotationObserver,
|
2007-06-30 20:35:19 -07:00
|
|
|
Ci.nsIObserver]),
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// nsIObserver
|
|
|
|
observe: function MSS_observe(subject, topic, data) {
|
|
|
|
switch (topic) {
|
2010-04-22 05:53:54 -07:00
|
|
|
case TOPIC_SHUTDOWN:
|
2007-03-22 10:30:00 -07:00
|
|
|
this._destroy();
|
|
|
|
break;
|
2007-04-27 13:32:23 -07:00
|
|
|
case "nsPref:changed":
|
|
|
|
if (data == "enabled")
|
2007-08-17 15:44:28 -07:00
|
|
|
this._initTimers();
|
2007-04-27 13:32:23 -07:00
|
|
|
break;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
},
|
2010-03-23 06:55:22 -07:00
|
|
|
// cross-session timer used to periodically check for generator or
|
|
|
|
// microsummaries updates.
|
2009-09-18 11:03:27 -07:00
|
|
|
notify: function MSS_notify(timer) {
|
2010-03-23 06:55:22 -07:00
|
|
|
if (timer == this._timer)
|
|
|
|
this._updateMicrosummaries();
|
|
|
|
else
|
|
|
|
this._updateGenerators();
|
2009-09-18 11:03:27 -07:00
|
|
|
},
|
2007-08-17 15:44:28 -07:00
|
|
|
_initTimers: function MSS__initTimers() {
|
2007-04-27 13:32:23 -07:00
|
|
|
if (this._timer)
|
|
|
|
this._timer.cancel();
|
|
|
|
|
|
|
|
if (!getPref("browser.microsummary.enabled", true))
|
|
|
|
return;
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// Periodically update microsummaries that need updating.
|
|
|
|
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
2010-03-23 06:55:22 -07:00
|
|
|
this._timer.initWithCallback(this, CHECK_INTERVAL,
|
2007-03-22 10:30:00 -07:00
|
|
|
this._timer.TYPE_REPEATING_SLACK);
|
|
|
|
},
|
2010-03-23 06:55:22 -07:00
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
_destroy: function MSS__destroy() {
|
2010-04-22 05:53:54 -07:00
|
|
|
Services.obs.removeObserver(this, TOPIC_SHUTDOWN, true);
|
2009-06-12 15:26:04 -07:00
|
|
|
this._ans.removeObserver(this);
|
2010-03-23 06:55:22 -07:00
|
|
|
Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
|
|
|
|
getBranch("browser.microsummary.").
|
|
|
|
QueryInterface(Ci.nsIPrefBranch2).
|
|
|
|
removeObserver("", this);
|
2007-03-22 10:30:00 -07:00
|
|
|
this._timer.cancel();
|
|
|
|
this._timer = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateMicrosummaries: function MSS__updateMicrosummaries() {
|
|
|
|
var now = Date.now();
|
|
|
|
var updateInterval = this._updateInterval;
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._bookmarks.length; i++) {
|
|
|
|
var bookmarkID = this._bookmarks[i];
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// Skip this page if its microsummary hasn't expired yet.
|
2007-09-17 14:20:14 -07:00
|
|
|
if (this._ans.itemHasAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION) &&
|
|
|
|
this._ans.getItemAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION) > now)
|
2007-03-22 10:30:00 -07:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// Reset the expiration time immediately, so if the refresh is failing
|
|
|
|
// we don't try it every 15 seconds, potentially overloading the server.
|
2007-09-17 14:20:14 -07:00
|
|
|
this._setAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION, now + updateInterval);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// Try to update the microsummary, but trap errors, so an update
|
|
|
|
// that throws doesn't prevent us from updating the rest of them.
|
|
|
|
try {
|
|
|
|
this.refreshMicrosummary(bookmarkID);
|
|
|
|
}
|
|
|
|
catch(ex) {
|
2007-06-30 20:35:19 -07:00
|
|
|
Cu.reportError(ex);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2007-08-17 15:44:28 -07:00
|
|
|
|
|
|
|
_updateGenerators: function MSS__updateGenerators() {
|
|
|
|
var generators = this._localGenerators;
|
|
|
|
var update = getPref("browser.microsummary.updateGenerators", true);
|
|
|
|
if (!generators || !update)
|
|
|
|
return;
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let uri in generators) {
|
2007-08-17 15:44:28 -07:00
|
|
|
generators[uri].update();
|
2010-03-23 06:55:22 -07:00
|
|
|
}
|
2007-08-17 15:44:28 -07:00
|
|
|
},
|
2007-03-22 10:30:00 -07:00
|
|
|
_updateMicrosummary: function MSS__updateMicrosummary(bookmarkID, microsummary) {
|
2007-09-17 14:20:14 -07:00
|
|
|
var title = this._bms.getItemTitle(bookmarkID);
|
2007-07-25 20:31:40 -07:00
|
|
|
|
|
|
|
// Ensure the user-given title is cached
|
2007-09-17 14:20:14 -07:00
|
|
|
if (!this._ans.itemHasAnnotation(bookmarkID, ANNO_STATIC_TITLE))
|
|
|
|
this._setAnnotation(bookmarkID, ANNO_STATIC_TITLE, title);
|
2007-04-27 13:37:52 -07:00
|
|
|
|
|
|
|
// A string identifying the bookmark to use when logging the update.
|
2007-05-10 01:05:19 -07:00
|
|
|
var bookmarkIdentity = bookmarkID;
|
2007-04-27 13:37:52 -07:00
|
|
|
|
2007-07-25 20:31:40 -07:00
|
|
|
// Update if the microsummary differs from the current title.
|
|
|
|
if (!title || title != microsummary.content) {
|
2007-09-17 14:20:14 -07:00
|
|
|
this._bms.setItemTitle(bookmarkID, microsummary.content);
|
2007-04-27 13:37:52 -07:00
|
|
|
var subject = new LiveTitleNotificationSubject(bookmarkID, microsummary);
|
|
|
|
LOG("updated live title for " + bookmarkIdentity +
|
2007-07-25 20:31:40 -07:00
|
|
|
" from '" + (title == null ? "<no live title>" : title) +
|
2007-04-27 13:37:52 -07:00
|
|
|
"' to '" + microsummary.content + "'");
|
2010-04-16 14:51:33 -07:00
|
|
|
Services.obs.notifyObservers(subject, "microsummary-livetitle-updated", title);
|
2007-04-27 13:37:52 -07:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
LOG("didn't update live title for " + bookmarkIdentity + "; it hasn't changed");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Whether or not the title itself has changed, we still save any changes
|
|
|
|
// to the update interval, since the interval represents how long to wait
|
|
|
|
// before checking again for updates, and that can vary across updates,
|
|
|
|
// even when the title itself hasn't changed.
|
2007-09-17 14:20:14 -07:00
|
|
|
this._setAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION,
|
|
|
|
Date.now() + (microsummary.updateInterval || this._updateInterval));
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load local generators into the cache.
|
|
|
|
*/
|
|
|
|
_cacheLocalGenerators: function MSS__cacheLocalGenerators() {
|
|
|
|
// Load generators from the user's profile.
|
2010-04-16 14:51:33 -07:00
|
|
|
var msDir = Services.dirsvc.get("ProfDS", Ci.nsIFile);
|
2010-03-23 06:55:19 -07:00
|
|
|
msDir.append(USER_MICROSUMMARY_GENS_DIR);
|
2010-01-22 08:55:58 -08:00
|
|
|
if (msDir.exists())
|
|
|
|
this._cacheLocalGeneratorDir(msDir);
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load local generators from a directory into the cache.
|
|
|
|
*
|
|
|
|
* @param dir
|
|
|
|
* nsIFile object pointing to directory containing generator files
|
|
|
|
*/
|
|
|
|
_cacheLocalGeneratorDir: function MSS__cacheLocalGeneratorDir(dir) {
|
|
|
|
var files = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
|
|
|
|
var file = files.nextFile;
|
|
|
|
|
|
|
|
while (file) {
|
|
|
|
// Recursively load generators so support packs containing
|
|
|
|
// lots of generators can organize them into multiple directories.
|
|
|
|
if (file.isDirectory())
|
|
|
|
this._cacheLocalGeneratorDir(file);
|
|
|
|
else
|
|
|
|
this._cacheLocalGeneratorFile(file);
|
|
|
|
|
|
|
|
file = files.nextFile;
|
|
|
|
}
|
|
|
|
files.close();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load a local generator from a file into the cache.
|
|
|
|
*
|
|
|
|
* @param file
|
|
|
|
* nsIFile object pointing to file from which to load generator
|
|
|
|
*/
|
|
|
|
_cacheLocalGeneratorFile: function MSS__cacheLocalGeneratorFile(file) {
|
2009-10-03 14:48:43 -07:00
|
|
|
var uri = NetUtil.ioService.newFileURI(file);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
var t = this;
|
|
|
|
var callback =
|
|
|
|
function MSS_cacheLocalGeneratorCallback(resource) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
t._handleLocalGenerator(resource);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
resource.destroy();
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
};
|
|
|
|
var resource = new MicrosummaryResource(uri);
|
|
|
|
resource.load(callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
_handleLocalGenerator: function MSS__handleLocalGenerator(resource) {
|
|
|
|
if (!resource.isXML)
|
|
|
|
throw(resource.uri.spec + " microsummary generator loaded, but not XML");
|
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
var generator = new MicrosummaryGenerator(null, resource.uri);
|
2007-03-22 10:30:00 -07:00
|
|
|
generator.initFromXML(resource.content);
|
|
|
|
|
|
|
|
// Add the generator to the local generators cache.
|
|
|
|
// XXX Figure out why Firefox crashes on shutdown if we index generators
|
|
|
|
// by uri.spec but doesn't crash if we index by uri.spec.split().join().
|
|
|
|
//this._localGenerators[generator.uri.spec] = generator;
|
|
|
|
this._localGenerators[generator.uri.spec.split().join()] = generator;
|
|
|
|
|
|
|
|
LOG("loaded local microsummary generator\n" +
|
|
|
|
" file: " + generator.localURI.spec + "\n" +
|
|
|
|
" ID: " + generator.uri.spec);
|
|
|
|
},
|
|
|
|
|
|
|
|
// nsIMicrosummaryService
|
|
|
|
|
2007-05-15 16:28:22 -07:00
|
|
|
/**
|
|
|
|
* Return a microsummary generator for the given URI.
|
|
|
|
*
|
|
|
|
* @param generatorURI
|
|
|
|
* the URI of the generator
|
|
|
|
*/
|
|
|
|
getGenerator: function MSS_getGenerator(generatorURI) {
|
|
|
|
return this._localGenerators[generatorURI.spec] ||
|
2010-03-23 06:55:22 -07:00
|
|
|
new MicrosummaryGenerator(generatorURI);
|
2007-05-15 16:28:22 -07:00
|
|
|
},
|
2007-03-22 10:30:00 -07:00
|
|
|
/**
|
|
|
|
* Install the microsummary generator from the resource at the supplied URI.
|
|
|
|
* Callable by content via the addMicrosummaryGenerator() sidebar method.
|
|
|
|
*
|
|
|
|
* @param generatorURI
|
|
|
|
* the URI of the resource providing the generator
|
|
|
|
*/
|
|
|
|
addGenerator: function MSS_addGenerator(generatorURI) {
|
|
|
|
var t = this;
|
|
|
|
var callback =
|
|
|
|
function MSS_addGeneratorCallback(resource) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
t._handleNewGenerator(resource);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
resource.destroy();
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
};
|
|
|
|
var resource = new MicrosummaryResource(generatorURI);
|
|
|
|
resource.load(callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
_handleNewGenerator: function MSS__handleNewGenerator(resource) {
|
|
|
|
if (!resource.isXML)
|
|
|
|
throw(resource.uri.spec + " microsummary generator loaded, but not XML");
|
|
|
|
|
|
|
|
// XXX Make sure it's a valid microsummary generator.
|
|
|
|
|
|
|
|
var rootNode = resource.content.documentElement;
|
|
|
|
|
|
|
|
// Add a reference to the URI from which we got this generator so we have
|
|
|
|
// a unique identifier for the generator and also so we can check back later
|
|
|
|
// for updates.
|
|
|
|
rootNode.setAttribute("uri", "urn:source:" + resource.uri.spec);
|
|
|
|
|
|
|
|
this.installGenerator(resource.content);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Install a microsummary generator from the given XML definition.
|
|
|
|
*
|
|
|
|
* @param xmlDefinition
|
|
|
|
* an nsIDOMDocument XML document defining the generator
|
|
|
|
*
|
|
|
|
* @returns the newly-installed nsIMicrosummaryGenerator generator
|
|
|
|
*/
|
|
|
|
installGenerator: function MSS_installGenerator(xmlDefinition) {
|
|
|
|
var rootNode = xmlDefinition.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
|
|
|
|
|
|
|
|
var generatorID = rootNode.getAttribute("uri");
|
|
|
|
|
|
|
|
// The existing cache entry for this generator, if it is already installed.
|
|
|
|
var generator = this._localGenerators[generatorID];
|
|
|
|
|
|
|
|
var topic;
|
2007-08-17 15:44:28 -07:00
|
|
|
if (generator)
|
2007-03-22 10:30:00 -07:00
|
|
|
topic = "microsummary-generator-updated";
|
|
|
|
else {
|
|
|
|
// This generator is not already installed. Save it as a new file.
|
2007-08-17 15:44:28 -07:00
|
|
|
topic = "microsummary-generator-installed";
|
2007-03-22 10:30:00 -07:00
|
|
|
var generatorName = rootNode.getAttribute("name");
|
|
|
|
var fileName = sanitizeName(generatorName) + ".xml";
|
2010-04-16 14:51:33 -07:00
|
|
|
var file = Services.dirsvc.get("ProfDS", Ci.nsIFile);
|
2010-03-23 06:55:19 -07:00
|
|
|
file.append(USER_MICROSUMMARY_GENS_DIR);
|
|
|
|
if (!file.exists() || !file.isDirectory()) {
|
|
|
|
file.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
|
|
|
|
}
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
file.append(fileName);
|
|
|
|
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
|
2009-10-03 14:48:43 -07:00
|
|
|
generator = new MicrosummaryGenerator(null,
|
|
|
|
NetUtil.ioService.newFileURI(file));
|
2007-03-22 10:30:00 -07:00
|
|
|
this._localGenerators[generatorID] = generator;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize (or reinitialize) the generator from its XML definition,
|
|
|
|
// the save the definition to the generator's file.
|
|
|
|
generator.initFromXML(xmlDefinition);
|
2007-08-17 15:44:28 -07:00
|
|
|
generator.saveXMLToFile(xmlDefinition);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
LOG("installed generator " + generatorID);
|
|
|
|
|
2010-04-16 14:51:33 -07:00
|
|
|
Services.obs.notifyObservers(generator, topic, null);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
return generator;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the set of microsummaries available for a given page. The set
|
|
|
|
* might change after this method returns, since this method will trigger
|
|
|
|
* an asynchronous load of the page in question (if it isn't already loaded)
|
|
|
|
* to see if it references any page-specific microsummaries.
|
|
|
|
*
|
|
|
|
* If the caller passes a bookmark ID, and one of the microsummaries
|
|
|
|
* is the current one for the bookmark, this method will retrieve content
|
|
|
|
* from the datastore for that microsummary, which is useful when callers
|
|
|
|
* want to display a list of microsummaries for a page that isn't loaded,
|
|
|
|
* and they want to display the actual content of the selected microsummary
|
|
|
|
* immediately (rather than after the content is asynchronously loaded).
|
|
|
|
*
|
|
|
|
* @param pageURI
|
|
|
|
* the URI of the page for which to retrieve available microsummaries
|
|
|
|
*
|
|
|
|
* @param bookmarkID (optional)
|
|
|
|
* the ID of the bookmark for which this method is being called
|
|
|
|
*
|
|
|
|
* @returns an nsIMicrosummarySet of nsIMicrosummaries for the given page
|
|
|
|
*/
|
|
|
|
getMicrosummaries: function MSS_getMicrosummaries(pageURI, bookmarkID) {
|
|
|
|
var microsummaries = new MicrosummarySet();
|
|
|
|
|
2007-04-27 13:32:23 -07:00
|
|
|
if (!getPref("browser.microsummary.enabled", true))
|
|
|
|
return microsummaries;
|
2007-03-22 10:30:00 -07:00
|
|
|
// Get microsummaries defined by local generators.
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let genURISpec in this._localGenerators) {
|
2007-03-22 10:30:00 -07:00
|
|
|
var generator = this._localGenerators[genURISpec];
|
|
|
|
if (generator.appliesToURI(pageURI)) {
|
|
|
|
var microsummary = new Microsummary(pageURI, generator);
|
|
|
|
|
|
|
|
// If this is the current microsummary for this bookmark, load the content
|
|
|
|
// from the datastore so it shows up immediately in microsummary picking UI.
|
2007-05-10 01:05:19 -07:00
|
|
|
if (bookmarkID != -1 && this.isMicrosummary(bookmarkID, microsummary))
|
2007-09-17 14:20:14 -07:00
|
|
|
microsummary._content = this._bms.getItemTitle(bookmarkID);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
microsummaries.AppendElement(microsummary);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
// If a bookmark identifier has been provided, list its microsummary
|
|
|
|
// synchronously, if any.
|
2007-05-10 01:05:19 -07:00
|
|
|
if (bookmarkID != -1 && this.hasMicrosummary(bookmarkID)) {
|
2007-03-22 16:13:57 -07:00
|
|
|
var currentMicrosummary = this.getMicrosummary(bookmarkID);
|
|
|
|
if (!microsummaries.hasItemForMicrosummary(currentMicrosummary))
|
|
|
|
microsummaries.AppendElement(currentMicrosummary);
|
|
|
|
}
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
// Get microsummaries defined by the page. If we don't have the page,
|
|
|
|
// download it asynchronously, and then finish populating the set.
|
|
|
|
var resource = getLoadedMicrosummaryResource(pageURI);
|
|
|
|
if (resource) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
microsummaries.extractFromPage(resource);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
resource.destroy();
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Load the page with a callback that will add the page's microsummaries
|
|
|
|
// to the set once the page has loaded.
|
|
|
|
var callback = function MSS_extractFromPageCallback(resource) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
microsummaries.extractFromPage(resource);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
resource.destroy();
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
};
|
|
|
|
try {
|
|
|
|
resource = new MicrosummaryResource(pageURI);
|
|
|
|
resource.load(callback);
|
|
|
|
}
|
|
|
|
catch(e) {
|
|
|
|
// We don't have to do anything special if the call fails besides
|
|
|
|
// destroying the Resource object. We can just return the list
|
|
|
|
// of microsummaries without including page-defined microsummaries.
|
|
|
|
if (resource)
|
|
|
|
resource.destroy();
|
|
|
|
LOG("error downloading page to extract its microsummaries: " + e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return microsummaries;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Change all occurrences of a specific value in a given field to a new value.
|
|
|
|
*
|
|
|
|
* @param fieldName
|
|
|
|
* the name of the field whose values should be changed
|
|
|
|
* @param oldValue
|
|
|
|
* the value that should be changed
|
|
|
|
* @param newValue
|
|
|
|
* the value to which it should be changed
|
|
|
|
*/
|
|
|
|
_changeField: function MSS__changeField(fieldName, oldValue, newValue) {
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._bookmarks.length; i++) {
|
|
|
|
var bookmarkID = this._bookmarks[i];
|
2007-09-17 14:20:14 -07:00
|
|
|
if (this._ans.itemHasAnnotation(bookmarkID, fieldName) &&
|
|
|
|
this._ans.getItemAnnotation(bookmarkID, fieldName) == oldValue)
|
|
|
|
this._setAnnotation(bookmarkID, fieldName, newValue);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2009-06-12 15:26:04 -07:00
|
|
|
* Get the set of bookmarks that have microsummaries.
|
2007-03-22 10:30:00 -07:00
|
|
|
*
|
2009-06-12 15:26:04 -07:00
|
|
|
* This caches the list of microsummarized bookmarks. The cache is
|
|
|
|
* managed by observing the annotation service, and updating
|
|
|
|
* when a microsummary annotation is added or removed.
|
2007-03-22 10:30:00 -07:00
|
|
|
*
|
2009-06-12 15:26:04 -07:00
|
|
|
* @returns an array of item ids for microsummarized bookmarks
|
2007-03-22 10:30:00 -07:00
|
|
|
*/
|
2009-06-12 15:26:04 -07:00
|
|
|
get _bookmarks() {
|
2009-11-02 13:04:15 -08:00
|
|
|
var bookmarks = this._ans.getItemsWithAnnotation(ANNO_MICSUM_GEN_URI);
|
2009-06-12 15:26:04 -07:00
|
|
|
this.__defineGetter__("_bookmarks", function() bookmarks);
|
|
|
|
return this._bookmarks;
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
2007-09-17 14:20:14 -07:00
|
|
|
_setAnnotation: function MSS__setAnnotation(aBookmarkId, aFieldName, aFieldValue) {
|
2007-07-11 02:57:59 -07:00
|
|
|
this._ans.setItemAnnotation(aBookmarkId,
|
|
|
|
aFieldName,
|
|
|
|
aFieldValue,
|
|
|
|
0,
|
|
|
|
this._ans.EXPIRE_NEVER);
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the set of bookmarks with microsummaries.
|
|
|
|
*
|
|
|
|
* This is the external version of this method and is accessible via XPCOM.
|
2009-06-12 15:26:04 -07:00
|
|
|
* Use it outside this component. Inside the component, use _bookmarks
|
2007-03-22 10:30:00 -07:00
|
|
|
* (with underscore prefix) instead for performance.
|
|
|
|
*
|
|
|
|
* @returns an nsISimpleEnumerator enumeration of bookmark IDs
|
|
|
|
*/
|
|
|
|
getBookmarks: function MSS_getBookmarks() {
|
2009-06-12 15:26:04 -07:00
|
|
|
return new ArrayEnumerator(this._bookmarks);
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current microsummary for the given bookmark.
|
|
|
|
*
|
|
|
|
* @param bookmarkID
|
|
|
|
* the bookmark for which to get the current microsummary
|
|
|
|
*
|
|
|
|
* @returns the current microsummary for the bookmark, or null
|
|
|
|
* if the bookmark does not have a current microsummary
|
|
|
|
*/
|
|
|
|
getMicrosummary: function MSS_getMicrosummary(bookmarkID) {
|
|
|
|
if (!this.hasMicrosummary(bookmarkID))
|
|
|
|
return null;
|
|
|
|
|
2007-09-17 14:20:14 -07:00
|
|
|
var pageURI = this._bms.getBookmarkURI(bookmarkID);
|
2009-10-03 14:48:43 -07:00
|
|
|
var generatorURI = NetUtil.newURI(this._ans.getItemAnnotation(bookmarkID,
|
|
|
|
ANNO_MICSUM_GEN_URI));
|
2007-05-15 16:28:22 -07:00
|
|
|
var generator = this.getGenerator(generatorURI);
|
|
|
|
|
|
|
|
return new Microsummary(pageURI, generator);
|
|
|
|
},
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2007-05-15 16:28:22 -07:00
|
|
|
/**
|
|
|
|
* Get a microsummary for a given page URI and generator URI.
|
|
|
|
*
|
|
|
|
* @param pageURI
|
|
|
|
* the URI of the page to be summarized
|
|
|
|
*
|
|
|
|
* @param generatorURI
|
|
|
|
* the URI of the microsummary generator
|
|
|
|
*
|
|
|
|
* @returns an nsIMicrosummary for the given page and generator URIs.
|
|
|
|
*/
|
|
|
|
createMicrosummary: function MSS_createMicrosummary(pageURI, generatorURI) {
|
|
|
|
var generator = this.getGenerator(generatorURI);
|
2007-03-22 16:13:57 -07:00
|
|
|
return new Microsummary(pageURI, generator);
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the current microsummary for the given bookmark.
|
|
|
|
*
|
|
|
|
* @param bookmarkID
|
|
|
|
* the bookmark for which to set the current microsummary
|
|
|
|
*
|
|
|
|
* @param microsummary
|
|
|
|
* the microsummary to set as the current one
|
|
|
|
*/
|
|
|
|
setMicrosummary: function MSS_setMicrosummary(bookmarkID, microsummary) {
|
2007-09-17 14:20:14 -07:00
|
|
|
this._setAnnotation(bookmarkID, ANNO_MICSUM_GEN_URI, microsummary.generator.uri.spec);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2007-09-17 14:20:14 -07:00
|
|
|
if (microsummary.content)
|
2007-03-22 10:30:00 -07:00
|
|
|
this._updateMicrosummary(bookmarkID, microsummary);
|
2007-09-17 14:20:14 -07:00
|
|
|
else
|
2007-03-22 10:30:00 -07:00
|
|
|
this.refreshMicrosummary(bookmarkID);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the current microsummary for the given bookmark.
|
|
|
|
*
|
|
|
|
* @param bookmarkID
|
|
|
|
* the bookmark for which to remove the current microsummary
|
|
|
|
*/
|
|
|
|
removeMicrosummary: function MSS_removeMicrosummary(bookmarkID) {
|
2007-07-25 20:31:40 -07:00
|
|
|
// Restore the user's title
|
2007-09-17 14:20:14 -07:00
|
|
|
if (this._ans.itemHasAnnotation(bookmarkID, ANNO_STATIC_TITLE))
|
|
|
|
this._bms.setItemTitle(bookmarkID, this._ans.getItemAnnotation(bookmarkID, ANNO_STATIC_TITLE));
|
2007-07-25 20:31:40 -07:00
|
|
|
|
2007-09-17 14:20:14 -07:00
|
|
|
var fields = [ANNO_MICSUM_GEN_URI,
|
|
|
|
ANNO_MICSUM_EXPIRATION,
|
|
|
|
ANNO_STATIC_TITLE,
|
|
|
|
ANNO_CONTENT_TYPE];
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2007-07-25 20:31:40 -07:00
|
|
|
for (let i = 0; i < fields.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
var field = fields[i];
|
2007-09-17 14:20:14 -07:00
|
|
|
if (this._ans.itemHasAnnotation(bookmarkID, field))
|
|
|
|
this._ans.removeItemAnnotation(bookmarkID, field);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not the given bookmark has a current microsummary.
|
|
|
|
*
|
2009-06-12 15:26:04 -07:00
|
|
|
* @param bookmarkId
|
|
|
|
* the bookmark id to check
|
2007-03-22 10:30:00 -07:00
|
|
|
*
|
|
|
|
* @returns a boolean representing whether or not the given bookmark
|
2009-06-12 15:26:04 -07:00
|
|
|
* currently has a microsummary
|
2007-03-22 10:30:00 -07:00
|
|
|
*/
|
2009-06-12 15:26:04 -07:00
|
|
|
hasMicrosummary: function MSS_hasMicrosummary(aBookmarkId) {
|
|
|
|
return (this._bookmarks.indexOf(aBookmarkId) != -1);
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not the given microsummary is the current microsummary
|
|
|
|
* for the given bookmark.
|
|
|
|
*
|
|
|
|
* @param bookmarkID
|
|
|
|
* the bookmark to check
|
|
|
|
*
|
|
|
|
* @param microsummary
|
|
|
|
* the microsummary to check
|
|
|
|
*
|
|
|
|
* @returns whether or not the microsummary is the current one
|
|
|
|
* for the bookmark
|
|
|
|
*/
|
2007-03-22 16:13:57 -07:00
|
|
|
isMicrosummary: function MSS_isMicrosummary(aBookmarkID, aMicrosummary) {
|
|
|
|
if (!aMicrosummary || !aBookmarkID)
|
|
|
|
throw Cr.NS_ERROR_INVALID_ARG;
|
|
|
|
|
|
|
|
if (this.hasMicrosummary(aBookmarkID)) {
|
2009-06-12 15:26:04 -07:00
|
|
|
var currentMicrosummarry = this.getMicrosummary(aBookmarkID);
|
2007-03-22 16:13:57 -07:00
|
|
|
if (aMicrosummary.equals(currentMicrosummarry))
|
|
|
|
return true;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
return false
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Refresh a microsummary, updating its value in the datastore and UI.
|
|
|
|
* If this method can refresh the microsummary instantly, it will.
|
|
|
|
* Otherwise, it'll asynchronously download the necessary information
|
|
|
|
* (the generator and/or page) before refreshing the microsummary.
|
|
|
|
*
|
|
|
|
* Callers should check the "content" property of the returned microsummary
|
|
|
|
* object to distinguish between sync and async refreshes. If its value
|
|
|
|
* is "null", then it's an async refresh, and the caller should register
|
|
|
|
* itself as an nsIMicrosummaryObserver via nsIMicrosummary.addObserver()
|
|
|
|
* to find out when the refresh completes.
|
|
|
|
*
|
|
|
|
* @param bookmarkID
|
|
|
|
* the bookmark whose microsummary is being refreshed
|
|
|
|
*
|
|
|
|
* @returns the microsummary being refreshed
|
|
|
|
*/
|
|
|
|
refreshMicrosummary: function MSS_refreshMicrosummary(bookmarkID) {
|
|
|
|
if (!this.hasMicrosummary(bookmarkID))
|
|
|
|
throw "bookmark " + bookmarkID + " does not have a microsummary";
|
|
|
|
|
2007-09-17 14:20:14 -07:00
|
|
|
var pageURI = this._bms.getBookmarkURI(bookmarkID);
|
2007-03-22 10:30:00 -07:00
|
|
|
if (!pageURI)
|
|
|
|
throw("can't get URL for bookmark with ID " + bookmarkID);
|
2009-10-03 14:48:43 -07:00
|
|
|
var generatorURI = NetUtil.newURI(this._ans.getItemAnnotation(bookmarkID,
|
|
|
|
ANNO_MICSUM_GEN_URI));
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
var generator = this._localGenerators[generatorURI.spec] ||
|
|
|
|
new MicrosummaryGenerator(generatorURI);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
var microsummary = new Microsummary(pageURI, generator);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// A microsummary observer that calls the microsummary service
|
|
|
|
// to update the datastore when the microsummary finishes loading.
|
|
|
|
var observer = {
|
|
|
|
_svc: this,
|
|
|
|
_bookmarkID: bookmarkID,
|
|
|
|
onContentLoaded: function MSS_observer_onContentLoaded(microsummary) {
|
|
|
|
try {
|
|
|
|
this._svc._updateMicrosummary(this._bookmarkID, microsummary);
|
|
|
|
}
|
2009-06-12 15:26:04 -07:00
|
|
|
catch (ex) {
|
|
|
|
Cu.reportError("refreshMicrosummary() observer: " + ex);
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
finally {
|
|
|
|
this._svc = null;
|
|
|
|
this._bookmarkID = null;
|
|
|
|
microsummary.removeObserver(this);
|
|
|
|
}
|
2007-08-17 20:27:22 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
onError: function MSS_observer_onError(microsummary) {
|
|
|
|
if (microsummary.needsRemoval)
|
|
|
|
this._svc.removeMicrosummary(this._bookmarkID);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Register the observer with the microsummary and trigger the microsummary
|
|
|
|
// to update itself.
|
|
|
|
microsummary.addObserver(observer);
|
|
|
|
microsummary.update();
|
|
|
|
|
|
|
|
return microsummary;
|
2009-06-12 15:26:04 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
// nsIAnnotationObserver
|
|
|
|
onItemAnnotationSet: function(aItemId, aAnnotationName) {
|
|
|
|
if (aAnnotationName == ANNO_MICSUM_GEN_URI &&
|
|
|
|
this._bookmarks.indexOf(aItemId) == -1)
|
|
|
|
this._bookmarks.push(aItemId);
|
|
|
|
},
|
|
|
|
onItemAnnotationRemoved: function(aItemId, aAnnotationName) {
|
|
|
|
var index = this._bookmarks.indexOf(aItemId);
|
|
|
|
var isMicsumAnno = aAnnotationName == ANNO_MICSUM_GEN_URI ||
|
|
|
|
!aAnnotationName.length; /* all annos were removed */
|
|
|
|
if (index > -1 && isMicsumAnno)
|
|
|
|
this._bookmarks.splice(index, 1);
|
|
|
|
},
|
|
|
|
onPageAnnotationSet: function(aUri, aAnnotationName) {},
|
|
|
|
onPageAnnotationRemoved: function(aUri, aAnnotationName) {},
|
2007-03-22 10:30:00 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2007-04-27 13:37:52 -07:00
|
|
|
function LiveTitleNotificationSubject(bookmarkID, microsummary) {
|
|
|
|
this.bookmarkID = bookmarkID;
|
|
|
|
this.microsummary = microsummary;
|
|
|
|
}
|
|
|
|
|
|
|
|
LiveTitleNotificationSubject.prototype = {
|
|
|
|
bookmarkID: null,
|
|
|
|
microsummary: null,
|
|
|
|
|
|
|
|
// nsISupports
|
2007-06-30 20:35:19 -07:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsILiveTitleNotificationSubject]),
|
2007-04-27 13:37:52 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
function Microsummary(aPageURI, aGenerator) {
|
2007-03-22 10:30:00 -07:00
|
|
|
this._observers = [];
|
2007-03-22 16:13:57 -07:00
|
|
|
this._pageURI = aPageURI || null;
|
|
|
|
this._generator = aGenerator || null;
|
|
|
|
this._content = null;
|
|
|
|
this._pageContent = null;
|
|
|
|
this._updateInterval = null;
|
2007-08-17 20:27:22 -07:00
|
|
|
this._needsRemoval = false;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
Microsummary.prototype = {
|
|
|
|
get _mss() {
|
2010-03-23 06:55:22 -07:00
|
|
|
var svc = Cc["@mozilla.org/microsummary/service;1"].
|
|
|
|
getService(Ci.nsIMicrosummaryService);
|
|
|
|
this.__defineGetter__("_mss", function() svc);
|
|
|
|
return this._mss;
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
// nsISupports
|
2007-06-30 20:35:19 -07:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummary]),
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// nsIMicrosummary
|
|
|
|
get content() {
|
|
|
|
// If we have everything we need to generate the content, generate it.
|
2010-03-23 06:55:22 -07:00
|
|
|
if (!this._content && this.generator.loaded &&
|
2007-03-22 10:30:00 -07:00
|
|
|
(this.pageContent || !this.generator.needsPageContent)) {
|
|
|
|
this._content = this.generator.generateMicrosummary(this.pageContent);
|
2007-03-22 16:13:57 -07:00
|
|
|
this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Note: we return "null" if the content wasn't already generated and we
|
|
|
|
// couldn't retrieve it from the generated title annotation or generate it
|
|
|
|
// ourselves. So callers shouldn't count on getting content; instead,
|
|
|
|
// they should call update if the return value of this getter is "null",
|
|
|
|
// setting an observer to tell them when content generation is done.
|
|
|
|
return this._content;
|
|
|
|
},
|
2010-03-23 06:55:22 -07:00
|
|
|
get generator() this._generator,
|
|
|
|
set generator(newValue) this._generator = newValue,
|
|
|
|
get pageURI() this._pageURI,
|
2007-03-22 16:13:57 -07:00
|
|
|
equals: function(aOther) {
|
2010-03-23 06:55:22 -07:00
|
|
|
return this._generator &&
|
|
|
|
this._pageURI.equals(aOther.pageURI) &&
|
|
|
|
this._generator.equals(aOther.generator);
|
2007-03-22 16:13:57 -07:00
|
|
|
},
|
2007-03-22 10:30:00 -07:00
|
|
|
get pageContent() {
|
|
|
|
if (!this._pageContent) {
|
|
|
|
// If the page is currently loaded into a browser window, use that.
|
|
|
|
var resource = getLoadedMicrosummaryResource(this.pageURI);
|
|
|
|
if (resource) {
|
|
|
|
this._pageContent = resource.content;
|
|
|
|
resource.destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this._pageContent;
|
|
|
|
},
|
2010-03-23 06:55:22 -07:00
|
|
|
set pageContent(newValue) this._pageContent = newValue,
|
|
|
|
get updateInterval() this._updateInterval,
|
|
|
|
set updateInterval(newValue) this._updateInterval = newValue,
|
|
|
|
get needsRemoval() this._needsRemoval,
|
2007-03-22 10:30:00 -07:00
|
|
|
// nsIMicrosummary
|
|
|
|
addObserver: function MS_addObserver(observer) {
|
|
|
|
// Register the observer, but only if it isn't already registered,
|
|
|
|
// so that we don't call the same observer twice for any given change.
|
|
|
|
if (this._observers.indexOf(observer) == -1)
|
|
|
|
this._observers.push(observer);
|
|
|
|
},
|
|
|
|
|
|
|
|
removeObserver: function MS_removeObserver(observer) {
|
|
|
|
if (this._observers.indexOf(observer) != -1)
|
|
|
|
this._observers.splice(this._observers.indexOf(observer), 1);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Regenerates the microsummary, asynchronously downloading its generator
|
|
|
|
* and content as needed.
|
|
|
|
*/
|
|
|
|
update: function MS_update() {
|
|
|
|
LOG("microsummary.update called for page:\n " + this.pageURI.spec +
|
|
|
|
"\nwith generator:\n " + this.generator.uri.spec);
|
|
|
|
|
|
|
|
var t = this;
|
|
|
|
|
2007-08-17 20:27:22 -07:00
|
|
|
// We use a common error callback here to flag this microsummary for removal
|
|
|
|
// if either the generator or page content have gone permanently missing.
|
|
|
|
var errorCallback = function MS_errorCallback(resource) {
|
|
|
|
if (resource.status == 410) {
|
|
|
|
t._needsRemoval = true;
|
|
|
|
LOG("server indicated " + resource.uri.spec + " is gone. flagging for removal");
|
|
|
|
}
|
|
|
|
resource.destroy();
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < t._observers.length; i++) {
|
2007-08-17 20:27:22 -07:00
|
|
|
t._observers[i].onError(t);
|
2010-03-23 06:55:22 -07:00
|
|
|
}
|
2007-08-17 20:27:22 -07:00
|
|
|
};
|
2007-03-22 10:30:00 -07:00
|
|
|
// If we don't have the generator, download it now. After it downloads,
|
|
|
|
// we'll re-call this method to continue updating the microsummary.
|
|
|
|
if (!this.generator.loaded) {
|
|
|
|
// If this generator is identified by a URN, then it's a local generator
|
|
|
|
// that should have been cached on application start, so it's missing.
|
|
|
|
if (this.generator.uri.scheme == "urn") {
|
|
|
|
// If it was installed via nsSidebar::addMicrosummaryGenerator (i.e. it
|
|
|
|
// has a URN that identifies the source URL from which we installed it),
|
|
|
|
// try to reinstall it (once).
|
|
|
|
if (/^source:/.test(this.generator.uri.path)) {
|
|
|
|
this._reinstallMissingGenerator();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throw "missing local generator: " + this.generator.uri.spec;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG("generator not yet loaded; downloading it");
|
|
|
|
var generatorCallback =
|
|
|
|
function MS_generatorCallback(resource) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
t._handleGeneratorLoad(resource);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
resource.destroy();
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
};
|
|
|
|
var resource = new MicrosummaryResource(this.generator.uri);
|
2007-08-17 20:27:22 -07:00
|
|
|
resource.load(generatorCallback, errorCallback);
|
2007-03-22 10:30:00 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we need the page content, and we don't have it, download it now.
|
|
|
|
// Afterwards we'll re-call this method to continue updating the microsummary.
|
|
|
|
if (this.generator.needsPageContent && !this.pageContent) {
|
|
|
|
LOG("page content not yet loaded; downloading it");
|
|
|
|
var pageCallback =
|
|
|
|
function MS_pageCallback(resource) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
t._handlePageLoad(resource);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
resource.destroy();
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
};
|
|
|
|
var resource = new MicrosummaryResource(this.pageURI);
|
2007-08-17 20:27:22 -07:00
|
|
|
resource.load(pageCallback, errorCallback);
|
2007-03-22 10:30:00 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG("generator (and page, if needed) both loaded; generating microsummary");
|
|
|
|
|
|
|
|
// Now that we have both the generator and (if needed) the page content,
|
|
|
|
// generate the microsummary, then let the observers know about it.
|
2007-03-22 16:13:57 -07:00
|
|
|
this._content = this.generator.generateMicrosummary(this.pageContent);
|
|
|
|
this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
|
2007-03-22 10:30:00 -07:00
|
|
|
this.pageContent = null;
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._observers.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
this._observers[i].onContentLoaded(this);
|
2010-03-23 06:55:22 -07:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
LOG("generated microsummary: " + this.content);
|
|
|
|
},
|
|
|
|
|
|
|
|
_handleGeneratorLoad: function MS__handleGeneratorLoad(resource) {
|
|
|
|
LOG(this.generator.uri.spec + " microsummary generator downloaded");
|
|
|
|
if (resource.isXML)
|
|
|
|
this.generator.initFromXML(resource.content);
|
|
|
|
else if (resource.contentType == "text/plain")
|
|
|
|
this.generator.initFromText(resource.content);
|
|
|
|
else if (resource.contentType == "text/html")
|
|
|
|
this.generator.initFromText(resource.content.body.textContent);
|
|
|
|
else
|
|
|
|
throw("generator is neither XML nor plain text");
|
|
|
|
|
|
|
|
// Only trigger a [content] update if we were able to init the generator.
|
|
|
|
if (this.generator.loaded)
|
|
|
|
this.update();
|
|
|
|
},
|
|
|
|
|
|
|
|
_handlePageLoad: function MS__handlePageLoad(resource) {
|
|
|
|
if (!resource.isXML && resource.contentType != "text/html")
|
|
|
|
throw("page is neither HTML nor XML");
|
|
|
|
|
|
|
|
this.pageContent = resource.content;
|
|
|
|
this.update();
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Try to reinstall a missing local generator that was originally installed
|
2009-06-12 15:26:04 -07:00
|
|
|
* from a URL using nsSidebar::addMicrosummaryGenerator.
|
2007-03-22 10:30:00 -07:00
|
|
|
*/
|
|
|
|
_reinstallMissingGenerator: function MS__reinstallMissingGenerator() {
|
|
|
|
LOG("attempting to reinstall missing generator " + this.generator.uri.spec);
|
|
|
|
|
|
|
|
var t = this;
|
|
|
|
var loadCallback =
|
|
|
|
function MS_missingGeneratorLoadCallback(resource) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
t._handleMissingGeneratorLoad(resource);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
resource.destroy();
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
};
|
|
|
|
var errorCallback =
|
|
|
|
function MS_missingGeneratorErrorCallback(resource) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
t._handleMissingGeneratorError(resource);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
resource.destroy();
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
};
|
|
|
|
try {
|
|
|
|
// Extract the URI from which the generator was originally installed.
|
|
|
|
var sourceURL = this.generator.uri.path.replace(/^source:/, "");
|
2009-10-03 14:48:43 -07:00
|
|
|
var sourceURI = NetUtil.newURI(sourceURL);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
var resource = new MicrosummaryResource(sourceURI);
|
|
|
|
resource.load(loadCallback, errorCallback);
|
|
|
|
}
|
|
|
|
catch(ex) {
|
2007-06-30 20:35:19 -07:00
|
|
|
Cu.reportError(ex);
|
2007-03-22 10:30:00 -07:00
|
|
|
this._handleMissingGeneratorError();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a load event for a missing local generator by trying to reinstall
|
|
|
|
* the generator. If this fails, call _handleMissingGeneratorError to unset
|
|
|
|
* microsummaries for bookmarks using this generator so we don't repeatedly
|
|
|
|
* try to reinstall the generator, creating too much traffic to the website
|
|
|
|
* from which we downloaded it.
|
|
|
|
*
|
|
|
|
* @param resource
|
|
|
|
* the nsIMicrosummaryResource representing the downloaded generator
|
|
|
|
*/
|
|
|
|
_handleMissingGeneratorLoad: function MS__handleMissingGeneratorLoad(resource) {
|
|
|
|
try {
|
|
|
|
// Make sure the generator is XML, since local generators have to be.
|
|
|
|
if (!resource.isXML)
|
|
|
|
throw("downloaded, but not XML " + this.generator.uri.spec);
|
|
|
|
|
|
|
|
// Store the generator's ID in its XML definition.
|
|
|
|
var generatorID = this.generator.uri.spec;
|
|
|
|
resource.content.documentElement.setAttribute("uri", generatorID);
|
|
|
|
|
|
|
|
// Reinstall the generator and replace our placeholder generator object
|
|
|
|
// with the newly installed generator.
|
|
|
|
this.generator = this._mss.installGenerator(resource.content);
|
|
|
|
|
|
|
|
// A reinstalled generator should always be loaded. But just in case
|
|
|
|
// it isn't, throw an error so we don't get into an infinite loop
|
|
|
|
// (otherwise this._update would try again to reinstall it).
|
|
|
|
if (!this.generator.loaded)
|
|
|
|
throw("supposedly installed, but not in cache " + this.generator.uri.spec);
|
2010-03-23 06:55:22 -07:00
|
|
|
|
|
|
|
LOG("reinstall succeeded; resuming update " + this.generator.uri.spec);
|
|
|
|
this.update();
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
catch(ex) {
|
2007-06-30 20:35:19 -07:00
|
|
|
Cu.reportError(ex);
|
2007-03-22 10:30:00 -07:00
|
|
|
this._handleMissingGeneratorError(resource);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle an error event for a missing local generator load by unsetting
|
|
|
|
* the microsummaries for bookmarks using this generator so we don't
|
|
|
|
* repeatedly try to reinstall the generator, creating too much traffic
|
|
|
|
* to the website from which we downloaded it.
|
|
|
|
*
|
|
|
|
* @param resource
|
|
|
|
* the nsIMicrosummaryResource representing the downloaded generator
|
|
|
|
*/
|
|
|
|
_handleMissingGeneratorError: function MS__handleMissingGeneratorError(resource) {
|
|
|
|
LOG("reinstall failed; removing microsummaries " + this.generator.uri.spec);
|
|
|
|
var bookmarks = this._mss.getBookmarks();
|
|
|
|
while (bookmarks.hasMoreElements()) {
|
|
|
|
var bookmarkID = bookmarks.getNext();
|
|
|
|
var microsummary = this._mss.getMicrosummary(bookmarkID);
|
|
|
|
if (microsummary.generator.uri.equals(this.generator.uri)) {
|
|
|
|
LOG("removing microsummary for " + microsummary.pageURI.spec);
|
|
|
|
this._mss.removeMicrosummary(bookmarkID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
function MicrosummaryGenerator(aURI, aLocalURI, aName) {
|
|
|
|
this._uri = aURI || null;
|
|
|
|
this._localURI = aLocalURI || null;
|
|
|
|
this._name = aName || null;
|
|
|
|
this._loaded = false;
|
|
|
|
this._rules = [];
|
|
|
|
this._template = null;
|
|
|
|
this._content = null;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
MicrosummaryGenerator.prototype = {
|
|
|
|
// nsISupports
|
2007-06-30 20:35:19 -07:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummaryGenerator]),
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// nsIMicrosummaryGenerator
|
|
|
|
|
|
|
|
// Normally this is just the URL from which we download the generator,
|
|
|
|
// but for generators stored in the app or profile generators directory
|
|
|
|
// it's the value of the generator tag's "uri" attribute (or its local URI
|
|
|
|
// should the "uri" attribute be missing).
|
2010-03-23 06:55:22 -07:00
|
|
|
get uri() this._uri || this.localURI,
|
2007-03-22 10:30:00 -07:00
|
|
|
// For generators bundled with the browser or installed by the user,
|
|
|
|
// the local URI is the URI of the local file containing the generator XML.
|
2010-03-23 06:55:22 -07:00
|
|
|
get localURI() this._localURI,
|
|
|
|
get name() this._name,
|
|
|
|
get loaded() this._loaded,
|
2007-03-22 16:13:57 -07:00
|
|
|
equals: function(aOther) {
|
|
|
|
// XXX: could the uri attribute for an exposed generator ever be null?
|
|
|
|
return aOther.uri.equals(this.uri);
|
|
|
|
},
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines whether or not the generator applies to a given URI.
|
|
|
|
* By default, the generator does not apply to any URI. In order for it
|
|
|
|
* to apply to a URI, the URI must match one or more of the generator's
|
|
|
|
* "include" rules and not match any of the generator's "exclude" rules.
|
|
|
|
*
|
|
|
|
* @param uri
|
|
|
|
* the URI to test to see if this generator applies to it
|
|
|
|
*
|
|
|
|
* @returns boolean
|
|
|
|
* whether or not the generator applies to the given URI
|
|
|
|
*/
|
|
|
|
appliesToURI: function(uri) {
|
|
|
|
var applies = false;
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._rules.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
var rule = this._rules[i];
|
|
|
|
switch (rule.type) {
|
|
|
|
case "include":
|
|
|
|
if (rule.regexp.test(uri.spec))
|
|
|
|
applies = true;
|
|
|
|
break;
|
|
|
|
case "exclude":
|
|
|
|
if (rule.regexp.test(uri.spec))
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return applies;
|
|
|
|
},
|
|
|
|
|
|
|
|
get needsPageContent() {
|
2007-03-22 16:13:57 -07:00
|
|
|
if (this._template)
|
2007-03-22 10:30:00 -07:00
|
|
|
return true;
|
2007-03-22 16:13:57 -07:00
|
|
|
if (this._content)
|
2007-03-22 10:30:00 -07:00
|
|
|
return false;
|
2007-03-22 16:13:57 -07:00
|
|
|
|
|
|
|
throw("needsPageContent called on uninitialized microsummary generator");
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes a generator from text content. Generators initialized
|
|
|
|
* from text content merely return that content when their generate() method
|
|
|
|
* gets called.
|
|
|
|
*
|
|
|
|
* @param text
|
|
|
|
* the text content
|
|
|
|
*/
|
|
|
|
initFromText: function(text) {
|
2007-03-22 16:13:57 -07:00
|
|
|
this._content = text;
|
|
|
|
this._loaded = true;
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes a generator from an XML description of it.
|
|
|
|
*
|
|
|
|
* @param xmlDocument
|
|
|
|
* An XMLDocument object describing a microsummary generator.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
initFromXML: function(xmlDocument) {
|
|
|
|
// XXX Make sure the argument is a valid generator XML document.
|
|
|
|
|
|
|
|
// XXX I would have wanted to retrieve the info from the XML via E4X,
|
|
|
|
// but we'll need to pass the XSLT transform sheet to the XSLT processor,
|
|
|
|
// and the processor can't deal with an E4X-wrapped template node.
|
|
|
|
|
|
|
|
// XXX Right now the code retrieves the first "generator" element
|
|
|
|
// in the microsummaries namespace, regardless of whether or not
|
|
|
|
// it's the root element. Should it matter?
|
|
|
|
var generatorNode = xmlDocument.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
|
|
|
|
if (!generatorNode)
|
|
|
|
throw Cr.NS_ERROR_FAILURE;
|
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
this._name = generatorNode.getAttribute("name");
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2007-08-17 15:44:28 -07:00
|
|
|
// We have to retrieve the URI from local generators via the "uri" attribute
|
|
|
|
// of its generator tag.
|
|
|
|
if (this.localURI && generatorNode.hasAttribute("uri"))
|
2009-10-03 14:48:43 -07:00
|
|
|
this._uri = NetUtil.newURI(generatorNode.getAttribute("uri"), null, null);
|
2007-03-22 10:30:00 -07:00
|
|
|
function getFirstChildByTagName(tagName, parentNode, namespace) {
|
|
|
|
var nodeList = parentNode.getElementsByTagNameNS(namespace, tagName);
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < nodeList.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
// Make sure that the node is a direct descendent of the generator node
|
|
|
|
if (nodeList[i].parentNode == parentNode)
|
|
|
|
return nodeList[i];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slurp the include/exclude rules that determine the pages to which
|
|
|
|
// this generator applies. Order is important, so we add the rules
|
|
|
|
// in the order in which they appear in the XML.
|
2007-03-22 16:13:57 -07:00
|
|
|
this._rules.splice(0);
|
2007-03-22 10:30:00 -07:00
|
|
|
var pages = getFirstChildByTagName("pages", generatorNode, MICSUM_NS);
|
|
|
|
if (pages) {
|
|
|
|
// XXX Make sure the pages tag exists.
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < pages.childNodes.length ; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
var node = pages.childNodes[i];
|
|
|
|
if (node.nodeType != node.ELEMENT_NODE ||
|
|
|
|
node.namespaceURI != MICSUM_NS ||
|
|
|
|
(node.nodeName != "include" && node.nodeName != "exclude"))
|
|
|
|
continue;
|
|
|
|
var urlRegexp = node.textContent.replace(/^\s+|\s+$/g, "");
|
|
|
|
this._rules.push({ type: node.nodeName, regexp: new RegExp(urlRegexp) });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// allow the generators to set individual update values (even varying
|
|
|
|
// depending on certain XPath expressions)
|
|
|
|
var update = getFirstChildByTagName("update", generatorNode, MICSUM_NS);
|
|
|
|
if (update) {
|
|
|
|
function _parseInterval(string) {
|
|
|
|
// convert from minute fractions to milliseconds
|
|
|
|
// and ensure a minimum value of 1 minute
|
|
|
|
return Math.round(Math.max(parseFloat(string) || 0, 1) * 60 * 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._unconditionalUpdateInterval =
|
|
|
|
update.hasAttribute("interval") ?
|
|
|
|
_parseInterval(update.getAttribute("interval")) : null;
|
|
|
|
// collect the <condition expression="XPath Expression" interval="time"/> clauses
|
|
|
|
this._updateIntervals = new Array();
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < update.childNodes.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
node = update.childNodes[i];
|
|
|
|
if (node.nodeType != node.ELEMENT_NODE || node.namespaceURI != MICSUM_NS ||
|
|
|
|
node.nodeName != "condition")
|
|
|
|
continue;
|
|
|
|
if (!node.getAttribute("expression") || !node.getAttribute("interval")) {
|
|
|
|
LOG("ignoring incomplete conditional update interval for " + this.uri.spec);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this._updateIntervals.push({
|
|
|
|
expression: node.getAttribute("expression"),
|
|
|
|
interval: _parseInterval(node.getAttribute("interval"))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var templateNode = getFirstChildByTagName("template", generatorNode, MICSUM_NS);
|
|
|
|
if (templateNode) {
|
2007-03-22 16:13:57 -07:00
|
|
|
this._template = getFirstChildByTagName("transform", templateNode, XSLT_NS) ||
|
|
|
|
getFirstChildByTagName("stylesheet", templateNode, XSLT_NS);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
// XXX Make sure the template is a valid XSL transform sheet.
|
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
this._loaded = true;
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
generateMicrosummary: function MSD_generateMicrosummary(pageContent) {
|
|
|
|
var content;
|
2007-03-22 16:13:57 -07:00
|
|
|
if (this._content)
|
|
|
|
content = this._content;
|
|
|
|
else if (this._template)
|
2007-03-22 10:30:00 -07:00
|
|
|
content = this._processTemplate(pageContent);
|
2007-03-22 16:13:57 -07:00
|
|
|
else
|
2007-03-22 10:30:00 -07:00
|
|
|
throw("generateMicrosummary called on uninitialized microsummary generator");
|
|
|
|
|
|
|
|
// Clean up the output
|
|
|
|
content = content.replace(/^\s+|\s+$/g, "");
|
|
|
|
if (content.length > MAX_SUMMARY_LENGTH)
|
|
|
|
content = content.substring(0, MAX_SUMMARY_LENGTH);
|
|
|
|
|
|
|
|
return content;
|
|
|
|
},
|
|
|
|
|
|
|
|
calculateUpdateInterval: function MSD_calculateUpdateInterval(doc) {
|
2007-03-22 16:13:57 -07:00
|
|
|
if (this._content || !this._updateIntervals || !doc)
|
2007-03-22 10:30:00 -07:00
|
|
|
return null;
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._updateIntervals.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
try {
|
|
|
|
if (doc.evaluate(this._updateIntervals[i].expression, doc, null,
|
|
|
|
Ci.nsIDOMXPathResult.BOOLEAN_TYPE, null).booleanValue)
|
|
|
|
return this._updateIntervals[i].interval;
|
|
|
|
}
|
|
|
|
catch (ex) {
|
2007-06-30 20:35:19 -07:00
|
|
|
Cu.reportError(ex);
|
2007-03-22 10:30:00 -07:00
|
|
|
// remove the offending conditional update interval
|
|
|
|
this._updateIntervals.splice(i--, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this._unconditionalUpdateInterval;
|
|
|
|
},
|
|
|
|
|
|
|
|
_processTemplate: function MSD__processTemplate(doc) {
|
2007-03-22 16:13:57 -07:00
|
|
|
LOG("processing template " + this._template + " against document " + doc);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// XXX Should we just have one global instance of the processor?
|
|
|
|
var processor = Cc["@mozilla.org/document-transformer;1?type=xslt"].
|
|
|
|
createInstance(Ci.nsIXSLTProcessor);
|
|
|
|
|
|
|
|
// Turn off document loading of all kinds (document(), <include>, <import>)
|
|
|
|
// for security (otherwise local generators would be able to load local files).
|
|
|
|
processor.flags |= Ci.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
|
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
processor.importStylesheet(this._template);
|
2007-03-22 10:30:00 -07:00
|
|
|
var fragment = processor.transformToFragment(doc, doc);
|
|
|
|
|
|
|
|
LOG("template processing result: " + fragment.textContent);
|
|
|
|
|
|
|
|
// XXX When we support HTML microsummaries we'll need to do something
|
|
|
|
// more sophisticated than just returning the text content of the fragment.
|
|
|
|
return fragment.textContent;
|
2007-08-17 15:44:28 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
saveXMLToFile: function MSD_saveXMLToFile(xmlDefinition) {
|
|
|
|
var file = this.localURI.QueryInterface(Ci.nsIFileURL).file.clone();
|
|
|
|
|
|
|
|
LOG("saving definition to " + file.path);
|
|
|
|
|
|
|
|
// Write the generator XML to the local file.
|
|
|
|
var outputStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
|
|
|
|
createInstance(Ci.nsIFileOutputStream);
|
|
|
|
var localFile = file.QueryInterface(Ci.nsILocalFile);
|
|
|
|
outputStream.init(localFile, (MODE_WRONLY | MODE_TRUNCATE | MODE_CREATE),
|
|
|
|
PERMS_FILE, 0);
|
|
|
|
var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
|
|
|
|
createInstance(Ci.nsIDOMSerializer);
|
|
|
|
serializer.serializeToStream(xmlDefinition, outputStream, null);
|
|
|
|
if (outputStream instanceof Ci.nsISafeOutputStream) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
outputStream.finish();
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
outputStream.close();
|
|
|
|
}
|
2007-08-17 15:44:28 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
outputStream.close();
|
|
|
|
},
|
|
|
|
|
|
|
|
update: function MSD_update() {
|
|
|
|
// Update this generator if it was downloaded from a remote source and has
|
|
|
|
// been modified since we last downloaded it.
|
|
|
|
var genURI = this.uri;
|
|
|
|
if (genURI && /^urn:source:/i.test(genURI.spec)) {
|
|
|
|
let genURL = genURI.spec.replace(/^urn:source:/, "");
|
2009-10-03 14:48:43 -07:00
|
|
|
genURI = NetUtil.newURI(genURL, null, null);
|
2007-08-17 15:44:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only continue if we have a valid remote URI
|
|
|
|
if (!genURI || !/^https?/.test(genURI.scheme)) {
|
|
|
|
LOG("generator did not have valid URI; skipping update: " + genURI.spec);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We use a HEAD request to check if the generator has been modified since
|
|
|
|
// the last time we downloaded it. If it has, we move to _preformUpdate() to
|
|
|
|
// actually download and save the new generator.
|
|
|
|
var t = this;
|
|
|
|
var loadCallback = function(resource) {
|
|
|
|
if (resource.status != 304)
|
|
|
|
t._performUpdate(genURI);
|
|
|
|
else
|
|
|
|
LOG("generator is already up to date: " + genURI.spec);
|
|
|
|
resource.destroy();
|
|
|
|
};
|
|
|
|
var errorCallback = function(resource) {
|
|
|
|
resource.destroy();
|
|
|
|
};
|
|
|
|
|
|
|
|
var file = this.localURI.QueryInterface(Ci.nsIFileURL).file.clone();
|
|
|
|
var lastmod = new Date(file.lastModifiedTime);
|
|
|
|
LOG("updating generator: " + genURI.spec);
|
|
|
|
var resource = new MicrosummaryResource(genURI);
|
|
|
|
resource.lastMod = lastmod.toUTCString();
|
|
|
|
resource.method = "HEAD";
|
|
|
|
resource.load(loadCallback, errorCallback);
|
|
|
|
},
|
|
|
|
|
|
|
|
_performUpdate: function MSD__performUpdate(uri) {
|
|
|
|
var t = this;
|
|
|
|
var loadCallback = function(resource) {
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
t._handleUpdateLoad(resource);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
resource.destroy();
|
|
|
|
}
|
2007-08-17 15:44:28 -07:00
|
|
|
};
|
|
|
|
var errorCallback = function(resource) {
|
|
|
|
resource.destroy();
|
|
|
|
};
|
|
|
|
|
|
|
|
var resource = new MicrosummaryResource(uri);
|
|
|
|
resource.load(loadCallback, errorCallback);
|
|
|
|
},
|
|
|
|
|
|
|
|
_handleUpdateLoad: function MSD__handleUpdateLoad(resource) {
|
|
|
|
if (!resource.isXML)
|
|
|
|
throw("update failed, downloaded resource is not XML: " + this.uri.spec);
|
|
|
|
|
|
|
|
// Preserve the generator's ID.
|
|
|
|
// XXX Check for redirects and update the URI if it changes.
|
|
|
|
var generatorID = this.uri.spec;
|
|
|
|
resource.content.documentElement.setAttribute("uri", generatorID);
|
|
|
|
|
|
|
|
// Reinitialize this generator with the newly downloaded XML and save to disk.
|
|
|
|
this.initFromXML(resource.content);
|
|
|
|
this.saveXMLToFile(resource.content);
|
|
|
|
|
|
|
|
// Let observers know we've updated this generator
|
2010-04-16 14:51:33 -07:00
|
|
|
Services.obs.notifyObservers(this, "microsummary-generator-updated", null);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2010-03-23 06:55:22 -07:00
|
|
|
/**
|
|
|
|
* Microsummary sets are collections of microsummaries. They allow callers
|
|
|
|
* to register themselves as observers of the set, and when any microsummary
|
|
|
|
* in the set changes, the observers get notified. Thus a caller can observe
|
|
|
|
* the set instead of each individual microsummary.
|
|
|
|
*/
|
2007-03-22 10:30:00 -07:00
|
|
|
function MicrosummarySet() {
|
|
|
|
this._observers = [];
|
|
|
|
this._elements = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
MicrosummarySet.prototype = {
|
2007-06-30 20:35:19 -07:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummarySet,
|
|
|
|
Ci.nsIMicrosummaryObserver]),
|
2007-03-22 10:30:00 -07:00
|
|
|
// nsIMicrosummaryObserver
|
|
|
|
onContentLoaded: function MSSet_onContentLoaded(microsummary) {
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._observers.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
this._observers[i].onContentLoaded(microsummary);
|
2010-03-23 06:55:22 -07:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
2007-08-17 20:27:22 -07:00
|
|
|
onError: function MSSet_onError(microsummary) {
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._observers.length; i++) {
|
2007-08-17 20:27:22 -07:00
|
|
|
this._observers[i].onError(microsummary);
|
2010-03-23 06:55:22 -07:00
|
|
|
}
|
2007-08-17 20:27:22 -07:00
|
|
|
},
|
2007-03-22 10:30:00 -07:00
|
|
|
// nsIMicrosummarySet
|
|
|
|
addObserver: function MSSet_addObserver(observer) {
|
|
|
|
if (this._observers.length == 0) {
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._elements.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
this._elements[i].addObserver(this);
|
2010-03-23 06:55:22 -07:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
// Register the observer, but only if it isn't already registered,
|
|
|
|
// so that we don't call the same observer twice for any given change.
|
|
|
|
if (this._observers.indexOf(observer) == -1)
|
|
|
|
this._observers.push(observer);
|
|
|
|
},
|
2010-03-23 06:55:22 -07:00
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
removeObserver: function MSSet_removeObserver(observer) {
|
|
|
|
if (this._observers.indexOf(observer) != -1)
|
|
|
|
this._observers.splice(this._observers.indexOf(observer), 1);
|
2010-03-23 06:55:22 -07:00
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
if (this._observers.length == 0) {
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._elements.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
this._elements[i].removeObserver(this);
|
2010-03-23 06:55:22 -07:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
extractFromPage: function MSSet_extractFromPage(resource) {
|
|
|
|
if (!resource.isXML && resource.contentType != "text/html")
|
|
|
|
throw("page is neither HTML nor XML");
|
|
|
|
|
|
|
|
// XXX Handle XML documents, whose microsummaries are specified
|
|
|
|
// via processing instructions.
|
|
|
|
var links = resource.content.getElementsByTagName("link");
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < links.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
var link = links[i];
|
2010-03-23 06:55:22 -07:00
|
|
|
if (!link.hasAttribute("rel"))
|
2007-03-22 10:30:00 -07:00
|
|
|
continue;
|
|
|
|
var relAttr = link.getAttribute("rel");
|
|
|
|
|
|
|
|
// The attribute's value can be a space-separated list of link types,
|
|
|
|
// check to see if "microsummary" is one of them.
|
|
|
|
var linkTypes = relAttr.split(/\s+/);
|
2010-03-23 06:55:22 -07:00
|
|
|
if (!linkTypes.some(function(v) {return v.toLowerCase() == "microsummary";}))
|
2007-03-22 10:30:00 -07:00
|
|
|
continue;
|
|
|
|
// Look for a TITLE attribute to give the generator a nice name in the UI.
|
|
|
|
var linkTitle = link.getAttribute("title");
|
|
|
|
|
|
|
|
|
|
|
|
// Unlike the "href" attribute, the "href" property contains
|
|
|
|
// an absolute URI spec, so we use it here to create the URI.
|
2009-10-03 14:48:43 -07:00
|
|
|
var generatorURI = NetUtil.newURI(link.href, resource.content.characterSet,
|
|
|
|
null);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2007-08-14 23:18:30 -07:00
|
|
|
if (!/^https?$/i.test(generatorURI.scheme)) {
|
2007-03-22 10:30:00 -07:00
|
|
|
LOG("can't load generator " + generatorURI.spec + " from page " +
|
2007-08-14 23:18:30 -07:00
|
|
|
resource.uri.spec);
|
2007-03-22 10:30:00 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
var generator = new MicrosummaryGenerator(generatorURI, null, linkTitle);
|
|
|
|
var microsummary = new Microsummary(resource.uri, generator);
|
|
|
|
if (!this.hasItemForMicrosummary(microsummary))
|
|
|
|
this.AppendElement(microsummary);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2007-03-22 16:13:57 -07:00
|
|
|
/**
|
2009-06-12 15:26:04 -07:00
|
|
|
* Determines whether the given microsummary is already represented in the
|
2007-03-22 16:13:57 -07:00
|
|
|
* set.
|
|
|
|
*/
|
|
|
|
hasItemForMicrosummary: function MSSet_hasItemForMicrosummary(aMicrosummary) {
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._elements.length; i++) {
|
2007-03-22 16:13:57 -07:00
|
|
|
if (this._elements[i].equals(aMicrosummary))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
// XXX Turn this into a complete implementation of nsICollection?
|
|
|
|
AppendElement: function MSSet_AppendElement(element) {
|
|
|
|
// Query the element to a microsummary.
|
|
|
|
// XXX Should we NS_ASSERT if this fails?
|
|
|
|
element = element.QueryInterface(Ci.nsIMicrosummary);
|
|
|
|
|
|
|
|
if (this._elements.indexOf(element) == -1) {
|
|
|
|
this._elements.push(element);
|
|
|
|
element.addObserver(this);
|
|
|
|
}
|
|
|
|
// Notify observers that an element has been appended.
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < this._observers.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
this._observers[i].onElementAppended(element);
|
2010-03-23 06:55:22 -07:00
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
Enumerate: function MSSet_Enumerate() {
|
|
|
|
return new ArrayEnumerator(this._elements);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An enumeration of items in a JS array.
|
|
|
|
* @constructor
|
|
|
|
*/
|
|
|
|
function ArrayEnumerator(aItems) {
|
|
|
|
if (aItems) {
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < aItems.length; ++i) {
|
2007-03-22 10:30:00 -07:00
|
|
|
if (!aItems[i])
|
2007-11-14 23:33:00 -08:00
|
|
|
aItems.splice(i--, 1);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
2007-11-14 23:33:00 -08:00
|
|
|
this._contents = aItems;
|
|
|
|
} else {
|
|
|
|
this._contents = [];
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ArrayEnumerator.prototype = {
|
2007-06-30 20:35:19 -07:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
|
2007-11-14 23:33:00 -08:00
|
|
|
|
|
|
|
_index: 0,
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
hasMoreElements: function() {
|
|
|
|
return this._index < this._contents.length;
|
|
|
|
},
|
2007-11-14 23:33:00 -08:00
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
getNext: function() {
|
|
|
|
return this._contents[this._index++];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Outputs aText to the JavaScript console as well as to stdout if the
|
|
|
|
* microsummary logging pref (browser.microsummary.log) is set to true.
|
|
|
|
*
|
|
|
|
* @param aText
|
|
|
|
* the text to log
|
|
|
|
*/
|
|
|
|
function LOG(aText) {
|
2007-11-14 23:33:00 -08:00
|
|
|
var f = arguments.callee;
|
|
|
|
if (!("_enabled" in f))
|
|
|
|
f._enabled = getPref("browser.microsummary.log", false);
|
|
|
|
if (f._enabled) {
|
2007-03-22 10:30:00 -07:00
|
|
|
dump("*** Microsummaries: " + aText + "\n");
|
|
|
|
var consoleService = Cc["@mozilla.org/consoleservice;1"].
|
|
|
|
getService(Ci.nsIConsoleService);
|
|
|
|
consoleService.logStringMessage(aText);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A resource (page, microsummary, generator, etc.) identifiable by URI.
|
|
|
|
* This object abstracts away much of the code for loading resources
|
|
|
|
* and parsing their content if they are XML or HTML.
|
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
*
|
|
|
|
* @param uri
|
|
|
|
* the location of the resource
|
|
|
|
*/
|
|
|
|
function MicrosummaryResource(uri) {
|
|
|
|
// Make sure we're not loading javascript: or data: URLs, which could
|
|
|
|
// take advantage of the load to run code with chrome: privileges.
|
|
|
|
// XXX Perhaps use nsIScriptSecurityManager.checkLoadURI instead.
|
2007-11-14 23:33:00 -08:00
|
|
|
if (!(uri.schemeIs("http") || uri.schemeIs("https") || uri.schemeIs("file")))
|
2007-03-22 10:30:00 -07:00
|
|
|
throw NS_ERROR_DOM_BAD_URI;
|
|
|
|
|
2007-11-14 23:33:00 -08:00
|
|
|
this._uri = uri;
|
2007-03-22 16:13:57 -07:00
|
|
|
this._content = null;
|
|
|
|
this._contentType = null;
|
|
|
|
this._isXML = false;
|
|
|
|
this.__authFailed = false;
|
2007-08-09 15:07:42 -07:00
|
|
|
this._status = null;
|
|
|
|
this._method = "GET";
|
2007-08-17 15:44:28 -07:00
|
|
|
this._lastMod = null;
|
2007-03-22 16:13:57 -07:00
|
|
|
|
|
|
|
// A function to call when we finish loading/parsing the resource.
|
|
|
|
this._loadCallback = null;
|
|
|
|
// A function to call if we get an error while loading/parsing the resource.
|
|
|
|
this._errorCallback = null;
|
|
|
|
// A hidden iframe to parse HTML content.
|
|
|
|
this._iframe = null;
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
MicrosummaryResource.prototype = {
|
2010-03-23 06:55:22 -07:00
|
|
|
get uri() this._uri,
|
|
|
|
get content() this._content,
|
|
|
|
get contentType() this._contentType,
|
|
|
|
get isXML() this._isXML,
|
|
|
|
get status() this._status,
|
|
|
|
set status(aStatus) this._status = aStatus,
|
|
|
|
get method() this._method,
|
|
|
|
set method(aMethod) this._method = aMethod,
|
|
|
|
get lastMod() this._lastMod,
|
|
|
|
set lastMod(aMod) this._lastMod = aMod,
|
2007-03-22 10:30:00 -07:00
|
|
|
// Implement notification callback interfaces so we can suppress UI
|
|
|
|
// and abort loads for bad SSL certs and HTTP authorization requests.
|
|
|
|
|
|
|
|
// Interfaces this component implements.
|
2007-10-03 04:47:26 -07:00
|
|
|
interfaces: [Ci.nsIAuthPromptProvider,
|
2007-03-22 10:30:00 -07:00
|
|
|
Ci.nsIAuthPrompt,
|
2008-01-15 07:06:34 -08:00
|
|
|
Ci.nsIBadCertListener2,
|
|
|
|
Ci.nsISSLErrorListener,
|
2007-03-22 10:30:00 -07:00
|
|
|
Ci.nsIPrompt,
|
|
|
|
Ci.nsIProgressEventSink,
|
|
|
|
Ci.nsIInterfaceRequestor,
|
|
|
|
Ci.nsISupports],
|
|
|
|
// nsISupports
|
|
|
|
QueryInterface: function MSR_QueryInterface(iid) {
|
2010-03-23 06:55:22 -07:00
|
|
|
if (!this.interfaces.some(function(v) {return iid.equals(v);}))
|
2007-03-22 10:30:00 -07:00
|
|
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
|
|
|
// nsIAuthPrompt and nsIPrompt need separate implementations because
|
|
|
|
// their method signatures conflict. The other interfaces we implement
|
|
|
|
// within MicrosummaryResource itself.
|
2010-02-06 02:40:06 -08:00
|
|
|
switch (iid) {
|
2007-03-22 10:30:00 -07:00
|
|
|
case Ci.nsIAuthPrompt:
|
|
|
|
return this.authPrompt;
|
|
|
|
case Ci.nsIPrompt:
|
|
|
|
return this.prompt;
|
|
|
|
default:
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// nsIInterfaceRequestor
|
|
|
|
getInterface: function MSR_getInterface(iid) {
|
|
|
|
return this.QueryInterface(iid);
|
|
|
|
},
|
|
|
|
|
2008-01-15 07:06:34 -08:00
|
|
|
// nsIBadCertListener2
|
|
|
|
// Suppress any certificate errors
|
|
|
|
notifyCertProblem: function MSR_certProblem(socketInfo, status, targetSite) {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
// nsISSLErrorListener
|
|
|
|
// Suppress any ssl errors
|
|
|
|
notifySSLError: function MSR_SSLError(socketInfo, error, targetSite) {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
// Suppress UI and abort loads for files secured by authentication.
|
|
|
|
|
|
|
|
// Auth requests appear to succeed when we cancel them (since the server
|
|
|
|
// redirects us to a "you're not authorized" page), so we have to set a flag
|
|
|
|
// to let the load handler know to treat the load as a failure.
|
2010-03-23 06:55:22 -07:00
|
|
|
get _authFailed() this.__authFailed,
|
|
|
|
set _authFailed(newValue) this.__authFailed = newValue,
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// nsIAuthPromptProvider
|
|
|
|
getAuthPrompt: function(aPromptReason, aIID) {
|
|
|
|
this._authFailed = true;
|
|
|
|
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
|
|
|
},
|
|
|
|
|
|
|
|
// HTTP always requests nsIAuthPromptProvider first, so it never needs
|
|
|
|
// nsIAuthPrompt, but not all channels use nsIAuthPromptProvider, so we
|
|
|
|
// implement nsIAuthPrompt too.
|
|
|
|
// nsIAuthPrompt
|
|
|
|
get authPrompt() {
|
|
|
|
var resource = this;
|
|
|
|
return {
|
2007-06-30 20:35:19 -07:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
|
2007-03-22 10:30:00 -07:00
|
|
|
prompt: function(dialogTitle, text, passwordRealm, savePassword, defaultText, result) {
|
|
|
|
resource._authFailed = true;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
promptUsernameAndPassword: function(dialogTitle, text, passwordRealm, savePassword, user, pwd) {
|
|
|
|
resource._authFailed = true;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
promptPassword: function(dialogTitle, text, passwordRealm, savePassword, pwd) {
|
|
|
|
resource._authFailed = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
// nsIPrompt
|
|
|
|
get prompt() {
|
|
|
|
var resource = this;
|
|
|
|
return {
|
2007-06-30 20:35:19 -07:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
|
2007-03-22 10:30:00 -07:00
|
|
|
alert: function(dialogTitle, text) {
|
|
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
},
|
|
|
|
alertCheck: function(dialogTitle, text, checkMessage, checkValue) {
|
|
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
},
|
|
|
|
confirm: function(dialogTitle, text) {
|
|
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
},
|
|
|
|
confirmCheck: function(dialogTitle, text, checkMessage, checkValue) {
|
|
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
},
|
|
|
|
confirmEx: function(dialogTitle, text, buttonFlags, button0Title, button1Title, button2Title, checkMsg, checkValue) {
|
|
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
},
|
|
|
|
prompt: function(dialogTitle, text, value, checkMsg, checkValue) {
|
|
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
},
|
|
|
|
promptPassword: function(dialogTitle, text, password, checkMsg, checkValue) {
|
|
|
|
resource._authFailed = true;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
promptUsernameAndPassword: function(dialogTitle, text, username, password, checkMsg, checkValue) {
|
|
|
|
resource._authFailed = true;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
select: function(dialogTitle, text, count, selectList, outSelection) {
|
|
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
// XXX We implement nsIProgressEventSink because otherwise bug 253127
|
|
|
|
// would cause too many extraneous errors to get reported to the console.
|
|
|
|
// Fortunately this doesn't screw up XMLHttpRequest, because it ensures
|
|
|
|
// that its implementation of nsIProgressEventSink will always get called
|
|
|
|
// in addition to whatever notification callbacks we set on the channel.
|
|
|
|
// nsIProgressEventSink
|
|
|
|
onProgress: function(aRequest, aContext, aProgress, aProgressMax) {},
|
|
|
|
onStatus: function(aRequest, aContext, aStatus, aStatusArg) {},
|
|
|
|
/**
|
|
|
|
* Initialize the resource from an existing DOM document object.
|
|
|
|
*
|
|
|
|
* @param document
|
|
|
|
* a DOM document object
|
|
|
|
*/
|
|
|
|
initFromDocument: function MSR_initFromDocument(document) {
|
|
|
|
this._content = document;
|
|
|
|
this._contentType = document.contentType;
|
|
|
|
|
|
|
|
// Normally we set this property based on whether or not
|
|
|
|
// XMLHttpRequest parsed the content into an XML document object,
|
|
|
|
// but since we already have the content, we have to analyze
|
|
|
|
// its content type ourselves to see if it is XML.
|
|
|
|
this._isXML = (this.contentType == "text/xml" ||
|
|
|
|
this.contentType == "application/xml" ||
|
|
|
|
/^.+\/.+\+xml$/.test(this.contentType));
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroy references to avoid leak-causing cycles. Instantiators must call
|
|
|
|
* this method on all instances they instantiate once they're done with them.
|
|
|
|
*/
|
|
|
|
destroy: function MSR_destroy() {
|
|
|
|
this._uri = null;
|
|
|
|
this._content = null;
|
|
|
|
this._loadCallback = null;
|
|
|
|
this._errorCallback = null;
|
|
|
|
this._loadTimer = null;
|
|
|
|
this._authFailed = false;
|
|
|
|
if (this._iframe) {
|
|
|
|
if (this._iframe && this._iframe.parentNode)
|
|
|
|
this._iframe.parentNode.removeChild(this._iframe);
|
|
|
|
this._iframe = null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load the resource.
|
|
|
|
*
|
|
|
|
* @param loadCallback
|
|
|
|
* a function to invoke when the resource finishes loading
|
|
|
|
* @param errorCallback
|
|
|
|
* a function to invoke when an error occurs during the load
|
|
|
|
*/
|
|
|
|
load: function MSR_load(loadCallback, errorCallback) {
|
|
|
|
LOG(this.uri.spec + " loading");
|
|
|
|
|
|
|
|
this._loadCallback = loadCallback;
|
|
|
|
this._errorCallback = errorCallback;
|
|
|
|
|
|
|
|
var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
|
|
|
|
|
|
|
|
var loadHandler = {
|
|
|
|
_self: this,
|
|
|
|
handleEvent: function MSR_loadHandler_handleEvent(event) {
|
|
|
|
if (this._self._loadTimer)
|
|
|
|
this._self._loadTimer.cancel();
|
|
|
|
|
2007-08-09 15:07:42 -07:00
|
|
|
this._self.status = event.target.status;
|
|
|
|
|
|
|
|
if (this._self._authFailed || this._self.status >= 400) {
|
2007-03-22 10:30:00 -07:00
|
|
|
// Technically the request succeeded, but we treat it as a failure,
|
2007-08-09 15:07:42 -07:00
|
|
|
// since we won't be able to extract anything relevant from the result.
|
|
|
|
|
|
|
|
// XXX For now HTTP is the only protocol we handle that might fail
|
|
|
|
// auth. This message will need to change once we support FTP, which
|
|
|
|
// returns 0 for all statuses.
|
|
|
|
LOG(this._self.uri.spec + " load failed; HTTP status: " + this._self.status);
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
this._self._handleError(event);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
this._self = null;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
else if (event.target.channel.contentType == "multipart/x-mixed-replace") {
|
|
|
|
// Technically the request succeeded, but we treat it as a failure,
|
|
|
|
// since we aren't able to handle multipart content.
|
|
|
|
LOG(this._self.uri.spec + " load failed; contains multipart content");
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
this._self._handleError(event);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
this._self = null;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
LOG(this._self.uri.spec + " load succeeded; invoking callback");
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
this._self._handleLoad(event);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
this._self = null;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var errorHandler = {
|
|
|
|
_self: this,
|
|
|
|
handleEvent: function MSR_errorHandler_handleEvent(event) {
|
|
|
|
if (this._self._loadTimer)
|
|
|
|
this._self._loadTimer.cancel();
|
|
|
|
LOG(this._self.uri.spec + " load failed");
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
this._self._handleError(event);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
this._self = null;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
// cancel loads that take too long
|
|
|
|
// timeout specified in seconds at browser.microsummary.requestTimeout,
|
|
|
|
// or 300 seconds (five minutes)
|
|
|
|
var timeout = getPref("browser.microsummary.requestTimeout", 300) * 1000;
|
|
|
|
var timerObserver = {
|
|
|
|
_self: this,
|
|
|
|
observe: function MSR_timerObserver_observe() {
|
|
|
|
LOG("timeout loading microsummary resource " + this._self.uri.spec + ", aborting request");
|
|
|
|
request.abort();
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
this._self.destroy();
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
this._self = null;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
|
|
this._loadTimer.init(timerObserver, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
|
|
|
|
|
|
|
|
request = request.QueryInterface(Ci.nsIDOMEventTarget);
|
|
|
|
request.addEventListener("load", loadHandler, false);
|
|
|
|
request.addEventListener("error", errorHandler, false);
|
|
|
|
|
|
|
|
request = request.QueryInterface(Ci.nsIXMLHttpRequest);
|
2007-08-09 15:07:42 -07:00
|
|
|
request.open(this.method, this.uri.spec, true);
|
2007-03-22 10:30:00 -07:00
|
|
|
request.setRequestHeader("X-Moz", "microsummary");
|
2007-08-17 15:44:28 -07:00
|
|
|
if (this.lastMod)
|
|
|
|
request.setRequestHeader("If-Modified-Since", this.lastMod);
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
// Register ourselves as a listener for notification callbacks so we
|
|
|
|
// can handle authorization requests and SSL issues like cert mismatches.
|
|
|
|
// XMLHttpRequest will handle the notifications we don't handle.
|
|
|
|
request.channel.notificationCallbacks = this;
|
|
|
|
|
|
|
|
// If this is a bookmarked resource, and the bookmarks service recorded
|
|
|
|
// its charset in the bookmarks datastore the last time the user visited it,
|
|
|
|
// then specify the charset in the channel so XMLHttpRequest loads
|
|
|
|
// the resource correctly.
|
|
|
|
try {
|
|
|
|
var resolver = Cc["@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1"].
|
|
|
|
getService(Ci.nsICharsetResolver);
|
|
|
|
if (resolver) {
|
|
|
|
var charset = resolver.requestCharset(null, request.channel, {}, {});
|
2009-07-30 11:40:45 -07:00
|
|
|
if (charset != "")
|
2007-03-22 10:30:00 -07:00
|
|
|
request.channel.contentCharset = charset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(ex) {}
|
|
|
|
|
|
|
|
request.send(null);
|
|
|
|
},
|
|
|
|
|
|
|
|
_handleLoad: function MSR__handleLoad(event) {
|
|
|
|
var request = event.target;
|
|
|
|
|
|
|
|
if (request.responseXML) {
|
|
|
|
this._isXML = true;
|
|
|
|
// XXX Figure out the parsererror format and log a specific error.
|
2007-09-30 13:17:06 -07:00
|
|
|
if (request.responseXML.documentElement.nodeName == "parsererror") {
|
|
|
|
this._handleError(event);
|
|
|
|
return;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
this._content = request.responseXML;
|
|
|
|
this._contentType = request.channel.contentType;
|
|
|
|
this._loadCallback(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (request.channel.contentType == "text/html") {
|
|
|
|
this._parse(request.responseText);
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
// This catches text/plain as well as any other content types
|
|
|
|
// not accounted for by the content type-specific code above.
|
|
|
|
this._content = request.responseText;
|
|
|
|
this._contentType = request.channel.contentType;
|
|
|
|
this._loadCallback(this);
|
|
|
|
}
|
|
|
|
},
|
2007-08-17 20:27:22 -07:00
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
_handleError: function MSR__handleError(event) {
|
|
|
|
// Call the error callback, then destroy ourselves to prevent memory leaks.
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
if (this._errorCallback)
|
|
|
|
this._errorCallback(this);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
this.destroy();
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a string of HTML text. Used by _load() when it retrieves HTML.
|
|
|
|
* We do this via hidden XUL iframes, which according to bz is the best way
|
|
|
|
* to do it currently, since bug 102699 is hard to fix.
|
|
|
|
*
|
|
|
|
* @param htmlText
|
|
|
|
* a string containing the HTML content
|
|
|
|
*/
|
|
|
|
_parse: function MSR__parse(htmlText) {
|
|
|
|
// Find a window to stick our hidden iframe into.
|
2010-04-16 14:51:33 -07:00
|
|
|
var window = Services.wm.getMostRecentWindow("navigator:browser");
|
2007-03-22 10:30:00 -07:00
|
|
|
// XXX We can use other windows, too, so perhaps we should try to get
|
|
|
|
// some other window if there's no browser window open. Perhaps we should
|
|
|
|
// even prefer other windows, since there's less chance of any browser
|
|
|
|
// window machinery like throbbers treating our load like one initiated
|
|
|
|
// by the user.
|
2007-09-30 13:17:06 -07:00
|
|
|
if (!window) {
|
|
|
|
this._handleError(event);
|
|
|
|
return;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
var document = window.document;
|
|
|
|
var rootElement = document.documentElement;
|
|
|
|
|
|
|
|
// Create an iframe, make it hidden, and secure it against untrusted content.
|
|
|
|
this._iframe = document.createElement('iframe');
|
|
|
|
this._iframe.setAttribute("collapsed", true);
|
|
|
|
this._iframe.setAttribute("type", "content");
|
|
|
|
|
|
|
|
// Insert the iframe into the window, creating the doc shell.
|
|
|
|
rootElement.appendChild(this._iframe);
|
|
|
|
|
|
|
|
// When we insert the iframe into the window, it immediately starts loading
|
|
|
|
// about:blank, which we don't need and could even hurt us (for example
|
|
|
|
// by triggering bugs like bug 344305), so cancel that load.
|
|
|
|
var webNav = this._iframe.docShell.QueryInterface(Ci.nsIWebNavigation);
|
|
|
|
webNav.stop(Ci.nsIWebNavigation.STOP_NETWORK);
|
|
|
|
|
|
|
|
// Turn off JavaScript and auth dialogs for security and other things
|
|
|
|
// to reduce network load.
|
|
|
|
// XXX We should also turn off CSS.
|
|
|
|
this._iframe.docShell.allowJavascript = false;
|
|
|
|
this._iframe.docShell.allowAuth = false;
|
|
|
|
this._iframe.docShell.allowPlugins = false;
|
|
|
|
this._iframe.docShell.allowMetaRedirects = false;
|
|
|
|
this._iframe.docShell.allowSubframes = false;
|
|
|
|
this._iframe.docShell.allowImages = false;
|
2009-07-06 19:24:04 -07:00
|
|
|
this._iframe.docShell.allowDNSPrefetch = false;
|
2007-03-22 10:30:00 -07:00
|
|
|
|
|
|
|
var parseHandler = {
|
|
|
|
_self: this,
|
|
|
|
handleEvent: function MSR_parseHandler_handleEvent(event) {
|
|
|
|
event.target.removeEventListener("DOMContentLoaded", this, false);
|
2010-03-23 06:55:22 -07:00
|
|
|
try {
|
|
|
|
this._self._handleParse(event);
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
this._self = null;
|
|
|
|
}
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Convert the HTML text into an input stream.
|
|
|
|
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
|
|
|
createInstance(Ci.nsIScriptableUnicodeConverter);
|
|
|
|
converter.charset = "UTF-8";
|
|
|
|
var stream = converter.convertToInputStream(htmlText);
|
|
|
|
|
|
|
|
// Set up a channel to load the input stream.
|
|
|
|
var channel = Cc["@mozilla.org/network/input-stream-channel;1"].
|
|
|
|
createInstance(Ci.nsIInputStreamChannel);
|
|
|
|
channel.setURI(this._uri);
|
|
|
|
channel.contentStream = stream;
|
|
|
|
|
|
|
|
// Load in the background so we don't trigger web progress listeners.
|
|
|
|
var request = channel.QueryInterface(Ci.nsIRequest);
|
|
|
|
request.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND;
|
|
|
|
|
|
|
|
// Specify the content type since we're not loading content from a server,
|
|
|
|
// so it won't get specified for us, and if we don't specify it ourselves,
|
|
|
|
// then Firefox will prompt the user to download content of "unknown type".
|
|
|
|
var baseChannel = channel.QueryInterface(Ci.nsIChannel);
|
|
|
|
baseChannel.contentType = "text/html";
|
|
|
|
|
|
|
|
// Load as UTF-8, which it'll always be, because XMLHttpRequest converts
|
|
|
|
// the text (i.e. XMLHTTPRequest.responseText) from its original charset
|
|
|
|
// to UTF-16, then the string input stream component converts it to UTF-8.
|
|
|
|
baseChannel.contentCharset = "UTF-8";
|
|
|
|
|
|
|
|
// Register the parse handler as a load event listener and start the load.
|
|
|
|
// Listen for "DOMContentLoaded" instead of "load" because background loads
|
|
|
|
// don't fire "load" events.
|
|
|
|
this._iframe.addEventListener("DOMContentLoaded", parseHandler, true);
|
|
|
|
var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
|
|
|
|
uriLoader.openURI(channel, true, this._iframe.docShell);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a load event for the iframe-based parser.
|
|
|
|
*
|
|
|
|
* @param event
|
|
|
|
* the event object representing the load event
|
|
|
|
*/
|
|
|
|
_handleParse: function MSR__handleParse(event) {
|
|
|
|
// XXX Make sure the parse was successful?
|
|
|
|
|
|
|
|
this._content = this._iframe.contentDocument;
|
|
|
|
this._contentType = this._iframe.contentDocument.contentType;
|
|
|
|
this._loadCallback(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a resource currently loaded into a browser window. Checks windows
|
|
|
|
* one at a time, starting with the frontmost (a.k.a. most recent) one.
|
2010-03-23 06:55:22 -07:00
|
|
|
*
|
2007-03-22 10:30:00 -07:00
|
|
|
* @param uri
|
|
|
|
* the URI of the resource
|
|
|
|
*
|
|
|
|
* @returns a Resource object, if the resource is currently loaded
|
|
|
|
* into a browser window; otherwise null
|
|
|
|
*/
|
|
|
|
function getLoadedMicrosummaryResource(uri) {
|
|
|
|
|
|
|
|
// Apparently the Z order enumerator is broken on Linux per bug 156333.
|
|
|
|
//var windows = mediator.getZOrderDOMWindowEnumerator("navigator:browser", true);
|
2010-04-16 14:51:33 -07:00
|
|
|
var windows = Services.wm.getEnumerator("navigator:browser");
|
2007-03-22 10:30:00 -07:00
|
|
|
while (windows.hasMoreElements()) {
|
|
|
|
var win = windows.getNext();
|
2010-03-23 06:55:22 -07:00
|
|
|
if (win.closed)
|
|
|
|
continue;
|
2007-03-22 10:30:00 -07:00
|
|
|
var tabBrowser = win.document.getElementById("content");
|
2010-03-23 06:55:22 -07:00
|
|
|
for (let i = 0; i < tabBrowser.browsers.length; i++) {
|
2007-03-22 10:30:00 -07:00
|
|
|
var browser = tabBrowser.browsers[i];
|
|
|
|
if (uri.equals(browser.currentURI)) {
|
|
|
|
var resource = new MicrosummaryResource(uri);
|
|
|
|
resource.initFromDocument(browser.contentDocument);
|
|
|
|
return resource;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a value from a pref or a default value if the pref doesn't exist.
|
|
|
|
*
|
|
|
|
* @param prefName
|
|
|
|
* @param defaultValue
|
|
|
|
* @returns the pref's value or the default (if it is missing)
|
|
|
|
*/
|
|
|
|
function getPref(prefName, defaultValue) {
|
|
|
|
try {
|
2010-04-16 14:51:33 -07:00
|
|
|
var type = Services.prefs.getPrefType(prefName);
|
2007-11-14 23:33:00 -08:00
|
|
|
switch (type) {
|
2010-04-16 14:51:33 -07:00
|
|
|
case Services.prefs.PREF_BOOL:
|
|
|
|
return Services.prefs.getBoolPref(prefName);
|
|
|
|
case Services.prefs.PREF_INT:
|
|
|
|
return Services.prefs.getIntPref(prefName);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
}
|
2010-03-23 06:55:22 -07:00
|
|
|
catch (ex) {}
|
|
|
|
|
2007-03-22 10:30:00 -07:00
|
|
|
return defaultValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2010-03-23 06:55:22 -07:00
|
|
|
* Generates a sanitized name to be used as a filename, or a random name if a
|
|
|
|
* sanitized name cannot be obtained (if aName contains no valid characters).
|
2007-03-22 10:30:00 -07:00
|
|
|
*/
|
|
|
|
function sanitizeName(aName) {
|
|
|
|
var name = aName.toLowerCase();
|
2010-03-23 06:55:22 -07:00
|
|
|
name = name.replace(/\s+/g, "-");
|
|
|
|
name = name.replace(/[^-a-z0-9]/g, "");
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2010-03-23 06:55:22 -07:00
|
|
|
// If our input had not enough valid characters, use a random name.
|
|
|
|
if (name.length < MIN_GENERATOR_NAME_LENGTH)
|
2010-03-26 14:57:28 -07:00
|
|
|
name = Math.random().toString(36).replace(/^.*\./, '');
|
2007-03-22 10:30:00 -07:00
|
|
|
|
2010-03-23 06:55:22 -07:00
|
|
|
// Force max length.
|
|
|
|
return name.substring(0, MAX_GENERATOR_NAME_LENGTH);
|
2007-03-22 10:30:00 -07:00
|
|
|
}
|
|
|
|
|
2010-06-22 09:59:15 -07:00
|
|
|
var NSGetFactory = XPCOMUtils.generateNSGetFactory([MicrosummaryService]);
|