mozdev.org

Mozile (xhtml editing in your browser)    

resources:
Overview

core.js

Summary

This file defines the core Mozile objects, which are required to allow basic editing and to support the rest of the Mozile system. Additional tools and interface elements are defined in "core/interface.js".

Version: 0.7

Author: James A. Overton


Class Summary
InsertionPoint  
MozileComponent  
MozileEditor  
MozileLinkResource  
MozileMediator  
MozileModule  
MozileResource  
MozileScriptResource  
MozileStyleResource  

Method Summary
static String checkArgument(<String> defaultValue, value)
           Checks
static Object dumpArray(arr)
           Dump Array - Displays the contents of the array in key=>value pairs, using an alert.
static Array executeXPath(<String> expression, <Node> node)
           Executes an XPath expression in the context of the document or a node.
static Object mozileHandleBlur(event)
           Mozile Handle Blur - Takes appropriate action when a Mozile editor loses focus: hide the caret and toobar.
static Void mozileHandleEvent(event)
           A centralized event handler, which calls other handlers as needed.
static Object mozileHandleFocus(event)
           Mozile Handle Focus - Takes appropriate action when a Mozile editor gains focus: shows the caret and toolbar, then sets the currentEditor.
static Object mozileHandleKeypress(event)
           Mozile Handle Keypress - Decides what action to take for the given "keypress" event, and calls the appropriate function.
static Object mozileHandleKeyup(event)
           Mozile Handle Keyup - Decides what action to take for the given "keyup" event, and calls the appropriate functions: update the toolbar and store the current state for undo/redo.
static Object mozileHandleMouseup(event)
           Mozile Handle Mouseup - Takes appropriate action when Mozile detects a mouseup event: show the caret and the toolbar.
static Object printXML(XML)
           Print XML - Serializes the given XML and returns it as a string

/* ***** BEGIN LICENSE BLOCK *****
 * Licensed under Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * Full Terms at http://mozile.mozdev.org/license2.html
 *
 * 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 James A. Overton's code (james@overton.ca).
 *
 * The Initial Developer of the Original Code is James A. Overton.
 * Portions created by the Initial Developer are Copyright (C) 2005-2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *	James A. Overton <james@overton.ca>
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * @fileoverview This file defines the core Mozile objects, which are required to allow basic editing and to support the rest of the Mozile system. Additional tools and interface elements are defined in "core/interface.js".
 * @link http://mozile.mozdev.org 
 * @author James A. Overton <james@overton.ca>
 * @version 0.7
 */


/**** GLOBALS ****/

var mozileVersion = "0.7.6";

// Declare the XUL namespace, which is used for the creation of elements in the Mozile toolbar.
var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";





/**** FUNCTIONS ****/

/** Mozile Debug -
 * A basic debugging tool. It logs messages to the mozileDebugArray, but only if their level exceeds the Mozile.getDebugLevel() setting, or they are marked "Status Message".
 * 
 * @param details An array of information. The first two fields are "File", and "Function" (usually a function name). Fancier debugging functions might use more fields. 
 * @param level An integer describing the importance of the debugging message. 4="critical", 3="very important", 2="important", 1="normal", 0="not important".
 * @param message A string containing the debugging message.
 * @return Always true.
 */
function mozileDebug(details, level, message) {
	// If the level is higher than the debug level or the "Status Message" value is true, then save the message.
	if(level >= mozile.getDebugLevel() || details["Status Message"]) {
		var date = new Date();
		// add it to the debugging array
		mozileDebugList.push([date.toLocaleString(), details, level, message]);
	}
	
	return true;
}
// Create the global debug list for this document.
var mozileDebugList = new Array();



/**
 * A centralized event handler, which calls other handlers as needed.
 * @param event The focus event to be handled. 
 * @type Void
 */
function mozileHandleEvent(event) {
	
	switch(event.type) {
		case "keypress":
			mozileHandleKeypress(event);
			break;
		case "keyup":
			mozileHandleKeyup(event);
			break;
		case "focus":
			mozileHandleFocus(event);
			break;
		case "blur":
			mozileHandleBlur(event);
			break;
		case "mouseup":
			mozileHandleMouseup(event);
			break;
	}

}



/** Mozile Handle Focus -
 * Takes appropriate action when a Mozile editor gains focus: shows the caret and toolbar, then sets the currentEditor.
 * 
 * @param event The focus event to be handled. 
 * @return True if successful.
 */
function mozileHandleFocus(event) {
	try{ mozile } catch(e) { return; }
	if(!mozile || !mozile.isEditable()) {
		if(mozile.getOption("activateOnFocus") == true) mozile.startEditing();
		else return;
	}

	mozile.showCaret();
	if(mozileInterface) mozile.showToolbars();
	
	// TOOD: This might not work right with Mozile.makeDocumentEditable()
	if(event.target && event.target.nodeType==1 && document.defaultView.getComputedStyle(event.target, '').getPropertyValue("-moz-user-modify").toLowerCase() == "read-write") {
		mozile.setCurrentEditor(event.target);
		mozileEditor.replaceAnchors();
	}
	
	return;
}

/** Mozile Handle Blur -
 * Takes appropriate action when a Mozile editor loses focus: hide the caret and toobar.
 * 
 * @param event The blur event to be handled. 
 * @return True if successful.
 */
function mozileHandleBlur(event) {
	mozile.hideCaret();
	
	if(mozileInterface) mozile.hideToolbars();
	
	mozileEditor.restoreAnchors();
	
	return true;
}


/** Mozile Handle Keypress -
 * Decides what action to take for the given "keypress" event, and calls the appropriate function. Somewhat complicated. I've tried to optimize it for speed, because it gets called frequently, so it only asks for information just-in-time.
 * 
 * @param event The keypress event to be handled. 
 * @return True when successful, false otherwise.
 */
function mozileHandleKeypress(event) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "mozileHandleKeypress()";
	//mozile.debug(f,1,"Handling keypress event "+ event);
	
	// Ignore the function keys (i.e. F7).
	if(event.keyCode >= 112 && event.keyCode <= 135) return true;
	
	var selection = window.getSelection();
	// Make sure we have a focusNode
	if(!selection.focusNode) return true;

	// Handle arrow keys. When the cursor has moved, update the toolbar. Also, don't let the cursor get stuck against an empty element.
	if(event.keyCode >= event.DOM_VK_PAGE_UP && event.keyCode <= event.DOM_VK_DOWN) {	
		// Handle left and right arrow keys.
		var direction = null;
		if(event.keyCode == event.DOM_VK_LEFT) direction = InsertionPoint.prototype.PREVIOUS;
		if(event.keyCode == event.DOM_VK_RIGHT) direction = InsertionPoint.prototype.NEXT;
		
		if(direction && selection.isCollapsed) {
			var IP = selection.getInsertionPoint();
			IP.seek(direction);
			if(!IP) return false;
			if(event.shiftKey) selection.extend(IP.getNode(), IP.getOffset());
			else selection.collapse(IP.getNode(), IP.getOffset());
			//mozile.status([], 1, selection.focusNode +" "+ selection.focusOffset +"/"+ selection.focusNode.getLength());
			event.stopPropagation();
			event.preventDefault();
		}
		if(mozileInterface && mozile.getOption("toolbarUpdateFrequency") == 2) {
			mozile.updateToolbars();
		}
		return true;
	}
		
		// Check to see if the document and node are editable.
	if(mozile.isEditable() == false) return false;
	if(!event.target || !event.target.isEditable()) return false;

	// Handle Modifiers. Check for keyboard shortcuts triggering commands.
	if((event.ctrlKey || event.metaKey) && mozile.getOption("keyboardShortcuts") && mozile.getAccelerators() != null) {
		var accel = "";
		if(event.metaKey)  accel = accel + "Meta-";
		if(event.ctrlKey)  accel = accel + "Control-";
		if(event.altKey)   accel = accel + "Alt-";
		if(event.shiftKey) accel = accel + "Shift-";
		accel = accel + String.fromCharCode(event.charCode).toUpperCase();
		// mozile.debug(f,0,"Accelerator keypress: "+ accel);
		//alert(accel);
		//dumpArray(mozile.getAccelerators());
		if(mozile.getAccelerator(accel)) {
			mozile.executeCommand(mozile.getAccelerator(accel).getId(), event);
			if(mozileInterface) mozile.updateToolbars();
			// mozile.debug(f,0,"Accelerator command found: "+ mozile.getAccelerators()[accel].id);
				// In the case of copy, don't prevent propagation
			if(mozile.getAccelerator(accel).getId()=="Mozile-Copy") return true;
			event.stopPropagation();
  		event.preventDefault();
			return true;
		}
		else {
			// mozile.debug(f,0,"Accelerator command not found");
			return true;
		}
	}
	
	// Handle delete and backspace keys.
	if(event.keyCode == event.DOM_VK_BACK_SPACE) {
		mozileEditor.deletion(InsertionPoint.prototype.PREVIOUS);
		// mozile.debug(f,0,"Back space keypress");
		event.stopPropagation();
  	event.preventDefault();
		return true;
	}
	if(event.keyCode == event.DOM_VK_DELETE){
		mozileEditor.deletion(InsertionPoint.prototype.NEXT);
		// mozile.debug(f,0,"Delete keypress");
		event.stopPropagation();
  	event.preventDefault();
		return true;
	}
	
	
	
	// Handle all other non-command keystrokes by inserting a string with the value of the character code.
	if(!event.ctrlKey && !event.metaKey && event.keyCode != event.DOM_VK_ENTER && event.keyCode != event.DOM_VK_RETURN && event.keyCode != event.DOM_VK_TAB ) {
		mozileEditor.insertString(String.fromCharCode(event.charCode));
		mozile.keyCounter++;
		if(mozile.keyCounter > mozile.getOption("maxKeyCount")) mozile.storeState("Typing");
		// mozile.debug(f,0,"Non-command keypress "+event.charCode);
		event.stopPropagation();
  	event.preventDefault();
  	return true;
	}
	
	// Check to see if the node uses CSS white-space="pre".
	var whiteSpace = document.defaultView.getComputedStyle(selection.focusNode.parentNode, '').getPropertyValue("white-space").toLowerCase();
	
	// Handle the enter key. If the white-space is "pre", insert a newline, and otherwise split the current block element.
	if(event.keyCode == event.DOM_VK_ENTER || event.keyCode == event.DOM_VK_RETURN){
		if(whiteSpace=="pre") {
			mozileEditor.insertString("\n");
		}
		else {
			mozileEditor.splitBlock();
		}
		mozile.storeState("Enter Key");
		// mozile.debug(f,0,"Enter keypress");
		event.stopPropagation();
		return true;
	}
	// Handle the tab key by inserting a tab. We might add other behaviours later.
	if(event.keyCode == event.DOM_VK_TAB) {
		mozileEditor.insertString("\t");
		mozile.keyCounter++;
		if(mozile.keyCounter > mozile.getOption("maxKeyCount")) mozile.storeState("Typing");
		// mozile.debug(f,0,"Tab keypress");
		event.stopPropagation();
  	event.preventDefault();
		return true;
	}
	mozile.debug(f,1,"Keypress not handled");
	return true;
}

/** Mozile Handle Keyup -
 * Decides what action to take for the given "keyup" event, and calls the appropriate functions: update the toolbar and store the current state for undo/redo.
 * 
 * @param event The keyup event to be handled. 
 * @return True when successful, false otherwise.
 */
function mozileHandleKeyup(event) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "mozileHandleKeyup()";
	//mozile.debug(f,1,"Handling keyup event "+ event);
	
	if(!mozile.isEditable()) return false;
	
	if(mozileInterface) mozile.showToolbars();
	
	// if an arrow key has been release, update the toolbar.
	if(event.keyCode >=33 && event.keyCode <= 40) {
		if(mozileInterface) mozile.updateToolbars();
		return true;
	}
	// Handle delete and backspace keys.
	if(event.keyCode == event.DOM_VK_BACK_SPACE) {
		mozile.storeState("Backspace Key");
		return true;
	}
	if(event.keyCode == event.DOM_VK_DELETE){
		mozile.storeState("Delete Key");
		return true;
	}
	
	return true;
}



/** Mozile Handle Mouseup -
 * Takes appropriate action when Mozile detects a mouseup event: show the caret and the toolbar.
 * 
 * @param event The mouseup event to be handled. 
 * @return True if successful.
 */
function mozileHandleMouseup(event) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "mozileHandleMouseup()";
	mozile.debug(f,1,"Mouseup "+ event);
	
	if(!mozile.isEditable()) return false;

	mozile.showCaret();
	if(mozileInterface) {
		mozile.showToolbars();
		if(mozile.getOption("toolbarUpdateFrequency")==2) mozile.updateToolbars();
	}
	
	return true;
}



