mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
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:
parent
2e047ca037
commit
5f3f11820e
@ -38,11 +38,13 @@
|
|||||||
}, {
|
}, {
|
||||||
name: "persistent-storage",
|
name: "persistent-storage",
|
||||||
type: "persistent-storage",
|
type: "persistent-storage",
|
||||||
|
}, {
|
||||||
|
name: "midi",
|
||||||
|
type: "midi",
|
||||||
} ];
|
} ];
|
||||||
|
|
||||||
const UNSUPPORTED_PERMISSIONS = [
|
const UNSUPPORTED_PERMISSIONS = [
|
||||||
"foobarbaz", // Not in spec, for testing only.
|
"foobarbaz", // Not in spec, for testing only.
|
||||||
"midi",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create a closure, so that tests are run on the correct window object.
|
// Create a closure, so that tests are run on the correct window object.
|
||||||
|
@ -49,6 +49,36 @@
|
|||||||
"permission should also not be set for subdomain"
|
"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
|
// Explicitly set the permission as blocked, and expect the
|
||||||
// `requestMIDIAccess` call to be automatically rejected (not having any
|
// `requestMIDIAccess` call to be automatically rejected (not having any
|
||||||
// permission set would trigger the synthetic addon install provided by
|
// permission set would trigger the synthetic addon install provided by
|
||||||
@ -70,6 +100,13 @@
|
|||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
ok(true, "MIDI Access Request denied by default");
|
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.
|
// Gated permission should prompt for localhost.
|
||||||
@ -105,6 +142,9 @@
|
|||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
ok(false, "MIDI Access Request failed");
|
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.
|
// Gated permission should also apply to subdomains.
|
||||||
@ -114,6 +154,17 @@
|
|||||||
is(response, "succeeded", "MIDI Access Request allowed for subdomain");
|
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.
|
// Remove the permission.
|
||||||
await SpecialPowers.removePermission("midi-sysex", document);
|
await SpecialPowers.removePermission("midi-sysex", document);
|
||||||
await SpecialPowers.removePermission("midi", document);
|
await SpecialPowers.removePermission("midi", document);
|
||||||
|
34
dom/permission/MidiPermissionStatus.cpp
Normal file
34
dom/permission/MidiPermissionStatus.cpp
Normal 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
|
32
dom/permission/MidiPermissionStatus.h
Normal file
32
dom/permission/MidiPermissionStatus.h
Normal 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_
|
@ -63,6 +63,10 @@ JSObject* PermissionStatus::WrapObject(JSContext* aCx,
|
|||||||
return PermissionStatus_Binding::Wrap(aCx, this, aGivenProto);
|
return PermissionStatus_Binding::Wrap(aCx, this, aGivenProto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsLiteralCString PermissionStatus::GetPermissionType() {
|
||||||
|
return PermissionNameToType(mName);
|
||||||
|
}
|
||||||
|
|
||||||
nsresult PermissionStatus::UpdateState() {
|
nsresult PermissionStatus::UpdateState() {
|
||||||
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
|
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
|
||||||
if (NS_WARN_IF(!window)) {
|
if (NS_WARN_IF(!window)) {
|
||||||
@ -83,7 +87,7 @@ nsresult PermissionStatus::UpdateState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nsresult rv = permissionHandler->GetPermissionForPermissionsAPI(
|
nsresult rv = permissionHandler->GetPermissionForPermissionsAPI(
|
||||||
PermissionNameToType(mName), &action);
|
GetPermissionType(), &action);
|
||||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace mozilla::dom {
|
|||||||
|
|
||||||
class PermissionObserver;
|
class PermissionObserver;
|
||||||
|
|
||||||
class PermissionStatus final : public DOMEventTargetHelper {
|
class PermissionStatus : public DOMEventTargetHelper {
|
||||||
friend class PermissionObserver;
|
friend class PermissionObserver;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -34,13 +34,26 @@ class PermissionStatus final : public DOMEventTargetHelper {
|
|||||||
|
|
||||||
PermissionName Name() const { return mName; }
|
PermissionName Name() const { return mName; }
|
||||||
|
|
||||||
private:
|
nsresult Init();
|
||||||
|
|
||||||
|
protected:
|
||||||
~PermissionStatus();
|
~PermissionStatus();
|
||||||
|
|
||||||
PermissionStatus(nsPIDOMWindowInner* aWindow, PermissionName aName);
|
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();
|
nsresult UpdateState();
|
||||||
|
|
||||||
already_AddRefed<nsIPrincipal> GetPrincipal() const;
|
already_AddRefed<nsIPrincipal> GetPrincipal() const;
|
||||||
|
@ -15,7 +15,10 @@ static const nsLiteralCString kPermissionTypes[] = {
|
|||||||
"desktop-notification"_ns,
|
"desktop-notification"_ns,
|
||||||
// Alias `push` to `desktop-notification`.
|
// Alias `push` to `desktop-notification`.
|
||||||
"desktop-notification"_ns,
|
"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
|
// clang-format on
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,6 +33,12 @@ const nsLiteralCString& PermissionNameToType(PermissionName aName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Maybe<PermissionName> TypeToPermissionName(const nsACString& aType) {
|
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) {
|
for (size_t i = 0; i < ArrayLength(kPermissionTypes); ++i) {
|
||||||
if (kPermissionTypes[i].Equals(aType)) {
|
if (kPermissionTypes[i].Equals(aType)) {
|
||||||
return Some(static_cast<PermissionName>(i));
|
return Some(static_cast<PermissionName>(i));
|
||||||
|
@ -14,6 +14,15 @@
|
|||||||
namespace mozilla::dom {
|
namespace mozilla::dom {
|
||||||
|
|
||||||
const nsLiteralCString& PermissionNameToType(PermissionName aName);
|
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);
|
Maybe<PermissionName> TypeToPermissionName(const nsACString& aType);
|
||||||
|
|
||||||
PermissionState ActionToPermissionState(uint32_t aAction);
|
PermissionState ActionToPermissionState(uint32_t aAction);
|
||||||
|
@ -8,9 +8,10 @@
|
|||||||
|
|
||||||
#include "mozilla/dom/ContentChild.h"
|
#include "mozilla/dom/ContentChild.h"
|
||||||
#include "mozilla/dom/Document.h"
|
#include "mozilla/dom/Document.h"
|
||||||
|
#include "mozilla/dom/MidiPermissionStatus.h"
|
||||||
#include "mozilla/dom/PermissionMessageUtils.h"
|
#include "mozilla/dom/PermissionMessageUtils.h"
|
||||||
#include "mozilla/dom/PermissionsBinding.h"
|
|
||||||
#include "mozilla/dom/PermissionStatus.h"
|
#include "mozilla/dom/PermissionStatus.h"
|
||||||
|
#include "mozilla/dom/PermissionsBinding.h"
|
||||||
#include "mozilla/dom/Promise.h"
|
#include "mozilla/dom/Promise.h"
|
||||||
#include "mozilla/Components.h"
|
#include "mozilla/Components.h"
|
||||||
#include "nsIPermissionManager.h"
|
#include "nsIPermissionManager.h"
|
||||||
@ -50,6 +51,16 @@ already_AddRefed<PermissionStatus> CreatePermissionStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (permission.mName) {
|
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::Geolocation:
|
||||||
case PermissionName::Notifications:
|
case PermissionName::Notifications:
|
||||||
case PermissionName::Push:
|
case PermissionName::Push:
|
||||||
|
@ -8,11 +8,13 @@ with Files("**"):
|
|||||||
BUG_COMPONENT = ("Core", "DOM: Core & HTML")
|
BUG_COMPONENT = ("Core", "DOM: Core & HTML")
|
||||||
|
|
||||||
EXPORTS.mozilla.dom += [
|
EXPORTS.mozilla.dom += [
|
||||||
|
"MidiPermissionStatus.h",
|
||||||
"Permissions.h",
|
"Permissions.h",
|
||||||
"PermissionStatus.h",
|
"PermissionStatus.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
UNIFIED_SOURCES += [
|
UNIFIED_SOURCES += [
|
||||||
|
"MidiPermissionStatus.cpp",
|
||||||
"PermissionObserver.cpp",
|
"PermissionObserver.cpp",
|
||||||
"Permissions.cpp",
|
"Permissions.cpp",
|
||||||
"PermissionStatus.cpp",
|
"PermissionStatus.cpp",
|
||||||
|
@ -39,11 +39,13 @@
|
|||||||
}, {
|
}, {
|
||||||
name: 'persistent-storage',
|
name: 'persistent-storage',
|
||||||
type: 'persistent-storage'
|
type: 'persistent-storage'
|
||||||
|
}, {
|
||||||
|
name: 'midi',
|
||||||
|
type: 'midi'
|
||||||
}, ];
|
}, ];
|
||||||
|
|
||||||
const UNSUPPORTED_PERMISSIONS = [
|
const UNSUPPORTED_PERMISSIONS = [
|
||||||
'foobarbaz', // Not in spec, for testing only.
|
'foobarbaz', // Not in spec, for testing only.
|
||||||
'midi',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create a closure, so that tests are run on the correct window object.
|
// Create a closure, so that tests are run on the correct window object.
|
||||||
|
@ -11,8 +11,8 @@ enum PermissionName {
|
|||||||
"geolocation",
|
"geolocation",
|
||||||
"notifications",
|
"notifications",
|
||||||
"push",
|
"push",
|
||||||
"persistent-storage"
|
"persistent-storage",
|
||||||
// Unsupported: "midi"
|
"midi"
|
||||||
};
|
};
|
||||||
|
|
||||||
[GenerateInit]
|
[GenerateInit]
|
||||||
@ -20,6 +20,11 @@ dictionary PermissionDescriptor {
|
|||||||
required PermissionName name;
|
required PermissionName name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[GenerateInit]
|
||||||
|
dictionary MidiPermissionDescriptor : PermissionDescriptor {
|
||||||
|
boolean sysex;
|
||||||
|
};
|
||||||
|
|
||||||
// We don't implement `PushPermissionDescriptor` because we use a background
|
// We don't implement `PushPermissionDescriptor` because we use a background
|
||||||
// message quota instead of `userVisibleOnly`.
|
// message quota instead of `userVisibleOnly`.
|
||||||
|
|
||||||
|
@ -38,6 +38,8 @@ static const DelegateInfo sPermissionsMap[] = {
|
|||||||
{"persistent-storage", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
|
{"persistent-storage", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
|
||||||
{"vibration", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
|
{"vibration", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
|
||||||
{"midi", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
|
{"midi", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
|
||||||
|
// Like "midi" but with sysex support.
|
||||||
|
{"midi-sysex", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
|
||||||
{"storage-access", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
|
{"storage-access", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
|
||||||
{"camera", u"camera", DelegatePolicy::eDelegateUseFeaturePolicy},
|
{"camera", u"camera", DelegatePolicy::eDelegateUseFeaturePolicy},
|
||||||
{"microphone", u"microphone", DelegatePolicy::eDelegateUseFeaturePolicy},
|
{"microphone", u"microphone", DelegatePolicy::eDelegateUseFeaturePolicy},
|
||||||
|
@ -52,7 +52,7 @@ class PermissionDelegateHandler final : public nsIPermissionDelegateHandler {
|
|||||||
explicit PermissionDelegateHandler() = default;
|
explicit PermissionDelegateHandler() = default;
|
||||||
explicit PermissionDelegateHandler(mozilla::dom::Document* aDocument);
|
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 {
|
typedef struct DelegatedPermissionList {
|
||||||
Array<uint32_t, DELEGATED_PERMISSION_COUNT> mPermissions;
|
Array<uint32_t, DELEGATED_PERMISSION_COUNT> mPermissions;
|
||||||
|
@ -20,9 +20,6 @@
|
|||||||
[Query "magnetometer" permission]
|
[Query "magnetometer" permission]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Query "midi" permission]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Query "nfc" permission]
|
[Query "nfc" permission]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user