Bug 1732410 wait for focus before proceeding for enumerateDevices() Promises r=jib,nika

https://github.com/w3c/mediacapture-main/pull/574

Focus on browser chrome widgets is accepted provided the tab is fully active
and foreground.
https://github.com/w3c/mediacapture-main/issues/752#issuecomment-742036800

Differential Revision: https://phabricator.services.mozilla.com/D127051
This commit is contained in:
Karl Tomlinson 2021-10-11 20:44:39 +00:00
parent a88ced1c73
commit e91d1ff3ec
11 changed files with 81 additions and 19 deletions

View File

@ -33,6 +33,7 @@
#include "mozilla/dom/HTMLIFrameElement.h"
#include "mozilla/dom/Location.h"
#include "mozilla/dom/LocationBinding.h"
#include "mozilla/dom/MediaDevices.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SessionStorageManager.h"
@ -2930,14 +2931,19 @@ void BrowsingContext::DidSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>,
if (RefPtr<Document> doc = aContext->GetExtantDocument()) {
doc->UpdateDocumentStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE, true);
RefPtr<nsPIDOMWindowInner> win = doc->GetInnerWindow();
RefPtr<MediaDevices> devices;
if (isActivateEvent && (devices = win->GetExtantMediaDevices())) {
devices->BrowserWindowBecameActive();
}
if (XRE_IsContentProcess() &&
(!aContext->GetParent() || !aContext->GetParent()->IsInProcess())) {
// Send the inner window an activate/deactivate event if
// the context is the top of a sub-tree of in-process
// contexts.
nsContentUtils::DispatchEventOnlyToChrome(
doc, doc->GetWindow()->GetCurrentInnerWindow(),
isActivateEvent ? u"activate"_ns : u"deactivate"_ns,
doc, win, isActivateEvent ? u"activate"_ns : u"deactivate"_ns,
CanBubble::eYes, Cancelable::eYes, nullptr);
}
}

View File

