// Determining whether or not to use the cache
var RPCachePreferenceObserver =
{
    mUseCache: true,
    mPrefCache: "network.http.use-cache",
    mObserveCacheTopic: "nsPref:changed",
    
    // --------------------------------------------------------------------------------------------
    // nsISupports --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    QueryInterface: function(iid)
    {
        if (!iid.equals(Components.interfaces.nsISupports) &&
            !iid.equals(Components.interfaces.nsIObserver)) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        return this;
    },
    
    // --------------------------------------------------------------------------------------------
    // nsIObserver --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    // void nsIObserver::observe(in nsISupports, in string, in wstring)
    observe: function(oiSupports, strTopic, strData)
    {
        if (strTopic == this.mObserveCacheTopic && strData == this.mPrefCache)
        {
            try
            {
                if (!this.mPrefManager)
                    this.mPrefManager = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
                this.mUseCache = this.mPrefManager.getBoolPref(this.mPrefCache);
            }
            catch(e)
            {
                RealNetworks.Debug.fPrint("Failed in getting pref changes for cache");
            }
        }
    },
    
    init: function()
    {
        try
        {
            if (!this.mPrefManager)
                this.mPrefManager = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
            this.mUseCache = this.mPrefManager.getBoolPref(this.mPrefCache);
        }
        catch(e)
        {
            RealNetworks.Debug.fPrint("Error in obtaining the cache preference");
        }
        
        try
        {
            var observerServices = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
            observerServices.addObserver(RPCachePreferenceObserver, this.mObserveCacheTopic, false);
        }
        catch(e)
        {
            RealNetworks.Debug.fPrint("Failed to add an observer to listen to the cache pref changes");
        }
    }
};
RPCachePreferenceObserver.init();

function AddRMEmbedObserver(oEmbedObject)
{
    var observer = null;
    if (oEmbedObject)
    {
        try
        {
            var player = null;
            if (oEmbedObject.wrappedJSObject)
            {
                try
                {
                    player = oEmbedObject.wrappedJSObject.QueryInterface(Components.interfaces.nsIObserverService);
                }
                catch(e2)
                {
                    player = null;
                }

                if (!player && oEmbedObject.wrappedJSObject.addObserver2)
                {
                    player = oEmbedObject.wrappedJSObject;
                }
            }
            
            if (player)
            {
                observer = new CRPEmbedObserver(oEmbedObject);
                if (observer)
                {
		    if(player.addObserver2)
		    {
    			player.addObserver2(observer, "", false);
		    }
		    else if(player.addObserver)
		    {
			player.addObserver(observer, "", false);
		    }
                }
            }
        }
        catch(e)
        {
        }
    }
    return observer;
}

function CRPEmbedObserver(oEmbedObject)
{
    this.embed = oEmbedObject;
}

CRPEmbedObserver.prototype =
{
    isRecordable: null,
    
    // --------------------------------------------------------------------------------------------
    // nsISupports --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    QueryInterface: function(iid)
    {
        if (!iid.equals(Components.interfaces.nsISupports) &&
            !iid.equals(Components.interfaces.nsIObserver)) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        return this;
    },
    
    // --------------------------------------------------------------------------------------------
    // nsIObserver --------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    // void nsIObserver::observe(in nsISupports, in string, in wstring)
    observe: function(oiSupports, strTopic, strData)
    {
        var bModified = false;
        if (strTopic == "legacy-player-on-recordable-state-change")
        {
            this.isRecordable = (strData == "1");
            bModified = true;
        }
        else if (strTopic == "LicenseInfo")
        {
            this.isRecordable = (!strData || strData == '' || strData.toLowerCase().indexOf('allowdownload=1') != -1);
            bModified = true;
        }
        
        if (bModified)
        {
            try
            {
                var oBrowser = RealNetworks.Utils.GetBrowserForDocument(this.embed.wrappedJSObject.ownerDocument);
                var oURIListener = g_dTabs[oBrowser.RealPlayerBrowserPluginUniqueValue];
                if (oURIListener)
                {
                    oURIListener.notifyOfRecordableObjects();
                }
            }
            catch(e)
            {}
        }
    },
    
    hasRecordableState: function()
    {
        return (this.isRecordable != null);
    }
};

function CRecordableObject(oElem)
{
    this.elem = oElem;
    this.observer = AddRMEmbedObserver(this.elem);
    
    if (!this.observer && this.elem.wrappedJSObject.type == "application/vnd.rn-realav")
    {
	if(!g_DelayedRecordableObjects)
	{
	    g_DelayedRecordableObjects = new Array();
	}
	if(g_DelayedRecordableObjects)
	{
	    g_DelayedRecordableObjects.push(this);
	    setTimeout("DelayRecordableObjectProcessing()", 300);
	}
    }
}

CRecordableObject.prototype =
{
    elem: null,
    
    getElement: function()
    {
        return this.elem;
    },
    
    hasRecordableState: function()
    {
        if (this.observer)
        {
            return this.observer.hasRecordableState();
        }
        return false;
    },
    
    isRecordable: function()
    {
        if (this.observer)
        {
            return this.observer.isRecordable;
        }
        return true;
    }
};

// Initializing the Browser Record XPCOM component
function createXPCOMBrowserRecord()
{
    const cid = "@real.com/RealPlayer/BrowserRecord;1";
    var oBrowserRecord = Components.classes[cid].createInstance();

    oBrowserRecord = oBrowserRecord.QueryInterface(Components.interfaces.nsIRPBrowserRecord);

    // Creating AccessibleRetrieval object
    var oAccessibleRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].getService(Components.interfaces.nsIAccessibleRetrieval);
    oBrowserRecord.SetAccessibleRetrieval(oAccessibleRetrieval);
    return oBrowserRecord;
}

// CRPDocRequestStruct
function CRPDocRequestStruct(oDoc)
{
    this.oDoc = oDoc;
    this.asRequests = new Array();
}

// CRPUrlInfo
function CRPUrlInfo(sUrl, sMimeType, iAssociatedRecordableObjectIndex)
{
    this.sUrl = sUrl;
    this.sMimeType = sMimeType;
    this.iAssociatedRecordableObjectIndex = iAssociatedRecordableObjectIndex;
}

// CRPURIListener
var g_dTabs = {};
var g_Counter = 0;
var g_LastTab = null; // Need to keep track of the last tab, for tab switching
var g_DelayedRecordableObjects = null;

function startURIListener()
{
    var oURIListener = new CRPURIListener();
    oURIListener.init();
    return oURIListener;
}

function stopURIListener(oURIListener)
{
    oURIListener.terminate();
    delete oURIListener;
    oURIListener = null;
}

function CRPURIListener()
{
}

