Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SourceFilter.h"
8 #include "MediaResource.h"
9 #include "mozilla/RefPtr.h"
10 #include "DirectShowUtils.h"
11 #include "MP3FrameParser.h"
12 #include "prlog.h"
13 #include <algorithm>
15 using namespace mozilla::media;
17 namespace mozilla {
19 // Define to trace what's on...
20 //#define DEBUG_SOURCE_TRACE 1
22 #if defined(PR_LOGGING) && defined (DEBUG_SOURCE_TRACE)
23 PRLogModuleInfo* GetDirectShowLog();
24 #define DIRECTSHOW_LOG(...) PR_LOG(GetDirectShowLog(), PR_LOG_DEBUG, (__VA_ARGS__))
25 #else
26 #define DIRECTSHOW_LOG(...)
27 #endif
29 static HRESULT
30 DoGetInterface(IUnknown* aUnknown, void** aInterface)
31 {
32 if (!aInterface)
33 return E_POINTER;
34 *aInterface = aUnknown;
35 aUnknown->AddRef();
36 return S_OK;
37 }
39 // Stores details of IAsyncReader::Request().
40 class ReadRequest {
41 public:
43 ReadRequest(IMediaSample* aSample,
44 DWORD_PTR aDwUser,
45 uint32_t aOffset,
46 uint32_t aCount)
47 : mSample(aSample),
48 mDwUser(aDwUser),
49 mOffset(aOffset),
50 mCount(aCount)
51 {
52 MOZ_COUNT_CTOR(ReadRequest);
53 }
55 ~ReadRequest() {
56 MOZ_COUNT_DTOR(ReadRequest);
57 }
59 RefPtr<IMediaSample> mSample;
60 DWORD_PTR mDwUser;
61 uint32_t mOffset;
62 uint32_t mCount;
63 };
65 // A wrapper around media resource that presents only a partition of the
66 // underlying resource to the caller to use. The partition returned is from
67 // an offset to the end of stream, and this object deals with ensuring
68 // the offsets and lengths etc are translated from the reduced partition
69 // exposed to the caller, to the absolute offsets of the underlying stream.
70 class MediaResourcePartition {
71 public:
72 MediaResourcePartition(MediaResource* aResource,
73 int64_t aDataStart)
74 : mResource(aResource),
75 mDataOffset(aDataStart)
76 {}
78 int64_t GetLength() {
79 int64_t len = mResource->GetLength();
80 if (len == -1) {
81 return len;
82 }
83 return std::max<int64_t>(0, len - mDataOffset);
84 }
85 nsresult ReadAt(int64_t aOffset, char* aBuffer,
86 uint32_t aCount, uint32_t* aBytes)
87 {
88 return mResource->ReadAt(aOffset + mDataOffset,
89 aBuffer,
90 aCount,
91 aBytes);
92 }
93 int64_t GetCachedDataEnd() {
94 int64_t tell = mResource->Tell();
95 int64_t dataEnd = mResource->GetCachedDataEnd(tell) - mDataOffset;
96 return dataEnd;
97 }
98 private:
99 // MediaResource from which we read data.
100 RefPtr<MediaResource> mResource;
101 int64_t mDataOffset;
102 };
105 // Output pin for SourceFilter, which implements IAsyncReader, to
106 // allow downstream filters to pull/read data from it. Downstream pins
107 // register to read data using Request(), and asynchronously wait for the
108 // reads to complete using WaitForNext(). They may also synchronously read
109 // using SyncRead(). This class is a delegate (tear off) of
110 // SourceFilter.
111 //
112 // We can expose only a segment of the MediaResource to the filter graph.
113 // This is used to strip off the ID3v2 tags from the stream, as DirectShow
114 // has trouble parsing some headers.
115 //
116 // Implements:
117 // * IAsyncReader
118 // * IPin
119 // * IQualityControl
120 // * IUnknown
121 //
122 class DECLSPEC_UUID("18e5cfb2-1015-440c-a65c-e63853235894")
123 OutputPin : public IAsyncReader,
124 public BasePin
125 {
126 public:
128 OutputPin(MediaResource* aMediaResource,
129 SourceFilter* aParent,
130 CriticalSection& aFilterLock,
131 int64_t aMP3DataStart);
132 virtual ~OutputPin();
134 // IUnknown
135 // Defer to ref counting to BasePin, which defers to owning nsBaseFilter.
136 STDMETHODIMP_(ULONG) AddRef() MOZ_OVERRIDE { return BasePin::AddRef(); }
137 STDMETHODIMP_(ULONG) Release() MOZ_OVERRIDE { return BasePin::Release(); }
138 STDMETHODIMP QueryInterface(REFIID iid, void** ppv) MOZ_OVERRIDE;
140 // BasePin Overrides.
141 // Determines if the pin accepts a specific media type.
142 HRESULT CheckMediaType(const MediaType* aMediaType) MOZ_OVERRIDE;
144 // Retrieves a preferred media type, by index value.
145 HRESULT GetMediaType(int aPosition, MediaType* aMediaType) MOZ_OVERRIDE;
147 // Releases the pin from a connection.
148 HRESULT BreakConnect(void) MOZ_OVERRIDE;
150 // Determines whether a pin connection is suitable.
151 HRESULT CheckConnect(IPin* aPin) MOZ_OVERRIDE;
154 // IAsyncReader overrides
156 // The RequestAllocator method requests an allocator during the
157 // pin connection.
158 STDMETHODIMP RequestAllocator(IMemAllocator* aPreferred,
159 ALLOCATOR_PROPERTIES* aProps,
160 IMemAllocator** aActual) MOZ_OVERRIDE;
162 // The Request method queues an asynchronous request for data. Downstream
163 // will call WaitForNext() when they want to retrieve the result.
164 STDMETHODIMP Request(IMediaSample* aSample, DWORD_PTR aUserData) MOZ_OVERRIDE;
166 // The WaitForNext method waits for the next pending read request
167 // to complete. This method fails if the graph is flushing.
168 // Defers to SyncRead/5.
169 STDMETHODIMP WaitForNext(DWORD aTimeout,
170 IMediaSample** aSamples,
171 DWORD_PTR* aUserData) MOZ_OVERRIDE;
173 // The SyncReadAligned method performs a synchronous read. The method
174 // blocks until the request is completed. Defers to SyncRead/5. This
175 // method does not fail if the graph is flushing.
176 STDMETHODIMP SyncReadAligned(IMediaSample* aSample) MOZ_OVERRIDE;
178 // The SyncRead method performs a synchronous read. The method blocks
179 // until the request is completed. Defers to SyncRead/5. This
180 // method does not fail if the graph is flushing.
181 STDMETHODIMP SyncRead(LONGLONG aPosition, LONG aLength, BYTE* aBuffer) MOZ_OVERRIDE;
183 // The Length method retrieves the total length of the stream.
184 STDMETHODIMP Length(LONGLONG* aTotal, LONGLONG* aAvailable) MOZ_OVERRIDE;
186 // IPin Overrides
187 STDMETHODIMP BeginFlush(void) MOZ_OVERRIDE;
188 STDMETHODIMP EndFlush(void) MOZ_OVERRIDE;
190 uint32_t GetAndResetBytesConsumedCount();
192 private:
194 // Protects thread-shared data/structures (mFlushCount, mPendingReads).
195 // WaitForNext() also waits on this monitor
196 CriticalSection& mPinLock;
198 // Signal used with mPinLock to implement WaitForNext().
199 Signal mSignal;
201 // The filter that owns us. Weak reference, as we're a delegate (tear off).
202 SourceFilter* mParentSource;
204 MediaResourcePartition mResource;
206 // Counter, inc'd in BeginFlush(), dec'd in EndFlush(). Calls to this can
207 // come from multiple threads and can interleave, hence the counter.
208 int32_t mFlushCount;
210 // Number of bytes that have been read from the output pin since the last
211 // time GetAndResetBytesConsumedCount() was called.
212 uint32_t mBytesConsumed;
214 // Deque of ReadRequest* for reads that are yet to be serviced.
215 // nsReadRequest's are stored on the heap, popper must delete them.
216 nsDeque mPendingReads;
218 // Flags if the downstream pin has QI'd for IAsyncReader. We refuse
219 // connection if they don't query, as it means they're assuming that we're
220 // a push filter, and we're not.
221 bool mQueriedForAsyncReader;
223 };
225 // For mingw __uuidof support
226 #ifdef __CRT_UUID_DECL
227 }
228 __CRT_UUID_DECL(mozilla::OutputPin, 0x18e5cfb2,0x1015,0x440c,0xa6,0x5c,0xe6,0x38,0x53,0x23,0x58,0x94);
229 namespace mozilla {
230 #endif
232 OutputPin::OutputPin(MediaResource* aResource,
233 SourceFilter* aParent,
234 CriticalSection& aFilterLock,
235 int64_t aMP3DataStart)
236 : BasePin(static_cast<BaseFilter*>(aParent),
237 &aFilterLock,
238 L"MozillaOutputPin",
239 PINDIR_OUTPUT),
240 mPinLock(aFilterLock),
241 mSignal(&mPinLock),
242 mParentSource(aParent),
243 mResource(aResource, aMP3DataStart),
244 mFlushCount(0),
245 mBytesConsumed(0),
246 mQueriedForAsyncReader(false)
247 {
248 MOZ_COUNT_CTOR(OutputPin);
249 DIRECTSHOW_LOG("OutputPin::OutputPin()");
250 }
252 OutputPin::~OutputPin()
253 {
254 MOZ_COUNT_DTOR(OutputPin);
255 DIRECTSHOW_LOG("OutputPin::~OutputPin()");
256 }
258 HRESULT
259 OutputPin::BreakConnect()
260 {
261 mQueriedForAsyncReader = false;
262 return BasePin::BreakConnect();
263 }
265 STDMETHODIMP
266 OutputPin::QueryInterface(REFIID aIId, void** aInterface)
267 {
268 if (aIId == IID_IAsyncReader) {
269 mQueriedForAsyncReader = true;
270 return DoGetInterface(static_cast<IAsyncReader*>(this), aInterface);
271 }
273 if (aIId == __uuidof(OutputPin)) {
274 AddRef();
275 *aInterface = this;
276 return S_OK;
277 }
279 return BasePin::QueryInterface(aIId, aInterface);
280 }
282 HRESULT
283 OutputPin::CheckConnect(IPin* aPin)
284 {
285 // Our connection is only suitable if the downstream pin knows
286 // that we're asynchronous (i.e. it queried for IAsyncReader).
287 return mQueriedForAsyncReader ? S_OK : S_FALSE;
288 }
290 HRESULT
291 OutputPin::CheckMediaType(const MediaType* aMediaType)
292 {
293 const MediaType *myMediaType = mParentSource->GetMediaType();
295 if (IsEqualGUID(aMediaType->majortype, myMediaType->majortype) &&
296 IsEqualGUID(aMediaType->subtype, myMediaType->subtype) &&
297 IsEqualGUID(aMediaType->formattype, myMediaType->formattype))
298 {
299 DIRECTSHOW_LOG("OutputPin::CheckMediaType() Match: major=%s minor=%s TC=%d FSS=%d SS=%u",
300 GetDirectShowGuidName(aMediaType->majortype),
301 GetDirectShowGuidName(aMediaType->subtype),
302 aMediaType->TemporalCompression(),
303 aMediaType->bFixedSizeSamples,
304 aMediaType->SampleSize());
305 return S_OK;
306 }
308 DIRECTSHOW_LOG("OutputPin::CheckMediaType() Failed to match: major=%s minor=%s TC=%d FSS=%d SS=%u",
309 GetDirectShowGuidName(aMediaType->majortype),
310 GetDirectShowGuidName(aMediaType->subtype),
311 aMediaType->TemporalCompression(),
312 aMediaType->bFixedSizeSamples,
313 aMediaType->SampleSize());
314 return S_FALSE;
315 }
317 HRESULT
318 OutputPin::GetMediaType(int aPosition, MediaType* aMediaType)
319 {
320 if (!aMediaType)
321 return E_POINTER;
323 if (aPosition == 0) {
324 aMediaType->Assign(mParentSource->GetMediaType());
325 return S_OK;
326 }
327 return VFW_S_NO_MORE_ITEMS;
328 }
330 static inline bool
331 IsPowerOf2(int32_t x) {
332 return ((-x & x) != x);
333 }
335 STDMETHODIMP
336 OutputPin::RequestAllocator(IMemAllocator* aPreferred,
337 ALLOCATOR_PROPERTIES* aProps,
338 IMemAllocator** aActual)
339 {
340 // Require the downstream pin to suggest what they want...
341 if (!aPreferred) return E_POINTER;
342 if (!aProps) return E_POINTER;
343 if (!aActual) return E_POINTER;
345 // We only care about alignment - our allocator will reject anything
346 // which isn't power-of-2 aligned, so so try a 4-byte aligned allocator.
347 ALLOCATOR_PROPERTIES props;
348 memcpy(&props, aProps, sizeof(ALLOCATOR_PROPERTIES));
349 if (aProps->cbAlign == 0 || IsPowerOf2(aProps->cbAlign)) {
350 props.cbAlign = 4;
351 }
353 // Limit allocator's number of buffers. We know that the media will most
354 // likely be bound by network speed, not by decoding speed. We also
355 // store the incoming data in a Gecko stream, if we don't limit buffers
356 // here we'll end up duplicating a lot of storage. We must have enough
357 // space for audio key frames to fit in the first batch of buffers however,
358 // else pausing may fail for some downstream decoders.
359 if (props.cBuffers > BaseFilter::sMaxNumBuffers) {
360 props.cBuffers = BaseFilter::sMaxNumBuffers;
361 }
363 // The allocator properties that are actually used. We don't store
364 // this, we need it for SetProperties() below to succeed.
365 ALLOCATOR_PROPERTIES actualProps;
366 HRESULT hr;
368 if (aPreferred) {
369 // Play nice and prefer the downstream pin's preferred allocator.
370 hr = aPreferred->SetProperties(&props, &actualProps);
371 if (SUCCEEDED(hr)) {
372 aPreferred->AddRef();
373 *aActual = aPreferred;
374 return S_OK;
375 }
376 }
378 // Else downstream hasn't requested a specific allocator, so create one...
380 // Just create a default allocator. It's highly unlikely that we'll use
381 // this anyway, as most parsers insist on using their own allocators.
382 nsRefPtr<IMemAllocator> allocator;
383 hr = CoCreateInstance(CLSID_MemoryAllocator,
384 0,
385 CLSCTX_INPROC_SERVER,
386 IID_IMemAllocator,
387 getter_AddRefs(allocator));
388 if(FAILED(hr) || (allocator == nullptr)) {
389 NS_WARNING("Can't create our own DirectShow allocator.");
390 return hr;
391 }
393 // See if we can make it suitable
394 hr = allocator->SetProperties(&props, &actualProps);
395 if (SUCCEEDED(hr)) {
396 // We need to release our refcount on pAlloc, and addref
397 // it to pass a refcount to the caller - this is a net nothing.
398 allocator.forget(aActual);
399 return S_OK;
400 }
402 NS_WARNING("Failed to pick an allocator");
403 return hr;
404 }
406 STDMETHODIMP
407 OutputPin::Request(IMediaSample* aSample, DWORD_PTR aDwUser)
408 {
409 if (!aSample) return E_FAIL;
411 CriticalSectionAutoEnter lock(*mLock);
412 NS_ASSERTION(!mFlushCount, "Request() while flushing");
414 if (mFlushCount)
415 return VFW_E_WRONG_STATE;
417 REFERENCE_TIME refStart = 0, refEnd = 0;
418 if (FAILED(aSample->GetTime(&refStart, &refEnd))) {
419 NS_WARNING("Sample incorrectly timestamped");
420 return VFW_E_SAMPLE_TIME_NOT_SET;
421 }
423 // Convert reference time to bytes.
424 uint32_t start = (uint32_t)(refStart / 10000000);
425 uint32_t end = (uint32_t)(refEnd / 10000000);
427 uint32_t numBytes = end - start;
429 ReadRequest* request = new ReadRequest(aSample,
430 aDwUser,
431 start,
432 numBytes);
433 // Memory for |request| is free when it's popped from the completed
434 // reads list.
436 // Push this onto the queue of reads to be serviced.
437 mPendingReads.Push(request);
439 // Notify any threads blocked in WaitForNext() which are waiting for mPendingReads
440 // to become non-empty.
441 mSignal.Notify();
443 return S_OK;
444 }
446 STDMETHODIMP
447 OutputPin::WaitForNext(DWORD aTimeout,
448 IMediaSample** aOutSample,
449 DWORD_PTR* aOutDwUser)
450 {
451 NS_ASSERTION(aTimeout == 0 || aTimeout == INFINITE,
452 "Oops, we don't handle this!");
454 *aOutSample = nullptr;
455 *aOutDwUser = 0;
457 LONGLONG offset = 0;
458 LONG count = 0;
459 BYTE* buf = nullptr;
461 {
462 CriticalSectionAutoEnter lock(*mLock);
464 // Wait until there's a pending read to service.
465 while (aTimeout && mPendingReads.GetSize() == 0 && !mFlushCount) {
466 // Note: No need to guard against shutdown-during-wait here, as
467 // typically the thread doing the pull will have already called
468 // Request(), so we won't Wait() here anyway. SyncRead() will fail
469 // on shutdown.
470 mSignal.Wait();
471 }
473 nsAutoPtr<ReadRequest> request(reinterpret_cast<ReadRequest*>(mPendingReads.PopFront()));
474 if (!request)
475 return VFW_E_WRONG_STATE;
477 *aOutSample = request->mSample;
478 *aOutDwUser = request->mDwUser;
480 offset = request->mOffset;
481 count = request->mCount;
482 buf = nullptr;
483 request->mSample->GetPointer(&buf);
484 NS_ASSERTION(buf != nullptr, "Invalid buffer!");
486 if (mFlushCount) {
487 return VFW_E_TIMEOUT;
488 }
489 }
491 return SyncRead(offset, count, buf);
492 }
494 STDMETHODIMP
495 OutputPin::SyncReadAligned(IMediaSample* aSample)
496 {
497 {
498 // Ignore reads while flushing.
499 CriticalSectionAutoEnter lock(*mLock);
500 if (mFlushCount) {
501 return S_FALSE;
502 }
503 }
505 if (!aSample)
506 return E_FAIL;
508 REFERENCE_TIME lStart = 0, lEnd = 0;
509 if (FAILED(aSample->GetTime(&lStart, &lEnd))) {
510 NS_WARNING("Sample incorrectly timestamped");
511 return VFW_E_SAMPLE_TIME_NOT_SET;
512 }
514 // Convert reference time to bytes.
515 int32_t start = (int32_t)(lStart / 10000000);
516 int32_t end = (int32_t)(lEnd / 10000000);
518 int32_t numBytes = end - start;
520 // If the range extends off the end of stream, truncate to the end of stream
521 // as per IAsyncReader specificiation.
522 int64_t streamLength = mResource.GetLength();
523 if (streamLength != -1) {
524 // We know the exact length of the stream, fail if the requested offset
525 // is beyond it.
526 if (start > streamLength) {
527 return VFW_E_BADALIGN;
528 }
530 // If the end of the chunk to read is off the end of the stream,
531 // truncate it to the end of the stream.
532 if ((start + numBytes) > streamLength) {
533 numBytes = (uint32_t)(streamLength - start);
534 }
535 }
537 BYTE* buf=0;
538 aSample->GetPointer(&buf);
540 return SyncRead(start, numBytes, buf);
541 }
543 STDMETHODIMP
544 OutputPin::SyncRead(LONGLONG aPosition,
545 LONG aLength,
546 BYTE* aBuffer)
547 {
548 MOZ_ASSERT(!NS_IsMainThread());
549 NS_ENSURE_TRUE(aPosition >= 0, E_FAIL);
550 NS_ENSURE_TRUE(aLength > 0, E_FAIL);
551 NS_ENSURE_TRUE(aBuffer, E_POINTER);
553 DIRECTSHOW_LOG("OutputPin::SyncRead(%lld, %d)", aPosition, aLength);
554 {
555 // Ignore reads while flushing.
556 CriticalSectionAutoEnter lock(*mLock);
557 if (mFlushCount) {
558 return S_FALSE;
559 }
560 }
562 // Read in a loop to ensure we fill the buffer, when possible.
563 LONG totalBytesRead = 0;
564 while (totalBytesRead < aLength) {
565 BYTE* readBuffer = aBuffer + totalBytesRead;
566 uint32_t bytesRead = 0;
567 LONG length = aLength - totalBytesRead;
568 nsresult rv = mResource.ReadAt(aPosition + totalBytesRead,
569 reinterpret_cast<char*>(readBuffer),
570 length,
571 &bytesRead);
572 if (NS_FAILED(rv)) {
573 return E_FAIL;
574 }
575 totalBytesRead += bytesRead;
576 if (bytesRead == 0) {
577 break;
578 }
579 }
580 if (totalBytesRead > 0) {
581 CriticalSectionAutoEnter lock(*mLock);
582 mBytesConsumed += totalBytesRead;
583 }
584 return (totalBytesRead == aLength) ? S_OK : S_FALSE;
585 }
587 STDMETHODIMP
588 OutputPin::Length(LONGLONG* aTotal, LONGLONG* aAvailable)
589 {
590 HRESULT hr = S_OK;
591 int64_t length = mResource.GetLength();
592 if (length == -1) {
593 hr = VFW_S_ESTIMATED;
594 // Don't have a length. Just lie, it seems to work...
595 *aTotal = INT32_MAX;
596 } else {
597 *aTotal = length;
598 }
599 if (aAvailable) {
600 *aAvailable = mResource.GetCachedDataEnd();
601 }
603 DIRECTSHOW_LOG("OutputPin::Length() len=%lld avail=%lld", *aTotal, *aAvailable);
605 return hr;
606 }
608 STDMETHODIMP
609 OutputPin::BeginFlush()
610 {
611 CriticalSectionAutoEnter lock(*mLock);
612 mFlushCount++;
613 mSignal.Notify();
614 return S_OK;
615 }
617 STDMETHODIMP
618 OutputPin::EndFlush(void)
619 {
620 CriticalSectionAutoEnter lock(*mLock);
621 mFlushCount--;
622 return S_OK;
623 }
625 uint32_t
626 OutputPin::GetAndResetBytesConsumedCount()
627 {
628 CriticalSectionAutoEnter lock(*mLock);
629 uint32_t bytesConsumed = mBytesConsumed;
630 mBytesConsumed = 0;
631 return bytesConsumed;
632 }
634 SourceFilter::SourceFilter(const GUID& aMajorType,
635 const GUID& aSubType)
636 : BaseFilter(L"MozillaDirectShowSource", __uuidof(SourceFilter))
637 {
638 MOZ_COUNT_CTOR(SourceFilter);
639 mMediaType.majortype = aMajorType;
640 mMediaType.subtype = aSubType;
642 DIRECTSHOW_LOG("SourceFilter Constructor(%s, %s)",
643 GetDirectShowGuidName(aMajorType),
644 GetDirectShowGuidName(aSubType));
645 }
647 SourceFilter::~SourceFilter()
648 {
649 MOZ_COUNT_DTOR(SourceFilter);
650 DIRECTSHOW_LOG("SourceFilter Destructor()");
651 }
653 BasePin*
654 SourceFilter::GetPin(int n)
655 {
656 if (n == 0) {
657 NS_ASSERTION(mOutputPin != 0, "GetPin with no pin!");
658 return static_cast<BasePin*>(mOutputPin);
659 } else {
660 return nullptr;
661 }
662 }
664 // Get's the media type we're supplying.
665 const MediaType*
666 SourceFilter::GetMediaType() const
667 {
668 return &mMediaType;
669 }
671 nsresult
672 SourceFilter::Init(MediaResource* aResource, int64_t aMP3Offset)
673 {
674 DIRECTSHOW_LOG("SourceFilter::Init()");
676 mOutputPin = new OutputPin(aResource,
677 this,
678 mLock,
679 aMP3Offset);
680 NS_ENSURE_TRUE(mOutputPin != nullptr, NS_ERROR_FAILURE);
682 return NS_OK;
683 }
685 uint32_t
686 SourceFilter::GetAndResetBytesConsumedCount()
687 {
688 return mOutputPin->GetAndResetBytesConsumedCount();
689 }
692 } // namespace mozilla