void
MediaManager::OnNavigation(uint64_t aWindowID)
{
  NS_ASSERTION(NS_IsMainThread(), "OnNavigation called off main thread");

  // Invalidate this window. The runnables check this value before making
  // a call to content.

  // This is safe since we're on main-thread, and the windowlist can only
  // be added to from the main-thread (see OnNavigation)
  StreamListeners* listeners = GetActiveWindows()->Get(aWindowID);
  if (!listeners) {
    return;
  }

  uint32_t length = listeners->Length();
  for (uint32_t i = 0; i < length; i++) {
    nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
      listeners->ElementAt(i);
    listener->Invalidate();
  }
  listeners->Clear();

  GetActiveWindows()->Remove(aWindowID);
}
Exemplo n.º 2
0
nsresult
MediaManager::MediaCaptureWindowStateInternal(nsIDOMWindow* aWindow, bool* aVideo,
        bool* aAudio)
{
    // We need to return the union of all streams in all innerwindows that
    // correspond to that outerwindow.

    // Iterate the docshell tree to find all the child windows, find
    // all the listeners for each one, get the booleans, and merge the
    // results.
    nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
    if (piWin) {
        if (piWin->GetCurrentInnerWindow() || piWin->IsInnerWindow()) {
            uint64_t windowID;
            if (piWin->GetCurrentInnerWindow()) {
                windowID = piWin->GetCurrentInnerWindow()->WindowID();
            } else {
                windowID = piWin->WindowID();
            }
            StreamListeners* listeners = GetActiveWindows()->Get(windowID);
            if (listeners) {
                uint32_t length = listeners->Length();
                for (uint32_t i = 0; i < length; ++i) {
                    nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
                        listeners->ElementAt(i);
                    if (listener->CapturingVideo()) {
                        *aVideo = true;
                    }
                    if (listener->CapturingAudio()) {
                        *aAudio = true;
                    }
                    if (*aAudio && *aVideo) {
                        return NS_OK; // no need to continue iterating
                    }
                }
            }
        }

        // iterate any children of *this* window (iframes, etc)
        nsCOMPtr<nsIDocShellTreeNode> node =
            do_QueryInterface(piWin->GetDocShell());
        if (node) {
            int32_t i, count;
            node->GetChildCount(&count);
            for (i = 0; i < count; ++i) {
                nsCOMPtr<nsIDocShellTreeItem> item;
                node->GetChildAt(i, getter_AddRefs(item));
                nsCOMPtr<nsPIDOMWindow> win = do_GetInterface(item);

                MediaCaptureWindowStateInternal(win, aVideo, aAudio);
                if (*aAudio && *aVideo) {
                    return NS_OK; // no need to continue iterating
                }
            }
        }
    }
    return NS_OK;
}
Exemplo n.º 3
0
/**
 * The entry point for this file. A call from Navigator::mozGetUserMedia
 * will end up here. MediaManager is a singleton that is responsible
 * for handling all incoming getUserMedia calls from every window.
 */
nsresult
MediaManager::GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow,
  nsIMediaStreamOptions* aParams,
  nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
  nsIDOMGetUserMediaErrorCallback* aOnError)
{
  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");

  NS_ENSURE_TRUE(aParams, NS_ERROR_NULL_POINTER);
  NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
  NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
  NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);

  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);

  /* Get options */
  nsresult rv;
  bool fake, audio, video, picture;

  rv = aParams->GetFake(&fake);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aParams->GetPicture(&picture);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aParams->GetAudio(&audio);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aParams->GetVideo(&video);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIMediaDevice> audiodevice;
  rv = aParams->GetAudioDevice(getter_AddRefs(audiodevice));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIMediaDevice> videodevice;
  rv = aParams->GetVideoDevice(getter_AddRefs(videodevice));
  NS_ENSURE_SUCCESS(rv, rv);

  // If a device was provided, make sure it support the type of stream requested.
  if (audiodevice) {
    nsString type;
    audiodevice->GetType(type);
    if (audio && !type.EqualsLiteral("audio")) {
      return NS_ERROR_FAILURE;
    }
  }
  if (videodevice) {
    nsString type;
    videodevice->GetType(type);
    if ((picture || video) && !type.EqualsLiteral("video")) {
        return NS_ERROR_FAILURE;
    }
  }

  // We only support "front" or "back". TBD: Send to GetUserMediaRunnable.
  nsString cameraType;
  rv = aParams->GetCamera(cameraType);
  NS_ENSURE_SUCCESS(rv, rv);

  /**
   * If we were asked to get a picture, before getting a snapshot, we check if
   * the calling page is allowed to open a popup. We do this because
   * {picture:true} will open a new "window" to let the user preview or select
   * an image, on Android. The desktop UI for {picture:true} is TBD, at which
   * may point we can decide whether to extend this test there as well.
   */
