Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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/. */
6 :
7 : #include "ImageEncoder.h"
8 : #include "mozilla/dom/CanvasRenderingContext2D.h"
9 : #include "mozilla/gfx/2D.h"
10 : #include "mozilla/gfx/DataSurfaceHelpers.h"
11 : #include "mozilla/layers/AsyncCanvasRenderer.h"
12 : #include "mozilla/RefPtr.h"
13 : #include "mozilla/SyncRunnable.h"
14 : #include "mozilla/Unused.h"
15 : #include "gfxUtils.h"
16 : #include "nsIThreadPool.h"
17 : #include "nsNetUtil.h"
18 : #include "nsXPCOMCIDInternal.h"
19 : #include "WorkerPrivate.h"
20 : #include "YCbCrUtils.h"
21 :
22 : using namespace mozilla::gfx;
23 :
24 : namespace mozilla {
25 : namespace dom {
26 :
27 : // This class should be placed inside GetBRGADataSourceSurfaceSync(). However,
28 : // due to B2G ICS uses old complier (C++98/03) which forbids local class as
29 : // template parameter, we need to move this class outside.
30 0 : class SurfaceHelper : public Runnable {
31 : public:
32 0 : explicit SurfaceHelper(already_AddRefed<layers::Image> aImage)
33 0 : : Runnable("SurfaceHelper")
34 0 : , mImage(aImage)
35 0 : {}
36 :
37 : // It retrieves a SourceSurface reference and convert color format on main
38 : // thread and passes DataSourceSurface to caller thread.
39 0 : NS_IMETHOD Run() override {
40 : // It guarantees the reference will be released on main thread.
41 0 : nsCountedRef<nsMainThreadSourceSurfaceRef> surface;
42 0 : surface.own(mImage->GetAsSourceSurface().take());
43 :
44 0 : if (surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8) {
45 0 : mDataSourceSurface = surface->GetDataSurface();
46 : } else {
47 : mDataSourceSurface = gfxUtils::
48 0 : CopySurfaceToDataSourceSurfaceWithFormat(surface,
49 0 : gfx::SurfaceFormat::B8G8R8A8);
50 : }
51 0 : return NS_OK;
52 : }
53 :
54 0 : already_AddRefed<gfx::DataSourceSurface> GetDataSurfaceSafe() {
55 0 : nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadEventTarget();
56 0 : MOZ_ASSERT(mainTarget);
57 0 : SyncRunnable::DispatchToThread(mainTarget, this, false);
58 :
59 0 : return mDataSourceSurface.forget();
60 : }
61 :
62 : private:
63 : RefPtr<layers::Image> mImage;
64 : RefPtr<gfx::DataSourceSurface> mDataSourceSurface;
65 : };
66 :
67 : // This function returns a DataSourceSurface in B8G8R8A8 format.
68 : // It uses SourceSurface to do format convert. Because most SourceSurface in
69 : // image formats should be referenced or dereferenced on main thread, it uses a
70 : // sync class SurfaceHelper to retrieve SourceSurface and convert to B8G8R8A8 on
71 : // main thread.
72 : already_AddRefed<DataSourceSurface>
73 0 : GetBRGADataSourceSurfaceSync(already_AddRefed<layers::Image> aImage)
74 : {
75 0 : RefPtr<SurfaceHelper> helper = new SurfaceHelper(Move(aImage));
76 0 : return helper->GetDataSurfaceSafe();
77 : }
78 :
79 : class EncodingCompleteEvent : public CancelableRunnable
80 : {
81 0 : virtual ~EncodingCompleteEvent() {}
82 :
83 : public:
84 0 : explicit EncodingCompleteEvent(EncodeCompleteCallback* aEncodeCompleteCallback)
85 0 : : CancelableRunnable("EncodingCompleteEvent")
86 : , mImgSize(0)
87 : , mType()
88 : , mImgData(nullptr)
89 : , mEncodeCompleteCallback(aEncodeCompleteCallback)
90 0 : , mFailed(false)
91 : {
92 0 : if (!NS_IsMainThread() && workers::GetCurrentThreadWorkerPrivate()) {
93 0 : mCreationEventTarget = GetCurrentThreadEventTarget();
94 : } else {
95 0 : mCreationEventTarget = GetMainThreadEventTarget();
96 : }
97 0 : }
98 :
99 0 : NS_IMETHOD Run() override
100 : {
101 0 : nsresult rv = NS_OK;
102 :
103 0 : if (!mFailed) {
104 : // The correct parentObject has to be set by the mEncodeCompleteCallback.
105 : RefPtr<Blob> blob =
106 0 : Blob::CreateMemoryBlob(nullptr, mImgData, mImgSize, mType);
107 0 : MOZ_ASSERT(blob);
108 :
109 0 : rv = mEncodeCompleteCallback->ReceiveBlob(blob.forget());
110 : }
111 :
112 0 : mEncodeCompleteCallback = nullptr;
113 :
114 0 : return rv;
115 : }
116 :
117 0 : void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType)
118 : {
119 0 : mImgData = aImgData;
120 0 : mImgSize = aImgSize;
121 0 : mType = aType;
122 0 : }
123 :
124 0 : void SetFailed()
125 : {
126 0 : mFailed = true;
127 0 : }
128 :
129 0 : nsIEventTarget* GetCreationThreadEventTarget()
130 : {
131 0 : return mCreationEventTarget;
132 : }
133 :
134 : private:
135 : uint64_t mImgSize;
136 : nsAutoString mType;
137 : void* mImgData;
138 : nsCOMPtr<nsIEventTarget> mCreationEventTarget;
139 : RefPtr<EncodeCompleteCallback> mEncodeCompleteCallback;
140 : bool mFailed;
141 : };
142 :
143 : class EncodingRunnable : public Runnable
144 : {
145 0 : virtual ~EncodingRunnable() {}
146 :
147 : public:
148 : NS_DECL_ISUPPORTS_INHERITED
149 :
150 0 : EncodingRunnable(const nsAString& aType,
151 : const nsAString& aOptions,
152 : UniquePtr<uint8_t[]> aImageBuffer,
153 : layers::Image* aImage,
154 : imgIEncoder* aEncoder,
155 : EncodingCompleteEvent* aEncodingCompleteEvent,
156 : int32_t aFormat,
157 : const nsIntSize aSize,
158 : bool aUsingCustomOptions)
159 0 : : Runnable("EncodingRunnable")
160 : , mType(aType)
161 : , mOptions(aOptions)
162 0 : , mImageBuffer(Move(aImageBuffer))
163 : , mImage(aImage)
164 : , mEncoder(aEncoder)
165 : , mEncodingCompleteEvent(aEncodingCompleteEvent)
166 : , mFormat(aFormat)
167 : , mSize(aSize)
168 0 : , mUsingCustomOptions(aUsingCustomOptions)
169 0 : {}
170 :
171 0 : nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData)
172 : {
173 0 : nsCOMPtr<nsIInputStream> stream;
174 0 : nsresult rv = ImageEncoder::ExtractDataInternal(mType,
175 : mOptions,
176 : mImageBuffer.get(),
177 : mFormat,
178 : mSize,
179 : mImage,
180 : nullptr,
181 : nullptr,
182 0 : getter_AddRefs(stream),
183 0 : mEncoder);
184 :
185 : // If there are unrecognized custom parse options, we should fall back to
186 : // the default values for the encoder without any options at all.
187 0 : if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) {
188 0 : rv = ImageEncoder::ExtractDataInternal(mType,
189 0 : EmptyString(),
190 : mImageBuffer.get(),
191 : mFormat,
192 : mSize,
193 : mImage,
194 : nullptr,
195 : nullptr,
196 0 : getter_AddRefs(stream),
197 0 : mEncoder);
198 : }
199 0 : NS_ENSURE_SUCCESS(rv, rv);
200 :
201 0 : rv = stream->Available(aImgSize);
202 0 : NS_ENSURE_SUCCESS(rv, rv);
203 0 : NS_ENSURE_TRUE(*aImgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
204 :
205 0 : rv = NS_ReadInputStreamToBuffer(stream, aImgData, *aImgSize);
206 0 : NS_ENSURE_SUCCESS(rv, rv);
207 :
208 0 : return rv;
209 : }
210 :
211 0 : NS_IMETHOD Run() override
212 : {
213 : uint64_t imgSize;
214 0 : void* imgData = nullptr;
215 :
216 0 : nsresult rv = ProcessImageData(&imgSize, &imgData);
217 0 : if (NS_FAILED(rv)) {
218 0 : mEncodingCompleteEvent->SetFailed();
219 : } else {
220 0 : mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType);
221 : }
222 : rv = mEncodingCompleteEvent->GetCreationThreadEventTarget()->
223 0 : Dispatch(mEncodingCompleteEvent, nsIThread::DISPATCH_NORMAL);
224 0 : if (NS_FAILED(rv)) {
225 : // Better to leak than to crash.
226 0 : Unused << mEncodingCompleteEvent.forget();
227 0 : return rv;
228 : }
229 :
230 0 : return rv;
231 : }
232 :
233 : private:
234 : nsAutoString mType;
235 : nsAutoString mOptions;
236 : UniquePtr<uint8_t[]> mImageBuffer;
237 : RefPtr<layers::Image> mImage;
238 : nsCOMPtr<imgIEncoder> mEncoder;
239 : RefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
240 : int32_t mFormat;
241 : const nsIntSize mSize;
242 : bool mUsingCustomOptions;
243 : };
244 :
245 0 : NS_IMPL_ISUPPORTS_INHERITED0(EncodingRunnable, Runnable);
246 :
247 3 : StaticRefPtr<nsIThreadPool> ImageEncoder::sThreadPool;
248 :
249 : /* static */
250 : nsresult
251 0 : ImageEncoder::ExtractData(nsAString& aType,
252 : const nsAString& aOptions,
253 : const nsIntSize aSize,
254 : nsICanvasRenderingContextInternal* aContext,
255 : layers::AsyncCanvasRenderer* aRenderer,
256 : nsIInputStream** aStream)
257 : {
258 0 : nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
259 0 : if (!encoder) {
260 0 : return NS_IMAGELIB_ERROR_NO_ENCODER;
261 : }
262 :
263 0 : return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, nullptr,
264 0 : aContext, aRenderer, aStream, encoder);
265 : }
266 :
267 : /* static */
268 : nsresult
269 0 : ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
270 : const nsAString& aOptions,
271 : bool aUsingCustomOptions,
272 : layers::Image* aImage,
273 : EncodeCompleteCallback* aEncodeCallback)
274 : {
275 0 : nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
276 0 : if (!encoder) {
277 0 : return NS_IMAGELIB_ERROR_NO_ENCODER;
278 : }
279 :
280 0 : nsresult rv = EnsureThreadPool();
281 0 : if (NS_FAILED(rv)) {
282 0 : return rv;
283 : }
284 :
285 : RefPtr<EncodingCompleteEvent> completeEvent =
286 0 : new EncodingCompleteEvent(aEncodeCallback);
287 :
288 0 : nsIntSize size(aImage->GetSize().width, aImage->GetSize().height);
289 : nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
290 : aOptions,
291 : nullptr,
292 : aImage,
293 : encoder,
294 : completeEvent,
295 : imgIEncoder::INPUT_FORMAT_HOSTARGB,
296 : size,
297 0 : aUsingCustomOptions);
298 0 : return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
299 : }
300 :
301 : /* static */
302 : nsresult
303 0 : ImageEncoder::ExtractDataAsync(nsAString& aType,
304 : const nsAString& aOptions,
305 : bool aUsingCustomOptions,
306 : UniquePtr<uint8_t[]> aImageBuffer,
307 : int32_t aFormat,
308 : const nsIntSize aSize,
309 : EncodeCompleteCallback* aEncodeCallback)
310 : {
311 0 : nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
312 0 : if (!encoder) {
313 0 : return NS_IMAGELIB_ERROR_NO_ENCODER;
314 : }
315 :
316 0 : nsresult rv = EnsureThreadPool();
317 0 : if (NS_FAILED(rv)) {
318 0 : return rv;
319 : }
320 :
321 : RefPtr<EncodingCompleteEvent> completeEvent =
322 0 : new EncodingCompleteEvent(aEncodeCallback);
323 :
324 : nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
325 : aOptions,
326 0 : Move(aImageBuffer),
327 : nullptr,
328 : encoder,
329 : completeEvent,
330 : aFormat,
331 : aSize,
332 0 : aUsingCustomOptions);
333 0 : return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
334 : }
335 :
336 : /*static*/ nsresult
337 0 : ImageEncoder::GetInputStream(int32_t aWidth,
338 : int32_t aHeight,
339 : uint8_t* aImageBuffer,
340 : int32_t aFormat,
341 : imgIEncoder* aEncoder,
342 : const char16_t* aEncoderOptions,
343 : nsIInputStream** aStream)
344 : {
345 : nsresult rv =
346 0 : aEncoder->InitFromData(aImageBuffer,
347 0 : aWidth * aHeight * 4, aWidth, aHeight, aWidth * 4,
348 : aFormat,
349 0 : nsDependentString(aEncoderOptions));
350 0 : NS_ENSURE_SUCCESS(rv, rv);
351 :
352 0 : return CallQueryInterface(aEncoder, aStream);
353 : }
354 :
355 : /* static */
356 : nsresult
357 0 : ImageEncoder::ExtractDataInternal(const nsAString& aType,
358 : const nsAString& aOptions,
359 : uint8_t* aImageBuffer,
360 : int32_t aFormat,
361 : const nsIntSize aSize,
362 : layers::Image* aImage,
363 : nsICanvasRenderingContextInternal* aContext,
364 : layers::AsyncCanvasRenderer* aRenderer,
365 : nsIInputStream** aStream,
366 : imgIEncoder* aEncoder)
367 : {
368 0 : if (aSize.IsEmpty()) {
369 0 : return NS_ERROR_INVALID_ARG;
370 : }
371 :
372 0 : nsCOMPtr<nsIInputStream> imgStream;
373 :
374 : // get image bytes
375 : nsresult rv;
376 0 : if (aImageBuffer) {
377 0 : if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
378 0 : return NS_ERROR_INVALID_ARG;
379 : }
380 :
381 0 : rv = ImageEncoder::GetInputStream(
382 0 : aSize.width,
383 0 : aSize.height,
384 : aImageBuffer,
385 : aFormat,
386 : aEncoder,
387 0 : nsPromiseFlatString(aOptions).get(),
388 0 : getter_AddRefs(imgStream));
389 0 : } else if (aContext) {
390 0 : NS_ConvertUTF16toUTF8 encoderType(aType);
391 0 : rv = aContext->GetInputStream(encoderType.get(),
392 0 : nsPromiseFlatString(aOptions).get(),
393 0 : getter_AddRefs(imgStream));
394 0 : } else if (aRenderer) {
395 0 : NS_ConvertUTF16toUTF8 encoderType(aType);
396 0 : rv = aRenderer->GetInputStream(encoderType.get(),
397 0 : nsPromiseFlatString(aOptions).get(),
398 0 : getter_AddRefs(imgStream));
399 0 : } else if (aImage) {
400 : // It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread.
401 : // Other image formats could have problem to convert format off-main-thread.
402 : // So here it uses a help function GetBRGADataSourceSurfaceSync() to convert
403 : // format on main thread.
404 0 : if (aImage->GetFormat() == ImageFormat::PLANAR_YCBCR) {
405 0 : nsTArray<uint8_t> data;
406 0 : layers::PlanarYCbCrImage* ycbcrImage = static_cast<layers::PlanarYCbCrImage*> (aImage);
407 0 : gfxImageFormat format = SurfaceFormat::A8R8G8B8_UINT32;
408 0 : uint32_t stride = GetAlignedStride<16>(aSize.width, 4);
409 0 : size_t length = BufferSizeFromStrideAndHeight(stride, aSize.height);
410 0 : if (length == 0) {
411 0 : return NS_ERROR_INVALID_ARG;
412 : }
413 0 : data.SetCapacity(length);
414 :
415 0 : ConvertYCbCrToRGB(*ycbcrImage->GetData(),
416 : format,
417 : aSize,
418 : data.Elements(),
419 0 : stride);
420 :
421 0 : rv = aEncoder->InitFromData(data.Elements(),
422 0 : aSize.width * aSize.height * 4,
423 0 : aSize.width,
424 0 : aSize.height,
425 0 : aSize.width * 4,
426 : imgIEncoder::INPUT_FORMAT_HOSTARGB,
427 0 : aOptions);
428 : } else {
429 0 : if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
430 0 : return NS_ERROR_INVALID_ARG;
431 : }
432 :
433 0 : RefPtr<gfx::DataSourceSurface> dataSurface;
434 0 : RefPtr<layers::Image> image(aImage);
435 0 : dataSurface = GetBRGADataSourceSurfaceSync(image.forget());
436 :
437 : DataSourceSurface::MappedSurface map;
438 0 : if (!dataSurface->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
439 0 : return NS_ERROR_INVALID_ARG;
440 : }
441 0 : rv = aEncoder->InitFromData(map.mData,
442 0 : aSize.width * aSize.height * 4,
443 0 : aSize.width,
444 0 : aSize.height,
445 0 : aSize.width * 4,
446 : imgIEncoder::INPUT_FORMAT_HOSTARGB,
447 0 : aOptions);
448 0 : dataSurface->Unmap();
449 : }
450 :
451 0 : if (NS_SUCCEEDED(rv)) {
452 0 : imgStream = do_QueryInterface(aEncoder);
453 : }
454 : } else {
455 0 : if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
456 0 : return NS_ERROR_INVALID_ARG;
457 : }
458 :
459 : // no context, so we have to encode an empty image
460 : // note that if we didn't have a current context, the spec says we're
461 : // supposed to just return transparent black pixels of the canvas
462 : // dimensions.
463 : RefPtr<DataSourceSurface> emptyCanvas =
464 0 : Factory::CreateDataSourceSurfaceWithStride(IntSize(aSize.width, aSize.height),
465 : SurfaceFormat::B8G8R8A8,
466 0 : 4 * aSize.width, true);
467 0 : if (NS_WARN_IF(!emptyCanvas)) {
468 0 : return NS_ERROR_INVALID_ARG;
469 : }
470 :
471 : DataSourceSurface::MappedSurface map;
472 0 : if (!emptyCanvas->Map(DataSourceSurface::MapType::WRITE, &map)) {
473 0 : return NS_ERROR_INVALID_ARG;
474 : }
475 0 : rv = aEncoder->InitFromData(map.mData,
476 0 : aSize.width * aSize.height * 4,
477 0 : aSize.width,
478 0 : aSize.height,
479 0 : aSize.width * 4,
480 : imgIEncoder::INPUT_FORMAT_HOSTARGB,
481 0 : aOptions);
482 0 : emptyCanvas->Unmap();
483 0 : if (NS_SUCCEEDED(rv)) {
484 0 : imgStream = do_QueryInterface(aEncoder);
485 : }
486 : }
487 0 : NS_ENSURE_SUCCESS(rv, rv);
488 :
489 0 : imgStream.forget(aStream);
490 0 : return rv;
491 : }
492 :
493 : /* static */
494 : already_AddRefed<imgIEncoder>
495 0 : ImageEncoder::GetImageEncoder(nsAString& aType)
496 : {
497 : // Get an image encoder for the media type.
498 0 : nsCString encoderCID("@mozilla.org/image/encoder;2?type=");
499 0 : NS_ConvertUTF16toUTF8 encoderType(aType);
500 0 : encoderCID += encoderType;
501 0 : nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
502 :
503 0 : if (!encoder && aType != NS_LITERAL_STRING("image/png")) {
504 : // Unable to create an encoder instance of the specified type. Falling back
505 : // to PNG.
506 0 : aType.AssignLiteral("image/png");
507 0 : nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png");
508 0 : encoder = do_CreateInstance(PNGEncoderCID.get());
509 : }
510 :
511 0 : return encoder.forget();
512 : }
513 :
514 0 : class EncoderThreadPoolTerminator final : public nsIObserver
515 : {
516 : public:
517 : NS_DECL_ISUPPORTS
518 :
519 0 : NS_IMETHOD Observe(nsISupports *, const char *topic, const char16_t *) override
520 : {
521 0 : NS_ASSERTION(!strcmp(topic, "xpcom-shutdown-threads"),
522 : "Unexpected topic");
523 0 : if (ImageEncoder::sThreadPool) {
524 0 : ImageEncoder::sThreadPool->Shutdown();
525 0 : ImageEncoder::sThreadPool = nullptr;
526 : }
527 0 : return NS_OK;
528 : }
529 : private:
530 0 : ~EncoderThreadPoolTerminator() {}
531 : };
532 :
533 0 : NS_IMPL_ISUPPORTS(EncoderThreadPoolTerminator, nsIObserver)
534 :
535 : static void
536 0 : RegisterEncoderThreadPoolTerminatorObserver()
537 : {
538 0 : MOZ_ASSERT(NS_IsMainThread());
539 0 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
540 0 : NS_ASSERTION(os, "do_GetService failed");
541 0 : os->AddObserver(new EncoderThreadPoolTerminator(),
542 : "xpcom-shutdown-threads",
543 0 : false);
544 0 : }
545 :
546 : /* static */
547 : nsresult
548 0 : ImageEncoder::EnsureThreadPool()
549 : {
550 0 : if (!sThreadPool) {
551 0 : nsCOMPtr<nsIThreadPool> threadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
552 0 : sThreadPool = threadPool;
553 :
554 0 : if (!NS_IsMainThread()) {
555 0 : NS_DispatchToMainThread(NS_NewRunnableFunction(
556 : "dom::ImageEncoder::EnsureThreadPool",
557 0 : []() -> void { RegisterEncoderThreadPoolTerminatorObserver(); }));
558 : } else {
559 0 : RegisterEncoderThreadPoolTerminatorObserver();
560 : }
561 :
562 0 : const uint32_t kThreadLimit = 2;
563 0 : const uint32_t kIdleThreadLimit = 1;
564 0 : const uint32_t kIdleThreadTimeoutMs = 30000;
565 :
566 0 : nsresult rv = sThreadPool->SetName(NS_LITERAL_CSTRING("EncodingRunnable"));
567 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
568 0 : return rv;
569 : }
570 :
571 0 : rv = sThreadPool->SetThreadLimit(kThreadLimit);
572 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
573 0 : return rv;
574 : }
575 :
576 0 : rv = sThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
577 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
578 0 : return rv;
579 : }
580 :
581 0 : rv = sThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
582 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
583 0 : return rv;
584 : }
585 : }
586 :
587 0 : return NS_OK;
588 : }
589 :
590 : } // namespace dom
591 : } // namespace mozilla
|