Skip to content

Commit

Permalink
Fix our WebIDL for Safari
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alexcrichton committed Oct 1, 2018
1 parent 473258f commit bb7271a
Show file tree
Hide file tree
Showing 13 changed files with 90 additions and 48 deletions.
1 change: 1 addition & 0 deletions crates/web-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//! require.

#![doc(html_root_url = "https://docs.rs/web-sys/0.2")]
#![allow(deprecated)]

extern crate js_sys;
extern crate wasm_bindgen;
Expand Down
5 changes: 5 additions & 0 deletions crates/web-sys/webidls/enabled/AudioBufferSourceNode.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ interface AudioBufferSourceNode : AudioScheduledSourceNode {
attribute double loopStart;
attribute double loopEnd;

attribute EventHandler onended;

[Throws]
void start(optional double when = 0, optional double grainOffset = 0,
optional double grainDuration);

[Throws]
void stop (optional double when = 0);
};
2 changes: 2 additions & 0 deletions crates/web-sys/webidls/enabled/AudioContext.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ interface AudioContext : BaseAudioContext {
[NewObject, Throws]
MediaStreamAudioDestinationNode createMediaStreamDestination();
};

AudioContext includes rustBaseAudioContext;
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
* liability, trademark and document use rules apply.
*/

[RustDeprecated="doesn't exist in Safari, use parent class methods instead"]
interface AudioScheduledSourceNode : AudioNode {
};

AudioScheduledSourceNode includes rustAudioScheduledSourceNode;

