Import from 1.9a8 tarball
[mozilla-extra.git] / extensions / help / resources / content / help.js
blob3ae09c0b97a5e72749f9f6c6d75c8f71ddb98d89
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is Mozilla Communicator client code, released
16  * March 31, 1998.
17  *
18  * The Initial Developer of the Original Code is
19  * Netscape Communications Corporation.
20  * Portions created by the Initial Developer are Copyright (C) 1998-1999
21  * the Initial Developer. All Rights Reserved.
22  *
23  * Contributor(s):
24  *   Ian Oeschger <oeschger@brownhen.com> (Original Author)
25  *   Peter Wilson (added sidebar tabs)
26  *   R.J. Keller <rlk@trfenv.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 ***** */
42 //-------- global variables
43 var helpBrowser;
44 var helpExternal;
45 var helpSearchPanel;
46 var emptySearch;
47 var emptySearchText = "No search items found.";
48 var emptySearchLink = "about:blank";
49 var helpTocPanel;
50 var helpIndexPanel;
51 var helpGlossaryPanel;
53 // Namespaces
54 const NC = "http://home.netscape.com/NC-rdf#";
55 const MAX_LEVEL = 40; // maximum depth of recursion in search datasources.
57 // Resources
58 const RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
59 const RDF_ROOT = RDF.GetResource("urn:root");
60 const NC_PANELLIST = RDF.GetResource(NC + "panellist");
61 const NC_PANELID = RDF.GetResource(NC + "panelid");
62 const NC_PLATFORM = RDF.GetResource(NC + "platform");
63 const NC_EMPTY_SEARCH_TEXT = RDF.GetResource(NC + "emptysearchtext");
64 const NC_EMPTY_SEARCH_LINK = RDF.GetResource(NC + "emptysearchlink");
65 const NC_DATASOURCES = RDF.GetResource(NC + "datasources");
66 const NC_SUBHEADINGS = RDF.GetResource(NC + "subheadings");
67 const NC_NAME = RDF.GetResource(NC + "name");
68 const NC_CHILD = RDF.GetResource(NC + "child");
69 const NC_LINK = RDF.GetResource(NC + "link");
70 const NC_TITLE = RDF.GetResource(NC + "title");
71 const NC_BASE = RDF.GetResource(NC + "base"); 
72 const NC_DEFAULTTOPIC = RDF.GetResource(NC + "defaulttopic"); 
74 const RDFCUtils = Components.classes["@mozilla.org/rdf/container-utils;1"].getService(Components.interfaces.nsIRDFContainerUtils);
75 var RDFContainer = Components.classes["@mozilla.org/rdf/container;1"].createInstance(Components.interfaces.nsIRDFContainer);
76 const CONSOLE_SERVICE = Components.classes['@mozilla.org/consoleservice;1'].getService(Components.interfaces.nsIConsoleService);
77             
78 var RE;
80 var helpFileURI;
81 var helpFileDS;
82 // Set from nc:base attribute on help rdf file. It may be used for prefix reduction on all links within
83 // the current help set.
84 var helpBaseURI;
86 const defaultHelpFile = "chrome://help/locale/mozillahelp.rdf";
87 // Set from nc:defaulttopic. It is used when the requested uri has no topic specified. 
88 var defaultTopic = "welcome"; 
89 var searchDatasources = "rdf:null";
90 var searchDS = null;
91 var platform = /mac/i.test(navigator.platform) ? "mac" :
92                /win/i.test(navigator.platform) ? "win" :
93                /os\/2/i.test(navigator.platform) ? "os/2" : "unix";
95 const NSRESULT_RDF_SYNTAX_ERROR = 0x804e03f7; 
97 // This function is called by dialogs/windows that want to display context-sensitive help
98 // These dialogs/windows should include the script chrome://help/content/contextHelp.js
99 function displayTopic(topic) {
100   // Use default topic if topic is not specified.
101   if (!topic)
102     topic = defaultTopic;
104   // Get the help page to open.
105   var uri = getLink(topic);
107   // Use default topic if specified topic is not found.
108   if (!uri) // Topic not found - revert to default.
109     uri = getLink(defaultTopic);
110   loadURI(uri);
113 var helpContentListener = {
114   onStartURIOpen: function(aURI) {
115     return false;
116   },
117   doContent: function(aContentType, aIsContentPreferred, aRequest, aContentHandler) {
118     throw Components.results.NS_ERROR_UNEXPECTED;
119   },
120   isPreferred: function(aContentType, aDesiredContentType) {
121     return false;
122   },
123   canHandleContent: function(aContentType, aIsContentPreferred, aDesiredContentType) {
124     return false;
125   },
126   loadCookie: null,
127   parentContentListener: null,
128   QueryInterface: function (aIID) {
129     if (aIID.equals(Components.interfaces.nsIURIContentListener) ||
130         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
131         aIID.equals(Components.interfaces.nsISupports))
132       return this;
133     Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
134     return null;
135   }
138 // Initialize the Help window
139 function init() {
140   //cache panel references.
141   helpSearchPanel = document.getElementById("help-search-panel");
142   helpTocPanel = document.getElementById("help-toc-panel");
143   helpIndexPanel = document.getElementById("help-index-panel");
144   helpGlossaryPanel = document.getElementById("help-glossary-panel");
145   helpBrowser = document.getElementById("help-content");
146   helpExternal = document.getElementById("help-external");
147   helpExternal.docShell.useErrorPages = false;
148   helpExternal
149     .docShell
150     .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
151     .getInterface(Components.interfaces.nsIURIContentListener)
152     .parentContentListener = helpContentListener;
154   // Get the help content pack, base URL, and help topic
155   var helpTopic = defaultTopic;
156   if ("arguments" in window && window.arguments[0] instanceof Components.interfaces.nsIDialogParamBlock) {
157     helpFileURI = window.arguments[0].GetString(0);
158     helpBaseURI = helpFileURI.substring(0, helpFileURI.lastIndexOf("/")+1); // trailing "/" included.
159     helpTopic = window.arguments[0].GetString(1);
160   }
162   loadHelpRDF();
164   displayTopic(helpTopic);
166   window.XULBrowserWindow = new nsHelpStatusHandler();
168   //Start the status handler.
169   window.XULBrowserWindow.init();
171   // Hook up UI through Progress Listener
172   helpBrowser.addProgressListener(window.XULBrowserWindow, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
173   helpExternal.addProgressListener(window.XULBrowserWindow, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
175   //Always show the Table of Contents sidebar at startup.
176   showPanel('help-toc');
178   // alwaysRaised only works on Mac & Win.
179   if (!/Win|Mac/.test(navigator.platform)) {
180     var toggleSeparator = document.getElementById("context-sep-selectall");
181     var toggleOnTop = document.getElementById("context-zlevel");
182     toggleOnTop.hidden = true;
183     toggleSeparator.hidden = true;
184   }
187 function contentClick(event) {
188   // is this a left click on a link?
189   if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey || event.button != 0)
190     return true;
192   // is this a link?
193   var target = event.target;
194   while (!(target instanceof HTMLAnchorElement))
195     if (!(target = target.parentNode))
196       return true;
198   // is this an internal link?
199   if (target.href.lastIndexOf("chrome:", 0) == 0)
200     return true;
202   var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
203                   .getService(Components.interfaces.nsIURLFormatter);
204   var uri = target.href;
205   if (/^x-moz-url-link:/.test(uri))
206     uri = formatter.formatURLPref(RegExp.rightContext);
208   const loadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_IS_LINK;
209   try {
210     helpExternal.webNavigation.loadURI(uri, loadFlags, null, null, null);
211   } catch (e) {}
212   return false;
215 function loadHelpRDF() {
216   if (!helpFileDS) {
217     try {
218       helpFileDS = RDF.GetDataSourceBlocking(helpFileURI);
219     }
220     catch (e if (e.result == NSRESULT_RDF_SYNTAX_ERROR)) {
221       log("Help file: " + helpFileURI + " contains a syntax error.");
222     }
223     catch (e) {
224       log("Help file: " + helpFileURI + " was not found.");
225     }
226     try {
227       document.title = getAttribute(helpFileDS, RDF_ROOT, NC_TITLE, "");
228       helpBaseURI = getAttribute(helpFileDS, RDF_ROOT, NC_BASE, helpBaseURI);
229       defaultTopic = getAttribute(helpFileDS, RDF_ROOT, NC_DEFAULTTOPIC, "welcome");
231       var panelDefs = helpFileDS.GetTarget(RDF_ROOT, NC_PANELLIST, true);      
232       RDFContainer.Init(helpFileDS, panelDefs);
233       var iterator = RDFContainer.GetElements();
234       while (iterator.hasMoreElements()) {
235         var panelDef = iterator.getNext();
237         var panelPlatforms = getAttribute(helpFileDS, panelDef, NC_PLATFORM, platform);
238         panelPlatforms = panelPlatforms.split(/\s+/);
240         if (panelPlatforms.indexOf(platform) == -1)
241           continue; // ignore datasources for other platforms.
243         var panelID = getAttribute(helpFileDS, panelDef, NC_PANELID, null);
245         var datasources = getAttribute(helpFileDS, panelDef, NC_DATASOURCES, "rdf:null");
246         datasources = normalizeLinks(helpBaseURI, datasources);
247         // cache additional datsources to augment search datasources.
248         if (panelID == "search") {
249           emptySearchText = getAttribute(helpFileDS, panelDef, NC_EMPTY_SEARCH_TEXT, emptySearchText);
250           emptySearchLink = getAttribute(helpFileDS, panelDef, NC_EMPTY_SEARCH_LINK, emptySearchLink);
251           searchDatasources += " " + datasources;
252           continue; // but don't try to display them yet!
253         }  
255         // cache toc datasources for use by ID lookup.
256         var tree = document.getElementById("help-" + panelID + "-panel");
257         loadDatabasesBlocking(datasources);
258         tree.setAttribute("datasources", tree.getAttribute("datasources") + " " + datasources);
259       }  
260     }
261     catch (e) {
262       log(e + "");      
263     }
264   }
267 function loadDatabasesBlocking(datasources) {
268   var ds = datasources.split(/\s+/);
269   for (var i=0; i < ds.length; ++i) {
270     if (ds[i] == "rdf:null" || ds[i] == "")
271       continue;
272     try {  
273       // we need blocking here to ensure the database is loaded so getLink(topic) works.
274       var datasource = RDF.GetDataSourceBlocking(ds[i]);
275     }
276     catch (e) {
277       log("Datasource: " + ds[i] + " was not found.");
278     }
279   }
282 // prepend helpBaseURI to list of space separated links if the don't start with "chrome:"
283 function normalizeLinks(helpBaseURI, links) {
284   if (!helpBaseURI)
285     return links;
286   var ls = links.split(/\s+/);
287   if (ls.length == 0)
288     return links;
289   for (var i=0; i < ls.length; ++i) {
290     if (ls[i] == "")
291       continue;
292     if (ls[i].substr(0,7) != "chrome:" && ls[i].substr(0,4) != "rdf:") 
293       ls[i] = helpBaseURI + ls[i];
294   }
295   return ls.join(" ");  
298 function getLink(ID) {
299   if (!ID)
300     return null;
301   // Note resources are stored in fileURL#ID format.
302   // We have one possible source for an ID for each datasource in the composite datasource.
303   // The first ID which matches is returned.
304   var tocTree = document.getElementById("help-toc-panel");
305   var tocDS = tocTree.database;
306     if (tocDS == null)
307       return null;
308     var tocDatasources = tocTree.getAttribute("datasources");
309   var ds = tocDatasources.split(/\s+/);
310   for (var i=0; i < ds.length; ++i) {
311     if (ds[i] == "rdf:null" || ds[i] == "")
312       continue;
313     try {
314       var rdfID = ds[i] + "#" + ID;
315       var resource = RDF.GetResource(rdfID);
316       if (resource) {
317         var link = tocDS.GetTarget(resource, NC_LINK, true);
318         if (link) {
319           link = link.QueryInterface(Components.interfaces.nsIRDFLiteral);
320           if (link) 
321             return link.Value;
322           else  
323             return null;
324         }  
325       }
326     }
327     catch (e) { log(rdfID + " " + e);}
328   }
329   return null;
332 // Called by contextHelp.js to determine if this window is displaying the requested help file.
333 function getHelpFileURI() {
334   return helpFileURI;
338 function getWebNavigation()
340   return helpBrowser.webNavigation;
343 function loadURI(uri)
345   if (uri.substr(0,7) != "chrome:")
346     uri = helpBaseURI + uri;
347   const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
348   getWebNavigation().loadURI(uri, nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
351 function goBack()
353   var webNavigation = getWebNavigation();
354   if (webNavigation.canGoBack)
355     webNavigation.goBack();
358 function goForward()
360   var webNavigation = getWebNavigation();
361   if (webNavigation.canGoForward)
362     webNavigation.goForward();
365 function goHome() {
366   // load "Welcome" page
367   displayTopic(defaultTopic);
370 function print()
372   try {
373     content.print();
374   } catch (e) {
375   }
378 function createBackMenu(event)
380   return FillHistoryMenu(event.target, "back");
383 function createForwardMenu(event)
385   return FillHistoryMenu(event.target, "forward");
388 function gotoHistoryIndex(aEvent)
390   var index = aEvent.target.getAttribute("index");
391   if (!index)
392     return false;
393   try {
394     getWebNavigation().gotoIndex(index);
395   }
396   catch(ex) {
397     return false;
398   }
399   return true;
402 function nsHelpStatusHandler()
406 nsHelpStatusHandler.prototype =
408   onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
409   {
410     const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
412     // Turn on the throbber.
413     if (aStateFlags & nsIWebProgressListener.STATE_START)
414       this.throbberElement.setAttribute("busy", "true");
415     else if (aStateFlags & nsIWebProgressListener.STATE_STOP)
416       this.throbberElement.removeAttribute("busy");
417   },
418   onStatusChange : function(aWebProgress, aRequest, aStateFlags, aStatus) {},
419   onProgressChange : function(aWebProgress, aRequest, aCurSelfProgress,
420                               aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {},
421   onSecurityChange : function(aWebProgress, aRequest, state) {},
422   onLocationChange : function(aWebProgress, aRequest, aLocation)
423   {
424     UpdateBackForwardButtons();
425   },
426   QueryInterface : function(aIID)
427   {
428     if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
429       aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
430       aIID.equals(Components.interfaces.nsIXULBrowserWindow) ||
431       aIID.equals(Components.interfaces.nsISupports))
432       return this;
433     throw Components.results.NS_NOINTERFACE;
434   },
436   init : function()
437   {
438     this.throbberElement = document.getElementById("navigator-throbber");
439   },
441   destroy : function()
442   {
443     //this is needed to avoid memory leaks, see bug 60729
444     this.throbberElement = null;
445   },
447   setJSStatus : function(status) {},
448   setJSDefaultStatus : function(status) {},
449   setOverLink : function(link, context) {}
452 function UpdateBackForwardButtons()
454   var backBroadcaster = document.getElementById("canGoBack");
455   var forwardBroadcaster = document.getElementById("canGoForward");
456   var webNavigation = getWebNavigation();
458   // Avoid setting attributes on broadcasters if the value hasn't changed!
459   // Remember, guys, setting attributes on elements is expensive!  They
460   // get inherited into anonymous content, broadcast to other widgets, etc.!
461   // Don't do it if the value hasn't changed! - dwh
463   var backDisabled = (backBroadcaster.getAttribute("disabled") == "true");
464   var forwardDisabled = (forwardBroadcaster.getAttribute("disabled") == "true");
466   if (backDisabled == webNavigation.canGoBack)
467     backBroadcaster.setAttribute("disabled", !backDisabled);
468   
469   if (forwardDisabled == webNavigation.canGoForward)
470     forwardBroadcaster.setAttribute("disabled", !forwardDisabled);
473 var gFindInstData;
474 function getFindInstData()
476   if (!gFindInstData) {
477     gFindInstData = new nsFindInstData();
478     gFindInstData.browser = helpBrowser;
479     // defaults for rootSearchWindow and currentSearchWindow are fine here
480   }
481   return gFindInstData;
484 function find(again, reverse)
486   if (again)
487     findAgainInPage(getFindInstData(), reverse);
488   else
489     findInPage(getFindInstData())
492 function getMarkupDocumentViewer()
494   return helpBrowser.markupDocumentViewer;
497 //Show the selected sidebar panel
498 function showPanel(panelId) {
499   //hide other sidebar panels and show the panel name taken in from panelID.
500   helpSearchPanel.setAttribute("hidden", "true");
501   helpTocPanel.setAttribute("hidden", "true");
502   helpIndexPanel.setAttribute("hidden", "true");
503   helpGlossaryPanel.setAttribute("hidden", "true");
504   var thePanel = document.getElementById(panelId + "-panel");
505   thePanel.setAttribute("hidden","false");
507   //remove the selected style from the previous panel selected.
508   document.getElementById("help-glossary-btn").removeAttribute("selected");
509   document.getElementById("help-index-btn").removeAttribute("selected");
510   document.getElementById("help-search-btn").removeAttribute("selected");
511   document.getElementById("help-toc-btn").removeAttribute("selected");
513   //add the selected style to the correct panel.
514   var theButton = document.getElementById(panelId + "-btn");
515   theButton.setAttribute("selected", "true");
516   document.commandDispatcher.advanceFocusIntoSubtree(theButton);
519 function onselect_loadURI(tree) {
520   var row = tree.currentIndex;
521   if (row >= 0) {
522     var resource = tree.view.getResourceAtIndex(row);
523     var link = tree.database.GetTarget(resource, NC_LINK, true);
524     if (link instanceof Components.interfaces.nsIRDFLiteral && link.Value)
525       loadURI(link.Value);
526   }
529 function doFind() {
530   var searchTree = document.getElementById("help-search-tree");
531   var findText = document.getElementById("findText");
533   // clear any previous results.
534   clearDatabases(searchTree.database);
536     // if the search string is empty or contains only whitespace, purge the results tree and return
537     RE = findText.value.match(/\S+/g);
538     if (!RE) {
539       searchTree.builder.rebuild();
540       return;
541     }
543     // compile the search string, which has already been split up above, into regexps
544     for (var i=0; i < RE.length; ++i) {
545       RE[i] = new RegExp(RE[i], "i");
546   }
547   emptySearch = true;
549   // search TOC
550   var resultsDS =  Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].createInstance(Components.interfaces.nsIRDFDataSource);
551   var tree = document.getElementById("help-toc-panel");
552   var sourceDS = tree.database;
553   doFindOnDatasource(resultsDS, sourceDS, RDF_ROOT, 0);
555   // search additional search datasources
556   if (searchDatasources != "rdf:null") {
557     if (!searchDS)
558       searchDS = loadCompositeDS(searchDatasources);
559     doFindOnDatasource(resultsDS, searchDS, RDF_ROOT, 0);
560   }
562   // search index.
563   tree = document.getElementById("help-index-panel");
564   sourceDS = tree.database;
565   if (!sourceDS) // If the index has never been displayed this will be null.
566     sourceDS = loadCompositeDS(tree.datasources);
567   doFindOnDatasource(resultsDS, sourceDS, RDF_ROOT, 0);
569   // search glossary.
570   tree = document.getElementById("help-glossary-panel");
571   sourceDS = tree.database;
572   if (!sourceDS) // If the glossary has never been displayed this will be null (sigh!).
573     sourceDS = loadCompositeDS(tree.datasources);
574   doFindOnDatasource(resultsDS, sourceDS, RDF_ROOT, 0);
576   if (emptySearch)
577                 assertSearchEmpty(resultsDS);
578   // Add the datasource to the search tree
579   searchTree.database.AddDataSource(resultsDS);
580   searchTree.builder.rebuild();
583 function clearDatabases(compositeDataSource) {
584   var enumDS = compositeDataSource.GetDataSources()
585   while (enumDS.hasMoreElements()) {
586     var ds = enumDS.getNext();
587         compositeDataSource.RemoveDataSource(ds);
588   }
591 function doFindOnDatasource(resultsDS, sourceDS, resource, level) {
592   if (level > MAX_LEVEL) {
593     try {
594       log("Recursive reference to resource: " + resource.Value + ".");
595     }
596     catch (e) {
597       log("Recursive reference to unknown resource.");
598     }
599     return;
600   }
601   // find all SUBHEADING children of current resource.
602   var targets = sourceDS.GetTargets(resource, NC_SUBHEADINGS, true);
603   while (targets.hasMoreElements()) {
604       var target = targets.getNext();
605       target = target.QueryInterface(Components.interfaces.nsIRDFResource);
606         // The first child of a rdf:subheading should (must) be a rdf:seq.
607         // Should we test for a SEQ here?
608     doFindOnSeq(resultsDS, sourceDS, target, level+1);       
609   }  
612 function doFindOnSeq(resultsDS, sourceDS, resource, level) {
613   // load up an RDFContainer so we can access the contents of the current rdf:seq.    
614   RDFContainer.Init(sourceDS, resource);
615   var targets = RDFContainer.GetElements();
616   while (targets.hasMoreElements()) {
617     var target = targets.getNext();
618     var link = sourceDS.GetTarget(target, NC_LINK, true);
619     var name = sourceDS.GetTarget(target, NC_NAME, true);
620     name = name.QueryInterface(Components.interfaces.nsIRDFLiteral);
621     if (link && isMatch(name.Value)) {
622       // we have found a search entry - add it to the results datasource.
623       var urn = RDF.GetAnonymousResource();
624       resultsDS.Assert(urn, NC_NAME, name, true);
625       resultsDS.Assert(urn, NC_LINK, link, true);
626       resultsDS.Assert(RDF_ROOT, NC_CHILD, urn, true);
627       emptySearch = false;      
628     }
629     // process any nested rdf:seq elements.
630     doFindOnDatasource(resultsDS, sourceDS, target, level+1);
631   }
634 function assertSearchEmpty(resultsDS) {
635         var resSearchEmpty = RDF.GetResource("urn:emptySearch");
636         resultsDS.Assert(RDF_ROOT,
637                          NC_CHILD,
638                          resSearchEmpty,
639                          true);
640         resultsDS.Assert(resSearchEmpty,
641                          NC_NAME,
642                          RDF.GetLiteral(emptySearchText),
643                          true);
644         resultsDS.Assert(resSearchEmpty,
645                          NC_LINK,
646                          RDF.GetLiteral(emptySearchLink),
647                          true);
650 function isMatch(text) {
651   for (var i=0; i < RE.length; ++i ) {
652     if (!RE[i].test(text))
653       return false;
654   }
655   return true;
658 function loadCompositeDS(datasources) {
659   // We can't search on each individual datasource's - only the aggregate (for each sidebar tab)
660   // has the appropriate structure.
661   var compositeDS =  Components.classes["@mozilla.org/rdf/datasource;1?name=composite-datasource"]
662       .createInstance(Components.interfaces.nsIRDFCompositeDataSource);
663   
664   var ds = datasources.split(/\s+/);
665   for (var i=0; i < ds.length; ++i) {
666     if (ds[i] == "rdf:null" || ds[i] == "")
667       continue;
668     try {  
669       // we need blocking here to ensure the database is loaded.
670       var sourceDS = RDF.GetDataSourceBlocking(ds[i]);
671       compositeDS.AddDataSource(sourceDS);
672     }
673     catch (e) {
674       log("Datasource: " + ds[i] + " was not found.");
675     }
676   }
677   return compositeDS;
680 function getAttribute(datasource, resource, attributeResourceName, defaultValue) {
681   var literal = datasource.GetTarget(resource, attributeResourceName, true);
682   return literal instanceof Components.interfaces.nsIRDFLiteral ? literal.Value : defaultValue;
684 // Write debug string to error console.
685 function log(aText) {
686   CONSOLE_SERVICE.logStringMessage(aText);
690 //INDEX OPENING FUNCTION -- called in oncommand for index pane
691 // iterate over all the items in the outliner;
692 // open the ones at the top-level (i.e., expose the headings underneath
693 // the letters in the list.
694 function displayIndex() {
695   var treeview = helpIndexPanel.view;
696   var i = treeview.rowCount;
697   while (i--)
698     if (!treeview.getLevel(i) && !treeview.isContainerOpen(i))
699       treeview.toggleOpenState(i);
702 // Shows the panel relative to the currently selected panel.
703 // Takes a boolean parameter - if true it will show the next panel, 
704 // otherwise it will show the previous panel.
705 function showRelativePanel(goForward) {
706   var selectedIndex = -1;
707   var sidebarBox = document.getElementById("helpsidebar-box");
708   var sidebarButtons = new Array();
709   for (var i = 0; i < sidebarBox.childNodes.length; i++) {
710     var btn = sidebarBox.childNodes[i];
711     if (btn.nodeName == "toolbarbutton") {
712       if (btn.getAttribute("selected") == "true")
713         selectedIndex = sidebarButtons.length;
714       sidebarButtons.push(btn);
715     }
716   }
717   if (selectedIndex == -1)
718     return;
719   selectedIndex += goForward ? 1 : -1;
720   if (selectedIndex >= sidebarButtons.length)
721     selectedIndex = 0;
722   else if (selectedIndex < 0)
723     selectedIndex = sidebarButtons.length - 1;
724   sidebarButtons[selectedIndex].doCommand();
727 // getXulWin - Returns the current Help window as a nsIXULWindow.
728 function getXulWin() {
729   window.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
730   var webnav = window.getInterface(Components.interfaces.nsIWebNavigation);
731   var dsti = webnav.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
732   var treeowner = dsti.treeOwner;
733   var ifreq = treeowner.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
735   return ifreq.getInterface(Components.interfaces.nsIXULWindow);
738 // toggleZLevel - Toggles whether or not the window will always appear on top.
739 // element is the DOM node that persists the checked state
740 function toggleZLevel(element) {
741   var xulwin = getXulWin();
743   // Now we can flip the zLevel, and set the attribute so that it persists correctly
744   if (xulwin.zLevel > xulwin.normalZ) {
745     xulwin.zLevel = xulwin.normalZ;
746     element.setAttribute("checked", "false");
747   } else {
748     xulwin.zLevel = xulwin.raisedZ;
749     element.setAttribute("checked", "true");
750   }