Bug 1861709 replace AudioCallbackDriver::ThreadRunning() assertions that mean to...
[gecko.git] / netwerk / streamconv / nsStreamConverterService.cpp
blobce95bb0404f74b3fd6834652242beaacff74dc1c
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsComponentManagerUtils.h"
8 #include "nsStreamConverterService.h"
9 #include "nsIComponentRegistrar.h"
10 #include "nsString.h"
11 #include "nsAtom.h"
12 #include "nsDeque.h"
13 #include "nsIInputStream.h"
14 #include "nsIStreamConverter.h"
15 #include "nsICategoryManager.h"
16 #include "nsXPCOM.h"
17 #include "nsISupportsPrimitives.h"
18 #include "nsTArray.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsISimpleEnumerator.h"
21 #include "mozilla/UniquePtr.h"
23 ///////////////////////////////////////////////////////////////////
24 // Breadth-First-Search (BFS) algorithm state classes and types.
26 // Used to establish discovered verticies.
27 enum BFScolors { white, gray, black };
29 // BFS hashtable data class.
30 struct BFSTableData {
31 nsCString key;
32 BFScolors color;
33 int32_t distance;
34 mozilla::UniquePtr<nsCString> predecessor;
36 explicit BFSTableData(const nsACString& aKey)
37 : key(aKey), color(white), distance(-1) {}
40 ////////////////////////////////////////////////////////////
41 // nsISupports methods
42 NS_IMPL_ISUPPORTS(nsStreamConverterService, nsIStreamConverterService)
44 ////////////////////////////////////////////////////////////
45 // nsIStreamConverterService methods
47 ////////////////////////////////////////////////////////////
48 // nsStreamConverterService methods
50 // Builds the graph represented as an adjacency list (and built up in
51 // memory using an nsObjectHashtable and nsCOMArray combination).
53 // :BuildGraph() consults the category manager for all stream converter
54 // CONTRACTIDS then fills the adjacency list with edges.
55 // An edge in this case is comprised of a FROM and TO MIME type combination.
57 // CONTRACTID format:
58 // @mozilla.org/streamconv;1?from=text/html&to=text/plain
59 // XXX curently we only handle a single from and to combo, we should repeat the
60 // XXX registration process for any series of from-to combos.
61 // XXX can use nsTokenizer for this.
64 nsresult nsStreamConverterService::BuildGraph() {
65 nsresult rv;
67 nsCOMPtr<nsICategoryManager> catmgr(
68 do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
69 if (NS_FAILED(rv)) return rv;
71 nsCOMPtr<nsISimpleEnumerator> entries;
72 rv = catmgr->EnumerateCategory(NS_ISTREAMCONVERTER_KEY,
73 getter_AddRefs(entries));
74 if (NS_FAILED(rv)) return rv;
76 // go through each entry to build the graph
77 nsCOMPtr<nsISupports> supports;
78 nsCOMPtr<nsISupportsCString> entry;
79 rv = entries->GetNext(getter_AddRefs(supports));
80 while (NS_SUCCEEDED(rv)) {
81 entry = do_QueryInterface(supports);
83 // get the entry string
84 nsAutoCString entryString;
85 rv = entry->GetData(entryString);
86 if (NS_FAILED(rv)) return rv;
88 // cobble the entry string w/ the converter key to produce a full
89 // contractID.
90 nsAutoCString contractID(NS_ISTREAMCONVERTER_KEY);
91 contractID.Append(entryString);
93 // now we've got the CONTRACTID, let's parse it up.
94 rv = AddAdjacency(contractID.get());
95 if (NS_FAILED(rv)) return rv;
97 rv = entries->GetNext(getter_AddRefs(supports));
100 return NS_OK;
103 // XXX currently you can not add the same adjacency (i.e. you can't have
104 // multiple
105 // XXX stream converters registering to handle the same from-to combination.
106 // It's
107 // XXX not programatically prohibited, it's just that results are un-predictable
108 // XXX right now.
109 nsresult nsStreamConverterService::AddAdjacency(const char* aContractID) {
110 nsresult rv;
111 // first parse out the FROM and TO MIME-types.
113 nsAutoCString fromStr, toStr;
114 rv = ParseFromTo(aContractID, fromStr, toStr);
115 if (NS_FAILED(rv)) return rv;
117 // Each MIME-type is a vertex in the graph, so first lets make sure
118 // each MIME-type is represented as a key in our hashtable.
120 nsTArray<RefPtr<nsAtom>>* const fromEdges =
121 mAdjacencyList.GetOrInsertNew(fromStr);
123 mAdjacencyList.GetOrInsertNew(toStr);
125 // Now we know the FROM and TO types are represented as keys in the hashtable.
126 // Let's "connect" the verticies, making an edge.
128 RefPtr<nsAtom> vertex = NS_Atomize(toStr);
129 if (!vertex) return NS_ERROR_OUT_OF_MEMORY;
131 NS_ASSERTION(fromEdges, "something wrong in adjacency list construction");
132 if (!fromEdges) return NS_ERROR_FAILURE;
134 // XXX(Bug 1631371) Check if this should use a fallible operation as it
135 // pretended earlier.
136 fromEdges->AppendElement(vertex);
137 return NS_OK;
140 nsresult nsStreamConverterService::ParseFromTo(const char* aContractID,
141 nsCString& aFromRes,
142 nsCString& aToRes) {
143 nsAutoCString ContractIDStr(aContractID);
145 int32_t fromLoc = ContractIDStr.Find("from=");
146 int32_t toLoc = ContractIDStr.Find("to=");
147 if (-1 == fromLoc || -1 == toLoc) return NS_ERROR_FAILURE;
149 fromLoc = fromLoc + 5;
150 toLoc = toLoc + 3;
152 nsAutoCString fromStr, toStr;
154 ContractIDStr.Mid(fromStr, fromLoc, toLoc - 4 - fromLoc);
155 ContractIDStr.Mid(toStr, toLoc, ContractIDStr.Length() - toLoc);
157 aFromRes.Assign(fromStr);
158 aToRes.Assign(toStr);
160 return NS_OK;
163 using BFSHashTable = nsClassHashtable<nsCStringHashKey, BFSTableData>;
165 // nsObjectHashtable enumerator functions.
167 class CStreamConvDeallocator : public nsDequeFunctor<nsCString> {
168 public:
169 void operator()(nsCString* anObject) override { delete anObject; }
172 // walks the graph using a breadth-first-search algorithm which generates a
173 // discovered verticies tree. This tree is then walked up (from destination
174 // vertex, to origin vertex) and each link in the chain is added to an
175 // nsStringArray. A direct lookup for the given CONTRACTID should be made prior
176 // to calling this method in an attempt to find a direct converter rather than
177 // walking the graph.
178 nsresult nsStreamConverterService::FindConverter(
179 const char* aContractID, nsTArray<nsCString>** aEdgeList) {
180 nsresult rv;
181 if (!aEdgeList) return NS_ERROR_NULL_POINTER;
182 *aEdgeList = nullptr;
184 // walk the graph in search of the appropriate converter.
186 uint32_t vertexCount = mAdjacencyList.Count();
187 if (0 >= vertexCount) return NS_ERROR_FAILURE;
189 // Create a corresponding color table for each vertex in the graph.
190 BFSHashTable lBFSTable;
191 for (const auto& entry : mAdjacencyList) {
192 const nsACString& key = entry.GetKey();
193 MOZ_ASSERT(entry.GetWeak(), "no data in the table iteration");
194 lBFSTable.InsertOrUpdate(key, mozilla::MakeUnique<BFSTableData>(key));
197 NS_ASSERTION(lBFSTable.Count() == vertexCount,
198 "strmconv BFS table init problem");
200 // This is our source vertex; our starting point.
201 nsAutoCString fromC, toC;
202 rv = ParseFromTo(aContractID, fromC, toC);
203 if (NS_FAILED(rv)) return rv;
205 BFSTableData* data = lBFSTable.Get(fromC);
206 if (!data) {
207 return NS_ERROR_FAILURE;
210 data->color = gray;
211 data->distance = 0;
212 auto* dtorFunc = new CStreamConvDeallocator();
214 nsDeque grayQ(dtorFunc);
216 // Now generate the shortest path tree.
217 grayQ.Push(new nsCString(fromC));
218 while (0 < grayQ.GetSize()) {
219 nsCString* currentHead = (nsCString*)grayQ.PeekFront();
220 nsTArray<RefPtr<nsAtom>>* data2 = mAdjacencyList.Get(*currentHead);
221 if (!data2) return NS_ERROR_FAILURE;
223 // Get the state of the current head to calculate the distance of each
224 // reachable vertex in the loop.
225 BFSTableData* headVertexState = lBFSTable.Get(*currentHead);
226 if (!headVertexState) return NS_ERROR_FAILURE;
228 int32_t edgeCount = data2->Length();
230 for (int32_t i = 0; i < edgeCount; i++) {
231 nsAtom* curVertexAtom = data2->ElementAt(i);
232 auto* curVertex = new nsCString();
233 curVertexAtom->ToUTF8String(*curVertex);
235 BFSTableData* curVertexState = lBFSTable.Get(*curVertex);
236 if (!curVertexState) {
237 delete curVertex;
238 return NS_ERROR_FAILURE;
241 if (white == curVertexState->color) {
242 curVertexState->color = gray;
243 curVertexState->distance = headVertexState->distance + 1;
244 curVertexState->predecessor =
245 mozilla::MakeUnique<nsCString>(*currentHead);
246 grayQ.Push(curVertex);
247 } else {
248 delete curVertex; // if this vertex has already been discovered, we
249 // don't want to leak it. (non-discovered vertex's
250 // get cleaned up when they're popped).
253 headVertexState->color = black;
254 nsCString* cur = (nsCString*)grayQ.PopFront();
255 delete cur;
256 cur = nullptr;
258 // The shortest path (if any) has been generated and is represented by the
259 // chain of BFSTableData->predecessor keys. Start at the bottom and work our
260 // way up.
262 // first parse out the FROM and TO MIME-types being registered.
264 nsAutoCString fromStr, toMIMEType;
265 rv = ParseFromTo(aContractID, fromStr, toMIMEType);
266 if (NS_FAILED(rv)) return rv;
268 // get the root CONTRACTID
269 nsAutoCString ContractIDPrefix(NS_ISTREAMCONVERTER_KEY);
270 auto* shortestPath = new nsTArray<nsCString>();
272 data = lBFSTable.Get(toMIMEType);
273 if (!data) {
274 // If this vertex isn't in the BFSTable, then no-one has registered for it,
275 // therefore we can't do the conversion.
276 delete shortestPath;
277 return NS_ERROR_FAILURE;
280 while (data) {
281 if (fromStr.Equals(data->key)) {
282 // found it. We're done here.
283 *aEdgeList = shortestPath;
284 return NS_OK;
287 // reconstruct the CONTRACTID.
288 // Get the predecessor.
289 if (!data->predecessor) break; // no predecessor
290 BFSTableData* predecessorData = lBFSTable.Get(*data->predecessor);
292 if (!predecessorData) break; // no predecessor, chain doesn't exist.
294 // build out the CONTRACTID.
295 nsAutoCString newContractID(ContractIDPrefix);
296 newContractID.AppendLiteral("?from=");
298 newContractID.Append(predecessorData->key);
300 newContractID.AppendLiteral("&to=");
301 newContractID.Append(data->key);
303 // Add this CONTRACTID to the chain.
304 // XXX(Bug 1631371) Check if this should use a fallible operation as it
305 // pretended earlier.
306 shortestPath->AppendElement(newContractID);
308 // move up the tree.
309 data = predecessorData;
311 delete shortestPath;
312 return NS_ERROR_FAILURE; // couldn't find a stream converter or chain.
315 /////////////////////////////////////////////////////
316 // nsIStreamConverterService methods
317 NS_IMETHODIMP
318 nsStreamConverterService::CanConvert(const char* aFromType, const char* aToType,
319 bool* _retval) {
320 nsCOMPtr<nsIComponentRegistrar> reg;
321 nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg));
322 if (NS_FAILED(rv)) return rv;
324 nsAutoCString contractID;
325 contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
326 contractID.Append(aFromType);
327 contractID.AppendLiteral("&to=");
328 contractID.Append(aToType);
330 // See if we have a direct match
331 rv = reg->IsContractIDRegistered(contractID.get(), _retval);
332 if (NS_FAILED(rv)) return rv;
333 if (*_retval) return NS_OK;
335 // Otherwise try the graph.
336 rv = BuildGraph();
337 if (NS_FAILED(rv)) return rv;
339 nsTArray<nsCString>* converterChain = nullptr;
340 rv = FindConverter(contractID.get(), &converterChain);
341 *_retval = NS_SUCCEEDED(rv);
343 delete converterChain;
344 return NS_OK;
347 NS_IMETHODIMP
348 nsStreamConverterService::ConvertedType(const nsACString& aFromType,
349 nsIChannel* aChannel,
350 nsACString& aOutToType) {
351 // first determine whether we can even handle this conversion
352 // build a CONTRACTID
353 nsAutoCString contractID;
354 contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
355 contractID.Append(aFromType);
356 contractID.AppendLiteral("&to=*/*");
357 const char* cContractID = contractID.get();
359 nsresult rv;
360 nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
361 if (NS_SUCCEEDED(rv)) {
362 return converter->GetConvertedType(aFromType, aChannel, aOutToType);
364 return rv;
367 NS_IMETHODIMP
368 nsStreamConverterService::Convert(nsIInputStream* aFromStream,
369 const char* aFromType, const char* aToType,
370 nsISupports* aContext,
371 nsIInputStream** _retval) {
372 if (!aFromStream || !aFromType || !aToType || !_retval) {
373 return NS_ERROR_NULL_POINTER;
375 nsresult rv;
377 // first determine whether we can even handle this conversion
378 // build a CONTRACTID
379 nsAutoCString contractID;
380 contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
381 contractID.Append(aFromType);
382 contractID.AppendLiteral("&to=");
383 contractID.Append(aToType);
384 const char* cContractID = contractID.get();
386 nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
387 if (NS_FAILED(rv)) {
388 // couldn't go direct, let's try walking the graph of converters.
389 rv = BuildGraph();
390 if (NS_FAILED(rv)) return rv;
392 nsTArray<nsCString>* converterChain = nullptr;
394 rv = FindConverter(cContractID, &converterChain);
395 if (NS_FAILED(rv)) {
396 // can't make this conversion.
397 // XXX should have a more descriptive error code.
398 return NS_ERROR_FAILURE;
401 int32_t edgeCount = int32_t(converterChain->Length());
402 NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
404 // convert the stream using each edge of the graph as a step.
405 // this is our stream conversion traversal.
406 nsCOMPtr<nsIInputStream> dataToConvert = aFromStream;
407 nsCOMPtr<nsIInputStream> convertedData;
409 for (int32_t i = edgeCount - 1; i >= 0; i--) {
410 const char* lContractID = converterChain->ElementAt(i).get();
412 converter = do_CreateInstance(lContractID, &rv);
414 if (NS_FAILED(rv)) {
415 delete converterChain;
416 return rv;
419 nsAutoCString fromStr, toStr;
420 rv = ParseFromTo(lContractID, fromStr, toStr);
421 if (NS_FAILED(rv)) {
422 delete converterChain;
423 return rv;
426 rv = converter->Convert(dataToConvert, fromStr.get(), toStr.get(),
427 aContext, getter_AddRefs(convertedData));
428 dataToConvert = convertedData;
429 if (NS_FAILED(rv)) {
430 delete converterChain;
431 return rv;
435 delete converterChain;
436 convertedData.forget(_retval);
437 } else {
438 // we're going direct.
439 rv = converter->Convert(aFromStream, aFromType, aToType, aContext, _retval);
442 return rv;
445 NS_IMETHODIMP
446 nsStreamConverterService::AsyncConvertData(const char* aFromType,
447 const char* aToType,
448 nsIStreamListener* aListener,
449 nsISupports* aContext,
450 nsIStreamListener** _retval) {
451 if (!aFromType || !aToType || !aListener || !_retval) {
452 return NS_ERROR_NULL_POINTER;
455 nsresult rv;
457 // first determine whether we can even handle this conversion
458 // build a CONTRACTID
459 nsAutoCString contractID;
460 contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
461 contractID.Append(aFromType);
462 contractID.AppendLiteral("&to=");
463 contractID.Append(aToType);
464 const char* cContractID = contractID.get();
466 nsCOMPtr<nsIStreamConverter> listener(do_CreateInstance(cContractID, &rv));
467 if (NS_FAILED(rv)) {
468 // couldn't go direct, let's try walking the graph of converters.
469 rv = BuildGraph();
470 if (NS_FAILED(rv)) return rv;
472 nsTArray<nsCString>* converterChain = nullptr;
474 rv = FindConverter(cContractID, &converterChain);
475 if (NS_FAILED(rv)) {
476 // can't make this conversion.
477 // XXX should have a more descriptive error code.
478 return NS_ERROR_FAILURE;
481 // aListener is the listener that wants the final, converted, data.
482 // we initialize finalListener w/ aListener so it gets put at the
483 // tail end of the chain, which in the loop below, means the *first*
484 // converter created.
485 nsCOMPtr<nsIStreamListener> finalListener = aListener;
487 // convert the stream using each edge of the graph as a step.
488 // this is our stream conversion traversal.
489 int32_t edgeCount = int32_t(converterChain->Length());
490 NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
491 for (int i = 0; i < edgeCount; i++) {
492 const char* lContractID = converterChain->ElementAt(i).get();
494 // create the converter for this from/to pair
495 nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(lContractID));
496 NS_ASSERTION(converter,
497 "graph construction problem, built a contractid that wasn't "
498 "registered");
500 nsAutoCString fromStr, toStr;
501 rv = ParseFromTo(lContractID, fromStr, toStr);
502 if (NS_FAILED(rv)) {
503 delete converterChain;
504 return rv;
507 // connect the converter w/ the listener that should get the converted
508 // data.
509 rv = converter->AsyncConvertData(fromStr.get(), toStr.get(),
510 finalListener, aContext);
511 if (NS_FAILED(rv)) {
512 delete converterChain;
513 return rv;
516 // the last iteration of this loop will result in finalListener
517 // pointing to the converter that "starts" the conversion chain.
518 // this converter's "from" type is the original "from" type. Prior
519 // to the last iteration, finalListener will continuously be wedged
520 // into the next listener in the chain, then be updated.
521 finalListener = converter;
523 delete converterChain;
524 // return the first listener in the chain.
525 finalListener.forget(_retval);
526 } else {
527 // we're going direct.
528 rv = listener->AsyncConvertData(aFromType, aToType, aListener, aContext);
529 listener.forget(_retval);
532 return rv;
535 nsresult NS_NewStreamConv(nsStreamConverterService** aStreamConv) {
536 MOZ_ASSERT(aStreamConv != nullptr, "null ptr");
537 if (!aStreamConv) return NS_ERROR_NULL_POINTER;
539 RefPtr<nsStreamConverterService> conv = new nsStreamConverterService();
540 conv.forget(aStreamConv);
542 return NS_OK;