CRPURIListener.prototype =
{
    init: function()
    {
        this.addToListener();
        this.oXPCOMBrowserRecord = createXPCOMBrowserRecord();
        
        try
        {
            this.oXPCOMBrowserRecord.QueryInterface(Components.interfaces.nsIRPBrowserRecord);
            this.oXPCOMBrowserRecord.Initialize(this);
        }
        catch(e)
        {
            RealNetworks.Debug.fPrint("CRPURIListener.init:\n"+e);
        }
        
        this.oRelatedBrowser = null;
        this.RealPlayerBrowserPluginUniqueValue = g_Counter++;
        
        this.reset(true);
    },
   
    terminate: function()
    {
        this.removeFromListener();
        
        this.reset(false);
        
        delete this.oXPCOMBrowserRecord;
        this.oXPCOMBrowserRecord = null;
    },
    
    reset: function(bReinitialize)
    {
        if (this.aoDocRequestStructList && this.aoDocRequestStructList.length > 0)
        {
            for (var x = 0; x < this.aoDocRequestStructList.length; ++x)
            {
                try
                {
                    this.aoDocRequestStructList[x].oDoc = null;
                    delete this.aoDocRequestStructList[x].asRequests;
                    this.aoDocRequestStructList[x].asRequests = null;
                    delete this.aoDocRequestStructList[x];
                    this.aoDocRequestStructList[x] = null;
                }
                catch (e)
                {
                    RealNetworks.Debug.fPrint("terminate:\n"+e);
                }
            }
            delete this.aoDocRequestStructList;
            this.aoDocRequestStructList = null;
        }
        if (bReinitialize) {this.aoDocRequestStructList = new Array();}
        
        delete this.aoRecordableObjects;
        this.aoRecordableObjects = null;
        if (bReinitialize) {this.aoRecordableObjects = new Array();}
        
        delete this.aoUnmatchedRecordableFiles;
        this.aoUnmatchedRecordableFiles = null;
        if (bReinitialize) {this.aoUnmatchedRecordableFiles = new Array();}
        
        this.bMutationEvent = false;
        RealNetworks.Debug.fPrint("URIListener Reset");
    },
    
    // --------------------------------------------------------------------------------------------
    // nsISupports --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    QueryInterface: function(iid)
    {
        if (!iid.equals(Components.interfaces.nsISupports) &&
            !iid.equals(Components.interfaces.nsIHttpNotify) &&
            !iid.equals(Components.interfaces.nsIObserver) &&
            !iid.equals(Components.interfaces.nsIRPBrowserRecordJS)) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        return this;
    },
    
    // --------------------------------------------------------------------------------------------
    // nsIHttpNotify ------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    // nsIHttpNotify::onModifyRequest(in nsIHttpChannel)
    // This gets hit every time a new request is sent
    onModifyRequest: function (oiHttp)
    {
        try
        {
            var cacheSession = this.getCacheSession();
            var uri = oiHttp.URI.asciiSpec;
            if (cacheSession && uri)
            {
                var cacheEntryDescriptor = cacheSession.openCacheEntry(uri, Components.interfaces.nsICache.ACCESS_READ, false);
                if (cacheEntryDescriptor)
                {
                    // This is a synchronous read of the cache entry
                    try
                    {
                        var mimeType = "";
                        try
                        {
                            // Reading the 4 CC of the cache file to obtain the mimetype
                            var cacheEntryInputStream = cacheEntryDescriptor.openInputStream(0);
                            mimeType = this.oXPCOMBrowserRecord.getStreamMimeType(cacheEntryInputStream);
                        }
                        catch(e3)
                        {
                        }
                        // if we were unable to get the mimetype from the stream, just use the contentType from the header.
                        if (!mimeType)
                            mimeType = RealNetworks.Utils.GetContentTypeFromResponseHeader(uri, cacheEntryDescriptor.getMetaDataElement("response-head"));
                        
                        var originalURI = oiHttp.originalURI ? oiHttp.originalURI.asciiSpec : null;
                        var referrer = oiHttp.referrer ? oiHttp.referrer.asciiSpec : null;
                        var oDocRequestStruct = this.getDocRequestStructFromNSIHttpRequestElseCreateOne(oiHttp);
                        this.checkRequest(uri, originalURI, referrer, mimeType, oDocRequestStruct);
                    }
                    catch(e2)
                    {
                    }
                    cacheEntryDescriptor.close();
                }
            }
        }
        catch(e)
        {
        }
    },
    
    // nsIHttpNotify::onExamineResponse(in nsIHttpChannel)
    // This gets hit everytime we get a response back from the server.  This does not include cached items!
    onExamineResponse: function(oiHttp)
    {
        this.onExamineResponse2(oiHttp, oiHttp.contentType, oiHttp.referrer ? oiHttp.referrer.asciiSpec : null);
    },
    
    onExamineResponse2: function(oiHttp, contentType, referrer)
    {
        try
        {
            // Need to ignore https because when I try to access the notificationCallbacks of the https channel
            // it freezes up.
            var uri = oiHttp.URI.asciiSpec;
            if (oiHttp.URI.scheme != "https")
            {
                var originalURI = oiHttp.originalURI ? oiHttp.originalURI.asciiSpec : null;
                var oDocRequestStruct = this.getDocRequestStructFromNSIHttpRequestElseCreateOne(oiHttp);
                this.checkRequest(uri, originalURI, referrer, contentType, oDocRequestStruct);
            }
        }
        catch(e)
        {
            RealNetworks.Debug.fPrint("onExamineResponse error:"+e);
        }
    },

    // --------------------------------------------------------------------------------------------
    // nsIObserver --------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    // void nsIObserver::observe(in nsISupports, in string, in wstring)
    observe: function(oiSupports, strTopic, strData)
    {
        if (strTopic == "http-on-modify-request")
        {
            oiSupports = oiSupports.QueryInterface(Components.interfaces.nsIHttpChannel);
            this.onModifyRequest(oiSupports);
        }
        else if (strTopic == "http-on-examine-response")
        {
            oiSupports = oiSupports.QueryInterface(Components.interfaces.nsIHttpChannel);
            this.onExamineResponse(oiSupports);
        }
        else if (strTopic == "rp-http-on-examine-channel-content-type")
        {
            if (strData)
            {
                oiSupports = oiSupports.QueryInterface(Components.interfaces.nsIHttpChannel);
                // Sending a null referrer because for some reason when I access the referrer attribute
                // of the http request, it crashes firefox after navigating through several pages.
                this.onExamineResponse2(oiSupports, strData, null);
            }
        }
    },
    
    // --------------------------------------------------------------------------------------------
    // nsIRPBrowserRecordJS -----------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    // long GetRecordableObjectCount()
    GetRecordableObjectCount: function()
    {
        return (this.aoRecordableObjects) ? this.aoRecordableObjects.length : 0;
    },
    
    // string GetRecordableObjectUrl(in long iIndex)
    GetRecordableObjectUrl: function(iIndex, bGetFinalUrl)
    {
        var sRetUrl = "";
        var oElem = this.getRecordableElement(iIndex);
        
        try
        {
            var bIsFlash = false;
            try
            {
                bIsFlash = (oElem.wrappedJSObject && oElem.wrappedJSObject.__RP_Url_Callback_Vid && oElem.wrappedJSObject.__RP_Url_Callback_Vid.length > 0);
            }
            catch(e2) {}
            
            // TODO: instead of directly using this attribute, the vid url should be added to the url list.
            // We should do it like that to avoid any confusion with which one has the actual url and which one doesn't.
            // For the preview, the current implementation should be good enough.
            if (RealNetworks.RPBrowserEmbeddedObjectUtils.IsMoveNetworksElement(oElem))
            {
                // This is a hack for MoveNetworks content.  Since we are unable to record it at the moment, we will
                // just return a fake URL
                sRetUrl = 'http://www.real.com/file_does_not_exist.qmx';
            }
            else if (bIsFlash)
            {
                var sParentUrl = oElem.wrappedJSObject.__RP_Url_Callback_Parent;
                if (oElem.wrappedJSObject.__RP_Connection_Callback_Url && oElem.wrappedJSObject.__RP_TotalBytes_Callback_Bytes && oElem.wrappedJSObject.__RP_TotalBytes_Callback_Bytes == "0" && oElem.wrappedJSObject.__RP_Connection_Callback_Url.length > 0)
                    sParentUrl = oElem.wrappedJSObject.__RP_Connection_Callback_Url;
                sRetUrl = RealNetworks.Utils.ResolveUrl(sParentUrl, oElem.wrappedJSObject.__RP_Url_Callback_Vid);
            }
            else
            {
                sRetUrl = RealNetworks.Utils.GetElementUrl(oElem, true);
            
                if (bGetFinalUrl)
                {
                    aUrlAndMimeType = this.findRedirectedURLAndMimeType(sRetUrl, oElem.ownerDocument);
                    if (aUrlAndMimeType)
                        sRetUrl = aUrlAndMimeType[0];
                }
            }
        }
        catch(e) {}
        RealNetworks.Debug.fPrint("GetRecordableObjectUrl: "+iIndex+"\n"+sRetUrl);
        
        return ""+sRetUrl;
    },
    
    // string GetRecordableObjectMimeType(in long iIndex)
    GetRecordableObjectMimeType: function(iIndex)
    {
        // TODO: redo this so that you get the URL mimetype
        var sRetMimeType = "";
        var oElem = this.getRecordableElement(iIndex);
        
        try
        {
            var bIsFlash = false;
            try
            {
                bIsFlash = (oElem.wrappedJSObject && oElem.wrappedJSObject.__RP_Url_Callback_Vid && oElem.wrappedJSObject.__RP_Url_Callback_Vid.length > 0);
            }
            catch(e2) {}
            // TODO: instead of directly using this attribute, the vid mimetype should be added to the url list.
            // We should do it like that to avoid any confusion with which one has the actual url and which one doesn't.
            // For the preview, the current implementation should be good enough.
            if (RealNetworks.RPBrowserEmbeddedObjectUtils.IsMoveNetworksElement(oElem))
            {
                // This is a hack for MoveNetworks content.  Since we are unable to record it at the moment, we will
                // just return a unrecordable mimetype
                sRetMimeType = 'application/qmx';
            }
            else if (bIsFlash)
            {
                if (RealNetworks.Utils.IsRTMP(oElem))
                    sRetMimeType = 'application/x-fcs';
                else
                    sRetMimeType = 'video/flv';
            }
            else
            {
                var sRetUrl = RealNetworks.Utils.GetElementUrl(oElem, true);
                if (oElem instanceof HTMLEmbedElement)
                {
                    aUrlAndMimeType = this.findRedirectedURLAndMimeType(sRetUrl, oElem.ownerDocument);
                    
                    if (aUrlAndMimeType && aUrlAndMimeType[1])
                    {
                        sRetMimeType = aUrlAndMimeType[1];
                    }
                    else
                    {
                        sRetMimeType = oElem.type;
                    }
                }
                else if (oElem instanceof HTMLObjectElement)
                {
                    aUrlAndMimeType = this.findRedirectedURLAndMimeType(sRetUrl, oElem.ownerDocument);
                    
                    if (aUrlAndMimeType && aUrlAndMimeType[1])
                    {
                        sRetMimeType = aUrlAndMimeType[1];
                    }
                    else
                    {
                        var classid = oElem.getAttribute("classid");
                        if (classid && classid.length > 0)
                        {
                            sRetMimeType = RealNetworks.RPBrowserEmbeddedObjectUtils.GetMimeTypeFromClsid(classid);
                        }
                        else
                        {
                            sRetMimeType = oElem.type;
                        }
                    }
                }
            }
        }
        catch (e) {}
        
        RealNetworks.Debug.fPrint("The Mimetype: ("+iIndex+") - "+sRetMimeType);
        return sRetMimeType;
    },
    
    // string GetRecordableObjectProperty(in long iIndex, in string pPropertyName)
    GetRecordableObjectProperty: function(iIndex, pPropertyName)
    {
        var retPropertyValue = null;
        var oElem = this.getRecordableElement(iIndex);
        if (oElem)
        {
            if (oElem.hasAttribute(pPropertyName))
                retPropertyValue = oElem.getAttribute(pPropertyName);
            else if (oElem instanceof HTMLObjectElement)
                retPropertyValue = RealNetworks.Utils.FindParamElementWithFollowingId(oElem, pPropertyName);
        }
        return retPropertyValue ? ("" + retPropertyValue) : null;
    },
    
    // void GetRecordableObjectRect(in long iIndex, out long top, out long bottom, out long left, out long right)
    GetRecordableObjectRect: function(iIndex, top, bottom, left, right)
    {
        var oElem = this.getRecordableElement(iIndex);
        if (oElem)
        {
            var aOffset = RealNetworks.Utils.FindElementRectOffsetToMainParentDocument(oElem, false);
            top.value = aOffset[0];
            bottom.value = aOffset[1];
            left.value = aOffset[2];
            right.value = aOffset[3];
            
            try
            {
                if (oElem.wrappedJSObject && oElem.wrappedJSObject.__RP_Coord_Callback_Left && oElem.wrappedJSObject.__RP_Coord_Callback_Top && oElem.wrappedJSObject.__RP_Coord_Callback_Right && oElem.wrappedJSObject.__RP_Coord_Callback_Bottom)
                {
                    bottom.value = parseInt(top.value) + parseInt(oElem.wrappedJSObject.__RP_Coord_Callback_Bottom);
                    top.value = parseInt(top.value) + parseInt(oElem.wrappedJSObject.__RP_Coord_Callback_Top);
                    right.value = parseInt(left.value) + parseInt(oElem.wrappedJSObject.__RP_Coord_Callback_Right);
                    left.value = parseInt(left.value) + parseInt(oElem.wrappedJSObject.__RP_Coord_Callback_Left);
                }
            }
            catch(e) {}
        }
        else
        {
            top.value = 0;
            bottom.value = 0;
            left.value = 0;
            right.value = 0;
        }
    },
    
    // void GetVisibleRecordableObjectRect(in long iIndex, out long top, out long bottom, out long left, out long right)
    GetVisibleRecordableObjectRect: function(iIndex, top, bottom, left, right)
    {
        var oElem = this.getRecordableElement(iIndex);
        if (oElem)
        {
            var aOffset = RealNetworks.Utils.FindElementRectOffsetToMainParentDocument(oElem, true);
            top.value = aOffset[0];
            bottom.value = aOffset[1];
            left.value = aOffset[2];
            right.value = aOffset[3];
            
            try
            {
                if (oElem.wrappedJSObject && oElem.wrappedJSObject.__RP_Coord_Callback_Left && oElem.wrappedJSObject.__RP_Coord_Callback_Top && oElem.wrappedJSObject.__RP_Coord_Callback_Right && oElem.wrappedJSObject.__RP_Coord_Callback_Bottom)
                {
                    bottom.value = parseInt(top.value) + parseInt(oElem.wrappedJSObject.__RP_Coord_Callback_Bottom);
                    top.value = parseInt(top.value) + parseInt(oElem.wrappedJSObject.__RP_Coord_Callback_Top);
                    right.value = parseInt(left.value) + parseInt(oElem.wrappedJSObject.__RP_Coord_Callback_Right);
                    left.value = parseInt(left.value) + parseInt(oElem.wrappedJSObject.__RP_Coord_Callback_Left);
                }
            }
            catch (e) {}
        }
        else
        {
            top.value = 0;
            bottom.value = 0;
            left.value = 0;
            right.value = 0;
        }
    },
    
    // nsIDOMHTMLDocument GetCurrentHTMLDocument()
    GetCurrentHTMLDocument: function()
    {
        if (this.oRelatedBrowser)
            return this.oRelatedBrowser.contentWindow.top.document;
        return null;
    },
    
    // string GetDocumentLocation()
    GetDocumentLocation: function()
    {
        var sRetLocation = "";
        if (this.aoDocRequestStructList && this.aoDocRequestStructList[0] && this.aoDocRequestStructList[0].oDoc)
        {
            var oDoc = this.aoDocRequestStructList[0].oDoc;
            while (oDoc.ownerDocument && !(oDoc === oDoc.ownerDocument))
            {
                oDoc = oDoc.ownerDocument;
            }
            
            if (oDoc && oDoc.location)
                sRetLocation = oDoc.location.toString();
        }
        return sRetLocation;
    },
    
    // boolean OpenNewDocument(in string pDocumentUrl, in string pDocumentTarget)
    OpenNewDocument: function(sDocumentUrl, sDocumentTarget)
    {
        var theWindow = window.open(sDocumentUrl, sDocumentTarget);
        if (theWindow)
            return true;
        return false;
    },
    
    // long GetRecordableFileCount()
    GetRecordableFileCount: function()
    {
        return (this.aoUnmatchedRecordableFiles) ? this.aoUnmatchedRecordableFiles.length : 0;
    },
    
    // string GetRecordableFileUrl(in long iIndex)
    GetRecordableFileUrl: function(iIndex)
    {
        var sFileUrl = "";
        if (this.aoUnmatchedRecordableFiles && this.aoUnmatchedRecordableFiles[iIndex])
        {
            sFileUrl = this.aoUnmatchedRecordableFiles[iIndex].sUrl;
            RealNetworks.Debug.fPrint("GetRecordableFileUrl:"+sFileUrl);
        }
        return sFileUrl ? sFileUrl : "";
    },
    
    // string GetRecordableFileMimeType(in long iIndex)
    GetRecordableFileMimeType: function(iIndex)
    {
        var sMimeType = "";
        if (this.aoUnmatchedRecordableFiles && this.aoUnmatchedRecordableFiles[iIndex])
        {
            sMimeType = this.aoUnmatchedRecordableFiles[iIndex].sMimeType;
        }
        return sMimeType ? sMimeType : "";
    },
    
    // long GetRecordableObjectFromFile(in long iFileIndex)
    GetRecordableObjectFromFile: function(iFileIndex)
    {
        var iIndex = -1;
        if (this.aoUnmatchedRecordableFiles && this.aoUnmatchedRecordableFiles[iFileIndex])
        {
            iIndex = this.aoUnmatchedRecordableFiles[iFileIndex].iAssociatedRecordableObjectIndex;
        }
        return iIndex;
    },
    
    // boolean IsRecordable(in long iIndex)
    IsRecordable: function(iIndex)
    {
        var bIsRecordable = true;
        var recordableObject = this.aoRecordableObjects[iIndex];
        var oElem = null;
        if (recordableObject)
            oElem = recordableObject.getElement();

        try
        {
            if (recordableObject && recordableObject.hasRecordableState())
            {
                bIsRecordable = recordableObject.isRecordable();
            }
            else if (oElem)
            {
                bIsRecordable = true;
                if (RealNetworks.RPBrowserEmbeddedObjectUtils.IsMoveNetworksElement(oElem))
                {
                    bIsRecordable = false;
                }
            }
        }
        catch(e)
        {}
        return bIsRecordable;
    },
    
    // --------------------------------------------------------------------------------------------
    // Internal Functions -------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    addToListener: function()
    {
        // Should be a new version of  Mozilla/Phoenix (after september 15, 2003)
        var observerServices = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
        observerServices.addObserver(this, "http-on-modify-request", false);
        observerServices.addObserver(this, "http-on-examine-response", false);
        observerServices.addObserver(this, "rp-http-on-examine-channel-content-type", false);
    },
   
    removeFromListener: function()
    {
        var observerServices = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
        observerServices.removeObserver(this, "http-on-modify-request");
        observerServices.removeObserver(this, "http-on-examine-response");
        observerServices.removeObserver(this, "rp-http-on-examine-channel-content-type");
    },
    
    getDocRequestStructFromDoc: function(oDoc)
    {
        var oDocRequestStruct = null;
        if (oDoc)
        {
            for (var x = 0; x < this.aoDocRequestStructList.length; ++x)
            {
                if (this.aoDocRequestStructList[x] && (this.aoDocRequestStructList[x].oDoc === oDoc))
                {
                    oDocRequestStruct = this.aoDocRequestStructList[x];
                    break;
                }
            }
        }
        return oDocRequestStruct;
    },
    
    // There have been bugs appearing where the extension doesn't seem to work.  For example,
    // previous metacafe, veoh, etc, worked but for some reason it doesn't work, then next time it
    // works.  This is due to the fact that I was not adding the document into the document list
    // IF the document onload event was not hit first before this function.  To fix that, if the
    // document is not in my list before getDocRequestStructFromNSIHttpRequestElseCreateOne is hit,
    // then it will add the document of the http request to the list IF AND ONLY IF the document is
    // an instance of and HTMLDocument.
    getDocRequestStructFromNSIHttpRequestElseCreateOne: function(oiHttp)
    {
        var oDocRequestStruct = null;
        try
        {
            var oDoc = null;
            var notificationCallbacks = oiHttp.notificationCallbacks;
            if (!notificationCallbacks && oiHttp.loadGroup)
                notificationCallbacks = oiHttp.loadGroup.notificationCallbacks;
            if (notificationCallbacks)
            {
                // If there are multiple documents in a tab (frames), then we have to associate
                // this HTTP Request to the actual document, so we can do that by getting the
                // docshell associated with this request
                try
                {
                    var iDocShell = notificationCallbacks.getInterface(Components.interfaces.nsIDocShell);
                    if (iDocShell && iDocShell.contentViewer)
                        oDoc = iDocShell.contentViewer.DOMDocument;
                }
                catch(e2)
                {}
            }
            
            if (!oDoc)
            {
                // If we failed to get the document via the associated docshell, then just default
                // to the page directly associated with the tab.
                var oTab = RealNetworks.Utils.FindAssociatedTabFromChannel(oiHttp);
                if (oTab)
                {
                    var oBrowser = getBrowser().getBrowserForTab(oTab);
                    if (oBrowser)
                    {
                        oDoc = oBrowser.contentDocument;
                    }
                }
            }
            
            oDocRequestStruct = this.getDocRequestStructFromDoc(oDoc);
            
            if (!oDocRequestStruct && (oDoc instanceof HTMLDocument))
            {
                // I need to make sure this document is from the tab that this urilistener is listening to
                var tempBrowser = RealNetworks.Utils.GetBrowserForDocument(oDoc);
                if (tempBrowser && tempBrowser.RealPlayerBrowserPluginUniqueValue == this.RealPlayerBrowserPluginUniqueValue)
                {
                    this.addDocument(oDoc);
                    oDocRequestStruct = this.getDocRequestStructFromDoc(oDoc);
                }
            }
        }
        catch (e)
        {
            RealNetworks.Debug.fPrint("getDocRequestStructFromNSIHttpRequestElseCreateOne:\n"+e);
        }
        return oDocRequestStruct;
    },
    
    checkRequest: function(uri, originalURI, referrer, mimeType, oDocRequestStruct)
    {
        var bRecordable = RealNetworks.RPBrowserEmbeddedObjectUtils.IsRecordableMediaMimeType(mimeType);
        var reportString = mimeType+"\n";
        
        if (oDocRequestStruct)
        {
            var asRequests = oDocRequestStruct.asRequests;
            var oDoc = oDocRequestStruct.oDoc;
            
            try
            {
                // Mapping the original URI request to the new request
                if (originalURI)
                {
                    // BUG: Should not replace if we are already have a recordable mimetype and we're
                    // overwriting with a non-recordable mimetype
                    var bReplace = true;
                    if (asRequests[originalURI])
                    {
                        var bOrigRecordable = RealNetworks.RPBrowserEmbeddedObjectUtils.IsRecordableMediaMimeType(asRequests[originalURI][1]);
                        bReplace = !(bOrigRecordable && !bRecordable);
                    }
                    if (bReplace)
                        asRequests[originalURI] = [uri, mimeType];
                    
                    reportString = reportString + 'originalURL:\n'+originalURI+'\n\n';
                }
                
                reportString = reportString + 'newURL:\n' + uri + '\n'+mimeType+'\n\n';
            
                if (referrer)
                {
                    reportString = reportString + 'referrer:\n'+referrer+'\n\n';
                }
                
                RealNetworks.Debug.fPrint("ReportString:\n"+reportString+"\nIsRecordable?"+bRecordable);
                asRequests[uri] = ["", null];
            
                if (bRecordable)
                    this.onMutationEvent(null);
                this.evaluateMutation();
                // Checking if this request is associated to an object that we are monitoring
                var oAssocElement = FindAssociatedElement(this.aoRecordableObjects, originalURI ? originalURI : uri);
                var sAssocUrl = "";
                var iAssocIndex = oAssocElement[0];
                if (iAssocIndex != -1 && this.aoRecordableObjects && this.aoRecordableObjects[iAssocIndex])
                {
                    var oElem = this.getRecordableElement(iAssocIndex);
                    if (oElem)
                    {
                        sAssocUrl = RealNetworks.Utils.GetElementUrl(oElem, true);
                    }
                }
                
                RealNetworks.Debug.fPrint("Checking out the associatedElement:\n"+uri+"\n\n"+sAssocUrl+"\n\n"+oAssocElement+"\n\n"+mimeType+"\n\n"+bRecordable+" - "+iAssocIndex);
                if (sAssocUrl && iAssocIndex != -1 && bRecordable)
                {
                    RealNetworks.Debug.fPrint("Setting out the associatedElement:\n"+sAssocUrl+"\n\n"+uri);
                    asRequests[sAssocUrl] = [uri, mimeType];
                    this.aoUnmatchedRecordableFiles.push(new CRPUrlInfo(uri,mimeType,iAssocIndex));
                    this.oXPCOMBrowserRecord.OnRecordableObjectDataChangedEvent(iAssocIndex);
                }
                else if (bRecordable)
                {
                    this.aoUnmatchedRecordableFiles.push(new CRPUrlInfo(uri,mimeType,-1));
                    RealNetworks.Debug.fPrint("OnRecordableFileChangedEvent:"+(this.aoUnmatchedRecordableFiles.length - 1)+"\n"+uri+"\n"+this.aoRecordableObjects.length);
                    this.oXPCOMBrowserRecord.OnRecordableFileChangedEvent(this.aoUnmatchedRecordableFiles.length - 1);
                }
            }
            catch (e)
            {
                RealNetworks.Debug.fPrint("checkRequest2:\n"+e);
            }
        }
    },
    
    setBrowser: function(oBrowser)
    {
        this.oRelatedBrowser = oBrowser;
    },
    
    // --------------------------------------------------------------------------------------------
    // Event Handlers -----------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    onMutationEvent: function(oEvent)
    {
        try
        {
            this.bMutationEvent = true;
        }
        catch(e)
        {
            RealNetworks.Debug.fPrint("onMutationEvent:\n"+e);
        }
    },
    
    evaluateMutation: function()
    {
        try
        {
            if (this.bMutationEvent)
            {
                this.cacheRecordableObjects(); // TODO: is this the correct place to put it in?
                this.notifyOfRecordableObjects(); // TODO: Should check if the mutation added/modified recordable elements
                this.bMutationEvent = false;
            }
        }
        catch(e)
        {
            RealNetworks.Debug.fPrint("evaluateMutation:\n"+e);
        }
    },
    
    // --------------------------------------------------------------------------------------------
    // Public Functions ---------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------
    findRedirectedURLAndMimeType: function(url, oDoc)
    {
        var strDebugText = url;
        var strLastRedirectedURL = url;
        var strLastMimeType = null;
        try
        {
            var oDocRequestStruct = this.getDocRequestStructFromDoc(oDoc);
            var asRequests = null;
            if (oDocRequestStruct)
            {
                asRequests = oDocRequestStruct.asRequests;
            }
            
            if (asRequests)
            {
                var asRequestsObject = [strLastRedirectedURL, null];
                var bBreak = false;
                while (asRequestsObject && asRequestsObject[0] && !bBreak)
                {
                    asRequestsObject = asRequests[asRequestsObject[0]]
                    if (asRequestsObject && asRequestsObject[0])
                    {
                        bBreak = (strLastRedirectedURL == asRequestsObject[0]);
                        if (asRequestsObject[1])
                            strLastMimeType = asRequestsObject[1];
                        strLastRedirectedURL = asRequestsObject[0];
                        strDebugText = strDebugText + '\n\n\tURL: '+asRequestsObject[0]+'\n\tMimeType: '+asRequestsObject[1];
                    }
                }
            }
        }
        catch (err)
        {
            RealNetworks.Debug.fPrint('Got an error in findRedirectedURLAndMimeType')
        }
        
        RealNetworks.Debug.fPrint("FindRedirectedURLAndMimeType - pre debug text:\n"+url+"\n"+strDebugText);
        return [strLastRedirectedURL, strLastMimeType];
    },
    
    addDocument: function(doc)
    {
        var bFoundDuplicate = false;
        for (var x = 0; x < this.aoDocRequestStructList.length; ++x)
        {
            if (this.aoDocRequestStructList[x] && this.aoDocRequestStructList[x].oDoc === doc)
            {
                bFoundDuplicate = true;
                break;
            }
        }
        
        if (!bFoundDuplicate)
        {
            this.aoDocRequestStructList.push(new CRPDocRequestStruct(doc));
            RealNetworks.Debug.fPrint("added document");
        
            try
            {
                // FF 1.5 scrolling events
                if (doc && doc.documentElement && doc.documentElement.addEventListener)
                {
                    doc.documentElement.addEventListener("scroll", this.onScroll, false);
                }
                this.cacheRecordableObjects(); // TODO: is this the correct place to put it in?
                this.notifyOfRecordableObjects();
            }
            catch(e)
            {
                RealNetworks.Debug.fPrint("addDocument:\n"+e);
            }
        }
        
        return bFoundDuplicate;
    },
    
    removeDocument: function(doc)
    {
        for (var x = 0; x < this.aoDocRequestStructList.length; ++x)
        {
            if (this.aoDocRequestStructList[x].oDoc === doc)
            {
                var tail = this.aoDocRequestStructList.splice(x,x);
                this.aoDocRequestStructList.concat(tail);
                // TODO: Is this the only thing we need to do?
                
                // Need to notify that a document has been removed.
                this.cacheRecordableObjects();
                this.notifyOfRecordableObjects();
                RealNetworks.Debug.fPrint("removed document");
                break;
            }
        }
        
        if ((this.oRelatedBrowser.contentWindow.top.document == doc) || (this.aoDocRequestStructList && this.aoDocRequestStructList.length == 0))
        {
            this.reset(true);
            this.oXPCOMBrowserRecord.OnDocumentWindowStateChangedEvent(0);
            this.notifyOfRecordableObjects();
        }
    },
    
    onDocumentLoad: function(bTopLevelDocument)
    {
        // Bug 197325: This part for the onMutationEvent is somewhat of a hack.  For some sites, such as
        // http://www.apple.com/trailers/newline/rushhour3/medium.html, it seems like we do not receive
        // the "DOMNodeInsertedIntoDocument" event, instead we get the "DOMNodeInserted" event, but for most
        // every other site I've checked, the "DOMNodeInsertedIntoDocument" is sufficient.  Well since I don't
        // get the "DOMNodeInserted" event, I just make it so that after the document loads, I check if
        // the DOM has changed.
        if (bTopLevelDocument)
        {
            this.onMutationEvent(null);
            this.evaluateMutation();
            this.onMutationEvent(null);
        }
        this.oXPCOMBrowserRecord.OnDocumentLoadCompleteEvent(bTopLevelDocument);
        RealNetworks.Debug.fPrint("OnDocumentLoadCompleteEvent: "+bTopLevelDocument);
    },
    
    onTabSwitched: function(bEnabled)
    {
        // Need to notify that we switched tabs
        var ulWindowState = 0;
        if (bEnabled)
        {
            ulWindowState = 3;
        }
        
        this.oXPCOMBrowserRecord.OnDocumentWindowStateChangedEvent(ulWindowState);
    },
    
    onScroll: function()
    {
        var counter = this.aoRecordableObjects.length - 1;
        while (counter >= 0)
        {
            this.oXPCOMBrowserRecord.OnRecordableObjectRectChangedEvent(counter);
            counter--;
        }
    },
    
    getCacheSession: function()
    {
        try
        {
            // TODO
            // If not using cache, then return null
            if (!RPCachePreferenceObserver.mUseCache)
                return null;
            
            // If we don't have a cache session, then create one
            if (!this.mCacheSession)
            {
                var cacheService = Components.classes["@mozilla.org/network/cache-service;1"].getService(Components.interfaces.nsICacheService);
                if (cacheService)
                {
                    var cacheSession = cacheService.createSession("HTTP", Components.interfaces.nsICache.STORE_ANYWHERE, Components.interfaces.nsICache.STREAM_BASED);
                    cacheSession.doomEntriesIfExpired = false; // Is this alright?
                    this.mCacheSession = cacheSession;
                }
            }
        }
        catch(e)
        {
        }
        return this.mCacheSession;
    },
    
    clearRecordableObjects: function()
    {
        delete this.aoRecordableObjects;
        this.aoRecordableObjects = new Array();
    },
    
    cacheRecordableObjects: function()
    {
        this.clearRecordableObjects();
        for (var keyDocRequestStructList = 0; keyDocRequestStructList < this.aoDocRequestStructList.length; ++keyDocRequestStructList)
        {
            var oDocRequestStruct = this.aoDocRequestStructList[keyDocRequestStructList];
            if (!oDocRequestStruct)
                continue;
            var doc = oDocRequestStruct.oDoc;
            
            // We are ignoring searching for object tags on certain sites because certain sites break as a result of it.
            // For example, content on atomfilms.com break IF you just search all the HTMLObjectElements on the page.
            // We also need to look at object tags because it's the RIGHT thing to do and most sites that use the
            // MoveNetworks plugin seems to only use object tags (unlike most others that use object tags with an
            // embed as a child).
            var isBlockedSite = false;
            var standardUrl = RealNetworks.Utils.GetStandardURLInstance(doc.URL);
            if (standardUrl)
                isBlockedSite = (standardUrl.host.indexOf('.atomfilms.com') != -1);
            
            var objectAddedInsteadOfEmbed = new Array();
            
            // Checking all the embeds
            var plugins = doc.plugins;
            for (var x = 0; x < plugins.length; x++)
            {
                if (RealNetworks.RPBrowserEmbeddedObjectUtils.IsKnownPlugin(plugins[x]))
                {
                    var objectAddedInstead = false;
                    try
                    {
                        objectAddedInstead = (Components.interfaces.nsIMozAxPlugin && !isBlockedSite && plugins[x] && plugins[x].parentNode && plugins[x].parentNode.wrappedJSObject && plugins[x].parentNode.wrappedJSObject.QueryInterface(Components.interfaces.nsIMozAxPlugin));
                        if (objectAddedInstead)
                            objectAddedInsteadOfEmbed.push(plugins[x].parentNode);
                    }
                    catch(e)
                    {
                    }
                    
                    if (!objectAddedInstead)
                    {
                        this.aoRecordableObjects.push(new CRecordableObject(plugins[x]));
                    }
                }
            }
            
            // Checking all the objects
            if (!isBlockedSite)
            {
                var objects = doc.getElementsByTagName('object');
                for (var x = 0; x < objects.length; x++)
                {
                    var hasBeenAdded = false;
                    for (var y = 0; y < objectAddedInsteadOfEmbed.length; y++)
                    {
                        hasBeenAdded = (objects[x] === objectAddedInsteadOfEmbed[y]);
                        if (hasBeenAdded)
                            this.aoRecordableObjects.push(new CRecordableObject(objects[x]));
                    }
                    
                    if (!hasBeenAdded && RealNetworks.RPBrowserEmbeddedObjectUtils.IsKnownPlugin(objects[x]))
                    {
                        var childNodes = objects[x].childNodes;
                        var hasEmbedChild = false;
                        for (var y = childNodes.length-1; y >= 0 && !hasEmbedChild; y--)
                        {
                            if (childNodes[y] instanceof HTMLEmbedElement)
                                hasEmbedChild = true;
                        }
                        if (!hasEmbedChild)
                            this.aoRecordableObjects.push(new CRecordableObject(objects[x]));
                    }
                }
            }
            delete objectAddedInsteadOfEmbed;
            objectAddedInsteadOfEmbed = null;
        }
    },
    
    getRecordableElement: function(iIndex)
    {
        var recordableObject = this.aoRecordableObjects[iIndex];
        if (recordableObject)
            return recordableObject.getElement();
        return null;
    },
    
    notifyOfRecordableObjects: function()
    {
        if (this.aoRecordableObjects && this.aoRecordableObjects.length > 0)
        {
            this.oXPCOMBrowserRecord.QueryInterface(Components.interfaces.nsIRPBrowserRecord);
            this.oXPCOMBrowserRecord.OnRecordableObjectsAvailableEvent(true); // TODO: Should pass in proper argument, not always true
        }
        else
        {
            this.oXPCOMBrowserRecord.QueryInterface(Components.interfaces.nsIRPBrowserRecord);
            this.oXPCOMBrowserRecord.OnRecordableObjectsAvailableEvent(false); // TODO: Should pass in proper argument, not always true
        }
    }
};

