content/media/webrtc/MediaEngineDefault.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/media/webrtc/MediaEngineDefault.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,516 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +#include "MediaEngineDefault.h"
     1.9 +
    1.10 +#include "nsCOMPtr.h"
    1.11 +#include "nsDOMFile.h"
    1.12 +#include "nsILocalFile.h"
    1.13 +#include "Layers.h"
    1.14 +#include "ImageContainer.h"
    1.15 +#include "ImageTypes.h"
    1.16 +#include "prmem.h"
    1.17 +#include "nsContentUtils.h"
    1.18 +
    1.19 +#include "nsIFilePicker.h"
    1.20 +#include "nsIPrefService.h"
    1.21 +#include "nsIPrefBranch.h"
    1.22 +
    1.23 +#ifdef MOZ_WIDGET_ANDROID
    1.24 +#include "AndroidBridge.h"
    1.25 +#include "nsISupportsUtils.h"
    1.26 +#endif
    1.27 +
    1.28 +#if defined(MOZ_WEBRTC) && defined(MOZ_WEBRTC_SIGNALING)
    1.29 +#include "YuvStamper.h"
    1.30 +#endif
    1.31 +
    1.32 +#define VIDEO_RATE USECS_PER_S
    1.33 +#define AUDIO_RATE 16000
    1.34 +#define AUDIO_FRAME_LENGTH ((AUDIO_RATE * MediaEngine::DEFAULT_AUDIO_TIMER_MS) / 1000)
    1.35 +namespace mozilla {
    1.36 +
    1.37 +using namespace mozilla::gfx;
    1.38 +
    1.39 +NS_IMPL_ISUPPORTS(MediaEngineDefaultVideoSource, nsITimerCallback)
    1.40 +/**
    1.41 + * Default video source.
    1.42 + */
    1.43 +
    1.44 +MediaEngineDefaultVideoSource::MediaEngineDefaultVideoSource()
    1.45 +  : mTimer(nullptr), mMonitor("Fake video")
    1.46 +{
    1.47 +  mImageContainer = layers::LayerManager::CreateImageContainer();
    1.48 +  mState = kReleased;
    1.49 +}
    1.50 +
    1.51 +MediaEngineDefaultVideoSource::~MediaEngineDefaultVideoSource()
    1.52 +{}
    1.53 +
    1.54 +void
    1.55 +MediaEngineDefaultVideoSource::GetName(nsAString& aName)
    1.56 +{
    1.57 +  aName.Assign(NS_LITERAL_STRING("Default Video Device"));
    1.58 +  return;
    1.59 +}
    1.60 +
    1.61 +void
    1.62 +MediaEngineDefaultVideoSource::GetUUID(nsAString& aUUID)
    1.63 +{
    1.64 +  aUUID.Assign(NS_LITERAL_STRING("1041FCBD-3F12-4F7B-9E9B-1EC556DD5676"));
    1.65 +  return;
    1.66 +}
    1.67 +
    1.68 +nsresult
    1.69 +MediaEngineDefaultVideoSource::Allocate(const VideoTrackConstraintsN &aConstraints,
    1.70 +                                        const MediaEnginePrefs &aPrefs)
    1.71 +{
    1.72 +  if (mState != kReleased) {
    1.73 +    return NS_ERROR_FAILURE;
    1.74 +  }
    1.75 +
    1.76 +  mOpts = aPrefs;
    1.77 +  mOpts.mWidth = mOpts.mWidth ? mOpts.mWidth : MediaEngine::DEFAULT_43_VIDEO_WIDTH;
    1.78 +  mOpts.mHeight = mOpts.mHeight ? mOpts.mHeight : MediaEngine::DEFAULT_43_VIDEO_HEIGHT;
    1.79 +  mState = kAllocated;
    1.80 +  return NS_OK;
    1.81 +}
    1.82 +
    1.83 +nsresult
    1.84 +MediaEngineDefaultVideoSource::Deallocate()
    1.85 +{
    1.86 +  if (mState != kStopped && mState != kAllocated) {
    1.87 +    return NS_ERROR_FAILURE;
    1.88 +  }
    1.89 +  mState = kReleased;
    1.90 +  return NS_OK;
    1.91 +}
    1.92 +
    1.93 +static void AllocateSolidColorFrame(layers::PlanarYCbCrData& aData,
    1.94 +                                    int aWidth, int aHeight,
    1.95 +                                    int aY, int aCb, int aCr)
    1.96 +{
    1.97 +  MOZ_ASSERT(!(aWidth&1));
    1.98 +  MOZ_ASSERT(!(aHeight&1));
    1.99 +  // Allocate a single frame with a solid color
   1.100 +  int yLen = aWidth*aHeight;
   1.101 +  int cbLen = yLen>>2;
   1.102 +  int crLen = cbLen;
   1.103 +  uint8_t* frame = (uint8_t*) PR_Malloc(yLen+cbLen+crLen);
   1.104 +  memset(frame, aY, yLen);
   1.105 +  memset(frame+yLen, aCb, cbLen);
   1.106 +  memset(frame+yLen+cbLen, aCr, crLen);
   1.107 +
   1.108 +  aData.mYChannel = frame;
   1.109 +  aData.mYSize = IntSize(aWidth, aHeight);
   1.110 +  aData.mYStride = aWidth;
   1.111 +  aData.mCbCrStride = aWidth>>1;
   1.112 +  aData.mCbChannel = frame + yLen;
   1.113 +  aData.mCrChannel = aData.mCbChannel + cbLen;
   1.114 +  aData.mCbCrSize = IntSize(aWidth>>1, aHeight>>1);
   1.115 +  aData.mPicX = 0;
   1.116 +  aData.mPicY = 0;
   1.117 +  aData.mPicSize = IntSize(aWidth, aHeight);
   1.118 +  aData.mStereoMode = StereoMode::MONO;
   1.119 +}
   1.120 +
   1.121 +static void ReleaseFrame(layers::PlanarYCbCrData& aData)
   1.122 +{
   1.123 +  PR_Free(aData.mYChannel);
   1.124 +}
   1.125 +
   1.126 +nsresult
   1.127 +MediaEngineDefaultVideoSource::Start(SourceMediaStream* aStream, TrackID aID)
   1.128 +{
   1.129 +  if (mState != kAllocated) {
   1.130 +    return NS_ERROR_FAILURE;
   1.131 +  }
   1.132 +
   1.133 +  mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   1.134 +  if (!mTimer) {
   1.135 +    return NS_ERROR_FAILURE;
   1.136 +  }
   1.137 +
   1.138 +  aStream->AddTrack(aID, VIDEO_RATE, 0, new VideoSegment());
   1.139 +  aStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
   1.140 +
   1.141 +  // Remember TrackID so we can end it later
   1.142 +  mTrackID = aID;
   1.143 +
   1.144 +  // Start timer for subsequent frames
   1.145 +#if defined(MOZ_WIDGET_GONK) && defined(DEBUG)
   1.146 +// B2G emulator debug is very, very slow and has problems dealing with realtime audio inputs
   1.147 +  mTimer->InitWithCallback(this, (1000 / mOpts.mFPS)*10, nsITimer::TYPE_REPEATING_SLACK);
   1.148 +#else
   1.149 +  mTimer->InitWithCallback(this, 1000 / mOpts.mFPS, nsITimer::TYPE_REPEATING_SLACK);
   1.150 +#endif
   1.151 +  mState = kStarted;
   1.152 +
   1.153 +  return NS_OK;
   1.154 +}
   1.155 +
   1.156 +nsresult
   1.157 +MediaEngineDefaultVideoSource::Stop(SourceMediaStream *aSource, TrackID aID)
   1.158 +{
   1.159 +  if (mState != kStarted) {
   1.160 +    return NS_ERROR_FAILURE;
   1.161 +  }
   1.162 +  if (!mTimer) {
   1.163 +    return NS_ERROR_FAILURE;
   1.164 +  }
   1.165 +
   1.166 +  mTimer->Cancel();
   1.167 +  mTimer = nullptr;
   1.168 +
   1.169 +  aSource->EndTrack(aID);
   1.170 +  aSource->Finish();
   1.171 +
   1.172 +  mState = kStopped;
   1.173 +  return NS_OK;
   1.174 +}
   1.175 +
   1.176 +nsresult
   1.177 +MediaEngineDefaultVideoSource::Snapshot(uint32_t aDuration, nsIDOMFile** aFile)
   1.178 +{
   1.179 +  *aFile = nullptr;
   1.180 +
   1.181 +#ifndef MOZ_WIDGET_ANDROID
   1.182 +  return NS_ERROR_NOT_IMPLEMENTED;
   1.183 +#else
   1.184 +  nsAutoString filePath;
   1.185 +  nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
   1.186 +  if (!filePicker)
   1.187 +    return NS_ERROR_FAILURE;
   1.188 +
   1.189 +  nsXPIDLString title;
   1.190 +  nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, "Browse", title);
   1.191 +  int16_t mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
   1.192 +
   1.193 +  nsresult rv = filePicker->Init(nullptr, title, mode);
   1.194 +  NS_ENSURE_SUCCESS(rv, rv);
   1.195 +  filePicker->AppendFilters(nsIFilePicker::filterImages);
   1.196 +
   1.197 +  // XXX - This API should be made async
   1.198 +  PRInt16 dialogReturn;
   1.199 +  rv = filePicker->Show(&dialogReturn);
   1.200 +  NS_ENSURE_SUCCESS(rv, rv);
   1.201 +  if (dialogReturn == nsIFilePicker::returnCancel) {
   1.202 +    *aFile = nullptr;
   1.203 +    return NS_OK;
   1.204 +  }
   1.205 +
   1.206 +  nsCOMPtr<nsIFile> localFile;
   1.207 +  filePicker->GetFile(getter_AddRefs(localFile));
   1.208 +
   1.209 +  if (!localFile) {
   1.210 +    *aFile = nullptr;
   1.211 +    return NS_OK;
   1.212 +  }
   1.213 +
   1.214 +  nsCOMPtr<nsIDOMFile> domFile = new nsDOMFileFile(localFile);
   1.215 +  domFile.forget(aFile);
   1.216 +  return NS_OK;
   1.217 +#endif
   1.218 +}
   1.219 +
   1.220 +NS_IMETHODIMP
   1.221 +MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer)
   1.222 +{
   1.223 +  // Update the target color
   1.224 +  if (mCr <= 16) {
   1.225 +    if (mCb < 240) {
   1.226 +      mCb++;
   1.227 +    } else {
   1.228 +      mCr++;
   1.229 +    }
   1.230 +  } else if (mCb >= 240) {
   1.231 +    if (mCr < 240) {
   1.232 +      mCr++;
   1.233 +    } else {
   1.234 +      mCb--;
   1.235 +    }
   1.236 +  } else if (mCr >= 240) {
   1.237 +    if (mCb > 16) {
   1.238 +      mCb--;
   1.239 +    } else {
   1.240 +      mCr--;
   1.241 +    }
   1.242 +  } else {
   1.243 +    mCr--;
   1.244 +  }
   1.245 +
   1.246 +  // Allocate a single solid color image
   1.247 +  nsRefPtr<layers::Image> image = mImageContainer->CreateImage(ImageFormat::PLANAR_YCBCR);
   1.248 +  nsRefPtr<layers::PlanarYCbCrImage> ycbcr_image =
   1.249 +      static_cast<layers::PlanarYCbCrImage*>(image.get());
   1.250 +  layers::PlanarYCbCrData data;
   1.251 +  AllocateSolidColorFrame(data, mOpts.mWidth, mOpts.mHeight, 0x80, mCb, mCr);
   1.252 +
   1.253 +#if defined(MOZ_WEBRTC) && defined(MOZ_WEBRTC_SIGNALING)
   1.254 +  uint64_t timestamp = PR_Now();
   1.255 +  YuvStamper::Encode(mOpts.mWidth, mOpts.mHeight, mOpts.mWidth,
   1.256 +		     data.mYChannel,
   1.257 +		     reinterpret_cast<unsigned char*>(&timestamp), sizeof(timestamp),
   1.258 +		     0, 0);
   1.259 +#endif
   1.260 +
   1.261 +  ycbcr_image->SetData(data);
   1.262 +  // SetData copies data, so we can free the frame
   1.263 +  ReleaseFrame(data);
   1.264 +
   1.265 +  MonitorAutoLock lock(mMonitor);
   1.266 +
   1.267 +  // implicitly releases last image
   1.268 +  mImage = ycbcr_image.forget();
   1.269 +
   1.270 +  return NS_OK;
   1.271 +}
   1.272 +
   1.273 +void
   1.274 +MediaEngineDefaultVideoSource::NotifyPull(MediaStreamGraph* aGraph,
   1.275 +                                          SourceMediaStream *aSource,
   1.276 +                                          TrackID aID,
   1.277 +                                          StreamTime aDesiredTime,
   1.278 +                                          TrackTicks &aLastEndTime)
   1.279 +{
   1.280 +  // AddTrack takes ownership of segment
   1.281 +  VideoSegment segment;
   1.282 +  MonitorAutoLock lock(mMonitor);
   1.283 +  if (mState != kStarted) {
   1.284 +    return;
   1.285 +  }
   1.286 +
   1.287 +  // Note: we're not giving up mImage here
   1.288 +  nsRefPtr<layers::Image> image = mImage;
   1.289 +  TrackTicks target = TimeToTicksRoundUp(USECS_PER_S, aDesiredTime);
   1.290 +  TrackTicks delta = target - aLastEndTime;
   1.291 +
   1.292 +  if (delta > 0) {
   1.293 +    // nullptr images are allowed
   1.294 +    IntSize size(image ? mOpts.mWidth : 0, image ? mOpts.mHeight : 0);
   1.295 +    segment.AppendFrame(image.forget(), delta, size);
   1.296 +    // This can fail if either a) we haven't added the track yet, or b)
   1.297 +    // we've removed or finished the track.
   1.298 +    if (aSource->AppendToTrack(aID, &segment)) {
   1.299 +      aLastEndTime = target;
   1.300 +    }
   1.301 +  }
   1.302 +}
   1.303 +
   1.304 +// generate 1k sine wave per second
   1.305 +class SineWaveGenerator
   1.306 +{
   1.307 +public:
   1.308 +  static const int bytesPerSample = 2;
   1.309 +  static const int millisecondsPerSecond = 1000;
   1.310 +  static const int frequency = 1000;
   1.311 +
   1.312 +  SineWaveGenerator(int aSampleRate) :
   1.313 +    mTotalLength(aSampleRate / frequency),
   1.314 +    mReadLength(0) {
   1.315 +    MOZ_ASSERT(mTotalLength * frequency == aSampleRate);
   1.316 +    mAudioBuffer = new int16_t[mTotalLength];
   1.317 +    for(int i = 0; i < mTotalLength; i++) {
   1.318 +      // Set volume to -20db. It's from 32768.0 * 10^(-20/20) = 3276.8
   1.319 +      mAudioBuffer[i] = (3276.8f * sin(2 * M_PI * i / mTotalLength));
   1.320 +    }
   1.321 +  }
   1.322 +
   1.323 +  // NOTE: only safely called from a single thread (MSG callback)
   1.324 +  void generate(int16_t* aBuffer, int16_t aLengthInSamples) {
   1.325 +    int16_t remaining = aLengthInSamples;
   1.326 +
   1.327 +    while (remaining) {
   1.328 +      int16_t processSamples = 0;
   1.329 +
   1.330 +      if (mTotalLength - mReadLength >= remaining) {
   1.331 +        processSamples = remaining;
   1.332 +      } else {
   1.333 +        processSamples = mTotalLength - mReadLength;
   1.334 +      }
   1.335 +      memcpy(aBuffer, mAudioBuffer + mReadLength, processSamples * bytesPerSample);
   1.336 +      aBuffer += processSamples;
   1.337 +      mReadLength += processSamples;
   1.338 +      remaining -= processSamples;
   1.339 +      if (mReadLength == mTotalLength) {
   1.340 +        mReadLength = 0;
   1.341 +      }
   1.342 +    }
   1.343 +  }
   1.344 +
   1.345 +private:
   1.346 +  nsAutoArrayPtr<int16_t> mAudioBuffer;
   1.347 +  int16_t mTotalLength;
   1.348 +  int16_t mReadLength;
   1.349 +};
   1.350 +
   1.351 +/**
   1.352 + * Default audio source.
   1.353 + */
   1.354 +NS_IMPL_ISUPPORTS(MediaEngineDefaultAudioSource, nsITimerCallback)
   1.355 +
   1.356 +MediaEngineDefaultAudioSource::MediaEngineDefaultAudioSource()
   1.357 +  : mTimer(nullptr)
   1.358 +{
   1.359 +  mState = kReleased;
   1.360 +}
   1.361 +
   1.362 +MediaEngineDefaultAudioSource::~MediaEngineDefaultAudioSource()
   1.363 +{}
   1.364 +
   1.365 +void
   1.366 +MediaEngineDefaultAudioSource::GetName(nsAString& aName)
   1.367 +{
   1.368 +  aName.Assign(NS_LITERAL_STRING("Default Audio Device"));
   1.369 +  return;
   1.370 +}
   1.371 +
   1.372 +void
   1.373 +MediaEngineDefaultAudioSource::GetUUID(nsAString& aUUID)
   1.374 +{
   1.375 +  aUUID.Assign(NS_LITERAL_STRING("B7CBD7C1-53EF-42F9-8353-73F61C70C092"));
   1.376 +  return;
   1.377 +}
   1.378 +
   1.379 +nsresult
   1.380 +MediaEngineDefaultAudioSource::Allocate(const AudioTrackConstraintsN &aConstraints,
   1.381 +                                        const MediaEnginePrefs &aPrefs)
   1.382 +{
   1.383 +  if (mState != kReleased) {
   1.384 +    return NS_ERROR_FAILURE;
   1.385 +  }
   1.386 +
   1.387 +  mState = kAllocated;
   1.388 +  // generate 1Khz sine wave
   1.389 +  mSineGenerator = new SineWaveGenerator(AUDIO_RATE);
   1.390 +  return NS_OK;
   1.391 +}
   1.392 +
   1.393 +nsresult
   1.394 +MediaEngineDefaultAudioSource::Deallocate()
   1.395 +{
   1.396 +  if (mState != kStopped && mState != kAllocated) {
   1.397 +    return NS_ERROR_FAILURE;
   1.398 +  }
   1.399 +  mState = kReleased;
   1.400 +  return NS_OK;
   1.401 +}
   1.402 +
   1.403 +nsresult
   1.404 +MediaEngineDefaultAudioSource::Start(SourceMediaStream* aStream, TrackID aID)
   1.405 +{
   1.406 +  if (mState != kAllocated) {
   1.407 +    return NS_ERROR_FAILURE;
   1.408 +  }
   1.409 +
   1.410 +  mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   1.411 +  if (!mTimer) {
   1.412 +    return NS_ERROR_FAILURE;
   1.413 +  }
   1.414 +
   1.415 +  mSource = aStream;
   1.416 +
   1.417 +  // AddTrack will take ownership of segment
   1.418 +  AudioSegment* segment = new AudioSegment();
   1.419 +  mSource->AddTrack(aID, AUDIO_RATE, 0, segment);
   1.420 +
   1.421 +  // We aren't going to add any more tracks
   1.422 +  mSource->AdvanceKnownTracksTime(STREAM_TIME_MAX);
   1.423 +
   1.424 +  // Remember TrackID so we can finish later
   1.425 +  mTrackID = aID;
   1.426 +
   1.427 +  // 1 Audio frame per 10ms
   1.428 +#if defined(MOZ_WIDGET_GONK) && defined(DEBUG)
   1.429 +// B2G emulator debug is very, very slow and has problems dealing with realtime audio inputs
   1.430 +  mTimer->InitWithCallback(this, MediaEngine::DEFAULT_AUDIO_TIMER_MS*10,
   1.431 +                           nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
   1.432 +#else
   1.433 +  mTimer->InitWithCallback(this, MediaEngine::DEFAULT_AUDIO_TIMER_MS,
   1.434 +                           nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
   1.435 +#endif
   1.436 +  mState = kStarted;
   1.437 +
   1.438 +  return NS_OK;
   1.439 +}
   1.440 +
   1.441 +nsresult
   1.442 +MediaEngineDefaultAudioSource::Stop(SourceMediaStream *aSource, TrackID aID)
   1.443 +{
   1.444 +  if (mState != kStarted) {
   1.445 +    return NS_ERROR_FAILURE;
   1.446 +  }
   1.447 +  if (!mTimer) {
   1.448 +    return NS_ERROR_FAILURE;
   1.449 +  }
   1.450 +
   1.451 +  mTimer->Cancel();
   1.452 +  mTimer = nullptr;
   1.453 +
   1.454 +  aSource->EndTrack(aID);
   1.455 +  aSource->Finish();
   1.456 +
   1.457 +  mState = kStopped;
   1.458 +  return NS_OK;
   1.459 +}
   1.460 +
   1.461 +nsresult
   1.462 +MediaEngineDefaultAudioSource::Snapshot(uint32_t aDuration, nsIDOMFile** aFile)
   1.463 +{
   1.464 +   return NS_ERROR_NOT_IMPLEMENTED;
   1.465 +}
   1.466 +
   1.467 +NS_IMETHODIMP
   1.468 +MediaEngineDefaultAudioSource::Notify(nsITimer* aTimer)
   1.469 +{
   1.470 +  AudioSegment segment;
   1.471 +  nsRefPtr<SharedBuffer> buffer = SharedBuffer::Create(AUDIO_FRAME_LENGTH * sizeof(int16_t));
   1.472 +  int16_t* dest = static_cast<int16_t*>(buffer->Data());
   1.473 +
   1.474 +  mSineGenerator->generate(dest, AUDIO_FRAME_LENGTH);
   1.475 +  nsAutoTArray<const int16_t*,1> channels;
   1.476 +  channels.AppendElement(dest);
   1.477 +  segment.AppendFrames(buffer.forget(), channels, AUDIO_FRAME_LENGTH);
   1.478 +  mSource->AppendToTrack(mTrackID, &segment);
   1.479 +
   1.480 +  return NS_OK;
   1.481 +}
   1.482 +
   1.483 +void
   1.484 +MediaEngineDefault::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >* aVSources) {
   1.485 +  MutexAutoLock lock(mMutex);
   1.486 +
   1.487 +  // We once had code here to find a VideoSource with the same settings and re-use that.
   1.488 +  // This no longer is possible since the resolution is being set in Allocate().
   1.489 +
   1.490 +  nsRefPtr<MediaEngineVideoSource> newSource = new MediaEngineDefaultVideoSource();
   1.491 +  mVSources.AppendElement(newSource);
   1.492 +  aVSources->AppendElement(newSource);
   1.493 +
   1.494 +  return;
   1.495 +}
   1.496 +
   1.497 +void
   1.498 +MediaEngineDefault::EnumerateAudioDevices(nsTArray<nsRefPtr<MediaEngineAudioSource> >* aASources) {
   1.499 +  MutexAutoLock lock(mMutex);
   1.500 +  int32_t len = mASources.Length();
   1.501 +
   1.502 +  for (int32_t i = 0; i < len; i++) {
   1.503 +    nsRefPtr<MediaEngineAudioSource> source = mASources.ElementAt(i);
   1.504 +    if (source->IsAvailable()) {
   1.505 +      aASources->AppendElement(source);
   1.506 +    }
   1.507 +  }
   1.508 +
   1.509 +  // All streams are currently busy, just make a new one.
   1.510 +  if (aASources->Length() == 0) {
   1.511 +    nsRefPtr<MediaEngineAudioSource> newSource =
   1.512 +      new MediaEngineDefaultAudioSource();
   1.513 +    mASources.AppendElement(newSource);
   1.514 +    aASources->AppendElement(newSource);
   1.515 +  }
   1.516 +  return;
   1.517 +}
   1.518 +
   1.519 +} // namespace mozilla

mercurial