/**
 * Checks
 * @param {String} defaultValue The default value.
 * @param value The value to check.
 * @type String
 */
function checkArgument(defaultValue, value) { 
	if(value) {
		return String(value);
	}
	else {
		return defaultValue;
	}
}

/**
 * Executes an XPath expression in the context of the document or a node.
 * @param {String} expression The XPath expression.
 * @param {Node} node Optional. The context within which the expression should be executed. The default is the documentElement.
 * @type Array
 * @return An array of results.
 */
function executeXPath(expression, node) {
	if(!node) node = document.documentElement;
	var XP = new XPathEvaluator;
	var results = XP.evaluate(expression, node, XP.createNSResolver(node.ownerDocument.documentElement), 0, null);
	var nodes = new Array();
	var result = results.iterateNext();
	while(result) {
		nodes.push(result);
		result = results.iterateNext();
	}
	return nodes;
}




/**** OBJECTS ****/


/**
 * A test to see if tis is an X/HTML document. Fairly crude test at the moment.
 * @type Boolean
 */
Document.prototype.isHTML = function() {
	if(document.documentElement.localName.toLowerCase() == "html") return true;
	else return false;
}

/**
 * Returns the "head" element of an HTML document, or the document elment of a general XML document.
 * @type Element
 */
Document.prototype.getHead = function() {
	var head = document.documentElement;
	if(this.isHTML()) head = document.getElementsByTagName("head")[0];
	return head;
}

/**
 * Returns the "body" element of an HTML document, or the document elment of a general XML document.
 * @type Element
 */
Document.prototype.getBody = function() {
	var body = document.documentElement;
	if(this.isHTML()) body = document.getElementsByTagName("body")[0];
	return body;
}


/**
 * A simple convenience function which inserted the newNode after the referenceNode.
 * @param {Node} newNode. The new node to be inserted.
 * @param {Node} refNode. The node which will be the newNode's previousSibling.
 * @type Node
 * @return The inserted node.
 */
Node.prototype.insertAfter = function(newNode, refNode) {
	if(refNode.nextSibling) return this.insertBefore(newNode, refNode.nextSibling);
	else return this.appendChild(newNode);
}

/** 
 * Checks to see if this node has a CSS display property which matches the matchDisplayBlock regular expression (defined globally at the top of this file). Currently, "block", "list-item", "table-cell", and "moz-box" qualify as blocks, but this could change.
 * @type Boolean
 * @return True if the node counts as a block, false otherwise.
 */
Node.prototype.isBlock = function() {
	if(!this._matchDisplayBlock) this._matchDisplayBlock = /(block|list\-item|table\-cell|\-moz\-box)/;
	if(this.nodeType == 1) {		
		var display = document.defaultView.getComputedStyle(this, '').getPropertyValue("display").toLowerCase();	
		if(this._matchDisplayBlock.test(display)) {
			return true;
		}
	}
	return false;
}

/**
 * Climbs toward the root of the DOM tree until it finds an ancestor node for which isBlock is true, or it reaches the documentElement.
 * @type Node
 * @return First node which is an ancestor and which isBlock=true, or the documentElement. In practise this will always be an element.
 */
Node.prototype.getParentBlock = function() {
	var thisNode = this;
	while(thisNode) {
		if(thisNode.isBlock()) {
			return thisNode;
		}
		thisNode = thisNode.parentNode;
	}
	return document.documentElement;
}

/**
 * Climbs the DOM tree until it finds the given node, or the documentElement. 
 * @param {Node} node The node which might be an ancestor of this node.
 * @type Boolean
 * @return True if the given node is an ancestor of this node, false otherwise.
 */
Node.prototype.isAncestorOf = function(node) {
	var thisNode = node;
	while(thisNode) {
		if(thisNode == this) {
			return true;
		}
		thisNode = thisNode.parentNode;
	}
	return false;
}

/** 
 * Returns a simple unique XPath location for this node.
 * @type String
 */
Node.prototype.getXPath = function() {
	if(!this.parentNode) return "";
	else return this.parentNode.getXPath();
}




/** 
 * Returns either the firs or the last insertion point (if any), depending on the given direction.
 * @param {Integer} direction See the special constants in {@link InsertionPoint}. Can be NEXT or PREVIOUS.
 * @type InsertionPoint
 */
Node.prototype.getTerminalInsertionPoint = function(direction) {
	if(direction == InsertionPoint.prototype.PREVIOUS) return this.getLastInsertionPoint(); 
	else return this.getFirstInsertionPoint();
}

/** 
 * Returns the first insertion point in this element, if any.
 * @type InsertionPoint
 */
Node.prototype.getFirstInsertionPoint = function() {
	return null;
}

/** 
 * Returns the last insertion point in this element, if any.
 * @type InsertionPoint
 */
Node.prototype.getLastInsertionPoint = function() {
	return null;
}

/** 
 * Returns the offset of this child within it's parent.
 * @type Integer
 */
Node.prototype.getPosition = function() {
	var parent = this.parentNode;
	if(parent) {
		for(var c=0; c < parent.childNodes.length; c++) {
			if(parent.childNodes[c] == this) return c;
		}
	}
	return null;
}

/** 
 * Checks to see if the node is editable. True of text can be inserted into this node. Checks the parent node.
 * @type Boolean
 */
Node.prototype.isEditable = function() {
	if(this.parentNode) return this.parentNode.isEditable();
	else return false;
}

/** 
 * Gets the length of this node. Defined for elements and text nodes.
 * @type Null
 */
Node.prototype.getLength = function() {
	return undefined;
}


/** 
 * Returns a simple unique XPath location for this node.
 * @type String
 */
Attr.prototype.getXPath = function() {
	if(!this.ownerElement) return "";
	return this.ownerElement.getXPath() +"/@"+ this.nodeName;
}


/** 
 * Returns a simple unique XPath location for this node.
 * @type String
 */
Element.prototype.getXPath = function() {
	var nodeName;
	if(document.isHTML()) nodeName = this.nodeName.toLowerCase();
	else if(this.prefix) nodeName = this.nodeName;
	else nodeName = "xmlns:"+this.nodeName;

	if(this == document.documentElement) return "/"+nodeName;
	if(!this.parentNode) return "";
	var s=1;
	for(var c=0; c < this.parentNode.childNodes.length; c++) {
		if(this.parentNode.childNodes[c] == this) break;
		else if(this.parentNode.childNodes[c].nodeName == this.nodeName) s++;
	}
	return this.parentNode.getXPath() +"/"+ nodeName +"["+ s +"]";
}

/** 
 * Checks to see if the node is editable. True if the "-moz-user-modify" CSS property applies to this element and is set to "read-write".
 * @type Boolean
 */
Element.prototype.isEditable = function() {
	if(document.defaultView.getComputedStyle(this, '').getPropertyValue("-moz-user-modify").toLowerCase() == "read-write") return true;
	else return false;
}

/** 
 * Returns the first insertion point in this element, if any.
 * @type InsertionPoint
 */
Element.prototype.getFirstInsertionPoint = function() {
	if(this.hasChildNodes()) {
		var IP = this.childNodes[0].getFirstInsertionPoint();
		if(IP) return IP;
	}
	else if(this.canHaveChildNodes()) return new InsertionPoint(this, 0);
	return null;
}

/** 
 * Returns the last insertion point in this element, if any.
 * @type InsertionPoint
 */
Element.prototype.getLastInsertionPoint = function() {
	if(this.hasChildNodes()) {
		var IP = this.childNodes[this.childNodes.length-1].getLastInsertionPoint();
		if(IP) return IP;
	}
	else if(this.canHaveChildNodes()) return new InsertionPoint(this, 0);
	return null;
}


Element.prototype._canHaveChildNodesList = new Array("p", "div", "li", "ol", "ul");

/** 
 * True if this element can have children.
 * @type Boolean
 */
Element.prototype.canHaveChildNodes = function() {
	for(var i=0; i < this._canHaveChildNodesList.length; i++) {
		if(this.localName == this._canHaveChildNodesList[i]) return true;
	}
	return false;
}

/** 
 * Gets the number of child nodes for this element.
 * @type Integer
 */
Element.prototype.getLength = function() {
	return this.childNodes.length;
}

/** 
 * Returns a simple unique XPath location for this node.
 * @type String
 */
Text.prototype.getXPath = function() {
	if(!this.parentNode) return "";
	var s=1;
	for(var c=0; c < this.parentNode.childNodes.length; c++) {
		if(this.parentNode.childNodes[c] == this) break;
		else if(this.parentNode.childNodes[c].nodeType == this.TEXT_NODE) s++;
	}
	return this.parentNode.getXPath() +"/text()["+ s +"]";
}

/** 
 * Returns the first insertion point in this element, if any.
 * @type InsertionPoint
 */
Text.prototype.getFirstInsertionPoint = function() {
	return new InsertionPoint(this, 0);
}

/** 
 * Returns the last insertion point in this element, if any.
 * @type InsertionPoint
 */
Text.prototype.getLastInsertionPoint = function() {
	return new InsertionPoint(this, this.textContent.length);
}

/** 
 * Gets the length of the text contents of this node.
 * @type Integer
 */
Text.prototype.getLength = function() {
	return this.textContent.length;
}



/** 
 * Deletes the contents of the first included range.
 * @type Void
 */
Selection.prototype.deleteContents = function() {
	var range = document.createRange();
	range.setStart(this.anchorNode, this.anchorOffset);
		// Check to see if the focusNode is before the anchorNode
	if(range.comparePoint(this.focusNode, this.focusOffset) == -1) {
		range.setStart(this.focusNode, this.focusOffset);
		range.setEnd(this.anchorNode, this.anchorOffset);
	}
	else {
		range.setEnd(this.focusNode, this.focusOffset);
	}
	this.removeAllRanges();
	range.deleteContents();
	this.addRange(range);
}

/** 
 * Stores the selection's key properties in a memento object for later restoration.
 * @type Object
 */
Selection.prototype.store = function() {
	if(!this.anchorNode || !this.focusNode) return null;
	var memento = new Object();
	memento.anchorPath = this.anchorNode.getXPath();
	memento.anchorOffset = this.anchorOffset;
	memento.focusPath = this.focusNode.getXPath();
	memento.focusOffset = this.focusOffset;
	return memento;
}

/** 
 * Restores the selection state from a memento object.
 * @param {Object} memento The output of Selection.store().
 * @type Void
 */
Selection.prototype.restore = function(memento) {
	try {
		var anchorNode = executeXPath(memento.anchorPath)[0];
		var focusNode = executeXPath(memento.focusPath)[0];
		if(!anchorNode.nodeType || !focusNode.nodeType) return;
	}
	catch(e) { return; }
	
	var range = document.createRange();
	range.setStart(anchorNode, memento.anchorOffset);
		// Check to see if the focusNode is before the anchorNode
	if(range.comparePoint(focusNode, memento.focusOffset)==-1) {
		range.setStart(focusNode, memento.focusOffset);
		range.setEnd(anchorNode, memento.anchorOffset);
	}
	else range.setEnd(focusNode, memento.focusOffset);
	
	this.removeAllRanges();
	this.addRange(range);
}

/**
 * Creates a new insertion point from the focusNode and focusOffset.
 * @type InsertionPoint
 */
Selection.prototype.getInsertionPoint = function() {
	if(this.focusNode && this.focusOffset != undefined) return new InsertionPoint(this.focusNode, this.focusOffset);
	else return null;
}



/** 
 * An insertion point is the pair of either a) a text node and an offset within the text, or b) an element and an offset within the childNodes list. This corresponds to the points used by the Selection and Range objects. 
 * @constructor
 * @param {Node} node
 * @param {Integer} offset The offset within the node.
 */
function InsertionPoint(node, offset) {
	/**
	 * Stores the current node.
	 * @private
	 */ 
	this._node = node;

	/**
	 * Stores the current offset.
	 * @private
	 */
	this._offset = offset;
}

/**
 * Constant indicating movement toward the beginning of the document.
 * @type Integer
 */
InsertionPoint.prototype.PREVIOUS = -1;

/**
 * Constant indicating movement toward the end of the document.
 * @type Integer
 */
InsertionPoint.prototype.NEXT = 1;

// Define some regular expressions.
/**
 * Matches whitespace at the beginning of a string.
 * @private
 * @type RegExp
 */
InsertionPoint.prototype._matchLeadingWS = /^(\s*)/;
/**
 * Matches whitespace at the end of a string.
 * @private
 * @type RegExp
 */