function FindAssociatedElement(aElementList, url)
{
    var iBestMatchPercentage = 0;
    var bExactMatch = false;
    var iIndex = -1;
    try
    {
        if (getQueryString(url).length > 0 && aElementList && aElementList.length > 0)
        {
            for (var iCount = 0; iCount < aElementList.length; iCount++)
            {
                var recordableObject = aElementList[iCount];
                if (recordableObject && recordableObject.elem)
                {
                    var sElementUrl = RealNetworks.Utils.GetElementUrl(recordableObject.elem, false);
                    if (sElementUrl)
                    {
                        var aCurrentMatchPercentage = getMatchPercentage(url, sElementUrl);
                        if (aCurrentMatchPercentage[1] && iBestMatchPercentage == 0)
                        {
                            // if we have an exact match, this will be our default, unless we find another match
                            bExactMatch = aCurrentMatchPercentage[1];
                            iIndex = iCount;
                        }
                        else if (aCurrentMatchPercentage[0] > iBestMatchPercentage)
                        {
                            iBestMatchPercentage = aCurrentMatchPercentage[0];
                            bExactMatch = aCurrentMatchPercentage[1];
                            iIndex = iCount;
                        }
                    }
                }
            }
        }
    }
    catch (e)
    {
        RealNetworks.Debug.fPrint('Caught an error @ FindAssociatedElement:\n'+e);
    }

    return [iIndex, iBestMatchPercentage, bExactMatch];
}

