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); }
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; }
/** * 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; }
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; }