sbSearchSuggester.js
Go to the documentation of this file.
1 
35 const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
36 const XPCOM_SHUTDOWN_TOPIC = "xpcom-shutdown";
37 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
38 
39 const SONGBIRD_DATAREMOTE_CONTRACTID = "@songbirdnest.com/Songbird/DataRemote;1";
40 const sbIDataRemote = Components.interfaces.sbIDataRemote;
41 
42 
44  "@mozilla.org/autocomplete/search;1?name=songbird-autocomplete";
45 const SEARCH_SUGGEST_CLASSNAME = "Songbird Search Suggestions";
47  Components.ID("{0be64502-ee00-11db-8314-0800200c9a66}");
48 
49 const SEARCH_BUNDLE = "chrome://songbird/locale/songbird.properties";
50 
51 const Cc = Components.classes;
52 const Ci = Components.interfaces;
53 const Cr = Components.results;
54 
55 
62 function AutoCompleteResult(searchString,
63  defaultIndex,
64  errorDescription,
65  results,
66  comments) {
67  this._searchString = searchString;
68  this._defaultIndex = defaultIndex;
69  this._errorDescription = errorDescription;
70  this._results = results;
71  this._comments = comments;
72 }
73 AutoCompleteResult.prototype = {
78  _searchString: "",
79 
84  _defaultIndex: 0,
85 
90  _errorDescription: "",
91 
96  _results: [],
97 
103  _comments: [],
104 
105 
109  get searchString() {
110  return this._searchString;
111  },
112 
120  get searchResult() {
121  if (this._results.length > 0) {
122  return Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
123  } else {
124  Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
125  }
126  },
127 
131  get defaultIndex() {
132  return this._defaultIndex;
133  },
134 
138  get errorDescription() {
139  return this._errorDescription;
140  },
141 
145  get matchCount() {
146  return this._results.length;
147  },
148 
154  getValueAt: function(index) {
155  return this._results[index];
156  },
157 
163  getCommentAt: function(index) {
164  return this._comments[index];
165  },
166 
172  getStyleAt: function(index) {
173  if (!this._comments[index])
174  return null; // not a category label, so no special styling
175 
176  if (index == 0)
177  return "suggestfirst"; // category label on first line of results
178 
179  return "suggesthint"; // category label on any other line of results
180  },
181 
187  getImageAt: function(index) {
188  return "";
189  },
190 
195  removeValueAt: function(index, removeFromDatabase) {
196  this._results.splice(index, 1);
197  this._comments.splice(index, 1);
198  },
199 
206  QueryInterface: function(iid) {
207  if (!iid.equals(Ci.nsIAutoCompleteResult) &&
208  !iid.equals(Ci.nsISupports))
209  throw Cr.NS_ERROR_NO_INTERFACE;
210  return this;
211  }
212 };
213 
214 
215 
216 
217 
218 
228 function SearchSuggester() {
229  this._addObservers();
230  this._loadSuggestPref();
231  this._loadDataRemotes();
232 }
233 SearchSuggester.prototype = {
234 
238  get _strings() {
239  if (!this.__strings) {
240  var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
241  getService(Ci.nsIStringBundleService);
242 
243  this.__strings = sbs.createBundle(SEARCH_BUNDLE);
244  }
245  return this.__strings;
246  },
247  __strings: null,
248 
252  _loadSuggestPref: function SAC_loadSuggestPref() {
253  var prefService = Cc["@mozilla.org/preferences-service;1"].
254  getService(Ci.nsIPrefBranch);
255  this._suggestEnabled = prefService.getBoolPref(BROWSER_SUGGEST_PREF);
256  },
257  _suggestEnabled: null,
258 
259  // Metadata for the current playing track in Songbird
260  _metadataTitle: null,
261  _metadataArtist: null,
262  _metadataAlbum: null,
263 
267  _loadDataRemotes: function SAC_loadDataRemotes() {
268 
269  // TODO: Is this an issue? Should I be waiting for profile load before doing this?
270  var createDataRemote = new Components.Constructor(
272  this._metadataTitle = createDataRemote("metadata.title", null);
273  this._metadataArtist = createDataRemote("metadata.artist", null);
274  this._metadataAlbum = createDataRemote("metadata.album", null);
275  },
276 
280  _releaseDataRemotes: function() {
281  this._metadataTitle.unbind();
282  this._metadataArtist.unbind();
283  this._metadataAlbum.unbind();
284  },
285 
286 
292  _listener: null,
293 
294 
302  onSearchResult: function(searchString, results, comments) {
303  if (this._listener) {
304  var result = new AutoCompleteResult(
305  searchString,
306  0,
307  "",
308  results,
309  comments);
310 
311  this._listener.onSearchResult(this, result);
312 
313  // Null out listener to make sure we don't notify it twice, in case our
314  // timer callback still hasn't run.
315  this._listener = null;
316  }
317  },
318 
319 
334  startSearch: function(searchString, searchParam, previousResult, listener) {
335  this.stopSearch();
336 
337  var searchService = Cc["@mozilla.org/browser/search-service;1"].
338  getService(Ci.nsIBrowserSearchService);
339 
340  // If there's an existing request, stop it
341  this.stopSearch();
342 
343  this._listener = listener;
344 
345  var results = [];
346 
347  var engine = searchService.currentEngine;
348 
349  // Normally we would do something asynchronous, but since
350  // for now all we're returning is dataremote values, we
351  // might as well just do it immediately.
352 
353  // If there is no search query then get some default suggestions
354  if (searchString == "") {
355  results = this._getPlayerContextSuggestions();
356  }
357 
358  // TODO Add a localized comment
359  var comments = [];
360  for (var i = 0; i < results.length; i++) {
361  comments.push("");
362  }
363 
364  this.onSearchResult(searchString, results, comments);
365  },
366 
371  stopSearch: function() {
372  // Nothing to do since we return our searches immediately.
373  },
374 
375 
379  _getPlayerContextSuggestions: function() {
380  var results = [];
381 
382  // TODO: Do not return metadata unless playing or paused!
383  // Currently returns metadata from the previous session.
384 
385  if (this._metadataTitle.stringValue != "") {
386  results.push(this._metadataTitle.stringValue);
387  }
388  if (this._metadataAlbum.stringValue != "") {
389  results.push(this._metadataAlbum.stringValue);
390  }
391  if (this._metadataArtist.stringValue != "") {
392  results.push(this._metadataArtist.stringValue);
393  }
394  return results;
395  },
396 
397 
401  observe: function SAC_observe(aSubject, aTopic, aData) {
402  switch (aTopic) {
404  this._loadSuggestPref();
405  break;
407  this.stopSearch();
408  this._removeObservers();
409  this._releaseDataRemotes();
410  break;
411  }
412  },
413 
414  _addObservers: function SAC_addObservers() {
415  var prefService2 = Cc["@mozilla.org/preferences-service;1"].
416  getService(Ci.nsIPrefBranch2);
417  prefService2.addObserver(BROWSER_SUGGEST_PREF, this, false);
418 
419  var os = Cc["@mozilla.org/observer-service;1"].
420  getService(Ci.nsIObserverService);
421  os.addObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
422  },
423 
424  _removeObservers: function SAC_removeObservers() {
425  var prefService2 = Cc["@mozilla.org/preferences-service;1"].
426  getService(Ci.nsIPrefBranch2);
427  prefService2.removeObserver(BROWSER_SUGGEST_PREF, this);
428 
429  var os = Cc["@mozilla.org/observer-service;1"].
430  getService(Ci.nsIObserverService);
431  os.removeObserver(this, XPCOM_SHUTDOWN_TOPIC);
432  },
433 
440  QueryInterface: function(iid) {
441  if (!iid.equals(Ci.nsIAutoCompleteSearch) &&
442  !iid.equals(Ci.nsIObserver) &&
443  !iid.equals(Ci.nsISupports))
444  throw Cr.NS_ERROR_NO_INTERFACE;
445  return this;
446  }
447 };
448 
449 
450 
451 var gModule = {
460  registerSelf: function(componentManager, location, loaderString, type) {
461  if (this._firstTime) {
462  this._firstTime = false;
463  throw Cr.NS_ERROR_FACTORY_REGISTER_AGAIN;
464  }
465  componentManager =
466  componentManager.QueryInterface(Ci.nsIComponentRegistrar);
467 
468  for (var key in this.objects) {
469  var obj = this.objects[key];
470  componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
471  location, loaderString, type);
472  }
473  },
474 
483  getClassObject: function(componentManager, cid, iid) {
484  if (!iid.equals(Ci.nsIFactory))
485  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
486 
487  for (var key in this.objects) {
488  if (cid.equals(this.objects[key].CID))
489  return this.objects[key].factory;
490  }
491 
492  throw Cr.NS_ERROR_NO_INTERFACE;
493  },
494 
500  _makeFactory: function(constructor) {
501  function createInstance(outer, iid) {
502  if (outer != null)
503  throw Cr.NS_ERROR_NO_AGGREGATION;
504  return (new constructor()).QueryInterface(iid);
505  }
506  return { createInstance: createInstance };
507  },
508 
513  canUnload: function(componentManager) {
514  return true;
515  }
516 };
517 
524 function NSGetModule(componentManager, location) {
525  // Metadata about the objects this module can construct
526  gModule.objects = {
527  search: {
531  factory: gModule._makeFactory(SearchSuggester)
532  },
533  };
534  return gModule;
535 }
const SEARCH_SUGGEST_CONTRACTID
var gModule
sbOSDControlService prototype className
SafebrowsingApplicationMod prototype registerSelf
for(let i=0;i< aHistory.count;i++)
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
const BROWSER_SUGGEST_PREF
sidebarFactory createInstance
Definition: nsSidebar.js:351
sbOSDControlService prototype QueryInterface
const Cc
const SONGBIRD_DATAREMOTE_CONTRACTID
An interface for accessing, and binding to, stored data.
getService(Ci.sbIFaceplateManager)
const CID
SafebrowsingApplicationMod prototype getClassObject
function search(aFolderId, aSearchStr, aExpectedScopeButtonId)
const SEARCH_SUGGEST_CLASSID
DataRemote prototype constructor
var createDataRemote
function AutoCompleteResult(searchString, defaultIndex, errorDescription, results, comments)
return null
Definition: FeedWriter.js:1143
var os
const Cr
const SEARCH_SUGGEST_CLASSNAME
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
function SearchSuggester()
_getSelectedPageStyle s i
const XPCOM_SHUTDOWN_TOPIC
_updateTextAndScrollDataForFrame aData
sbDeviceFirmwareAutoCheckForUpdate prototype observe
const SEARCH_BUNDLE
function NSGetModule(componentManager, location)
const Ci