dom/media/MediaPermissionGonk.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 #include "MediaManager.h"
     6 #include "MediaPermissionGonk.h"
     8 #include "nsCOMPtr.h"
     9 #include "nsCxPusher.h"
    10 #include "nsIContentPermissionPrompt.h"
    11 #include "nsIDocument.h"
    12 #include "nsIDOMNavigatorUserMedia.h"
    13 #include "nsIStringEnumerator.h"
    14 #include "nsISupportsArray.h"
    15 #include "nsJSUtils.h"
    16 #include "nsPIDOMWindow.h"
    17 #include "nsTArray.h"
    18 #include "GetUserMediaRequest.h"
    19 #include "PCOMContentPermissionRequestChild.h"
    20 #include "mozilla/dom/PBrowserChild.h"
    21 #include "mozilla/dom/TabChild.h"
    22 #include "mozilla/dom/MediaStreamTrackBinding.h"
    23 #include "nsISupportsPrimitives.h"
    24 #include "nsServiceManagerUtils.h"
    25 #include "nsArrayUtils.h"
    26 #include "nsContentPermissionHelper.h"
    27 #include "mozilla/dom/PermissionMessageUtils.h"
    29 #define AUDIO_PERMISSION_NAME "audio-capture"
    30 #define VIDEO_PERMISSION_NAME "video-capture"
    32 using namespace mozilla::dom;
    34 namespace mozilla {
    36 static MediaPermissionManager *gMediaPermMgr = nullptr;
    38 static uint32_t
    39 ConvertArrayToPermissionRequest(nsIArray* aSrcArray,
    40                                 nsTArray<PermissionRequest>& aDesArray)
    41 {
    42   uint32_t len = 0;
    43   aSrcArray->GetLength(&len);
    44   for (uint32_t i = 0; i < len; i++) {
    45     nsCOMPtr<nsIContentPermissionType> cpt = do_QueryElementAt(aSrcArray, i);
    46     nsAutoCString type;
    47     nsAutoCString access;
    48     cpt->GetType(type);
    49     cpt->GetAccess(access);
    51     nsCOMPtr<nsIArray> optionArray;
    52     cpt->GetOptions(getter_AddRefs(optionArray));
    53     uint32_t optionsLength = 0;
    54     optionArray->GetLength(&optionsLength);
    55     nsTArray<nsString> options;
    56     for (uint32_t j = 0; j < optionsLength; ++j) {
    57       nsCOMPtr<nsISupportsString> isupportsString = do_QueryElementAt(optionArray, j);
    58       if (isupportsString) {
    59         nsString option;
    60         isupportsString->GetData(option);
    61         options.AppendElement(option);
    62       }
    63     }
    65     aDesArray.AppendElement(PermissionRequest(type, access, options));
    66   }
    67   return len;
    68 }
    70 static void
    71 CreateDeviceNameList(nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices,
    72                      nsTArray<nsString> &aDeviceNameList)
    73 {
    74   for (uint32_t i = 0; i < aDevices.Length(); ++i) {
    75      nsString name;
    76      nsresult rv = aDevices[i]->GetName(name);
    77      NS_ENSURE_SUCCESS_VOID(rv);
    78      aDeviceNameList.AppendElement(name);
    79   }
    80 }
    82 static already_AddRefed<nsIMediaDevice>
    83 FindDeviceByName(nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices,
    84                  const nsAString &aDeviceName)
    85 {
    86   for (uint32_t i = 0; i < aDevices.Length(); ++i) {
    87     nsCOMPtr<nsIMediaDevice> device = aDevices[i];
    88     nsString deviceName;
    89     device->GetName(deviceName);
    90     if (deviceName.Equals(aDeviceName)) {
    91       return device.forget();
    92     }
    93   }
    95   return nullptr;
    96 }
    98 // Helper function for notifying permission granted
    99 static nsresult
   100 NotifyPermissionAllow(const nsAString &aCallID, nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
   101 {
   102   nsresult rv;
   103   nsCOMPtr<nsISupportsArray> array;
   104   rv = NS_NewISupportsArray(getter_AddRefs(array));
   105   NS_ENSURE_SUCCESS(rv, rv);
   107   for (uint32_t i = 0; i < aDevices.Length(); ++i) {
   108     rv = array->AppendElement(aDevices.ElementAt(i));
   109     NS_ENSURE_SUCCESS(rv, rv);
   110   }
   112   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   113   NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
   115   return obs->NotifyObservers(array, "getUserMedia:response:allow",
   116                               aCallID.BeginReading());
   117 }
   119 // Helper function for notifying permision denial or error
   120 static nsresult
   121 NotifyPermissionDeny(const nsAString &aCallID, const nsAString &aErrorMsg)
   122 {
   123   nsresult rv;
   124   nsCOMPtr<nsISupportsString> supportsString =
   125     do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
   126   NS_ENSURE_SUCCESS(rv, rv);
   128   rv = supportsString->SetData(aErrorMsg);
   129   NS_ENSURE_SUCCESS(rv, rv);
   131   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   132   NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
   134   return obs->NotifyObservers(supportsString, "getUserMedia:response:deny",
   135                               aCallID.BeginReading());
   136 }
   138 namespace {
   140 /**
   141  * MediaPermissionRequest will send a prompt ipdl request to b2g process according
   142  * to its owned type.
   143  */
   144 class MediaPermissionRequest : public nsIContentPermissionRequest
   145                              , public PCOMContentPermissionRequestChild
   146 {
   147 public:
   148   NS_DECL_ISUPPORTS
   149   NS_DECL_NSICONTENTPERMISSIONREQUEST
   151   MediaPermissionRequest(nsRefPtr<dom::GetUserMediaRequest> &aRequest,
   152                          nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices);
   153   virtual ~MediaPermissionRequest() {}
   155   // It will be called when prompt dismissed.
   156   virtual bool Recv__delete__(const bool &allow,
   157                               const InfallibleTArray<PermissionChoice>& choices) MOZ_OVERRIDE;
   158   virtual void IPDLRelease() MOZ_OVERRIDE { Release(); }
   160   already_AddRefed<nsPIDOMWindow> GetOwner();
   162 private:
   163   nsresult DoAllow(const nsString &audioDevice, const nsString &videoDevice);
   165   bool mAudio; // Request for audio permission
   166   bool mVideo; // Request for video permission
   167   nsRefPtr<dom::GetUserMediaRequest> mRequest;
   168   nsTArray<nsCOMPtr<nsIMediaDevice> > mAudioDevices; // candidate audio devices
   169   nsTArray<nsCOMPtr<nsIMediaDevice> > mVideoDevices; // candidate video devices
   170 };
   172 // MediaPermissionRequest
   173 NS_IMPL_ISUPPORTS(MediaPermissionRequest, nsIContentPermissionRequest)
   175 MediaPermissionRequest::MediaPermissionRequest(nsRefPtr<dom::GetUserMediaRequest> &aRequest,
   176                                                nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
   177   : mRequest(aRequest)
   178 {
   179   dom::MediaStreamConstraints constraints;
   180   mRequest->GetConstraints(constraints);
   182   mAudio = !constraints.mAudio.IsBoolean() || constraints.mAudio.GetAsBoolean();
   183   mVideo = !constraints.mVideo.IsBoolean() || constraints.mVideo.GetAsBoolean();
   185   for (uint32_t i = 0; i < aDevices.Length(); ++i) {
   186     nsCOMPtr<nsIMediaDevice> device(aDevices[i]);
   187     nsAutoString deviceType;
   188     device->GetType(deviceType);
   189     if (mAudio && deviceType.EqualsLiteral("audio")) {
   190       mAudioDevices.AppendElement(device);
   191     }
   192     if (mVideo && deviceType.EqualsLiteral("video")) {
   193       mVideoDevices.AppendElement(device);
   194     }
   195   }
   196 }
   198 // nsIContentPermissionRequest methods
   199 NS_IMETHODIMP
   200 MediaPermissionRequest::GetTypes(nsIArray** aTypes)
   201 {
   202   nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
   203   //XXX append device list
   204   if (mAudio) {
   205     nsTArray<nsString> audioDeviceNames;
   206     CreateDeviceNameList(mAudioDevices, audioDeviceNames);
   207     nsCOMPtr<nsISupports> AudioType =
   208       new ContentPermissionType(NS_LITERAL_CSTRING(AUDIO_PERMISSION_NAME),
   209                                 NS_LITERAL_CSTRING("unused"),
   210                                 audioDeviceNames);
   211     types->AppendElement(AudioType, false);
   212   }
   213   if (mVideo) {
   214     nsTArray<nsString> videoDeviceNames;
   215     CreateDeviceNameList(mVideoDevices, videoDeviceNames);
   216     nsCOMPtr<nsISupports> VideoType =
   217       new ContentPermissionType(NS_LITERAL_CSTRING(VIDEO_PERMISSION_NAME),
   218                                 NS_LITERAL_CSTRING("unused"),
   219                                 videoDeviceNames);
   220     types->AppendElement(VideoType, false);
   221   }
   222   NS_IF_ADDREF(*aTypes = types);
   224   return NS_OK;
   225 }
   227 NS_IMETHODIMP
   228 MediaPermissionRequest::GetPrincipal(nsIPrincipal **aRequestingPrincipal)
   229 {
   230   NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
   232   nsCOMPtr<nsPIDOMWindow> window = static_cast<nsPIDOMWindow*>
   233       (nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID()));
   234   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
   236   nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
   237   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
   239   NS_ADDREF(*aRequestingPrincipal = doc->NodePrincipal());
   240   return NS_OK;
   241 }
   243 NS_IMETHODIMP
   244 MediaPermissionRequest::GetWindow(nsIDOMWindow** aRequestingWindow)
   245 {
   246   NS_ENSURE_ARG_POINTER(aRequestingWindow);
   247   nsCOMPtr<nsPIDOMWindow> window = static_cast<nsPIDOMWindow*>
   248       (nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID()));
   249   window.forget(aRequestingWindow);
   250   return NS_OK;
   251 }
   253 NS_IMETHODIMP
   254 MediaPermissionRequest::GetElement(nsIDOMElement** aRequestingElement)
   255 {
   256   NS_ENSURE_ARG_POINTER(aRequestingElement);
   257   *aRequestingElement = nullptr;
   258   return NS_OK;
   259 }
   261 NS_IMETHODIMP
   262 MediaPermissionRequest::Cancel()
   263 {
   264   nsString callID;
   265   mRequest->GetCallID(callID);
   266   NotifyPermissionDeny(callID, NS_LITERAL_STRING("Permission Denied"));
   267   return NS_OK;
   268 }
   270 NS_IMETHODIMP
   271 MediaPermissionRequest::Allow(JS::HandleValue aChoices)
   272 {
   273   // check if JS object
   274   if (!aChoices.isObject()) {
   275     MOZ_ASSERT(false, "Not a correct format of PermissionChoice");
   276     return NS_ERROR_INVALID_ARG;
   277   }
   278   // iterate through audio-capture and video-capture
   279   AutoSafeJSContext cx;
   280   JS::Rooted<JSObject*> obj(cx, &aChoices.toObject());
   281   JSAutoCompartment ac(cx, obj);
   282   JS::Rooted<JS::Value> v(cx);
   284   // get selected audio device name
   285   nsString audioDevice;
   286   if (mAudio) {
   287     if (!JS_GetProperty(cx, obj, AUDIO_PERMISSION_NAME, &v) || !v.isString()) {
   288       return NS_ERROR_FAILURE;
   289     }
   290     nsDependentJSString deviceName;
   291     if (!deviceName.init(cx, v)) {
   292       MOZ_ASSERT(false, "Couldn't initialize string from aChoices");
   293       return NS_ERROR_FAILURE;
   294     }
   295     audioDevice = deviceName;
   296   }
   298   // get selected video device name
   299   nsString videoDevice;
   300   if (mVideo) {
   301     if (!JS_GetProperty(cx, obj, VIDEO_PERMISSION_NAME, &v) || !v.isString()) {
   302       return NS_ERROR_FAILURE;
   303     }
   304     nsDependentJSString deviceName;
   305     if (!deviceName.init(cx, v)) {
   306       MOZ_ASSERT(false, "Couldn't initialize string from aChoices");
   307       return NS_ERROR_FAILURE;
   308     }
   309     videoDevice = deviceName;
   310   }
   312   return DoAllow(audioDevice, videoDevice);
   313 }
   315 nsresult
   316 MediaPermissionRequest::DoAllow(const nsString &audioDevice,
   317                                 const nsString &videoDevice)
   318 {
   319   nsTArray<nsCOMPtr<nsIMediaDevice> > selectedDevices;
   320   if (mAudio) {
   321     nsCOMPtr<nsIMediaDevice> device =
   322       FindDeviceByName(mAudioDevices, audioDevice);
   323     if (device) {
   324       selectedDevices.AppendElement(device);
   325     }
   326   }
   328   if (mVideo) {
   329     nsCOMPtr<nsIMediaDevice> device =
   330       FindDeviceByName(mVideoDevices, videoDevice);
   331     if (device) {
   332       selectedDevices.AppendElement(device);
   333     }
   334   }
   336   nsString callID;
   337   mRequest->GetCallID(callID);
   338   return NotifyPermissionAllow(callID, selectedDevices);
   339 }
   341 already_AddRefed<nsPIDOMWindow>
   342 MediaPermissionRequest::GetOwner()
   343 {
   344   nsCOMPtr<nsPIDOMWindow> window = static_cast<nsPIDOMWindow*>
   345       (nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID()));
   346   return window.forget();
   347 }
   349 //PCOMContentPermissionRequestChild
   350 bool
   351 MediaPermissionRequest::Recv__delete__(const bool& allow,
   352                                        const InfallibleTArray<PermissionChoice>& choices)
   353 {
   354   if (allow) {
   355     // get selected device name for audio and video
   356     nsString audioDevice, videoDevice;
   357     for (uint32_t i = 0; i < choices.Length(); ++i) {
   358       const nsString &choice = choices[i].choice();
   359       if (choices[i].type().EqualsLiteral(AUDIO_PERMISSION_NAME)) {
   360         audioDevice = choice;
   361       } else if (choices[i].type().EqualsLiteral(VIDEO_PERMISSION_NAME)) {
   362         videoDevice = choice;
   363       }
   364     }
   365     (void) DoAllow(audioDevice, videoDevice);
   366   } else {
   367     (void) Cancel();
   368   }
   369   return true;
   370 }
   372 // Success callback for MediaManager::GetUserMediaDevices().
   373 class MediaDeviceSuccessCallback: public nsIGetUserMediaDevicesSuccessCallback
   374 {
   375 public:
   376   NS_DECL_ISUPPORTS
   377   NS_DECL_NSIGETUSERMEDIADEVICESSUCCESSCALLBACK
   379   MediaDeviceSuccessCallback(nsRefPtr<dom::GetUserMediaRequest> &aRequest)
   380     : mRequest(aRequest) {}
   381   virtual ~MediaDeviceSuccessCallback() {}
   383 private:
   384   nsresult DoPrompt(nsRefPtr<MediaPermissionRequest> &req);
   385   nsRefPtr<dom::GetUserMediaRequest> mRequest;
   386 };
   388 NS_IMPL_ISUPPORTS(MediaDeviceSuccessCallback, nsIGetUserMediaDevicesSuccessCallback)
   390 // nsIGetUserMediaDevicesSuccessCallback method
   391 NS_IMETHODIMP
   392 MediaDeviceSuccessCallback::OnSuccess(nsIVariant* aDevices)
   393 {
   394   nsIID elementIID;
   395   uint16_t elementType;
   396   void* rawArray;
   397   uint32_t arrayLen;
   399   nsresult rv;
   400   rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen, &rawArray);
   401   NS_ENSURE_SUCCESS(rv, rv);
   403   if (elementType != nsIDataType::VTYPE_INTERFACE) {
   404     NS_Free(rawArray);
   405     return NS_ERROR_FAILURE;
   406   }
   408   // Create array for nsIMediaDevice
   409   nsTArray<nsCOMPtr<nsIMediaDevice> > devices;
   411   nsISupports **supportsArray = reinterpret_cast<nsISupports **>(rawArray);
   412   for (uint32_t i = 0; i < arrayLen; ++i) {
   413     nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
   414     devices.AppendElement(device);
   415     NS_IF_RELEASE(supportsArray[i]); // explicitly decrease reference count for raw pointer
   416   }
   417   NS_Free(rawArray); // explicitly free for the memory from nsIVariant::GetAsArray
   419   // Send MediaPermissionRequest
   420   nsRefPtr<MediaPermissionRequest> req = new MediaPermissionRequest(mRequest, devices);
   421   rv = DoPrompt(req);
   423   NS_ENSURE_SUCCESS(rv, rv);
   424   return NS_OK;
   425 }
   427 // Trigger permission prompt UI
   428 nsresult
   429 MediaDeviceSuccessCallback::DoPrompt(nsRefPtr<MediaPermissionRequest> &req)
   430 {
   431   // for content process
   432   if (XRE_GetProcessType() == GeckoProcessType_Content) {
   433     MOZ_ASSERT(NS_IsMainThread()); // IPC can only be execute on main thread.
   435     nsresult rv;
   437     nsCOMPtr<nsPIDOMWindow> window(req->GetOwner());
   438     NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
   440     dom::TabChild* child = dom::TabChild::GetFrom(window->GetDocShell());
   441     NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
   443     nsCOMPtr<nsIArray> typeArray;
   444     rv = req->GetTypes(getter_AddRefs(typeArray));
   445     NS_ENSURE_SUCCESS(rv, rv);
   447     nsTArray<PermissionRequest> permArray;
   448     ConvertArrayToPermissionRequest(typeArray, permArray);
   450     nsCOMPtr<nsIPrincipal> principal;
   451     rv = req->GetPrincipal(getter_AddRefs(principal));
   452     NS_ENSURE_SUCCESS(rv, rv);
   454     req->AddRef();
   455     child->SendPContentPermissionRequestConstructor(req,
   456                                                     permArray,
   457                                                     IPC::Principal(principal));
   459     req->Sendprompt();
   460     return NS_OK;
   461   }
   463   // for chrome process
   464   nsCOMPtr<nsIContentPermissionPrompt> prompt =
   465       do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
   466   if (prompt) {
   467     prompt->Prompt(req);
   468   }
   469   return NS_OK;
   470 }
   472 // Error callback for MediaManager::GetUserMediaDevices()
   473 class MediaDeviceErrorCallback: public nsIDOMGetUserMediaErrorCallback
   474 {
   475 public:
   476   NS_DECL_ISUPPORTS
   477   NS_DECL_NSIDOMGETUSERMEDIAERRORCALLBACK
   479   MediaDeviceErrorCallback(const nsAString &aCallID)
   480     : mCallID(aCallID) {}
   482   virtual ~MediaDeviceErrorCallback() {}
   484 private:
   485   const nsString mCallID;
   486 };
   488 NS_IMPL_ISUPPORTS(MediaDeviceErrorCallback, nsIDOMGetUserMediaErrorCallback)
   490 // nsIDOMGetUserMediaErrorCallback method
   491 NS_IMETHODIMP
   492 MediaDeviceErrorCallback::OnError(const nsAString &aError)
   493 {
   494   return NotifyPermissionDeny(mCallID, aError);
   495 }
   497 } // namespace anonymous
   499 // MediaPermissionManager
   500 NS_IMPL_ISUPPORTS(MediaPermissionManager, nsIObserver)
   502 MediaPermissionManager*
   503 MediaPermissionManager::GetInstance()
   504 {
   505   if (!gMediaPermMgr) {
   506     gMediaPermMgr = new MediaPermissionManager();
   507   }
   509   return gMediaPermMgr;
   510 }
   512 MediaPermissionManager::MediaPermissionManager()
   513 {
   514   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   515   if (obs) {
   516     obs->AddObserver(this, "getUserMedia:request", false);
   517     obs->AddObserver(this, "xpcom-shutdown", false);
   518   }
   519 }
   521 MediaPermissionManager::~MediaPermissionManager()
   522 {
   523   this->Deinit();
   524 }
   526 nsresult
   527 MediaPermissionManager::Deinit()
   528 {
   529   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   530   if (obs) {
   531     obs->RemoveObserver(this, "getUserMedia:request");
   532     obs->RemoveObserver(this, "xpcom-shutdown");
   533   }
   534   return NS_OK;
   535 }
   537 // nsIObserver method
   538 NS_IMETHODIMP
   539 MediaPermissionManager::Observe(nsISupports* aSubject, const char* aTopic,
   540   const char16_t* aData)
   541 {
   542   nsresult rv;
   543   if (!strcmp(aTopic, "getUserMedia:request")) {
   544     nsRefPtr<dom::GetUserMediaRequest> req =
   545         static_cast<dom::GetUserMediaRequest*>(aSubject);
   546     rv = HandleRequest(req);
   548     if (NS_FAILED(rv)) {
   549       nsString callID;
   550       req->GetCallID(callID);
   551       NotifyPermissionDeny(callID, NS_LITERAL_STRING("unable to enumerate media device"));
   552     }
   553   } else if (!strcmp(aTopic, "xpcom-shutdown")) {
   554     rv = this->Deinit();
   555   } else {
   556     // not reachable
   557     rv = NS_ERROR_FAILURE;
   558   }
   559   return rv;
   560 }
   562 // Handle GetUserMediaRequest, query available media device first.
   563 nsresult
   564 MediaPermissionManager::HandleRequest(nsRefPtr<dom::GetUserMediaRequest> &req)
   565 {
   566   nsString callID;
   567   req->GetCallID(callID);
   569   nsCOMPtr<nsPIDOMWindow> innerWindow = static_cast<nsPIDOMWindow*>
   570       (nsGlobalWindow::GetInnerWindowWithId(req->InnerWindowID()));
   571   if (!innerWindow) {
   572     MOZ_ASSERT(false, "No inner window");
   573     return NS_ERROR_FAILURE;
   574   }
   576   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess =
   577       new MediaDeviceSuccessCallback(req);
   578   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError =
   579       new MediaDeviceErrorCallback(callID);
   581   dom::MediaStreamConstraints constraints;
   582   req->GetConstraints(constraints);
   584   nsRefPtr<MediaManager> MediaMgr = MediaManager::GetInstance();
   585   nsresult rv = MediaMgr->GetUserMediaDevices(innerWindow, constraints, onSuccess, onError);
   586   NS_ENSURE_SUCCESS(rv, rv);
   588   return NS_OK;
   589 }
   591 } // namespace mozilla

mercurial