Bug 315598 Allow more than one global redirect observer, using the

"net-channel-event-sinks" category.
r=darin sr=bz
This commit is contained in:
cbiesinger%web.de 2006-02-22 14:07:21 +00:00
parent 6253dfb314
commit 9ca6a7aab1
14 changed files with 574 additions and 34 deletions

View File

@ -40,6 +40,7 @@
#include "nsURLHelper.h"
#include "nsNetUtil.h"
#include "nsMimeTypes.h"
#include "nsIOService.h"
#include "nsIHttpEventSink.h"
#include "nsIHttpChannel.h"
#include "nsIChannelEventSink.h"
@ -119,16 +120,12 @@ nsBaseChannel::Redirect(nsIChannel *newChannel, PRUint32 redirectFlags)
// we support nsIHttpEventSink if we are an HTTP channel and if this is not
// an internal redirect.
nsresult rv;
// Give the global event sink a chance to observe/block this redirect.
nsCOMPtr<nsIChannelEventSink> channelEventSink =
do_GetService(NS_GLOBAL_CHANNELEVENTSINK_CONTRACTID);
if (channelEventSink) {
rv = channelEventSink->OnChannelRedirect(this, newChannel, redirectFlags);
if (NS_FAILED(rv))
return rv;
}
// Global observers. These come first so that other observers don't see
// redirects that get aborted for security reasons anyway.
NS_ASSERTION(gIOService, "Must have an IO service");
nsresult rv = gIOService->OnChannelRedirect(this, newChannel, redirectFlags);
if (NS_FAILED(rv))
return rv;
// Backwards compat for non-internal redirects from a HTTP channel.
if (!(redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
@ -144,6 +141,7 @@ nsBaseChannel::Redirect(nsIChannel *newChannel, PRUint32 redirectFlags)
}
}
nsCOMPtr<nsIChannelEventSink> channelEventSink;
// Give our consumer a chance to observe/block this redirect.
GetCallback(channelEventSink);
if (channelEventSink) {

View File

@ -45,6 +45,7 @@
#include "nsIURI.h"
#include "nsIStreamListener.h"
#include "prprf.h"
#include "prlog.h"
#include "nsLoadGroup.h"
#include "nsInputStreamChannel.h"
#include "nsXPIDLString.h"
@ -77,6 +78,8 @@ static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
static NS_DEFINE_CID(kErrorServiceCID, NS_ERRORSERVICE_CID);
static NS_DEFINE_CID(kProtocolProxyServiceCID, NS_PROTOCOLPROXYSERVICE_CID);
nsIOService* gIOService = nsnull;
// A general port blacklist. Connections to these ports will not be avoided unless
// the protocol overrides.
//
@ -149,8 +152,9 @@ nsIMemory* nsIOService::gBufferCache = nsnull;
////////////////////////////////////////////////////////////////////////////////
nsIOService::nsIOService()
: mOffline(PR_FALSE),
mOfflineForProfileChange(PR_FALSE)
: mOffline(PR_FALSE)
, mOfflineForProfileChange(PR_FALSE)
, mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY)
{
// Get the allocator ready
if (!gBufferCache)
@ -228,6 +232,8 @@ nsIOService::Init()
}
else
NS_WARNING("failed to get observer service");
gIOService = this;
return NS_OK;
}
@ -235,6 +241,7 @@ nsIOService::Init()
nsIOService::~nsIOService()
{
gIOService = nsnull;
}
NS_IMPL_THREADSAFE_ISUPPORTS4(nsIOService,
@ -245,6 +252,31 @@ NS_IMPL_THREADSAFE_ISUPPORTS4(nsIOService,
////////////////////////////////////////////////////////////////////////////////
nsresult
nsIOService::OnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
PRUint32 flags)
{
nsCOMPtr<nsIChannelEventSink> sink =
do_GetService(NS_GLOBAL_CHANNELEVENTSINK_CONTRACTID);
if (sink) {
nsresult rv = sink->OnChannelRedirect(oldChan, newChan, flags);
if (NS_FAILED(rv))
return rv;
}
// Finally, our category
const nsCOMArray<nsIChannelEventSink>& entries =
mChannelEventSinks.GetEntries();
PRInt32 len = entries.Count();
for (PRInt32 i = 0; i < len; ++i) {
nsresult rv = entries[i]->OnChannelRedirect(oldChan, newChan, flags);
if (NS_FAILED(rv))
return rv;
}
return NS_OK;
}
nsresult
nsIOService::CacheProtocolHandler(const char *scheme, nsIProtocolHandler *handler)
{

View File

@ -55,6 +55,8 @@
#include "nsIObserver.h"
#include "nsWeakReference.h"
#include "nsINetUtil.h"
#include "nsIChannelEventSink.h"
#include "nsCategoryCache.h"
#define NS_N(x) (sizeof(x)/sizeof(*x))
@ -65,7 +67,8 @@
#endif
#define NS_NECKO_15_MINS (15 * 60)
static const char *gScheme[] = {"chrome", "file", "http", "jar", "resource"};
static const char gScheme[][sizeof("resource")] =
{"chrome", "file", "http", "jar", "resource"};
class nsIPrefBranch;
class nsIPrefBranch2;
@ -89,6 +92,11 @@ public:
nsIURI* *result,
nsIProtocolHandler* *hdlrResult);
// Called by channels before a redirect happens. This notifies the global
// redirect observers.
nsresult OnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
PRUint32 flags);
protected:
NS_HIDDEN_(nsresult) GetCachedProtocolHandler(const char *scheme,
nsIProtocolHandler* *hdlrResult,
@ -113,6 +121,9 @@ protected:
// Cached protocol handlers
nsWeakPtr mWeakHandler[NS_N(gScheme)];
// cached categories
nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
nsVoidArray mRestrictedPortList;
public:
@ -121,4 +132,9 @@ public:
static nsIMemory *gBufferCache;
};
/**
* Reference to the IO service singleton. May be null.
*/
extern nsIOService* gIOService;
#endif // nsIOService_h__

