mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
680bd63580
Differential Revision: https://phabricator.services.mozilla.com/D209747
545 lines
18 KiB
C++
545 lines
18 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsStreamConverterService.h"
|
|
#include "nsIComponentRegistrar.h"
|
|
#include "nsString.h"
|
|
#include "nsAtom.h"
|
|
#include "nsDeque.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsIStreamConverter.h"
|
|
#include "nsICategoryManager.h"
|
|
#include "nsXPCOM.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "nsTArray.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsISimpleEnumerator.h"
|
|
#include "mozilla/Components.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Breadth-First-Search (BFS) algorithm state classes and types.
|
|
|
|
// Used to establish discovered verticies.
|
|
enum BFScolors { white, gray, black };
|
|
|
|
// BFS hashtable data class.
|
|
struct BFSTableData {
|
|
nsCString key;
|
|
BFScolors color;
|
|
int32_t distance;
|
|
mozilla::UniquePtr<nsCString> predecessor;
|
|
|
|
explicit BFSTableData(const nsACString& aKey)
|
|
: key(aKey), color(white), distance(-1) {}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// nsISupports methods
|
|
NS_IMPL_ISUPPORTS(nsStreamConverterService, nsIStreamConverterService)
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// nsIStreamConverterService methods
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// nsStreamConverterService methods
|
|
|
|
// Builds the graph represented as an adjacency list (and built up in
|
|
// memory using an nsObjectHashtable and nsCOMArray combination).
|
|
//
|
|
// :BuildGraph() consults the category manager for all stream converter
|
|
// CONTRACTIDS then fills the adjacency list with edges.
|
|
// An edge in this case is comprised of a FROM and TO MIME type combination.
|
|
//
|
|
// CONTRACTID format:
|
|
// @mozilla.org/streamconv;1?from=text/html&to=text/plain
|
|
// XXX curently we only handle a single from and to combo, we should repeat the
|
|
// XXX registration process for any series of from-to combos.
|
|
// XXX can use nsTokenizer for this.
|
|
//
|
|
|
|
nsresult nsStreamConverterService::BuildGraph() {
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsICategoryManager> catmgr(
|
|
mozilla::components::CategoryManager::Service(&rv));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> entries;
|
|
rv = catmgr->EnumerateCategory(NS_ISTREAMCONVERTER_KEY,
|
|
getter_AddRefs(entries));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// go through each entry to build the graph
|
|
nsCOMPtr<nsISupports> supports;
|
|
nsCOMPtr<nsISupportsCString> entry;
|
|
rv = entries->GetNext(getter_AddRefs(supports));
|
|
while (NS_SUCCEEDED(rv)) {
|
|
entry = do_QueryInterface(supports);
|
|
|
|
// get the entry string
|
|
nsAutoCString entryString;
|
|
rv = entry->GetData(entryString);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// cobble the entry string w/ the converter key to produce a full
|
|
// contractID.
|
|
nsAutoCString contractID(NS_ISTREAMCONVERTER_KEY);
|
|
contractID.Append(entryString);
|
|
|
|
// now we've got the CONTRACTID, let's parse it up.
|
|
rv = AddAdjacency(contractID.get());
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = entries->GetNext(getter_AddRefs(supports));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX currently you can not add the same adjacency (i.e. you can't have
|
|
// multiple
|
|
// XXX stream converters registering to handle the same from-to combination.
|
|
// It's
|
|
// XXX not programatically prohibited, it's just that results are un-predictable
|
|
// XXX right now.
|
|
nsresult nsStreamConverterService::AddAdjacency(const char* aContractID) {
|
|
nsresult rv;
|
|
// first parse out the FROM and TO MIME-types.
|
|
|
|
nsAutoCString fromStr, toStr;
|
|
rv = ParseFromTo(aContractID, fromStr, toStr);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Each MIME-type is a vertex in the graph, so first lets make sure
|
|
// each MIME-type is represented as a key in our hashtable.
|
|
|
|
nsTArray<RefPtr<nsAtom>>* const fromEdges =
|
|
mAdjacencyList.GetOrInsertNew(fromStr);
|
|
|
|
mAdjacencyList.GetOrInsertNew(toStr);
|
|
|
|
// Now we know the FROM and TO types are represented as keys in the hashtable.
|
|
// Let's "connect" the verticies, making an edge.
|
|
|
|
RefPtr<nsAtom> vertex = NS_Atomize(toStr);
|
|
if (!vertex) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ASSERTION(fromEdges, "something wrong in adjacency list construction");
|
|
if (!fromEdges) return NS_ERROR_FAILURE;
|
|
|
|
// XXX(Bug 1631371) Check if this should use a fallible operation as it
|
|
// pretended earlier.
|
|
fromEdges->AppendElement(vertex);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsStreamConverterService::ParseFromTo(const char* aContractID,
|
|
nsCString& aFromRes,
|
|
nsCString& aToRes) {
|
|
nsAutoCString ContractIDStr(aContractID);
|
|
|
|
int32_t fromLoc = ContractIDStr.Find("from=");
|
|
int32_t toLoc = ContractIDStr.Find("to=");
|
|
if (-1 == fromLoc || -1 == toLoc) return NS_ERROR_FAILURE;
|
|
|
|
fromLoc = fromLoc + 5;
|
|
toLoc = toLoc + 3;
|
|
|
|
nsAutoCString fromStr, toStr;
|
|
|
|
ContractIDStr.Mid(fromStr, fromLoc, toLoc - 4 - fromLoc);
|
|
ContractIDStr.Mid(toStr, toLoc, ContractIDStr.Length() - toLoc);
|
|
|
|
aFromRes.Assign(fromStr);
|
|
aToRes.Assign(toStr);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
using BFSHashTable = nsClassHashtable<nsCStringHashKey, BFSTableData>;
|
|
|
|
// nsObjectHashtable enumerator functions.
|
|
|
|
class CStreamConvDeallocator : public nsDequeFunctor<nsCString> {
|
|
public:
|
|
void operator()(nsCString* anObject) override { delete anObject; }
|
|
};
|
|
|
|
// walks the graph using a breadth-first-search algorithm which generates a
|
|
// discovered verticies tree. This tree is then walked up (from destination
|
|
// vertex, to origin vertex) and each link in the chain is added to an
|
|
// nsStringArray. A direct lookup for the given CONTRACTID should be made prior
|
|
// to calling this method in an attempt to find a direct converter rather than
|
|
// walking the graph.
|
|
nsresult nsStreamConverterService::FindConverter(
|
|
const char* aContractID, nsTArray<nsCString>** aEdgeList) {
|
|
nsresult rv;
|
|
if (!aEdgeList) return NS_ERROR_NULL_POINTER;
|
|
*aEdgeList = nullptr;
|
|
|
|
// walk the graph in search of the appropriate converter.
|
|
|
|
uint32_t vertexCount = mAdjacencyList.Count();
|
|
if (0 >= vertexCount) return NS_ERROR_FAILURE;
|
|
|
|
// Create a corresponding color table for each vertex in the graph.
|
|
BFSHashTable lBFSTable;
|
|
for (const auto& entry : mAdjacencyList) {
|
|
const nsACString& key = entry.GetKey();
|
|
MOZ_ASSERT(entry.GetWeak(), "no data in the table iteration");
|
|
lBFSTable.InsertOrUpdate(key, mozilla::MakeUnique<BFSTableData>(key));
|
|
}
|
|
|
|
NS_ASSERTION(lBFSTable.Count() == vertexCount,
|
|
"strmconv BFS table init problem");
|
|
|
|
// This is our source vertex; our starting point.
|
|
nsAutoCString fromC, toC;
|
|
rv = ParseFromTo(aContractID, fromC, toC);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
BFSTableData* data = lBFSTable.Get(fromC);
|
|
if (!data) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
data->color = gray;
|
|
data->distance = 0;
|
|
auto* dtorFunc = new CStreamConvDeallocator();
|
|
|
|
nsDeque grayQ(dtorFunc);
|
|
|
|
// Now generate the shortest path tree.
|
|
grayQ.Push(new nsCString(fromC));
|
|
while (0 < grayQ.GetSize()) {
|
|
nsCString* currentHead = (nsCString*)grayQ.PeekFront();
|
|
nsTArray<RefPtr<nsAtom>>* data2 = mAdjacencyList.Get(*currentHead);
|
|
if (!data2) return NS_ERROR_FAILURE;
|
|
|
|
// Get the state of the current head to calculate the distance of each
|
|
// reachable vertex in the loop.
|
|
BFSTableData* headVertexState = lBFSTable.Get(*currentHead);
|
|
if (!headVertexState) return NS_ERROR_FAILURE;
|
|
|
|
int32_t edgeCount = data2->Length();
|
|
|
|
for (int32_t i = 0; i < edgeCount; i++) {
|
|
nsAtom* curVertexAtom = data2->ElementAt(i);
|
|
auto* curVertex = new nsCString();
|
|
curVertexAtom->ToUTF8String(*curVertex);
|
|
|
|
BFSTableData* curVertexState = lBFSTable.Get(*curVertex);
|
|
if (!curVertexState) {
|
|
delete curVertex;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (white == curVertexState->color) {
|
|
curVertexState->color = gray;
|
|
curVertexState->distance = headVertexState->distance + 1;
|
|
curVertexState->predecessor =
|
|
mozilla::MakeUnique<nsCString>(*currentHead);
|
|
grayQ.Push(curVertex);
|
|
} else {
|
|
delete curVertex; // if this vertex has already been discovered, we
|
|
// don't want to leak it. (non-discovered vertex's
|
|
// get cleaned up when they're popped).
|
|
}
|
|
}
|
|
headVertexState->color = black;
|
|
nsCString* cur = (nsCString*)grayQ.PopFront();
|
|
delete cur;
|
|
cur = nullptr;
|
|
}
|
|
// The shortest path (if any) has been generated and is represented by the
|
|
// chain of BFSTableData->predecessor keys. Start at the bottom and work our
|
|
// way up.
|
|
|
|
// first parse out the FROM and TO MIME-types being registered.
|
|
|
|
nsAutoCString fromStr, toMIMEType;
|
|
rv = ParseFromTo(aContractID, fromStr, toMIMEType);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// get the root CONTRACTID
|
|
nsAutoCString ContractIDPrefix(NS_ISTREAMCONVERTER_KEY);
|
|
auto* shortestPath = new nsTArray<nsCString>();
|
|
|
|
data = lBFSTable.Get(toMIMEType);
|
|
if (!data) {
|
|
// If this vertex isn't in the BFSTable, then no-one has registered for it,
|
|
// therefore we can't do the conversion.
|
|
delete shortestPath;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
while (data) {
|
|
if (fromStr.Equals(data->key)) {
|
|
// found it. We're done here.
|
|
*aEdgeList = shortestPath;
|
|
return NS_OK;
|
|
}
|
|
|
|
// reconstruct the CONTRACTID.
|
|
// Get the predecessor.
|
|
if (!data->predecessor) break; // no predecessor
|
|
BFSTableData* predecessorData = lBFSTable.Get(*data->predecessor);
|
|
|
|
if (!predecessorData) break; // no predecessor, chain doesn't exist.
|
|
|
|
// build out the CONTRACTID.
|
|
nsAutoCString newContractID(ContractIDPrefix);
|
|
newContractID.AppendLiteral("?from=");
|
|
|
|
newContractID.Append(predecessorData->key);
|
|
|
|
newContractID.AppendLiteral("&to=");
|
|
newContractID.Append(data->key);
|
|
|
|
// Add this CONTRACTID to the chain.
|
|
// XXX(Bug 1631371) Check if this should use a fallible operation as it
|
|
// pretended earlier.
|
|
shortestPath->AppendElement(newContractID);
|
|
|
|
// move up the tree.
|
|
data = predecessorData;
|
|
}
|
|
delete shortestPath;
|
|
return NS_ERROR_FAILURE; // couldn't find a stream converter or chain.
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
// nsIStreamConverterService methods
|
|
NS_IMETHODIMP
|
|
nsStreamConverterService::CanConvert(const char* aFromType, const char* aToType,
|
|
bool* _retval) {
|
|
nsCOMPtr<nsIComponentRegistrar> reg;
|
|
nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsAutoCString contractID;
|
|
contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
|
|
contractID.Append(aFromType);
|
|
contractID.AppendLiteral("&to=");
|
|
contractID.Append(aToType);
|
|
|
|
// See if we have a direct match
|
|
rv = reg->IsContractIDRegistered(contractID.get(), _retval);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (*_retval) return NS_OK;
|
|
|
|
// Otherwise try the graph.
|
|
rv = BuildGraph();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsTArray<nsCString>* converterChain = nullptr;
|
|
rv = FindConverter(contractID.get(), &converterChain);
|
|
*_retval = NS_SUCCEEDED(rv);
|
|
|
|
delete converterChain;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsStreamConverterService::ConvertedType(const nsACString& aFromType,
|
|
nsIChannel* aChannel,
|
|
nsACString& aOutToType) {
|
|
// first determine whether we can even handle this conversion
|
|
// build a CONTRACTID
|
|
nsAutoCString contractID;
|
|
contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
|
|
contractID.Append(aFromType);
|
|
contractID.AppendLiteral("&to=*/*");
|
|
const char* cContractID = contractID.get();
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return converter->GetConvertedType(aFromType, aChannel, aOutToType);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsStreamConverterService::Convert(nsIInputStream* aFromStream,
|
|
const char* aFromType, const char* aToType,
|
|
nsISupports* aContext,
|
|
nsIInputStream** _retval) {
|
|
if (!aFromStream || !aFromType || !aToType || !_retval) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
nsresult rv;
|
|
|
|
// first determine whether we can even handle this conversion
|
|
// build a CONTRACTID
|
|
nsAutoCString contractID;
|
|
contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
|
|
contractID.Append(aFromType);
|
|
contractID.AppendLiteral("&to=");
|
|
contractID.Append(aToType);
|
|
const char* cContractID = contractID.get();
|
|
|
|
nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
|
|
if (NS_FAILED(rv)) {
|
|
// couldn't go direct, let's try walking the graph of converters.
|
|
rv = BuildGraph();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsTArray<nsCString>* converterChain = nullptr;
|
|
|
|
rv = FindConverter(cContractID, &converterChain);
|
|
if (NS_FAILED(rv)) {
|
|
// can't make this conversion.
|
|
// XXX should have a more descriptive error code.
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
int32_t edgeCount = int32_t(converterChain->Length());
|
|
NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
|
|
|
|
// convert the stream using each edge of the graph as a step.
|
|
// this is our stream conversion traversal.
|
|
nsCOMPtr<nsIInputStream> dataToConvert = aFromStream;
|
|
nsCOMPtr<nsIInputStream> convertedData;
|
|
|
|
for (int32_t i = edgeCount - 1; i >= 0; i--) {
|
|
const char* lContractID = converterChain->ElementAt(i).get();
|
|
|
|
converter = do_CreateInstance(lContractID, &rv);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
delete converterChain;
|
|
return rv;
|
|
}
|
|
|
|
nsAutoCString fromStr, toStr;
|
|
rv = ParseFromTo(lContractID, fromStr, toStr);
|
|
if (NS_FAILED(rv)) {
|
|
delete converterChain;
|
|
return rv;
|
|
}
|
|
|
|
rv = converter->Convert(dataToConvert, fromStr.get(), toStr.get(),
|
|
aContext, getter_AddRefs(convertedData));
|
|
dataToConvert = convertedData;
|
|
if (NS_FAILED(rv)) {
|
|
delete converterChain;
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
delete converterChain;
|
|
convertedData.forget(_retval);
|
|
} else {
|
|
// we're going direct.
|
|
rv = converter->Convert(aFromStream, aFromType, aToType, aContext, _retval);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsStreamConverterService::AsyncConvertData(const char* aFromType,
|
|
const char* aToType,
|
|
nsIStreamListener* aListener,
|
|
nsISupports* aContext,
|
|
nsIStreamListener** _retval) {
|
|
if (!aFromType || !aToType || !aListener || !_retval) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
|
|
nsresult rv;
|
|
|
|
// first determine whether we can even handle this conversion
|
|
// build a CONTRACTID
|
|
nsAutoCString contractID;
|
|
contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
|
|
contractID.Append(aFromType);
|
|
contractID.AppendLiteral("&to=");
|
|
contractID.Append(aToType);
|
|
const char* cContractID = contractID.get();
|
|
|
|
nsCOMPtr<nsIStreamConverter> listener(do_CreateInstance(cContractID, &rv));
|
|
if (NS_FAILED(rv)) {
|
|
// couldn't go direct, let's try walking the graph of converters.
|
|
rv = BuildGraph();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsTArray<nsCString>* converterChain = nullptr;
|
|
|
|
rv = FindConverter(cContractID, &converterChain);
|
|
if (NS_FAILED(rv)) {
|
|
// can't make this conversion.
|
|
// XXX should have a more descriptive error code.
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// aListener is the listener that wants the final, converted, data.
|
|
// we initialize finalListener w/ aListener so it gets put at the
|
|
// tail end of the chain, which in the loop below, means the *first*
|
|
// converter created.
|
|
nsCOMPtr<nsIStreamListener> finalListener = aListener;
|
|
|
|
// convert the stream using each edge of the graph as a step.
|
|
// this is our stream conversion traversal.
|
|
int32_t edgeCount = int32_t(converterChain->Length());
|
|
NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
|
|
for (int i = 0; i < edgeCount; i++) {
|
|
const char* lContractID = converterChain->ElementAt(i).get();
|
|
|
|
// create the converter for this from/to pair
|
|
nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(lContractID));
|
|
NS_ASSERTION(converter,
|
|
"graph construction problem, built a contractid that wasn't "
|
|
"registered");
|
|
|
|
nsAutoCString fromStr, toStr;
|
|
rv = ParseFromTo(lContractID, fromStr, toStr);
|
|
if (NS_FAILED(rv)) {
|
|
delete converterChain;
|
|
return rv;
|
|
}
|
|
|
|
// connect the converter w/ the listener that should get the converted
|
|
// data.
|
|
rv = converter->AsyncConvertData(fromStr.get(), toStr.get(),
|
|
finalListener, aContext);
|
|
if (NS_FAILED(rv)) {
|
|
delete converterChain;
|
|
return rv;
|
|
}
|
|
|
|
// the last iteration of this loop will result in finalListener
|
|
// pointing to the converter that "starts" the conversion chain.
|
|
// this converter's "from" type is the original "from" type. Prior
|
|
// to the last iteration, finalListener will continuously be wedged
|
|
// into the next listener in the chain, then be updated.
|
|
finalListener = converter;
|
|
}
|
|
delete converterChain;
|
|
// return the first listener in the chain.
|
|
finalListener.forget(_retval);
|
|
} else {
|
|
// we're going direct.
|
|
rv = listener->AsyncConvertData(aFromType, aToType, aListener, aContext);
|
|
listener.forget(_retval);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult NS_NewStreamConv(nsStreamConverterService** aStreamConv) {
|
|
MOZ_ASSERT(aStreamConv != nullptr, "null ptr");
|
|
if (!aStreamConv) return NS_ERROR_NULL_POINTER;
|
|
|
|
RefPtr<nsStreamConverterService> conv = new nsStreamConverterService();
|
|
conv.forget(aStreamConv);
|
|
|
|
return NS_OK;
|
|
}
|