InsertionPoint.prototype._matchTrailingWS = /(\s*)$/;
/**
 * Matches any non-whitespace character.
 * @private
 * @type RegExp
 */
InsertionPoint.prototype._matchNonWS = /\S/;

/**
 * Gets the current node.
 * @type Node
 */
InsertionPoint.prototype.getNode = function() { return this._node; }

/**
 * Gets the offset in the current node.
 * @type Integer
 */
InsertionPoint.prototype.getOffset = function() { return this._offset; }

/**
 * Collapses the selection to the current IP.
 * @type Void
 */
InsertionPoint.prototype.select = function() {
	var selection = window.getSelection();
	selection.collapse(this.getNode(), this.getOffset());
}

/**
 * Extends the selection to the IP.
 * @type Void
 */
InsertionPoint.prototype.extend = function() {
	try {
		window.getSelection().extend(this.getNode(), this.getOffset());
	} catch(e) { }
}

/**
 * Sets the node and offset to the next insertion point.
 * @type Void
 */
InsertionPoint.prototype.next = function() {
	this.seek(this.NEXT);
}

/**
 * Sets the node and offset to the previous insertion point.
 * @type Void
 */
InsertionPoint.prototype.previous = function() {
	this.seek(this.PREVIOUS);
}

/**
 * Sets the node and offset to the next insertion point.
 * <p>If the offset is at the end of the node, then the next appropriate ndoe is used.
 * <p>If the node is an element, then the method increments the offset. If the new node has an insertion point, that new IP is used. In this way the method digs down into the child nodes.
 * <p>If the node is a text node, then we have to worry about the XML white space rules. We want to treat adjacent whitespace as a single character. So we measure the length of the whitespace after the offset (if any). Then "moveBy" is set based on the length of the result and the CSS white-space mode. If the length takes the offset to the end of the node, seekNode is called.
 * @param {Integer} direction Can be NEXT or PREVIOUS.
 * @type Void
 */
InsertionPoint.prototype.seek = function(direction) {
	var node = this.getNode();
	var offset = this.getOffset();
	if(!node || offset == undefined) return false;

	if( (direction == this.PREVIOUS && offset == 0) ||
		(direction == this.NEXT && offset == node.getLength()) ) {
		var parentBlock = node.getParentBlock();
		if(this.seekNode(direction)) {
			// when moving between blocks, don't seek again
			if(this.getNode().nodeType == Node.TEXT_NODE &&
				parentBlock != this.getNode().getParentBlock() &&
				this._matchNonWS(this.getNode().textContent) ) 
				return true;
			else 
				return this.seek(direction);
		}
		else return false;
	}
	else offset = offset + direction;
	if(!node || offset == undefined) return false;

	if(node.nodeType == node.ELEMENT_NODE) {
		// Iterate once for NEXT, twice for PREVIOUS
		var j = 1;
		if(direction == this.PREVIOUS) j = 2;
		for(var i=0; i < j; i++) {
			do {
				var child = node.childNodes[offset + i * direction];
				if(child && child.nodeType <= node.TEXT_NODE) break;
				offset = offset + direction;
			} while(child);
			if(child) {
				// Try to enter this node.
				var IP = child.getTerminalInsertionPoint(direction);
				if(IP) {
					node = IP.getNode();
					offset = IP.getOffset();
					break;
				}
			}
		}
	}
	
	else if(node.nodeType == node.TEXT_NODE) {
		// Handle whitespace
		var content = node.textContent;
		var substring, result;
		if(direction == this.NEXT) {
			substring = content.substring(this.getOffset(), content.length);
			result = this._matchLeadingWS(substring);
		}
		else {
			substring = content.substring(0, this.getOffset());
			result = this._matchTrailingWS(substring);
		}
		
		var moveBy = 0;
		if(result[0].length < 2) moveBy = direction;
		else if(document.defaultView.getComputedStyle(node.parentNode, '').getPropertyValue("white-space").toLowerCase() == "pre") moveBy = direction;
		else if(result[0].length < substring.length) moveBy = result[0].length * direction;
		else if(result[0].length == substring.length) {
		  return this.seekNode(direction);
		}
		else throw Error("Unhandled case in InsertionPoint.seek()");
		offset = this.getOffset() + moveBy;
	}
	
	// Other cases, like comments
	else {
		return this.seekNode(direction);
	}

	this._node = node;
	this._offset = offset;
	return true;
}

/**
 * Seeks the next appropriate node and sets the IP's node and offset. An "appropriate" node in an element or a text node. Siblings are checks first, and if none are found the method climbs to the parentNode.
 * @param {Integer} direction Can be NEXT or PREVIOUS.
 * @type Void
 */
var globalList = new Array();
InsertionPoint.prototype.seekNode = function(direction) {
	var node = this.getNode();
	if(!node) return false;
	var offset, lastNode;
		globalList.push("new");
	while(node) {
		globalList.push(node.nodeName);
		lastNode = node;
		if(direction == this.NEXT) node = node.nextSibling;
		else node = node.previousSibling;
	
		if(node) {
			// Try to find an insertion point
			var IP = node.getTerminalInsertionPoint(direction);
			if(IP) {
				this._node = IP.getNode();
				this._offset = IP.getOffset();
				return true;
			}
			else if(node.nodeType == node.ELEMENT_NODE) {
				this._node = node.parentNode;
				this._offset = node.getPosition();
				if(direction == this.PREVIOUS) this._offset++; // special case
				return true;
			}
		}
		// No more siblings, so climb up the tree
		else {
			globalList.push("up");
			node = lastNode.parentNode;
		}
	}
	return false;
}


/** Mozile Component -
 * Class defining Mozile components with subclasses for modules, commands, and the mediator.
 * <p>Configuration String format (some options may conflict): "root='path/to/mozile', mode=XHTML, namespace='http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd', keyboardShortcuts=true, toolbarPosition=fixed, toolbarUpdateFrequency=2, warnBeforeUnload=true, debugLevel=0". See the notes for "Mozile.parseConfig" for proper configuration string format.
 * @constructor
 * @param {String} configString A properly formatted configuration string.
 */
function MozileComponent(configString) {

	/**
	 * @private
	 * @type String
	 */
	this._configString = String(configString);
}

/**
 * Returns "[object MozileComponent]".
 * @type String
 */
MozileComponent.prototype.toString = function() {
	return "[object "+ this.constructor.toString().match(/^function\s+(\w+)/)[1] +"]";
}

/**
 * Get the name from the config string. It will be the first word.
 * @type String
 * @return The resource name. 
 */
MozileComponent.prototype.getName = function() { 
	if(!this._name) {
			// Determine Name
		var firstWord = /\s*(\w*)/;
		var name = firstWord.exec(this.getConfigString())[1];
		if(name) {
			/**
			 * @private
			 * @type String
			 */
			this._name = name;
		}
		else {
			throw Error("Invalid configuration string.");
		}
	}
	return this._name; 
}

/**
 * @type String
 * @return The configuration string. 
 */
MozileComponent.prototype.getConfigString = function() { return this._configString; }

/**
 * @type Object
 * @return The options object -- treat as a named key-value array or a hash table. 
 */
MozileComponent.prototype.getOptions = function() { 
	if(this._options) return this._options;
	else return this._parseOptions();
}

/**
 * Gets a value from the options for a given key
 * @param {String} key
 * @param defaultValue Optional. If this is given and the key does not exist then defaultValue is assigned and returned.
 * @type String
 * @return The value if found or the defaultValue if given. Otherwise undefined.
 */
MozileComponent.prototype.getOption = function(key, defaultValue) { 
	if(this.getOptions()[key] != undefined) return this.getOptions()[key];
	else if(defaultValue != undefined) return this.setOption(key, defaultValue);
	else return undefined;
}

/**
 * Gets a value from the options for a given key
 * @param {String} key
 * @param value
 * @return The value given.
 */
MozileComponent.prototype.setOption = function(key, value) {
	this.getOptions()[key] = this._cleanOption(value);
	return value;
}


/**
 * Tries to convert a string into a number or boolean.
 * @param {String} key
 * @return The converted value.
 */
MozileComponent.prototype._cleanOption = function(val) {
	if(String(val).toLowerCase() == "false") val = false;
	else if(String(val).toLowerCase() == "true") val = true;
	else if(!isNaN(Number(val))) val = Number(val);
	return val;
}

/** 
 * Parses the configuration string into an option array.
 * @private
 * @param {String} optionString The option string, following the standard format for module or command options (see "mozile.js" under "Declare Modules", or the Mozile.createCommand() method in this file).
 * @return A module options array.
 * @type Array
 */
MozileComponent.prototype._parseOptions = function() {
	var options = new Object();
	this._options = options;
	
		// If there is no colon there are no options, so return.
	if(this.getConfigString().indexOf(":")==-1) {
		return this._options;
	}
	
	var optionString = this.getConfigString().substring(this.getConfigString().indexOf(":")+1, this.getConfigString().length);
	
		// get arrays, which have the format "name=[val1, 'val2', val3]"
	var arrayPattern = /(\S+)=\[(.*?)\]/;
	var leading = /^\s*'?/;
	var trailing = /'?\s*$/;
	while(optionString.match(arrayPattern)) {
		optionString = optionString.replace(arrayPattern, 
			function(word) {
				var whole = word.match(arrayPattern);
				if(whole.length == 3) {
					var results = new Array();
					var parts = whole[2].split(",");
					var part;
					for(var p=0; p<parts.length; p++) {
						part = parts[p].replace(leading, '').replace(trailing, '');
						if(part != "") results.push( MozileComponent.prototype._cleanOption(part));
					}
					options[whole[1]] = results;
				}
				return "";
			}
		);
	}
	
	var optionArray = optionString.split(",");
	
	// Now parse each option into key-value pairs, and add them to the options as options[key]=value.
	// This regular expression matches two formats: with='spa ces' and without=spaces.
	var parseOption = /(\S+)='(.+)'|(\S+)=(\S+)/;
	var option, arr;
	for(o in optionArray) {
		option = optionArray[o];
		arr = parseOption.exec(option);
		if(arr) {
			var key,val;
			if(!arr[1] && !arr[2]) key=arr[3],val=arr[4];
			else key=arr[1],val=arr[2];
			options[key] = this._cleanOption(val);
		}
	}

	return this._options;
}









/** Mozile Module -
 * Subclass of MozileComponent designed for manipulating modules. 
 * @constructor
 * @param {String} configString A properly formatted configuration string.
 */
function MozileModule(configString) {

	/**
	 * @private
	 * @type String
	 */
	this._configString = String(configString);

}

MozileModule.prototype = new MozileComponent;
MozileModule.prototype.constructor = MozileModule;

/**
 * Gets the id for the module, generating it if necessary.
 * @type String
 */
MozileModule.prototype.getId = function() {
	if(!this._id) this._id = "Mozile-"+ this.getName() +"-"+ this.getName() +".js";
	return this._id;
}

/**
 * Gets the path to the module, generating it if necessary.
 * @type String
 */
MozileModule.prototype.getPath = function() {
	if(!this._path) {
		var path="";
		if(this.getOption("remotePath")) path = this.getOption("remotePath");
		else path = mozile.getRoot() +"modules/"; 
		path = path + this.getName();
		if(this.getOption("remoteVersion")) path = path +"-"+ this.getOption("remoteVersion");
		this._path = path +"/";
	}
	return this._path;
}

/**
 * Gets the source to the module, generating it if necessary.
 * @type String
 */
MozileModule.prototype.getSource = function() {
	if(!this._source) this._source = this.getPath() + this.getName() +".js";
	return this._source;
}

/**
 * Loads the module's main script, and registers the module with the Mozile object.
 * @type Void
 */
MozileModule.prototype.load = function() {
	var script = new MozileScriptResource(this.getId(), this.getSource());
	script.load();
		// Register the module with the global mediator
	mozile.addModule(this);
	this.setOption("version", "Unknown Version");
}

/**
 * Initializes the module.
 * @type Void
 */
MozileModule.prototype.init = function() { }






/** Mozile Mediator -
 * Coordinates the behaviour of the other objects. Follows the Mediator design pattern. The Mozile code base relies on there being a single global instance of this object called "mozile".
 * <p>Configuration String format (some options may conflict): "root='path/to/mozile', mode=XHTML, namespace='http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd', keyboardShortcuts=true, toolbarPosition=fixed, toolbarUpdateFrequency=2, warnBeforeUnload=true, debugLevel=0". See the notes for "Mozile.parseConfig" for proper configuration string format.
 * @constructor
 * @param {String} configString A properly formatted configuration string.
 */