View File

@ -767,5 +767,16 @@
#define NS_GLOBAL_CHANNELEVENTSINK_CONTRACTID \
"@mozilla.org/netwerk/global-channel-event-sink;1"
/******************************************************************************
* Categories
*/
/**
* Services registered in this category will get notified via
* nsIChannelEventSink about all redirects that happen and have the opportunity
* to veto them. The value of the category entries is interpreted as the
* contract ID of the service.
*/
#define NS_CHANNEL_EVENT_SINK_CATEGORY "net-channel-event-sinks"
#endif // nsNetCID_h__

View File

@ -120,12 +120,14 @@ interface nsIHttpProtocolHandler : nsIProxiedProtocolHandler
};
%{C++
// ----------- Categories -----------
/**
* At initialization time, the HTTP handler will initialize each service
* registered under this category:
*/
#define NS_HTTP_STARTUP_CATEGORY "http-startup-category"
// ----------- Observer topics -----------
/**
* nsIObserver notification corresponding to startup category. Services
* registered under the startup category will receive this observer topic at
@ -155,4 +157,6 @@ interface nsIHttpProtocolHandler : nsIProxiedProtocolHandler
* things, the Content-Type header will be set correctly.
*/
#define NS_HTTP_ON_EXAMINE_MERGED_RESPONSE_TOPIC "http-on-examine-merged-response"
%}

View File

