michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: * michael@0: * michael@0: * This Original Code has been modified by IBM Corporation. michael@0: * Modifications made by IBM described herein are michael@0: * Copyright (c) International Business Machines michael@0: * Corporation, 2000 michael@0: * michael@0: * Modifications to Mozilla code or documentation michael@0: * identified per MPL Section 3.3 michael@0: * michael@0: * Date Modified by Description of modification michael@0: * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink michael@0: * use in OS2 michael@0: */ michael@0: michael@0: #include "nsStreamConverterService.h" michael@0: #include "nsIComponentRegistrar.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsDeque.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIStreamConverter.h" michael@0: #include "nsICategoryManager.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsTArray.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsHashtable.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: michael@0: /////////////////////////////////////////////////////////////////// michael@0: // Breadth-First-Search (BFS) algorithm state classes and types. michael@0: michael@0: // Adjacency list data class. michael@0: typedef nsCOMArray SCTableData; michael@0: michael@0: // Delete all the entries in the adjacency list michael@0: static bool DeleteAdjacencyEntry(nsHashKey *aKey, void *aData, void* closure) { michael@0: SCTableData *entry = (SCTableData*)aData; michael@0: delete entry; michael@0: return true; michael@0: } michael@0: michael@0: // Used to establish discovered verticies. michael@0: enum BFScolors {white, gray, black}; michael@0: michael@0: // BFS hashtable data class. michael@0: struct BFSTableData { michael@0: nsCStringKey *key; michael@0: BFScolors color; michael@0: int32_t distance; michael@0: nsAutoPtr predecessor; michael@0: michael@0: explicit BFSTableData(nsCStringKey* aKey) michael@0: : key(aKey), color(white), distance(-1) michael@0: { michael@0: } michael@0: }; michael@0: michael@0: //////////////////////////////////////////////////////////// michael@0: // nsISupports methods michael@0: NS_IMPL_ISUPPORTS(nsStreamConverterService, nsIStreamConverterService) michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////// michael@0: // nsIStreamConverterService methods michael@0: michael@0: //////////////////////////////////////////////////////////// michael@0: // nsStreamConverterService methods michael@0: nsStreamConverterService::nsStreamConverterService() michael@0: : mAdjacencyList(nullptr, nullptr, DeleteAdjacencyEntry, nullptr) michael@0: { michael@0: } michael@0: michael@0: nsStreamConverterService::~nsStreamConverterService() { michael@0: } michael@0: michael@0: // Builds the graph represented as an adjacency list (and built up in michael@0: // memory using an nsObjectHashtable and nsISupportsArray combination). michael@0: // michael@0: // :BuildGraph() consults the category manager for all stream converter michael@0: // CONTRACTIDS then fills the adjacency list with edges. michael@0: // An edge in this case is comprised of a FROM and TO MIME type combination. michael@0: // michael@0: // CONTRACTID format: michael@0: // @mozilla.org/streamconv;1?from=text/html&to=text/plain michael@0: // XXX curently we only handle a single from and to combo, we should repeat the michael@0: // XXX registration process for any series of from-to combos. michael@0: // XXX can use nsTokenizer for this. michael@0: // michael@0: michael@0: nsresult michael@0: nsStreamConverterService::BuildGraph() { michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr catmgr(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr entries; michael@0: rv = catmgr->EnumerateCategory(NS_ISTREAMCONVERTER_KEY, getter_AddRefs(entries)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // go through each entry to build the graph michael@0: nsCOMPtr supports; michael@0: nsCOMPtr entry; michael@0: rv = entries->GetNext(getter_AddRefs(supports)); michael@0: while (NS_SUCCEEDED(rv)) { michael@0: entry = do_QueryInterface(supports); michael@0: michael@0: // get the entry string michael@0: nsAutoCString entryString; michael@0: rv = entry->GetData(entryString); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // cobble the entry string w/ the converter key to produce a full contractID. michael@0: nsAutoCString contractID(NS_ISTREAMCONVERTER_KEY); michael@0: contractID.Append(entryString); michael@0: michael@0: // now we've got the CONTRACTID, let's parse it up. michael@0: rv = AddAdjacency(contractID.get()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = entries->GetNext(getter_AddRefs(supports)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // XXX currently you can not add the same adjacency (i.e. you can't have multiple michael@0: // XXX stream converters registering to handle the same from-to combination. It's michael@0: // XXX not programatically prohibited, it's just that results are un-predictable michael@0: // XXX right now. michael@0: nsresult michael@0: nsStreamConverterService::AddAdjacency(const char *aContractID) { michael@0: nsresult rv; michael@0: // first parse out the FROM and TO MIME-types. michael@0: michael@0: nsAutoCString fromStr, toStr; michael@0: rv = ParseFromTo(aContractID, fromStr, toStr); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Each MIME-type is a vertex in the graph, so first lets make sure michael@0: // each MIME-type is represented as a key in our hashtable. michael@0: michael@0: nsCStringKey fromKey(fromStr); michael@0: SCTableData *fromEdges = (SCTableData*)mAdjacencyList.Get(&fromKey); michael@0: if (!fromEdges) { michael@0: // There is no fromStr vertex, create one. michael@0: fromEdges = new SCTableData(); michael@0: mAdjacencyList.Put(&fromKey, fromEdges); michael@0: } michael@0: michael@0: nsCStringKey toKey(toStr); michael@0: if (!mAdjacencyList.Get(&toKey)) { michael@0: // There is no toStr vertex, create one. michael@0: mAdjacencyList.Put(&toKey, new SCTableData()); michael@0: } michael@0: michael@0: // Now we know the FROM and TO types are represented as keys in the hashtable. michael@0: // Let's "connect" the verticies, making an edge. michael@0: michael@0: nsCOMPtr vertex = do_GetAtom(toStr); michael@0: if (!vertex) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ASSERTION(fromEdges, "something wrong in adjacency list construction"); michael@0: if (!fromEdges) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return fromEdges->AppendObject(vertex) ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: nsStreamConverterService::ParseFromTo(const char *aContractID, nsCString &aFromRes, nsCString &aToRes) { michael@0: michael@0: nsAutoCString ContractIDStr(aContractID); michael@0: michael@0: int32_t fromLoc = ContractIDStr.Find("from="); michael@0: int32_t toLoc = ContractIDStr.Find("to="); michael@0: if (-1 == fromLoc || -1 == toLoc ) return NS_ERROR_FAILURE; michael@0: michael@0: fromLoc = fromLoc + 5; michael@0: toLoc = toLoc + 3; michael@0: michael@0: nsAutoCString fromStr, toStr; michael@0: michael@0: ContractIDStr.Mid(fromStr, fromLoc, toLoc - 4 - fromLoc); michael@0: ContractIDStr.Mid(toStr, toLoc, ContractIDStr.Length() - toLoc); michael@0: michael@0: aFromRes.Assign(fromStr); michael@0: aToRes.Assign(toStr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsObjectHashtable enumerator functions. michael@0: michael@0: // Initializes the BFS state table. michael@0: static bool InitBFSTable(nsHashKey *aKey, void *aData, void* closure) { michael@0: NS_ASSERTION((SCTableData*)aData, "no data in the table enumeration"); michael@0: michael@0: nsHashtable *BFSTable = (nsHashtable*)closure; michael@0: if (!BFSTable) return false; michael@0: michael@0: BFSTable->Put(aKey, new BFSTableData(static_cast(aKey))); michael@0: return true; michael@0: } michael@0: michael@0: // cleans up the BFS state table michael@0: static bool DeleteBFSEntry(nsHashKey *aKey, void *aData, void *closure) { michael@0: BFSTableData *data = (BFSTableData*)aData; michael@0: data->key = nullptr; michael@0: delete data; michael@0: return true; michael@0: } michael@0: michael@0: class CStreamConvDeallocator : public nsDequeFunctor { michael@0: public: michael@0: virtual void* operator()(void* anObject) { michael@0: nsCStringKey *key = (nsCStringKey*)anObject; michael@0: delete key; michael@0: return 0; michael@0: } michael@0: }; michael@0: michael@0: // walks the graph using a breadth-first-search algorithm which generates a discovered michael@0: // verticies tree. This tree is then walked up (from destination vertex, to origin vertex) michael@0: // and each link in the chain is added to an nsStringArray. A direct lookup for the given michael@0: // CONTRACTID should be made prior to calling this method in an attempt to find a direct michael@0: // converter rather than walking the graph. michael@0: nsresult michael@0: nsStreamConverterService::FindConverter(const char *aContractID, nsTArray **aEdgeList) { michael@0: nsresult rv; michael@0: if (!aEdgeList) return NS_ERROR_NULL_POINTER; michael@0: *aEdgeList = nullptr; michael@0: michael@0: // walk the graph in search of the appropriate converter. michael@0: michael@0: int32_t vertexCount = mAdjacencyList.Count(); michael@0: if (0 >= vertexCount) return NS_ERROR_FAILURE; michael@0: michael@0: // Create a corresponding color table for each vertex in the graph. michael@0: nsObjectHashtable lBFSTable(nullptr, nullptr, DeleteBFSEntry, nullptr); michael@0: mAdjacencyList.Enumerate(InitBFSTable, &lBFSTable); michael@0: michael@0: NS_ASSERTION(lBFSTable.Count() == vertexCount, "strmconv BFS table init problem"); michael@0: michael@0: // This is our source vertex; our starting point. michael@0: nsAutoCString fromC, toC; michael@0: rv = ParseFromTo(aContractID, fromC, toC); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCStringKey *source = new nsCStringKey(fromC.get()); michael@0: michael@0: BFSTableData *data = (BFSTableData*)lBFSTable.Get(source); michael@0: if (!data) { michael@0: delete source; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: data->color = gray; michael@0: data->distance = 0; michael@0: CStreamConvDeallocator *dtorFunc = new CStreamConvDeallocator(); michael@0: michael@0: nsDeque grayQ(dtorFunc); michael@0: michael@0: // Now generate the shortest path tree. michael@0: grayQ.Push(source); michael@0: while (0 < grayQ.GetSize()) { michael@0: nsCStringKey *currentHead = (nsCStringKey*)grayQ.PeekFront(); michael@0: SCTableData *data2 = (SCTableData*)mAdjacencyList.Get(currentHead); michael@0: if (!data2) return NS_ERROR_FAILURE; michael@0: michael@0: // Get the state of the current head to calculate the distance of each michael@0: // reachable vertex in the loop. michael@0: BFSTableData *headVertexState = (BFSTableData*)lBFSTable.Get(currentHead); michael@0: if (!headVertexState) return NS_ERROR_FAILURE; michael@0: michael@0: int32_t edgeCount = data2->Count(); michael@0: michael@0: for (int32_t i = 0; i < edgeCount; i++) { michael@0: nsIAtom* curVertexAtom = data2->ObjectAt(i); michael@0: nsAutoString curVertexStr; michael@0: curVertexAtom->ToString(curVertexStr); michael@0: nsCStringKey *curVertex = new nsCStringKey(ToNewCString(curVertexStr), michael@0: curVertexStr.Length(), nsCStringKey::OWN); michael@0: michael@0: BFSTableData *curVertexState = (BFSTableData*)lBFSTable.Get(curVertex); michael@0: if (!curVertexState) { michael@0: delete curVertex; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (white == curVertexState->color) { michael@0: curVertexState->color = gray; michael@0: curVertexState->distance = headVertexState->distance + 1; michael@0: curVertexState->predecessor = (nsCStringKey*)currentHead->Clone(); michael@0: if (!curVertexState->predecessor) { michael@0: delete curVertex; michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: grayQ.Push(curVertex); michael@0: } else { michael@0: delete curVertex; // if this vertex has already been discovered, we don't want michael@0: // to leak it. (non-discovered vertex's get cleaned up when michael@0: // they're popped). michael@0: } michael@0: } michael@0: headVertexState->color = black; michael@0: nsCStringKey *cur = (nsCStringKey*)grayQ.PopFront(); michael@0: delete cur; michael@0: cur = nullptr; michael@0: } michael@0: // The shortest path (if any) has been generated and is represented by the chain of michael@0: // BFSTableData->predecessor keys. Start at the bottom and work our way up. michael@0: michael@0: // first parse out the FROM and TO MIME-types being registered. michael@0: michael@0: nsAutoCString fromStr, toStr; michael@0: rv = ParseFromTo(aContractID, fromStr, toStr); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // get the root CONTRACTID michael@0: nsAutoCString ContractIDPrefix(NS_ISTREAMCONVERTER_KEY); michael@0: nsTArray *shortestPath = new nsTArray(); michael@0: michael@0: nsCStringKey toMIMEType(toStr); michael@0: data = (BFSTableData*)lBFSTable.Get(&toMIMEType); michael@0: if (!data) { michael@0: // If this vertex isn't in the BFSTable, then no-one has registered for it, michael@0: // therefore we can't do the conversion. michael@0: delete shortestPath; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: while (data) { michael@0: nsCStringKey *key = data->key; michael@0: michael@0: if (fromStr.Equals(key->GetString())) { michael@0: // found it. We're done here. michael@0: *aEdgeList = shortestPath; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // reconstruct the CONTRACTID. michael@0: // Get the predecessor. michael@0: if (!data->predecessor) break; // no predecessor michael@0: BFSTableData *predecessorData = (BFSTableData*)lBFSTable.Get(data->predecessor); michael@0: michael@0: if (!predecessorData) break; // no predecessor, chain doesn't exist. michael@0: michael@0: // build out the CONTRACTID. michael@0: nsAutoCString newContractID(ContractIDPrefix); michael@0: newContractID.AppendLiteral("?from="); michael@0: michael@0: nsCStringKey *predecessorKey = predecessorData->key; michael@0: newContractID.Append(predecessorKey->GetString()); michael@0: michael@0: newContractID.AppendLiteral("&to="); michael@0: newContractID.Append(key->GetString()); michael@0: michael@0: // Add this CONTRACTID to the chain. michael@0: rv = shortestPath->AppendElement(newContractID) ? NS_OK : NS_ERROR_FAILURE; // XXX this method incorrectly returns a bool michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "AppendElement failed"); michael@0: michael@0: // move up the tree. michael@0: data = predecessorData; michael@0: } michael@0: delete shortestPath; michael@0: return NS_ERROR_FAILURE; // couldn't find a stream converter or chain. michael@0: } michael@0: michael@0: michael@0: ///////////////////////////////////////////////////// michael@0: // nsIStreamConverterService methods michael@0: NS_IMETHODIMP michael@0: nsStreamConverterService::CanConvert(const char* aFromType, michael@0: const char* aToType, michael@0: bool* _retval) { michael@0: nsCOMPtr reg; michael@0: nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsAutoCString contractID; michael@0: contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from="); michael@0: contractID.Append(aFromType); michael@0: contractID.AppendLiteral("&to="); michael@0: contractID.Append(aToType); michael@0: michael@0: // See if we have a direct match michael@0: rv = reg->IsContractIDRegistered(contractID.get(), _retval); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: if (*_retval) michael@0: return NS_OK; michael@0: michael@0: // Otherwise try the graph. michael@0: rv = BuildGraph(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsTArray *converterChain = nullptr; michael@0: rv = FindConverter(contractID.get(), &converterChain); michael@0: *_retval = NS_SUCCEEDED(rv); michael@0: michael@0: delete converterChain; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStreamConverterService::Convert(nsIInputStream *aFromStream, michael@0: const char *aFromType, michael@0: const char *aToType, michael@0: nsISupports *aContext, michael@0: nsIInputStream **_retval) { michael@0: if (!aFromStream || !aFromType || !aToType || !_retval) return NS_ERROR_NULL_POINTER; michael@0: nsresult rv; michael@0: michael@0: // first determine whether we can even handle this conversion michael@0: // build a CONTRACTID michael@0: nsAutoCString contractID; michael@0: contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from="); michael@0: contractID.Append(aFromType); michael@0: contractID.AppendLiteral("&to="); michael@0: contractID.Append(aToType); michael@0: const char *cContractID = contractID.get(); michael@0: michael@0: nsCOMPtr converter(do_CreateInstance(cContractID, &rv)); michael@0: if (NS_FAILED(rv)) { michael@0: // couldn't go direct, let's try walking the graph of converters. michael@0: rv = BuildGraph(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsTArray *converterChain = nullptr; michael@0: michael@0: rv = FindConverter(cContractID, &converterChain); michael@0: if (NS_FAILED(rv)) { michael@0: // can't make this conversion. michael@0: // XXX should have a more descriptive error code. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: int32_t edgeCount = int32_t(converterChain->Length()); michael@0: NS_ASSERTION(edgeCount > 0, "findConverter should have failed"); michael@0: michael@0: michael@0: // convert the stream using each edge of the graph as a step. michael@0: // this is our stream conversion traversal. michael@0: nsCOMPtr dataToConvert = aFromStream; michael@0: nsCOMPtr convertedData; michael@0: michael@0: for (int32_t i = edgeCount-1; i >= 0; i--) { michael@0: const char *lContractID = converterChain->ElementAt(i).get(); michael@0: michael@0: converter = do_CreateInstance(lContractID, &rv); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: delete converterChain; michael@0: return rv; michael@0: } michael@0: michael@0: nsAutoCString fromStr, toStr; michael@0: rv = ParseFromTo(lContractID, fromStr, toStr); michael@0: if (NS_FAILED(rv)) { michael@0: delete converterChain; michael@0: return rv; michael@0: } michael@0: michael@0: rv = converter->Convert(dataToConvert, fromStr.get(), toStr.get(), aContext, getter_AddRefs(convertedData)); michael@0: dataToConvert = convertedData; michael@0: if (NS_FAILED(rv)) { michael@0: delete converterChain; michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: delete converterChain; michael@0: *_retval = convertedData; michael@0: NS_ADDREF(*_retval); michael@0: michael@0: } else { michael@0: // we're going direct. michael@0: rv = converter->Convert(aFromStream, aFromType, aToType, aContext, _retval); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsStreamConverterService::AsyncConvertData(const char *aFromType, michael@0: const char *aToType, michael@0: nsIStreamListener *aListener, michael@0: nsISupports *aContext, michael@0: nsIStreamListener **_retval) { michael@0: if (!aFromType || !aToType || !aListener || !_retval) return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsresult rv; michael@0: michael@0: // first determine whether we can even handle this conversion michael@0: // build a CONTRACTID michael@0: nsAutoCString contractID; michael@0: contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from="); michael@0: contractID.Append(aFromType); michael@0: contractID.AppendLiteral("&to="); michael@0: contractID.Append(aToType); michael@0: const char *cContractID = contractID.get(); michael@0: michael@0: nsCOMPtr listener(do_CreateInstance(cContractID, &rv)); michael@0: if (NS_FAILED(rv)) { michael@0: // couldn't go direct, let's try walking the graph of converters. michael@0: rv = BuildGraph(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsTArray *converterChain = nullptr; michael@0: michael@0: rv = FindConverter(cContractID, &converterChain); michael@0: if (NS_FAILED(rv)) { michael@0: // can't make this conversion. michael@0: // XXX should have a more descriptive error code. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // aListener is the listener that wants the final, converted, data. michael@0: // we initialize finalListener w/ aListener so it gets put at the michael@0: // tail end of the chain, which in the loop below, means the *first* michael@0: // converter created. michael@0: nsCOMPtr finalListener = aListener; michael@0: michael@0: // convert the stream using each edge of the graph as a step. michael@0: // this is our stream conversion traversal. michael@0: int32_t edgeCount = int32_t(converterChain->Length()); michael@0: NS_ASSERTION(edgeCount > 0, "findConverter should have failed"); michael@0: for (int i = 0; i < edgeCount; i++) { michael@0: const char *lContractID = converterChain->ElementAt(i).get(); michael@0: michael@0: // create the converter for this from/to pair michael@0: nsCOMPtr converter(do_CreateInstance(lContractID)); michael@0: NS_ASSERTION(converter, "graph construction problem, built a contractid that wasn't registered"); michael@0: michael@0: nsAutoCString fromStr, toStr; michael@0: rv = ParseFromTo(lContractID, fromStr, toStr); michael@0: if (NS_FAILED(rv)) { michael@0: delete converterChain; michael@0: return rv; michael@0: } michael@0: michael@0: // connect the converter w/ the listener that should get the converted data. michael@0: rv = converter->AsyncConvertData(fromStr.get(), toStr.get(), finalListener, aContext); michael@0: if (NS_FAILED(rv)) { michael@0: delete converterChain; michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr chainListener(do_QueryInterface(converter, &rv)); michael@0: if (NS_FAILED(rv)) { michael@0: delete converterChain; michael@0: return rv; michael@0: } michael@0: michael@0: // the last iteration of this loop will result in finalListener michael@0: // pointing to the converter that "starts" the conversion chain. michael@0: // this converter's "from" type is the original "from" type. Prior michael@0: // to the last iteration, finalListener will continuously be wedged michael@0: // into the next listener in the chain, then be updated. michael@0: finalListener = chainListener; michael@0: } michael@0: delete converterChain; michael@0: // return the first listener in the chain. michael@0: *_retval = finalListener; michael@0: NS_ADDREF(*_retval); michael@0: michael@0: } else { michael@0: // we're going direct. michael@0: *_retval = listener; michael@0: NS_ADDREF(*_retval); michael@0: michael@0: rv = listener->AsyncConvertData(aFromType, aToType, aListener, aContext); michael@0: } michael@0: michael@0: return rv; michael@0: michael@0: } michael@0: michael@0: nsresult michael@0: NS_NewStreamConv(nsStreamConverterService** aStreamConv) michael@0: { michael@0: NS_PRECONDITION(aStreamConv != nullptr, "null ptr"); michael@0: if (!aStreamConv) return NS_ERROR_NULL_POINTER; michael@0: michael@0: *aStreamConv = new nsStreamConverterService(); michael@0: NS_ADDREF(*aStreamConv); michael@0: michael@0: return NS_OK; michael@0: }