@ -178,6 +178,7 @@ class Navigator final : public nsISupports, public nsWrapperCache {
already_AddRefed<LegacyMozTCPSocket> MozTCPSocket();
network::Connection* GetConnection(ErrorResult& aRv);
MediaDevices* GetMediaDevices(ErrorResult& aRv);
MediaDevices* GetExtantMediaDevices() const { return mMediaDevices; };
void GetGamepads(nsTArray<RefPtr<Gamepad>>& aGamepads, ErrorResult& aRv);
GamepadServiceTest* RequestGamepadServiceTest();

View File

@ -137,6 +137,7 @@
#include "mozilla/dom/LocalStorage.h"
#include "mozilla/dom/LocalStorageCommon.h"
#include "mozilla/dom/Location.h"
#include "mozilla/dom/MediaDevices.h"
#include "mozilla/dom/MediaKeys.h"
#include "mozilla/dom/NavigatorBinding.h"
#include "mozilla/dom/Nullable.h"
@ -2310,6 +2311,10 @@ Navigator* nsPIDOMWindowInner::Navigator() {
return mNavigator;
}
MediaDevices* nsPIDOMWindowInner::GetExtantMediaDevices() const {
return mNavigator ? mNavigator->GetExtantMediaDevices() : nullptr;
}
VisualViewport* nsGlobalWindowInner::VisualViewport() {
if (!mVisualViewport) {
mVisualViewport = new mozilla::dom::VisualViewport(this);
@ -5570,6 +5575,10 @@ void nsGlobalWindowInner::Resume(bool aIncludeSubWindows) {
mAudioContexts[i]->ResumeFromChrome();
}
if (RefPtr<MediaDevices> devices = GetExtantMediaDevices()) {
devices->WindowResumed();
}
mTimeoutManager->Resume();
ResumeIdleRequests();
@ -5721,6 +5730,13 @@ void nsGlobalWindowInner::SyncStateFromParentWindow() {
}
}
void nsGlobalWindowInner::UpdateBackgroundState() {
if (RefPtr<MediaDevices> devices = GetExtantMediaDevices()) {
devices->BackgroundStateChanged();
}
mTimeoutManager->UpdateBackgroundState();
}
template <typename Method, typename... Args>
CallState nsGlobalWindowInner::CallOnInProcessDescendantsInternal(
BrowsingContext* aBrowsingContext, bool aChildOnly, Method aMethod,

View File

@ -325,6 +325,12 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
virtual bool IsFrozen() const override;
void SyncStateFromParentWindow();
// Called on the current inner window of a browsing context when its
// background state changes according to selected tab or visibility of the
// browser window. Used with Suspend()/Resume() or Freeze()/Thaw() because
// background state may change while the inner window is not current.
void UpdateBackgroundState();
mozilla::dom::DebuggerNotificationManager*
GetOrCreateDebuggerNotificationManager() override;

View File

@ -6757,7 +6757,7 @@ void nsGlobalWindowOuter::SetIsBackground(bool aIsBackground) {
nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
if (inner && changed) {
inner->mTimeoutManager->UpdateBackgroundState();
inner->UpdateBackgroundState();
}
if (aIsBackground) {

View File

@ -62,6 +62,7 @@ class DocGroup;
class Document;
class Element;
class Location;
class MediaDevices;
class MediaKeys;
class Navigator;
class Performance;
@ -577,6 +578,7 @@ class nsPIDOMWindowInner : public mozIDOMWindow {
uint32_t GetMarkedCCGeneration() { return mMarkedCCGeneration; }
mozilla::dom::Navigator* Navigator();
mozilla::dom::MediaDevices* GetExtantMediaDevices() const;
virtual mozilla::dom::Location* Location() = 0;
virtual nsresult GetControllers(nsIControllers** aControllers) = 0;

View File

@ -5,6 +5,7 @@
#include "mozilla/dom/MediaDevices.h"
#include "AudioDeviceInfo.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/MediaDeviceInfo.h"
@ -139,10 +140,39 @@ already_AddRefed<Promise> MediaDevices::EnumerateDevices(ErrorResult& aRv) {
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mPendingEnumerateDevicesPromises.AppendElement(p);
MaybeResumeDeviceExposure();
return p.forget();
}
void MediaDevices::MaybeResumeDeviceExposure() {
if (mPendingEnumerateDevicesPromises.IsEmpty()) {
return;
}
nsPIDOMWindowInner* window = GetOwner();
if (!window || !window->IsFullyActive()) {
return;
}
BrowsingContext* bc = window->GetBrowsingContext();
if (!bc->IsActive() || // not foreground tab
!bc->GetIsActiveBrowserWindow()) { // browser window does not have focus
return;
}
auto pending = std::move(mPendingEnumerateDevicesPromises);
for (auto& promise : pending) {
ResumeEnumerateDevices(std::move(promise));
}
}
void MediaDevices::ResumeEnumerateDevices(RefPtr<Promise> aPromise) {
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
MOZ_ASSERT(window, "Fully active document should have window");
RefPtr<MediaDevices> self(this);
MediaManager::Get()->EnumerateDevices(owner)->Then(
MediaManager::Get()->EnumerateDevices(window)->Then(
GetCurrentSerialEventTarget(), __func__,
[this, self, p](RefPtr<MediaManager::MediaDeviceSetRefCnt>&& aDevices) {
[this, self,
aPromise](RefPtr<MediaManager::MediaDeviceSetRefCnt>&& aDevices) {
nsPIDOMWindowInner* window = GetWindowIfCurrent();
if (!window) {
return; // Leave Promise pending after navigation by design.
@ -189,16 +219,15 @@ already_AddRefed<Promise> MediaDevices::EnumerateDevices(ErrorResult& aRv) {
infos.AppendElement(MakeRefPtr<MediaDeviceInfo>(
device->mID, device->mKind, label, device->mGroupID));
}
p->MaybeResolve(std::move(infos));
aPromise->MaybeResolve(std::move(infos));
},
[this, self, p](const RefPtr<MediaMgrError>& error) {
[this, self, aPromise](const RefPtr<MediaMgrError>& error) {
nsPIDOMWindowInner* window = GetWindowIfCurrent();
if (!window) {
return; // Leave Promise pending after navigation by design.
}
error->Reject(p);
error->Reject(aPromise);
});
return p.forget();
}
already_AddRefed<Promise> MediaDevices::GetDisplayMedia(
@ -479,7 +508,10 @@ RefPtr<MediaDevices::SinkInfoPromise> MediaDevices::GetSinkDevice(
});
}
NS_IMPL_ISUPPORTS_INHERITED0(MediaDevices, DOMEventTargetHelper)
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(MediaDevices,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaDevices, DOMEventTargetHelper,
mPendingEnumerateDevicesPromises)
void MediaDevices::OnDeviceChange() {
MOZ_ASSERT(NS_IsMainThread());

View File

@ -39,6 +39,7 @@ class MediaDevices final : public DOMEventTargetHelper {
explicit MediaDevices(nsPIDOMWindowInner* aWindow);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaDevices, DOMEventTargetHelper)
JSObject* WrapObject(JSContext* cx,
JS::Handle<JSObject*> aGivenProto) override;
@ -79,14 +80,21 @@ class MediaDevices final : public DOMEventTargetHelper {
void EventListenerAdded(nsAtom* aType) override;
using DOMEventTargetHelper::EventListenerAdded;
void BackgroundStateChanged() { MaybeResumeDeviceExposure(); }
void WindowResumed() { MaybeResumeDeviceExposure(); }
void BrowserWindowBecameActive() { MaybeResumeDeviceExposure(); }
private:
class GumResolver;
class EnumDevResolver;
class GumRejecter;
virtual ~MediaDevices();
void MaybeResumeDeviceExposure();
void ResumeEnumerateDevices(RefPtr<Promise> aPromise);
nsTHashSet<nsString> mExplicitlyGrantedAudioOutputIds;
nsTArray<RefPtr<Promise>> mPendingEnumerateDevicesPromises;
nsCOMPtr<nsITimer> mFuzzTimer;
// Connect/Disconnect on main thread only

View File

@ -1,4 +0,0 @@
[enumerateDevices-with-navigation.https.html]
expected: TIMEOUT
[enumerateDevices with navigation]
expected: TIMEOUT

View File

@ -1,5 +1,3 @@
[enumerateDevices-in-background.https.html]
disabled:
if buildapp != 'browser': uses Firefox-for-Desktop specific API
[enumerateDevices in background]
expected: FAIL

View File

@ -1,3 +0,0 @@
[enumerateDevices-without-focus.https.html]
[enumerateDevices without focus]
expected: FAIL