#if !defined(MOZ_WEBRTC)
  if (picture && !aPrivileged) {
    if (aWindow->GetPopupControlState() > openControlled) {
      nsCOMPtr<nsIPopupWindowManager> pm =
        do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
      if (!pm) {
        return NS_OK;
      }
      uint32_t permission;
      nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
      pm->TestPermission(doc->NodePrincipal(), &permission);
      if ((permission == nsIPopupWindowManager::DENY_POPUP)) {
        nsCOMPtr<nsIDOMDocument> domDoc = aWindow->GetExtantDocument();
        nsGlobalWindow::FirePopupBlockedEvent(
          domDoc, aWindow, nullptr, EmptyString(), EmptyString()
        );
        return NS_OK;
      }
    }
  }
#endif

  static bool created = false;
  if (!created) {
    // Force MediaManager to startup before we try to access it from other threads
    // Hack: should init singleton earlier unless it's expensive (mem or CPU)
    (void) MediaManager::Get();
  }

  // Store the WindowID in a hash table and mark as active. The entry is removed
  // when this window is closed or navigated away from.
  uint64_t windowID = aWindow->WindowID();
  nsRefPtr<GetUserMediaRunnable> gUMRunnable;
  // This is safe since we're on main-thread, and the windowlist can only
  // be invalidated from the main-thread (see OnNavigation)
  StreamListeners* listeners = GetActiveWindows()->Get(windowID);
  if (!listeners) {
    listeners = new StreamListeners;
    GetActiveWindows()->Put(windowID, listeners);
  }

  // Developer preference for turning off permission check.
  if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
    aPrivileged = true;
  }

  /**
   * Pass runnables along to GetUserMediaRunnable so it can add the
   * MediaStreamListener to the runnable list. The last argument can
   * optionally be a MediaDevice object, which should provided if one was
   * selected by the user via the UI, or was provided by privileged code
   * via the device: attribute via nsIMediaStreamOptions.
   *
   * If a fake stream was requested, we force the use of the default backend.
   */
  if (fake) {
    // Fake stream from default backend.
    gUMRunnable = new GetUserMediaRunnable(
      audio, video, onSuccess.forget(), onError.forget(), windowID,
      new MediaEngineDefault()
                                           );
  } else if (audiodevice || videodevice) {
    // Stream from provided device.
    gUMRunnable = new GetUserMediaRunnable(
      audio, video, picture, onSuccess.forget(), onError.forget(), windowID,
      static_cast<MediaDevice*>(audiodevice.get()),
      static_cast<MediaDevice*>(videodevice.get())
                                           );
  } else {
    // Stream from default device from WebRTC backend.
    gUMRunnable = new GetUserMediaRunnable(
      audio, video, picture, onSuccess.forget(), onError.forget(), windowID
                                           );
  }

#ifdef ANDROID
  if (picture) {
    // ShowFilePickerForMimeType() must run on the Main Thread! (on Android)
    NS_DispatchToMainThread(gUMRunnable);
  }
  // XXX No support for Audio or Video in Android yet
#else
  // XXX No full support for picture in Desktop yet (needs proper UI)
  if (aPrivileged || fake) {
    mMediaThread->Dispatch(gUMRunnable, NS_DISPATCH_NORMAL);
  } else {
    // Ask for user permission, and dispatch runnable (or not) when a response
    // is received via an observer notification. Each call is paired with its
    // runnable by a GUID.
    nsresult rv;
    nsCOMPtr<nsIUUIDGenerator> uuidgen =
      do_GetService("@mozilla.org/uuid-generator;1", &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    // Generate a call ID.
    nsID id;
    rv = uuidgen->GenerateUUIDInPlace(&id);
    NS_ENSURE_SUCCESS(rv, rv);

    char buffer[NSID_LENGTH];
    id.ToProvidedString(buffer);
    NS_ConvertUTF8toUTF16 callID(buffer);

    // Store the current callback.
    mActiveCallbacks.Put(callID, gUMRunnable);

    // Construct JSON structure with both the windowID and the callID.
    nsAutoString data;
    data.Append(NS_LITERAL_STRING("{\"windowID\":"));

    // Convert window ID to string.
    char windowBuffer[32];
    PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu",
                aWindow->GetOuterWindow()->WindowID());
    data.Append(NS_ConvertUTF8toUTF16(windowBuffer));

    data.Append(NS_LITERAL_STRING(", \"callID\":\""));
    data.Append(callID);
    data.Append(NS_LITERAL_STRING("\"}"));

    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    obs->NotifyObservers(aParams, "getUserMedia:request", data.get());
  }