function FindTabFromCRPURIListener(oURIListener)
{
    for (var x in g_dTabs)
    {
        if (g_dTabs[x])
        {
            if (g_dTabs[x] === oURIListener)
            {
                return x;
            }
        }
    }
    return null;
}

// --------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------
function onTabAdded(oEvent)
{
    // TODO: not every event that I receive here is for tab open/close because the way that we receive tab open/close
    // events for Firefox <=1.5 allows for stray events to hit here.  Make sure to test this thoroughly.
    // New browser for the tab
    var oBrowser;
    if (oEvent.target.localName == "vbox") // Firefox <=1.5
        oBrowser = oEvent.target.childNodes[1];

    if (!oBrowser) // Firefox 2+
        oBrowser = oEvent.target.linkedBrowser;
    
    if (oBrowser && oBrowser.RealPlayerBrowserPluginUniqueValue == null)
    {
        var oURIListener = startURIListener();
        oURIListener.setBrowser(oBrowser);
        oBrowser.RealPlayerBrowserPluginUniqueValue = oURIListener.RealPlayerBrowserPluginUniqueValue;
        g_dTabs[oBrowser.RealPlayerBrowserPluginUniqueValue] = oURIListener;
    }
}

function onTabRemoved(oEvent)
{
    // TODO: need to figure out how to remove entries from the dictionary that we have.  Is it a possible memory leak?
    var oBrowser = oEvent.target.childNodes[1]; // Firefox <=1.5
    if (!oBrowser)
        oBrowser = oEvent.target.linkedBrowser; // Firefox 2+
    
    if (oBrowser && oBrowser.RealPlayerBrowserPluginUniqueValue != null)
    {
        var oURIListener = g_dTabs[oBrowser.RealPlayerBrowserPluginUniqueValue];
        if (oURIListener)
        {
            stopURIListener(oURIListener);
            oURIListener = null;
            g_dTabs[oBrowser] = null;
        }
    }
}

