RetroZilla/extensions/inspector/resources/content/search/inSearchModule.js
2015-10-20 23:03:22 -04:00

531 lines
15 KiB
JavaScript

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2001
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Joe Hewitt <hewitt@netscape.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/***************************************************************
* inSearchModule -----------------------------------------------
* Encapsulates an ISML module and exposes it to inSearchService.
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* REQUIRED IMPORTS:
* chrome://inspector/content/jsutil/xpcom/XPCU.js
* chrome://inspector/content/jsutil/rdf/RDFU.js
* chrome://inspector/content/jsutil/rdf/RDFArray.js
* chrome://inspector/content/jsutil/xul/inFormManager.js
* chrome://inspector/content/search/xul/inSearchUtils.js
****************************************************************/
//////////// global variables /////////////////////
//////////// global constants ////////////////////
////////////////////////////////////////////////////////////////////////////
//// class inSearchModule
function inSearchModule(aBaseURL)
{
this.mObservers = [];
this.mBaseURL = aBaseURL;
}
inSearchModule.prototype =
{
mTitle: null,
mBaseURL: null,
mImpl: null,
mDialogElementIds: null,
mDialogURL: null,
mContextMenuItems: null,
mColumns: null,
mColDelimiter: null,
mNameSpace: null,
mRDFArray: null,
mResult: null,
mObservers: null,
mStartTime: null,
mElapsed: 0,
//////////////////////////////////////////////////////////////////////////
//// Properties
get searchService() { return this.mSearchService},
set searchService(aVal) { this.mSearchService = aVal },
get title() { return this.mTitle },
get baseURL() { return this.mBaseURL },
get defaultIconURL() { return this.mDefaultIconURL },
get datasource() { return this.mRDFArray.datasource },
get resultCount() { return this.mRDFArray.length },
get progressPercent() { return this.mImpl.progressPercent },
get progressText() { return this.mImp.progressText },
get isPastMilestone() { return this.mImpl.isPastMilestone },
get elapsed() { return this.mElapsed },
//////////////////////////////////////////////////////////////////////////
//// Initialization
initFromElement: function(aSearchEl)
{
this.parseSearch(aSearchEl);
if (this.mImpl.constructor)
this.mImpl.constructor();
},
//////////////////////////////////////////////////////////////////////////
//// Parser
parseSearch: function(aSearchEl)
{
// get the title
this.mTitle = aSearchEl.getAttribute("title");
// get the default icon url
this.mDefaultIconURL = aSearchEl.getAttribute("defaultIcon");
// get the namespace
var ns = aSearchEl.getAttribute("namespace")
this.mNameSpace = ns ? ns : kInspectorNSURI;
this.parseDialog(aSearchEl);
this.parseContextMenu(aSearchEl);
this.parseColumns(aSearchEl);
this.parseImplementation(aSearchEl);
},
parseDialog: function(aSearchEl)
{
var els = aSearchEl.getElementsByTagNameNS(kISMLNSURI, "dialog");
if (els.length > 0) {
var dialogEl = els[0];
// get the array of dialog element ids
var ids = dialogEl.getAttribute("elements");
if (ids)
this.mDialogElementIds = ids.split(",");
// get the dialog url
this.setDialogURL(dialogEl.getAttribute("href"));
// get the dialog parameters
this.mDialogResizable = (dialogEl.getAttribute("resizable") == "true") ? true : false;
}
},
parseContextMenu: function(aSearchEl)
{
var els = aSearchEl.getElementsByTagNameNS(kISMLNSURI, "contextmenu");
if (els.length > 0) {
var kids = els[0].childNodes;
this.mContextMenu = [];
for (var i = 0; i < kids.length; ++i) {
this.mContextMenu[i] = kids[i].cloneNode(true);
}
}
},
parseColumns: function(aSearchEl)
{
// get the result columns
var els = aSearchEl.getElementsByTagNameNS(kISMLNSURI, "columns");
if (els.length > 0) {
this.mColumns = [];
var cols = els[0];
this.mColDelimiter = cols.getAttribute("delimiter");
var kids = cols.childNodes;
var col, data;
for (var i= 0; i < kids.length; ++i) {
col = kids[i];
if (col.nodeType == Node.ELEMENT_NODE) { // ignore non-element nodes
data = {
name: col.getAttribute("name"),
title: col.getAttribute("title"),
flex: col.getAttribute("flex"),
className: col.getAttribute("class"),
copy: col.getAttribute("copy") == "true"
};
this.mColumns.push(data);
}
}
}
},
parseImplementation: function(aSearchEl)
{
this.mImpl = this.getDefaultImplementation();
// get the implementation object
var els = aSearchEl.getElementsByTagNameNS(kISMLNSURI, "implementation");
if (els.length > 0) {
var kids = aSearchEl.getElementsByTagNameNS(kISMLNSURI, "*");
for (var i = 0; i < kids.length; i++) {
if (kids[i].localName == "property")
this.parseProperty(kids[i]);
if (kids[i].localName == "method")
this.parseMethod(kids[i]);
}
}
},
parseProperty: function(aPropEl)
{
var name = aPropEl.getAttribute("name");
var fn = null;
// look for a getter
try {
fn = this.getCodeTagFunction(aPropEl, "getter", null);
if (fn)
this.mImpl.__defineGetter__(name, fn);
} catch (ex) {
throw "### SYNTAX ERROR IN ISML GETTER \"" + name + "\" ###\n" + ex;
}
// look for a setter
try {
fn = this.getCodeTagFunction(aPropEl, "setter", ["val"]);
if (fn)
this.mImpl.__defineSetter__(name, fn);
} catch (ex) {
throw "### SYNTAX ERROR IN ISML SETTER \"" + name + "\" ###\n" + ex;
}
},
parseMethod: function(aMethodEl)
{
var name = aMethodEl.getAttribute("name");
var def = aMethodEl.getAttribute("defaultCommand") == "true";
// get all the parameters
var els = aMethodEl.getElementsByTagNameNS(kISMLNSURI, "parameter");
var params = [];
for (var i = 0; i < els.length; i++) {
params[i] = els[i].getAttribute("name");
}
// get the body javascript and create the function
try {
var fn = this.getCodeTagFunction(aMethodEl, "body", params);
this.mImpl[name] = fn;
if (def)
this.mImpl.__DefaultCmd__ = fn;
} catch (ex) {
throw "### SYNTAX ERROR IN ISML METHOD \"" + name + "\" ###\n" + ex;
}
},
getCodeTagFunction: function(aParent, aLocalName, aParams)
{
var els = aParent.getElementsByTagNameNS(kISMLNSURI, aLocalName);
if (els.length) {
var body = els[0];
// try to determine where the code is located
var node = body.childNodes.length > 0 ? body.firstChild : body;
return this.getJSFunction(aParams, node.nodeValue);
}
return null;
},
getJSFunction: function(aParams, aCode)
{
var params = "";
if (aParams) {
for (var i = 0; i < aParams.length; i++) {
params += aParams[i];
if (i < aParams.length-1) params += ",";
}
}
var js = "function(" + params + ") " +
"{" +
(aCode ? aCode : "") +
"}";
var fn;
eval("fn = " + js);
return fn;
},
getDefaultImplementation: function()
{
return { module: this };
},
//////////////////////////////////////////////////////////////////////////
//// Dialog box
openDialog: function()
{
window.openDialog(this.mDialogURL, "inSearchModule_dialog",
"chrome,modal,resizable="+this.mDialogResizable, this);
},
processDialog: function(aWindow)
{
var map = inFormManager.readWindow(aWindow, this.mDialogElementIds);
this.implStartSearch(map);
},
//////////////////////////////////////////////////////////////////////////
//// Searching
startSearch: function()
{
if (this.mDialogURL) {
this.openDialog();
} else
this.implStartSearch(null);
},
implStartSearch: function(aMap)
{
this.mStartTime = new Date();
this.mElapsed = 0;
this.notifySearchStart();
this.initDataSource();
this.prepareForResult();
this.mImpl.searchStart(aMap);
},
stopSearch: function()
{
this.searchEnd();
},
//////////////////////////////////////////////////////////////////////////
//// Result Returns
setResultProperty: function(aAttr, aValue)
{
this.mResult[aAttr] = aValue;
},
searchResultReady: function()
{
this.mRDFArray.add(this.mResult);
this.notifySearchResult();
this.prepareForResult();
},
searchError: function(aMsg)
{
},
searchEnd: function()
{
this.mElapsed = new Date() - this.mStartTime;
this.notifySearchEnd();
},
//////////////////////////////////////////////////////////////////////////
//// Columns
get columnCount() { return this.mColumns.length },
getColumn: function(aIndex) { return this.mColumns[aIndex] },
getColumnName: function(aIndex) { return this.mColumns[aIndex].name },
getColumnTitle: function(aIndex) { return this.mColumns[aIndex].title },
getColumnClassName: function(aIndex) { return this.mColumns[aIndex].className },
getColumnFlex: function(aIndex) { return this.mColumns[aIndex].flex },
//////////////////////////////////////////////////////////////////////////
//// RDF Datasource
initDataSource: function()
{
this.mRDFArray = new RDFArray(this.mNameSpace, "inspector:searchResults", "results");
this.mRDFArray.initialize();
},
getResultPropertyAt: function(aIndex, aProp)
{
return this.mRDFArray.get(aIndex, aProp);
},
getItemText: function(aIndex)
{
var cols = this.mColumns;
var text = [];
for (var i = 0; i < cols.length; ++i) {
if (cols[i].copy) {
text.push(this.getResultPropertyAt(aIndex, cols[i].name));
}
}
return text.join(this.mColDelimiter);
},
//////////////////////////////////////////////////////////////////////////
//// Context Menu
installContextMenu: function(aMenu, aInsertionPoint, aDir)
{
if (this.mContextMenu) {
aMenu._searchModule = this;
var item;
this.mMenuItems = [];
if (this.mContextMenu.length == 0)
aInsertionPoint.setAttribute("hide", "true");
for (var i = 0; i < this.mContextMenu.length; ++i) {
item = this.mContextMenu[i];
this.mMenuItems.push(item);
this.installSearchReference(item);
if (aDir == inSearchService.INSERT_BEFORE)
aMenu.insertBefore(item, aInsertionPoint);
else {
// NOT YET IMPLEMENTED
}
}
}
},
uninstallContextMenu: function(aMenu, aInsertionPoint, aDir)
{
if (this.mContextMenu) {
if (this.mContextMenu.length == 0)
aInsertionPoint.removeAttribute("hide");
// remove the menu items
for (var i = 0; i < this.mContextMenu.length; ++i)
aMenu.removeChild(this.mMenuItems[i]);
}
},
installSearchReference: function(aItem)
{
if (aItem.nodeType == Node.ELEMENT_NODE) {
if (aItem.localName == "menuitem") {
aItem.search = this.mImpl;
for (var i = 0; i < aItem.childNodes.length; ++i)
this.installSearchReference(aItem.childNodes[i]);
}
}
},
//////////////////////////////////////////////////////////////////////////
//// Event Notification
// NOTE TO SELF - this code could be cut down to nothing if you write a module
// called "ObserverManager" to do the work for you
addSearchObserver: function(aObserver)
{
this.mObservers.push(aObserver);
},
removeSearchObserver: function(aObserver)
{
var o;
var obs = this.mObservers;
for (var i = 0; i < obs.length; i++) {
o = obs[i];
if (o == aObserver) {
obs.splice(i, 1);
return;
}
}
},
notifySearchStart: function()
{
var o = this.mObservers;
for (var i = 0; i < o.length; i++)
o[i].onSearchStart(this);
},
notifySearchResult: function()
{
var o = this.mObservers;
for (var i = 0; i < o.length; i++)
o[i].onSearchResult(this);
},
notifySearchEnd: function(aResult)
{
var o = this.mObservers;
for (var i = 0; i < o.length; i++)
o[i].onSearchEnd(this, aResult);
},
notifySearchError: function(aMsg)
{
var o = this.mObservers;
for (var i = 0; i < o.length; i++)
o[i].onSearchError(this, aMsg);
},
//////////////////////////////////////////////////////////////////////////
//// Uncategorized
callDefaultCommand: function()
{
if (this.mImpl.__DefaultCmd__)
this.mImpl.__DefaultCmd__();
},
prepareForResult: function()
{
this.mResult = { _icon: this.mDefaultIconURL };
},
setDialogURL: function(aURL)
{
this.mDialogURL = aURL;
// This block below doesn't work for now because none of the local file implementations
// implement SetURL. So, for now, the url in the search file MUST be absolute :(
/* this.mDialogURL = aURL;
var baseFile = inSearchUtils.createLocalFile(this.mBaseURL);
try {
baseFile.append(aURL);
} catch (ex) {
basefile = inSearchUtils.createLocalFile(aURL);
}
var file = XPCU.QI(file, "nsIFile");
var ioService = XPCU.getService("@mozilla.org/network/io-service;1", "nsIIOService");
var fileHandler = XPCU.QI(ioService.getProtocolHandler("file"), "nsIFileProtocolHandler");
this.mDialogURL = fileHandler.getURLSpecFromFile(basefile);
*/
}
};
//////////////////////////////////////////////////////////////////////////
//// Event Listeners