function MozileMediator(configString) {

	/**
	 * @private
	 * @type String
	 */
	this._configString = String("MozileMediator: "+configString);

	/**
	 * True if editing is ON, false if editing is OFF.
	 * @private
	 * @type Boolean
	 */
	//this._editable = false;
	
	/** Mozile - Key Counter -
	 * Counts the number of keypresses which lead to character insertion. Used to trigger storeState undo/redo steps.
	 * @type Integer
	 */
	this.keyCounter = 0;
	
	/** Mozile - Changes Saved -
	 * This is true after any of the output methods (documentToXML, etc.) have been called, and false after any other command.
	 */
	this.changesSaved = true;
	
	// Define some default options
	this.getOption("mode", "XHTML");
	this.getOption("toolbarPosition", "absolute");  // can be "absolute" or "fixed"
	this.getOption("maxKeyCount", 20);
	this.getOption("toolbarUpdateFrequency", 2);
	this.getOption("hideInactiveCommands", false);
	this.getOption("defaultInterval", 100);
	this.getOption("keyboardShortcuts", true);
	this.getOption("preloaded", false);
	this.getOption("loadJIT", false);
	this.getOption("activateOnFocus", true);
	this.getOption("replaceAnchors", true);
		// Save option defaults
	this.getOption("content", "document");  // can be "document" or "editor"

	// Set up the onbeforeunload event handler. If warnBeforUnload has been set to "false", then the function returns nothing. Otherwise (the default case) a message is returned.
	if(this.getOption("warnBeforeUnload", true)) {
		window.onbeforeunload = function() {
			if(!mozile.changesSaved) return "There are unsaved changes in this document. Changes will be lost if you navigate away from this page.";
			else return null;
		}
	}
	else {
		window.onbeforeunload = function() { return; }
	}
	
	if(!this.isExtension()) {
		this.setSharedData("enhancement", "false");
		this.setSharedData("editable", "false");
		this.setSharedData("activateOnFocus", this.getOption("activateOnFocus"));
	}

	/**
	 * @private
	 */
	this._interfaces = new Array(
		{name: "MozileAboutInterface", access: "mozile.getAboutInterface()", source: "core/about.xml"}, 
		{name: "MozileMessageInterface", access: "mozile.getMessageInterface()", source: "core/message.xml"}, 
		{name: "MozileSaveInterface", access: "mozile.getSaveInterface()", source: "core/save.xml"}, 
		{name: "MozileSourceInterface", access: "mozile.getSourceInterface()", source: "core/source.xml"}, 
		{name: "HTTPPostInterface", access: "mozile.getModule('HTTPPost').getInterface()", source: "modules/HTTPPost/savemsg.xml"} 
		);

}

MozileMediator.prototype = new MozileComponent;
MozileMediator.prototype.constructor = MozileMediator;
MozileMediator.prototype.debug = mozileDebug;



/**
 * @type String
 */
MozileMediator.prototype.getVersion = function() {
	return mozileVersion;
}

/**
 * The root directory for this Mozile installation. This file "core.js" should be found at "[root]core/core.js".
 * @type String
 */
MozileMediator.prototype.getRoot = function() {
	if(!this.getOption("root")) throw Error("Invalid configuration string.");
	return this.getOption("root");
}

/**
 * Returns true if this object is running in the Mozile Extension, and false otherwise.
 * @type Boolean
 */
MozileMediator.prototype.isExtension = function() {
	if(this._extension == undefined) {
		try { if(mozileExtension) this._extension = true; }
		catch(e) { this._extension = false; }
	}
	return this._extension;
}

/**
 * Returns true if the Mozile Extension is enhancing this object, and false otherwise. Can also check for a particular enhancement.
 * @param {String} name Optional. The name of the enhancement to be checked.
 * @type Boolean
 */
MozileMediator.prototype.isEnhanced = function(name) {
	if(this.getSharedData("enhancement")==undefined) return false;
	else if(name && typeof(this.getSharedData("enhancement"))=="string") {
		var enhancements = this.getSharedData("enhancement").split(";");
		for(var i=0; i < enhancements.length; i++) {
			if(enhancements[i] == name) return true;
		}
		return false;
	}
	else return this.getSharedData("enhancement");
}

/**
 * The mode controls which tools are used to manipulate the document. The options are "HTML", "XHTML", and "XML". Each of the three modes has its quirks.
 * TODO: Currently, Mozile makes no use of the mode.
 * @type String
 */
MozileMediator.prototype.getMode = function() {
	return this.getOption("mode");
}

/**
 * An integer indicating how verbose debugging should be. 0 means only critical errors are shown. Higher values mean less verbose debugging: 4="critical", 3="very important", 2="important", 1="normal", 0="not important". Only messages with the specified level or higher will be logged.
 * @type Integer
 */
MozileMediator.prototype.getDebugLevel = function() {
	return this.getSharedData("debugLevel", this.getOption("debugLevel", 4));
}



/** 
 * Get an attribute from the core.js script tag.
 * @param {String} attribute The attribute name.
 * @type String
 */
MozileMediator.prototype.getSharedData = function(attribute, defaultValue) {
	if(document.getElementById("Mozile-Core-core.js") && document.getElementById("Mozile-Core-core.js").hasAttribute(attribute)) {
		var value = document.getElementById("Mozile-Core-core.js").getAttribute(attribute);
		return this._cleanOption(unescape(value));
	}
	else if(defaultValue != undefined) return this.setSharedData(attribute, defaultValue);
	else return undefined;
}

/** 
 * Set an attribute from the core.js script tag. 
 * @param {String} attribute
 * @param {String} value
 * @type String
 */
MozileMediator.prototype.setSharedData = function(attribute, value) {
	if(!document.getElementById("Mozile-Core-core.js")) return value;
	if(value=="") document.getElementById("Mozile-Core-core.js").removeAttribute(attribute);
	else document.getElementById("Mozile-Core-core.js").setAttribute(attribute, escape(value));
 	return value;
}

/** 
 * Checks for changes to the attributes.
 * @type Void
 */
MozileMediator.prototype.watchSharedData = function() {
 	if(!mozile._watches) {
 		mozile._watches = new Object();
 		mozile._watches["editable"] = {value: undefined, ontrue: "mozile.startEditing()", onfalse: "mozile.stopEditing()"};
 		mozile._watches["activateOnFocus"] = {value: undefined, onchange: "mozile.setOption('activateOnFocus', value)"};
 		mozile._watches["serverRequest"] = {value: undefined, onchange: "eval(value); this.setSharedData('serverRequest', '')"};
 	}
 	var value;
 	var watches = mozile._watches;
 	for(key in watches) {
 		if(!watches[key]) continue;
 		value = mozile.getSharedData(key);
 		if(value != watches[key]["value"]) {
 			try {
 				if(value==null) { /* do nothing */ }
				else if(value == "true" && watches[key]["ontrue"]) eval(watches[key]["ontrue"]);
				else if(value == "false" && watches[key]["onfalse"]) eval(watches[key]["onfalse"]);
				else if(watches[key]["onchange"]) eval(watches[key]["onchange"]);
			} catch(e) { }
				// Key might have been erased
			if(watches[key]) watches[key]["value"]	= value;
 		}
 	}
}


/** 
 * If Mozile is enhanced, sends a request to the MozileExtension.
 * @param {String} requestCode Only a few request types are handled: "getClipboard", "setClipboard".
 * @type Void
 */
MozileMediator.prototype.clientRequest = function(requestCode, evalOnChange) {
	if(!mozile.isEnhanced()) return;
	
	this.clearRequest();
	this._watches["serverReply"] = {value: "", onchange: "try{"+evalOnChange+"}catch(e){};mozile.clearRequest()" }
	this.setSharedData("clientRequest", requestCode);
}

/** 
 * Clears the shared data attributes relevat to client requests.
 * @type Void
 */
MozileMediator.prototype.clearRequest = function() {
	mozile._watches["serverReply"] = undefined;
	mozile.setSharedData("serverReply", "");
	mozile.setSharedData("clientRequest", "");
}

/** 
 * An associative array containing all the MozileResource objects for scripts, links, and styles.
 * <p>TODO: What should be done about core.js and interface.js?
 * @type Object
 */
MozileMediator.prototype.getResources = function() {
 	if(!this._resources) this._resources = new Object();
 	return this._resources;
}

/** 
 * Gets a resource from the list by its id.
 * @param {String} id
 * @type MozileResource
 */
MozileMediator.prototype.getResource = function(id) {
 	if(this.getResources()[id]) return this.getResources()[id];
 	else return undefined;
}

/** 
 * Adds a resource to the resources list.
 * @param {MozileResource} resource
 * @type MozileResource
 * @return The resource object given.
 */
MozileMediator.prototype.addResource = function(resource) {
	if(resource.getId()) this.getResources()[resource.getId()] = resource;
	else throw Error("Invalid resource.");
	return resource;
}

/** 
 * An associative array containing all the loaded module objects, indexed by their names. 
 * @type Object
 */
MozileMediator.prototype.getModules = function() {
 	if(!this._modules) this._modules = new Object();
 	return this._modules;
}

/** 
 * Gets a module from the list by its name.
 * @param {String} name
 * @type MozileModule
 */
MozileMediator.prototype.getModule = function(name) {
 	if(this.getModules()[name]) return this.getModules()[name];
 	else return undefined;
}

/** 
 * Adds a module to the modules list.
 * @param {MozileModule} module
 * @type MozileModule
 * @return The module object given.
 */
MozileMediator.prototype.addModule = function(module) {
	if(module.getName()) this.getModules()[module.getName()] = module;
	else throw Error("Invalid module.");
	return module;
}

/** 
 * An array containing entries for every editor in the document.
 * @type Array
 */
MozileMediator.prototype.getEditors = function() {
 	if(!this._editors) this._editors = new Array();
 	return this._editors;
}

/** 
 * Returns true if the given element is in the editors array.
 * @type Boolean
 */
MozileMediator.prototype.isEditor = function(element) {
	for(var i=0; i < this.getEditors().length; i++) {
		if(this.getEditors()[i] == element) return true;
	}
	return false;
}

/** 
 * Adds an element to the editors list.
 * @param {Element} editor
 * @type Element
 * @return The element given.
 */
MozileMediator.prototype.addEditor = function(editor) {
	this.getEditors().push(editor);
	return editor;
}


/** 
 * Get the element of the editor which last had the focus.
 * @type Element
 */
MozileMediator.prototype.getCurrentEditor = function() {
	if(this._currentEditor) return this._currentEditor;
	else return undefined;
}

/** 
 * Sets the current editor.
 * @param {Element} element The root element of the current editor.
 * @type Element
 */
MozileMediator.prototype.setCurrentEditor = function(element) {
	this._currentEditor = element;
	return element;
}

/** 
 * Gets the CSSStyleSheet object which is manipulated to add the XBL bindings which control the Mozile toolbar and the Mozile editors. Adds the stylesheet if needed.
 * @type CSSStyleSheet
 */
MozileMediator.prototype.getStyleSheet = function() {
	if(!this._styleSheet) {
		var style = new MozileStyleResource("Mozile-Core-StyleSheet");
		style.load();
		this._styleSheet = style.getStylesheet();

		// Add the rules to the sheet.
		this._styleSheet.insertRule("mozileAnchorReplacement { color: blue; text-decoration: underline; }", this._styleSheet.cssRules.length);
	}	
	return this._styleSheet;
}


/** 
 * Mozile checks the UserAgent string for the browser, and tries to determine what operating system the browser is running under. Can be "Linux", "Windows", or "Mac". Note that the UserAgent can be spoofed, so this is not entirely reliable.
 * @type String
 */
MozileMediator.prototype.getOperatingSystem = function() {
	if(!this._operatingSystem) {
		var userAgent = navigator.userAgent.toLowerCase();
		if(userAgent.indexOf("windows") >= 0) this._operatingSystem = "Windows";
		if(userAgent.indexOf("linux") >= 0) this._operatingSystem = "Linux";
		if(userAgent.indexOf("macintosh") >= 0) this._operatingSystem = "Mac";
	}
	return this._operatingSystem;
}

/** 
 * Gets the name of the browser. Can be "Firefox", "SeaMonkey", or "Mozilla" (which is the default).
 * @type String
 */
MozileMediator.prototype.getBrowserName = function() {
	if(!this._browserName) {
		var userAgent = navigator.userAgent.toLowerCase();
		if(userAgent.indexOf("firefox") >= 0) this._browserName = "Firefox";
		else if(userAgent.indexOf("seamonkey") >= 0) this._browserName = "SeaMonkey";
		else this._browserName = "Mozilla";
	}
	return this._browserName;
}