function onTabSelected(oEvent)
{
    var oBrowser = gBrowser.getBrowserAtIndex(gBrowser.mTabContainer.selectedIndex); // Firefox <=1.5
    if (!oBrowser)
        oBrowser = gBrowser.selectedTab; // Firefox 2+

    if (oBrowser && oBrowser.RealPlayerBrowserPluginUniqueValue != null)
    {
        if (g_dTabs[g_LastTab])
            g_dTabs[g_LastTab].onTabSwitched(false);
        RealNetworks.Debug.fPrint("OnTabSelected: "+g_LastTab+" to "+oBrowser.RealPlayerBrowserPluginUniqueValue);
        var oURIListener = g_dTabs[oBrowser.RealPlayerBrowserPluginUniqueValue];
        if (oURIListener)
            oURIListener.onTabSwitched(true);
        g_LastTab = oBrowser.RealPlayerBrowserPluginUniqueValue;
    }
}

// --------------------------------------------------------------------------------------------
// DOM Event Handlers
// --------------------------------------------------------------------------------------------
function onMutationEvent(oEvent)
{
    var oOwnerDocument = oEvent.originalTarget;
    while (oOwnerDocument.ownerDocument)
    {
        oOwnerDocument = oOwnerDocument.ownerDocument;
    }
    
    var oOwnerBrowser = RealNetworks.Utils.GetBrowserForDocument(oOwnerDocument);
    if (oOwnerBrowser && oOwnerBrowser.RealPlayerBrowserPluginUniqueValue != null)
    {
        var oURIListener = g_dTabs[oOwnerBrowser.RealPlayerBrowserPluginUniqueValue];
        oURIListener.onMutationEvent(oEvent);
    }
}

