Skip to content

Commit bb7271a

Browse files
committed
Fix our WebIDL for Safari
This commit employs the strategy described in #908 to apply a non-breaking change to fix WebIDL to be compatible with all browsers, including Safari. The problem here is that `BaseAudioContext` and `AudioScheduledSourceNode` are not types in Safari, but they are types in Firefox/Chrome. The fix here was to move the contents of these two interfaces into mixins, and then include the mixins in all classes which inherit from these two classes. That should have the same effect as defining the methods inherently on the original interface. Additionally a special `[RustDeprecated]` attribute to WebIDL was added to signify interfaces this has happened to. Currently it's directly tailored towards this case of "this intermediate class doesn't exist in all browsers", but we may want to refine and extend the deprecation message over time. Although it's possible we could do this as a breaking change to `web-sys` I'm hoping that we can do this as a non-breaking change for now and then eventually on the next breaking release batch all these changes together, deleting the intermediate classes. This is also hopefully a good trial run for how stable web-sys can be when it's actually stable! cc #897 cc #908
1 parent 473258f commit bb7271a

13 files changed

+90
-48
lines changed

crates/web-sys/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! require.
1313
1414
#![doc(html_root_url = "https://docs.rs/web-sys/0.2")]
15+
#![allow(deprecated)]
1516

1617
extern crate js_sys;
1718
extern crate wasm_bindgen;

crates/web-sys/webidls/enabled/AudioBufferSourceNode.webidl

+5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ interface AudioBufferSourceNode : AudioScheduledSourceNode {
3232
attribute double loopStart;
3333
attribute double loopEnd;
3434

35+
attribute EventHandler onended;
36+
3537
[Throws]
3638
void start(optional double when = 0, optional double grainOffset = 0,
3739
optional double grainDuration);
40+
41+
[Throws]
42+
void stop (optional double when = 0);
3843
};

crates/web-sys/webidls/enabled/AudioContext.webidl

+2
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ interface AudioContext : BaseAudioContext {
3737
[NewObject, Throws]
3838
MediaStreamAudioDestinationNode createMediaStreamDestination();
3939
};
40+
41+
AudioContext includes rustBaseAudioContext;

crates/web-sys/webidls/enabled/AudioScheduledSourceNode.webidl

+6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010
* liability, trademark and document use rules apply.
1111
*/
1212