/** 
 * Gets the version number for the browser.
 * @type String
 */
MozileMediator.prototype.getBrowserVersion = function() {
	if(!this._browserVersion) {
		var userAgent = navigator.userAgent;
		if(this.getBrowserName()=="Firefox") this._browserVersion = userAgent.match(/Firefox\/(\S+)/)[1];
		else if(this.getBrowserName()=="SeaMonkey") this._browserVersion = userAgent.match(/SeaMonkey\/(\S+)/)[1];
		else this._browserVersion = this.getMozillaVersion();
	}
	return this._browserVersion;
}

/** 
 * Gets the Gecko version for the browser.
 * @type String
 */
MozileMediator.prototype.getGeckoVersion = function() {
	if(!this._geckoVersion) {
		this._geckoVersion = navigator.userAgent.match(/Gecko\/(\S+)/)[1];
	}
	return this._geckoVersion;
}

/** 
 * Gets the version of Mozilla on which the browser is based.
 * @type String
 */
MozileMediator.prototype.getMozillaVersion = function() {
	if(!this._mozillaVersion) {
		this._mozillaVersion = navigator.userAgent.match(/rv\:(\S+)\)/)[1];
	}
	return this._mozillaVersion;
}

/** 
 * "true" if editing has been started, "false" otherwise.
 * @type Boolean
 */
MozileMediator.prototype.isEditable = function() {
	return this.getSharedData("editable");
	// return this._editable;
}

/** 
 * Enables editing in the document.
 * @type Void
 */
MozileMediator.prototype.startEditing = function() {
	if(!this.isEditable()) {
		//this._editable = true;
		this.setSharedData("editable", "true");
		this.load();
		this.showCaret();
		this.showToolbars();
		this.storeState("Editing started");
	}
}

/** 
 * Disables editing in the document.
 * @type Void
 */
MozileMediator.prototype.stopEditing = function() {
	if(this.isEditable() != false) {
		mozileEditor.restoreAnchors();
		this.hideCaret();
		this.hideToolbars();
		//this._editable = false;
		this.setSharedData("editable", "false");
	}
}




/** Mozile - Status -
 * Sets the content of the Mozile statusbar and enters a special kind of debugging message. In addition to the normal debug arguments, status messages can include a "value" argument which indicates the percentage displayed on the progress bar (if none is given or the value is "false" then the progress bar is hidden). They can also include a "more" string, which will be entered into the "oncommand" attribute of the mozileMoreButton; this is usually used to popup a window or dialog with additional information.
 * 
 * @param details An array of information. The first two fields are "File", and "Function" (the a function name). Fancier debugging functions might use more fields. 
 * @param level An integer describing the importance of the debugging message. 4="critical", 3="very important", 2="important", 1="normal", 0="not important".
 * @param message A string containing the debugging message.
 * @param value Optional An integer between 0 and 100 indicating the value of the progressmeter. If no value is provided or the value is "false", the progressmeter is not shown.
 * @param more Optional A string which is placed in the "oncommand" attribute of the "mozileMoreButton". If no string is provided, the button is not shown.
 * @return Always true.
 */
MozileMediator.prototype.status = function(details, level, message) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile.status()";
	//this.debug(f,1,"Setting status");
	try {
		var value = false;
		if(arguments.length > 3) {
			value = arguments[3];
		}
		var more = false;
		if(arguments.length > 4) {
			more = arguments[4];
		}
		//this.debug(f,1,"Setting status to: "+ message +" -> "+ value +"% -> "+ more);
	
		// Send this message to the debug function.
		var g = new Array();
		for(key in details) {
			g[key] = details[key];
		}
		g["Status Message"]=true;
		var msg = message;
		if(value) msg = msg +" "+ value +"%";
		this.debug(g, level, msg);

		// If the statusbar doesn't exist, just return now.
		//if(!this._statusbar) return true;
		var messageField = document.getElementById("mozileStatusMessage");
		var progressmeter = document.getElementById("mozileProgressmeter");
		var moreButton = document.getElementById("mozileMoreButton");
		if(!messageField || !progressmeter || !moreButton) return true;
		messageField.value = message;
		// If "value" is set, show the progressmeter and set its value.	
		if(value != false) {
			progressmeter.value = value;
			progressmeter.collapsed=false;
		}
		// Otherwise hide the progresmeter and zero it.
		else {
			progressmeter.value = 0;
			progressmeter.collapsed=true;
		}
		// If "more" is set, show the moreButton and set the oncommand attribute.
		// should be "oncommand" but thestatusbar tends to disappear too fast...	
		if(more != false) {
			moreButton.setAttribute("onmousedown", more);
			moreButton.collapsed = false;
		}
		// Otherwise hide it and clear the oncommand attribute.
		else {
			moreButton.setAttribute("onmousedown", "");	
			moreButton.collapsed = true;
		}
	}
	catch(e) {
		alert(e);
		//this.debug(f,2,"Failed to set status message: "+ details["File"] +", "+ details["function"] +", "+ message);
	}
	return true;
}



/** 
 * Activates the editing caret. It requires privileges to set the "browsewithcaret" preference. When there are no privileges the "editing" shared data attribute is set instead, and the Mozile Extension will activate the caret if it is present.
 * @type Boolean
 */
MozileMediator.prototype.showCaret = function() {
	if(this.isExtension()) {
		if(!this._mozilePrefs) {
			netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
			this._mozilePrefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch(null);	
		}
		this._mozilePrefs.setBoolPref('accessibility.browsewithcaret', true);
		return true;
	}
	else {
		this.setSharedData("editing", "true");
		return false;
	}
}


/** 
 * Disables the editing caret unless the "caretAlwaysOn" preference is set.
 * @type Boolean
 */
MozileMediator.prototype.hideCaret = function() {
	if(this.isExtension()) {
		if(!this._mozilePrefs) {
			netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
			this._mozilePrefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch(null);	
		}
		if(!this._mozilePrefs.getBoolPref("mozile.caretAlwaysOn")) {
			this._mozilePrefs.setBoolPref('accessibility.browsewithcaret', false);
		}
		return true;
	}
	else {
		this.setSharedData("editing", "false");
		return false;
	}
}



/** Mozile - Create Editor -
 * Creates a Mozile editor in the document using its id. Two CSS rules are added to this.styleSheet, the first of which binds an XBL widget to the element and captures the focus, while the second tells all children to ignore the focus.
 * 
 * @param id String. The id of the element to be made an editor. 
 * @param options String. A list of options which will be used in this editor. TODO: No options are currently implemented.
 * @return True if successful.
 */
MozileMediator.prototype.createEditor = function(id, options) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile.createEditor()";
	this.debug(f,1,"Creating editor "+ id +" "+ options);
	
	
	// Define the new rules.
	var rule1 = "#"+ id +" { -moz-binding: url(" + this.getRoot() +"core/core.xml#editor); -moz-user-focus: normal; -moz-user-modify: read-write; -moz-user-input: auto; -moz-user-select: text; }";
	var rule2 = "#"+ id +" * { -moz-user-focus: ignore; }";
	var rule3 = "#"+ id +" input { -moz-user-focus: none; -moz-user-modify: read-only; -moz-user-input: auto; -moz-user-select: text; }";
	
	// Add the rules.
	this.getStyleSheet().insertRule(rule1, this.getStyleSheet().cssRules.length);
	this.getStyleSheet().insertRule(rule2, this.getStyleSheet().cssRules.length);
	this.getStyleSheet().insertRule(rule3, this.getStyleSheet().cssRules.length);
	return true;
}


/** Mozile - Create Editors -
 * Creates multiple Mozile editors in the document, using a CSS selector.
 * 
 * @param selector String. The id of the element to be made an editor. 
 * @param options String. A list of options which will be used in this editor. TODO: No options are currently implemented.
 * @return True if successful.
 */
MozileMediator.prototype.createEditors = function(selector, options) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile.createEditors()";
	this.debug(f,1,"Creating editor "+ selector +" "+ options);
	
	
	// Define the new rules
	var rule1 = selector +" { -moz-binding: url(" + this.getRoot() +"core/core.xml#editor); -moz-user-focus: normal; -moz-user-modify: read-write; -moz-user-input: auto; -moz-user-select: text; }";
	var rule2 = selector +" * { -moz-user-focus: ignore; }";
	var rule3 = selector +" input { -moz-user-focus: none; -moz-user-modify: read-only; -moz-user-input: auto; -moz-user-select: text; }";
	// Add the rules to the sheet.
	this.getStyleSheet().insertRule(rule1, this.getStyleSheet().cssRules.length);
	this.getStyleSheet().insertRule(rule2, this.getStyleSheet().cssRules.length);
	this.getStyleSheet().insertRule(rule3, this.getStyleSheet().cssRules.length);
	return true;
}


/** Mozile - Make Document Editable -
 * Makes the entire document editable. Instead of using XBL bindings on elements selected by CSS, this function creates a number of document-wide eventListeners.
 * 
 * @param options String. A list of options which will be used in this editor. TODO: No options are currently implemented.
 * @return True if successful.
 */
MozileMediator.prototype.makeDocumentEditable = function(options) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile.makeDocumentEditable()";
	this.debug(f,1,"Making document editable "+ options);
	
	//this.createEditors("body > center","");
	//return true;
	
	var events = ["focus", "blur", "keydown", "keypress", "keyup", "mousedown", "click", "mouseup", "dblclick", "select"];
	for(var e=0; e< events.length; e++) {
		document.addEventListener(events[e], mozileHandleEvent, false);
	}
	
	// Get the root element name
	var rootNode = document.getBody();
	var rootName = rootNode.nodeName;
	
	
	// Create the CSS rules
	var rule1 = rootName +" { -moz-user-focus: normal; -moz-user-modify: read-write; -moz-user-input: auto; -moz-user-select: text; }";
	var rule2 = rootName +" * { -moz-user-focus: ignore; }";
	var rule3 = rootName +" input { -moz-user-focus: none; -moz-user-modify: read-only; -moz-user-input: auto; -moz-user-select: text; }";
	
	// Apply the CSS rules to the sheet.
	this.getStyleSheet().insertRule(rule1, this.getStyleSheet().cssRules.length);
	this.getStyleSheet().insertRule(rule2, this.getStyleSheet().cssRules.length);
	this.getStyleSheet().insertRule(rule3, this.getStyleSheet().cssRules.length);
	
	this.addEditor(rootNode);
	rootNode.setAttribute('tabindex',12345);
	
	return true;
}


/** 
 * Removes any interfaces which may have been injected into the document by the Mozile Extension.
 * @type Void
 */
MozileMediator.prototype._cleanInterfaces = function() {
	var element;
	for (var i=0; i < this._interfaces.length; i++) {
		try { eval(this._interfaces[i]["access"] +".hide()"); }
		catch(e) {
			element = document.getElementById(this._interfaces[i]["name"]);
			if(element) element.parentNode.removeChild(element); 
		}
	}
}



/** 
 * The XML declaration for the document, if there is one.
 * @private
 * @type String
 */
MozileMediator.prototype._getXMLDeclaration = function() {
	var xmlDeclaration = "";
	if(document.xmlVersion) {
		xmlDeclaration = '<?xml version="'+ document.xmlVersion +'" encoding="'+ document.xmlEncoding +'"?>\n'
	}
	return xmlDeclaration;
}

/** 
 * The DOCTYPE declaration for the document, if there is one.
 * @private
 * @type String
 */
MozileMediator.prototype._getDoctypeDeclaration = function() {
	// Get the DOCTYPE if there is one, and serialize it.
	var doctypeDeclaration = ""
	if(document.doctype) {
		var serializer = new XMLSerializer;
		doctypeDeclaration = serializer.serializeToString(document.doctype) +"\n";
	}
	return doctypeDeclaration;
}

/** 
 * The Processing Instructions for the document, if there are any.
 * @private
 * @type String
 */
MozileMediator.prototype._getProcessingInstructions = function() {
	var	evaluator =	new	XPathEvaluator();
	var PIString = "";
	var PIList = evaluator.evaluate("/processing-instruction()", document, null, XPathResult.ANY_TYPE, null);
	var PI = PIList.iterateNext();
	while (PI) {
		PIString += "<?"+ PI.target +" "+ PI.data + "?>\n";
		PI = PIList.iterateNext();
	}
	return PIString;
}

/** 
 * Removes all traces of Mozile in the document. It does this by removing all of the resources on the resourceList, the toolbar element, and the "tabindex" attributes of all elements with tabindex=12345. It's meant to act on a clone of the document element, and not the document itself (which would disable Mozile in the process).
 * 
 * @param {Element} element The root element from which all other elements will be removed.
 * @type Element
 * @return The cleaned up element.
 */