function addDOMEventHandler(doc)
{
    doc.addEventListener("DOMAttrModified", onMutationEvent, true);
    doc.addEventListener("DOMCharacterDataModified", onMutationEvent, true);
    doc.addEventListener("DOMNodeInsertedIntoDocument", onMutationEvent, true);
    doc.addEventListener("DOMNodeRemovedFromDocument", onMutationEvent, true);
}

function removeDOMEventHandler(doc)
{
    doc.removeEventListener("DOMAttrModified", onMutationEvent, true);
    doc.removeEventListener("DOMCharacterDataModified", onMutationEvent, true);
    doc.removeEventListener("DOMNodeInsertedIntoDocument", onMutationEvent, true);
    doc.removeEventListener("DOMNodeRemovedFromDocument", onMutationEvent, true);
}

// --------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------
function flashCoordCallback(ev)
{
    try
    {
        // Need to imitate a scroll event so that the UI can update the placement
        var oBrowser = RealNetworks.Utils.GetBrowserForDocument(ev.originalTarget.ownerDocument);
        var oURIListener = g_dTabs[oBrowser.RealPlayerBrowserPluginUniqueValue];
        if (oURIListener)
            oURIListener.onScroll(null);
    }
    catch(e)
    {
        RealNetworks.Debug.fPrint("flashCoordCallback:"+e);
    }
}