13+
[RustDeprecated="doesn't exist in Safari, use parent class methods instead"]
1314
interface AudioScheduledSourceNode : AudioNode {
15+
};
16+
17+
AudioScheduledSourceNode includes rustAudioScheduledSourceNode;
18+
19+
interface mixin rustAudioScheduledSourceNode {
1420
attribute EventHandler onended;
1521
[Throws]
1622
void start (optional double when = 0);

crates/web-sys/webidls/enabled/BaseAudioContext.webidl

+6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ enum AudioContextState {
1919
"closed"
2020
};
2121

22+
[RustDeprecated="doesn't exist in Safari, use `AudioContext` instead now"]
2223
interface BaseAudioContext : EventTarget {
24+
};
25+
26+
BaseAudioContext includes rustBaseAudioContext;
27+
28+
interface mixin rustBaseAudioContext {
2329
readonly attribute AudioDestinationNode destination;
2430
readonly attribute float sampleRate;
2531
readonly attribute double currentTime;

crates/web-sys/webidls/enabled/ConstantSourceNode.webidl

+2
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ dictionary ConstantSourceOptions {
1919
interface ConstantSourceNode : AudioScheduledSourceNode {
2020
readonly attribute AudioParam offset;
2121
};
22+
23+
ConstantSourceNode includes rustAudioScheduledSourceNode;

crates/web-sys/webidls/enabled/OfflineAudioContext.webidl

+2
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ interface OfflineAudioContext : BaseAudioContext {
2929
readonly attribute unsigned long length;
3030
attribute EventHandler oncomplete;
3131
};
32+
33+
OfflineAudioContext includes rustBaseAudioContext;

crates/web-sys/webidls/enabled/OscillatorNode.webidl

+2
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ interface OscillatorNode : AudioScheduledSourceNode {
3737

3838
void setPeriodicWave(PeriodicWave periodicWave);
3939
};
40+
41+
OscillatorNode includes rustAudioScheduledSourceNode;

crates/webidl/src/first_pass.rs

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub(crate) struct FirstPassRecord<'src> {
4444
pub(crate) struct InterfaceData<'src> {
4545
/// Whether only partial interfaces were encountered
4646
pub(crate) partial: bool,
47+
pub(crate) deprecated: Option<String>,
4748
pub(crate) attributes: Vec<&'src AttributeInterfaceMember<'src>>,
4849
pub(crate) consts: Vec<&'src ConstMember<'src>>,
4950
pub(crate) operations: BTreeMap<OperationId<'src>, OperationData<'src>>,
@@ -311,6 +312,8 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> {
311312
interface_data.partial = false;
312313
interface_data.superclass = self.inheritance.map(|s| s.identifier.0);
313314
interface_data.definition_attributes = self.attributes.as_ref();
315+
interface_data.deprecated = util::get_rust_deprecated(&self.attributes)
316+
.map(|s| s.to_string());
314317
}
315318
if let Some(attrs) = &self.attributes {
316319
for attr in attrs.body.list.iter() {

crates/webidl/src/lib.rs

+18-8
Original file line numberDiff line numberDiff line change
@@ -488,18 +488,15 @@ impl<'src> FirstPassRecord<'src> {
488488
data: &InterfaceData<'src>,
489489
) {
490490
let mut doc_comment = Some(format!("The `{}` object\n\n{}", name, mdn_doc(name, None),));
491-
let derive = syn::Attribute {
492-
pound_token: Default::default(),
493-
style: syn::AttrStyle::Outer,
494-
bracket_token: Default::default(),
495-
path: Ident::new("derive", Span::call_site()).into(),
496-
tts: quote!((Debug, Clone)),
497-
};
491+
492+
let mut attrs = Vec::new();
493+
attrs.push(parse_quote!( #[derive(Debug, Clone)] ));
494+
self.add_deprecated(data, &mut attrs);
498495
let mut import_type = backend::ast::ImportType {
499496
vis: public(),
500497
rust_name: rust_ident(camel_case_ident(name).as_str()),
501498
js_name: name.to_string(),
502-
attrs: vec![derive],
499+
attrs,
503500
doc_comment: None,
504501
instanceof_shim: format!("__widl_instanceof_{}", name),
505502
extends: Vec::new(),
@@ -539,6 +536,7 @@ impl<'src> FirstPassRecord<'src> {
539536
self.member_attribute(
540537
program,
541538
name,
539+
data,
542540
member.modifier,
543541
member.readonly.is_some(),
544542
&member.type_,
@@ -559,6 +557,7 @@ impl<'src> FirstPassRecord<'src> {
559557
self.member_attribute(
560558
program,
561559
name,
560+
data,
562561
if let Some(s) = member.stringifier {
563562
Some(weedle::interface::StringifierOrInheritOrStatic::Stringifier(s))
564563
} else {
@@ -578,6 +577,7 @@ impl<'src> FirstPassRecord<'src> {
578577
&self,
579578
program: &mut backend::ast::Program,
580579
self_name: &'src str,
580+
data: &InterfaceData<'src>,
581581
modifier: Option<weedle::interface::StringifierOrInheritOrStatic>,
582582
readonly: bool,
583583
type_: &'src weedle::types::AttributedType<'src>,
@@ -620,6 +620,7 @@ impl<'src> FirstPassRecord<'src> {
620620
let mut doc = import_function.doc_comment.take();
621621
self.append_required_features_doc(&import_function, &mut doc, &[]);
622622
import_function.doc_comment = doc;
623+
self.add_deprecated(data, &mut import_function.function.rust_attrs);
623624
program.imports.push(wrap_import_function(import_function));
624625
}
625626
}
@@ -677,10 +678,19 @@ impl<'src> FirstPassRecord<'src> {
677678
let mut doc = doc.clone();
678679
self.append_required_features_doc(&method, &mut doc, &[]);
679680
method.doc_comment = doc;
681+
self.add_deprecated(data, &mut method.function.rust_attrs);
680682
program.imports.push(wrap_import_function(method));
681683
}
682684
}
683685

686+
fn add_deprecated(&self, data: &InterfaceData<'src>, dst: &mut Vec<syn::Attribute>) {
687+
let msg = match &data.deprecated {
688+
Some(s) => s,
689+
None => return,
690+
};
691+
dst.push(parse_quote!( #[deprecated(note = #msg)] ));
692+
}
693+
684694
fn append_required_features_doc(
685695
&self,
686696
item: impl ImportedTypeReferences,

crates/webidl/src/util.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
77
use proc_macro2::{Ident, Span};
88
use syn;
99
use weedle;
10-
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList};
10+
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList, IdentifierOrString};
1111
use weedle::literal::{ConstValue, FloatLit, IntegerLit};
1212

1313
use first_pass::{FirstPassRecord, OperationData, OperationId, Signature};
@@ -666,6 +666,29 @@ pub fn is_no_interface_object(ext_attrs: &Option<ExtendedAttributeList>) -> bool
666666
has_named_attribute(ext_attrs.as_ref(), "NoInterfaceObject")
667667
}
668668

669+
pub fn get_rust_deprecated<'a>(ext_attrs: &Option<ExtendedAttributeList<'a>>)
670+
-> Option<&'a str>
671+
{
672+
ext_attrs.as_ref()?
673+
.body
674+
.list
675+
.iter()
676+
.filter_map(|attr| {
677+
match attr {
678+
ExtendedAttribute::Ident(id) => Some(id),
679+
_ => None,
680+
}
681+
})
682+
.filter_map(|ident| {
683+
match ident.rhs {
684+
IdentifierOrString::String(s) => Some(s),
685+
IdentifierOrString::Identifier(_) => None,
686+
}
687+
})
688+
.next()
689+
.map(|s| s.0)
690+
}
691+
669692
/// Whether a webidl object is marked as structural.
670693
pub fn is_structural(
671694
item_attrs: Option<&ExtendedAttributeList>,

examples/webaudio/Cargo.toml

-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ features = [
1616
'AudioDestinationNode',
1717
'AudioNode',
1818
'AudioParam',
19-
'AudioScheduledSourceNode',
20-
'BaseAudioContext',
2119
'GainNode',
2220
'OscillatorNode',
2321
'OscillatorType',

examples/webaudio/src/lib.rs

+19-37
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ extern crate wasm_bindgen;
22
extern crate web_sys;
33

44
use wasm_bindgen::prelude::*;
5-
use web_sys::{
6-
AudioContext, AudioNode, AudioScheduledSourceNode, BaseAudioContext, OscillatorType,
7-
};
5+
use web_sys::{AudioContext, AudioNode, OscillatorType};
86

97
/// Converts a midi note to frequency
108
///
@@ -45,22 +43,14 @@ impl Drop for FmOsc {
4543
#[wasm_bindgen]
4644
impl FmOsc {
4745
#[wasm_bindgen(constructor)]
48-
pub fn new() -> FmOsc {
49-
let ctx = web_sys::AudioContext::new().unwrap();
50-
let primary;
51-
let fm_osc;
52-
let gain;
53-
let fm_gain;
46+
pub fn new() -> Result<FmOsc, JsValue> {
47+
let ctx = web_sys::AudioContext::new()?;
5448

55-
{
56-
let base: &BaseAudioContext = ctx.as_ref();
57-
58-
// Create our web audio objects.
59-
primary = base.create_oscillator().unwrap();
60-
fm_osc = base.create_oscillator().unwrap();
61-
gain = base.create_gain().unwrap();
62-
fm_gain = base.create_gain().unwrap();
63-
}
49+
// Create our web audio objects.
50+
let primary = ctx.create_oscillator()?;
51+
let fm_osc = ctx.create_oscillator()?;
52+
let gain = ctx.create_gain()?;
53+
let fm_gain = ctx.create_gain()?;
6454

6555
// Some initial settings:
6656
primary.set_type(OscillatorType::Sine);
@@ -76,50 +66,42 @@ impl FmOsc {
7666
let gain_node: &AudioNode = gain.as_ref();
7767
let fm_osc_node: &AudioNode = fm_osc.as_ref();
7868
let fm_gain_node: &AudioNode = fm_gain.as_ref();
79-
let base: &BaseAudioContext = ctx.as_ref();
80-
let destination = base.destination();
69+
let destination = ctx.destination();
8170
let destination_node: &AudioNode = destination.as_ref();
8271

8372
// Connect the nodes up!
8473

8574
// The primary oscillator is routed through the gain node, so that
8675
// it can control the overall output volume.
87-
primary_node.connect_with_audio_node(gain.as_ref()).unwrap();
76+
primary_node.connect_with_audio_node(gain.as_ref())?;
8877

8978
// Then connect the gain node to the AudioContext destination (aka
9079
// your speakers).
91-
gain_node.connect_with_audio_node(destination_node).unwrap();
80+
gain_node.connect_with_audio_node(destination_node)?;
9281

9382
// The FM oscillator is connected to its own gain node, so it can
9483
// control the amount of modulation.
95-
fm_osc_node
96-
.connect_with_audio_node(fm_gain.as_ref())
97-
.unwrap();
84+
fm_osc_node.connect_with_audio_node(fm_gain.as_ref())?;
85+
9886

9987
// Connect the FM oscillator to the frequency parameter of the main
10088
// oscillator, so that the FM node can modulate its frequency.
101-
fm_gain_node
102-
.connect_with_audio_param(&primary.frequency())
103-
.unwrap();
89+
fm_gain_node.connect_with_audio_param(&primary.frequency())?;
10490
}
10591

10692
// Start the oscillators!
107-
AsRef::<AudioScheduledSourceNode>::as_ref(&primary)
108-
.start()
109-
.unwrap();
110-
AsRef::<AudioScheduledSourceNode>::as_ref(&fm_osc)
111-
.start()
112-
.unwrap();
113-
114-
FmOsc {
93+
primary.start()?;
94+
fm_osc.start()?;
95+
96+
Ok(FmOsc {
11597
ctx,
11698
primary,
11799
gain,
118100
fm_gain,
119101
fm_osc,
120102
fm_freq_ratio: 0.0,
121103
fm_gain_ratio: 0.0,
122-
}
104+
})
123105
}
124106

125107
/// Sets the gain for this oscillator, between 0.0 and 1.0.

0 commit comments

Comments
 (0)