Skip to content

Commit 5bbb1cb

Browse files
Bug 1758468 - Use stable, anonymous IDs for Web MIDI ports r=padenot
Differential Revision: https://phabricator.services.mozilla.com/D142550
1 parent cfcb4cf commit 5bbb1cb

13 files changed

+213
-28
lines changed

dom/midi/MIDIAccess.cpp

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@ void MIDIAccess::FireConnectionEvent(MIDIPort* aPort) {
8989
aPort->GetId(id);
9090
ErrorResult rv;
9191
if (aPort->State() == MIDIPortDeviceState::Disconnected) {
92-
if (aPort->Type() == MIDIPortType::Input &&
93-
MIDIInputMap_Binding::MaplikeHelpers::Has(mInputMap, id, rv)) {
94-
MIDIInputMap_Binding::MaplikeHelpers::Delete(mInputMap, id, rv);
95-
} else if (aPort->Type() == MIDIPortType::Output &&
96-
MIDIOutputMap_Binding::MaplikeHelpers::Has(mOutputMap, id, rv)) {
97-
MIDIOutputMap_Binding::MaplikeHelpers::Delete(mOutputMap, id, rv);
92+
if (aPort->Type() == MIDIPortType::Input && mInputMap->Has(id)) {
93+
MIDIInputMap_Binding::MaplikeHelpers::Delete(mInputMap, aPort->StableId(),
94+
rv);
95+
mInputMap->Remove(id);
96+
} else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
97+
MIDIOutputMap_Binding::MaplikeHelpers::Delete(mOutputMap,
98+
aPort->StableId(), rv);
99+
mOutputMap->Remove(id);
98100
}
99101
// Check to make sure Has()/Delete() calls haven't failed.
100102
if (NS_WARN_IF(rv.Failed())) {
@@ -106,31 +108,31 @@ void MIDIAccess::FireConnectionEvent(MIDIPort* aPort) {
106108
// this means a port that was disconnected has been reconnected, with the
107109
// port owner holding the object during that time, and we should add that
108110
// port object to our maps again.
109-
if (aPort->Type() == MIDIPortType::Input &&
110-
!MIDIInputMap_Binding::MaplikeHelpers::Has(mInputMap, id, rv)) {
111+
if (aPort->Type() == MIDIPortType::Input && !mInputMap->Has(id)) {
111112
if (NS_WARN_IF(rv.Failed())) {
112113
LOG("Input port not found");
113114
return;
114115
}
115116
MIDIInputMap_Binding::MaplikeHelpers::Set(
116-
mInputMap, id, *(static_cast<MIDIInput*>(aPort)), rv);
117+
mInputMap, aPort->StableId(), *(static_cast<MIDIInput*>(aPort)), rv);
117118
if (NS_WARN_IF(rv.Failed())) {
118119
LOG("Map Set failed for input port");
119120
return;
120121
}
121-
} else if (aPort->Type() == MIDIPortType::Output &&
122-
!MIDIOutputMap_Binding::MaplikeHelpers::Has(mOutputMap, id,
123-
rv)) {
122+
mInputMap->Insert(id, aPort);
123+
} else if (aPort->Type() == MIDIPortType::Output && mOutputMap->Has(id)) {
124124
if (NS_WARN_IF(rv.Failed())) {
125125
LOG("Output port not found");
126126
return;
127127
}
128128
MIDIOutputMap_Binding::MaplikeHelpers::Set(
129-
mOutputMap, id, *(static_cast<MIDIOutput*>(aPort)), rv);
129+
mOutputMap, aPort->StableId(), *(static_cast<MIDIOutput*>(aPort)),
130+
rv);
130131
if (NS_WARN_IF(rv.Failed())) {
131132
LOG("Map set failed for output port");
132133
return;
133134
}
135+
mOutputMap->Insert(id, aPort);
134136
}
135137
}
136138
RefPtr<MIDIConnectionEvent> event =
@@ -144,9 +146,7 @@ void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo& aInfo,
144146
MIDIPortType type = static_cast<MIDIPortType>(aInfo.type());
145147
RefPtr<MIDIPort> port;
146148
if (type == MIDIPortType::Input) {
147-
bool hasPort =
148-
MIDIInputMap_Binding::MaplikeHelpers::Has(mInputMap, id, aRv);
149-
if (hasPort || NS_WARN_IF(aRv.Failed())) {
149+
if (mInputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
150150
// We already have the port in our map.
151151
return;
152152
}
@@ -157,15 +157,15 @@ void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo& aInfo,
157157
return;
158158
}
159159
MIDIInputMap_Binding::MaplikeHelpers::Set(
160-
mInputMap, id, *(static_cast<MIDIInput*>(port.get())), aRv);
160+
mInputMap, port->StableId(), *(static_cast<MIDIInput*>(port.get())),
161+
aRv);
161162
if (NS_WARN_IF(aRv.Failed())) {
162163
LOG("Coudld't set input port in map");
163164
return;
164165
}
166+
mInputMap->Insert(id, port);
165167
} else if (type == MIDIPortType::Output) {
166-
bool hasPort =
167-
MIDIOutputMap_Binding::MaplikeHelpers::Has(mOutputMap, id, aRv);
168-
if (hasPort || NS_WARN_IF(aRv.Failed())) {
168+
if (mOutputMap->Has(id) || NS_WARN_IF(aRv.Failed())) {
169169
// We already have the port in our map.
170170
return;
171171
}
@@ -176,11 +176,13 @@ void MIDIAccess::MaybeCreateMIDIPort(const MIDIPortInfo& aInfo,
176176
return;
177177
}
178178
MIDIOutputMap_Binding::MaplikeHelpers::Set(
179-
mOutputMap, id, *(static_cast<MIDIOutput*>(port.get())), aRv);
179+
mOutputMap, port->StableId(), *(static_cast<MIDIOutput*>(port.get())),
180+
aRv);
180181
if (NS_WARN_IF(aRv.Failed())) {
181182
LOG("Coudld't set output port in map");
182183
return;
183184
}
185+
mOutputMap->Insert(id, port);
184186
} else {
185187
// If we hit this, then we have some port that is neither input nor output.
186188
// That is bad.

dom/midi/MIDIInputMap.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
#ifndef mozilla_dom_MIDIInputMap_h
88
#define mozilla_dom_MIDIInputMap_h
99

10+
#include "mozilla/dom/MIDIPort.h"
1011
#include "nsCOMPtr.h"
12+
#include "nsTHashMap.h"
1113
#include "nsWrapperCache.h"
1214

1315
class nsPIDOMWindowInner;
@@ -27,9 +29,15 @@ class MIDIInputMap final : public nsISupports, public nsWrapperCache {
2729
explicit MIDIInputMap(nsPIDOMWindowInner* aParent);
2830
JSObject* WrapObject(JSContext* aCx,
2931
JS::Handle<JSObject*> aGivenProto) override;
32+
bool Has(nsAString& aId) { return mPorts.Get(aId) != nullptr; }
33+
void Insert(nsAString& aId, RefPtr<MIDIPort> aPort) {
34+
mPorts.InsertOrUpdate(aId, aPort);
35+
}
36+
void Remove(nsAString& aId) { mPorts.Remove(aId); }
3037

3138
private:
3239
~MIDIInputMap() = default;
40+
nsTHashMap<nsString, RefPtr<MIDIPort>> mPorts;
3341
nsCOMPtr<nsPIDOMWindowInner> mParent;
3442
};
3543

dom/midi/MIDIOutputMap.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
#ifndef mozilla_dom_MIDIOutputMap_h
88
#define mozilla_dom_MIDIOutputMap_h
99

10+
#include "mozilla/dom/MIDIPort.h"
1011
#include "nsCOMPtr.h"
12+
#include "nsTHashMap.h"
1113
#include "nsWrapperCache.h"
1214

1315
class nsPIDOMWindowInner;
@@ -30,9 +32,15 @@ class MIDIOutputMap final : public nsISupports, public nsWrapperCache {
3032

3133
JSObject* WrapObject(JSContext* aCx,
3234
JS::Handle<JSObject*> aGivenProto) override;
35+
bool Has(nsAString& aId) { return mPorts.Get(aId) != nullptr; }
36+
void Insert(nsAString& aId, RefPtr<MIDIPort> aPort) {
37+
mPorts.InsertOrUpdate(aId, aPort);
38+
}
39+
void Remove(nsAString& aId) { mPorts.Remove(aId); }
3340

3441
private:
3542
~MIDIOutputMap() = default;
43+
nsTHashMap<nsString, RefPtr<MIDIPort>> mPorts;
3644
nsCOMPtr<nsPIDOMWindowInner> mParent;
3745
};
3846

dom/midi/MIDIPort.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "mozilla/dom/Document.h"
1515
#include "mozilla/dom/Promise.h"
1616
#include "mozilla/Unused.h"
17+
#include "nsContentUtils.h"
1718
#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, MOZ_COUNT_DTOR
1819
#include "MIDILog.h"
1920

@@ -63,8 +64,17 @@ MIDIPort::~MIDIPort() {
6364
}
6465

6566
bool MIDIPort::Initialize(const MIDIPortInfo& aPortInfo, bool aSysexEnabled) {
67+
nsIURI* uri = GetDocumentIfCurrent()->GetDocumentURI();
68+
nsAutoCString origin;
69+
nsresult rv = nsContentUtils::GetASCIIOrigin(uri, origin);
70+
if (NS_FAILED(rv)) {
71+
return false;
72+
}
6673
RefPtr<MIDIPortChild> port =
6774
new MIDIPortChild(aPortInfo, aSysexEnabled, this);
75+
if (NS_FAILED(port->GenerateStableId(origin))) {
76+
return false;
77+
}
6878
PBackgroundChild* b = BackgroundChild::GetForCurrentThread();
6979
MOZ_ASSERT(b,
7080
"Should always have a valid BackgroundChild when creating a port "
@@ -91,7 +101,7 @@ void MIDIPort::UnsetIPCPort() {
91101

92102
void MIDIPort::GetId(nsString& aRetVal) const {
93103
MOZ_ASSERT(mPort);
94-
aRetVal = mPort->MIDIPortInterface::Id();
104+
aRetVal = mPort->StableId();
95105
}
96106

97107
void MIDIPort::GetManufacturer(nsString& aRetVal) const {
@@ -252,4 +262,6 @@ void MIDIPort::DontKeepAliveOnStatechange() {
252262
}
253263
}
254264

265+
const nsString& MIDIPort::StableId() { return mPort->StableId(); }
266+
255267
} // namespace mozilla::dom

dom/midi/MIDIPort.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class MIDIPort : public DOMEventTargetHelper,
7373
IMPL_EVENT_HANDLER(statechange)
7474

7575
void DisconnectFromOwner() override;
76+
const nsString& StableId();
7677

7778
protected:
7879
// IPC Actor corresponding to this class

dom/midi/MIDIPortChild.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "mozilla/dom/MIDIPortChild.h"
88
#include "mozilla/dom/MIDIPort.h"
99
#include "mozilla/dom/MIDIPortInterface.h"
10+
#include "nsContentUtils.h"
1011

1112
using namespace mozilla;
1213
using namespace mozilla::dom;
@@ -55,3 +56,19 @@ void MIDIPortChild::SetActorAlive() {
5556
mActorWasAlive = true;
5657
AddRef();
5758
}
59+
60+
nsresult MIDIPortChild::GenerateStableId(const nsACString& aOrigin) {
61+
const size_t kIdLength = 64;
62+
mStableId.SetCapacity(kIdLength);
63+
mStableId.Append(Name());
64+
mStableId.Append(Manufacturer());
65+
mStableId.Append(Version());
66+
// Extend to at least 64 characters
67+
while (mStableId.Length() < kIdLength) {
68+
mStableId.Append(' ');
69+
}
70+
nsContentUtils::AnonymizeId(mStableId, aOrigin,
71+
nsContentUtils::OriginFormat::Plain);
72+
mStableId.Truncate(kIdLength);
73+
return NS_OK;
74+
}

dom/midi/MIDIPortChild.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
#ifndef mozilla_dom_MIDIPortChild_h
88
#define mozilla_dom_MIDIPortChild_h
99

10-
#include "mozilla/dom/PMIDIPortChild.h"
1110
#include "mozilla/dom/MIDIPortInterface.h"
11+
#include "mozilla/dom/PMIDIPortChild.h"
1212

1313
namespace mozilla::dom {
1414

@@ -33,6 +33,8 @@ class MIDIPortChild final : public PMIDIPortChild, public MIDIPortInterface {
3333

3434
MIDIPortChild(const MIDIPortInfo& aPortInfo, bool aSysexEnabled,
3535
MIDIPort* aPort);
36+
nsresult GenerateStableId(const nsACString& aOrigin);
37+
const nsString& StableId() { return mStableId; };
3638
// virtual void Shutdown() override;
3739
void SetActorAlive();
3840

@@ -43,6 +45,7 @@ class MIDIPortChild final : public PMIDIPortChild, public MIDIPortInterface {
4345
// Pointer to the DOM object this actor represents. The actor cannot outlive
4446
// the DOM object.
4547
MIDIPort* mDOMPort;
48+
nsString mStableId;
4649
bool mActorWasAlive;
4750
};
4851
} // namespace mozilla::dom

dom/midi/moz.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,4 @@ LOCAL_INCLUDES += [
7373
]
7474

7575
MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]
76+
BROWSER_CHROME_MANIFESTS += ["tests/browser.ini"]

dom/midi/tests/MIDITestUtils.js

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,31 +23,41 @@ var MIDITestUtils = {
2323
// This list needs to stay synced with the ports in
2424
// dom/midi/TestMIDIPlatformService.
2525
inputInfo: {
26-
id: "b744eebe-f7d8-499b-872b-958f63c8f522",
26+
get id() {
27+
return MIDITestUtils.stableId(this);
28+
},
2729
name: "Test Control MIDI Device Input Port",
2830
manufacturer: "Test Manufacturer",
2931
version: "1.0.0",
3032
},
3133
outputInfo: {
32-
id: "ab8e7fe8-c4de-436a-a960-30898a7c9a3d",
34+
get id() {
35+
return MIDITestUtils.stableId(this);
36+
},
3337
name: "Test Control MIDI Device Output Port",
3438
manufacturer: "Test Manufacturer",
3539
version: "1.0.0",
3640
},
3741
stateTestInputInfo: {
38-
id: "a9329677-8588-4460-a091-9d4a7f629a48",
42+
get id() {
43+
return MIDITestUtils.stableId(this);
44+
},
3945
name: "Test State MIDI Device Input Port",
4046
manufacturer: "Test Manufacturer",
4147
version: "1.0.0",
4248
},
4349
stateTestOutputInfo: {
44-
id: "478fa225-b5fc-4fa6-a543-d32d9cb651e7",
50+
get id() {
51+
return MIDITestUtils.stableId(this);
52+
},
4553
name: "Test State MIDI Device Output Port",
4654
manufacturer: "Test Manufacturer",
4755
version: "1.0.0",
4856
},
4957
alwaysClosedTestOutputInfo: {
50-
id: "f87d0c76-3c68-49a9-a44f-700f1125c07a",
58+
get id() {
59+
return MIDITestUtils.stableId(this);
60+
},
5161
name: "Always Closed MIDI Device Output Port",
5262
manufacturer: "Test Manufacturer",
5363
version: "1.0.0",
@@ -60,4 +70,29 @@ var MIDITestUtils = {
6070
is(expected[i], actual[i], "Packet value " + expected[i] + " matches.");
6171
}
6272
},
73+
stableId: info => {
74+
// This computes the stable ID of a MIDI port according to the logic we
75+
// use in the Web MIDI implementation. See MIDIPortChild::GenerateStableId()
76+
// and nsContentUtils::AnonymizeId().
77+
const Cc = SpecialPowers.Cc;
78+
const Ci = SpecialPowers.Ci;
79+
const id = info.name + info.manufacturer + info.version;
80+
const encoder = new TextEncoder();
81+
const data = encoder.encode(id.padEnd(64));
82+
let key = self.origin;
83+
84+
var digest;
85+
let keyObject = Cc["@mozilla.org/security/keyobjectfactory;1"]
86+
.getService(Ci.nsIKeyObjectFactory)
87+
.keyFromString(Ci.nsIKeyObject.HMAC, key);
88+
let cryptoHMAC = Cc["@mozilla.org/security/hmac;1"].createInstance(
89+
Ci.nsICryptoHMAC
90+
);
91+
92+
cryptoHMAC.init(Ci.nsICryptoHMAC.SHA256, keyObject);
93+
cryptoHMAC.update(data, data.length);
94+
digest = cryptoHMAC.finish(true);
95+
96+
return digest.slice(0, 64);
97+
},
6398
};

dom/midi/tests/browser.ini

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[DEFAULT]
2+
prefs =
3+
dom.webmidi.enabled=true
4+
midi.testing=true
5+
midi.prompt.testing=true
6+
media.navigator.permission.disabled=true
7+
8+
[browser_stable_midi_port_ids.js]
9+
run-if = (os != 'android')
10+
support-files =
11+
port_ids_page_1.html
12+
port_ids_page_2.html

0 commit comments

Comments
 (0)