// vim: foldmethod=marker

//
// BEGIN FLOCK GPL
// 
// Copyright Flock Inc. 2005-2006
// http://flock.com
// 
// This file may be used under the terms of of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.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.
// 
// END FLOCK GPL
//



//  Constants
// Debug/*{{{*/
const FLOCK_DEBUG = 1;
/*}}}*/
// URLs that need remembering/*{{{*/
const FLOCK_WEB = "http://www.flock.com";
const FLOCK_FEEDBACK_URL = FLOCK_WEB+"/feedback";
const FLOCK_API = FLOCK_WEB+"/api";
/*}}}*/
// Namespaces/*{{{*/
const NS_XHTML = 'http://www.w3.org/1999/xhtml';
/*}}}*/
// nsIWebProgressListener constants/*{{{*/
const STATE_IS_NETWORK = 
		Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK;
const STATE_STOP = 
		Components.interfaces.nsIWebProgressListener.STATE_STOP;
/*}}}*/
// nsIDOMNode constants/*{{{*/
const TEXT_NODE = Components.interfaces.nsIDOMNode.TEXT_NODE;
const CDATA_SECTION_NODE = Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE;
const ELEMENT_NODE = Components.interfaces.nsIDOMNode.ELEMENT_NODE;
/*}}}*/
// Validation Regular Expressions/*{{{*/
const RE_VALID_URL = new RegExp ("^(https?|file)://");
/*}}}*/
// Something the shelf uses/*{{{*/
const MARK_SELECTION_START = '\u200B\u200B\u200B\u200B\u200B';
const MARK_SELECTION_END = '\u200C\u200C\u200C\u200C\u200C';
/*}}}*/

// termie - I know that you probably want to put this in your dojo tree
var FlockLogger = {/*{{{*/
    files : {},
    transports : {},
    write: function(aFname, aBuf) {
        try {
            if(flock_getBoolPref("flock.logging.on")!=true) return;
            dump(aBuf);
            var obj_File = null;
            var obj_Transport = null;
            if(this.files[aFname]) {
                obj_File = this.files[aFname];
                obj_Transport = this.transports[aFname];
            }
            else {
                const DIRECTORY_SERVICE_CONTRACTID  = '@mozilla.org/file/directory_service;1';
                const nsIProperties = Components.interfaces.nsIProperties;
                const nsIFile = Components.interfaces.nsIFile;
                var profd = Components.classes[DIRECTORY_SERVICE_CONTRACTID].getService(nsIProperties).get('ProfD', nsIFile);
                obj_File = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
                obj_File.initWithPath(profd.path);
                obj_File.append("log");
                if(!obj_File.exists()) obj_File.create(1,0700);
                obj_File.append(aFname);
                if(!obj_File.exists()) obj_File.create(0x00,0644);

                obj_Transport = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
                obj_Transport.init( obj_File, 0x04 | 0x08 | 0x10, 064, 0 );
                this.files[aFname] = obj_File;
                this.transports[aFname] = obj_Transport;
            }
		    obj_Transport.write(aBuf,aBuf.length);
        }
        catch(e){
           //alert(e);
        }
    }
}/*}}}*/

// XXX andy: this file will slowly be rewritten to 
//           use the same style as dojo and these calls will
//           become deprecated
var flock = {/*{{{*/
    // this is love.
};/*}}}*/

flock.debug = function () {/*{{{*/
    if (!FLOCK_DEBUG) return;
	var out = ''
	if (arguments.length > 1) {
		for (var i = 0;i < arguments.length;i++) {
			out += arguments[i] + " : ";
		}
	} else {
		out = "DEBUG : "+arguments[0];
	}
	dump(out+"\n");
}/*}}}*/
flock.dumpvar = function (aObj) {/*{{{*/
	for (p in aObj) {
		try {
			dump(p + ' :: ' + aObj[p] + '\n'); 
			if (typeof aObj[p] == 'aObject') { 
				for (q in aObj[p]) { 
					dump(p + '.' + q + ' :: ' + aObj[p][q] + '\n'); 
				} 
			}
		} catch(e) {
			continue;
		}
	}
};/*}}}*/
var XXX = flock.debug;
var dd = flock.dumpvar;
flock.deprecated = function (aMsg, aVersion) {/*{{{*/
    var msg = aMsg;
    if (aVersion) msg += " -- removal scheduled for version "+aVersion;
    flock.debug('DEPRECATED', msg);
}/*}}}*/
flock.load = function (aSpec) {/*{{{*/
    if (aSpec.substr(0,6) == "flock.") {
        var converted = aSpec.replace(/\./g,"/");
        aSpec = "chrome://browser/content/" + converted + ".js";
    }
    if (flock.load.loaded.indexOf(aSpec) != -1) {
        return;
    }
    XXX('flock.load', aSpec);
    var loader = Components.classes['@mozilla.org/moz/jssubscript-loader;1']
        .getService(Components.interfaces.mozIJSSubScriptLoader);
    loader.loadSubScript(aSpec);
    flock.load.loaded.push(aSpec);
}/*}}}*/
flock.load.loaded = [];
flock.require = flock.load;

flock.lang = {/*{{{*/
    // you are searching for beauty
    // in all the wrong places
};/*}}}*/

