-
Notifications
You must be signed in to change notification settings - Fork 188
/
scanning.bs
949 lines (875 loc) · 35 KB
/
scanning.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
<pre class='metadata'>
Title: Web Bluetooth Scanning
Repository: WebBluetoothCG/web-bluetooth
Status: CG-DRAFT
ED: https://webbluetoothcg.github.io/web-bluetooth/scanning.html
Shortname: web-bluetooth-scanning
Level: 1
Editor: See contributors on GitHub, , https://github.com/WebBluetoothCG/web-bluetooth/graphs/contributors
Abstract: This document describes an API to scan for nearby Bluetooth Low Energy devices in real time.
Group: web-bluetooth-cg
!Participate: <a href="https://www.w3.org/community/web-bluetooth/">Join the W3C Community Group</a>
!Participate: <a href="https://github.com/WebBluetoothCG/web-bluetooth">Fix the text through GitHub</a>
!Participate: <a href="mailto:[email protected]">[email protected]</a> (<a href="https://lists.w3.org/Archives/Public/public-web-bluetooth/" rel="discussion">archives</a>)
!Participate: <a href="irc://irc.w3.org:6665/#web-bluetooth">IRC: #web-bluetooth on W3C's IRC</a>
Markup Shorthands: css no, markdown yes
</pre>
<pre class=biblio>
{
"BLUETOOTH42": {
"href": "https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439",
"title": "BLUETOOTH SPECIFICATION Version 4.2",
"publisher": "Bluetooth SIG",
"date": "2 December 2014"
},
"BLUETOOTH-SUPPLEMENT6": {
"href": "https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=302735",
"title": "Supplement to the Bluetooth Core Specification Version 6",
"date": "14 July 2015",
"publisher": "Bluetooth SIG"
}
}
</pre>
<pre class="anchors">
spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/#
type: method
text: Array.prototype.map; url: sec-array.prototype.map
text: [[OwnPropertyKeys]]; for: Object; url: sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
type: interface
text: TypeError; url: sec-native-error-types-used-in-this-standard-typeerror
spec: WebIDL; urlPrefix: https://heycam.github.io/webidl/#
type: dfn
text: a copy of the bytes held; url: dfn-get-buffer-source-copy
spec: web-bluetooth; urlPrefix: index.html#
type: interface
text: BluetoothDevice
</pre>
<pre class="link-defaults">
spec: webidl
type: dfn
text: resolve
spec:web-bluetooth
type: dfn
text: read only arraybuffer
type: permission
text: "bluetooth"
</pre>
<style>
.argument-list { display: inline-block; vertical-align: top; }
/* Show self-links for various elements. This is incompatible with nearby floats. */
.note, .why, .example, .issue { overflow: inherit; }
</style>
<section class="non-normative">
<h2 id="introduction">Introduction</h2>
<em>This section is non-normative.</em>
<p>
<a href="https://www.bluetooth.com/what-is-bluetooth-technology/bluetooth-technology-basics/low-energy">Bluetooth Low Energy (BLE)</a>
allows devices to broadcast advertisements to nearby <a>observers</a>.
These advertisements can contain small amounts of data
of a variety of types defined in [[BLUETOOTH-SUPPLEMENT6]].
</p>
<p>
For example, a beacon might announce that it's next to a particular museum exhibit
and is advertising with 1mW of power,
which would let nearby observers know their approximate distance to that exhibit.
</p>
<p>
This specification extends [[web-bluetooth]] to allow websites to
receive these advertisements from nearby BLE devices,
with the user's permission.
</p>
<section>
<h3 id="introduction-examples">Examples</h3>
<div class="example" id="example-ibeacon">
<p>
To discover what iBeacons are nearby and measure their distance,
a website could use code like the following:
</p>
<pre highlight="js">
function recordNearbyBeacon(major, minor, pathLossVs1m) { ... }
navigator.bluetooth.<a idl for="Bluetooth" lt="requestLEScan()">requestLEScan</a>({
filters: [{manufacturerData: {0x004C: {dataPrefix: new Uint8Array([
0x02, 0x15, // iBeacon identifier.
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 // My beacon UUID.
])}}}],
keepRepeatedDevices: true
}).then(() => {
navigator.bluetooth.addEventListener('<a idl>advertisementreceived</a>', event => {
let appleData = event.manufacturerData.get(<a
href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers"
title="Apple, Inc.'s Company Identifier">0x004C</a>);
if (appleData.byteLength != 23) {
// Isn't an iBeacon.
return;
}
let major = appleData.getUint16(18, false);
let minor = appleData.getUint16(20, false);
let txPowerAt1m = -appleData.getInt8(22);
let pathLossVs1m = txPowerAt1m - event.rssi;
recordNearbyBeacon(major, minor, pathLossVs1m);
});
})
</pre>
</div>
</section>
</section>
<section class="non-normative">
<h2 id="privacy">Privacy considerations</h2>
<em>This section is non-normative.</em>
<p>
<a lt="active scanning">Actively scanning</a> for advertisements
broadcasts a <a>device address</a> of the scanning device.
If the UA's Bluetooth system supports the <a>privacy feature</a>,
this address rotates periodically,
which prevents remote radios from tracking the user's device.
However, if the UA's Bluetooth system does not support the <a>privacy feature</a>,
this address is a stable unique identifier that's difficult to change.
To mitigate this, UAs should either use <a>passive scanning</a>,
use the <a>privacy feature in an observer</a>,
or warn the user that nearby devices will learn the identity of their device.
</p>
<p>
The ambient advertisements in a user's area are unlikely to directly include GPS coordinates,
but are likely to contain unique identifiers
that could be manually correlated with particular physical locations
or with particular other people.
Given that, the user needs to give permission
before a website gets access to nearby advertisements.
</p>
<p>
If a user has already given a site permission to know their location,
it might be ok to implicitly grant access to BLE advertisements.
However, BLE advertisements give away
strictly less location information than full [[geolocation]] access,
so UAs should allow users to grant that intermediate level of access.
</p>
<p>
BLE advertisements are usually fully public,
since they're broadcast unencrypted on 2.4GHz radio waves.
However, it's possible that a user would
have a device broadcast private information in a radio-shielded room.
This is probably an inappropriate use for BLE advertisements,
but might argue for requiring explicit permission to scan,
rather than inferring it from having permission to get a geolocation.
</p>
</section>
<section class="non-normative">
<h2 id="security">Security considerations</h2>
<em>This section is non-normative.</em>
<p>
Because this API doesn't write anything,
there are few if any security implications.
A device in a shielded room might broadcast security-sensitive information,
but we don't have any actual attack scenarios for that.
</p>
</section>
<section>
<h2 id="scanning">Scanning for BLE advertisements</h2>
<pre class="idl">
dictionary BluetoothLEScanOptions {
sequence<BluetoothLEScanFilterInit> filters;
boolean keepRepeatedDevices = false;
boolean acceptAllAdvertisements = false;
};
partial interface Bluetooth {
[SecureContext]
Promise<BluetoothLEScan> requestLEScan(optional BluetoothLEScanOptions options = {});
};
</pre>
<div class="note" heading="{{Bluetooth/requestLEScan()}} summary">
<p>
<code>navigator.bluetooth.{{requestLEScan(options)}}</code>
starts scanning for BLE advertisements,
asking the user for permission if they haven't yet granted it.
</p>
<p>
Because this could show a prompt, it requires a <a>secure context</a>.
Additionally, UAs are likely to require a <a
href="https://html.spec.whatwg.org/#tracking-user-activation"> transient
user activation</a> on its [=relevant global object=] when
{{requestLEScan}} is called.
</p>
<p>
Advertising events that <a for="BluetoothLEScanFilter">match</a>
a {{BluetoothLEScanFilter}} in an active {{BluetoothLEScan}}
cause {{advertisementreceived}} events to be dispatched
to the sending {{BluetoothDevice}}.
A filter matches if the advertisement includes data equal to each present member.
Usually, you'll only include one member in each filter.
</p>
<p>
Normally scans will discard
the second and subsequent advertisements from a single device
to save power.
If you need to receive them,
set <dfn dict-member for="BluetoothLEScanOptions">keepRepeatedDevices</dfn> to `true`.
Note that setting {{BluetoothLEScanOptions/keepRepeatedDevices}} to `false`
doesn't guarantee you won't get redundant events;
it just allows the UA to save power by omitting them.
</p>
<p>
In the rare case that you want to receive every advertisement without filtering them,
use the <dfn dict-member for="BluetoothLEScanOptions">acceptAllAdvertisements</dfn> field.
</p>
</div>
<div algorithm>
<p>
The <code><dfn method for="Bluetooth">requestLEScan(|options|)</dfn></code> method,
when invoked, MUST return <a>a new promise</a> |promise|
and run the following steps <a>in parallel</a>:
</p>
<ol class="algorithm">
<li>
If [=this=]'s [=relevant global object=]'s [=associated Document=] is
not [=allowed to use=] the [=policy-controlled feature=] named
<code>"bluetooth"</code>, <a>reject</a> |promise| with a
{{SecurityError}} and abort these steps.
</li>
<li>
If <code>|options|.acceptAllAdvertisements</code> is `true`,
and <code>|options|.filters</code> is present,
<a>reject</a> |promise| with a {{TypeError}} and abort these steps.
Note: There's no need to include filters if all advertisements are being accepted.
</li>
<li>
If <code>|options|.acceptAllAdvertisements</code> is `false`,
and <code>|options|.filters</code> is either absent or empty,
<a>reject</a> |promise| with a {{TypeError}} and abort these steps.
Note: An empty set of filters wouldn't return any advertisements.
</li>
<li>
Let |filters| be
<code>{{Array.prototype.map}}.call(|options|.filters,
filter=>new {{BluetoothLEScanFilter/BluetoothLEScanFilter()|BluetoothLEScanFilter}}(filter))</code>
if <code>|options|.filters</code> is present, or an empty {{FrozenArray}} otherwise.
If this throws an exception,
<a>reject</a> |promise| with that exception and abort these steps.
</li>
<li id="scanning-permission">
<a>Request permission to use</a>
<pre highlight="js">
{
name: <a idl>"bluetooth-le-scan"</a>,
filters: <var>options</var>.filters,
keepRepeatedDevices: <var>options</var>.keepRepeatedDevices,
acceptAllAdvertisements: <var>options</var>.acceptAllAdvertisements,
}
</pre>
Note: This may require that this algorithm has a <a
href="https://html.spec.whatwg.org/#tracking-user-activation"> transient
activation</a> on its [=relevant global object=] when triggered.
</li>
<li>
If the result is "{{PermissionState/denied}}",
reject |promise| with a {{NotAllowedError}} and abort these steps.
</li>
<li>
Let |scan| be a new {{BluetoothLEScan}} instance
whose fields are initialized as in the following table:
<table class="data">
<thead><th>Field</th><th>Initial value</th></thead>
<tr><td>{{BluetoothLEScan/filters}}</td><td>|filters|</td></tr>
<tr>
<td>{{BluetoothLEScan/keepRepeatedDevices}}</td>
<td><code>|options|.keepRepeatedDevices</code></td>
</tr>
<tr>
<td>{{BluetoothLEScan/acceptAllAdvertisements}}</td>
<td><code>|options|.acceptAllAdvertisements</code></td>
</tr>
<tr><td>{{BluetoothLEScan/active}}</td><td>`true`</td></tr>
</table>
</li>
<li>
Add |scan| to <code>navigator.bluetooth.{{[[activeScans]]}}</code>.
</li>
<li>
Ensure the UA is scanning for BLE advertisements
in a mode that will receive at least
all advertisements <a for="BluetoothLEScan">matching</a> any scan
in any {{[[activeScans]]}} set in the whole UA.
<p class="issue" id="issue-limit-scan-periods">
Find wording that allows the UA to limit its scan to only certain periods of time,
to save power.
</p>
</li>
<li>
If the UA fails to start scanning,
remove |scan| from <code>navigator.bluetooth.{{[[activeScans]]}}</code>,
<a>reject</a> |promise| with one of the following errors,
and abort these steps:
<dl class="switch">
<dt>The UA doesn't support scanning for advertisements</dt>
<dd>{{NotSupportedError}}</dd>
<dt>Bluetooth is turned off</dt>
<dd>{{InvalidStateError}}</dd>
<dt>Other reasons</dt>
<dd>{{UnknownError}}</dd>
</dl>
</li>
<li>
<a>Resolve</a> |promise| with |scan|.
</li>
</ol>
</div>
<section>
<h3 id="scan-control">Controlling a BLE scan</h3>
<pre class="idl">
[Exposed=Window, SecureContext]
interface BluetoothDataFilter {
constructor(optional BluetoothDataFilterInit init = {});
readonly attribute ArrayBuffer dataPrefix;
readonly attribute ArrayBuffer mask;
};
[Exposed=Window, SecureContext]
interface BluetoothManufacturerDataFilter {
constructor(optional object init);
readonly maplike<unsigned short, BluetoothDataFilter>;
};
[Exposed=Window, SecureContext]
interface BluetoothServiceDataFilter {
constructor(optional object init);
readonly maplike<UUID, BluetoothDataFilter>;
};
[Exposed=Window, SecureContext]
interface BluetoothLEScanFilter {
constructor(optional BluetoothLEScanFilterInit init = {});
readonly attribute DOMString? name;
readonly attribute DOMString? namePrefix;
readonly attribute FrozenArray<UUID> services;
readonly attribute BluetoothManufacturerDataFilter manufacturerData;
readonly attribute BluetoothServiceDataFilter serviceData;
};
[Exposed=Window, SecureContext]
interface BluetoothLEScan {
readonly attribute FrozenArray<BluetoothLEScanFilter> filters;
readonly attribute boolean keepRepeatedDevices;
readonly attribute boolean acceptAllAdvertisements;
readonly attribute boolean active;
undefined stop();
};
</pre>
<div class="note" heading="{{BluetoothLEScan}} members">
<p>
{{BluetoothLEScan/stop()|BluetoothLEScan.stop()}}
stops a previously-requested scan.
Sites should do this as soon as possible to avoid wasting power.
</p>
</div>
<div algorithm>
<p>
The <dfn constructor for="BluetoothLEScanFilter">BluetoothLEScanFilter(|init|)</dfn>
constructor, when invoked MUST perform the following steps:
</p>
<ol class="algorithm">
<li>
Initialize all nullable fields to `null`.
</li>
<li>
If no member of |init| is present, throw a {{TypeError}}.
Note: A filter can't implicitly allow all advertisements.
Use {{BluetoothLEScanOptions/acceptAllAdvertisements}} to explicitly do it.
</li>
<li>
Initialize <code>this.{{BluetoothLEScanFilter/manufacturerData}}</code> as
<code>new BluetoothManufacturerDataFilter(|init|.{{BluetoothLEScanFilterInit/manufacturerData}})</code>.
</li>
<li>
Initialize <code>this.{{BluetoothLEScanFilter/serviceData}}</code> as
<code>new BluetoothServiceDataFilter(|init|.{{BluetoothLEScanFilterInit/serviceData}})</code>.
</li>
<li>
Initialize <code>this.{{BluetoothLEScanFilter/services}}</code> as
<code>{{Array.prototype.map}}.call(|init|.{{BluetoothLEScanFilterInit/services}},
service=>{{BluetoothUUID/getService()|BluetoothUUID.getService}}(service))</code>
if <code>|init|.{{BluetoothLEScanFilterInit/services}}</code> is present,
or an empty {{FrozenArray}} otherwise.
</li>
<li>
For each other present member in |init|,
set `this`'s attribute with a matching identifier to the value of the member.
</li>
<li>
If any of the above calls threw an exception,
propagate that exception from this constructor.
</li>
</ol>
</div>
<div algorithm>
<p>
The <dfn method for="BluetoothLEScan">stop()</dfn> method, when invoked,
MUST perform the following steps:
</p>
<ol class="algorithm">
<li>Set <code>this.{{BluetoothLEScan/active}}</code> to `false`.</li>
<li>Remove `this` from <code>navigator.bluetooth.{{[[activeScans]]}}</code>.</li>
<li>
The UA SHOULD reconfigure or stop its BLE scan to save power
while still receiving any advertisements that
<a for="BluetoothLEScan">match</a> any scan
in any {{[[activeScans]]}} set in the whole UA.
</li>
</ol>
</div>
<div algorithm>
<p>
The <dfn constructor for="BluetoothManufacturerDataFilter">BluetoothManufacturerDataFilter(|init|)</dfn>
constructor, when invoked, MUST perform the following steps:
</p>
<ol>
<li>
If |init| is not present
or <code>|init|.{{Object/[[OwnPropertyKeys]]}}()</code> is empty,
then `this` has no <a>map entries</a>.
Abort these steps.
</li>
<li>
Let |canonicalInit| be the `manufacturerData` field of the result of
<a for="BluetoothLEScanFilterInit">canonicalizing</a> <code>{manufacturerData: |init|}</code>.
If this throws an exception,
propagate that exception from this constructor and abort these steps.
</li>
<li>
`this`'s <a>map entries</a> map from each |key| in
<code>|canonicalInit|.{{Object/[[OwnPropertyKeys]]}}()</code>,
parsed as a base-10 integer,
to its value of <code>new {{BluetoothDataFilter}}(|canonicalInit|[|key|])</code>.
</li>
</ol>
</div>
<div algorithm>
<p>
The <dfn constructor for="BluetoothServiceDataFilter">BluetoothServiceDataFilter(|init|)</dfn>
constructor, when invoked, MUST perform the following steps:
</p>
<ol>
<li>
If |init| is not present
or <code>|init|.{{Object/[[OwnPropertyKeys]]}}()</code> is empty,
then `this` has no <a>map entries</a>.
Abort these steps.
</li>
<li>
Let |canonicalInit| be the `serviceData` field of the result of
<a for="BluetoothLEScanFilterInit">canonicalizing</a> <code>{serviceData: |init|}</code>.
If this throws an exception,
propagate that exception from this constructor and abort these steps.
</li>
<li>
`this`'s <a>map entries</a> map from each |key| in
<code>|canonicalInit|.{{Object/[[OwnPropertyKeys]]}}()</code>,
to its value of <code>new {{BluetoothDataFilter}}(|canonicalInit|[|key|])</code>.
</li>
</ol>
</div>
<div algorithm>
<p>
The <dfn constructor for="BluetoothDataFilter">BluetoothDataFilter(|init|)</dfn>
constructor, when invoked, MUST perform the following steps:
</p>
<ol>
<li>
Let |canonicalInit| be
the result of <a for="BluetoothDataFilterInit">canonicalizing</a> |init|.
If this throws an exception,
propagate that exception from this constructor and abort these steps.
</li>
<li>
Initialize <code>this.{{BluetoothDataFilter/dataPrefix}}</code> as
a <a>read only ArrayBuffer</a> whose contents are
<a>a copy of the bytes held</a> by
<code>|canonicalInit|.{{BluetoothDataFilterInit/dataPrefix}}</code>.
</li>
<li>
Initialize <code>this.{{BluetoothDataFilter/mask}}</code> as
a <a>read only ArrayBuffer</a> whose contents are
<a>a copy of the bytes held</a> by
<code>|canonicalInit|.{{BluetoothDataFilterInit/mask}}</code>.
</li>
</ol>
</div>
</section>
## Handling Document Loss of Full Activity ## {#handling-full-activity-loss}
Operations that initiate a scan for Bluetooth devices may only run in a [=fully
active=] [=document=]. When [=fully active|full activity=] is lost, scanning
operations for that [=document=] need to be aborted.
<div algorithm="handle full activity loss">
When the user agent determines that a <a>associated <code>Document</code></a> of
the [=current settings object=]'s [=relevant global object=] is no longer
[=fully active=], it must run these steps:
1. [=map/For each=] |activeScan| in <code>
navigator.bluetooth.{{[[activeScans]]}}</code>, perform the following
steps:
1. Call <code>|activeScan|.{{BluetoothLEScan/stop()}}</code>.
</div>
<section>
<h3 id="permission">Permission to scan</h3>
<p>
The <dfn enum-value for="PermissionName">"bluetooth-le-scan"</dfn>
<a>powerful feature</a>'s
permission-related algorithms and types
are defined as follows:
</p>
<dl>
<dt><a>permission descriptor type</a></dt>
<dd>
<pre class="idl">
dictionary BluetoothLEScanPermissionDescriptor : PermissionDescriptor {
// These match BluetoothLEScanOptions.
sequence<BluetoothLEScanFilterInit> filters;
boolean keepRepeatedDevices = false;
boolean acceptAllAdvertisements = false;
};
</pre>
</dd>
<dt><a>permission result type</a></dt>
<dd>
<pre class="idl">
[Exposed=Window, SecureContext]
interface BluetoothLEScanPermissionResult : PermissionStatus {
attribute FrozenArray<BluetoothLEScan> scans;
};
</pre>
</dd>
<dt><a>permission query algorithm</a></dt>
<dd algorithm="permission-query">
<p>
Given a {{BluetoothLEScanPermissionDescriptor}} |descriptor|
and a {{BluetoothLEScanPermissionResult}} |result|:
</p>
<ol class="algorithm">
<li>
Update <code>|result|.{{PermissionStatus/state}}</code>
to |descriptor|'s <a>permission state</a>.
</li>
<li>
If <code>|result|.{{PermissionStatus/state}}</code> is "{{PermissionState/denied}}",
set <code>|result|.scans</code> to an empty {{FrozenArray}}
and abort these steps.
</li>
<li>
Update <code>|result|.{{scans}}</code> to
a new {{FrozenArray}} containing
the elements of <code>navigator.bluetooth.{{[[activeScans]]}}</code>.
<p class="issue" id="issue-should-query-filter">
Consider filtering the result to active scans that
match the fields of the descriptor.
</p>
</li>
</ol>
</dd>
<dt>
<a>permission revocation algorithm</a>
</dt>
<dd algorithm="permission-revocation">
<ol class="algorithm">
<li>
For each |activeScan| in <code>navigator.bluetooth.{{[[activeScans]]}}</code>:
<ol>
<li>
If the <a>permission state</a> of
<pre highlight="js">
{
name: "bluetooth-le-scan",
filters: <var>activeScan</var>.filters,
keepRepeatedDevices: <var>activeScan</var>.keepRepeatedDevices
}
</pre>
is not "{{PermissionState/granted}}",
call <code>|activeScan|.{{stop()}}</code>.
</li>
</ol>
</li>
</ol>
</dd>
</dl>
</section>
</section>
<section>
<h2 id="events">Event handling</h2>
<section>
<h3 id="advertising-events">Responding to advertising events</h3>
<div algorithm="receive-advertising-event">
<p>
When the UA receives an <a>advertising event</a> |event|
(consisting of an advertising packet and an optional scan response),
it MUST run the following steps:
</p>
<ol class="algorithm">
<li>
Let <var>device</var> be the <a>Bluetooth device</a> that sent the advertising event.
</li>
<li>
For each {{Bluetooth}} instance |bluetooth| in the UA,
<a>queue a task</a> on
|bluetooth|'s <a>relevant settings object</a>'s <a>responsible event loop</a>
to do the following sub-steps:
<ol>
<li>
Let |scans| be the set of
{{BluetoothLEScan}}s in <code>|bluetooth|.{{[[activeScans]]}}</code>
that <a for="BluetoothLEScan">match</a> |event|.
</li>
<li>If |scans| is empty, abort these sub-steps.</li>
<li class="note">
Note: the user's <a href="#scanning-permission">permission to scan</a>
likely indicates that
they intend newly-discovered devices to appear in
<a permission>"bluetooth"</a>'s <a>extra permission data</a>,
but possibly with {{AllowedBluetoothDevice/mayUseGATT}} set to `false`.
</li>
<li>
<a>Get the `BluetoothDevice` representing</a> |device| inside |bluetooth|,
and let |deviceObj| be the result.
</li>
<li>
Add each {{BluetoothLEScan}} in |scans|
to <code>|deviceObj|.{{[[returnedFromScans]]}}</code>.
</li>
<li>
<a>Fire an `advertisementreceived` event</a>
for |event| at |deviceObj|.
</li>
</ol>
</li>
</ol>
</div>
<div algorithm="bluetoothlescan-match">
<p>
An advertising event |event|
<dfn for="BluetoothLEScan" lt="match">matches</dfn> a {{BluetoothLEScan}} |scan|
if the following steps return `match`:
</p>
<ol class="algorithm">
<li>
<code>|scan|.acceptAllAdvertisements</code> is `false` and
|event| doesn't <a for="BluetoothLEScanFilter">match</a>
any filter in <code>|scan|.{{BluetoothLEScan/filters}}</code>,
return `no match`.
</li>
<li>
If <code>|scan|.{{BluetoothLEScan/keepRepeatedDevices}}</code> is `false`,
there is a {{BluetoothDevice}} |device| that
represents the <a>same Bluetooth device</a> as the one that sent |event|,
and <code>|device|.{{[[returnedFromScans]]}}</code> includes |scan|,
the UA MAY return `no match`.
</li>
<li>Return `match`.</li>
</ol>
</div>
<div algorithm="bluetoothlescanfilter-match">
<p>
An advertising event |event|
<dfn for="BluetoothLEScanFilter" lt="match">matches</dfn>
a {{BluetoothLEScanFilter}} |filter|
if all of the following conditions hold:
</p>
<ul>
<li>
If <code>|filter|.name</code> is non-<code>null</code>,
|event| has a <a>Local Name</a> equal to <code>|filter|.name</code>.
Note: A <a>Shortened Local Name</a>
can match a {{BluetoothLEScanFilter/name}} filter.
</li>
<li>
If <code>|filter|.namePrefix</code> is non-<code>null</code>,
|event| has a <a>Local Name</a>,
and <code>|filter|.namePrefix</code> is a prefix of it.
</li>
<li>
For each |uuid| in <code>|filter|.services</code>,
some <a>Service UUID</a> in |event| is equal to |uuid|.
</li>
<li>
For each (|id|, |filter|) in <code>|filter|.manufacturerData</code>'s <a>map entries</a>,
some <a>Manufacturer Specific Data</a> in |event|
has a Company Identifier Code of |id|,
and whose array of bytes <a for="BluetoothDataFilterInit">matches</a> |filter|.
</li>
<li>
For each (|uuid|, |filter|) in
<code>|filter|.serviceData</code>'s <a>map entries</a>,
some <a>Service Data</a> in |event|
has a UUID whose 128-bit representation is |uuid|,
and whose array of bytes <a for="BluetoothDataFilterInit">matches</a> |filter|.
</li>
</ul>
</div>
</section>
</section>
<section>
<h2 id="changes">Changes to existing interfaces</h2>
<p>
Instances of {{Bluetooth}} additionally have the following internal slots:
</p>
<table class="data" dfn-for="Bluetooth" dfn-type="attribute">
<thead>
<th>Internal Slot</th>
<th>Initial Value</th>
<th>Description (non-normative)</th>
</thead>
<tr>
<td><dfn>\[[activeScans]]</dfn></td>
<td>
An empty set of {{BluetoothLEScan}} instances.
</td>
<td>
The contents of this set will have {{BluetoothLEScan/active}} equal to `true`.
</td>
</tr>
</table>
<p>
Instances of {{BluetoothDevice}} additionally have the following internal slots:
</p>
<table class="data" dfn-for="BluetoothDevice" dfn-type="attribute">
<thead>
<th>Internal Slot</th>
<th>Initial Value</th>
<th>Description (non-normative)</th>
</thead>
<tr>
<td><dfn>\[[returnedFromScans]]</dfn></td>
<td>
An empty set of {{BluetoothLEScan}} objects.
</td>
<td>
Used to implement {{BluetoothLEScanOptions/keepRepeatedDevices}}.
</td>
</tr>
</table>
</section>
<section>
<h2 id="terminology">Terminology and conventions</h2>
<p>
This specification uses a few conventions and several terms from other specifications.
This section lists those and links to their primary definitions.
</p>
<p>
When an algorithm in this specification uses a name defined in this or another specification,
the name MUST resolve to its initial value,
ignoring any changes that have been made to the name in the current execution environment.
For example, when the {{Bluetooth/requestLEScan()}} algorithm says to call
<code>{{Array.prototype.map}}.call(|options|.filters,
filter=>new {{BluetoothLEScanFilter/BluetoothLEScanFilter()|BluetoothLEScanFilter}}(filter))</code>,
this MUST apply the {{Array.prototype.map}} algorithm defined in [[ECMAScript]]
with <code>|options|.filters</code> as its <code>this</code> parameter and
<code>filter=>new {{BluetoothLEScanFilter/BluetoothLEScanFilter()|BluetoothLEScanFilter}}(filter)</code>
as its <code>callbackfn</code> parameter, regardless of any modifications that have
been made to <code>window</code>, <code>Array</code>,
<code>Array.prototype</code>, <code>Array.prototype.map</code>,
<code>Function</code>, <code>Function.prototype</code>,
<code>BluetoothLEScanFilter</code>, or other objects.
</p>
<dl>
<dt>[[!BLUETOOTH42]]</dt>
<dd>
<ol>
<li value="1">Architecture & Terminology Overview
<ol type="A">
<li value="1">General Description
<ol>
<li value="2">Overview of Bluetooth Low Energy Operation
(defines <dfn lt="advertising event|advertising events">advertising events</dfn>)
</li>
</ol>
</li>
</ol>
</li>
<li value="3">Core System Package [Host volume]
<ol type="A">
<li value="3">Generic Access Profile
<ol>
<li value="2">Profile Overview
<ol>
<li value="2">Profile Roles
<ol>
<li value="2">Roles when Operating over an LE Physical Transport
<ol>
<li value="2"><dfn>Observer</dfn> Role</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li value="10">Security Aspects — LE Physical Transport
<ol>
<li value="7"><dfn>Privacy Feature</dfn>
<ol>
<li value="4"><dfn>Privacy Feature in an Observer</dfn>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li value="6">Core System Package [Low Energy Controller volume]
<ol type="A">
<li value="2">Link Layer Specification
<ol>
<li value="1">General Description
<ol>
<li value="3"><dfn>Device Address</dfn></li>
</ol>
</li>
<li value="2">Air Interface Packets
<ol>
<li value="3">Advertising Channel PDU
<ol>
<li value="1">Advertising PDUs
<ol>
<li value="1">ADV_IND</li>
<li value="2">ADV_DIRECT_IND</li>
<li value="3">ADV_NONCONN_IND</li>
<li value="4">ADV_SCAN_IND</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li value="4">Air Interface Protocol
<ol>
<li value="4">Non-Connected States
<ol>
<li value="3">Scanning State
<ol>
<li value="1"><dfn>Passive Scanning</dfn></li>
<li value="2"><dfn>Active Scanning</dfn></li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</dd>
<dt>[[!BLUETOOTH-SUPPLEMENT6]]</dt>
<dd>
<ol type="A">
<li value="1">Data Types Specification
<ol>
<li value="1">Data Types Definitions and Formats
<ol>
<li value="1"><dfn>Service UUID</dfn></li>
<li value="1">
<dfn>Local Name</dfn>
also defines <dfn>Shortened Local Name</dfn>
and Complete Local Name
</li>
<li value="4"><dfn>Manufacturer Specific Data</dfn></li>
<li value="11"><dfn>Service Data</dfn></li>
</ol>
</li>
</ol>
</li>
</ol>
</dd>
</dl>
</section>