function flashUrlCallback(ev)
{
    try
    {
        var oBrowser = RealNetworks.Utils.GetBrowserForDocument(ev.originalTarget.ownerDocument);
        var oURIListener = g_dTabs[oBrowser.RealPlayerBrowserPluginUniqueValue];
        if (oURIListener)
            oURIListener.notifyOfRecordableObjects();
    }
    catch(e)
    {
        RealNetworks.Debug.fPrint("flashUrlCallback:"+e);
    }
}

// --------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------
function addDocument(oDoc, bDocumentLoadEvent)
{
    try
    {
        // Sometimes, oDoc is not the HTMLDocument itself, but to get the HTMLDocument you need
        // to get oDoc.contentDocument.  In this case, ignore this addition of the document.
        if (oDoc && (oDoc instanceof HTMLDocument))
        {
            var oBrowser = RealNetworks.Utils.GetBrowserForDocument(oDoc);
            var oURIListener = g_dTabs[oBrowser.RealPlayerBrowserPluginUniqueValue];
            var bFoundDuplicateDocument = oURIListener.addDocument(oDoc);
            if (!bFoundDuplicateDocument)
                addDOMEventHandler(oDoc);
            if (bDocumentLoadEvent)
            {
                var oDocAncestor = oDoc;
                while (oDocAncestor && oDocAncestor.defaultView && oDocAncestor.defaultView.parent && oDocAncestor.defaultView.parent.document && !(oDocAncestor.defaultView.parent.document === oDocAncestor))
                {
                    oDocAncestor = oDocAncestor.defaultView.parent.document;
                }
                oURIListener.onDocumentLoad(oDoc === oDocAncestor);
                
                // Adding javascript callback.
                // Custom Event caller
                oDoc.addEventListener("rp-js-coord-callback", flashCoordCallback, false);
                oDoc.addEventListener("rp-js-url-callback", flashUrlCallback, false);
                // No need to listen for the totalbytes or connection callback since that's all handled once the user
                // tries to access the url.  This may possibly be a performance hit, but it's more reliable.
                
                var scriptDefinition = "<!--\n";
                scriptDefinition += "function __RP_Callback_Helper(str, strCallbackEvent, splitSize, func){var event = null;if (strCallbackEvent){event = document.createEvent('Events');event.initEvent(strCallbackEvent, true, true);}if (str && str.length > 0){var splitList = str.split('|');var strCompare = str;if (splitList.length == splitSize)strCompare = splitList[splitSize-1];var pluginList = document.plugins;for (var count = 0; count < pluginList.length; count++){var sSrc = '';if (pluginList[count] && pluginList[count].src)sSrc = pluginList[count].src;if (strCompare.length >= sSrc.length){if (strCompare.indexOf(sSrc) != -1){func(str, count, pluginList, splitList);break;}}}}if (strCallbackEvent)document.body.dispatchEvent(event);}";
                // Coord callback
                scriptDefinition += "function __RP_Coord_Callback(str){var func = function(str, index, pluginList, splitList){pluginList[index].__RP_Coord_Callback = str;pluginList[index].__RP_Coord_Callback_Left = splitList[0];pluginList[index].__RP_Coord_Callback_Top = splitList[1];pluginList[index].__RP_Coord_Callback_Right = splitList[2];pluginList[index].__RP_Coord_Callback_Bottom = splitList[3];};__RP_Callback_Helper(str, 'rp-js-coord-callback', 5, func);}";
                // Url callback
                scriptDefinition += "function __RP_Url_Callback(str){var func = function(str, index, pluginList, splitList){pluginList[index].__RP_Url_Callback = str;pluginList[index].__RP_Url_Callback_Vid = splitList[0];pluginList[index].__RP_Url_Callback_Parent = splitList[1];};__RP_Callback_Helper(str, 'rp-js-url-callback', 3, func);}";
                // TotalBytes callback
                scriptDefinition += "function __RP_TotalBytes_Callback(str){var func = function(str, index, pluginList, splitList){pluginList[index].__RP_TotalBytes_Callback = str;pluginList[index].__RP_TotalBytes_Callback_Bytes = splitList[0];};__RP_Callback_Helper(str, null, 2, func);}";
                // Connection callback
                scriptDefinition += "function __RP_Connection_Callback(str){var func = function(str, index, pluginList, splitList){pluginList[index].__RP_Connection_Callback = str;pluginList[index].__RP_Connection_Callback_Url = splitList[0];};__RP_Callback_Helper(str, null, 2, func);}";
                scriptDefinition += "\n//-->";
                
                var scriptElement = oDoc.createElement("script");
                scriptElement.setAttribute("type","text/javascript");
                var scriptValue = oDoc.createTextNode(scriptDefinition);
                scriptElement.appendChild(scriptValue);
                                
                var htmlElements = oDoc.getElementsByTagName("html");
                for (var counter = 0; counter < htmlElements.length; counter++)
                {
                    if (htmlElements[counter])
                    {
                        htmlElements[counter].appendChild(scriptElement);
                        break;
                    }
                }
            }
        }
    }
    catch(e)
    {
        var errorString = "";
        if (oDoc && oDoc.documentURI)
            errorString = errorString + "\n" + oDoc.documentURI;
        errorString = errorString + "\n" + e;
        RealNetworks.Debug.fPrint("addDocument Error:"+errorString);
    }
}