#endif

  return NS_OK;
}
Exemplo n.º 4
0
nsresult
MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
  const PRUnichar* aData)
{
  NS_ASSERTION(NS_IsMainThread(), "Observer invoked off the main thread");
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();

  if (!strcmp(aTopic, "xpcom-shutdown")) {
    obs->RemoveObserver(this, "xpcom-shutdown");
    obs->RemoveObserver(this, "getUserMedia:response:allow");
    obs->RemoveObserver(this, "getUserMedia:response:deny");
    obs->RemoveObserver(this, "getUserMedia:revoke");

    // Close off any remaining active windows.
    {
      MutexAutoLock lock(mMutex);
      GetActiveWindows()->Clear();
      mActiveCallbacks.Clear();
      sSingleton = nullptr;
    }

    return NS_OK;
  }

  if (!strcmp(aTopic, "getUserMedia:response:allow")) {
    nsString key(aData);
    nsRefPtr<nsRunnable> runnable;
    if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
      return NS_OK;
    }
    mActiveCallbacks.Remove(key);

    if (aSubject) {
      // A particular device or devices were chosen by the user.
      // NOTE: does not allow setting a device to null; assumes nullptr
      GetUserMediaRunnable* gUMRunnable =
        static_cast<GetUserMediaRunnable*>(runnable.get());

      nsCOMPtr<nsISupportsArray> array(do_QueryInterface(aSubject));
      MOZ_ASSERT(array);
      uint32_t len = 0;
      array->Count(&len);
      MOZ_ASSERT(len);
      if (!len) {
        gUMRunnable->Denied(); // neither audio nor video were selected
        return NS_OK;
      }
      for (uint32_t i = 0; i < len; i++) {
        nsCOMPtr<nsISupports> supports;
        array->GetElementAt(i,getter_AddRefs(supports));
        nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supports));
        MOZ_ASSERT(device); // shouldn't be returning anything else...
        if (device) {
          nsString type;
          device->GetType(type);
          if (type.EqualsLiteral("video")) {
            gUMRunnable->SetVideoDevice(static_cast<MediaDevice*>(device.get()));
          } else if (type.EqualsLiteral("audio")) {
            gUMRunnable->SetAudioDevice(static_cast<MediaDevice*>(device.get()));
          } else {
            NS_WARNING("Unknown device type in getUserMedia");
          }
        }
      }
    }

    // Reuse the same thread to save memory.
    mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
    return NS_OK;
  }

  if (!strcmp(aTopic, "getUserMedia:response:deny")) {
    nsString key(aData);
    nsRefPtr<nsRunnable> runnable;
    if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
      return NS_OK;
    }
    mActiveCallbacks.Remove(key);

    GetUserMediaRunnable* gUMRunnable =
      static_cast<GetUserMediaRunnable*>(runnable.get());
    gUMRunnable->Denied();
    return NS_OK;
  }

  if (!strcmp(aTopic, "getUserMedia:revoke")) {
    nsresult rv;
    uint64_t windowID = nsString(aData).ToInteger64(&rv);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    if (NS_SUCCEEDED(rv)) {
      LOG(("Revoking MediaCapture access for window %llu",windowID));
      OnNavigation(windowID);
    }

    return NS_OK;
  }

  return NS_OK;
}
nsresult
MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
  const PRUnichar* aData)
{
  NS_ASSERTION(NS_IsMainThread(), "Observer invoked off the main thread");
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();

  if (!strcmp(aTopic, "xpcom-shutdown")) {
    obs->RemoveObserver(this, "xpcom-shutdown");
    obs->RemoveObserver(this, "getUserMedia:response:allow");
    obs->RemoveObserver(this, "getUserMedia:response:deny");

    // Close off any remaining active windows.
    {
      MutexAutoLock lock(mMutex);
      GetActiveWindows()->Clear();
      mActiveCallbacks.Clear();
      sSingleton = nullptr;
    }

    return NS_OK;
  }

  if (!strcmp(aTopic, "getUserMedia:response:allow")) {
    nsString key(aData);
    nsRefPtr<nsRunnable> runnable;
    if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
      return NS_OK;
    }
    mActiveCallbacks.Remove(key);

    if (aSubject) {
      // A particular device was chosen by the user.
      // NOTE: does not allow setting a device to null; assumes nullptr
      nsCOMPtr<nsIMediaDevice> device = do_QueryInterface(aSubject);
      if (device) {
        GetUserMediaRunnable* gUMRunnable =
          static_cast<GetUserMediaRunnable*>(runnable.get());
        nsString type;
        device->GetType(type);
        if (type.EqualsLiteral("video")) {
          gUMRunnable->SetVideoDevice(static_cast<MediaDevice*>(device.get()));
        } else if (type.EqualsLiteral("audio")) {
          gUMRunnable->SetAudioDevice(static_cast<MediaDevice*>(device.get()));
        } else {
          NS_WARNING("Unknown device type in getUserMedia");
        }
      }
    }

    // Reuse the same thread to save memory.
    mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
    return NS_OK;
  }

  if (!strcmp(aTopic, "getUserMedia:response:deny")) {
    nsString key(aData);
    nsRefPtr<nsRunnable> runnable;
    if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
      return NS_OK;
    }
    mActiveCallbacks.Remove(key);

    GetUserMediaRunnable* gUMRunnable =
      static_cast<GetUserMediaRunnable*>(runnable.get());
    gUMRunnable->Denied();
    return NS_OK;
  }

  return NS_OK;
}