From 9ca6a7aab179d6c74e8d135f324c6da5f6da4647 Mon Sep 17 00:00:00 2001 From: "cbiesinger%web.de" Date: Wed, 22 Feb 2006 14:07:21 +0000 Subject: [PATCH] Bug 315598 Allow more than one global redirect observer, using the "net-channel-event-sinks" category. r=darin sr=bz --- netwerk/base/src/nsBaseChannel.cpp | 18 +- netwerk/base/src/nsIOService.cpp | 36 +++- netwerk/base/src/nsIOService.h | 18 +- netwerk/build/nsNetCID.h | 11 ++ .../http/public/nsIHttpProtocolHandler.idl | 4 + netwerk/protocol/http/src/nsHttpChannel.cpp | 29 +-- netwerk/protocol/http/src/nsHttpHandler.cpp | 21 +++ netwerk/protocol/http/src/nsHttpHandler.h | 4 + netwerk/test/unit/head_http_server.js | 18 ++ netwerk/test/unit/test_event_sink.js | 139 +++++++++++++++ xpcom/build/dlldeps.cpp | 4 + xpcom/components/Makefile.in | 2 + xpcom/components/nsCategoryCache.cpp | 167 ++++++++++++++++++ xpcom/components/nsCategoryCache.h | 137 ++++++++++++++ 14 files changed, 574 insertions(+), 34 deletions(-) create mode 100644 netwerk/test/unit/test_event_sink.js create mode 100644 xpcom/components/nsCategoryCache.cpp create mode 100644 xpcom/components/nsCategoryCache.h diff --git a/netwerk/base/src/nsBaseChannel.cpp b/netwerk/base/src/nsBaseChannel.cpp index 3acf10897d4a..c045db1d8d3d 100644 --- a/netwerk/base/src/nsBaseChannel.cpp +++ b/netwerk/base/src/nsBaseChannel.cpp @@ -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 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 channelEventSink; // Give our consumer a chance to observe/block this redirect. GetCallback(channelEventSink); if (channelEventSink) { diff --git a/netwerk/base/src/nsIOService.cpp b/netwerk/base/src/nsIOService.cpp index 78107acc142a..abaa20aebd3d 100644 --- a/netwerk/base/src/nsIOService.cpp +++ b/netwerk/base/src/nsIOService.cpp @@ -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 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& 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) { diff --git a/netwerk/base/src/nsIOService.h b/netwerk/base/src/nsIOService.h index cb197d78584b..d03eeda02cf8 100644 --- a/netwerk/base/src/nsIOService.h +++ b/netwerk/base/src/nsIOService.h @@ -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 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__ diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index a076e3b2cafe..99647300fe69 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -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__ diff --git a/netwerk/protocol/http/public/nsIHttpProtocolHandler.idl b/netwerk/protocol/http/public/nsIHttpProtocolHandler.idl index 83b1021c8160..13f81d21834e 100644 --- a/netwerk/protocol/http/public/nsIHttpProtocolHandler.idl +++ b/netwerk/protocol/http/public/nsIHttpProtocolHandler.idl @@ -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" + + %} diff --git a/netwerk/protocol/http/src/nsHttpChannel.cpp b/netwerk/protocol/http/src/nsHttpChannel.cpp index c7dd3336194b..5ec7f83ff5bf 100644 --- a/netwerk/protocol/http/src/nsHttpChannel.cpp +++ b/netwerk/protocol/http/src/nsHttpChannel.cpp @@ -993,14 +993,10 @@ nsHttpChannel::ReplaceWithProxy(nsIProxyInfo *pi) return rv; // Inform consumers about this fake redirect - nsCOMPtr 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 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 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 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. diff --git a/netwerk/protocol/http/src/nsHttpHandler.cpp b/netwerk/protocol/http/src/nsHttpHandler.cpp index a92205da2b60..50a57687232f 100644 --- a/netwerk/protocol/http/src/nsHttpHandler.cpp +++ b/netwerk/protocol/http/src/nsHttpHandler.cpp @@ -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 @@ -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 sink; + NS_QueryNotificationCallbacks(oldChan, sink); + if (sink) + rv = sink->OnChannelRedirect(oldChan, newChan, flags); + + return rv; +} + //----------------------------------------------------------------------------- // nsHttpHandler //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/src/nsHttpHandler.h b/netwerk/protocol/http/src/nsHttpHandler.h index 6188567f885c..882a61b874bb 100644 --- a/netwerk/protocol/http/src/nsHttpHandler.h +++ b/netwerk/protocol/http/src/nsHttpHandler.h @@ -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: // diff --git a/netwerk/test/unit/head_http_server.js b/netwerk/test/unit/head_http_server.js index 3d780e598168..996a8f49ebb3 100644 --- a/netwerk/test/unit/head_http_server.js +++ b/netwerk/test/unit/head_http_server.js @@ -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); } }, diff --git a/netwerk/test/unit/test_event_sink.js b/netwerk/test/unit/test_event_sink.js new file mode 100644 index 000000000000..099073d74d7e --- /dev/null +++ b/netwerk/test/unit/test_event_sink.js @@ -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(); +} + diff --git a/xpcom/build/dlldeps.cpp b/xpcom/build/dlldeps.cpp index 362620e5f3d3..476d5c098f5e 100644 --- a/xpcom/build/dlldeps.cpp +++ b/xpcom/build/dlldeps.cpp @@ -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 catcache(nsnull); + // nsStringBuffer.h { nsString x; diff --git a/xpcom/components/Makefile.in b/xpcom/components/Makefile.in index 3bcc7b200383..2e1fb7bb4f25 100644 --- a/xpcom/components/Makefile.in +++ b/xpcom/components/Makefile.in @@ -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 \ diff --git a/xpcom/components/nsCategoryCache.cpp b/xpcom/components/nsCategoryCache.cpp new file mode 100644 index 000000000000..45f0544c4028 --- /dev/null +++ b/xpcom/components/nsCategoryCache.cpp @@ -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 . + * 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 catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) + return; + + nsCOMPtr enumerator; + nsresult rv = catMan->EnumerateCategory(aCategory, + getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr entry; + while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(entry)))) { + nsCOMPtr 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 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 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 strWrapper(do_QueryInterface(aSubject)); + if (strWrapper) + strWrapper->GetData(str); + + if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID) == 0) { + nsCOMPtr 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; +} diff --git a/xpcom/components/nsCategoryCache.h b/xpcom/components/nsCategoryCache.h new file mode 100644 index 000000000000..c7bfdaaa346d --- /dev/null +++ b/xpcom/components/nsCategoryCache.h @@ -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 . + * 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 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 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 nsCategoryCache : protected nsCategoryListener { + public: + explicit nsCategoryCache(const char* aCategory); + ~nsCategoryCache() { if (mObserver) mObserver->ListenerDied(); } + + const nsCOMArray& 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&); + + nsCOMArray mEntries; + nsRefPtr mObserver; +}; + +// ----------------------------------- +// Implementation + +template +nsCategoryCache::nsCategoryCache(const char* aCategory) +{ + mObserver = new nsCategoryObserver(aCategory, this); +} + +template +void nsCategoryCache::EntryAdded(const nsCString& aValue) { + nsCOMPtr catEntry = do_GetService(aValue.get()); + if (catEntry) + mEntries.AppendObject(catEntry); +} + +template +void nsCategoryCache::EntryRemoved(const nsCString& aValue) { + nsCOMPtr catEntry = do_GetService(aValue.get()); + if (catEntry) + mEntries.RemoveObject(catEntry); +} + +template +void nsCategoryCache::CategoryCleared() { + mEntries.Clear(); +} + +#endif