mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
Bug 1758468
- Use stable, anonymous IDs for Web MIDI ports r=padenot
Differential Revision: https://phabricator.services.mozilla.com/D142550
This commit is contained in:
parent
91ba6f771f
commit
e8dd00607f
@ -89,12 +89,14 @@ void MIDIAccess::FireConnectionEvent(MIDIPort* aPort) {
|
||||
aPort->GetId(id);
|
||||
ErrorResult rv;
|
||||
if (aPort->State() == MIDIPortDeviceState::Disconnected) {
|
||||
if (aPort->Type() == MIDIPortType::Input &&
|
||||
MIDIInputMap_Binding::MaplikeHelpers::Has(mInputMap, id, rv)) {
|
||||
MIDIInputMap_Binding::MaplikeHelpers::Delete(mInputMap, id, rv);
|
||||
} else if (aPort->Type() == MIDIPortType::Output &&
|
||||
MIDIOutputMap_Binding::MaplikeHelpers::Has(mOutputMap, id, rv)) {
|
||||
MIDIOutputMap_Binding::MaplikeHelpers::Delete(mOutputMap, id, rv);
|
||||
if (aPort->Type() == MIDIPortType::Input && mInputMap->Has(id)) {
|
||||
MIDIInputMap_Binding::MaplikeHelpers::Delete(mInputMap, aPort->StableId(),
|
||||
rv);
|
||||
mInputMap->Remove(id);
|
||||
} else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
|
||||
MIDIOutputMap_Binding::MaplikeHelpers::Delete(mOutputMap,
|
||||
aPort->StableId(), rv);
|
||||
mOutputMap->Remove(id);
|
||||
}
|
||||
// Check to make sure Has()/Delete() calls haven't failed.
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
@ -106,31 +108,31 @@ void MIDIAccess::FireConnectionEvent(MIDIPort* aPort) {
|
||||
// this means a port that was disconnected has been reconnected, with the
|
||||
// port owner holding the object during that time, and we should add that
|
||||
// port object to our maps again.
|
||||
if (aPort->Type() == MIDIPortType::Input &&
|
||||
!MIDIInputMap_Binding::MaplikeHelpers::Has(mInputMap, id, rv)) {
|
||||
if (aPort->Type() == MIDIPortType::Input && !mInputMap->Has(id)) {
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
LOG("Input port not found");
|
||||
return;
|
||||
}
|
||||
MIDIInputMap_Binding::MaplikeHelpers::Set(
|
||||
mInputMap, id, *(static_cast<MIDIInput*>(aPort)), rv);
|
||||
mInputMap, aPort->StableId(), *(static_cast<MIDIInput*>(aPort)), rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
LOG("Map Set failed for input port");
|
||||
return;
|
||||
}
|
||||
} else if (aPort->Type() == MIDIPortType::Output &&
|
||||
!MIDIOutputMap_Binding::MaplikeHelpers::Has(mOutputMap, id,
|
||||
rv)) {
|
||||
mInputMap->Insert(id, aPort);
|
||||
} else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
LOG("Output port not found");
|
||||
return;
|
||||
}
|
||||
MIDIOutputMap_Binding::MaplikeHelpers::Set(
|
||||
mOutputMap, id, *(static_cast<MIDIOutput*>(aPort)), rv);
|
||||
mOutputMap, aPort->StableId(), *(static_cast<MIDIOutput*>(aPort)),
|
||||
rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
LOG("Map set failed for output port");
|
||||
return;
|
||||
}
|
||||
mOutputMap->Insert(id, aPort);
|
||||
}
|
||||
}
|
||||
RefPtr<MIDIConnectionEvent> event =
|
||||
@ -144,9 +146,7 @@ void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo& aInfo,
|
||||
MIDIPortType type = static_cast<MIDIPortType>(aInfo.type());
|
||||
RefPtr<MIDIPort> port;
|
||||
if (type == MIDIPortType::Input) {
|
||||
bool hasPort =
|
||||
MIDIInputMap_Binding::MaplikeHelpers::Has(mInputMap, id, aRv);
|
||||
if (hasPort || NS_WARN_IF(aRv.Failed())) {
|
||||
if (mInputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
|
||||
// We already have the port in our map.
|
||||
return;
|
||||
}
|
||||
@ -157,15 +157,15 @@ void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo& aInfo,
|
||||
return;
|
||||
}
|
||||
MIDIInputMap_Binding::MaplikeHelpers::Set(
|
||||
mInputMap, id, *(static_cast<MIDIInput*>(port.get())), aRv);
|
||||
mInputMap, port->StableId(), *(static_cast<MIDIInput*>(port.get())),
|
||||
aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
LOG("Coudld't set input port in map");
|
||||
return;
|
||||
}
|
||||
mInputMap->Insert(id, port);
|
||||
} else if (type == MIDIPortType::Output) {
|
||||
bool hasPort =
|
||||
MIDIOutputMap_Binding::MaplikeHelpers::Has(mOutputMap, id, aRv);
|
||||
if (hasPort || NS_WARN_IF(aRv.Failed())) {
|
||||
if (mOutputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
|
||||
// We already have the port in our map.
|
||||
return;
|
||||
}
|
||||
@ -176,11 +176,13 @@ void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo& aInfo,
|
||||
return;
|
||||
}
|
||||
MIDIOutputMap_Binding::MaplikeHelpers::Set(
|
||||
mOutputMap, id, *(static_cast<MIDIOutput*>(port.get())), aRv);
|
||||
mOutputMap, port->StableId(), *(static_cast<MIDIOutput*>(port.get())),
|
||||
aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
LOG("Coudld't set output port in map");
|
||||
return;
|
||||
}
|
||||
mOutputMap->Insert(id, port);
|
||||
} else {
|
||||
// If we hit this, then we have some port that is neither input nor output.
|
||||
// That is bad.
|
||||
|
@ -7,7 +7,9 @@
|
||||
#ifndef mozilla_dom_MIDIInputMap_h
|
||||
#define mozilla_dom_MIDIInputMap_h
|
||||
|
||||
#include "mozilla/dom/MIDIPort.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsTHashMap.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
class nsPIDOMWindowInner;
|
||||
@ -27,9 +29,15 @@ class MIDIInputMap final : public nsISupports, public nsWrapperCache {
|
||||
explicit MIDIInputMap(nsPIDOMWindowInner* aParent);
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
bool Has(nsAString& aId) { return mPorts.Get(aId) != nullptr; }
|
||||
void Insert(nsAString& aId, RefPtr<MIDIPort> aPort) {
|
||||
mPorts.InsertOrUpdate(aId, aPort);
|
||||
}
|
||||
void Remove(nsAString& aId) { mPorts.Remove(aId); }
|
||||
|
||||
private:
|
||||
~MIDIInputMap() = default;
|
||||
nsTHashMap<nsString, RefPtr<MIDIPort>> mPorts;
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
};
|
||||
|
||||
|
@ -7,7 +7,9 @@
|
||||
#ifndef mozilla_dom_MIDIOutputMap_h
|
||||
#define mozilla_dom_MIDIOutputMap_h
|
||||
|
||||
#include "mozilla/dom/MIDIPort.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsTHashMap.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
class nsPIDOMWindowInner;
|
||||
@ -30,9 +32,15 @@ class MIDIOutputMap final : public nsISupports, public nsWrapperCache {
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
bool Has(nsAString& aId) { return mPorts.Get(aId) != nullptr; }
|
||||
void Insert(nsAString& aId, RefPtr<MIDIPort> aPort) {
|
||||
mPorts.InsertOrUpdate(aId, aPort);
|
||||
}
|
||||
void Remove(nsAString& aId) { mPorts.Remove(aId); }
|
||||
|
||||
private:
|
||||
~MIDIOutputMap() = default;
|
||||
nsTHashMap<nsString, RefPtr<MIDIPort>> mPorts;
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
};
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, MOZ_COUNT_DTOR
|
||||
#include "MIDILog.h"
|
||||
|
||||
@ -63,8 +64,17 @@ MIDIPort::~MIDIPort() {
|
||||
}
|
||||
|
||||
bool MIDIPort::Initialize(const MIDIPortInfo& aPortInfo, bool aSysexEnabled) {
|
||||
nsIURI* uri = GetDocumentIfCurrent()->GetDocumentURI();
|
||||
nsAutoCString origin;
|
||||
nsresult rv = nsContentUtils::GetASCIIOrigin(uri, origin);
|
||||
if (NS_FAILED(rv)) {
|
||||
return false;
|
||||
}
|
||||
RefPtr<MIDIPortChild> port =
|
||||
new MIDIPortChild(aPortInfo, aSysexEnabled, this);
|
||||
if (NS_FAILED(port->GenerateStableId(origin))) {
|
||||
return false;
|
||||
}
|
||||
PBackgroundChild* b = BackgroundChild::GetForCurrentThread();
|
||||
MOZ_ASSERT(b,
|
||||
"Should always have a valid BackgroundChild when creating a port "
|
||||
@ -91,7 +101,7 @@ void MIDIPort::UnsetIPCPort() {
|
||||
|
||||
void MIDIPort::GetId(nsString& aRetVal) const {
|
||||
MOZ_ASSERT(mPort);
|
||||
aRetVal = mPort->MIDIPortInterface::Id();
|
||||
aRetVal = mPort->StableId();
|
||||
}
|
||||
|
||||
void MIDIPort::GetManufacturer(nsString& aRetVal) const {
|
||||
@ -252,4 +262,6 @@ void MIDIPort::DontKeepAliveOnStatechange() {
|
||||
}
|
||||
}
|
||||
|
||||
const nsString& MIDIPort::StableId() { return mPort->StableId(); }
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
@ -73,6 +73,7 @@ class MIDIPort : public DOMEventTargetHelper,
|
||||
IMPL_EVENT_HANDLER(statechange)
|
||||
|
||||
void DisconnectFromOwner() override;
|
||||
const nsString& StableId();
|
||||
|
||||
protected:
|
||||
// IPC Actor corresponding to this class
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "mozilla/dom/MIDIPortChild.h"
|
||||
#include "mozilla/dom/MIDIPort.h"
|
||||
#include "mozilla/dom/MIDIPortInterface.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
@ -55,3 +56,14 @@ void MIDIPortChild::SetActorAlive() {
|
||||
mActorWasAlive = true;
|
||||
AddRef();
|
||||
}
|
||||
|
||||
nsresult MIDIPortChild::GenerateStableId(const nsACString& aOrigin) {
|
||||
const size_t kIdLength = 64;
|
||||
mStableId.SetCapacity(kIdLength);
|
||||
mStableId.Append(Name());
|
||||
mStableId.Append(Manufacturer());
|
||||
mStableId.Append(Version());
|
||||
nsContentUtils::AnonymizeId(mStableId, aOrigin,
|
||||
nsContentUtils::OriginFormat::Plain);
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -7,8 +7,8 @@
|
||||
#ifndef mozilla_dom_MIDIPortChild_h
|
||||
#define mozilla_dom_MIDIPortChild_h
|
||||
|
||||
#include "mozilla/dom/PMIDIPortChild.h"
|
||||
#include "mozilla/dom/MIDIPortInterface.h"
|
||||
#include "mozilla/dom/PMIDIPortChild.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
@ -33,6 +33,8 @@ class MIDIPortChild final : public PMIDIPortChild, public MIDIPortInterface {
|
||||
|
||||
MIDIPortChild(const MIDIPortInfo& aPortInfo, bool aSysexEnabled,
|
||||
MIDIPort* aPort);
|
||||
nsresult GenerateStableId(const nsACString& aOrigin);
|
||||
const nsString& StableId() { return mStableId; };
|
||||
// virtual void Shutdown() override;
|
||||
void SetActorAlive();
|
||||
|
||||
@ -43,6 +45,7 @@ class MIDIPortChild final : public PMIDIPortChild, public MIDIPortInterface {
|
||||
// Pointer to the DOM object this actor represents. The actor cannot outlive
|
||||
// the DOM object.
|
||||
MIDIPort* mDOMPort;
|
||||
nsString mStableId;
|
||||
bool mActorWasAlive;
|
||||
};
|
||||
} // namespace mozilla::dom
|
||||
|
@ -73,3 +73,4 @@ LOCAL_INCLUDES += [
|
||||
]
|
||||
|
||||
MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]
|
||||
BROWSER_CHROME_MANIFESTS += ["tests/browser.ini"]
|
||||
|
@ -23,31 +23,41 @@ var MIDITestUtils = {
|
||||
// This list needs to stay synced with the ports in
|
||||
// dom/midi/TestMIDIPlatformService.
|
||||
inputInfo: {
|
||||
id: "b744eebe-f7d8-499b-872b-958f63c8f522",
|
||||
get id() {
|
||||
return MIDITestUtils.stableId(this);
|
||||
},
|
||||
name: "Test Control MIDI Device Input Port",
|
||||
manufacturer: "Test Manufacturer",
|
||||
version: "1.0.0",
|
||||
},
|
||||
outputInfo: {
|
||||
id: "ab8e7fe8-c4de-436a-a960-30898a7c9a3d",
|
||||
get id() {
|
||||
return MIDITestUtils.stableId(this);
|
||||
},
|
||||
name: "Test Control MIDI Device Output Port",
|
||||
manufacturer: "Test Manufacturer",
|
||||
version: "1.0.0",
|
||||
},
|
||||
stateTestInputInfo: {
|
||||
id: "a9329677-8588-4460-a091-9d4a7f629a48",
|
||||
get id() {
|
||||
return MIDITestUtils.stableId(this);
|
||||
},
|
||||
name: "Test State MIDI Device Input Port",
|
||||
manufacturer: "Test Manufacturer",
|
||||
version: "1.0.0",
|
||||
},
|
||||
stateTestOutputInfo: {
|
||||
id: "478fa225-b5fc-4fa6-a543-d32d9cb651e7",
|
||||
get id() {
|
||||
return MIDITestUtils.stableId(this);
|
||||
},
|
||||
name: "Test State MIDI Device Output Port",
|
||||
manufacturer: "Test Manufacturer",
|
||||
version: "1.0.0",
|
||||
},
|
||||
alwaysClosedTestOutputInfo: {
|
||||
id: "f87d0c76-3c68-49a9-a44f-700f1125c07a",
|
||||
get id() {
|
||||
return MIDITestUtils.stableId(this);
|
||||
},
|
||||
name: "Always Closed MIDI Device Output Port",
|
||||
manufacturer: "Test Manufacturer",
|
||||
version: "1.0.0",
|
||||
@ -60,4 +70,27 @@ var MIDITestUtils = {
|
||||
is(expected[i], actual[i], "Packet value " + expected[i] + " matches.");
|
||||
}
|
||||
},
|
||||
stableId: info => {
|
||||
// This computes the stable ID of a MIDI port according to the logic we
|
||||
// use in the Web MIDI implementation. See MIDIPortChild::GenerateStableId()
|
||||
// and nsContentUtils::AnonymizeId().
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
const id = info.name + info.manufacturer + info.version;
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(id);
|
||||
let key = self.origin;
|
||||
|
||||
var digest;
|
||||
let keyObject = Cc["@mozilla.org/security/keyobjectfactory;1"]
|
||||
.getService(Ci.nsIKeyObjectFactory)
|
||||
.keyFromString(Ci.nsIKeyObject.HMAC, key);
|
||||
let cryptoHMAC = Cc["@mozilla.org/security/hmac;1"].createInstance(
|
||||
Ci.nsICryptoHMAC
|
||||
);
|
||||
|
||||
cryptoHMAC.init(Ci.nsICryptoHMAC.SHA256, keyObject);
|
||||
cryptoHMAC.update(data, data.length);
|
||||
return cryptoHMAC.finish(true);
|
||||
},
|
||||
};
|
||||
|
12
dom/midi/tests/browser.ini
Normal file
12
dom/midi/tests/browser.ini
Normal file
@ -0,0 +1,12 @@
|
||||
[DEFAULT]
|
||||
prefs =
|
||||
dom.webmidi.enabled=true
|
||||
midi.testing=true
|
||||
midi.prompt.testing=true
|
||||
media.navigator.permission.disabled=true
|
||||
|
||||
[browser_stable_midi_port_ids.js]
|
||||
run-if = (os != 'android')
|
||||
support-files =
|
||||
port_ids_page_1.html
|
||||
port_ids_page_2.html
|
52
dom/midi/tests/browser_stable_midi_port_ids.js
Normal file
52
dom/midi/tests/browser_stable_midi_port_ids.js
Normal file
@ -0,0 +1,52 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const EXAMPLE_COM_URL = "https://example.com/browser/dom/midi/tests/";
|
||||
const EXAMPLE_ORG_URL = "https://example.org/browser/dom/midi/tests/";
|
||||
const PAGE1 = "port_ids_page_1.html";
|
||||
const PAGE2 = "port_ids_page_2.html";
|
||||
|
||||
// Return the MIDI port id of the first input port for the given URL and page
|
||||
function id_for_tab(url, page) {
|
||||
return BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: url + page,
|
||||
waitForLoad: true,
|
||||
},
|
||||
async function(browser) {
|
||||
return SpecialPowers.spawn(browser, [""], function() {
|
||||
return content.wrappedJSObject.get_first_input_id();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
let com_page1;
|
||||
let com_page1_reload;
|
||||
let org_page1;
|
||||
let org_page2;
|
||||
|
||||
[com_page1, com_page1_reload, org_page1, org_page2] = await Promise.all([
|
||||
id_for_tab(EXAMPLE_COM_URL, PAGE1),
|
||||
id_for_tab(EXAMPLE_COM_URL, PAGE1),
|
||||
id_for_tab(EXAMPLE_ORG_URL, PAGE1),
|
||||
id_for_tab(EXAMPLE_ORG_URL, PAGE2),
|
||||
]);
|
||||
Assert.equal(
|
||||
com_page1,
|
||||
com_page1_reload,
|
||||
"MIDI port ids should be the same when reloading the same page"
|
||||
);
|
||||
Assert.notEqual(
|
||||
com_page1,
|
||||
org_page1,
|
||||
"MIDI port ids should be different in different origins"
|
||||
);
|
||||
Assert.equal(
|
||||
org_page1,
|
||||
org_page2,
|
||||
"MIDI port ids should be the same in the same origin"
|
||||
);
|
||||
});
|
17
dom/midi/tests/port_ids_page_1.html
Normal file
17
dom/midi/tests/port_ids_page_1.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Stable MIDI port id test</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
async function get_first_input_id() {
|
||||
let access = await navigator.requestMIDIAccess({ sysex: false });
|
||||
const inputs = access.inputs.values();
|
||||
const input = inputs.next();
|
||||
return input.value.id;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
17
dom/midi/tests/port_ids_page_2.html
Normal file
17
dom/midi/tests/port_ids_page_2.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Stable MIDI port id test</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
async function get_first_input_id() {
|
||||
let access = await navigator.requestMIDIAccess({ sysex: false });
|
||||
const inputs = access.inputs.values();
|
||||
const input = inputs.next();
|
||||
return input.value.id;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -17,7 +17,6 @@
|
||||
const access = await navigator.requestMIDIAccess({ sysex: true });
|
||||
const output = access.outputs.get(MIDITestUtils.stateTestOutputInfo.id);
|
||||
|
||||
dump("output = " + JSON.stringify(output, null, " ") + "\n");
|
||||
|
||||
// Note on(off).
|
||||
output.send([0xff, 0x90, 0x00, 0x00, 0x90, 0x07, 0x00]);
|
||||
@ -25,7 +24,6 @@
|
||||
try {
|
||||
output.send([0x00, 0x01])
|
||||
} catch (ex) {
|
||||
dump("Caught exception");
|
||||
ok(true, "Caught exception");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user