MozileMediator.prototype._cleanDOM = function(element) {
	// Unload all resources.
	for(var id in this.getResources()) {
		this.getResource(id).unload(element);
	}
	
		// Fix any altered anchor tags.
	mozileEditor.restoreAnchors(element);

	// Get rid of any MozileToolbar elements.
	var mozileToolbars = element.getElementsByTagName("mozileToolbar");
	while(mozileToolbars.length){
		mozileToolbars[0].parentNode.removeChild(mozileToolbars[0]);
	}

	/** Walk the tree...
	 * - Remove any "mozileInterface" elements (which might have been injected by the Extension but never used).
	 * - Remove "MozileToolbar" or "MozileStatusbar" if found.
	 * - Clean up the "tabindex" attributes that we've added. This is an effective but inefficient method: use a treewalker over elements, and check for a "tabindex" matching the (hopefully unique) code 12345.
	 */
	var treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT, null, false);
	var current = treeWalker.firstChild();
	var removals = new Array();
	while(current) {
		if(current.hasAttribute("class") && current.getAttribute("class") == "mozileInterface") removals.push(current); // mark for removal
		else if(current.hasAttribute("id") && current.getAttribute("id") == "MozileToolbar") removals.push(current);
		else if(current.hasAttribute("id") && current.getAttribute("id") == "MozileStatusbar") removals.push(current);
		else if(current.getAttribute("tabindex") && current.getAttribute("tabindex") == "12345") current.removeAttribute("tabindex");
		current = treeWalker.nextNode();
	}
	while(removals.length) {
		removals[0].parentNode.removeChild(removals[0]);
		removals.shift();
	}
	
		// Check the element itself for the tabindex attribute.
	if(element.getAttribute("tabindex") && element.getAttribute("tabindex") == "12345") {
		element.removeAttribute("tabindex");
	}
	
	return element;
}

/** 
 * Convert HTML to xhtml compatible. All tag names are converted to lower case. Converts a whole html document or just a fragment. The string to be converted must have been generated by the XML Serializer.
 * @private
 * @param {String} contents The string to be converted.
 * @type String
 * @return The converted string.
 */
MozileMediator.prototype._htmlToXHTML = function(contents) {
	if(!this._tagPattern) this._tagPattern = /<(\/*)(\w*)/g; /* match tags */
		// use an anonymous replace function to convert to lower case.
	return contents.replace(this._tagPattern, function(word) { return word.toLowerCase(); });
}

/** 
 * Convert HTML to xhtml compatible. All tag names are converted to lower case.  The string to be converted must have been generated by the XML Serializer.
 * @private
 * @param {String} contents The string to be converted.
 * @param {String} charset Optional. Specifies a character set, such as "UTF-8".
 * @param {Array} attrList Optional. See @see{_getConversionAttributes}.
 * @param {Array} entityList Optional. See @see{#_getEntityVersion}.
 * @type String
 * @return The converted string. If the conversion fails, the original string is returned.
 */
MozileMediator.prototype._convertCharacterSet = function(contents, charset, attrList, entityList) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile._convertCharacterSet()";
	
	if(!charset) charset = this.getSaveOption("current", "saveCharset", document.characterSet);
	
	var result = "Charset: "+ charset +"\n";
	result += "Conversion Attributes: "+ this._getConversionAttributes() +"\n";
	result += "Entity Version: "+ this._getEntityVersion() +"\n";
	this.status(f,1,result);

	try {
		var SAC = Components.classes["@mozilla.org/intl/saveascharset;1"].createInstance();
		SAC.QueryInterface(Components.interfaces.nsISaveAsCharset);
		SAC.Init(charset, this._getConversionAttributes(attrList), this._getEntityVersion(entityList));
		return SAC.Convert(contents);
	}catch (e) {
		//throw Error("nsISaveAsCharset exception: "+e);
		return contents;
	}
}

/** 
 * Given the "saveConversion" option array, this function computes an integer specifying what attributes should be converted by the nsISaveAsCharset.init method. Options are in groups: "none" or "entitybeforecharset" or "entitiyaftercharset"; "nofallback" or "questionmark" or "escapedunicode" or "decimalncr" or "hexncr"; "charsetfallback"; "ignoreignorables". For X/HTML the defaults are "entitybeforecharset", "ignoreignorables", "decimalncr".
 * @private
 * @param {Array} attrList Optional.
 * @type Integer
 * @return A PRUint32 signifying the attributes to convert.
 */
MozileMediator.prototype._getConversionAttributes = function(attrList) {
	if(!attrList) attrList = this.getSaveOption("current", "saveConversion");
	if(!attrList && this.getMode() != "XML") attrList = ["entitybeforecharset", "ignoreignorables", "decimalncr" ];
	var attr = 0;
	if ( (attrList instanceof Object) && (attrList instanceof Array) ) {
		var attributes = new Array();
		attributes = attributes.concat(attrList);
		while ( attributes.length > 0 ) {
			switch ( attributes.shift() ) {
				case "nofallback" :
					attr &= ~0xFF;
					break;
				case "questionmark" :
					attr &= ~0xFF;
					attr |= 1;
					break;
				case "escapedunicode" :
					attr &= ~0xFF;
					attr |= 2;
					break;
				case "decimalncr" :
					attr &= ~0xFF;
					attr |= 3;
					break;
				case "hexncr" :
					attr &= ~0xFF;
					attr |= 4;
					break;
				case "none" :
					attr &= ~0x300;
					break;
				case "entitybeforecharset" :
					attr &= ~0x300;
					attr |= 0x100;
					break;
				case "entityaftercharset" :
					attr &= ~0x300;
					attr |= 0x200;
					break;
				case "charsetfallback" :
					attr |= 0x400;
					break;
				case "ignoreignorables" :
					attr |= 0x800;
					break;
			}
		}
	}
	return attr;
}

/** 
 * Given the "saveEntities" option array, this function computes an integer specifying what entities which should be converted by the nsISaveAsCharset.init method. Options are: "none", "html40latin1", "html40symbols", "html40special", "mathml20", "transliterate".
 * @private
 * @param {Array} entityList Optional.
 * @type Integer
 * @return A PRUint32 signifying the attributes to convert.
 */
MozileMediator.prototype._getEntityVersion = function(entityList) {
	if(!entityList) entityList = this.getSaveOption("current", "saveEntities");
	var version = 0;
	if ( (entityList instanceof Object) && (entityList instanceof Array) ) {
		var entities = new Array();
		entities = entities.concat(entityList);
		while ( entities.length > 0 ) {
			switch ( entities.shift() ) {
				case "none" :
					entities = new Array();
					version = 0;
					break;
				case "html40latin1" :
					version |= 1;
					break;
				case "html40symbols" :
					version |= 2;
					break;
				case "html40special" :
					version |= 4;
					break;
				case "transliterate" :
					version |= 8;
					break;
				case "mathml20" :
					version |= 16;
					break;
			}
		}
	}
	return version;
}	



/** 
 * Extract the contents of the document as HTML, first cleaning up any mess that Mozile has made. It maintains any XML declaration, XML processing instructions, and Doctype declarations it finds.
 * <p>Since editing modes are not yet supported, this method behaves very much the documentToXML method.
 * @type String
 * @return String of the serialized HTML contents.
 */
MozileMediator.prototype.documentToHTML = function() {
	var newDoc = document.documentElement.cloneNode(true);
	newDoc = this._cleanDOM(newDoc);
	var contents = this._getXMLDeclaration();
	contents += this._getDoctypeDeclaration();
	contents += this._getProcessingInstructions();
	// Use innerHTML when possible, since it maintains attribute sequence.
	if(newDoc.innerHTML) {
		var container = document.createElement("div");
		container.appendChild(newDoc);
		contents += container.innerHTML;
	}
	else {
		var serializer = new XMLSerializer;
		contents += serializer.serializeToString(newDoc);
	}
	return contents;
}


/** Mozile - Document To XML -
 * Extract the contents of the document as XML, first cleaning up any mess that Mozile has made. It maintains any XML declaration, XML processing instructions, and Doctype declarations it finds.
 * <p>Because Mozile's mode support is not finished, this method isn't exactly as it should be. It lowercases all element names to make them look more like XML.
 * @type String
 * @return String of the serialized XML contents.
 */
MozileMediator.prototype.documentToXML = function() {
		// Lowercase the element names, and return the string.	
	return this._htmlToXHTML(this.documentToHTML());
}


/** 
 * Extract the contents of the current editor as HTML, first cleaning up any mess that Mozile has made. A simpler version of the documentToHTML method.
 * @type String
 * @return String of the serialized XML contents.
 */
MozileMediator.prototype.editorToHTML = function() {
	if(!this.getCurrentEditor() || this.getCurrentEditor() == null) throw Error("Error: No editor selected!");
	var editor = this.getCurrentEditor().cloneNode(true);
	editor = this._cleanDOM(editor);
	// Use innerHTML when possible, since it maintains attribute sequence.
	var contents;
	if(editor.innerHTML) {
		var container = document.createElement("div");
		container.appendChild(editor);
		contents = container.innerHTML;
	}
	else {
		var serializer = new XMLSerializer;
		contents = serializer.serializeToString(editor);
	}
	return contents;
}


/** 
 * Extract the contents of the current editor as XML, first cleaning up any mess that Mozile has made. A simpler version of documentToXML.
 * @type String
 * @return String of the serialized XML contents.
 */
MozileMediator.prototype.editorToXML = function() {
	return this._htmlToXHTML(this.editorToHTML());
}



/** Mozile - Content -
 * Returns the string of Mozile's content, depending on the current save options.
 * 
 * @return Always true.
 */
MozileMediator.prototype.content = function() {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile.content()";
	this.debug(f,1,"Getting Mozile content.");
	
	var content = this.getSaveOption("current", "content");
	var format = this.getSaveOption("current", "format");
	if(format == "XHTML") format = "XML";
	
	this.debug(f,1,"Saving "+ content +"To"+ format);
	var result;
	try {
		result = eval("this."+ content +"To"+ format +"()");
	} catch(e) {
		this.debug(f,1,"Bad result: "+e);
	}
	return result;
}


/** Mozile - Store State -
 * This is the stub of a command used in the UndoRedo module. It does nothing except rest the keyCounter and send a status message.
 * 
 * @return Always true.
 */
MozileMediator.prototype.storeState = function(command) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile.storeState()";
	this.debug(f,1,"Storing current state");
	// If this is the first change after a save, then change the status message and set the changesSaved to false.
	if(this.changesSaved == true) {
		this.status(f,1,"Editing");
		this.changesSaved = false;
	}
	
	// Reset the key counter.
	this.keyCounter = 0;
	
	return true;
}



/** 
 * Loads "interface.js" which is the second stage of Mozile loading.
 * @type Void
 */
MozileMediator.prototype.load = function() {
	if(this._loadComplete) return;
	if(mozileInterface) {
		this.addResource(new MozileScriptResource("Mozile-Core-core.js", this.getRoot() + "core/core.js"));
		if(this.getOption("preloaded")) this.addResource(new MozileScriptResource("Mozile-Core-mozile.js", this.getRoot() + "core/mozile.js"));
		
		var link = new MozileLinkResource("Mozile-Core-interface.css", this.getRoot() + "core/interface.css")
		link.load();
		
		var script = new MozileScriptResource("Mozile-Core-interface.js", this.getRoot() +"core/interface.js");
		if(this.getOption("preloaded")) this.addResource(script);
		else script.load();
		
		var mozileModule;
		for(var m=0; m < mozileModules.length; m++){
			mozileModule = new MozileModule(mozileModules[m]);
			if(this.getOption("preloaded")) {
				this.addResource(new MozileScriptResource(mozileModule.getId(), mozileModule.getSource()));
				this.addModule(mozileModule);
			}
			else mozileModule.load();
		}
		
		var complete = new MozileScriptResource("Mozile-Core-loadComplete", "");
		complete.setContent("mozile.loaded()");
		if(this.getOption("preloaded")) this.addResource(complete);
		else complete.load();
	}
	this._loadComplete = true; // load is complete, whether or not the interface was loaded.
}


/** 
 * A "post-load" function which calls methods after load is complete.
 * @type Void
 */
MozileMediator.prototype.loaded = function() {
	if(this._loadedComplete) return;
	this._loadedComplete = true;
	if(this.isExtension()) return;
	
	for(var name in this.getModules()) {
		this.getModule(name).init();
	}
	
	this.getToolbar();
	this._cleanInterfaces();
	
	var rootNode = document.getBody();
	if(this.isEditor(rootNode)) this.startEditing();
}





