Bug 1437171 - Add Web MIDI support in navigator.permissions.query(). r=webidl,smaug,pbz

Differential Revision: https://phabricator.services.mozilla.com/D164036
This commit is contained in:
William Durand 2022-12-15 10:24:17 +00:00
parent 2e047ca037
commit 5f3f11820e
15 changed files with 187 additions and 14 deletions

View File

@ -38,11 +38,13 @@
}, {
name: "persistent-storage",
type: "persistent-storage",
}, {
name: "midi",
type: "midi",
} ];
const UNSUPPORTED_PERMISSIONS = [
"foobarbaz", // Not in spec, for testing only.
"midi",
];
// Create a closure, so that tests are run on the correct window object.

View File

@ -49,6 +49,36 @@
"permission should also not be set for subdomain"
);
let onChangeCalled = 0;
let onChangeCalledWithSysex = 0;
// We expect the same states with and without sysex support.
const expectedChangedStates = ["denied", "granted", "prompt"];
for (let sysex of [false, true]) {
let result = await navigator.permissions.query({ name: "midi", sysex });
is(result?.state, "prompt", "expected 'prompt' permission status");
// Register two unique listeners that should be invoked every time we
// change permissions in the rest of this test case: one with sysex
// support, and the other one without.
if (sysex) {
result.onchange = () => {
is(
result.state,
expectedChangedStates[onChangeCalledWithSysex++],
"expected change event with sysex support"
);
};
} else {
result.onchange = () => {
is(
result.state,
expectedChangedStates[onChangeCalled++],
"expected change event"
);
};
}
}
// Explicitly set the permission as blocked, and expect the
// `requestMIDIAccess` call to be automatically rejected (not having any
// permission set would trigger the synthetic addon install provided by
@ -70,6 +100,13 @@
} catch (ex) {
ok(true, "MIDI Access Request denied by default");
}
let result = await navigator.permissions.query({ name: "midi", sysex });
// We expect "denied" because that's what has been set above (with
// `SpecialPowers.addPermission()`). In practice, this state should
// never be returned since explicit rejection is handled at the add-on
// installation level.
is(result?.state, "denied", "expected 'denied' permission status");
}
// Gated permission should prompt for localhost.
@ -105,6 +142,9 @@
} catch (ex) {
ok(false, "MIDI Access Request failed");
}
let result = await navigator.permissions.query({ name: "midi", sysex });
is(result?.state, "granted", "expected 'granted' permission status");
}
// Gated permission should also apply to subdomains.
@ -114,6 +154,17 @@
is(response, "succeeded", "MIDI Access Request allowed for subdomain");
}
is(
onChangeCalled,
expectedChangedStates.length - 1,
`expected onchange listener to have been called ${expectedChangedStates.length - 1} times`
);
is(
onChangeCalledWithSysex,
expectedChangedStates.length - 1,
`expected onchange listener to have been called ${expectedChangedStates.length - 1} times (sysex)`
);
// Remove the permission.
await SpecialPowers.removePermission("midi-sysex", document);
await SpecialPowers.removePermission("midi", document);

View File

@ -0,0 +1,34 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/MidiPermissionStatus.h"
#include "mozilla/dom/PermissionStatus.h"
#include "mozilla/Permission.h"
namespace mozilla::dom {
/* static */
already_AddRefed<PermissionStatus> MidiPermissionStatus::Create(
nsPIDOMWindowInner* aWindow, bool aSysex, ErrorResult& aRv) {
RefPtr<PermissionStatus> status = new MidiPermissionStatus(aWindow, aSysex);
aRv = status->Init();
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return status.forget();
}
MidiPermissionStatus::MidiPermissionStatus(nsPIDOMWindowInner* aWindow,
bool aSysex)
: PermissionStatus(aWindow, PermissionName::Midi), mSysex(aSysex) {}
nsLiteralCString MidiPermissionStatus::GetPermissionType() {
return mSysex ? "midi-sysex"_ns : "midi"_ns;
}
} // namespace mozilla::dom