@ -993,14 +993,10 @@ nsHttpChannel::ReplaceWithProxy(nsIProxyInfo *pi)
return rv;
// Inform consumers about this fake redirect
nsCOMPtr<nsIChannelEventSink> channelEventSink;
GetCallback(channelEventSink);
if (channelEventSink) {
PRUint32 flags = nsIChannelEventSink::REDIRECT_INTERNAL;
rv = channelEventSink->OnChannelRedirect(this, newChannel, flags);
if (NS_FAILED(rv))
return rv;
}
PRUint32 flags = nsIChannelEventSink::REDIRECT_INTERNAL;
rv = gHttpHandler->OnChannelRedirect(this, newChannel, flags);
if (NS_FAILED(rv))
return rv;
// open new channel
rv = newChannel->AsyncOpen(mListener, mListenerContext);
@ -2069,14 +2065,11 @@ nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
// verify that this is a legal redirect
nsCOMPtr<nsIChannelEventSink> globalObserver =
do_GetService(NS_GLOBAL_CHANNELEVENTSINK_CONTRACTID);
if (globalObserver) {
rv = globalObserver->OnChannelRedirect(this, newChannel, redirectFlags);
if (NS_FAILED(rv)) return rv;
}
rv = gHttpHandler->OnChannelRedirect(this, newChannel, redirectFlags);
if (NS_FAILED(rv))
return rv;
// call out to the event sink to notify it of this redirection.
// And now, the deprecated way
nsCOMPtr<nsIHttpEventSink> httpEventSink;
GetCallback(httpEventSink);
if (httpEventSink) {
@ -2085,12 +2078,6 @@ nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
rv = httpEventSink->OnRedirect(this, newChannel);
if (NS_FAILED(rv)) return rv;
}
nsCOMPtr<nsIChannelEventSink> channelEventSink;
GetCallback(channelEventSink);
if (channelEventSink) {
rv = channelEventSink->OnChannelRedirect(this, newChannel, redirectFlags);
if (NS_FAILED(rv)) return rv;
}
// XXX we used to talk directly with the script security manager, but that
// should really be handled by the event sink implementation.

View File

@ -72,6 +72,8 @@
#include "prprf.h"
#include "nsReadableUtils.h"
#include "nsQuickSort.h"
#include "nsNetUtil.h"
#include "nsIOService.h"
#if defined(XP_UNIX) || defined(XP_BEOS)
#include <sys/utsname.h>
@ -482,6 +484,25 @@ nsHttpHandler::NotifyObservers(nsIHttpChannel *chan, const char *event)
mObserverService->NotifyObservers(chan, event, nsnull);
}
nsresult
nsHttpHandler::OnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
PRUint32 flags)
{
// First, the global observer
NS_ASSERTION(gIOService, "Must have an IO service at this point");
nsresult rv = gIOService->OnChannelRedirect(oldChan, newChan, flags);
if (NS_FAILED(rv))
return rv;
// Now, the per-channel observers
nsCOMPtr<nsIChannelEventSink> sink;
NS_QueryNotificationCallbacks(oldChan, sink);
if (sink)
rv = sink->OnChannelRedirect(oldChan, newChan, flags);
return rv;
}
//-----------------------------------------------------------------------------
// nsHttpHandler <private>
//-----------------------------------------------------------------------------

View File

@ -193,6 +193,10 @@ public:
NotifyObservers(chan, NS_HTTP_ON_EXAMINE_MERGED_RESPONSE_TOPIC);
}
// Called by channels before a redirect happens. This notifies both the
// channel's and the global redirect observers.
nsresult OnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
PRUint32 flags);
private:
//

View File

@ -42,6 +42,24 @@ nsTestServ.prototype =
"\r\n" +
"200 OK";
stream.write(response, response.length);
},
"/redirect": function(stream)
{
var response = this.headers("301 Moved Permanently") +
"Location: http://localhost:4444/\r\n" +
"\r\n" +
"Moved";
stream.write(response, response.length);
},
"/redirectfile": function(stream)
{
var response = this.headers("301 Moved Permanently") +
"Location: file:///\r\n" +
"\r\n" +
"Moved to a file URI";
stream.write(response, response.length);
}
},

View File