flock.lang.classOf = function (aObj) {/*{{{*/
    if (typeof aObj != "object") return;
    var s = Object.prototype.toString.apply(aObj);
    var r = s.match(/object (\w+)/);
    return r[1];
}/*}}}*/
flock.lang.nameOf = function (aFunc) {/*{{{*/
    if (typeof aFunc != "function") return;
    var s = aFunc.toString();
    var r = s.match(/function (\w+)\(/);
    if (r) return r[1];
    return;
}/*}}}*/
flock.lang.toArray = function () {/*{{{*/
    var iterable = arguments[0];
    var out = [];
    if (arguments.length > 1) {
        iterable = arguments;
    }
    // check for nsISimpleEnumerator
    if (iterable['hasMoreElements']) {
        while (iterable.hasMoreElements()) {
            out.push(iterable.getNext());
        }
    }
    else {
        for (var i = 0; i < iterable.length; ++i) {
            out.push(iterable[i]);
        }
    }
    return out;
};/*}}}*/
flock.lang.unique = function (aArr) {/*{{{*/
    var ar = [];
    for (var i = 0; i < aArr.length; ++i) {
        var item = aArr[i];
        if (ar.indexOf(item) == -1) {
            ar.push(item);
        }
    }
    return ar;
};/*}}}*/
flock.lang.decorate = function (aFuncToWrap, aWrapper, aContext, aFuncName) {/*{{{*/
    // XXX andy: aFuncName is only here because javascript does not 
    //           keep track of the function name very nicely
    var context = aContext || this;
    var wrapper = aWrapper;
    // example wrapper function:
    // var wrapper = function (aFunc) {
    //     var out = function () {
    //         return aFunc.apply(this, arguments)
    //     }
    // }
    var wrappee = aFuncToWrap;
    return wrapper.call(context, wrappee, aFuncName);
}/*}}}*/
flock.lang.clearTimeouts = function (aArr) {/*{{{*/
    //var timeouts = dojo.lang.unnest(arguments);
    aArr.forEach(function (aVal) {
        clearTimeout(aVal);
    });
};/*}}}*/


flock.browser = {/*{{{*/
    // what shall we do when all is gone?
    // sail on, sail on
    // sail on and on
};/*}}}*/
flock.browser.open = function (aUrl, aWhere) {/*{{{*/
    if (!aWhere) aWhere = 'current';
    // if all windows are closed on a mac
    if (!flock.browser.mostRecentNavigator) aWhere = 'window';
    XXX('flock.browser.open', aUrl, aWhere);
    var background = false;
    switch (aWhere) {
        case "current":
            flock.browser.mostRecentNavigator.content.location = aUrl;
            break;
        case "tab":
            background = flock_getBoolPref('browser.tabs.loadInBackground');
        case "foreground_tab":
            var browser = flock.browser.mostRecentNavigator
                            .document.getElementById('content');
            browser.loadOneTab(aUrl, null, null, null, background);
            break;
        case "window":
            window.open(aUrl);
            break;
        default:
            dojo.raise('NOT IMPLEMENTED', aWhere);
            break;
    }
    
    // aWhere =
    //  'tab'
    //  'window'
    //  'current' -- default
};/*}}}*/
flock.browser.openFeeds = function (aFeedList, aWhere) {/*{{{*/
    var feeds = flock.lang.unique(aFeedList);
    if (feeds.length == 1) {
        var feed = feeds[0];
        if (typeof feed == "string") var url = feed;
        else if (feed.url) var url = feed.url;
        if (url.substr(0, 'feed'.length) == 'feed')
            var feedurl = url;
        else if (url.substr(0, 'http://'.length) == 'http://')
            var feedurl = 'feed' + url.substring('http'.length);
        else
            var feedurl = 'feed:' + url;
        flock.browser.open(feedurl, aWhere);
        return;
    }
    feeds = feeds.map(function (aVal) {
        var feed = aVal;
        if (typeof feed == "string") var url = feed;
        else if (feed.url) var url = feed.url;
        if (url.substr(0, 'feed://'.length) == 'feed://')
            url = 'http' + url.substring('feed'.length);
        else if (url.substr(0, 'feed:'.length) == 'feed:')
            url = url.substring('feed'.length);
        return escape(url);
    });
    dd(feeds);
    flock.browser.open("feeds:"+feeds.join("&"), aWhere);
    // aWhere =
    //  'tab'
    //  'window'
    //  'current'
};/*}}}*/

flock.browser.getCurrentFeeds = function (listener) {/*{{{*/
    flock.require('flock.feed.feed');
    var feeds = $$('content').mCurrentBrowser.feeds;
    if (!feeds) {
        listener.onComplete(0, []);
        return;
    }
    feeds = flock.lang.toArray(feeds);
    var feedUrls = feeds.map(function (aVal) { 
        return aVal.href; 
    });
    flock.feed.service.prioritizeFeeds(feedUrls.length, feedUrls, listener);
};/*}}}*/



flock.browser.currentWindowIsMostRecent = function() {/*{{{*/
    return flock.browser.mostRecentNavigator == top;
};/*}}}*/

// flock.browser.currentUrl

flock.browser.__defineGetter__("mostRecentNavigator", function () {/*{{{*/
    flock.require('flock.common.xpcom');
    return flock.xpcom.windowMediator.getMostRecentWindow("navigator:browser");
});/*}}}*/
flock.browser.__defineGetter__('currentUrl', function () {/*{{{*/
    var currentUri = $$('content').mCurrentBrowser.currentURI
    if (!currentUri) return "";
    return currentUri.spec;
});/*}}}*/
flock.browser.__defineGetter__('currentFeeds', function () {/*{{{*/
    flock.require("flock.feed.feed");
    var feeds = $$('content').mCurrentBrowser.feeds
    if (!feeds) return null;
    var dummyVar = {};
    goodFeeds = flock.feed.service.prioritizeFeedsQuick(feeds.length, feeds,
                                                        dummyVar);
    return goodFeeds.map(function (aVal) {
        return {
            url: aVal.href,
            name: aVal.title,
            type: aVal.type
        };
    });
});/*}}}*/
flock.browser.__defineGetter__('currentTitle', function () {/*{{{*/
    return $$('content').contentTitle || "Untitled";
});/*}}}*/
flock.browser.__defineGetter__('currentDocument', function () {/*{{{*/
    return $$('content').mCurrentBrowser.docShell.document;
});/*}}}*/
flock.browser.__defineGetter__('currentBrowser', function () {/*{{{*/
    return $$('content').mCurrentBrowser;
});/*}}}*/
flock.browser.__defineGetter__('currentTabs', function () {/*{{{*/
    return $$('content').mTabs;
});/*}}}*/
flock.browser.__defineGetter__('isFeedView', function () {/*{{{*/
    var curUrl = flock.browser.currentUrl;
    return curUrl.match(/^feed/);
});/*}}}*/



flock.Base = function (aOpts) {/*{{{*/
    if (aOpts) dojo.lang.mixin(this, aOpts);
    if (this.isDebug && this.initDebug) {
        this.initDebug();
    }
};/*}}}*/
dojo.lang.extend(flock.Base, {
    isDebug: false,
    initDebug: function () {/*{{{*/
        for (m in this) {
            if (m.match(/^debug_/)) continue;
            var get = this.__lookupGetter__(m);
            var set = this.__lookupSetter__(m);
            if (get || set) continue;
            if (typeof this[m] != "function") continue;
            var wrapper = this.debug_wrapper;
            if (this["debug_" + m]) {
                XXX('FOUND', "debug_" + m);
                wrapper = this["debug_" + m];
            }
            this[m] = flock.lang.decorate(
                this[m], 
                wrapper,
                this,
                m
            );
        }
    },/*}}}*/
    debug_wrapper: function (aFunc, aFuncName) {/*{{{*/
        var out = function () {
            var args = flock.lang.toArray(arguments);
            var debugArgs = ["DEBUG", aFuncName].concat(args);
            XXX.apply(this, debugArgs);
            return aFunc.apply(this, arguments);
        }
        return out;
    },/*}}}*/
});


flock.xul = {/*{{{*/
    // all our hopes fade to pale indigo
    // when they shut off the lights
    // but the stars still shine
};/*}}}*/

flock.xul.hide = function (aNode) {/*{{{*/
    aNode.hidden = true;
};/*}}}*/
flock.xul.show = function (aNode) {/*{{{*/
    aNode.hidden = false;
};/*}}}*/
flock.xul.disable = function (aNode) {/*{{{*/
    aNode.setAttribute('disabled', 'true');
};/*}}}*/
flock.xul.enable = function (aNode) {/*{{{*/
    aNode.removeAttribute('disabled');
};/*}}}*/
flock.xul.createElement = function (aTag, aProps, aChildren) {/*{{{*/
    if (!aProps) aProps = {};
    var elm = document.createElement(aTag);
    for (att in aProps) {
        elm.setAttribute(att, aProps[att]);
    }
    if (aChildren) {     
        for (var i = 0; i < aChildren.length; ++i) {
            elm.appendChild(aChildren[i]);
        }
    }
    return elm;
};/*}}}*/

flock.xul.Node = function (aNode, aOpts) {/*{{{*/
    this._eventListeners = [];
    flock.Base.call(this, aOpts);
    this.mNode = aNode;
    //this.attach();
};/*}}}*/
dojo.inherits(flock.xul.Node, flock.Base);
dojo.lang.extend(flock.xul.Node, {
    events: {/*{{{*/
        "onBlur": "onblur",
        "onBroadcast": "onbroadcast",
        "onChange": "onchange",
        "onClick": "onclick",
        "onClose": "onclose",
        "onCommand": "oncommand",
        "onCommandUpdate": "oncommandupdate",
        "onContextMenu": "oncontextmenu",
        "onDblClick": "ondblclick",
        "onDragDrop": "ondragdrop",
        "onDragEnter": "ondragenter",
        "onDragExit": "ondragexit",
        "onDragGesture": "ondraggesture",
        "onDragOver": "ondragover",
        "onFocus": "onfocus",
        "onInput": "oninput",
        "onKeyDown": "onkeydown",
        "onKeyPress": "onkeypress",
        "onKeyUp": "onkeyup",
        "onLoad": "onload",
        "onMouseDown": "onmousedown",
        "onMouseMove": "onmousemove",
        "onMouseOut": "onmouseout",
        "onMouseOver": "onmouseover",
        "onMouseUp": "onmouseup",
        "onOverflow": "onoverflow",
        "onOverflowChanged": "onoverflowchanged",
        // XXX andy: due to a bug in the way mozilla handles events
        //           on popups, dojo.event.connect cannot override them
        //           properly, so for cases that use these will have to stick
        //           to using addEventListener and whatnot. Suckage.
        //"onPopupHidden": "onpopuphidden",
        //"onPopupHiding": "onpopuphiding",
        //"onPopupShowing": "onpopupshowing",
        //"onPopupShown": "onpopupshown",
        "onSelect": "onselect",
        "onUnderflow": "onunderflow",
        "onUnload": "onunload"
    },/*}}}*/
    _isAttached: false,
    attach: function () {/*{{{*/
        if (this._isAttached) return;
        for (evt in this.events) {
            if (!this[evt]) continue;
            dojo.event.connect(this.mNode, this.events[evt], this, evt);
        }
        this._isAttached = true;
    },/*}}}*/
    detach: function () {/*{{{*/
        if (!this._isAttached) return;
        for (evt in this.events) {
            if (!this[evt]) continue;
            dojo.event.disconnect(this.mNode, this.events[evt], this, evt);
        }
        this._isAttached = false;
    },/*}}}*/
    
    _eventListeners: null,
    addEventListener: function (aEvent, aMethod, aCapture) {/*{{{*/
        var wrapped = dojo.lang.hitch(this, aMethod);
        this._eventListeners.push({
            'event': aEvent,
            'method': aMethod,
            'wrapped': wrapped,
            'capture': aCapture
        });
        this.mNode.addEventListener(aEvent, wrapped, aCapture);
    },/*}}}*/
    removeEventListener: function (aEvent, aMethod, aCapture) {/*{{{*/
        var eventListener = null;
        for (var i = 0; i < this._eventListeners.length; ++i) {
            var c = this._eventListeners[i];
            if (c.method == aMethod
                && c.event == aEvent
                && c.capture == aCapture) {
                eventListener = c;
                this._eventListeners.splice(i);
                break;
            }
        }
        if (!eventListener) return;
        this.mNode.removeEventListener(
            aEvent, 
            eventListener.wrapped,
            aCapture
        );
    }/*}}}*/
});

flock.browser.WebProgressListener = function (aOpts) {/*{{{*/
    flock.Base.call(this, aOpts);
};/*}}}*/
dojo.inherits(flock.browser.WebProgressListener, flock.Base);
dojo.lang.extend(flock.browser.WebProgressListener, {
    // nsIWebProgressListener   
    STATE_STOP: Components.interfaces.nsIWebProgressListener.STATE_STOP,
    STATE_IS_NETWORK: Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK,
    STATE_START: Components.interfaces.nsIWebProgressListener.STATE_START,
    onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {/*{{{*/
        if  (aStateFlags & this.STATE_STOP
             && aStateFlags & this.STATE_IS_NETWORK) {
            this.onStopLoad();   
        }
        else if (aStateFlags & this.STATE_START) {
            this.onStartLoad();
        }


    },/*}}}*/
    onProgressChange: function (aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {/*{{{*/

    },/*}}}*/
    onLocationChange: function (aWebProgress, aRequest, aLocation) {/*{{{*/
        var url = aLocation.spec;
        
        // strip anchors
        url = url.replace(/#.*/, '');

        if (this.lastUrl != url) {
            this.onNewLocation(url);
            this.lastUrl = url;
        }
    },/*}}}*/
    onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {/*{{{*/

    },/*}}}*/
    onSecurityChange: function (aWebProgress, aRequest, aStatus) {/*{{{*/

    },/*}}}*/
    
    // flockIWebProgressListener
    lastUrl: null,
    onNewLocation: function (aUrl) {/*{{{*/

    },/*}}}*/
    onStopLoad: function () {/*{{{*/
        
    },/*}}}*/
    onStartLoad: function () {/*{{{*/
        
    }/*}}}*/
});

flock.step = function () {
    XXX(flock.step.current++);
};
flock.step.current = 0;
flock.step.reset = function () {
    flock.step.current = 0;
}


// Javascript Extensions
function $(aElementName) {/*{{{*/
	return document.getElementById(aElementName);
}/*}}}*/
function $$(aElementName) {/*{{{*/
	return top.document.getElementById(aElementName);
}/*}}}*/
function flock_escapeHTML (aString) {/*{{{*/
	var div = document.createElement('div');
	var text = document.createTextNode(aString);
	div.appendChild(text);
	return div.innerHTML;
}/*}}}*/
function flock_escape(aStr){/*{{{*/
	txt='';
	for (i=0; i < aStr.length; i++) {
		if (aStr.charCodeAt(i) < 127) {
			txt += escape(aStr[i]);
		}
		else {
			txt += aStr[i];
		}
	}
	return txt;
}/*}}}*/
function isBoolean(a) {/*{{{*/
    return typeof a == 'boolean';
}/*}}}*/
function isFunction(a) {/*{{{*/
    return typeof a == 'function';
}/*}}}*/
function isNull(a) {/*{{{*/
    return typeof a == 'object' && !a;
}/*}}}*/
function isNumber(a) {/*{{{*/
    return typeof a == 'number' && isFinite(a);
}/*}}}*/
function isObject(a) {/*{{{*/
    return (a && typeof a == 'object') || isFunction(a);
}/*}}}*/
function isString(a) {/*{{{*/
    return typeof a == 'string';
}/*}}}*/

function argsToArray(aArgs, aOffset) {/*{{{*/
    if (!aOffset) aOffset = 0;
    var out = [];
    for (var i = aOffset; i < aArgs.length; ++i) {
        out.push(aArgs[i]);
    }
    return out;
}/*}}}*/

function catchWrapper(aFunc) {/*{{{*/
    var wrappedFunc = function () {
        try {
            aFunc.apply(this, arguments);
        }
        catch (e) {
            dd(e);
        }
    }
    return wrappedFunc;
}/*}}}*/


// Getting Info About The Current Browser State
flock.browser.getCurrentUrl = function() {/*{{{*/
	var curUrl = gBrowser.mCurrentBrowser.currentURI.spec;
	XXX("CURURL",curUrl);
	return curUrl;
}/*}}}*/
flock.browser.getCurrentTitle = function() {/*{{{*/
	var title =  $('content').contentTitle;
	if (title.length < 1) {
		title = "Untitled";
	}
	return title;
}/*}}}*/

// Feed Specific
function flock_getCurrentFeeds() {/*{{{*/
	var curFeeds = gBrowser.selectedBrowser.feeds;
	return curFeeds;
}/*}}}*/
function flock_getCurrentFeedsArray() {/*{{{*/
	var curFeeds = flock_getCurrentFeeds();
	if (curFeeds) {
		var outArr = new Array();
		for (var i = 0; i < curFeeds.length; ++i) {
			outArr.push(curFeeds[i].href);  
		}
		return outArr;
	}
	return;
}/*}}}*/


// Navigation
// XXX andy: Write window service function
function flock_mostRecentNavigator() {/*{{{*/
    return flock_getWindowMediatorService().getMostRecentWindow("navigator:browser");
}/*}}}*/
function flock_goto(aUrl) {/*{{{*/
	XXX("Sending user to url",aUrl);
	var topWindow = flock_mostRecentNavigator();
	topWindow.content.location = aUrl;
}/*}}}*/
// XXX andy: I do not think this works 
function flock_gotoTab(aUrl) {/*{{{*/
	XXX("Sending user to url in new tab",aUrl);
	var topWindow = flock_mostRecentNavigator();
	var browser = topWindow.document.getElementById('content');
	browser.loadOneTab(aUrl, null, null, null, false);
}/*}}}*/
function flock_gotoAgg(aFeeds, aReturnURL) {/*{{{*/
    if (aFeeds.length == 1) {
        var url = aFeeds[0];
        var proto = url.substr(0, "http://".length).toLowerCase();

        if (proto == "http://")
            flock_goto("feed://" + url.substring("http://".length));
        else
            flock_goto("feed:" + url);
    } else {
        var outArr = new Array();
        for (var i = 0;i < aFeeds.length;i++) {
            outArr.push(escape(aFeeds[i]));
        }

        flock_goto("feeds:"+outArr.join("&"));
    }
}/*}}}*/


// User feedback
function flock_notifyUser(aMsg) {/*{{{*/
	alert(aMsg);
}/*}}}*/
// XXX andy: when we have a better timer impl we should use it here
function flock_setStatusBar(aMsg) {/*{{{*/
	if (!aMsg) aMsg = "";
	$('statusbar-display').setAttribute('label',aMsg);
	setTimeout("flock_setStatusBar()",10000);
}/*}}}*/


// Services
function flock_getService(aClassId, aIface) {/*{{{*/
    if (isString(aIface)) {
        XXX("IFACE",aIface);
        aIface = Components.interfaces[aIface];
    }
    return Components.classes[aClassId].getService(aIface);
}/*}}}*/
// XXX andy: maybe this should be in dnd.js
function flock_getDragService() {/*{{{*/
    return flock_getService('@mozilla.org/widget/dragservice;1',
                            "nsIDragService");
}/*}}}*/
function flock_getFavoritesService() {/*{{{*/
    return flock_getService(
        '@mozilla.org/rdf/datasource;1?name=flock-favorites',
        "flockIFavoritesService");
}/*}}}*/
// XXX andy: maybe we should make a feeds.js
function flock_getFeedService() {/*{{{*/
    return flock_getService('@flock.com/feed-service;1',
                            "flockIFeedService");
}/*}}}*/
function flock_getObserverService() {/*{{{*/
    return flock_getService('@mozilla.org/observer-service;1',
                            "nsIObserverService");
}/*}}}*/
function flock_getPrefService() {/*{{{*/
    return flock_getService('@mozilla.org/preferences-service;1',
                            "nsIPrefBranch");
}/*}}}*/
// XXX andy: maybe this should be in rdf.js or something?
function flock_getRDFService() {/*{{{*/
    return flock_getService('@mozilla.org/rdf/rdf-service;1',
                            "nsIRDFService")
}/*}}}*/
function flock_getStringBundleService() {/*{{{*/
    return flock_getService("@mozilla.org/intl/stringbundle;1",
                            "nsIStringBundleService");
}/*}}}*/
function flock_getWindowMediatorService() {/*{{{*/
    return flock_getService("@mozilla.org/appshell/window-mediator;1",
                            "nsIWindowMediator");
}/*}}}*/


// DataSources
function flock_getFavoritesDataSource() {/*{{{*/
    return flock_getRDFService().GetDataSource('rdf:flock-favorites');
}/*}}}*/
function flock_getHistoryDataSource() {/*{{{*/
    return flock_getRDFService().GetDataSource('rdf:history');
}/*}}}*/


// StringBundle
function flock_getStringBundle(aChromeUrl) {/*{{{*/
    return flock_getStringBundleService().createBundle(aChromeUrl);
}/*}}}*/
function flock_getBundler(aChromeUrl) {/*{{{*/
    return function (aStr) {
        var bundle = flock_getStringBundle(aChromeUrl);
        // XXX andy: maybe we want to pull this outside the function?
        //           not sure which way is most efficient
        return bundle.GetStringFromName(aStr);
    }
}/*}}}*/


// RDF
// XXX andy: maybe these should be pulled out to rdf.js or whatever
function flock_getRDFResource(aStr) {/*{{{*/
	return flock_getRDFService().GetResource(aStr);
}/*}}}*/
function flock_clearDataSources(aElement) {/*{{{*/
    var sources = aElement.database.GetDataSources();
    while (sources.hasMoreElements()) {
        var source = sources.getNext();
        aElement.database.RemoveDataSource(source);
    }
}/*}}}*/
function flock_setDataSources(aElement) {/*{{{*/
    flock_clearDataSources(aElement);
    for (var i = 1; i < arguments.length; ++i) {
        XXX(arguments[i]);
        aElement.database.AddDataSource(arguments[i]);
    }
}/*}}}*/
// DEPRECATED: use flock_getRDFResource
function flock_getResource(aStr) {/*{{{*/
    XXX('DEPRECATED','flock_getResource','use flock_getRDFResource instead');
    return flock_getRDFResource(aStr);
}/*}}}*/


// Preferences
function flock_clearPref(aKey) {/*{{{*/
	var prefService = flock_getPrefService();
	prefService.clearUserPref(aKey);
	prefService.deleteBranch(aKey);
}/*}}}*/
function flock_getCharPref(aKey) {/*{{{*/
    try {
        return flock_getPrefService().getCharPref(aKey);
    } catch (e) {
        return;
    }
}/*}}}*/
function flock_setCharPref(aKey, aValue) {/*{{{*/
    flock_getPrefService().setCharPref(aKey, aValue);
}/*}}}*/
function flock_getIntPref(aKey) {/*{{{*/
    try {
        return flock_getPrefService().getIntPref(aKey);
    } catch (e) {
        return;
    }
}/*}}}*/
function flock_setIntPref(aKey, aValue) {/*{{{*/
    flock_getPrefService().setIntPref(aKey, aValue);
}/*}}}*/
function flock_getBoolPref(aKey) {/*{{{*/
    try {
        return flock_getPrefService().getBoolPref(aKey);
    } catch (e) {
        return;
    }
}/*}}}*/
function flock_setBoolPref(aKey, aValue) {/*{{{*/
    flock_getPrefService().setBoolPref(aKey, aValue);
}/*}}}*/
// DEPRECATED: use flock_getCharPref
function flock_getPref(aKey) {/*{{{*/
    XXX('DEPRECATED','flock_getPref','use flock_getCharPref instead');
    return flock_getCharPref(aKey);
}/*}}}*/
// DEPRECATED: use flock_setCharPref
function flock_setPref(aKey) {/*{{{*/
    XXX('DEPRECATED','flock_setPref','use flock_setCharPref instead');
    flock_setCharPref(aKey);
}/*}}}*/


// DOM helpers
function flock_clearChildren(aParent) {/*{{{*/
	var childrenLength = aParent.childNodes.length;
	for (var i = 0; i < childrenLength; i++) {
		aParent.removeChild(aParent.childNodes[0]);
	}
}/*}}}*/
function flock_limitPopupChildren(aTarget, aLimit) {/*{{{*/
	for (var i = 0; i < aLimit; i++) {
		if (aTarget.childNodes[i]) {
            // XXX andy: these may be platform specific...
			aTarget.childNodes[i].removeAttribute("collapsed");
			aTarget.childNodes[i].removeAttribute("hidden");
		}
	}
}/*}}}*/
function flock_reorderPopupChildren(aTarget) {/*{{{*/
	for (var i = 1; i < arguments.length; i++) {
		aTarget.appendChild($(arguments[i]));
	}
}/*}}}*/


// Scrolly Stuffs
// XXX andy: these should probably all go into a different file,
//           anthony has some better stuff
function flock_scrollLeft(aScrollbox, aScrollBy) {/*{{{*/
	var x = {};
	var y = {};
	aScrollbox.getPosition(x, y);
	flock_scrollTo(aScrollbox,x.value - aScrollBy, y.value);
}/*}}}*/
function flock_scrollRight(aScrollbox, aScrollBy) {/*{{{*/
	var x = {};
	var y = {};
	aScrollbox.getPosition(x, y);
	flock_scrollTo(aScrollbox,x.value + aScrollBy, y.value);
}/*}}}*/
function flock_scrollTo(aScrollbox, aPosX, aPosY) {/*{{{*/
	aScrollbox.scrollTo(aPosX, aPosY);
}/*}}}*/


// Misc Deprecated Stuff That I Want To Get Rid Of
function _write_file(str_Buffer,str_Filename){/*{{{*/
	dump('Got to _write_file: ' + str_Filename + '\n');
	try{
		var obj_File = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
		obj_File.initWithPath(str_Filename);
		if(!obj_File.exists())
			obj_File.create(0x00,0644);
	}
	catch(e){
		alert(e);
	}
	try {
		var obj_Transport = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
		obj_Transport.init( obj_File, 0x04 | 0x08 | 0x10, 064, 0 );
		obj_Transport.write(str_Buffer,str_Buffer.length);
		obj_Transport.close();
	}
	catch(e){
		alert(e);
	}
}/*}}}*/
function flock_ensureRDF(aURN, aFileName) {/*{{{*/
	try {
		//Simple rdf.
		var rdf='<?xml version="1.0"?>' +
			'<RDF:RDF xmlns:NS1="http://www.flock.com/rdf#"' +
			'	   xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' +
			'  <RDF:Seq about="'+aURN+'">' +
			'  </RDF:Seq>' +
			'</RDF:RDF>';

		//Create file object in the user's profile path.
		var dirService = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties);
		var profileDir = dirService.get('ProfD', Components.interfaces.nsILocalFile);
		var fileObj = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
		fileObj.initWithPath(profileDir.path);
		fileObj.append(aFileName);

		//If it doesn't exist, write it.
		if (!fileObj.exists()) {
			_write_file(rdf, fileObj.path);
		}
		return fileObj;
	} catch (e) {
		XXX("ERROR", e);
	}
}/*}}}*/
function flock_PageQuery(q) {/*{{{*/
	if(q.length > 1) this.q = q.substring(1, q.length);
	else this.q = null;

	this.keyValuePairs = new Array();

	if(q) {
		for(var i=0; i < this.q.split("&").length; i++) {
			this.keyValuePairs[i] = this.q.split("&")[i];
		}
	}
	this.getKeyValuePairs = function() { return this.keyValuePairs; }
	this.getValue = function(s) {
		for(var j=0; j < this.keyValuePairs.length; j++) {
			if(this.keyValuePairs[j].split("=")[0] == s)
			return unescape(this.keyValuePairs[j].split("=")[1]);
		}
		return false;
	}
	this.getParameters = function() {
		var a = new Array(this.getLength());
		for(var j=0; j < this.keyValuePairs.length; j++) {
			a[j] = this.keyValuePairs[j].split("=")[0];
		}
		return a;
	}

	this.getValues = function() {
		var a = new Array(this.getLength());
		for(var j=0; j < this.keyValuePairs.length; j++) {
			a[j] = unescape(this.keyValuePairs[j].split("=")[1]);
		}
		return a;
	}
	this.getLength = function() { return this.keyValuePairs.length; }
}/*}}}*/

// Get selected text in HTML window
flock.browser.getSelection = function(){
   var focusedWindow = document.commandDispatcher.focusedWindow;
   var winWrapper = new XPCNativeWrapper(focusedWindow, 'document', 'getSelection()');
   return winWrapper.getSelection();
}

// XXX ERW This function is used by the Shelf.
// It will be deprecated soon. DON'T USE IT.

function flock_getMarkedUpSelection(aEvt, aDropData, aSession){/*{{{*/
    var windowManager = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
    var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);
    var topWindow = windowManagerInterface.getMostRecentWindow("navigator:browser");

    var data="";
    var node=aSession.sourceNode;
    var serializer = new XMLSerializer();
    var parser = new DOMParser();

    var header="Header";
    var label="Label";
    var type="document";

    // Text (from input zone or external application)
    if (aDropData.flavour.contentType == "text/unicode") {
       var location = topWindow.document.getElementById('content').mCurrentBrowser.contentWindow.location + "";
       var text = aDropData.data;
       var src;
       if (location.match(/^chrome:\/\/.+/)){
          location = "";
          src = text;
       } else {
          src = "<a href='"+location+"'>"+text+"</a>";
       }
       return { type: "document", header: location, label: text, src: src };
    }

    var contentDetector = new nsContentDetector();
    contentDetector.setTarget(node);


    // Images
    if (contentDetector.onImage) {
          header=node.alt;
          label=contentDetector.imageURL;
          type='image';
          var src='<img src="' + label + '" />';
          return { type: type, header: header, label: label, src: src };
    }
    
	//Not handling files just yet.
	if(aDropData.flavour.contentType=='application/x-moz-file'){
		return null;
	}

	//For dragging from outside the browser. Not supported for now.
	if(!node){
		return null;
	}

    // XXX('markedselection', node.nodeName.toLowerCase());
    switch(node.nodeName.toLowerCase()){
	//Handle image maps
    case 'area':
		var map='#' + node.parentNode.getAttribute('name');
		var images=topWindow.window._content.document.getElementsByTagName('img');
		dump(images.length + '\n');
		for(i=0; i<images.length; i++){
			dump('IMG: ' + images[i].src + '\n');
			if(images[i].getAttribute('usemap')==map){
				node=images[i];
			}
		}
		if(node.nodeName.toLowerCase()=='area'){
			dump('Could not find image corresponding to map ' + map + '.\n');
			return false;
		}
		//TODO: Normalize paths for node...
		return { type: type, header: header, label: label, src: serializer.serializeToString(node) };
	
	//Big hack. This is for when somebody drags the favicon onto the shelf. Without this, we get the xul source b/c we're using sourceNode.
	case 'image':
		header=topWindow.document.getElementById('content').contentTitle;
		label=topWindow.document.getElementById('content').contentDocument.URL;
		type='link';
		var src='<a href="' + label + '">' + $$('content').contentTitle + '</a>';
		return { type: type, header: header, label: label, src: src };

	//Handle flickr photos
	case 'photo':
		var doc = parser.parseFromString(node.photoHTML, "text/xml");
		img_elm=doc.getElementsByTagName('img');
		a_elm=doc.getElementsByTagName('a');
		header=img_elm[0].getAttribute('alt');
		header=header.replace(/%20/g, ' ');
		label=img_elm[0].getAttribute('src');
		return { type: "image", header: header, label: label, src: node.photoHTML };
    // Browser tabs
    case 'tab':
    case 'xul:tab':
        return { type: "link", header: node.label, label: window.top.content.document.location.toString(), src: "" };
    // Can be from the shelf itself (to ignore)
    // or from the favorite manager (to insert)
    // XXX need to find a cleaner way to do all that stuff
    case 'shelficon':
         if (node.type == 'document'){
            return { type: node.type, header: node.url, label: node.title, src: node.src, dragfrom: node.id };
         } else {
            return { type: node.type, header: node.title, label: node.url, src: node.src, dragfrom: node.id };
         }
    case 'treechildren': // ERW: Big hack to get the info I need. Will be better once we have metadata attached to each object we drag
        XXX('node.parentNode.id', node.parentNode.id);
        if (node.parentNode.id == "flock_favoriteView")
        {
            var source = aDropData.data;
            var title =  source.split(">")[1].split("<")[0];
            var url =  source.split("'")[1];
            return { type: "link", header: title, label: url, src: source };
        } else {
        //    for (i in node.parentNode){
        //        XXX('treechildren.parentnode->'+i, node.parentNode[i]);
        //    }
        }
    default:
	    //Handle selections	
	    var selection = flock.browser.getSelection();
        for (i in node){
            XXX("node."+i, node[i]);
        }
        if(selection.toString().length > 0){
            //XXX('We get the selection ', selection);
            var selection_formatted=select_src(selection);
		    title=topWindow.document.getElementById('content').contentTitle;
		    if(title.length==0) title = location;

		    var location = topWindow.document.getElementById('content').mCurrentBrowser.contentWindow.location + "";
		    if (location.match(/^chrome:\/\/.+/)) location = "";

		    //TODO: Normalize paths for selection...
		    data = '<blockquote cite="' + location + '">' + selection_formatted.html + "</blockquote>";
		    data += '<p class="citation"><cite cite="' + location + '"><a href="' + location + '">' + title + '</a>' + "</cite></p><br/>";
		    header=location;
		    label=selection;
		    return { type: 'document', header: header, label: label, src: data };
	    }
	    else{
		    if(node.nodeName.toString().toLowerCase()=='#text'){
			    node=node.parentNode;
		    }

		    //If it's an img, make sure the src attribute is the full path
		    if(node.src){
			    node.setAttribute('src',node.src);
			    type='image';
			    label=node.src;
			    header=node.src;
			    if(node.getAttribute('alt')){
				    header=node.getAttribute('alt');
			    }
		    }
		    //If it's a link make sure the href attribute is the full path
		    if(node.href){
			    node.setAttribute('href',node.href);
			    type='link';
			    label=node.href;
			    header=node.textContent;
		    }
		    return { type: type, header: header, label: label, src: serializer.serializeToString(node) };
	    }
    }
    XXX('flock_getMarkedUpSelection: unable to understand this dropdata!!');
    return aDropData.data;

}/*}}}*/
// XXX daryl: Ok, this needs a whole lot of cleaning up. 
// Ripped from viewPartialSource.js in global in core source and 
// hacked a smidgin.
function select_src(selection){/*{{{*/
	var range = selection.getRangeAt(0);
	var ancestorContainer = range.commonAncestorContainer;
	var doc = ancestorContainer.ownerDocument;

	var startContainer = range.startContainer;
	var endContainer = range.endContainer;
	var startOffset = range.startOffset;
	var endOffset = range.endOffset;

	// let the ancestor be an element
	if (ancestorContainer.nodeType == Node.TEXT_NODE ||
			ancestorContainer.nodeType == Node.CDATA_SECTION_NODE)
		ancestorContainer = ancestorContainer.parentNode;

	// for selectAll, let's use the entire document, including <html>...</html>
	// @see DocumentViewerImpl::SelectAll() for how selectAll is implemented
	try {
		if (ancestorContainer == doc.body)
			ancestorContainer = doc.documentElement;
	} catch (e) { }

	// each path is a "child sequence" (a.k.a. "tumbler") that
	// descends from the ancestor down to the boundary point
	var startPath = getPath(ancestorContainer, startContainer);
	var endPath = getPath(ancestorContainer, endContainer);

	// clone the fragment of interest and reset everything to be relative to it
	// note: it is with the clone that we operate from now on
	ancestorContainer = ancestorContainer.cloneNode(true);
	startContainer = ancestorContainer;
	endContainer = ancestorContainer;
	var i;
	for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) {
		startContainer = startContainer.childNodes.item(startPath[i]);
	}
	for (i = endPath ? endPath.length-1 : -1; i >= 0; i--) {
		endContainer = endContainer.childNodes.item(endPath[i]);
	}

	// add special markers to record the extent of the selection
	// note: |startOffset| and |endOffset| are interpreted either as
	// offsets in the text data or as child indices (see the Range spec)
	// (here, munging the end point first to keep the start point safe...)
	var tmpNode;
	if (endContainer.nodeType == Node.TEXT_NODE ||
			endContainer.nodeType == Node.CDATA_SECTION_NODE) {
		// do some extra tweaks to try to avoid the view-source output to look like
		// ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
		// To get a neat output, the idea here is to remap the end point from:
		// 1. ...<tag>]...	 to	 ...]<tag>...
		// 2. ...]</tag>...	to	 ...</tag>]...
		if ((endOffset > 0 && endOffset < endContainer.data.length) ||
				!endContainer.parentNode || !endContainer.parentNode.parentNode)
			endContainer.insertData(endOffset, MARK_SELECTION_END);
		else {
			tmpNode = doc.createTextNode(MARK_SELECTION_END);
			endContainer = endContainer.parentNode;
			if (endOffset == 0)
				endContainer.parentNode.insertBefore(tmpNode, endContainer);
			else
				endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
		}
	}
	else {
		tmpNode = doc.createTextNode(MARK_SELECTION_END);
		endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
	}

	if (startContainer.nodeType == Node.TEXT_NODE ||
			startContainer.nodeType == Node.CDATA_SECTION_NODE) {
		// do some extra tweaks to try to avoid the view-source output to look like
		// ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
		// To get a neat output, the idea here is to remap the start point from:
		// 1. ...<tag>[...	 to	 ...[<tag>...
		// 2. ...[</tag>...	to	 ...</tag>[...
		if ((startOffset > 0 && startOffset < startContainer.data.length) ||
				!startContainer.parentNode || !startContainer.parentNode.parentNode ||
				startContainer != startContainer.parentNode.lastChild)
			startContainer.insertData(startOffset, MARK_SELECTION_START);
		else {
			tmpNode = doc.createTextNode(MARK_SELECTION_START);
			startContainer = startContainer.parentNode;
			if (startOffset == 0)
				startContainer.parentNode.insertBefore(tmpNode, startContainer);
			else
				startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
		}
	}
	else {
		tmpNode = doc.createTextNode(MARK_SELECTION_START);
		startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
	}


	// now extract and display the syntax highlighted source
	tmpNode = doc.createElementNS(NS_XHTML, 'div');
	tmpNode.appendChild(ancestorContainer);

	html=tmpNode.innerHTML;
	txt=tmpNode.textContent;
	var html_start=html.indexOf(MARK_SELECTION_START) + 5; //Because there are five characters in that const.
	var html_end=html.indexOf(MARK_SELECTION_END);
	html=html.substring(html_start,html_end);
	var txt_start=txt.indexOf(MARK_SELECTION_START) + 5; //Because there are five characters in that const.
	var txt_end=txt.indexOf(MARK_SELECTION_END);
	txt=txt.substring(txt_start,txt_end);
	//dump('START: ' + mystart + '\nEND: ' + myend + '\nVAL: --' + html + '--\n');
	dump('TXT: ' + txt + '\n');
	dump('HTML: ' + html + '\n');
	return { html: html, txt: txt };


dump('NODE: \n' + tmpNode.innerHTML + '\n');
}/*}}}*/
// XXX daryl: Also ripped from viewPartialSource.js. Probably a good 
// idea to keep this here, in case it changes in the core source.
function getPath(ancestor, node)/*{{{*/
{
	var n = node;
	var p = n.parentNode;
	if (n == ancestor || !p)
		return null;
	var path = new Array();
	if (!path)
		return null;
	do {
		for (var i = 0; i < p.childNodes.length; i++) {
			if (p.childNodes.item(i) == n) {
				path.push(i);
				break;
			}
		}
		n = p;
		p = n.parentNode;
	} while (n != ancestor && p);
	return path;
}