/** Mozile Editor -
 * Controls the manipulation of the document through the Selection object.
 * @constructor
 * @param {String} configString A properly formatted configuration string.
 */
function MozileEditor(configString) {

	/**
	 * @private
	 * @type String
	 */
	this._configString = String(configString);

}

MozileEditor.prototype = new MozileComponent;
MozileEditor.prototype.constructor = MozileEditor;



var mozileEditor = new MozileEditor("DefaultEditor");



/**
 * Get the last node edited.
 * @type Node
 */
MozileEditor.prototype.getLastNode = function() { 
	if(this._lastNode) return this._lastNode; 
	else return undefined;
}

/**
 * Set the last node edited.
 * @param {Node} node
 * @type Node
 */
MozileEditor.prototype.setLastNode = function(node) { 
	this._lastNode = node;
	return node;
}

/**
 * Get the offset from the last node edited.
 * @type Integer
 */
MozileEditor.prototype.getLastOffset = function() { 
	if(this._lastOffset != undefined) return this._lastOffset; 
	else return undefined;
}

/**
 * Set the last node edited.
 * @param {Integer} offset
 * @type Integer
 */
MozileEditor.prototype.setLastOffset = function(offset) { 
	this._lastOffset = offset;
	return offset;
}

/**
 * Get the keyCode of the last keypress.
 * @type Integer
 */
MozileEditor.prototype.getLastKeyCode = function() { 
	if(this._lastKeyCode) return this._lastKeyCode; 
	else return undefined;
}

/**
 * Set the last node edited.
 * @param {Integer} keyCode
 * @type Integer
 */
MozileEditor.prototype.setLastKeyCode = function(keyCode) { 
	this._lastKeyCode = keyCode;
	return keyCode;
}

/**
 * Replace all "a" nodes with copies named "mozileLinkReplacement".
 * @param {Element} element Optional. The root element for the replacement. If none is given the mozile.currentEditor is used.
 * @type Integer
 * @return Number of elements replaced.
 */
MozileEditor.prototype.replaceAnchors = function(element) {
	if(!mozile.getOption("replaceAnchors")) return 0;
	var elements;
	if(arguments.length > 0) elements = element.getElementsByTagName("a");
	else elements = mozile.getCurrentEditor().getElementsByTagName("a");
	var newElement;
	var range = document.createRange();
	var i=0, j=0;
	while(elements.length) {
		newElement = document.createElement("mozileAnchorReplacement");
			// copy all attributes
		for(i=0; i < elements[0].attributes.length; i++) {
			newElement.setAttribute(elements[0].attributes[i].name, elements[0].attributes[i].value);
		}
			// Replace
		range.selectNodeContents(elements[0]);
		newElement.appendChild(range.extractContents());
		elements[0].parentNode.replaceChild(newElement, elements[0]);
		j++
	}
	return j;
}

/**
 * Restore all "a" nodes that were replaced.
 * @param {Element} element Optional. The root element for the replacement. If none is given the mozile.currentEditor is used.
 * @type Integer
 * @return Number of elements replaced.
 */
MozileEditor.prototype.restoreAnchors = function(element) {
	if(!mozile.getOption("replaceAnchors")) return 0;
	var elements;
	if(arguments.length > 0) elements = element.getElementsByTagName("mozileAnchorReplacement");
	else if(!mozile.getCurrentEditor()) return 0;
	else elements = mozile.getCurrentEditor().getElementsByTagName("mozileAnchorReplacement");
	var newElement;
	var range = document.createRange();
	var i=0, j=0;
	while(elements.length) {
		newElement = document.createElement("a");
			// copy all attributes
		for(i=0; i < elements[0].attributes.length; i++) {
			newElement.setAttribute(elements[0].attributes[i].name, elements[0].attributes[i].value);
		}
			// Replace
		range.selectNodeContents(elements[0]);
		newElement.appendChild(range.extractContents());
		elements[0].parentNode.replaceChild(newElement, elements[0]);
		j++
	}
	return j;
}

/** Mozile Editor - Insert String -
 * Inserts a string at the current selection index. If the selection is not collapsed, then the selection is deleted before the new string is inserted. The selection is then collapsed and set to the end of the inserted string.
 * 
 * @param string The string to be inserted. 
 * @return True when string has been inserted, false otherwise.
 */
MozileEditor.prototype.insertString = function(string) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile.insertString()";
	mozile.debug(f,1,"Inserting string "+ string);
	
	// Get the current selection
	var selection = window.getSelection();
	// If it is not collapsed, try to delete the contents of the selection
	if(!selection.isCollapsed) {
		if(!this.deletion(InsertionPoint.prototype.NEXT)) return false;
	}
	
	// Get the focusNode, and make sure it's a text node
	var focusNode = selection.focusNode;
	if(!focusNode || !focusNode.isEditable()) return false;
	if(focusNode.nodeType != focusNode.TEXT_NODE) {
		mozile.status(f, 1, "Inserting text node");
		var text = document.createTextNode("");
		selection.getRangeAt(0).insertNode(text);
		selection.collapse(text, 0);
		focusNode = selection.focusNode;
	}
	
	// Insert the string at the focusOffset
	focusNode.insertData(selection.focusOffset, string);
	
	// Try to move the selection forward by the length of the string.
	try {
		selection.extend(selection.focusNode, selection.focusOffset + string.length);
	}
	catch(e) {
		alert("Error in insertString when trying to extend selection: "+e);
	}
	// Collapse the selection again.
	selection.collapseToEnd();
	return true;
}

/** Mozile Editor - Insert Fragment -
 * Inserts all the children of a document fragment or an element at the current selection index. If the selection is not collapsed, then the selection is deleted before the fragment is inserted.
 * 
 * @param fragment Either a document fragment, or an element with child nodes to be cloned and inserted.
 * @return True when the fragment has been inserted, false otherwise.
 */
MozileEditor.prototype.insertFragment = function(fragment) {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile.insertFragment()";
	mozile.debug(f,1,"Inserting fragment "+ fragment);
	
	// Get the current selection
	var selection = window.getSelection();
	// If it is not collapsed, try to delete the contents of the selection
	if(!selection.isCollapsed) {
		if(!this.deletion(InsertionPoint.prototype.NEXT)) return false;
	}

	// Make sure it's safe to insert here.
	if(!selection.focusNode || !selection.focusNode.isEditable()) return false;
	
	// Get the focusNode, and make sure it's a text node
	var anchorNode = selection.anchorNode;
	if(anchorNode.nodeType != 3) {
		mozile.debug(f,4,"This node is not a text node! "+anchorNode);
		return false;
	}
	// Split the current text node to make room for the new nodes.
	selection.anchorNode.splitText(selection.anchorOffset);
	var parent = selection.anchorNode.parentNode;
	var next = selection.anchorNode.nextSibling;
	var children;
	// In both cases (document fragment or an element) get the children.
	if(fragment.documentElement) children = fragment.documentElement.childNodes;
	else children = fragment.cloneNode(true).childNodes;
	// Clone each child node "deep" and insert it before the "next" node.
	var newNode;
	for(var i=0;i<children.length;i++) {
		newNode = children[i].cloneNode(true);
		parent.insertBefore(newNode, next);
	}
	
	// Move the selection
	selection.collapseToEnd();
	return true;
}


/** Mozile Editor - Delete -
 * Deletes the current selection. If the selection is collapsed, it is extended to the next insertion point in the given direction.
 * @param {Integer} direction See the special constants in {@link InsertionPoint}. Can be NEXT or PREVIOUS.
 * @type Boolean
 */
MozileEditor.prototype.deletion = function(direction) {
	// var f = new Array();
	// f["File"] = "core/core.js";
	// f["Function"] = "Mozile.deletion()";
	// mozile.debug(f,1,"Deleting in direction "+ direction);
	
	// Get the current selection
	var selection = window.getSelection();
	var IP = selection.getInsertionPoint();
	if(!IP) return false;

	if(selection.isCollapsed) {
		IP.seek(direction);
		if(!IP) return false;
		selection.extend(IP.getNode(), IP.getOffset());
	}
	var anchorNode = selection.anchorNode;
	var focusNode = selection.focusNode;
	var focusOffset = selection.focusOffset;
	if(!anchorNode || !focusNode) return false;
	
	// Get some information about the range.
	var range = selection.getRangeAt(0).cloneRange();
	var startBlock = range.startContainer.getParentBlock();
	var endBlock = range.endContainer.getParentBlock();
	var commonAncestor = range.commonAncestorContainer;

	// Make sure it's safe to delete here.
	if(!focusNode || !focusNode.isEditable()) {
		selection.collapse(selection.anchorNode, selection.anchorOffset);
		return false;
	}
	if(!commonAncestor || !commonAncestor.isEditable()) return false;
	
	// Now delete the contents of the selection.
	selection.deleteContents();
	
	if(selection.focusNode.nodeType == Node.ELEMENT_NODE) {
		var i = 0;
		if(direction == IP.NEXT) i = 1
		IP = selection.focusNode.childNodes[selection.focusOffset - i].getTerminalInsertionPoint(-direction);
		if(IP) selection.collapse(IP.getNode(), IP.getOffset());
	}
	
	// If the start and end blocks are different but have the same name, then we want to merge the endBlock with the startBlock.
	if(startBlock != endBlock) {
		// Merge blocks
		if(startBlock.nodeName == endBlock.nodeName) {	
			range.collapse(true);
			selection.removeAllRanges();
			selection.addRange(range);
			var newRange = document.createRange();
			newRange.selectNodeContents(endBlock);
			startBlock.appendChild(newRange.extractContents());
			endBlock.parentNode.removeChild(endBlock);
			//startBlock.normalize();
		} 
		// Shift cursor to the new block
		else {
			IP = selection.getInsertionPoint()
			if(IP) IP.seekNode(direction);
			if(IP) selection.collapse(IP.getNode(), IP.getOffset());
		}
	}

	// TODO: Normalize the parent.
	// Problem: Normalize breaks deletion in direction NEXT
	//var element = selection.focusNode;
	//if(element.nodeType != element.ELEMENT_NODE) element = element.parentNode;
	//element.normalize();

	//mozile.status([], 1, selection.focusNode +" "+ selection.focusOffset +"/"+ selection.focusNode.getLength() +" in "+ startBlock.getLength());
	
	return true;
}



/** Mozile Editor - Split Block -
 * Splits the current block in two, creating a new block after the old one in the process. This is the usual behaviour of the enter key.
 * 
 * @return Always true.
 */
MozileEditor.prototype.splitBlock = function() {
	var f = new Array();
	f["File"] = "core/core.js";
	f["Function"] = "Mozile.splitBlock()";
	mozile.debug(f,1,"Splitting block");
	
	var selection = window.getSelection();
	var range = selection.getRangeAt(0).cloneRange();
	var focusNode = selection.focusNode;
	var focusOffset = selection.focusOffset;
	var node = focusNode;

	// If it is not collapsed, try to delete the contents of the selection
	if(!selection.isCollapsed) {
		if(!this.deletion(InsertionPoint.prototype.NEXT)) return false;
	}
	if(!selection.focusNode || !selection.focusNode.isEditable()) return false;

	// Don't split editors	
	if(node.nodeType != Node.ELEMENT_NODE && node.parentNode) {
		if(mozile.isEditor(node.parentNode)) return false;
	}
	node = node.getParentBlock();
	if(mozile.isEditor(node)) return false;
	
	// If the node has a parent, then split it.	Otherwise do nothing.
	if(node.parentNode) {
		range.selectNodeContents(node);
		var nodeString = range.toString();
		range.setStart(focusNode, focusOffset);
		var newNode = node.cloneNode(false);
		newNode.appendChild(range.extractContents());
		node.parentNode.insertAfter(newNode, node);	
		if(!focusNode || focusNode.textContent.length == 0) node.appendChild(document.createTextNode(" "));
		var IP = newNode.getFirstInsertionPoint();
		if(IP) IP.select();
	}
	
	mozile.debug(f,1,"Done splitting block");
	
	return true;

}









/** Mozile Resource -
 * An abstract class for external resources, such as JavaScripts and CSS files.
 * You can give the resource's element text content with the setContent(content) method. For mocre complex content, first createElement() then manipulate the returned element.
 * @constructor
 *
 * @param {String} category The category of the resource, used as the localName of the resource's element.
 * @param {String} id The id for the resource, used as the id of the resources' element.
 * @param {String} type Optional. The type for the resource, used for the type attribute. E.g. "text/css".
 * @param {String} namespace Optional. The namespace for the resource's element. Defaults to the global XHTMLNS.
 */