function onDocumentLoad(oEvent)
{
    var oDoc = oEvent.originalTarget;
    addDocument(oDoc, true);
}

function onDocumentUnload(oEvent)
{
    RealNetworks.Debug.fPrint("unloading document");
    var oDoc = oEvent.originalTarget;
    
    try
    {
        var oBrowser = RealNetworks.Utils.GetBrowserForDocument(oDoc);
        var oURIListener = g_dTabs[oBrowser.RealPlayerBrowserPluginUniqueValue];
        oURIListener.removeDocument(oDoc);
        removeDOMEventHandler(oDoc);
    }
    catch(e)
    {
        var errorString = "";
        if (oDoc && oDoc.documentURI)
            errorString = errorString + "\n" + oDoc.documentURI;
        errorString = errorString + "\n" + e;
        RealNetworks.Debug.fPrint("onDocumentUnload Error:"+errorString);
    }
}

function onPageShow(oEvent)
{
    var oDoc = oEvent.originalTarget;
    addDocument(oDoc, false);
}

function onPageHide(oEvent)
{
    onDocumentUnload(oEvent);
}

function onDOMContentLoaded(oEvent)
{
    addDocument(oEvent.target);
}

function onScroll(oEvent)
{
    if (oEvent && oEvent.target)
    {
        var oDoc = oEvent.target;
        var oBrowser = RealNetworks.Utils.GetBrowserForDocument(oDoc);
        if (oBrowser && oBrowser.RealPlayerBrowserPluginUniqueValue != null)
        {
            var oURIListener = g_dTabs[oBrowser.RealPlayerBrowserPluginUniqueValue];
            oURIListener.onScroll();
        }
    }
}

// --------------------------------------------------------------------------------------------
// Chrome (un)loading handler.
// --------------------------------------------------------------------------------------------
function onChromeLoad()
{
    // Adding 
    var appcontent = document.getElementById("appcontent");
    if (appcontent)
    {
        appcontent.addEventListener("load", onDocumentLoad, true);
        appcontent.addEventListener("scroll", onScroll, false); // For Firefox 2 scrolling events
        
        appcontent.addEventListener("pageshow", onPageShow, true);
        appcontent.addEventListener("pagehide", onPageHide, true);
        
        // Event listener for when the DOM is created.  This is an undocumented event.
        appcontent.addEventListener("DOMContentLoaded", onDOMContentLoaded, true);
        
        var bSetLastTab = false;
        var browserArray = RealNetworks.Utils.GetAllBrowsers();
        if (browserArray && browserArray.length > 0)
        {
            for (var iIndex = 0; iIndex < browserArray.length; ++iIndex)
            {
                var oURIListener = startURIListener();
                oURIListener.setBrowser(browserArray[iIndex]);
                browserArray[iIndex].RealPlayerBrowserPluginUniqueValue = oURIListener.RealPlayerBrowserPluginUniqueValue;
                g_dTabs[browserArray[iIndex].RealPlayerBrowserPluginUniqueValue] = oURIListener;
                if (!bSetLastTab)
                {
                    g_LastTab = browserArray[iIndex].RealPlayerBrowserPluginUniqueValue;
                    bSetLastTab = true;
                }
            }
        }
    }
    
    if (gBrowser.mPanelContainer)
    {
        // Firefox (<=1.5) to get events for tab addition/removal
        gBrowser.mPanelContainer.addEventListener("DOMNodeInserted", onTabAdded, false);
        gBrowser.mPanelContainer.addEventListener("DOMNodeRemoved", onTabRemoved, false);
        gBrowser.mPanelContainer.addEventListener("select", onTabSelected, false);
    }
    if (gBrowser.tabContainer)
    {
        // Firefox (2+) to get events for tab addition/removal
        gBrowser.tabContainer.addEventListener("TabOpen", onTabAdded, false);
        gBrowser.tabContainer.addEventListener("TabClose", onTabRemoved, false);
        gBrowser.tabContainer.addEventListener("TabSelect", onTabSelected, false);
    }
}

function onChromeUnload()
{
}

function DelayRecordableObjectProcessing() 
{
    while(g_DelayedRecordableObjects.length != 0)
    {
	var RecordableObject = g_DelayedRecordableObjects.pop();
	RecordableObject.observer = AddRMEmbedObserver(RecordableObject.elem);
    }
}

window.addEventListener("load", onChromeLoad, false);
window.addEventListener("unload", onChromeUnload, false);