function flock_parseDate(datestr)
{
    var date = null;

    datestr = datestr.replace(/^\s*|\s*$/g, '');

    var dateval = Date.parse(datestr);

    if (dateval) {
        date = new Date(dateval);
    } else {
        /* this is a parser for the W3C subset of ISO 8601 dates
         * http://www.w3.org/TR/NOTE-datetime
         */
        const re_iso8601 = new RegExp(
            "^(\\d{4})(-(\\d{2,3})(-(\\d{2})" +
            "(T(\\d{2}):(\\d{2})(:(\\d{2})(\\.(\\d+))?)?" +
            "(Z|(([-+])(\\d{2}):(\\d{2})))?)?)?)?$");

        var m = datestr.match(re_iso8601);

        if (m) {
            date = new Date(Date.UTC(m[1]))

            m[3]  && date.setUTCMonth   (m[3] - 1);
            m[5]  && date.setUTCDate    (m[5]);
            m[7]  && date.setUTCHours   (m[7]);
            m[8]  && date.setUTCMinutes (m[8]);
            m[10] && date.setUTCSeconds (m[10]);

            m[12] && date.setUTCMilliseconds(('0.' + m[12]) * 1000);

            if (m[14]) {
                var tz_off = m[16] * 60 + Number(m[17]);
                tz_off *= m[15] == '-' ? -1 : 1;
                tz_off *= 60 * 1000;

                date.setTime(date.getTime() - tz_off);
            }

        }
    }

    return date;
/*
    if (date)
        return date.toString();
    else
        return null;
*/
}/*}}}*/

function flock_getPhotoSvc() {
    const CID = '@mozilla.org/rdf/datasource;1?name=flock-photo';
    const IID = Components.interfaces.flockIPhotoService;
    var svc = Components.classes[CID].getService(IID);
    return svc;
}

flock.tags = {};

flock.tags.addTagToFormHistory = function (aTag) {
    flock.xpcom.formHistoryService.addEntry("flock-tags", aTag);
}

flock.tags.addTagElementsToFormHistory = function (aElements) {
    for(var i=0;i<aElements.length;++i) {
        flock.tags.addTagToFormHistory(aElements[i].value);
    }
}