View File

@ -0,0 +1,32 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_MidiPermissionStatus_h_
#define mozilla_dom_MidiPermissionStatus_h_
#include "mozilla/dom/PermissionStatus.h"
namespace mozilla::dom {
class MidiPermissionStatus final : public PermissionStatus {
public:
static already_AddRefed<PermissionStatus> Create(nsPIDOMWindowInner* aWindow,
bool aSysex,
ErrorResult& aRv);
private:
~MidiPermissionStatus() {}
MidiPermissionStatus(nsPIDOMWindowInner* aWindow, bool aSysex);
virtual nsLiteralCString GetPermissionType() override;
bool mSysex;
};
} // namespace mozilla::dom
#endif // mozilla_dom_MidiPermissionStatus_h_

View File

@ -63,6 +63,10 @@ JSObject* PermissionStatus::WrapObject(JSContext* aCx,
return PermissionStatus_Binding::Wrap(aCx, this, aGivenProto);
}
nsLiteralCString PermissionStatus::GetPermissionType() {
return PermissionNameToType(mName);
}
nsresult PermissionStatus::UpdateState() {
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
if (NS_WARN_IF(!window)) {
@ -83,7 +87,7 @@ nsresult PermissionStatus::UpdateState() {
}
nsresult rv = permissionHandler->GetPermissionForPermissionsAPI(
PermissionNameToType(mName), &action);
GetPermissionType(), &action);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

View File

@ -15,7 +15,7 @@ namespace mozilla::dom {
class PermissionObserver;
class PermissionStatus final : public DOMEventTargetHelper {
class PermissionStatus : public DOMEventTargetHelper {
friend class PermissionObserver;
public:
@ -34,13 +34,26 @@ class PermissionStatus final : public DOMEventTargetHelper {
PermissionName Name() const { return mName; }
private:
nsresult Init();
protected:
~PermissionStatus();
PermissionStatus(nsPIDOMWindowInner* aWindow, PermissionName aName);
nsresult Init();
/**
* This method returns the internal permission type, which should be equal to
* the permission name for all but the MIDI permission because of the SysEx
* support: internally, we have both "midi" and "midi-sysex" permission types
* but we only have a "midi" (public) permission name.
*
* Note: the `MidiPermissionDescriptor` descriptor has an optional `sysex`
* boolean, which is used to determine whether to return "midi" or
* "midi-sysex" for the MIDI permission.
*/
virtual nsLiteralCString GetPermissionType();
private:
nsresult UpdateState();
already_AddRefed<nsIPrincipal> GetPrincipal() const;

View File

@ -15,7 +15,10 @@ static const nsLiteralCString kPermissionTypes[] = {
"desktop-notification"_ns,
// Alias `push` to `desktop-notification`.
"desktop-notification"_ns,
"persistent-storage"_ns
"persistent-storage"_ns,
// "midi" is the only public permission but internally we have both "midi"
// and "midi-sysex" (and yes, this is confusing).
"midi"_ns
// clang-format on
};
@ -30,6 +33,12 @@ const nsLiteralCString& PermissionNameToType(PermissionName aName) {
}
Maybe<PermissionName> TypeToPermissionName(const nsACString& aType) {
// Annoyingly, "midi-sysex" is an internal permission. The public permission
// name is "midi" so we have to special-case it here...
if (aType.Equals("midi-sysex"_ns)) {
return Some(PermissionName::Midi);
}
for (size_t i = 0; i < ArrayLength(kPermissionTypes); ++i) {
if (kPermissionTypes[i].Equals(aType)) {
return Some(static_cast<PermissionName>(i));

View File

@ -14,6 +14,15 @@
namespace mozilla::dom {
const nsLiteralCString& PermissionNameToType(PermissionName aName);
/**
* Returns the permission name given a permission type.
*
* Note: the "midi" permission is implemented with two internal permissions
* ("midi" and "midi-sysex"). For this reason, when we pass "midi-sysex" to
* this function, it unconditionally returns the "midi" permission name,
* because that's the only public permission name.
*/
Maybe<PermissionName> TypeToPermissionName(const nsACString& aType);
PermissionState ActionToPermissionState(uint32_t aAction);

View File

@ -8,9 +8,10 @@
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/MidiPermissionStatus.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/PermissionsBinding.h"
#include "mozilla/dom/PermissionStatus.h"
#include "mozilla/dom/PermissionsBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Components.h"
#include "nsIPermissionManager.h"
@ -50,6 +51,16 @@ already_AddRefed<PermissionStatus> CreatePermissionStatus(
}
switch (permission.mName) {
case PermissionName::Midi: {
MidiPermissionDescriptor midiPerm;
if (NS_WARN_IF(!midiPerm.Init(aCx, value))) {
aRv.NoteJSContextException(aCx);
return nullptr;
}
bool sysex = midiPerm.mSysex.WasPassed() && midiPerm.mSysex.Value();
return MidiPermissionStatus::Create(aWindow, sysex, aRv);
}
case PermissionName::Geolocation:
case PermissionName::Notifications:
case PermissionName::Push:

View File

@ -8,11 +8,13 @@ with Files("**"):
BUG_COMPONENT = ("Core", "DOM: Core & HTML")
EXPORTS.mozilla.dom += [
"MidiPermissionStatus.h",
"Permissions.h",
"PermissionStatus.h",
]
UNIFIED_SOURCES += [
"MidiPermissionStatus.cpp",
"PermissionObserver.cpp",
"Permissions.cpp",
"PermissionStatus.cpp",

View File

@ -39,11 +39,13 @@
}, {
name: 'persistent-storage',
type: 'persistent-storage'
}, {
name: 'midi',
type: 'midi'
}, ];
const UNSUPPORTED_PERMISSIONS = [
'foobarbaz', // Not in spec, for testing only.
'midi',
];
// Create a closure, so that tests are run on the correct window object.

View File

@ -11,8 +11,8 @@ enum PermissionName {
"geolocation",
"notifications",
"push",
"persistent-storage"
// Unsupported: "midi"
"persistent-storage",
"midi"
};
[GenerateInit]
@ -20,6 +20,11 @@ dictionary PermissionDescriptor {
required PermissionName name;
};
[GenerateInit]
dictionary MidiPermissionDescriptor : PermissionDescriptor {
boolean sysex;
};
// We don't implement `PushPermissionDescriptor` because we use a background
// message quota instead of `userVisibleOnly`.

View File

@ -38,6 +38,8 @@ static const DelegateInfo sPermissionsMap[] = {
{"persistent-storage", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
{"vibration", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
{"midi", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
// Like "midi" but with sysex support.
{"midi-sysex", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
{"storage-access", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
{"camera", u"camera", DelegatePolicy::eDelegateUseFeaturePolicy},
{"microphone", u"microphone", DelegatePolicy::eDelegateUseFeaturePolicy},

View File

@ -52,7 +52,7 @@ class PermissionDelegateHandler final : public nsIPermissionDelegateHandler {
explicit PermissionDelegateHandler() = default;
explicit PermissionDelegateHandler(mozilla::dom::Document* aDocument);
static constexpr size_t DELEGATED_PERMISSION_COUNT = 11;
static constexpr size_t DELEGATED_PERMISSION_COUNT = 12;
typedef struct DelegatedPermissionList {
Array<uint32_t, DELEGATED_PERMISSION_COUNT> mPermissions;

View File

@ -20,9 +20,6 @@
[Query "magnetometer" permission]
expected: FAIL
[Query "midi" permission]
expected: FAIL
[Query "nfc" permission]
expected: FAIL