@ -0,0 +1,139 @@
// This file tests channel event sinks (bug 315598 et al)
const sinkCID = Components.ID("{14aa4b81-e266-45cb-88f8-89595dece114}");
const sinkContract = "@mozilla.org/network/unittest/channeleventsink;1";
const categoryName = "net-channel-event-sinks";
const NS_BINDING_ABORTED = 0x804b0002;
/**
* This object is both a factory and an nsIChannelEventSink implementation (so, it
* is de-facto a service). It's also an interface requestor that gives out
* itself when asked for nsIChannelEventSink.
*/
var eventsink = {
QueryInterface: function eventsink_qi(iid) {
if (iid.equals(Components.interfaces.nsISupports) ||
iid.equals(Components.interfaces.nsIFactory) ||
iid.equals(Components.interfaces.nsIChannelEventSink))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
createInstance: function eventsink_ci(outer, iid) {
if (outer)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
},
lockFactory: function eventsink_lockf(lock) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
onChannelRedirect: function eventsink_onredir(oldChan, newChan, flags) {
// veto
this.called = true;
throw NS_BINDING_ABORTED;
},
getInterface: function eventsink_gi(iid) {
if (iid.equals(Components.interfaces.nsIChannelEventSink))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
called: false
};
var listener = {
expectSinkCall: true,
onStartRequest: function test_onStartR(request, ctx) {
try {
// Commenting out this check pending resolution of bug 255119
//if (Components.isSuccessCode(request.status))
// do_throw("Channel should have a failure code!");
// The current URI must be the original URI, as all redirects have been
// cancelled
if (!(request instanceof Components.interfaces.nsIChannel) ||
!request.URI.equals(request.originalURI))
do_throw("Wrong URI: Is <" + request.URI.spec + ">, should be <" +
request.originalURI.spec + ">");
if (request instanceof Components.interfaces.nsIHttpChannel) {
// As we expect a blocked redirect, verify that we have a 3xx status
do_check_eq(Math.floor(request.responseStatus / 100), 3);
do_check_eq(request.requestSucceeded, false);
}
do_check_eq(eventsink.called, this.expectSinkCall);
} catch (e) {
do_throw("Unexpected exception: " + e);
}
throw Components.results.NS_ERROR_ABORT;
},
onDataAvailable: function test_ODA() {
do_throw("Should not get any data!");
},
onStopRequest: function test_onStopR(request, ctx, status) {
if (this._iteration <= 2)
run_test_continued();
do_test_finished();
},
_iteration: 1
};
function makeChan(url) {
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var chan = ios.newChannel(url, null, null)
.QueryInterface(Components.interfaces.nsIHttpChannel);
return chan;
}
function run_test() {
start_server(4444);
Components.manager.nsIComponentRegistrar.registerFactory(sinkCID,
"Unit test Event sink", sinkContract, eventsink);
// Step 1: Set the callbacks on the listener itself
var chan = makeChan("http://localhost:4444/redirect");
chan.notificationCallbacks = eventsink;
chan.asyncOpen(listener, null);
do_test_pending();
}
function run_test_continued() {
eventsink.called = false;
var catMan = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
var chan;
if (listener._iteration == 1) {
// Step 2: Category entry
catMan.nsICategoryManager.addCategoryEntry(categoryName, "unit test",
sinkContract, false, true);
chan = makeChan("http://localhost:4444/redirect")
} else {
// Step 3: Global contract id
catMan.nsICategoryManager.deleteCategoryEntry(categoryName, "unit test",
false);
listener.expectSinkCall = false;
chan = makeChan("http://localhost:4444/redirectfile");
}
listener._iteration++;
chan.asyncOpen(listener, null);
do_test_pending();
}

View File

@ -93,6 +93,7 @@
#include "nsHashPropertyBag.h"
#include "nsStringAPI.h"
#include "nsStringBuffer.h"
#include "nsCategoryCache.h"
#ifndef WINCE
#include "nsWindowsRegKey.h"
@ -241,6 +242,9 @@ void XXXNeverCalled()
NS_UTF16ToCString(str1, NS_CSTRING_ENCODING_ASCII, str2);
NS_CStringToUTF16(str2, NS_CSTRING_ENCODING_ASCII, str1);
nsCategoryObserver catobs(nsnull, nsnull);
nsCategoryCache<nsILocalFile> catcache(nsnull);
// nsStringBuffer.h
{
nsString x;

View File

@ -53,6 +53,7 @@ REQUIRES = string \
CPPSRCS = \
nsCategoryManager.cpp \
nsCategoryCache.cpp \
nsComponentManager.cpp \
nsNativeComponentLoader.cpp \
nsStaticComponentLoader.cpp \
@ -61,6 +62,7 @@ CPPSRCS = \
EXPORTS = \
nsCategoryManagerUtils.h \
nsCategoryCache.h \
nsIServiceManagerObsolete.h \
nsModule.h \
nsObsoleteModuleLoading.h \

View File

@ -0,0 +1,167 @@
/* vim:set st=2 sts=2 ts=2 et cin: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is a cache for services in a category.
*
* The Initial Developer of the Original Code is
* Christian Biesinger <cbiesinger@web.de>.
* Portions created by the Initial Developer are Copyright (C) 2005
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIObserverService.h"
#include "nsISupportsPrimitives.h"
#include "nsXPCOMCID.h"
#include "nsObserverService.h"
#include "nsCategoryCache.h"
nsCategoryObserver::nsCategoryObserver(const char* aCategory,
nsCategoryListener* aListener)
: mListener(nsnull), mCategory(aCategory)
{
if (!mHash.Init()) {
// OOM
return;
}
mListener = aListener;
// First, enumerate the currently existing entries
nsCOMPtr<nsICategoryManager> catMan =
do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
if (!catMan)
return;
nsCOMPtr<nsISimpleEnumerator> enumerator;
nsresult rv = catMan->EnumerateCategory(aCategory,
getter_AddRefs(enumerator));
if (NS_FAILED(rv))
return;
nsCOMPtr<nsISupports> entry;
while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(entry)))) {
nsCOMPtr<nsISupportsCString> entryName = do_QueryInterface(entry, &rv);
if (NS_SUCCEEDED(rv)) {
nsCAutoString categoryEntry;
rv = entryName->GetData(categoryEntry);
nsXPIDLCString entryValue;
catMan->GetCategoryEntry(aCategory,
categoryEntry.get(),
getter_Copies(entryValue));
if (NS_SUCCEEDED(rv)) {
mHash.Put(categoryEntry, entryValue);
mListener->EntryAdded(entryValue);
}
}
}
// Now, listen for changes
nsCOMPtr<nsIObserverService> serv =
do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
if (!serv)
return;
serv->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, PR_FALSE);
serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, PR_FALSE);
serv->AddObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, PR_FALSE);
}
nsCategoryObserver::~nsCategoryObserver() {
}
NS_IMPL_ISUPPORTS1(nsCategoryObserver, nsIObserver)
void
nsCategoryObserver::ListenerDied() {
mListener = nsnull;
nsCOMPtr<nsIObserverService> serv =
do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
if (!serv)
return;
serv->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
serv->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID);
serv->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID);
serv->RemoveObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID);
}
NS_IMETHODIMP
nsCategoryObserver::Observe(nsISupports* aSubject, const char* aTopic,
const PRUnichar* aData) {
if (!mListener)
return NS_OK;
if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
mHash.Clear();
mListener->CategoryCleared();
return NS_OK;
}
if (!aData || !nsDependentString(aData).EqualsASCII(mCategory.get()))
return NS_OK;
nsCAutoString str;
nsCOMPtr<nsISupportsCString> strWrapper(do_QueryInterface(aSubject));
if (strWrapper)
strWrapper->GetData(str);
if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID) == 0) {
nsCOMPtr<nsICategoryManager> catMan =
do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
if (!catMan)
return NS_OK;
nsXPIDLCString entryValue;
catMan->GetCategoryEntry(mCategory.get(),
str.get(),
getter_Copies(entryValue));
mHash.Put(str, entryValue);
mListener->EntryAdded(entryValue);
} else if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID) == 0) {
nsCAutoString val;
if (mHash.Get(str, &val)) {
mHash.Remove(str);
mListener->EntryRemoved(val);
}
} else if (strcmp(aTopic, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID) == 0) {
mHash.Clear();
mListener->CategoryCleared();
}
return NS_OK;
}

View File

@ -0,0 +1,137 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is a cache for services in a category.
*
* The Initial Developer of the Original Code is
* Christian Biesinger <cbiesinger@web.de>.
* Portions created by the Initial Developer are Copyright (C) 2005
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsCategoryCache_h_
#define nsCategoryCache_h_
#include "nsICategoryManager.h"
#include "nsIObserver.h"
#include "nsISimpleEnumerator.h"
#include "nsISupportsPrimitives.h"
#include "nsServiceManagerUtils.h"
#include "nsAutoPtr.h"
#include "nsCOMArray.h"
#include "nsDataHashtable.h"
#include "nsXPCOM.h"
class NS_NO_VTABLE nsCategoryListener {
protected:
// no virtual destructor (people shouldn't delete through an
// nsCategoryListener pointer)
~nsCategoryListener() {}
public:
virtual void EntryAdded(const nsCString& aValue) = 0;
virtual void EntryRemoved(const nsCString& aValue) = 0;
virtual void CategoryCleared() = 0;
};
class NS_COM nsCategoryObserver : public nsIObserver {
public:
nsCategoryObserver(const char* aCategory,
nsCategoryListener* aCategoryListener);
~nsCategoryObserver();
void ListenerDied();
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
private:
nsDataHashtable<nsCStringHashKey, nsCString> mHash;
nsCategoryListener* mListener;
nsCString mCategory;
};
/**
* This is a helper class that caches services that are registered in a certain
* category. The intended usage is that a service stores a variable of type
* nsCategoryCache<nsIFoo> in a member variable, where nsIFoo is the interface
* that these services should implement. The constructor of this class should
* then get the name of the category.
*/
template<class T>
class nsCategoryCache : protected nsCategoryListener {
public:
explicit nsCategoryCache(const char* aCategory);
~nsCategoryCache() { if (mObserver) mObserver->ListenerDied(); }
const nsCOMArray<T>& GetEntries() const { return mEntries; }
protected:
virtual void EntryAdded(const nsCString& aValue);
virtual void EntryRemoved(const nsCString& aValue);
virtual void CategoryCleared();
private:
friend class CategoryObserver;
// Not to be implemented
nsCategoryCache(const nsCategoryCache<T>&);
nsCOMArray<T> mEntries;
nsRefPtr<nsCategoryObserver> mObserver;
};
// -----------------------------------
// Implementation
template<class T>
nsCategoryCache<T>::nsCategoryCache(const char* aCategory)
{
mObserver = new nsCategoryObserver(aCategory, this);
}
template<class T>
void nsCategoryCache<T>::EntryAdded(const nsCString& aValue) {
nsCOMPtr<T> catEntry = do_GetService(aValue.get());
if (catEntry)
mEntries.AppendObject(catEntry);
}
template<class T>
void nsCategoryCache<T>::EntryRemoved(const nsCString& aValue) {
nsCOMPtr<T> catEntry = do_GetService(aValue.get());
if (catEntry)
mEntries.RemoveObject(catEntry);
}
template<class T>
void nsCategoryCache<T>::CategoryCleared() {
mEntries.Clear();
}
#endif