nsMicrosummaryService.js
Go to the documentation of this file.
1 # ***** BEGIN LICENSE BLOCK *****
2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 #
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
7 # http://www.mozilla.org/MPL/
8 #
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
12 # License.
13 #
14 # The Original Code is Microsummarizer.
15 #
16 # The Initial Developer of the Original Code is Mozilla.
17 # Portions created by the Initial Developer are Copyright (C) 2006
18 # the Initial Developer. All Rights Reserved.
19 #
20 # Contributor(s):
21 # Myk Melez <myk@mozilla.org> (Original Author)
22 # Simon Bünzli <zeniko@gmail.com>
23 # Asaf Romano <mano@mozilla.com>
24 # Dan Mills <thunder@mozilla.com>
25 # Ryan Flint <rflint@dslr.net>
26 # Dietrich Ayala <dietrich@mozilla.com>
27 #
28 # Alternatively, the contents of this file may be used under the terms of
29 # either the GNU General Public License Version 2 or later (the "GPL"), or
30 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 # in which case the provisions of the GPL or the LGPL are applicable instead
32 # of those above. If you wish to allow use of your version of this file only
33 # under the terms of either the GPL or the LGPL, and not to allow others to
34 # use your version of this file under the terms of the MPL, indicate your
35 # decision by deleting the provisions above and replace them with the notice
36 # and other provisions required by the GPL or the LGPL. If you do not delete
37 # the provisions above, a recipient may use your version of this file under
38 # the terms of any one of the MPL, the GPL or the LGPL.
39 #
40 # ***** END LICENSE BLOCK *****
41 
42 const Cc = Components.classes;
43 const Ci = Components.interfaces;
44 const Cr = Components.results;
45 const Cu = Components.utils;
46 
47 const PERMS_FILE = 0644;
48 const MODE_WRONLY = 0x02;
49 const MODE_CREATE = 0x08;
50 const MODE_TRUNCATE = 0x20;
51 
52 const NS_ERROR_MODULE_DOM = 2152923136;
54 
55 // How often to check for microsummaries that need updating, in milliseconds.
56 const CHECK_INTERVAL = 15 * 1000; // 15 seconds
57 // How often to check for generator updates, in seconds
58 const GENERATOR_INTERVAL = 7 * 86400; // 1 week
59 
60 const MICSUM_NS = "http://www.mozilla.org/microsummaries/0.1";
61 const XSLT_NS = "http://www.w3.org/1999/XSL/Transform";
62 
63 const ANNO_MICSUM_GEN_URI = "microsummary/generatorURI";
64 const ANNO_MICSUM_EXPIRATION = "microsummary/expiration";
65 const ANNO_STATIC_TITLE = "bookmarks/staticTitle";
66 const ANNO_CONTENT_TYPE = "bookmarks/contentType";
67 
68 const MAX_SUMMARY_LENGTH = 4096;
69 
70 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
71 
72 function MicrosummaryService() {
73  this._obs.addObserver(this, "xpcom-shutdown", true);
74  this._ans.addObserver(this, false);
75 
76  Cc["@mozilla.org/preferences-service;1"].
77  getService(Ci.nsIPrefService).
78  getBranch("browser.microsummary.").
79  QueryInterface(Ci.nsIPrefBranch2).
80  addObserver("", this, true);
81 
82  this._initTimers();
83  this._cacheLocalGenerators();
84 }
85 
86 MicrosummaryService.prototype = {
87  // Bookmarks Service
88  get _bms() {
89  var svc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
90  getService(Ci.nsINavBookmarksService);
91  this.__defineGetter__("_bms", function() svc);
92  return this._bms;
93  },
94 
95  // Annotation Service
96  get _ans() {
97  var svc = Cc["@mozilla.org/browser/annotation-service;1"].
98  getService(Ci.nsIAnnotationService);
99  this.__defineGetter__("_ans", function() svc);
100  return this._ans;
101  },
102 
103  // IO Service
104  get _ios() {
105  var svc = Cc["@mozilla.org/network/io-service;1"].
106  getService(Ci.nsIIOService);
107  this.__defineGetter__("_ios", function() svc);
108  return this._ios;
109  },
110 
111  // Observer Service
112  get _obs() {
113  var svc = Cc["@mozilla.org/observer-service;1"].
114  getService(Ci.nsIObserverService);
115  this.__defineGetter__("_obs", function() svc);
116  return this._obs;
117  },
118 
125  _uri: function MSS__uri(spec) {
126  return this._ios.newURI(spec, null, null);
127  },
128 
129  // Directory Locator
130  __dirs: null,
131  get _dirs() {
132  if (!this.__dirs)
133  this.__dirs = Cc["@mozilla.org/file/directory_service;1"].
134  getService(Ci.nsIProperties);
135  return this.__dirs;
136  },
137 
138  // The update interval as specified by the user (defaults to 30 minutes)
139  get _updateInterval() {
140  var updateInterval =
141  getPref("browser.microsummary.updateInterval", 30);
142  // the minimum update interval is 1 minute
143  return Math.max(updateInterval, 1) * 60 * 1000;
144  },
145 
146  // A cache of local microsummary generators. This gets built on startup
147  // by the _cacheLocalGenerators() method.
148  _localGenerators: {},
149 
150  // The timer that periodically checks for microsummaries needing updating.
151  _timer: null,
152 
153  // XPCOM registration
154  classDescription: "Microsummary Service",
155  contractID: "@mozilla.org/microsummary/service;1",
156  classID: Components.ID("{460a9792-b154-4f26-a922-0f653e2c8f91}"),
157  _xpcom_categories: [{ category: "update-timer",
158  value: "@mozilla.org/microsummary/service;1," +
159  "getService,microsummary-generator-update-timer," +
160  "browser.microsummary.generatorUpdateInterval," +
162  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummaryService,
163  Ci.nsITimerCallback,
164  Ci.nsISupportsWeakReference,
165  Ci.nsIAnnotationObserver,
166  Ci.nsIObserver]),
167 
168  // nsIObserver
169  observe: function MSS_observe(subject, topic, data) {
170  switch (topic) {
171  case "xpcom-shutdown":
172  this._destroy();
173  break;
174  case "nsPref:changed":
175  if (data == "enabled")
176  this._initTimers();
177  break;
178  }
179  },
180 
181  // cross-session timer used to periodically check for generator updates.
182  notify: function MSS_notify(timer) {
183  this._updateGenerators();
184  },
185 
186  _initTimers: function MSS__initTimers() {
187  if (this._timer)
188  this._timer.cancel();
189 
190  if (!getPref("browser.microsummary.enabled", true))
191  return;
192 
193  // Periodically update microsummaries that need updating.
194  this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
195  var callback = {
196  _svc: this,
197  notify: function(timer) { this._svc._updateMicrosummaries() }
198  };
199  this._timer.initWithCallback(callback,
201  this._timer.TYPE_REPEATING_SLACK);
202  },
203 
204  _destroy: function MSS__destroy() {
205  this._obs.removeObserver(this, "xpcom-shutdown", true);
206  this._ans.removeObserver(this);
207  this._timer.cancel();
208  this._timer = null;
209  },
210 
211  _updateMicrosummaries: function MSS__updateMicrosummaries() {
212  var bookmarks = this._bookmarks;
213 
214  var now = Date.now();
215  var updateInterval = this._updateInterval;
216  for ( var i = 0; i < bookmarks.length; i++ ) {
217  var bookmarkID = bookmarks[i];
218 
219  // Skip this page if its microsummary hasn't expired yet.
220  if (this._ans.itemHasAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION) &&
221  this._ans.getItemAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION) > now)
222  continue;
223 
224  // Reset the expiration time immediately, so if the refresh is failing
225  // we don't try it every 15 seconds, potentially overloading the server.
226  this._setAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION, now + updateInterval);
227 
228  // Try to update the microsummary, but trap errors, so an update
229  // that throws doesn't prevent us from updating the rest of them.
230  try {
231  this.refreshMicrosummary(bookmarkID);
232  }
233  catch(ex) {
234  Cu.reportError(ex);
235  }
236  }
237  },
238 
239  _updateGenerators: function MSS__updateGenerators() {
240  var generators = this._localGenerators;
241  var update = getPref("browser.microsummary.updateGenerators", true);
242  if (!generators || !update)
243  return;
244 
245  for (let uri in generators)
246  generators[uri].update();
247  },
248 
249  _updateMicrosummary: function MSS__updateMicrosummary(bookmarkID, microsummary) {
250  var title = this._bms.getItemTitle(bookmarkID);
251 
252  // Ensure the user-given title is cached
253  if (!this._ans.itemHasAnnotation(bookmarkID, ANNO_STATIC_TITLE))
254  this._setAnnotation(bookmarkID, ANNO_STATIC_TITLE, title);
255 
256  // A string identifying the bookmark to use when logging the update.
257  var bookmarkIdentity = bookmarkID;
258 
259  // Update if the microsummary differs from the current title.
260  if (!title || title != microsummary.content) {
261  this._bms.setItemTitle(bookmarkID, microsummary.content);
262  var subject = new LiveTitleNotificationSubject(bookmarkID, microsummary);
263  LOG("updated live title for " + bookmarkIdentity +
264  " from '" + (title == null ? "<no live title>" : title) +
265  "' to '" + microsummary.content + "'");
266  this._obs.notifyObservers(subject, "microsummary-livetitle-updated", title);
267  }
268  else {
269  LOG("didn't update live title for " + bookmarkIdentity + "; it hasn't changed");
270  }
271 
272  // Whether or not the title itself has changed, we still save any changes
273  // to the update interval, since the interval represents how long to wait
274  // before checking again for updates, and that can vary across updates,
275  // even when the title itself hasn't changed.
276  this._setAnnotation(bookmarkID, ANNO_MICSUM_EXPIRATION,
277  Date.now() + (microsummary.updateInterval || this._updateInterval));
278  },
279 
284  _cacheLocalGenerators: function MSS__cacheLocalGenerators() {
285  // Load generators from the application directory.
286  var appDir = this._dirs.get("MicsumGens", Ci.nsIFile);
287  if (appDir.exists())
288  this._cacheLocalGeneratorDir(appDir);
289 
290  // Load generators from the user's profile.
291  var profileDir = this._dirs.get("UsrMicsumGens", Ci.nsIFile);
292  if (profileDir.exists())
293  this._cacheLocalGeneratorDir(profileDir);
294  },
295 
303  _cacheLocalGeneratorDir: function MSS__cacheLocalGeneratorDir(dir) {
304  var files = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
305  var file = files.nextFile;
306 
307  while (file) {
308  // Recursively load generators so support packs containing
309  // lots of generators can organize them into multiple directories.
310  if (file.isDirectory())
311  this._cacheLocalGeneratorDir(file);
312  else
313  this._cacheLocalGeneratorFile(file);
314 
315  file = files.nextFile;
316  }
317  files.close();
318  },
319 
327  _cacheLocalGeneratorFile: function MSS__cacheLocalGeneratorFile(file) {
328  var uri = this._ios.newFileURI(file);
329 
330  var t = this;
331  var callback =
332  function MSS_cacheLocalGeneratorCallback(resource) {
333  try { t._handleLocalGenerator(resource) }
334  finally { resource.destroy() }
335  };
336 
337  var resource = new MicrosummaryResource(uri);
338  resource.load(callback);
339  },
340 
341  _handleLocalGenerator: function MSS__handleLocalGenerator(resource) {
342  if (!resource.isXML)
343  throw(resource.uri.spec + " microsummary generator loaded, but not XML");
344 
345  var generator = new MicrosummaryGenerator(null, resource.uri);
346  generator.initFromXML(resource.content);
347 
348  // Add the generator to the local generators cache.
349  // XXX Figure out why Firefox crashes on shutdown if we index generators
350  // by uri.spec but doesn't crash if we index by uri.spec.split().join().
351  //this._localGenerators[generator.uri.spec] = generator;
352  this._localGenerators[generator.uri.spec.split().join()] = generator;
353 
354  LOG("loaded local microsummary generator\n" +
355  " file: " + generator.localURI.spec + "\n" +
356  " ID: " + generator.uri.spec);
357  },
358 
359  // nsIMicrosummaryService
360 
367  getGenerator: function MSS_getGenerator(generatorURI) {
368  return this._localGenerators[generatorURI.spec] ||
369  new MicrosummaryGenerator(generatorURI);
370  },
371 
380  addGenerator: function MSS_addGenerator(generatorURI) {
381  var t = this;
382  var callback =
383  function MSS_addGeneratorCallback(resource) {
384  try { t._handleNewGenerator(resource) }
385  finally { resource.destroy() }
386  };
387 
388  var resource = new MicrosummaryResource(generatorURI);
389  resource.load(callback);
390  },
391 
392  _handleNewGenerator: function MSS__handleNewGenerator(resource) {
393  if (!resource.isXML)
394  throw(resource.uri.spec + " microsummary generator loaded, but not XML");
395 
396  // XXX Make sure it's a valid microsummary generator.
397 
398  var rootNode = resource.content.documentElement;
399 
400  // Add a reference to the URI from which we got this generator so we have
401  // a unique identifier for the generator and also so we can check back later
402  // for updates.
403  rootNode.setAttribute("uri", "urn:source:" + resource.uri.spec);
404 
405  this.installGenerator(resource.content);
406  },
407 
417  installGenerator: function MSS_installGenerator(xmlDefinition) {
418  var rootNode = xmlDefinition.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
419 
420  var generatorID = rootNode.getAttribute("uri");
421 
422  // The existing cache entry for this generator, if it is already installed.
423  var generator = this._localGenerators[generatorID];
424 
425  var topic;
426  if (generator)
427  topic = "microsummary-generator-updated";
428  else {
429  // This generator is not already installed. Save it as a new file.
430  topic = "microsummary-generator-installed";
431  var generatorName = rootNode.getAttribute("name");
432  var fileName = sanitizeName(generatorName) + ".xml";
433  var file = this._dirs.get("UsrMicsumGens", Ci.nsIFile);
434  file.append(fileName);
435  file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
436  generator = new MicrosummaryGenerator(null, this._ios.newFileURI(file));
437  this._localGenerators[generatorID] = generator;
438  }
439 
440  // Initialize (or reinitialize) the generator from its XML definition,
441  // the save the definition to the generator's file.
442  generator.initFromXML(xmlDefinition);
443  generator.saveXMLToFile(xmlDefinition);
444 
445  LOG("installed generator " + generatorID);
446 
447  this._obs.notifyObservers(generator, topic, null);
448 
449  return generator;
450  },
451 
474  getMicrosummaries: function MSS_getMicrosummaries(pageURI, bookmarkID) {
475  var microsummaries = new MicrosummarySet();
476 
477  if (!getPref("browser.microsummary.enabled", true))
478  return microsummaries;
479 
480  // Get microsummaries defined by local generators.
481  for (var genURISpec in this._localGenerators) {
482  var generator = this._localGenerators[genURISpec];
483 
484  if (generator.appliesToURI(pageURI)) {
485  var microsummary = new Microsummary(pageURI, generator);
486 
487  // If this is the current microsummary for this bookmark, load the content
488  // from the datastore so it shows up immediately in microsummary picking UI.
489  if (bookmarkID != -1 && this.isMicrosummary(bookmarkID, microsummary))
490  microsummary._content = this._bms.getItemTitle(bookmarkID);
491 
492  microsummaries.AppendElement(microsummary);
493  }
494  }
495 
496  // If a bookmark identifier has been provided, list its microsummary
497  // synchronously, if any.
498  if (bookmarkID != -1 && this.hasMicrosummary(bookmarkID)) {
499  var currentMicrosummary = this.getMicrosummary(bookmarkID);
500  if (!microsummaries.hasItemForMicrosummary(currentMicrosummary))
501  microsummaries.AppendElement(currentMicrosummary);
502  }
503 
504  // Get microsummaries defined by the page. If we don't have the page,
505  // download it asynchronously, and then finish populating the set.
506  var resource = getLoadedMicrosummaryResource(pageURI);
507  if (resource) {
508  try { microsummaries.extractFromPage(resource) }
509  finally { resource.destroy() }
510  }
511  else {
512  // Load the page with a callback that will add the page's microsummaries
513  // to the set once the page has loaded.
514  var callback = function MSS_extractFromPageCallback(resource) {
515  try { microsummaries.extractFromPage(resource) }
516  finally { resource.destroy() }
517  };
518 
519  try {
520  resource = new MicrosummaryResource(pageURI);
521  resource.load(callback);
522  }
523  catch(e) {
524  // We don't have to do anything special if the call fails besides
525  // destroying the Resource object. We can just return the list
526  // of microsummaries without including page-defined microsummaries.
527  if (resource)
528  resource.destroy();
529  LOG("error downloading page to extract its microsummaries: " + e);
530  }
531  }
532 
533  return microsummaries;
534  },
535 
547  _changeField: function MSS__changeField(fieldName, oldValue, newValue) {
548  var bookmarks = this._bookmarks;
549 
550  for ( var i = 0; i < bookmarks.length; i++ ) {
551  var bookmarkID = bookmarks[i];
552 
553  if (this._ans.itemHasAnnotation(bookmarkID, fieldName) &&
554  this._ans.getItemAnnotation(bookmarkID, fieldName) == oldValue)
555  this._setAnnotation(bookmarkID, fieldName, newValue);
556  }
557  },
558 
569  get _bookmarks() {
570  var bookmarks = this._ans.getItemsWithAnnotation(ANNO_MICSUM_GEN_URI, {});
571  this.__defineGetter__("_bookmarks", function() bookmarks);
572  return this._bookmarks;
573  },
574 
575  _setAnnotation: function MSS__setAnnotation(aBookmarkId, aFieldName, aFieldValue) {
576  this._ans.setItemAnnotation(aBookmarkId,
577  aFieldName,
578  aFieldValue,
579  0,
580  this._ans.EXPIRE_NEVER);
581  },
582 
593  getBookmarks: function MSS_getBookmarks() {
594  return new ArrayEnumerator(this._bookmarks);
595  },
596 
607  getMicrosummary: function MSS_getMicrosummary(bookmarkID) {
608  if (!this.hasMicrosummary(bookmarkID))
609  return null;
610 
611  var pageURI = this._bms.getBookmarkURI(bookmarkID);
612  var generatorURI = this._uri(this._ans.getItemAnnotation(bookmarkID,
614  var generator = this.getGenerator(generatorURI);
615 
616  return new Microsummary(pageURI, generator);
617  },
618 
631  createMicrosummary: function MSS_createMicrosummary(pageURI, generatorURI) {
632  var generator = this.getGenerator(generatorURI);
633  return new Microsummary(pageURI, generator);
634  },
635 
646  setMicrosummary: function MSS_setMicrosummary(bookmarkID, microsummary) {
647  this._setAnnotation(bookmarkID, ANNO_MICSUM_GEN_URI, microsummary.generator.uri.spec);
648 
649  if (microsummary.content)
650  this._updateMicrosummary(bookmarkID, microsummary);
651  else
652  this.refreshMicrosummary(bookmarkID);
653  },
654 
662  removeMicrosummary: function MSS_removeMicrosummary(bookmarkID) {
663  // Restore the user's title
664  if (this._ans.itemHasAnnotation(bookmarkID, ANNO_STATIC_TITLE))
665  this._bms.setItemTitle(bookmarkID, this._ans.getItemAnnotation(bookmarkID, ANNO_STATIC_TITLE));
666 
667  var fields = [ANNO_MICSUM_GEN_URI,
671 
672  for (let i = 0; i < fields.length; i++) {
673  var field = fields[i];
674  if (this._ans.itemHasAnnotation(bookmarkID, field))
675  this._ans.removeItemAnnotation(bookmarkID, field);
676  }
677  },
678 
689  hasMicrosummary: function MSS_hasMicrosummary(aBookmarkId) {
690  return (this._bookmarks.indexOf(aBookmarkId) != -1);
691  },
692 
707  isMicrosummary: function MSS_isMicrosummary(aBookmarkID, aMicrosummary) {
708  if (!aMicrosummary || !aBookmarkID)
709  throw Cr.NS_ERROR_INVALID_ARG;
710 
711  if (this.hasMicrosummary(aBookmarkID)) {
712  var currentMicrosummarry = this.getMicrosummary(aBookmarkID);
713  if (aMicrosummary.equals(currentMicrosummarry))
714  return true;
715  }
716  return false
717  },
718 
737  refreshMicrosummary: function MSS_refreshMicrosummary(bookmarkID) {
738  if (!this.hasMicrosummary(bookmarkID))
739  throw "bookmark " + bookmarkID + " does not have a microsummary";
740 
741  var pageURI = this._bms.getBookmarkURI(bookmarkID);
742  if (!pageURI)
743  throw("can't get URL for bookmark with ID " + bookmarkID);
744  var generatorURI = this._uri(this._ans.getItemAnnotation(bookmarkID,
746 
747  var generator = this._localGenerators[generatorURI.spec] ||
748  new MicrosummaryGenerator(generatorURI);
749 
750  var microsummary = new Microsummary(pageURI, generator);
751 
752  // A microsummary observer that calls the microsummary service
753  // to update the datastore when the microsummary finishes loading.
754  var observer = {
755  _svc: this,
756  _bookmarkID: bookmarkID,
757  onContentLoaded: function MSS_observer_onContentLoaded(microsummary) {
758  try {
759  this._svc._updateMicrosummary(this._bookmarkID, microsummary);
760  }
761  catch (ex) {
762  Cu.reportError("refreshMicrosummary() observer: " + ex);
763  }
764  finally {
765  this._svc = null;
766  this._bookmarkID = null;
767  microsummary.removeObserver(this);
768  }
769  },
770 
771  onError: function MSS_observer_onError(microsummary) {
772  if (microsummary.needsRemoval)
773  this._svc.removeMicrosummary(this._bookmarkID);
774  }
775  };
776 
777  // Register the observer with the microsummary and trigger the microsummary
778  // to update itself.
779  microsummary.addObserver(observer);
780  microsummary.update();
781 
782  return microsummary;
783  },
784 
785  // nsIAnnotationObserver
786  onItemAnnotationSet: function(aItemId, aAnnotationName) {
787  if (aAnnotationName == ANNO_MICSUM_GEN_URI &&
788  this._bookmarks.indexOf(aItemId) == -1)
789  this._bookmarks.push(aItemId);
790  },
791  onItemAnnotationRemoved: function(aItemId, aAnnotationName) {
792  var index = this._bookmarks.indexOf(aItemId);
793  var isMicsumAnno = aAnnotationName == ANNO_MICSUM_GEN_URI ||
794  !aAnnotationName.length; /* all annos were removed */
795  if (index > -1 && isMicsumAnno)
796  this._bookmarks.splice(index, 1);
797  },
798  onPageAnnotationSet: function(aUri, aAnnotationName) {},
799  onPageAnnotationRemoved: function(aUri, aAnnotationName) {},
800 };
801 
802 
803 
804 
805 
806 function LiveTitleNotificationSubject(bookmarkID, microsummary) {
807  this.bookmarkID = bookmarkID;
808  this.microsummary = microsummary;
809 }
810 
811 LiveTitleNotificationSubject.prototype = {
812  bookmarkID: null,
813  microsummary: null,
814 
815  // nsISupports
816  QueryInterface: XPCOMUtils.generateQI([Ci.nsILiveTitleNotificationSubject]),
817 };
818 
819 
820 
821 
822 
823 function Microsummary(aPageURI, aGenerator) {
824  this._observers = [];
825  this._pageURI = aPageURI || null;
826  this._generator = aGenerator || null;
827  this._content = null;
828  this._pageContent = null;
829  this._updateInterval = null;
830  this._needsRemoval = false;
831 }
832 
833 Microsummary.prototype = {
834  // The microsummary service.
835  __mss: null,
836  get _mss() {
837  if (!this.__mss)
838  this.__mss = Cc["@mozilla.org/microsummary/service;1"].
839  getService(Ci.nsIMicrosummaryService);
840  return this.__mss;
841  },
842 
843  // IO Service
844  __ios: null,
845  get _ios() {
846  if (!this.__ios)
847  this.__ios = Cc["@mozilla.org/network/io-service;1"].
848  getService(Ci.nsIIOService);
849  return this.__ios;
850  },
851 
858  _uri: function MSS__uri(spec) {
859  return this._ios.newURI(spec, null, null);
860  },
861 
862  // nsISupports
863  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummary]),
864 
865  // nsIMicrosummary
866  get content() {
867  // If we have everything we need to generate the content, generate it.
868  if (!this._content &&
869  this.generator.loaded &&
870  (this.pageContent || !this.generator.needsPageContent)) {
871  this._content = this.generator.generateMicrosummary(this.pageContent);
872  this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
873  }
874 
875  // Note: we return "null" if the content wasn't already generated and we
876  // couldn't retrieve it from the generated title annotation or generate it
877  // ourselves. So callers shouldn't count on getting content; instead,
878  // they should call update if the return value of this getter is "null",
879  // setting an observer to tell them when content generation is done.
880  return this._content;
881  },
882 
883  get generator() { return this._generator },
884  set generator(newValue) { return this._generator = newValue },
885 
886  get pageURI() { return this._pageURI },
887 
888  equals: function(aOther) {
889  if (this._generator &&
890  this._pageURI.equals(aOther.pageURI) &&
891  this._generator.equals(aOther.generator))
892  return true;
893 
894  return false;
895  },
896 
897  get pageContent() {
898  if (!this._pageContent) {
899  // If the page is currently loaded into a browser window, use that.
900  var resource = getLoadedMicrosummaryResource(this.pageURI);
901  if (resource) {
902  this._pageContent = resource.content;
903  resource.destroy();
904  }
905  }
906 
907  return this._pageContent;
908  },
909  set pageContent(newValue) { return this._pageContent = newValue },
910 
911  get updateInterval() { return this._updateInterval; },
912  set updateInterval(newValue) { return this._updateInterval = newValue; },
913 
914  get needsRemoval() { return this._needsRemoval; },
915 
916  // nsIMicrosummary
917 
918  addObserver: function MS_addObserver(observer) {
919  // Register the observer, but only if it isn't already registered,
920  // so that we don't call the same observer twice for any given change.
921  if (this._observers.indexOf(observer) == -1)
922  this._observers.push(observer);
923  },
924 
925  removeObserver: function MS_removeObserver(observer) {
926  //NS_ASSERT(this._observers.indexOf(observer) != -1,
927  // "can't remove microsummary observer " + observer + ": not registered");
928 
929  //this._observers =
930  // this._observers.filter(function(i) { observer != i });
931  if (this._observers.indexOf(observer) != -1)
932  this._observers.splice(this._observers.indexOf(observer), 1);
933  },
934 
940  update: function MS_update() {
941  LOG("microsummary.update called for page:\n " + this.pageURI.spec +
942  "\nwith generator:\n " + this.generator.uri.spec);
943 
944  var t = this;
945 
946  // We use a common error callback here to flag this microsummary for removal
947  // if either the generator or page content have gone permanently missing.
948  var errorCallback = function MS_errorCallback(resource) {
949  if (resource.status == 410) {
950  t._needsRemoval = true;
951  LOG("server indicated " + resource.uri.spec + " is gone. flagging for removal");
952  }
953 
954  resource.destroy();
955 
956  for (let i = 0; i < t._observers.length; i++)
957  t._observers[i].onError(t);
958  };
959 
960  // If we don't have the generator, download it now. After it downloads,
961  // we'll re-call this method to continue updating the microsummary.
962  if (!this.generator.loaded) {
963  // If this generator is identified by a URN, then it's a local generator
964  // that should have been cached on application start, so it's missing.
965  if (this.generator.uri.scheme == "urn") {
966  // If it was installed via nsSidebar::addMicrosummaryGenerator (i.e. it
967  // has a URN that identifies the source URL from which we installed it),
968  // try to reinstall it (once).
969  if (/^source:/.test(this.generator.uri.path)) {
970  this._reinstallMissingGenerator();
971  return;
972  }
973  else
974  throw "missing local generator: " + this.generator.uri.spec;
975  }
976 
977  LOG("generator not yet loaded; downloading it");
978  var generatorCallback =
979  function MS_generatorCallback(resource) {
980  try { t._handleGeneratorLoad(resource) }
981  finally { resource.destroy() }
982  };
983  var resource = new MicrosummaryResource(this.generator.uri);
984  resource.load(generatorCallback, errorCallback);
985  return;
986  }
987 
988  // If we need the page content, and we don't have it, download it now.
989  // Afterwards we'll re-call this method to continue updating the microsummary.
990  if (this.generator.needsPageContent && !this.pageContent) {
991  LOG("page content not yet loaded; downloading it");
992  var pageCallback =
993  function MS_pageCallback(resource) {
994  try { t._handlePageLoad(resource) }
995  finally { resource.destroy() }
996  };
997  var resource = new MicrosummaryResource(this.pageURI);
998  resource.load(pageCallback, errorCallback);
999  return;
1000  }
1001 
1002  LOG("generator (and page, if needed) both loaded; generating microsummary");
1003 
1004  // Now that we have both the generator and (if needed) the page content,
1005  // generate the microsummary, then let the observers know about it.
1006  this._content = this.generator.generateMicrosummary(this.pageContent);
1007  this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
1008  this.pageContent = null;
1009  for ( var i = 0; i < this._observers.length; i++ )
1010  this._observers[i].onContentLoaded(this);
1011 
1012  LOG("generated microsummary: " + this.content);
1013  },
1014 
1015  _handleGeneratorLoad: function MS__handleGeneratorLoad(resource) {
1016  LOG(this.generator.uri.spec + " microsummary generator downloaded");
1017  if (resource.isXML)
1018  this.generator.initFromXML(resource.content);
1019  else if (resource.contentType == "text/plain")
1020  this.generator.initFromText(resource.content);
1021  else if (resource.contentType == "text/html")
1022  this.generator.initFromText(resource.content.body.textContent);
1023  else
1024  throw("generator is neither XML nor plain text");
1025 
1026  // Only trigger a [content] update if we were able to init the generator.
1027  if (this.generator.loaded)
1028  this.update();
1029  },
1030 
1031  _handlePageLoad: function MS__handlePageLoad(resource) {
1032  if (!resource.isXML && resource.contentType != "text/html")
1033  throw("page is neither HTML nor XML");
1034 
1035  this.pageContent = resource.content;
1036  this.update();
1037  },
1038 
1044  _reinstallMissingGenerator: function MS__reinstallMissingGenerator() {
1045  LOG("attempting to reinstall missing generator " + this.generator.uri.spec);
1046 
1047  var t = this;
1048 
1049  var loadCallback =
1050  function MS_missingGeneratorLoadCallback(resource) {
1051  try { t._handleMissingGeneratorLoad(resource) }
1052  finally { resource.destroy() }
1053  };
1054 
1055  var errorCallback =
1056  function MS_missingGeneratorErrorCallback(resource) {
1057  try { t._handleMissingGeneratorError(resource) }
1058  finally { resource.destroy() }
1059  };
1060 
1061  try {
1062  // Extract the URI from which the generator was originally installed.
1063  var sourceURL = this.generator.uri.path.replace(/^source:/, "");
1064  var sourceURI = this._uri(sourceURL);
1065 
1066  var resource = new MicrosummaryResource(sourceURI);
1067  resource.load(loadCallback, errorCallback);
1068  }
1069  catch(ex) {
1070  Cu.reportError(ex);
1071  this._handleMissingGeneratorError();
1072  }
1073  },
1074 
1086  _handleMissingGeneratorLoad: function MS__handleMissingGeneratorLoad(resource) {
1087  try {
1088  // Make sure the generator is XML, since local generators have to be.
1089  if (!resource.isXML)
1090  throw("downloaded, but not XML " + this.generator.uri.spec);
1091 
1092  // Store the generator's ID in its XML definition.
1093  var generatorID = this.generator.uri.spec;
1094  resource.content.documentElement.setAttribute("uri", generatorID);
1095 
1096  // Reinstall the generator and replace our placeholder generator object
1097  // with the newly installed generator.
1098  this.generator = this._mss.installGenerator(resource.content);
1099 
1100  // A reinstalled generator should always be loaded. But just in case
1101  // it isn't, throw an error so we don't get into an infinite loop
1102  // (otherwise this._update would try again to reinstall it).
1103  if (!this.generator.loaded)
1104  throw("supposedly installed, but not in cache " + this.generator.uri.spec);
1105  }
1106  catch(ex) {
1107  Cu.reportError(ex);
1108  this._handleMissingGeneratorError(resource);
1109  return;
1110  }
1111 
1112  LOG("reinstall succeeded; resuming update " + this.generator.uri.spec);
1113  this.update();
1114  },
1115 
1126  _handleMissingGeneratorError: function MS__handleMissingGeneratorError(resource) {
1127  LOG("reinstall failed; removing microsummaries " + this.generator.uri.spec);
1128  var bookmarks = this._mss.getBookmarks();
1129  while (bookmarks.hasMoreElements()) {
1130  var bookmarkID = bookmarks.getNext();
1131  var microsummary = this._mss.getMicrosummary(bookmarkID);
1132  if (microsummary.generator.uri.equals(this.generator.uri)) {
1133  LOG("removing microsummary for " + microsummary.pageURI.spec);
1134  this._mss.removeMicrosummary(bookmarkID);
1135  }
1136  }
1137  }
1138 
1139 };
1140 
1141 
1142 
1143 
1144 
1145 function MicrosummaryGenerator(aURI, aLocalURI, aName) {
1146  this._uri = aURI || null;
1147  this._localURI = aLocalURI || null;
1148  this._name = aName || null;
1149  this._loaded = false;
1150  this._rules = [];
1151  this._template = null;
1152  this._content = null;
1153 }
1154 
1155 MicrosummaryGenerator.prototype = {
1156 
1157  // IO Service
1158  __ios: null,
1159  get _ios() {
1160  if (!this.__ios)
1161  this.__ios = Cc["@mozilla.org/network/io-service;1"].
1162  getService(Ci.nsIIOService);
1163  return this.__ios;
1164  },
1165 
1166  // nsISupports
1167  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummaryGenerator]),
1168 
1169  // nsIMicrosummaryGenerator
1170 
1171  // Normally this is just the URL from which we download the generator,
1172  // but for generators stored in the app or profile generators directory
1173  // it's the value of the generator tag's "uri" attribute (or its local URI
1174  // should the "uri" attribute be missing).
1175  get uri() { return this._uri || this.localURI },
1176 
1177  // For generators bundled with the browser or installed by the user,
1178  // the local URI is the URI of the local file containing the generator XML.
1179  get localURI() { return this._localURI },
1180  get name() { return this._name },
1181  get loaded() { return this._loaded },
1182 
1183  equals: function(aOther) {
1184  // XXX: could the uri attribute for an exposed generator ever be null?
1185  return aOther.uri.equals(this.uri);
1186  },
1187 
1201  appliesToURI: function(uri) {
1202  var applies = false;
1203 
1204  for ( var i = 0 ; i < this._rules.length ; i++ ) {
1205  var rule = this._rules[i];
1206 
1207  switch (rule.type) {
1208  case "include":
1209  if (rule.regexp.test(uri.spec))
1210  applies = true;
1211  break;
1212  case "exclude":
1213  if (rule.regexp.test(uri.spec))
1214  return false;
1215  break;
1216  }
1217  }
1218 
1219  return applies;
1220  },
1221 
1222  get needsPageContent() {
1223  if (this._template)
1224  return true;
1225  if (this._content)
1226  return false;
1227 
1228  throw("needsPageContent called on uninitialized microsummary generator");
1229  },
1230 
1239  initFromText: function(text) {
1240  this._content = text;
1241  this._loaded = true;
1242  },
1243 
1251  initFromXML: function(xmlDocument) {
1252  // XXX Make sure the argument is a valid generator XML document.
1253 
1254  // XXX I would have wanted to retrieve the info from the XML via E4X,
1255  // but we'll need to pass the XSLT transform sheet to the XSLT processor,
1256  // and the processor can't deal with an E4X-wrapped template node.
1257 
1258  // XXX Right now the code retrieves the first "generator" element
1259  // in the microsummaries namespace, regardless of whether or not
1260  // it's the root element. Should it matter?
1261  var generatorNode = xmlDocument.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
1262  if (!generatorNode)
1263  throw Cr.NS_ERROR_FAILURE;
1264 
1265  this._name = generatorNode.getAttribute("name");
1266 
1267  // We have to retrieve the URI from local generators via the "uri" attribute
1268  // of its generator tag.
1269  if (this.localURI && generatorNode.hasAttribute("uri"))
1270  this._uri = this._ios.newURI(generatorNode.getAttribute("uri"), null, null);
1271 
1272  function getFirstChildByTagName(tagName, parentNode, namespace) {
1273  var nodeList = parentNode.getElementsByTagNameNS(namespace, tagName);
1274  for (var i = 0; i < nodeList.length; i++) {
1275  // Make sure that the node is a direct descendent of the generator node
1276  if (nodeList[i].parentNode == parentNode)
1277  return nodeList[i];
1278  }
1279  return null;
1280  }
1281 
1282  // Slurp the include/exclude rules that determine the pages to which
1283  // this generator applies. Order is important, so we add the rules
1284  // in the order in which they appear in the XML.
1285  this._rules.splice(0);
1286  var pages = getFirstChildByTagName("pages", generatorNode, MICSUM_NS);
1287  if (pages) {
1288  // XXX Make sure the pages tag exists.
1289  for ( var i = 0; i < pages.childNodes.length ; i++ ) {
1290  var node = pages.childNodes[i];
1291  if (node.nodeType != node.ELEMENT_NODE ||
1292  node.namespaceURI != MICSUM_NS ||
1293  (node.nodeName != "include" && node.nodeName != "exclude"))
1294  continue;
1295  var urlRegexp = node.textContent.replace(/^\s+|\s+$/g, "");
1296  this._rules.push({ type: node.nodeName, regexp: new RegExp(urlRegexp) });
1297  }
1298  }
1299 
1300  // allow the generators to set individual update values (even varying
1301  // depending on certain XPath expressions)
1302  var update = getFirstChildByTagName("update", generatorNode, MICSUM_NS);
1303  if (update) {
1304  function _parseInterval(string) {
1305  // convert from minute fractions to milliseconds
1306  // and ensure a minimum value of 1 minute
1307  return Math.round(Math.max(parseFloat(string) || 0, 1) * 60 * 1000);
1308  }
1309 
1310  this._unconditionalUpdateInterval =
1311  update.hasAttribute("interval") ?
1312  _parseInterval(update.getAttribute("interval")) : null;
1313 
1314  // collect the <condition expression="XPath Expression" interval="time"/> clauses
1315  this._updateIntervals = new Array();
1316  for (i = 0; i < update.childNodes.length; i++) {
1317  node = update.childNodes[i];
1318  if (node.nodeType != node.ELEMENT_NODE || node.namespaceURI != MICSUM_NS ||
1319  node.nodeName != "condition")
1320  continue;
1321  if (!node.getAttribute("expression") || !node.getAttribute("interval")) {
1322  LOG("ignoring incomplete conditional update interval for " + this.uri.spec);
1323  continue;
1324  }
1325  this._updateIntervals.push({
1326  expression: node.getAttribute("expression"),
1327  interval: _parseInterval(node.getAttribute("interval"))
1328  });
1329  }
1330  }
1331 
1332  var templateNode = getFirstChildByTagName("template", generatorNode, MICSUM_NS);
1333  if (templateNode) {
1334  this._template = getFirstChildByTagName("transform", templateNode, XSLT_NS) ||
1335  getFirstChildByTagName("stylesheet", templateNode, XSLT_NS);
1336  }
1337  // XXX Make sure the template is a valid XSL transform sheet.
1338 
1339  this._loaded = true;
1340  },
1341 
1342  generateMicrosummary: function MSD_generateMicrosummary(pageContent) {
1343 
1344  var content;
1345 
1346  if (this._content)
1347  content = this._content;
1348  else if (this._template)
1349  content = this._processTemplate(pageContent);
1350  else
1351  throw("generateMicrosummary called on uninitialized microsummary generator");
1352 
1353  // Clean up the output
1354  content = content.replace(/^\s+|\s+$/g, "");
1355  if (content.length > MAX_SUMMARY_LENGTH)
1356  content = content.substring(0, MAX_SUMMARY_LENGTH);
1357 
1358  return content;
1359  },
1360 
1361  calculateUpdateInterval: function MSD_calculateUpdateInterval(doc) {
1362  if (this._content || !this._updateIntervals || !doc)
1363  return null;
1364 
1365  for (var i = 0; i < this._updateIntervals.length; i++) {
1366  try {
1367  if (doc.evaluate(this._updateIntervals[i].expression, doc, null,
1368  Ci.nsIDOMXPathResult.BOOLEAN_TYPE, null).booleanValue)
1369  return this._updateIntervals[i].interval;
1370  }
1371  catch (ex) {
1372  Cu.reportError(ex);
1373  // remove the offending conditional update interval
1374  this._updateIntervals.splice(i--, 1);
1375  }
1376  }
1377 
1378  return this._unconditionalUpdateInterval;
1379  },
1380 
1381  _processTemplate: function MSD__processTemplate(doc) {
1382  LOG("processing template " + this._template + " against document " + doc);
1383 
1384  // XXX Should we just have one global instance of the processor?
1385  var processor = Cc["@mozilla.org/document-transformer;1?type=xslt"].
1386  createInstance(Ci.nsIXSLTProcessor);
1387 
1388  // Turn off document loading of all kinds (document(), <include>, <import>)
1389  // for security (otherwise local generators would be able to load local files).
1390  processor.flags |= Ci.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
1391 
1392  processor.importStylesheet(this._template);
1393  var fragment = processor.transformToFragment(doc, doc);
1394 
1395  LOG("template processing result: " + fragment.textContent);
1396 
1397  // XXX When we support HTML microsummaries we'll need to do something
1398  // more sophisticated than just returning the text content of the fragment.
1399  return fragment.textContent;
1400  },
1401 
1402  saveXMLToFile: function MSD_saveXMLToFile(xmlDefinition) {
1403  var file = this.localURI.QueryInterface(Ci.nsIFileURL).file.clone();
1404 
1405  LOG("saving definition to " + file.path);
1406 
1407  // Write the generator XML to the local file.
1408  var outputStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
1409  createInstance(Ci.nsIFileOutputStream);
1410  var localFile = file.QueryInterface(Ci.nsILocalFile);
1411  outputStream.init(localFile, (MODE_WRONLY | MODE_TRUNCATE | MODE_CREATE),
1412  PERMS_FILE, 0);
1413  var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
1414  createInstance(Ci.nsIDOMSerializer);
1415  serializer.serializeToStream(xmlDefinition, outputStream, null);
1416  if (outputStream instanceof Ci.nsISafeOutputStream) {
1417  try { outputStream.finish() }
1418  catch (e) { outputStream.close() }
1419  }
1420  else
1421  outputStream.close();
1422  },
1423 
1424  update: function MSD_update() {
1425  // Update this generator if it was downloaded from a remote source and has
1426  // been modified since we last downloaded it.
1427  var genURI = this.uri;
1428  if (genURI && /^urn:source:/i.test(genURI.spec)) {
1429  let genURL = genURI.spec.replace(/^urn:source:/, "");
1430  genURI = this._ios.newURI(genURL, null, null);
1431  }
1432 
1433  // Only continue if we have a valid remote URI
1434  if (!genURI || !/^https?/.test(genURI.scheme)) {
1435  LOG("generator did not have valid URI; skipping update: " + genURI.spec);
1436  return;
1437  }
1438 
1439  // We use a HEAD request to check if the generator has been modified since
1440  // the last time we downloaded it. If it has, we move to _preformUpdate() to
1441  // actually download and save the new generator.
1442  var t = this;
1443  var loadCallback = function(resource) {
1444  if (resource.status != 304)
1445  t._performUpdate(genURI);
1446  else
1447  LOG("generator is already up to date: " + genURI.spec);
1448  resource.destroy();
1449  };
1450  var errorCallback = function(resource) {
1451  resource.destroy();
1452  };
1453 
1454  var file = this.localURI.QueryInterface(Ci.nsIFileURL).file.clone();
1455  var lastmod = new Date(file.lastModifiedTime);
1456  LOG("updating generator: " + genURI.spec);
1457  var resource = new MicrosummaryResource(genURI);
1458  resource.lastMod = lastmod.toUTCString();
1459  resource.method = "HEAD";
1460  resource.load(loadCallback, errorCallback);
1461  },
1462 
1463  _performUpdate: function MSD__performUpdate(uri) {
1464  var t = this;
1465  var loadCallback = function(resource) {
1466  try { t._handleUpdateLoad(resource) }
1467  finally { resource.destroy() }
1468  };
1469  var errorCallback = function(resource) {
1470  resource.destroy();
1471  };
1472 
1473  var resource = new MicrosummaryResource(uri);
1474  resource.load(loadCallback, errorCallback);
1475  },
1476 
1477  _handleUpdateLoad: function MSD__handleUpdateLoad(resource) {
1478  if (!resource.isXML)
1479  throw("update failed, downloaded resource is not XML: " + this.uri.spec);
1480 
1481  // Preserve the generator's ID.
1482  // XXX Check for redirects and update the URI if it changes.
1483  var generatorID = this.uri.spec;
1484  resource.content.documentElement.setAttribute("uri", generatorID);
1485 
1486  // Reinitialize this generator with the newly downloaded XML and save to disk.
1487  this.initFromXML(resource.content);
1488  this.saveXMLToFile(resource.content);
1489 
1490  // Let observers know we've updated this generator
1491  var obs = Cc["@mozilla.org/observer-service;1"].
1492  getService(Ci.nsIObserverService);
1493  obs.notifyObservers(this, "microsummary-generator-updated", null);
1494  }
1495 };
1496 
1497 
1498 
1499 
1500 
1501 // Microsummary sets are collections of microsummaries. They allow callers
1502 // to register themselves as observers of the set, and when any microsummary
1503 // in the set changes, the observers get notified. Thus a caller can observe
1504 // the set instead of each individual microsummary.
1505 
1506 function MicrosummarySet() {
1507  this._observers = [];
1508  this._elements = [];
1509 }
1510 
1511 MicrosummarySet.prototype = {
1512  // IO Service
1513  __ios: null,
1514  get _ios() {
1515  if (!this.__ios)
1516  this.__ios = Cc["@mozilla.org/network/io-service;1"].
1517  getService(Ci.nsIIOService);
1518  return this.__ios;
1519  },
1520 
1521  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMicrosummarySet,
1522  Ci.nsIMicrosummaryObserver]),
1523 
1524  // nsIMicrosummaryObserver
1525 
1526  onContentLoaded: function MSSet_onContentLoaded(microsummary) {
1527  for ( var i = 0; i < this._observers.length; i++ )
1528  this._observers[i].onContentLoaded(microsummary);
1529  },
1530 
1531  onError: function MSSet_onError(microsummary) {
1532  for ( var i = 0; i < this._observers.length; i++ )
1533  this._observers[i].onError(microsummary);
1534  },
1535 
1536  // nsIMicrosummarySet
1537 
1538  addObserver: function MSSet_addObserver(observer) {
1539  if (this._observers.length == 0) {
1540  for ( var i = 0 ; i < this._elements.length ; i++ )
1541  this._elements[i].addObserver(this);
1542  }
1543 
1544  // Register the observer, but only if it isn't already registered,
1545  // so that we don't call the same observer twice for any given change.
1546  if (this._observers.indexOf(observer) == -1)
1547  this._observers.push(observer);
1548  },
1549 
1550  removeObserver: function MSSet_removeObserver(observer) {
1551  //NS_ASSERT(this._observers.indexOf(observer) != -1,
1552  // "can't remove microsummary observer " + observer + ": not registered");
1553 
1554  //this._observers =
1555  // this._observers.filter(function(i) { observer != i });
1556  if (this._observers.indexOf(observer) != -1)
1557  this._observers.splice(this._observers.indexOf(observer), 1);
1558 
1559  if (this._observers.length == 0) {
1560  for ( var i = 0 ; i < this._elements.length ; i++ )
1561  this._elements[i].removeObserver(this);
1562  }
1563  },
1564 
1565  extractFromPage: function MSSet_extractFromPage(resource) {
1566  if (!resource.isXML && resource.contentType != "text/html")
1567  throw("page is neither HTML nor XML");
1568 
1569  // XXX Handle XML documents, whose microsummaries are specified
1570  // via processing instructions.
1571 
1572  var links = resource.content.getElementsByTagName("link");
1573  for ( var i = 0; i < links.length; i++ ) {
1574  var link = links[i];
1575 
1576  if(!link.hasAttribute("rel"))
1577  continue;
1578 
1579  var relAttr = link.getAttribute("rel");
1580 
1581  // The attribute's value can be a space-separated list of link types,
1582  // check to see if "microsummary" is one of them.
1583  var linkTypes = relAttr.split(/\s+/);
1584  if (!linkTypes.some( function(v) { return v.toLowerCase() == "microsummary"; }))
1585  continue;
1586 
1587 
1588  // Look for a TITLE attribute to give the generator a nice name in the UI.
1589  var linkTitle = link.getAttribute("title");
1590 
1591 
1592  // Unlike the "href" attribute, the "href" property contains
1593  // an absolute URI spec, so we use it here to create the URI.
1594  var generatorURI = this._ios.newURI(link.href,
1595  resource.content.characterSet,
1596  null);
1597 
1598  if (!/^https?$/i.test(generatorURI.scheme)) {
1599  LOG("can't load generator " + generatorURI.spec + " from page " +
1600  resource.uri.spec);
1601  continue;
1602  }
1603 
1604  var generator = new MicrosummaryGenerator(generatorURI, null, linkTitle);
1605  var microsummary = new Microsummary(resource.uri, generator);
1606  if (!this.hasItemForMicrosummary(microsummary))
1607  this.AppendElement(microsummary);
1608  }
1609  },
1610 
1615  hasItemForMicrosummary: function MSSet_hasItemForMicrosummary(aMicrosummary) {
1616  for (var i = 0; i < this._elements.length; i++) {
1617  if (this._elements[i].equals(aMicrosummary))
1618  return true;
1619  }
1620  return false;
1621  },
1622 
1623  // XXX Turn this into a complete implementation of nsICollection?
1624  AppendElement: function MSSet_AppendElement(element) {
1625  // Query the element to a microsummary.
1626  // XXX Should we NS_ASSERT if this fails?
1627  element = element.QueryInterface(Ci.nsIMicrosummary);
1628 
1629  if (this._elements.indexOf(element) == -1) {
1630  this._elements.push(element);
1631  element.addObserver(this);
1632  }
1633 
1634  // Notify observers that an element has been appended.
1635  for ( var i = 0; i < this._observers.length; i++ )
1636  this._observers[i].onElementAppended(element);
1637  },
1638 
1639  Enumerate: function MSSet_Enumerate() {
1640  return new ArrayEnumerator(this._elements);
1641  }
1642 };
1643 
1644 
1645 
1646 
1647 
1652 function ArrayEnumerator(aItems) {
1653  if (aItems) {
1654  for (var i = 0; i < aItems.length; ++i) {
1655  if (!aItems[i])
1656  aItems.splice(i--, 1);
1657  }
1658  this._contents = aItems;
1659  } else {
1660  this._contents = [];
1661  }
1662 }
1663 
1664 ArrayEnumerator.prototype = {
1665  QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
1666 
1667  _index: 0,
1668 
1669  hasMoreElements: function() {
1670  return this._index < this._contents.length;
1671  },
1672 
1673  getNext: function() {
1674  return this._contents[this._index++];
1675  }
1676 };
1677 
1678 
1679 
1680 
1681 
1689 function LOG(aText) {
1690  var f = arguments.callee;
1691  if (!("_enabled" in f))
1692  f._enabled = getPref("browser.microsummary.log", false);
1693  if (f._enabled) {
1694  dump("*** Microsummaries: " + aText + "\n");
1695  var consoleService = Cc["@mozilla.org/consoleservice;1"].
1696  getService(Ci.nsIConsoleService);
1697  consoleService.logStringMessage(aText);
1698  }
1699 }
1700 
1701 
1702 
1703 
1704 
1717  // Make sure we're not loading javascript: or data: URLs, which could
1718  // take advantage of the load to run code with chrome: privileges.
1719  // XXX Perhaps use nsIScriptSecurityManager.checkLoadURI instead.
1720  if (!(uri.schemeIs("http") || uri.schemeIs("https") || uri.schemeIs("file")))
1721  throw NS_ERROR_DOM_BAD_URI;
1722 
1723  this._uri = uri;
1724  this._content = null;
1725  this._contentType = null;
1726  this._isXML = false;
1727  this.__authFailed = false;
1728  this._status = null;
1729  this._method = "GET";
1730  this._lastMod = null;
1731 
1732  // A function to call when we finish loading/parsing the resource.
1733  this._loadCallback = null;
1734  // A function to call if we get an error while loading/parsing the resource.
1735  this._errorCallback = null;
1736  // A hidden iframe to parse HTML content.
1737  this._iframe = null;
1738 }
1739 
1740 MicrosummaryResource.prototype = {
1741  // IO Service
1742  __ios: null,
1743  get _ios() {
1744  if (!this.__ios)
1745  this.__ios = Cc["@mozilla.org/network/io-service;1"].
1746  getService(Ci.nsIIOService);
1747  return this.__ios;
1748  },
1749 
1750  get uri() {
1751  return this._uri;
1752  },
1753 
1754  get content() {
1755  return this._content;
1756  },
1757 
1758  get contentType() {
1759  return this._contentType;
1760  },
1761 
1762  get isXML() {
1763  return this._isXML;
1764  },
1765 
1766  get status() { return this._status },
1767  set status(aStatus) { this._status = aStatus },
1768 
1769  get method() { return this._method },
1770  set method(aMethod) { this._method = aMethod },
1771 
1772  get lastMod() { return this._lastMod },
1773  set lastMod(aMod) { this._lastMod = aMod },
1774 
1775  // Implement notification callback interfaces so we can suppress UI
1776  // and abort loads for bad SSL certs and HTTP authorization requests.
1777 
1778  // Interfaces this component implements.
1779  interfaces: [Ci.nsIAuthPromptProvider,
1780  Ci.nsIAuthPrompt,
1781  Ci.nsIBadCertListener2,
1782  Ci.nsISSLErrorListener,
1783  Ci.nsIPrompt,
1784  Ci.nsIProgressEventSink,
1785  Ci.nsIInterfaceRequestor,
1786  Ci.nsISupports],
1787 
1788  // nsISupports
1789 
1790  QueryInterface: function MSR_QueryInterface(iid) {
1791  if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
1792  throw Cr.NS_ERROR_NO_INTERFACE;
1793 
1794  // nsIAuthPrompt and nsIPrompt need separate implementations because
1795  // their method signatures conflict. The other interfaces we implement
1796  // within MicrosummaryResource itself.
1797  switch(iid) {
1798  case Ci.nsIAuthPrompt:
1799  return this.authPrompt;
1800  case Ci.nsIPrompt:
1801  return this.prompt;
1802  default:
1803  return this;
1804  }
1805  },
1806 
1807  // nsIInterfaceRequestor
1808 
1809  getInterface: function MSR_getInterface(iid) {
1810  return this.QueryInterface(iid);
1811  },
1812 
1813  // nsIBadCertListener2
1814  // Suppress any certificate errors
1815  notifyCertProblem: function MSR_certProblem(socketInfo, status, targetSite) {
1816  return true;
1817  },
1818 
1819  // nsISSLErrorListener
1820  // Suppress any ssl errors
1821  notifySSLError: function MSR_SSLError(socketInfo, error, targetSite) {
1822  return true;
1823  },
1824 
1825 
1826  // Suppress UI and abort loads for files secured by authentication.
1827 
1828  // Auth requests appear to succeed when we cancel them (since the server
1829  // redirects us to a "you're not authorized" page), so we have to set a flag
1830  // to let the load handler know to treat the load as a failure.
1831  get _authFailed() { return this.__authFailed; },
1832  set _authFailed(newValue) { return this.__authFailed = newValue },
1833 
1834  // nsIAuthPromptProvider
1835 
1836  getAuthPrompt: function(aPromptReason, aIID) {
1837  this._authFailed = true;
1838  throw Cr.NS_ERROR_NOT_AVAILABLE;
1839  },
1840 
1841  // HTTP always requests nsIAuthPromptProvider first, so it never needs
1842  // nsIAuthPrompt, but not all channels use nsIAuthPromptProvider, so we
1843  // implement nsIAuthPrompt too.
1844 
1845  // nsIAuthPrompt
1846 
1847  get authPrompt() {
1848  var resource = this;
1849  return {
1850  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
1851  prompt: function(dialogTitle, text, passwordRealm, savePassword, defaultText, result) {
1852  resource._authFailed = true;
1853  return false;
1854  },
1855  promptUsernameAndPassword: function(dialogTitle, text, passwordRealm, savePassword, user, pwd) {
1856  resource._authFailed = true;
1857  return false;
1858  },
1859  promptPassword: function(dialogTitle, text, passwordRealm, savePassword, pwd) {
1860  resource._authFailed = true;
1861  return false;
1862  }
1863  };
1864  },
1865 
1866  // nsIPrompt
1867 
1868  get prompt() {
1869  var resource = this;
1870  return {
1871  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
1872  alert: function(dialogTitle, text) {
1873  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
1874  },
1875  alertCheck: function(dialogTitle, text, checkMessage, checkValue) {
1876  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
1877  },
1878  confirm: function(dialogTitle, text) {
1879  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
1880  },
1881  confirmCheck: function(dialogTitle, text, checkMessage, checkValue) {
1882  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
1883  },
1884  confirmEx: function(dialogTitle, text, buttonFlags, button0Title, button1Title, button2Title, checkMsg, checkValue) {
1885  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
1886  },
1887  prompt: function(dialogTitle, text, value, checkMsg, checkValue) {
1888  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
1889  },
1890  promptPassword: function(dialogTitle, text, password, checkMsg, checkValue) {
1891  resource._authFailed = true;
1892  return false;
1893  },
1894  promptUsernameAndPassword: function(dialogTitle, text, username, password, checkMsg, checkValue) {
1895  resource._authFailed = true;
1896  return false;
1897  },
1898  select: function(dialogTitle, text, count, selectList, outSelection) {
1899  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
1900  }
1901  };
1902  },
1903 
1904  // XXX We implement nsIProgressEventSink because otherwise bug 253127
1905  // would cause too many extraneous errors to get reported to the console.
1906  // Fortunately this doesn't screw up XMLHttpRequest, because it ensures
1907  // that its implementation of nsIProgressEventSink will always get called
1908  // in addition to whatever notification callbacks we set on the channel.
1909 
1910  // nsIProgressEventSink
1911 
1912  onProgress: function(aRequest, aContext, aProgress, aProgressMax) {},
1913  onStatus: function(aRequest, aContext, aStatus, aStatusArg) {},
1914 
1922  initFromDocument: function MSR_initFromDocument(document) {
1923  this._content = document;
1924  this._contentType = document.contentType;
1925 
1926  // Normally we set this property based on whether or not
1927  // XMLHttpRequest parsed the content into an XML document object,
1928  // but since we already have the content, we have to analyze
1929  // its content type ourselves to see if it is XML.
1930  this._isXML = (this.contentType == "text/xml" ||
1931  this.contentType == "application/xml" ||
1932  /^.+\/.+\+xml$/.test(this.contentType));
1933  },
1934 
1940  destroy: function MSR_destroy() {
1941  this._uri = null;
1942  this._content = null;
1943  this._loadCallback = null;
1944  this._errorCallback = null;
1945  this._loadTimer = null;
1946  this._authFailed = false;
1947  if (this._iframe) {
1948  if (this._iframe && this._iframe.parentNode)
1949  this._iframe.parentNode.removeChild(this._iframe);
1950  this._iframe = null;
1951  }
1952  },
1953 
1963  load: function MSR_load(loadCallback, errorCallback) {
1964  LOG(this.uri.spec + " loading");
1965 
1966  this._loadCallback = loadCallback;
1967  this._errorCallback = errorCallback;
1968 
1969  var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
1970 
1971  var loadHandler = {
1972  _self: this,
1973  handleEvent: function MSR_loadHandler_handleEvent(event) {
1974  if (this._self._loadTimer)
1975  this._self._loadTimer.cancel();
1976 
1977  this._self.status = event.target.status;
1978 
1979  if (this._self._authFailed || this._self.status >= 400) {
1980  // Technically the request succeeded, but we treat it as a failure,
1981  // since we won't be able to extract anything relevant from the result.
1982 
1983  // XXX For now HTTP is the only protocol we handle that might fail
1984  // auth. This message will need to change once we support FTP, which
1985  // returns 0 for all statuses.
1986  LOG(this._self.uri.spec + " load failed; HTTP status: " + this._self.status);
1987  try { this._self._handleError(event) }
1988  finally { this._self = null }
1989  }
1990  else if (event.target.channel.contentType == "multipart/x-mixed-replace") {
1991  // Technically the request succeeded, but we treat it as a failure,
1992  // since we aren't able to handle multipart content.
1993  LOG(this._self.uri.spec + " load failed; contains multipart content");
1994  try { this._self._handleError(event) }
1995  finally { this._self = null }
1996  }
1997  else {
1998  LOG(this._self.uri.spec + " load succeeded; invoking callback");
1999  try { this._self._handleLoad(event) }
2000  finally { this._self = null }
2001  }
2002  }
2003  };
2004 
2005  var errorHandler = {
2006  _self: this,
2007  handleEvent: function MSR_errorHandler_handleEvent(event) {
2008  if (this._self._loadTimer)
2009  this._self._loadTimer.cancel();
2010 
2011  LOG(this._self.uri.spec + " load failed");
2012  try { this._self._handleError(event) }
2013  finally { this._self = null }
2014  }
2015  };
2016 
2017  // cancel loads that take too long
2018  // timeout specified in seconds at browser.microsummary.requestTimeout,
2019  // or 300 seconds (five minutes)
2020  var timeout = getPref("browser.microsummary.requestTimeout", 300) * 1000;
2021  var timerObserver = {
2022  _self: this,
2023  observe: function MSR_timerObserver_observe() {
2024  LOG("timeout loading microsummary resource " + this._self.uri.spec + ", aborting request");
2025  request.abort();
2026  try { this._self.destroy() }
2027  finally { this._self = null }
2028  }
2029  };
2030  this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
2031  this._loadTimer.init(timerObserver, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
2032 
2033  request = request.QueryInterface(Ci.nsIDOMEventTarget);
2034  request.addEventListener("load", loadHandler, false);
2035  request.addEventListener("error", errorHandler, false);
2036 
2037  request = request.QueryInterface(Ci.nsIXMLHttpRequest);
2038  request.open(this.method, this.uri.spec, true);
2039  request.setRequestHeader("X-Moz", "microsummary");
2040  if (this.lastMod)
2041  request.setRequestHeader("If-Modified-Since", this.lastMod);
2042 
2043  // Register ourselves as a listener for notification callbacks so we
2044  // can handle authorization requests and SSL issues like cert mismatches.
2045  // XMLHttpRequest will handle the notifications we don't handle.
2046  request.channel.notificationCallbacks = this;
2047 
2048  // If this is a bookmarked resource, and the bookmarks service recorded
2049  // its charset in the bookmarks datastore the last time the user visited it,
2050  // then specify the charset in the channel so XMLHttpRequest loads
2051  // the resource correctly.
2052  try {
2053  var resolver = Cc["@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1"].
2054  getService(Ci.nsICharsetResolver);
2055  if (resolver) {
2056  var charset = resolver.requestCharset(null, request.channel, {}, {});
2057  if (charset != "")
2058  request.channel.contentCharset = charset;
2059  }
2060  }
2061  catch(ex) {}
2062 
2063  request.send(null);
2064  },
2065 
2066  _handleLoad: function MSR__handleLoad(event) {
2067  var request = event.target;
2068 
2069  if (request.responseXML) {
2070  this._isXML = true;
2071  // XXX Figure out the parsererror format and log a specific error.
2072  if (request.responseXML.documentElement.nodeName == "parsererror") {
2073  this._handleError(event);
2074  return;
2075  }
2076  this._content = request.responseXML;
2077  this._contentType = request.channel.contentType;
2078  this._loadCallback(this);
2079  }
2080 
2081  else if (request.channel.contentType == "text/html") {
2082  this._parse(request.responseText);
2083  }
2084 
2085  else {
2086  // This catches text/plain as well as any other content types
2087  // not accounted for by the content type-specific code above.
2088  this._content = request.responseText;
2089  this._contentType = request.channel.contentType;
2090  this._loadCallback(this);
2091  }
2092  },
2093 
2094  _handleError: function MSR__handleError(event) {
2095  // Call the error callback, then destroy ourselves to prevent memory leaks.
2096  try { if (this._errorCallback) this._errorCallback(this) }
2097  finally { this.destroy() }
2098  },
2099 
2109  _parse: function MSR__parse(htmlText) {
2110  // Find a window to stick our hidden iframe into.
2111  var windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].
2112  getService(Ci.nsIWindowMediator);
2113  var window = windowMediator.getMostRecentWindow("navigator:browser");
2114  // XXX We can use other windows, too, so perhaps we should try to get
2115  // some other window if there's no browser window open. Perhaps we should
2116  // even prefer other windows, since there's less chance of any browser
2117  // window machinery like throbbers treating our load like one initiated
2118  // by the user.
2119  if (!window) {
2120  this._handleError(event);
2121  return;
2122  }
2123  var document = window.document;
2124  var rootElement = document.documentElement;
2125 
2126  // Create an iframe, make it hidden, and secure it against untrusted content.
2127  this._iframe = document.createElement('iframe');
2128  this._iframe.setAttribute("collapsed", true);
2129  this._iframe.setAttribute("type", "content");
2130 
2131  // Insert the iframe into the window, creating the doc shell.
2132  rootElement.appendChild(this._iframe);
2133 
2134  // When we insert the iframe into the window, it immediately starts loading
2135  // about:blank, which we don't need and could even hurt us (for example
2136  // by triggering bugs like bug 344305), so cancel that load.
2137  var webNav = this._iframe.docShell.QueryInterface(Ci.nsIWebNavigation);
2138  webNav.stop(Ci.nsIWebNavigation.STOP_NETWORK);
2139 
2140  // Turn off JavaScript and auth dialogs for security and other things
2141  // to reduce network load.
2142  // XXX We should also turn off CSS.
2143  this._iframe.docShell.allowJavascript = false;
2144  this._iframe.docShell.allowAuth = false;
2145  this._iframe.docShell.allowPlugins = false;
2146  this._iframe.docShell.allowMetaRedirects = false;
2147  this._iframe.docShell.allowSubframes = false;
2148  this._iframe.docShell.allowImages = false;
2149  this._iframe.docShell.allowDNSPrefetch = false;
2150 
2151  var parseHandler = {
2152  _self: this,
2153  handleEvent: function MSR_parseHandler_handleEvent(event) {
2154  event.target.removeEventListener("DOMContentLoaded", this, false);
2155  try { this._self._handleParse(event) }
2156  finally { this._self = null }
2157  }
2158  };
2159 
2160  // Convert the HTML text into an input stream.
2161  var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
2162  createInstance(Ci.nsIScriptableUnicodeConverter);
2163  converter.charset = "UTF-8";
2164  var stream = converter.convertToInputStream(htmlText);
2165 
2166  // Set up a channel to load the input stream.
2167  var channel = Cc["@mozilla.org/network/input-stream-channel;1"].
2168  createInstance(Ci.nsIInputStreamChannel);
2169  channel.setURI(this._uri);
2170  channel.contentStream = stream;
2171 
2172  // Load in the background so we don't trigger web progress listeners.
2173  var request = channel.QueryInterface(Ci.nsIRequest);
2174  request.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND;
2175 
2176  // Specify the content type since we're not loading content from a server,
2177  // so it won't get specified for us, and if we don't specify it ourselves,
2178  // then Firefox will prompt the user to download content of "unknown type".
2179  var baseChannel = channel.QueryInterface(Ci.nsIChannel);
2180  baseChannel.contentType = "text/html";
2181 
2182  // Load as UTF-8, which it'll always be, because XMLHttpRequest converts
2183  // the text (i.e. XMLHTTPRequest.responseText) from its original charset
2184  // to UTF-16, then the string input stream component converts it to UTF-8.
2185  baseChannel.contentCharset = "UTF-8";
2186 
2187  // Register the parse handler as a load event listener and start the load.
2188  // Listen for "DOMContentLoaded" instead of "load" because background loads
2189  // don't fire "load" events.
2190  this._iframe.addEventListener("DOMContentLoaded", parseHandler, true);
2191  var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
2192  uriLoader.openURI(channel, true, this._iframe.docShell);
2193  },
2194 
2202  _handleParse: function MSR__handleParse(event) {
2203  // XXX Make sure the parse was successful?
2204 
2205  this._content = this._iframe.contentDocument;
2206  this._contentType = this._iframe.contentDocument.contentType;
2207  this._loadCallback(this);
2208  }
2209 
2210 };
2211 
2224  var mediator = Cc["@mozilla.org/appshell/window-mediator;1"].
2225  getService(Ci.nsIWindowMediator);
2226 
2227  // Apparently the Z order enumerator is broken on Linux per bug 156333.
2228  //var windows = mediator.getZOrderDOMWindowEnumerator("navigator:browser", true);
2229  var windows = mediator.getEnumerator("navigator:browser");
2230 
2231  while (windows.hasMoreElements()) {
2232  var win = windows.getNext();
2233  var tabBrowser = win.document.getElementById("content");
2234  for ( var i = 0; i < tabBrowser.browsers.length; i++ ) {
2235  var browser = tabBrowser.browsers[i];
2236  if (uri.equals(browser.currentURI)) {
2237  var resource = new MicrosummaryResource(uri);
2238  resource.initFromDocument(browser.contentDocument);
2239  return resource;
2240  }
2241  }
2242  }
2243 
2244  return null;
2245 }
2246 
2254 function getPref(prefName, defaultValue) {
2255  try {
2256  var prefBranch = Cc["@mozilla.org/preferences-service;1"].
2257  getService(Ci.nsIPrefBranch);
2258  var type = prefBranch.getPrefType(prefName);
2259  switch (type) {
2260  case prefBranch.PREF_BOOL:
2261  return prefBranch.getBoolPref(prefName);
2262  case prefBranch.PREF_INT:
2263  return prefBranch.getIntPref(prefName);
2264  }
2265  }
2266  catch (ex) { /* return the default value */ }
2267 
2268  return defaultValue;
2269 }
2270 
2271 
2272 // From http://lxr.mozilla.org/mozilla/source/browser/components/search/nsSearchService.js
2273 
2281 function sanitizeName(aName) {
2282  const chars = "-abcdefghijklmnopqrstuvwxyz0123456789";
2283  const maxLength = 60;
2284 
2285  var name = aName.toLowerCase();
2286  name = name.replace(/ /g, "-");
2287  //name = name.split("").filter(function (el) {
2288  // return chars.indexOf(el) != -1;
2289  // }).join("");
2290  var filteredName = "";
2291  for ( var i = 0 ; i < name.length ; i++ )
2292  if (chars.indexOf(name[i]) != -1)
2293  filteredName += name[i];
2294  name = filteredName;
2295 
2296  if (!name) {
2297  // Our input had no valid characters - use a random name
2298  for (var i = 0; i < 8; ++i)
2299  name += chars.charAt(Math.round(Math.random() * (chars.length - 1)));
2300  }
2301 
2302  if (name.length > maxLength)
2303  name = name.substring(0, maxLength);
2304 
2305  return name;
2306 }
2307 
2308 function NSGetModule(compMgr, fileSpec) {
2309  return XPCOMUtils.generateModule([MicrosummaryService]);
2310 }
function MicrosummarySet()
const MODE_TRUNCATE
prefs removeObserver(PREF_SELECTED_ACTION, this)
function sanitizeName(aName)
const XSLT_NS
var windows
const Cu
Wraps a js array in an nsISimpleEnumerator.
_selectMonthYear select
const ANNO_STATIC_TITLE
function doc() browser.contentDocument
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
const Cr
var event
var converter
function getPref(prefName, defaultValue)
sidebarFactory createInstance
Definition: nsSidebar.js:351
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
const Ci
function MicrosummaryResource(uri)
const ANNO_MICSUM_EXPIRATION
const GENERATOR_INTERVAL
getService(Ci.sbIFaceplateManager)
let window
sbDeviceFirmwareAutoCheckForUpdate prototype _timer
const NS_ERROR_DOM_BAD_URI
const MICSUM_NS
TimerLoop prototype notify
function LOG(aText)
inst settings prompt
var t
var count
Definition: test_bug7406.js:32
var loaded
const MODE_WRONLY
const MODE_CREATE
historySvc addObserver(this, false)
Element Properties load
grep callback
function Microsummary(aPageURI, aGenerator)
return null
Definition: FeedWriter.js:1143
function MicrosummaryService()
let node
return!aWindow arguments!aWindow arguments[0]
const CHECK_INTERVAL
SimpleArrayEnumerator prototype hasMoreElements
const Cc
_updateCookies aName
function MicrosummaryGenerator(aURI, aLocalURI, aName)
var uri
Definition: FeedWriter.js:1135
countRef value
Definition: FeedWriter.js:1423
observe topic
Definition: FeedWriter.js:1326
sbDeviceFirmwareAutoCheckForUpdate prototype classID
const ANNO_MICSUM_GEN_URI
function now()
sbWindowsAutoPlayServiceCfg _xpcom_categories
const NS_ERROR_MODULE_DOM
sbDeviceFirmwareAutoCheckForUpdate prototype interfaces
var browser
Definition: openLocation.js:42
__defineGetter__("Application", function(){delete this.Application;return this.Application=Cc["@mozilla.org/fuel/application;1"].getService(Ci.fuelIApplication);})
function LiveTitleNotificationSubject(bookmarkID, microsummary)
observe data
Definition: FeedWriter.js:1329
const ANNO_CONTENT_TYPE
var profileDir
_getSelectedPageStyle s i
const PERMS_FILE
let observer
function getLoadedMicrosummaryResource(uri)
var chars
var file
sbDeviceFirmwareAutoCheckForUpdate prototype observe
const MAX_SUMMARY_LENGTH
converter charset
function ArrayEnumerator(aItems)
function NSGetModule(compMgr, fileSpec)