mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-03 20:49:27 +00:00
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:
parent
6253dfb314
commit
9ca6a7aab1
@ -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) {
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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__
|
||||
|
@ -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__
|
||||
|
@ -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"
|
||||
|
||||
|
||||
%}
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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>
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -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:
|
||||
|
||||
//
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
139
netwerk/test/unit/test_event_sink.js
Normal file
139
netwerk/test/unit/test_event_sink.js
Normal 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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 \
|
||||
|
167
xpcom/components/nsCategoryCache.cpp
Normal file
167
xpcom/components/nsCategoryCache.cpp
Normal 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;
|
||||
}
|
137
xpcom/components/nsCategoryCache.h
Normal file
137
xpcom/components/nsCategoryCache.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user