interface mixin rustAudioScheduledSourceNode {
attribute EventHandler onended;
[Throws]
void start (optional double when = 0);
Expand Down
6 changes: 6 additions & 0 deletions crates/web-sys/webidls/enabled/BaseAudioContext.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ enum AudioContextState {
"closed"
};

[RustDeprecated="doesn't exist in Safari, use `AudioContext` instead now"]
interface BaseAudioContext : EventTarget {
};

BaseAudioContext includes rustBaseAudioContext;

interface mixin rustBaseAudioContext {
readonly attribute AudioDestinationNode destination;
readonly attribute float sampleRate;
readonly attribute double currentTime;
Expand Down
2 changes: 2 additions & 0 deletions crates/web-sys/webidls/enabled/ConstantSourceNode.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ dictionary ConstantSourceOptions {
interface ConstantSourceNode : AudioScheduledSourceNode {
readonly attribute AudioParam offset;
};

ConstantSourceNode includes rustAudioScheduledSourceNode;
2 changes: 2 additions & 0 deletions crates/web-sys/webidls/enabled/OfflineAudioContext.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ interface OfflineAudioContext : BaseAudioContext {
readonly attribute unsigned long length;
attribute EventHandler oncomplete;
};

OfflineAudioContext includes rustBaseAudioContext;
2 changes: 2 additions & 0 deletions crates/web-sys/webidls/enabled/OscillatorNode.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ interface OscillatorNode : AudioScheduledSourceNode {

void setPeriodicWave(PeriodicWave periodicWave);
};

OscillatorNode includes rustAudioScheduledSourceNode;
3 changes: 3 additions & 0 deletions crates/webidl/src/first_pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub(crate) struct FirstPassRecord<'src> {
pub(crate) struct InterfaceData<'src> {
/// Whether only partial interfaces were encountered
pub(crate) partial: bool,
pub(crate) deprecated: Option<String>,
pub(crate) attributes: Vec<&'src AttributeInterfaceMember<'src>>,
pub(crate) consts: Vec<&'src ConstMember<'src>>,
pub(crate) operations: BTreeMap<OperationId<'src>, OperationData<'src>>,
Expand Down Expand Up @@ -311,6 +312,8 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> {
interface_data.partial = false;
interface_data.superclass = self.inheritance.map(|s| s.identifier.0);
interface_data.definition_attributes = self.attributes.as_ref();
interface_data.deprecated = util::get_rust_deprecated(&self.attributes)
.map(|s| s.to_string());
}
if let Some(attrs) = &self.attributes {
for attr in attrs.body.list.iter() {
Expand Down
26 changes: 18 additions & 8 deletions crates/webidl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,18 +488,15 @@ impl<'src> FirstPassRecord<'src> {
data: &InterfaceData<'src>,
) {
let mut doc_comment = Some(format!("The `{}` object\n\n{}", name, mdn_doc(name, None),));
let derive = syn::Attribute {
pound_token: Default::default(),
style: syn::AttrStyle::Outer,
bracket_token: Default::default(),
path: Ident::new("derive", Span::call_site()).into(),
tts: quote!((Debug, Clone)),
};

let mut attrs = Vec::new();
attrs.push(parse_quote!( #[derive(Debug, Clone)] ));
self.add_deprecated(data, &mut attrs);
let mut import_type = backend::ast::ImportType {
vis: public(),
rust_name: rust_ident(camel_case_ident(name).as_str()),
js_name: name.to_string(),
attrs: vec![derive],
attrs,
doc_comment: None,
instanceof_shim: format!("__widl_instanceof_{}", name),
extends: Vec::new(),
Expand Down Expand Up @@ -539,6 +536,7 @@ impl<'src> FirstPassRecord<'src> {
self.member_attribute(
program,
name,
data,
member.modifier,
member.readonly.is_some(),
&member.type_,
Expand All @@ -559,6 +557,7 @@ impl<'src> FirstPassRecord<'src> {
self.member_attribute(
program,
name,
data,
if let Some(s) = member.stringifier {
Some(weedle::interface::StringifierOrInheritOrStatic::Stringifier(s))
} else {
Expand All @@ -578,6 +577,7 @@ impl<'src> FirstPassRecord<'src> {
&self,
program: &mut backend::ast::Program,
self_name: &'src str,
data: &InterfaceData<'src>,
modifier: Option<weedle::interface::StringifierOrInheritOrStatic>,
readonly: bool,
type_: &'src weedle::types::AttributedType<'src>,
Expand Down Expand Up @@ -620,6 +620,7 @@ impl<'src> FirstPassRecord<'src> {
let mut doc = import_function.doc_comment.take();
self.append_required_features_doc(&import_function, &mut doc, &[]);
import_function.doc_comment = doc;
self.add_deprecated(data, &mut import_function.function.rust_attrs);
program.imports.push(wrap_import_function(import_function));
}
}
Expand Down Expand Up @@ -677,10 +678,19 @@ impl<'src> FirstPassRecord<'src> {
let mut doc = doc.clone();
self.append_required_features_doc(&method, &mut doc, &[]);
method.doc_comment = doc;
self.add_deprecated(data, &mut method.function.rust_attrs);
program.imports.push(wrap_import_function(method));
}
}

fn add_deprecated(&self, data: &InterfaceData<'src>, dst: &mut Vec<syn::Attribute>) {
let msg = match &data.deprecated {
Some(s) => s,
None => return,
};
dst.push(parse_quote!( #[deprecated(note = #msg)] ));
}

fn append_required_features_doc(
&self,
item: impl ImportedTypeReferences,
Expand Down
25 changes: 24 additions & 1 deletion crates/webidl/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
use proc_macro2::{Ident, Span};
use syn;
use weedle;
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList};
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList, IdentifierOrString};
use weedle::literal::{ConstValue, FloatLit, IntegerLit};

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

pub fn get_rust_deprecated<'a>(ext_attrs: &Option<ExtendedAttributeList<'a>>)
-> Option<&'a str>
{
ext_attrs.as_ref()?
.body
.list
.iter()
.filter_map(|attr| {
match attr {
ExtendedAttribute::Ident(id) => Some(id),
_ => None,
}
})
.filter_map(|ident| {
match ident.rhs {
IdentifierOrString::String(s) => Some(s),
IdentifierOrString::Identifier(_) => None,
}
})
.next()
.map(|s| s.0)
}

/// Whether a webidl object is marked as structural.
pub fn is_structural(
item_attrs: Option<&ExtendedAttributeList>,
Expand Down
2 changes: 0 additions & 2 deletions examples/webaudio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ features = [
'AudioDestinationNode',
'AudioNode',
'AudioParam',
'AudioScheduledSourceNode',
'BaseAudioContext',
'GainNode',
'OscillatorNode',
'OscillatorType',
Expand Down
56 changes: 19 additions & 37 deletions examples/webaudio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ extern crate wasm_bindgen;
extern crate web_sys;

use wasm_bindgen::prelude::*;
use web_sys::{
AudioContext, AudioNode, AudioScheduledSourceNode, BaseAudioContext, OscillatorType,
};
use web_sys::{AudioContext, AudioNode, OscillatorType};

/// Converts a midi note to frequency
///
Expand Down Expand Up @@ -45,22 +43,14 @@ impl Drop for FmOsc {
#[wasm_bindgen]
impl FmOsc {
#[wasm_bindgen(constructor)]
pub fn new() -> FmOsc {
let ctx = web_sys::AudioContext::new().unwrap();
let primary;
let fm_osc;
let gain;
let fm_gain;
pub fn new() -> Result<FmOsc, JsValue> {
let ctx = web_sys::AudioContext::new()?;

{
let base: &BaseAudioContext = ctx.as_ref();

// Create our web audio objects.
primary = base.create_oscillator().unwrap();
fm_osc = base.create_oscillator().unwrap();
gain = base.create_gain().unwrap();
fm_gain = base.create_gain().unwrap();
}
// Create our web audio objects.
let primary = ctx.create_oscillator()?;
let fm_osc = ctx.create_oscillator()?;
let gain = ctx.create_gain()?;
let fm_gain = ctx.create_gain()?;

// Some initial settings:
primary.set_type(OscillatorType::Sine);
Expand All @@ -76,50 +66,42 @@ impl FmOsc {
let gain_node: &AudioNode = gain.as_ref();
let fm_osc_node: &AudioNode = fm_osc.as_ref();
let fm_gain_node: &AudioNode = fm_gain.as_ref();
let base: &BaseAudioContext = ctx.as_ref();
let destination = base.destination();
let destination = ctx.destination();
let destination_node: &AudioNode = destination.as_ref();

// Connect the nodes up!

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

// Then connect the gain node to the AudioContext destination (aka
// your speakers).
gain_node.connect_with_audio_node(destination_node).unwrap();
gain_node.connect_with_audio_node(destination_node)?;

// The FM oscillator is connected to its own gain node, so it can
// control the amount of modulation.
fm_osc_node
.connect_with_audio_node(fm_gain.as_ref())
.unwrap();
fm_osc_node.connect_with_audio_node(fm_gain.as_ref())?;


// Connect the FM oscillator to the frequency parameter of the main
// oscillator, so that the FM node can modulate its frequency.
fm_gain_node
.connect_with_audio_param(&primary.frequency())
.unwrap();
fm_gain_node.connect_with_audio_param(&primary.frequency())?;
}

// Start the oscillators!
AsRef::<AudioScheduledSourceNode>::as_ref(&primary)
.start()
.unwrap();
AsRef::<AudioScheduledSourceNode>::as_ref(&fm_osc)
.start()
.unwrap();

FmOsc {
primary.start()?;
fm_osc.start()?;

Ok(FmOsc {
ctx,
primary,
gain,
fm_gain,
fm_osc,
fm_freq_ratio: 0.0,
fm_gain_ratio: 0.0,
}
})
}

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

0 comments on commit bb7271a

Please sign in to comment.