function MozileResource(category, id, type, namespace) { 

	// Declare private variables.
	/**
	 * @private
	 * @type String
	 */
	this._category = String(category);
	
	/**
	 * @private
	 * @type String
	 */
	this._id = String(id);
	
	/**
	 * @private
	 * @type String
	 */
	this._type = checkArgument("", arguments[2]);
	
	/**
	 * @private
	 * @type String
	 */
	this._namespace = checkArgument(XHTMLNS, arguments[3]);

}

/**
 * Returns "[object MozileResource]".
 * @type String
 */
MozileResource.prototype.toString = function() { 
	return "[object "+ this.constructor.toString().match(/^function\s+(\w+)/)[1] +"]";
}

	
/**
 * @type String
 * @return The category of resource. 
 */
MozileResource.prototype.getCategory = function() { return this._category; }

/**
 * @type String
 * @return The resource id. 
 */
MozileResource.prototype.getId = function() { return this._id; }

/**
 * @type String
 */
MozileResource.prototype.getType = function() { return this._type; }

/**
 * @type String
 */
MozileResource.prototype.getNamespace = function() {return this._namespace; }

/**
 * Gets element associated with the resource. Creates the element if necessary.
 * @type String
 */
MozileResource.prototype.getElement = function() {
	if(!this._element) { 	
		if(document.getElementById(this.getId())) this._element = document.getElementById(this.getId());
		else this._element = this.createElement(); 
	}
	return this._element;
}

/**
 * @type TextNode
 * @return The content of the resource (a node). 
 */
MozileResource.prototype.getContent = function() { 
	if(this._contentNode) return this._contentNode; 
	else return undefined;
}

/**
 * @param {String} content Creates a text node which will be appended to the element. 
 * @type TextNode
 * @return The content of the resource. 
 */
MozileResource.prototype.setContent = function(content) {	
	this._contentNode = document.createTextNode(content);
	return this._contentNode;
}



/**
 * Creates a new element for the resource and returns it.
 * @type Element
 */
MozileResource.prototype.createElement = function() {	
	this._element = document.createElementNS(this._namespace, this._category);
	this._element.setAttribute("id", this.getId());
	this._element.setAttribute("type", this.getType());
	if(this.getContent() && this.getContent().nodeType == 3) {
		this._element.appendChild(this.getContent());
	}
	return this._element;
}

/**
 * Inserts the resource's element into the document
 * @type Void
 */
MozileResource.prototype.load = function() {
		// Don't load if already loaded.
	if(this.getElement().parentNode) return;

	if(document.documentElement.tagName.toLowerCase() == "html") {
		document.getElementsByTagName("head")[0].appendChild(this.getElement());
	}
	else {
		document.documentElement.insertBefore(this.getElement(), document.documentElement.firstChild);
	}

		// Register this resource with the global mediator
	mozile.addResource(this);
}

/**
 * Removes the resource's element from the document.
 *
 * @param {Element} element Optional. If an element is provided the resource's element will be removed from the given element.
 * @type Boolean
 * @return True if any elements have been removed. False otherwise.
 */
MozileResource.prototype.unload = function(element) {
	if(arguments.length > 0 && element) {
		element = arguments[0];
	}
	else {
		element = document;
	}

	var elements = element.getElementsByTagName(this._category);
	var elementsRemoved = false;
	for(var i=0;i<elements.length;i++) {
		if(elements[i].getAttribute("id") == this.getId()) {
			elements[i].parentNode.removeChild(elements[i]);
			elementsRemoved = true;
		}
	}
	return elementsRemoved;
}
		



/** Mozile Script Resource -
 * Subclass of MozileResource designed for script tags.
 * @constructor
 * @param {String} id The id for the resource, used as the id of the resources' element.
 * @param {String} source The URL for src attribute.
 * @param {String} type Optional. The type for the resource, used for the type attribute. Default is "application/x-javascript".
 * @param {String} namespace Optional. The namespace for the resource's element. Defaults to the global XHTMLNS.
 */
function MozileScriptResource(id, source, type, namespace) {

	// Declare private variables.
	/**
	 * @private
	 * @type String
	 */
	this._category = "script";
	
	/**
	 * @private
	 * @type String
	 */
	this._id = String(id);
	
	/**
	 * @private
	 * @type String
	 */
	this._source = String(source);
	
	/**
	 * @private
	 * @type String
	 */
	this._type = checkArgument("application/x-javascript", arguments[2]);
	
	/**
	 * @private
	 * @type String
	 */
	this._namespace = checkArgument(XHTMLNS, arguments[3]);
	
}

MozileScriptResource.prototype = new MozileResource;
MozileScriptResource.prototype.constructor = MozileScriptResource;

/**
 * @type String
 */
MozileScriptResource.prototype.getSource = function() { return this._source; }


/**
 * Creates a new element for the resource and returns it.
 * @type Element
 */
MozileScriptResource.prototype.createElement = function() {
	this._element = document.createElementNS(this._namespace, this._category);
	this._element.setAttribute("id", this.getId());
	this._element.setAttribute("type", this.getType());
	if(this.getSource() != "") this._element.setAttribute("src", this.getSource());
	if(this.getContent() && this.getContent().nodeType == 3) {
		this._element.appendChild(this.getContent());
	}
	return this._element;
}


/** Mozile Link Resource -
 * Subclass of MozileResource designed for link tags.
 * @constructor
 * @param {String} id The id for the resource, used as the id of the resources' element.
 * @param {String} source The URL for href attribute.
 * @param {String} relation Optional. The relation for the rel attribute. Default is "stylesheet".
 * @param {String} type Optional. The type for the resource, used for the type attribute.  Default is "text/css".
 * @param {String} namespace Optional. The namespace for the resource's element. Defaults to the global XHTMLNS.
 */
function MozileLinkResource(id, source, relation, type, namespace) {

	// Declare private variables.
	/**
	 * @private
	 * @type String
	 */
	this._category = "link";
	
	/**
	 * @private
	 * @type String
	 */
	this._id = String(id);
	
	/**
	 * @private
	 * @type String
	 */
	this._source = String(source);
	
	/**
	 * @private
	 * @type String
	 */
	this._relation = checkArgument("stylesheet", arguments[2]);
	
	/**
	 * @private
	 * @type String
	 */
	this._type = checkArgument("text/css", arguments[3]);
	
	/**
	 * @private
	 * @type String
	 */
	this._namespace = checkArgument(XHTMLNS, arguments[4]);

}

MozileLinkResource.prototype = new MozileResource;
MozileLinkResource.prototype.constructor = MozileLinkResource;

/**
 * @type String
 */
MozileLinkResource.prototype.getSource = function() { return this._source; }

/**
 * @type String 
 */
MozileLinkResource.prototype.getRelation = function() { return this._relation; }

/**
 * Link cannot have children. Overrides parent method with an empty function.
 * @type Undefined
 */
MozileLinkResource.prototype.getContent = function() { return undefined; }

/**
 * Link cannot have children. Overrides parent method with an empty function.
 * @type Undefined
 */
MozileLinkResource.prototype.setContent = function(content) { return undefined;	}


/**
 * Creates a new element for the resource and returns it.
 * @type Element
 */
MozileLinkResource.prototype.createElement = function() {
	this._element = document.createElementNS(this._namespace, this._category);
	this._element.setAttribute("id", this.getId());
	this._element.setAttribute("rel", this.getRelation());
	this._element.setAttribute("type", this.getType());
	this._element.setAttribute("href", this.getSource());
	return this._element;
}



/** Mozile Style Resource -
 * Subclass of MozileResource designed for style tags.
 * @constructor
 * @param {String} id The id for the resource, used as the id of the resources' element.
 * @param {String} type Optional. The type for the resource, used for the type attribute.  Default is "text/css".
 * @param {String} media Optional. The value for the media attribute. Default is none.
 * @param {String} namespace Optional. The namespace for the resource's element. Defaults to the global XHTMLNS.
 */
function MozileStyleResource(id, type, media, namespace) {

	// Declare private variables.
	/**
	 * @private
	 * @type String
	 */
	this._category = "style";
	
	/**
	 * @private
	 * @type String
	 */
	this._id = String(id);
	
	/**
	 * @private
	 * @type String
	 */
	this._type = checkArgument("text/css", arguments[1]);
	
	/**
	 * @private
	 * @type String
	 */
	this._media = checkArgument("", arguments[2]);
	
	/**
	 * @private
	 * @type String
	 */
	this._namespace = checkArgument(XHTMLNS, arguments[3]);

}
	
MozileStyleResource.prototype = new MozileResource;
MozileStyleResource.prototype.constructor = MozileStyleResource;


/**
 * @type String
 */
MozileStyleResource.prototype.getMedia = function() { return this._media; }

/**
 * Finds the {@link http://developer.mozilla.org/en/docs/DOM:stylesheet CSSStyleSheet} associated with the element.
 * @type CSSStyleSheet
 */
MozileStyleResource.prototype.getStylesheet = function() {
		// If there is no stylesheet, try to find the it
	if(!this._stylesheet) {
		for(var i=0; i < document.styleSheets.length; i++) {
			if(document.styleSheets.item(i).ownerNode == this.getElement()) {
				/** @private */
				this._stylesheet = document.styleSheets.item(i);
				return this._stylesheet;
			}
		}
	}
		// Check again
	if(!this._stylesheet) return undefined;
	else return this._stylesheet;
 }


/**
 * Creates a new element for the resource and returns it.
 * @type Element
 */
MozileStyleResource.prototype.createElement = function() {
	/** @private */
	this._element = document.createElementNS(this._namespace, this._category);
	this._element.setAttribute("id", this.getId());
	this._element.setAttribute("type", this.getType());
	if(this._media != "") {
		this._element.setAttribute("media", this.getMedia());
	}
	if(this.getContent() && this.getContent().nodeType == 3) {
		this._element.appendChild(this.getContent());
	}
	return this._element;
}
	
















/** Mozile - Test Function -
 * This function is only used for testing. It gets overridden in the TestModules.
 *
 * @return Always 0.
 */
// 
MozileMediator.prototype.testFunction = function() {
	return 0;
}

MozileMediator.prototype.testAlert = function() {
	try{
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect UniversalBrowserWrite ");
		alert("Mozile Server-Side Privileged! Extension: "+ mozile.isExtension());
	}	catch(e){
		alert("Mozile Server-Side Unprivileged! \n"+e);
	}
}


/**** Activate Configuration ****/
// If the "mozileInterface" variable is set to "false", then we are ready to call the mozileConfiguration() function, which will create a Mozile instance and load modules. 
// If the "mozileInterface" variable is not "false", then more code will be loaded before the configuration is called, so we do nothing here.
//try {
		// Global MozileMediator instance
	var mozile = new MozileMediator("root='"+mozileScriptSource+"'," + mozileOptions);
	//this._watchInterval = window.setInterval("mozile.watchSharedData()", mozile.getOption("defaultInterval"));
	document.addEventListener("DOMAttrModified", mozile.watchSharedData, false);
	mozileConfiguration();

		// Load Just-In-Time means wait before loading the interface
	if(!mozile.getOption("loadJIT")) {
		mozile.load();
	}
//}
//catch(e) {
//	alert("Error in core.js when calling mozileConfiguration: "+e);
//}


/**** UTILITY FUNCTIONS ****/
// These functions are used for development, and should not be relied upon in production code.

/** Dump Array -
 * Displays the contents of the array in key=>value pairs, using an alert. 
 * 
 * @param arr The array to be dumped
 * @return Nothing.
 */
function dumpArray(arr) {
	var s = "Array Dump: ";
	for(key in arr) {
		s = s + key +"=>"+ arr[key] +"\n";
	}
	alert(s);
}
/** Print XML -
 * Serializes the given XML and returns it as a string
 * 
 * @param XML The XML to be serialized.
 * @return String version of the XML.
 */
function printXML(XML) {
	if(!XML || XML=="") return "Nothing to print!!"; 
	var objXMLSerializer = new XMLSerializer;
	var output = objXMLSerializer.serializeToString(XML);
		// fix stupid uppercase tags!
	output = output.replace( /<(\/?)([A-Z]+)/g,
		function (output,p1,p2,offset,s) {
			return '<'+p1+p2.toLowerCase() ;
		} ) ;
	return output;
}



Documentation generated by JSDoc on Wed Nov 1 15:11:15 2006

The mozile project can be contacted through the mailing list or the member list.
Copyright © 2000-2019. All rights reserved. Terms of Use & Privacy Policy.