searchHandler.js
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2010 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
31 Components.utils.import("resource://app/jsmodules/DebugUtils.jsm");
32 const LOG = DebugUtils.generateLogFunction("searchHandler", 2);
33 
34 
50 const gSearchHandler = {
51 
52  // name identifying the standard library search, gets adjusted in init
53  SEARCHENGINE_NAME_SONGBIRD: "Nightingale",
54 
58  init: function SearchHandler_init() {
59 
60  // If there is no gBrowser, then there is nothing
61  // for us to do.
62  if (typeof gBrowser == 'undefined') {
63  return;
64  }
65 
66  // Listen for browser links in order to detect embedded search engines
67  gBrowser.addEventListener("DOMLinkAdded",
68  function (event) { gSearchHandler.onLinkAdded(event); },
69  false);
70 
71  // Listen for tab change events
72  gBrowser.addEventListener('TabContentChange',
73  function (event) { gSearchHandler.onTabChanged(event); },
74  false);
75  gBrowser.addEventListener('TabPropertyChange',
76  function (event) { gSearchHandler.onTabChanged(event); },
77  false);
78 
79  // Listen for search events
80  document.addEventListener("search",
81  function (event) { gSearchHandler.onSearchEvent(event); },
82  true);
83 
84  // get the brand strings to get the name of the library search
85  var stringBundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
86  var brand = stringBundleService.createBundle("chrome://branding/locale/brand.properties");
87  this.SEARCHENGINE_NAME_SONGBIRD = brand.GetStringFromName("brandShortName");
88 
89  this.internalSearchService = Cc["@getnightingale.com/Nightingale/internal-search-service;1"].getService(Ci.ngIInternalSearchEnginesService);
90 
91  // register the library search and activate live search
92  this.internalSearchService.registerInternalSearchEngine(this.SEARCHENGINE_NAME_SONGBIRD,"songbird-internal-search",true);
93  },
94 
95 
99  uninit: function SearchHandler_uninit() {
100  // Hmm, nothing to do?
101  this.internalSearchService.unregisterInternalSearchEngine(this.SEARCHENGINE_NAME_SONGBIRD);
102  },
103 
104 
106  // Private Variables ////////////////////////////////////////////////////////
108 
109 
110  // Used to save the state of the web search box
111  // when switching to the Songbird search engine
112  _previousSearchEngine: null,
113  _previousSearch: "",
114 
115 
116 
118  // Event Listeners /////////////////////////////////////////////////////////
120 
125  onLinkAdded: function SearchHandler_onLinkAdded(event) {
126  // XXX this event listener can/should probably be combined with the onLinkAdded
127  // listener in tabBrowser.xml. See comments in FeedHandler.onLinkAdded().
128  const target = event.target;
129  var etype = target.type;
130  const searchRelRegex = /(^|\s)search($|\s)/i;
131  const searchHrefRegex = /^(https?|ftp):\/\//i;
132 
133  if (!etype)
134  return;
135 
136  // Mozilla Bug 349431: If the engine has no suggested title, ignore it rather
137  // than trying to find an alternative.
138  if (!target.title)
139  return;
140 
141  if (etype == "application/opensearchdescription+xml" &&
142  searchRelRegex.test(target.rel) && searchHrefRegex.test(target.href))
143  {
144  const targetDoc = target.ownerDocument;
145  // Set the attribute of the (first) search-engine button.
146  var searchButton = document.getAnonymousElementByAttribute(this.getSearchBar(),
147  "anonid", "searchbar-engine-button");
148  if (searchButton) {
149  var browser = gBrowser.getBrowserForDocument(targetDoc);
150  // Append the URI and an appropriate title to the browser data.
151  var iconURL = null;
152  if (gBrowser.shouldLoadFavIcon(browser.currentURI)) {
153  var faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
154  .getService(Ci.nsIFaviconService);
155  try {
156  iconURL = faviconService.getFaviconForPage(
157  browser.contentDocument.documentURIObject).spec;
158 
159  // Favicon URI's are prepended with "moz-anno:favicon:".
160  iconURL = iconURL.replace(/^moz-anno:favicon:/, '');
161  }
162  catch(e) {
163  if (Components.lastResult != Cr.NS_ERROR_NOT_AVAILABLE)
164  Cu.reportError(e);
165 
166  //Default to favicon.ico if no favicon is available.
167  iconURL = browser.currentURI.prePath + "/favicon.ico";
168  }
169 
170  }
171 
172  var hidden = false;
173  // If this engine (identified by title) is already in the list, add it
174  // to the list of hidden engines rather than to the main list.
175  // XXX This will need to be changed when engines are identified by URL;
176  // see bug 335102.
177  var searchService = Cc["@mozilla.org/browser/search-service;1"]
178  .getService(Ci.nsIBrowserSearchService);
179  if (searchService.getEngineByName(target.title))
180  hidden = true;
181 
182  var engines = [];
183  if (hidden) {
184  if (browser.hiddenEngines)
185  engines = browser.hiddenEngines;
186  }
187  else {
188  if (browser.engines)
189  engines = browser.engines;
190  }
191 
192  engines.push({ uri: target.href,
193  title: target.title,
194  icon: iconURL });
195 
196  if (hidden) {
197  browser.hiddenEngines = engines;
198  }
199  else {
200  browser.engines = engines;
201  if (browser == gBrowser || browser == gBrowser.mCurrentBrowser)
202  this.updateSearchButton();
203  }
204  }
205  }
206  },
207 
208 
209 
215  onSearchEvent: function SearchHandler_onSearchEvent( evt )
216  {
217  var searchBar = this.getSearchBar();
218  if (searchBar == null) {
219  throw("gSearchHandler: Could not process search event. " +
220  "Target did not have a currentEngine property.");
221  }
222 
223  var currentEngine = searchBar.currentEngine;
224  // If this engine is an internal one, do the search internally.
225  var engineEntry = this.internalSearchService.getInternalSearchEngine(currentEngine.name);
226  if (engineEntry!==null)
227  {
228  // Empty search text means to disable the search filter. Still necessary
229  // to dispatch search.
230 
231  // Special case for our internal search. Other people can add their
232  // own listeners as well.
233  var contractID =
234  "@songbirdnest.com/Songbird/" + engineEntry.contractID + ";1";
235  if (contractID in Cc) {
236  var searchEngine = Cc[contractID].getService(Ci.sbISearchEngine);
237  searchEngine.doSearch(window, searchBar.value);
238 
239  return;
240  }
241  }
242 
243  // No need to dispatch web search if the search text is empty.
244  if (searchBar.value == "")
245  return;
246 
247  // null parameter below specifies HTML response for search
248  var submission = currentEngine.getSubmission(searchBar.value, null);
249 
250  // TODO: Some logic to determine where this opens?
251  var where = "current";
252 
253  openUILinkIn(submission.uri.spec, where, null, submission.postData);
254  },
255 
256 
257  onTabChanged: function SearchHandler_onTabChanged(event) {
258  // Update search button to reflect available engines.
259  // Note: In firefox this is called by browser.js asyncUpdateUI()
260  BrowserSearch.updateSearchButton();
261 
262  // Update mode depending on location
263  // (Library vs Website)
264  BrowserSearch.updateSearchMode();
265  },
266 
267 
268 
269 
271  // FireFox Search Engine Detection //////////////////////////////////////////
273 
274 
275 
281  updateSearchButton: function SearchHandler_updateSearchButton() {
282  var searchButton = document.getAnonymousElementByAttribute(this.getSearchBar(),
283  "anonid", "searchbar-engine-button");
284  if (!searchButton)
285  return;
286  var engines = gBrowser.mCurrentBrowser.engines;
287  if (!engines || engines.length == 0) {
288  if (searchButton.hasAttribute("addengines"))
289  searchButton.removeAttribute("addengines");
290  }
291  else {
292  searchButton.setAttribute("addengines", "true");
293  }
294  },
295 
301  webSearch: function SearchHandler_webSearch() {
302  var searchBar = this.getSearchBar();
303  if (searchBar) {
304  searchBar.select();
305  searchBar.focus();
306  } else {
307  var ss = Cc["@mozilla.org/browser/search-service;1"].
308  getService(Ci.nsIBrowserSearchService);
309  var searchForm = ss.defaultEngine.searchForm;
310  loadURI(searchForm, null, null, false);
311  }
312  },
313 
325  loadSearch: function SearchHandler_loadSearch(searchText, useNewTab) {
326  var ss = Cc["@mozilla.org/browser/search-service;1"].
327  getService(Ci.nsIBrowserSearchService);
328  var engine;
329 
330  // If the search bar is visible, use the current engine, otherwise, fall
331  // back to the default engine.
332  if (this.getSearchBar())
333  engine = ss.currentEngine;
334  else
335  engine = ss.defaultEngine;
336 
337  var submission = engine.getSubmission(searchText, null); // HTML response
338 
339  // getSubmission can return null if the engine doesn't have a URL
340  // with a text/html response type. This is unlikely (since
341  // SearchService._addEngineToStore() should fail for such an engine),
342  // but let's be on the safe side.
343  if (!submission)
344  return;
345 
346  if (useNewTab) {
347  window.gBrowser.loadOneTab(submission.uri.spec, null, null,
348  submission.postData, null, false);
349  } else
350  loadURI(submission.uri.spec, null, submission.postData, false);
351  },
352 
357  getSearchBar: function SearchHandler_getSearchBar() {
358  // Look for a searchbar element
359  var elements = document.getElementsByTagName("searchbar");
360  if (elements && elements.length > 0) {
361  return elements[0];
362  }
363  return null;
364  },
365 
369  getSongbirdSearchEngines: function SearchHandler_getSongbirdSearchEngines() {
370  var songbirdEngines = this.getSearchBar().searchService.getEngines({});
371  if (!songbirdEngines) {
372  LOG("\n\nCould not find any search engines for Songbird.\n");
373  }
374  return songbirdEngines;
375  },
376 
380  getSongbirdSearchEngine:
381  function SearchHandler_getSongbirdSearchEngine(aName) {
382  if (!aName||aName=="internal")
383  aName = this.SEARCHENGINE_NAME_SONGBIRD;
384  var songbirdEngine = this.getSearchBar()
385  .searchService
386  .getEngineByName(aName);
387  if (!songbirdEngine) {
388  LOG("\n\nThe Songbird search engine with name \"" + aName +
389  "\" could not be found.\n");
390  }
391  return songbirdEngine;
392  },
393 
394  loadAddEngines: function SearchHandler_loadAddEngines() {
395  // Hardcode Songbird to load the page in a tab
396  var where = "tab";
397  var regionBundle = document.getElementById("bundle_browser_region");
398 
399  var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
400  var searchEnginesURL = formatter.formatURLPref("browser.search.searchEnginesURL");
401 
402  openUILinkIn(searchEnginesURL, where);
403  },
404 
405 
407  // Songbird Search Mode Support /////////////////////////////////////////////
409 
410 
415  updateSearchMode: function SearchHandler_updateSearchMode() {
416 
417  // Do nothing until the browser is finished loading
418  // This avoids annoying flickering.
419  if (gBrowser.loading) {
420  return;
421  }
422 
423  // If a media page is open in the current tab,
424  // then we will need to restore the search filter state
425  if (this._isMediaTabOrMediaPageShowing())
426  {
427  this._switchToInternalSearch();
428  }
429  // Must be showing a regular page.
430  // May need to deactivate the songbird search.
431  else
432  {
433  this._switchToWebSearch();
434  }
435  },
436 
437 
443  _switchToInternalSearch: function SearchHandler__switchToInternalSearch() {
444  var searchBar = this.getSearchBar();
445  var currentEngine = searchBar.currentEngine;
446 
447  // Save the previous web search engine, used when switch to web search
448  if (this.internalSearchService.getInternalSearchEngine(currentEngine.name)===null)
449  {
450  this._previousSearchEngine = currentEngine;
451  this._previousSearch = searchBar.value;
452  }
453 
454  // Get the corresponding search engine for the service pane node.
455  var name = null;
456  if (gServicePane) {
457  // Get the current active node.
458  var node = gServicePane.activeNode;
459  if (node) {
460  name = node.searchtype;
461  }
462  }
463  var engine = this.getSongbirdSearchEngine(name);
464  if(engine.name!="")
465  name = engine.name;
466 
467  var liveSearchEnabled = false;
468  // Live search is disabled for search engines who don't support livesearch
469  if (this.internalSearchService.getInternalSearchEngine(name).liveSearch) {
470  liveSearchEnabled =
471  Application.prefs.getValue("songbird.livesearch.enabled", true);
472  }
473 
474  // Set live search mode for the songbird search engine
475  searchBar.setLiveSearchMode(engine, liveSearchEnabled);
476 
477  // Make sure the songbird search is selected
478  if (currentEngine != engine) {
479  // Switch to the songbird internal search engine...
480  // but first remove any query text so as not to cause
481  // the engine to immediately submit the query
482  searchBar.value = "";
483  searchBar.currentEngine = engine;
484  }
485 
486  // Set the query to match the state of the media page
487  this._syncSearchBarToMediaPage(name);
488 
489  searchBar.updateDisplay();
490  },
491 
492 
496  _switchToWebSearch: function SearchHandler__switchToWebSearch() {
497  var searchBar = this.getSearchBar()
498  var currentEngine = searchBar.currentEngine;
499 
500  // If the songbird engine is in live search mode then
501  // turn that feature off and restore the default
502  // display text.
503  if (searchBar.isInLiveSearchMode(currentEngine)) {
504  searchBar.setLiveSearchMode(currentEngine, false);
505  searchBar.setEngineDisplayText(currentEngine, null);
506  }
507 
508  // If this engine has not been registered as internal,
509  // we need to restore the engine active prior to us.
510  if (this.internalSearchService.getInternalSearchEngine(currentEngine.name)!==null)
511  {
512  // If there is a previous search engine, switch to it...
513  // but first remove any query text so as not to cause
514  // the engine to immediately submit the query
515  if (this._previousSearchEngine)
516  {
517  searchBar.value = "";
518  searchBar.currentEngine = this._previousSearchEngine;
519 
520  // Restore the old query
521  searchBar.value = this._previousSearch;
522  }
523  }
524 
525  // Sync the search bar contents
526  BrowserSearch._syncSearchBarToMediaPage();
527 
528  searchBar.updateDisplay();
529  },
530 
531 
537  _isMediaTabOrMediaPageShowing:
538  function SearchHandler__isMediaTabOrMediaPageShowing() {
539  return (gBrowser.mediaTab == gBrowser.selectedTab ||
540  gBrowser.currentMediaPage != null);
541  },
542 
543 
548  _getSearchEventTarget: function SearchHandler__getSearchEventTarget(evt) {
549  var target;
550 
551  // If normal search event
552  if (evt.target && evt.target.currentEngine) {
553  target = evt.target;
554  // If search target is within a binding
555  } else if (evt.originalTarget && evt.originalTarget.currentEngine) {
556  target = evt.originalTarget;
557  // If search target is within a browser document
558  } else if (evt.target && evt.target.wrappedJSObject &&
559  evt.target.wrappedJSObject.currentEngine)
560  {
561  target = evt.target.wrappedJSObject;
562  // If search target is within a browser document AND a binding
563  } else if (evt.originalTarget && evt.originalTarget.wrappedJSObject &&
564  evt.originalTarget.wrappedJSObject.currentEngine)
565  {
566  target = evt.originalTarget.wrappedJSObject;
567  // Else I'm out of ideas...
568  } else {
569  dump("\ngSearchHandler: Error! Search event received from" +
570  " a target with no currentEngine!\n");
571  try { dump("\ttarget " + evt.target.tagName + "\n"); } catch (e) {};
572  try { dump("\toriginalTarget " + evt.originalTarget.tagName + "\n"); } catch (e) {};
573  }
574  return target;
575  },
576 
577 
582  _getMediaPageSearch: function SearchHandler__getMediaPageSearch() {
583  // Get the currently displayed sbIMediaListView
584  var mediaListView = this._getCurrentMediaListView();
585 
586  // XXXsteve We need a better way to discover the actual search terms
587  // rather than reverse engineer it from the search constaint
588  if (mediaListView && mediaListView.searchConstraint) {
589  var search = mediaListView.searchConstraint;
590  var terms = [];
591  var groupCount = search.groupCount;
592  for (var i = 0; i < groupCount; i++) {
593  var group = search.getGroup(i);
594  var property = group.properties.getNext();
595  terms.push(group.getValues(property).getNext());
596  }
597  return terms.join(" ");
598  }
599  return "";
600  },
601 
602 
607  _setMediaPageSearch: function SearchHandler__setMediaPageSearch(query) {
608  // Get the currently displayed sbIMediaListView
609  var mediaListView = this._getCurrentMediaListView();
610 
611  /* we need an sbIMediaListView with a cascadeFilterSet */
612  if (!mediaListView || !mediaListView.cascadeFilterSet) {
613  Cu.reportError("Error: no cascade filter set!");
614  return;
615  }
616 
617  // Attempt to set the search filter on the media list view
618  var filters = mediaListView.cascadeFilterSet;
619 
620  var searchIndex = -1;
621  for (let i = 0; i < filters.length; ++i) {
622  if (filters.isSearch(i)) {
623  searchIndex = i;
624  break;
625  }
626  }
627  if (searchIndex < 0) {
628  searchIndex = filters.appendSearch(["*"], 1);
629  }
630 
631  if (query == "" || query == null) {
632  filters.set(searchIndex, [], 0);
633  } else {
634 
635  var stringTransform =
636  Cc["@songbirdnest.com/Songbird/Intl/StringTransform;1"]
637  .createInstance(Ci.sbIStringTransform);
638 
639  query = stringTransform.normalizeString("",
640  Ci.sbIStringTransform.TRANSFORM_IGNORE_NONSPACE,
641  query);
642 
643  var valArray = query.split(" ");
644 
645  filters.set(searchIndex, valArray, valArray.length);
646  }
647  },
648 
649 
654  _getMediaPageDisplayName: function SearchHandler__getMediaPageDisplayName() {
655 
656  // Get the currently displayed sbIMediaListView
657  var mediaListView = this._getCurrentMediaListView();
658 
659  // Return the mediaListView's mediaList's name
660  return mediaListView?mediaListView.mediaList.name:"";
661  },
662 
663 
668  _getCurrentMediaListView: function SearchHandler__getCurrentMediaListView() {
669  if (gBrowser.currentMediaPage && gBrowser.currentMediaPage.mediaListView) {
670  return gBrowser.currentMediaPage.mediaListView;
671  } else {
672  return null;
673  }
674  },
675 
676 
681  _syncSearchBarToMediaPage:
682  function SearchHandler__syncSearchBarToMediaPage(aName) {
683  // if we are not currently showing a view (iem we're showing a web site)
684  // then do not change anything, we want the content of the search bar to
685  // persist
686  var mediaListView = this._getCurrentMediaListView();
687  if (!mediaListView) return;
688 
689  // Get the search box element
690  var searchBar = this.getSearchBar();
691  if (searchBar == null) {
692  return;
693  }
694 
695  // Change the text displayed on empty query to reflect
696  // the current search context.
697  var mediaPageName = this._getMediaPageDisplayName();
698  if (mediaPageName != "") {
699  var engine = this.getSongbirdSearchEngine(aName);
700  searchBar.setEngineDisplayText(engine, mediaPageName);
701  }
702 
703  // Find out what search is filtering this medialist
704  var queryString = this._getMediaPageSearch();
705  searchBar.value = queryString;
706  }
707 } // End of gSearchHandler
708 
709 
710 // Also expose the search handler as "BrowserSearch" for
711 // compatibility with FireFox
712 const BrowserSearch = gSearchHandler;
713 
714 
715 // Initialize the search handler on load
716 window.addEventListener("load",
717  function() {
718  gSearchHandler.init();
719  },
720  false);
721 
722 // Shutdown the search handler on unload
723 window.addEventListener("unload",
724  function() {
725  gSearchHandler.uninit();
726  },
727  false);
const Cu
const Cc
var Application
Definition: sbAboutDRM.js:37
function openUILinkIn(url, where, allowThirdPartyFixup, postData, referrerUrl)
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
const gSearchHandler
Songbird Search Handler.
var event
getService(Ci.sbIFaceplateManager)
let window
_window init
Definition: FeedWriter.js:1144
function search(aFolderId, aSearchStr, aExpectedScopeButtonId)
const LOG
return null
Definition: FeedWriter.js:1143
let node
_updateCookies aName
function loadURI(uri, referrer, postData, allowThirdPartyFixup)
Definition: browser.js:1946
var uri
Definition: FeedWriter.js:1135
const Cr
const Ci
var gServicePane
Definition: mainWinInit.js:39
var hidden
var browser
Definition: openLocation.js:42
function uninit(aEvent)
Definition: aboutDialog.js:89
_getSelectedPageStyle s i
var group