implement broadcastchannel

This commit is contained in:
Gregory Terzian 2020-02-19 00:48:17 +08:00
parent 145c89a2d4
commit eb21d5f738
32 changed files with 763 additions and 216 deletions

View file

@ -126,12 +126,12 @@ use log::{Level, LevelFilter, Log, Metadata, Record};
use media::{GLPlayerThreads, WindowGLContext}; use media::{GLPlayerThreads, WindowGLContext};
use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert, SamplerControlMsg}; use msg::constellation_msg::{BackgroundHangMonitorRegister, HangMonitorAlert, SamplerControlMsg};
use msg::constellation_msg::{ use msg::constellation_msg::{
BrowsingContextGroupId, BrowsingContextId, HistoryStateId, PipelineId, BroadcastChannelRouterId, MessagePortId, MessagePortRouterId, PipelineNamespace,
TopLevelBrowsingContextId, PipelineNamespaceId, PipelineNamespaceRequest, TraversalDirection,
}; };
use msg::constellation_msg::{ use msg::constellation_msg::{
MessagePortId, MessagePortRouterId, PipelineNamespace, PipelineNamespaceId, BrowsingContextGroupId, BrowsingContextId, HistoryStateId, PipelineId,
PipelineNamespaceRequest, TraversalDirection, TopLevelBrowsingContextId,
}; };
use net_traits::pub_domains::reg_host; use net_traits::pub_domains::reg_host;
use net_traits::request::RequestBuilder; use net_traits::request::RequestBuilder;
@ -142,7 +142,8 @@ use profile_traits::time;
use script_traits::CompositorEvent::{MouseButtonEvent, MouseMoveEvent}; use script_traits::CompositorEvent::{MouseButtonEvent, MouseMoveEvent};
use script_traits::{webdriver_msg, LogEntry, ScriptToConstellationChan, ServiceWorkerMsg}; use script_traits::{webdriver_msg, LogEntry, ScriptToConstellationChan, ServiceWorkerMsg};
use script_traits::{ use script_traits::{
AnimationState, AnimationTickType, AuxiliaryBrowsingContextLoadInfo, CompositorEvent, AnimationState, AnimationTickType, AuxiliaryBrowsingContextLoadInfo, BroadcastMsg,
CompositorEvent,
}; };
use script_traits::{ConstellationControlMsg, DiscardBrowsingContext}; use script_traits::{ConstellationControlMsg, DiscardBrowsingContext};
use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData, LoadOrigin}; use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData, LoadOrigin};
@ -399,6 +400,12 @@ pub struct Constellation<Message, LTF, STF> {
/// A map of router-id to ipc-sender, to route messages to ports. /// A map of router-id to ipc-sender, to route messages to ports.
message_port_routers: HashMap<MessagePortRouterId, IpcSender<MessagePortMsg>>, message_port_routers: HashMap<MessagePortRouterId, IpcSender<MessagePortMsg>>,
/// A map of broadcast routers to their IPC sender.
broadcast_routers: HashMap<BroadcastChannelRouterId, IpcSender<BroadcastMsg>>,
/// A map of origin to a map of channel-name to a list of relevant routers.
broadcast_channels: HashMap<ImmutableOrigin, HashMap<String, Vec<BroadcastChannelRouterId>>>,
/// The set of all the pipelines in the browser. (See the `pipeline` module /// The set of all the pipelines in the browser. (See the `pipeline` module
/// for more details.) /// for more details.)
pipelines: HashMap<PipelineId, Pipeline>, pipelines: HashMap<PipelineId, Pipeline>,
@ -961,6 +968,8 @@ where
browsing_context_group_next_id: Default::default(), browsing_context_group_next_id: Default::default(),
message_ports: HashMap::new(), message_ports: HashMap::new(),
message_port_routers: HashMap::new(), message_port_routers: HashMap::new(),
broadcast_routers: HashMap::new(),
broadcast_channels: HashMap::new(),
pipelines: HashMap::new(), pipelines: HashMap::new(),
browsing_contexts: HashMap::new(), browsing_contexts: HashMap::new(),
pending_changes: vec![], pending_changes: vec![],
@ -1760,6 +1769,36 @@ where
FromScriptMsg::EntanglePorts(port1, port2) => { FromScriptMsg::EntanglePorts(port1, port2) => {
self.handle_entangle_messageports(port1, port2); self.handle_entangle_messageports(port1, port2);
}, },
FromScriptMsg::NewBroadcastChannelRouter(router_id, ipc_sender, origin) => {
self.handle_new_broadcast_channel_router(
source_pipeline_id,
router_id,
ipc_sender,
origin,
);
},
FromScriptMsg::NewBroadcastChannelNameInRouter(router_id, channel_name, origin) => {
self.handle_new_broadcast_channel_name_in_router(
source_pipeline_id,
router_id,
channel_name,
origin,
);
},
FromScriptMsg::RemoveBroadcastChannelNameInRouter(router_id, channel_name, origin) => {
self.handle_remove_broadcast_channel_name_in_router(
source_pipeline_id,
router_id,
channel_name,
origin,
);
},
FromScriptMsg::RemoveBroadcastChannelRouter(router_id, origin) => {
self.handle_remove_broadcast_channel_router(source_pipeline_id, router_id, origin);
},
FromScriptMsg::ScheduleBroadcast(router_id, message) => {
self.handle_schedule_broadcast(source_pipeline_id, router_id, message);
},
FromScriptMsg::ForwardToEmbedder(embedder_msg) => { FromScriptMsg::ForwardToEmbedder(embedder_msg) => {
self.embedder_proxy self.embedder_proxy
.send((Some(source_top_ctx_id), embedder_msg)); .send((Some(source_top_ctx_id), embedder_msg));
@ -1976,6 +2015,170 @@ where
} }
} }
/// Check the origin of a message against that of the pipeline it came from.
/// Note: this is still limited as a security check,
/// see https://github.com/servo/servo/issues/11722
fn check_origin_against_pipeline(
&self,
pipeline_id: &PipelineId,
origin: &ImmutableOrigin,
) -> Result<(), ()> {
let pipeline_origin = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline.load_data.url.origin(),
None => {
warn!("Received message from closed or unknown pipeline.");
return Err(());
},
};
if &pipeline_origin == origin {
return Ok(());
}
Err(())
}
/// Broadcast a message via routers in various event-loops.
fn handle_schedule_broadcast(
&self,
pipeline_id: PipelineId,
router_id: BroadcastChannelRouterId,
message: BroadcastMsg,
) {
if self
.check_origin_against_pipeline(&pipeline_id, &message.origin)
.is_err()
{
return warn!(
"Attempt to schedule broadcast from an origin not matching the origin of the msg."
);
}
if let Some(channels) = self.broadcast_channels.get(&message.origin) {
let routers = match channels.get(&message.channel_name) {
Some(routers) => routers,
None => return warn!("Broadcast to channel name without active routers."),
};
for router in routers {
// Exclude the sender of the broadcast.
// Broadcasting locally is done at the point of sending.
if router == &router_id {
continue;
}
if let Some(sender) = self.broadcast_routers.get(&router) {
if sender.send(message.clone()).is_err() {
warn!("Failed to broadcast message to router: {:?}", router);
}
} else {
warn!("No sender for broadcast router: {:?}", router);
}
}
} else {
warn!(
"Attempt to schedule a broadcast for an origin without routers {:?}",
message.origin
);
}
}
/// Remove a channel-name for a given broadcast router.
fn handle_remove_broadcast_channel_name_in_router(
&mut self,
pipeline_id: PipelineId,
router_id: BroadcastChannelRouterId,
channel_name: String,
origin: ImmutableOrigin,
) {
if self
.check_origin_against_pipeline(&pipeline_id, &origin)
.is_err()
{
return warn!("Attempt to remove channel name from an unexpected origin.");
}
if let Some(channels) = self.broadcast_channels.get_mut(&origin) {
let is_empty = if let Some(routers) = channels.get_mut(&channel_name) {
routers.retain(|router| router != &router_id);
routers.is_empty()
} else {
return warn!(
"Multiple attemps to remove name for broadcast-channel {:?} at {:?}",
channel_name, origin
);
};
if is_empty {
channels.remove(&channel_name);
}
} else {
warn!(
"Attempt to remove a channel-name for an origin without channels {:?}",
origin
);
}
}
/// Note a new channel-name relevant to a given broadcast router.
fn handle_new_broadcast_channel_name_in_router(
&mut self,
pipeline_id: PipelineId,
router_id: BroadcastChannelRouterId,
channel_name: String,
origin: ImmutableOrigin,
) {
if self
.check_origin_against_pipeline(&pipeline_id, &origin)
.is_err()
{
return warn!("Attempt to add channel name from an unexpected origin.");
}
let channels = self
.broadcast_channels
.entry(origin)
.or_insert_with(HashMap::new);
let routers = channels.entry(channel_name).or_insert_with(Vec::new);
routers.push(router_id);
}
/// Remove a broadcast router.
fn handle_remove_broadcast_channel_router(
&mut self,
pipeline_id: PipelineId,
router_id: BroadcastChannelRouterId,
origin: ImmutableOrigin,
) {
if self
.check_origin_against_pipeline(&pipeline_id, &origin)
.is_err()
{
return warn!("Attempt to remove broadcast router from an unexpected origin.");
}
if self.broadcast_routers.remove(&router_id).is_none() {
warn!("Attempt to remove unknown broadcast-channel router.");
}
}
/// Add a new broadcast router.
fn handle_new_broadcast_channel_router(
&mut self,
pipeline_id: PipelineId,
router_id: BroadcastChannelRouterId,
ipc_sender: IpcSender<BroadcastMsg>,
origin: ImmutableOrigin,
) {
if self
.check_origin_against_pipeline(&pipeline_id, &origin)
.is_err()
{
return warn!("Attempt to add broadcast router from an unexpected origin.");
}
if self
.broadcast_routers
.insert(router_id, ipc_sender)
.is_some()
{
warn!("Multple attempt to add broadcast-channel router.");
}
}
fn handle_request_wgpu_adapter( fn handle_request_wgpu_adapter(
&mut self, &mut self,
source_pipeline_id: PipelineId, source_pipeline_id: PipelineId,

View file

@ -171,6 +171,13 @@ impl PipelineNamespace {
} }
} }
fn next_broadcast_channel_router_id(&mut self) -> BroadcastChannelRouterId {
BroadcastChannelRouterId {
namespace_id: self.id,
index: BroadcastChannelRouterIndex(self.next_index()),
}
}
fn next_blob_id(&mut self) -> BlobId { fn next_blob_id(&mut self) -> BlobId {
BlobId { BlobId {
namespace_id: self.id, namespace_id: self.id,
@ -380,6 +387,42 @@ impl fmt::Display for MessagePortRouterId {
} }
} }
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct BroadcastChannelRouterIndex(pub NonZeroU32);
malloc_size_of_is_0!(BroadcastChannelRouterIndex);
#[derive(
Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
)]
pub struct BroadcastChannelRouterId {
pub namespace_id: PipelineNamespaceId,
pub index: BroadcastChannelRouterIndex,
}
impl BroadcastChannelRouterId {
pub fn new() -> BroadcastChannelRouterId {
PIPELINE_NAMESPACE.with(|tls| {
let mut namespace = tls.get().expect("No namespace set for this thread!");
let next_broadcast_channel_router_id = namespace.next_broadcast_channel_router_id();
tls.set(Some(namespace));
next_broadcast_channel_router_id
})
}
}
impl fmt::Display for BroadcastChannelRouterId {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let PipelineNamespaceId(namespace_id) = self.namespace_id;
let BroadcastChannelRouterIndex(index) = self.index;
write!(
fmt,
"(BroadcastChannelRouterId{},{})",
namespace_id,
index.get()
)
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct BlobIndex(pub NonZeroU32); pub struct BlobIndex(pub NonZeroU32);
malloc_size_of_is_0!(BlobIndex); malloc_size_of_is_0!(BlobIndex);

View file

@ -83,8 +83,8 @@ use media::WindowGLContext;
use metrics::{InteractiveMetrics, InteractiveWindow}; use metrics::{InteractiveMetrics, InteractiveWindow};
use mime::Mime; use mime::Mime;
use msg::constellation_msg::{ use msg::constellation_msg::{
BlobId, BrowsingContextId, HistoryStateId, MessagePortId, MessagePortRouterId, PipelineId, BlobId, BroadcastChannelRouterId, BrowsingContextId, HistoryStateId, MessagePortId,
TopLevelBrowsingContextId, MessagePortRouterId, PipelineId, TopLevelBrowsingContextId,
}; };
use net_traits::filemanager_thread::RelativePos; use net_traits::filemanager_thread::RelativePos;
use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image::base::{Image, ImageMetadata};
@ -175,6 +175,8 @@ unsafe_no_jsmanaged_fields!(MessagePortId);
unsafe_no_jsmanaged_fields!(RefCell<Option<MessagePortId>>); unsafe_no_jsmanaged_fields!(RefCell<Option<MessagePortId>>);
unsafe_no_jsmanaged_fields!(MessagePortRouterId); unsafe_no_jsmanaged_fields!(MessagePortRouterId);
unsafe_no_jsmanaged_fields!(BroadcastChannelRouterId);
unsafe_no_jsmanaged_fields!(BlobId); unsafe_no_jsmanaged_fields!(BlobId);
unsafe_no_jsmanaged_fields!(BlobImpl); unsafe_no_jsmanaged_fields!(BlobImpl);

View file

@ -0,0 +1,106 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::{
BroadcastChannelMethods, Wrap,
};
use crate::dom::bindings::error::{Error, ErrorResult};
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::structuredclone;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::JSContext as SafeJSContext;
use dom_struct::dom_struct;
use js::rust::HandleValue;
use script_traits::BroadcastMsg;
use std::cell::Cell;
use uuid::Uuid;
#[dom_struct]
pub struct BroadcastChannel {
eventtarget: EventTarget,
name: DOMString,
closed: Cell<bool>,
id: Uuid,
}
impl BroadcastChannel {
/// <https://html.spec.whatwg.org/multipage/#broadcastchannel>
#[allow(non_snake_case)]
pub fn Constructor(global: &GlobalScope, name: DOMString) -> DomRoot<BroadcastChannel> {
BroadcastChannel::new(global, name)
}
pub fn new(global: &GlobalScope, name: DOMString) -> DomRoot<BroadcastChannel> {
let channel = reflect_dom_object(
Box::new(BroadcastChannel::new_inherited(name)),
global,
Wrap,
);
global.track_broadcast_channel(&*channel);
channel
}
pub fn new_inherited(name: DOMString) -> BroadcastChannel {
BroadcastChannel {
eventtarget: EventTarget::new_inherited(),
name,
closed: Default::default(),
id: Uuid::new_v4(),
}
}
/// The unique Id of this channel.
/// Used for filtering out the sender from the local broadcast.
pub fn id(&self) -> &Uuid {
&self.id
}
/// Is this channel closed?
pub fn closed(&self) -> bool {
self.closed.get()
}
}
impl BroadcastChannelMethods for BroadcastChannel {
/// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage>
fn PostMessage(&self, cx: SafeJSContext, message: HandleValue) -> ErrorResult {
// Step 3, if closed.
if self.closed.get() {
return Err(Error::InvalidState);
}
// Step 6, StructuredSerialize(message).
let data = structuredclone::write(cx, message, None)?;
let global = self.global();
let msg = BroadcastMsg {
origin: global.origin().immutable().clone(),
channel_name: self.Name().to_string(),
data,
};
global.schedule_broadcast(msg, &self.id);
Ok(())
}
/// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-name>
fn Name(&self) -> DOMString {
self.name.clone()
}
/// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-close>
fn Close(&self) {
self.closed.set(true);
}
/// <https://html.spec.whatwg.org/multipage/#handler-broadcastchannel-onmessageerror>
event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
/// <https://html.spec.whatwg.org/multipage/#handler-broadcastchannel-onmessage>
event_handler!(message, GetOnmessage, SetOnmessage);
}

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods;
use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSourceBinding::EventSourceMethods; use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSourceBinding::EventSourceMethods;
use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
@ -19,6 +20,7 @@ use crate::dom::bindings::structuredclone;
use crate::dom::bindings::utils::to_frozen_array; use crate::dom::bindings::utils::to_frozen_array;
use crate::dom::bindings::weakref::{DOMTracker, WeakRef}; use crate::dom::bindings::weakref::{DOMTracker, WeakRef};
use crate::dom::blob::Blob; use crate::dom::blob::Blob;
use crate::dom::broadcastchannel::BroadcastChannel;
use crate::dom::crypto::Crypto; use crate::dom::crypto::Crypto;
use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
use crate::dom::errorevent::ErrorEvent; use crate::dom::errorevent::ErrorEvent;
@ -71,7 +73,9 @@ use js::rust::wrappers::EvaluateUtf8;
use js::rust::{get_object_class, CompileOptionsWrapper, ParentRuntime, Runtime}; use js::rust::{get_object_class, CompileOptionsWrapper, ParentRuntime, Runtime};
use js::rust::{HandleValue, MutableHandleValue}; use js::rust::{HandleValue, MutableHandleValue};
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL}; use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
use msg::constellation_msg::{BlobId, MessagePortId, MessagePortRouterId, PipelineId}; use msg::constellation_msg::{
BlobId, BroadcastChannelRouterId, MessagePortId, MessagePortRouterId, PipelineId,
};
use net_traits::blob_url_store::{get_blob_origin, BlobBuf}; use net_traits::blob_url_store::{get_blob_origin, BlobBuf};
use net_traits::filemanager_thread::{ use net_traits::filemanager_thread::{
FileManagerResult, FileManagerThreadMsg, ReadFileProgress, RelativePos, FileManagerResult, FileManagerThreadMsg, ReadFileProgress, RelativePos,
@ -82,7 +86,8 @@ use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_tim
use script_traits::serializable::{BlobData, BlobImpl, FileBlob}; use script_traits::serializable::{BlobData, BlobImpl, FileBlob};
use script_traits::transferable::MessagePortImpl; use script_traits::transferable::MessagePortImpl;
use script_traits::{ use script_traits::{
MessagePortMsg, MsDuration, PortMessageTask, ScriptMsg, ScriptToConstellationChan, TimerEvent, BroadcastMsg, MessagePortMsg, MsDuration, PortMessageTask, ScriptMsg,
ScriptToConstellationChan, TimerEvent,
}; };
use script_traits::{TimerEventId, TimerSchedulerMsg, TimerSource}; use script_traits::{TimerEventId, TimerSchedulerMsg, TimerSource};
use servo_url::{MutableOrigin, ServoUrl}; use servo_url::{MutableOrigin, ServoUrl};
@ -124,6 +129,9 @@ pub struct GlobalScope {
/// The message-port router id for this global, if it is managing ports. /// The message-port router id for this global, if it is managing ports.
message_port_state: DomRefCell<MessagePortState>, message_port_state: DomRefCell<MessagePortState>,
/// The broadcast channels state this global, if it is managing any.
broadcast_channel_state: DomRefCell<BroadcastChannelState>,
/// The blobs managed by this global, if any. /// The blobs managed by this global, if any.
blob_state: DomRefCell<BlobState>, blob_state: DomRefCell<BlobState>,
@ -237,6 +245,13 @@ struct MessageListener {
context: Trusted<GlobalScope>, context: Trusted<GlobalScope>,
} }
/// A wrapper for broadcasts coming in over IPC, and the event-loop.
struct BroadcastListener {
canceller: TaskCanceller,
task_source: DOMManipulationTaskSource,
context: Trusted<GlobalScope>,
}
/// A wrapper between timer events coming in over IPC, and the event-loop. /// A wrapper between timer events coming in over IPC, and the event-loop.
struct TimerListener { struct TimerListener {
canceller: TaskCanceller, canceller: TaskCanceller,
@ -310,6 +325,23 @@ pub struct ManagedMessagePort {
closed: bool, closed: bool,
} }
/// State representing whether this global is currently managing broadcast channels.
#[derive(JSTraceable, MallocSizeOf)]
#[unrooted_must_root_lint::must_root]
pub enum BroadcastChannelState {
/// The broadcast-channel router id for this global, and a queue of managed channels.
/// Step 9, "sort destinations"
/// of https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage
/// requires keeping track of creation order, hence the queue.
Managed(
BroadcastChannelRouterId,
/// The map of channel-name to queue of channels, in order of creation.
HashMap<DOMString, VecDeque<Dom<BroadcastChannel>>>,
),
/// This global is not managing any broadcast channels at this time.
UnManaged,
}
/// State representing whether this global is currently managing messageports. /// State representing whether this global is currently managing messageports.
#[derive(JSTraceable, MallocSizeOf)] #[derive(JSTraceable, MallocSizeOf)]
#[unrooted_must_root_lint::must_root] #[unrooted_must_root_lint::must_root]
@ -323,6 +355,29 @@ pub enum MessagePortState {
UnManaged, UnManaged,
} }
impl BroadcastListener {
/// Handle a broadcast coming in over IPC,
/// by queueing the appropriate task on the relevant event-loop.
fn handle(&self, event: BroadcastMsg) {
let context = self.context.clone();
// Note: strictly speaking we should just queue the message event tasks,
// not queue a task that then queues more tasks.
// This however seems to be hard to avoid in the light of the IPC.
// One can imagine queueing tasks directly,
// for channels that would be in the same script-thread.
let _ = self.task_source.queue_with_canceller(
task!(broadcast_message_event: move || {
let global = context.root();
// Step 10 of https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage,
// For each BroadcastChannel object destination in destinations, queue a task.
global.broadcast_message_event(event, None);
}),
&self.canceller,
);
}
}
impl TimerListener { impl TimerListener {
/// Handle a timer-event coming-in over IPC, /// Handle a timer-event coming-in over IPC,
/// by queuing the appropriate task on the relevant event-loop. /// by queuing the appropriate task on the relevant event-loop.
@ -501,6 +556,7 @@ impl GlobalScope {
) -> Self { ) -> Self {
Self { Self {
message_port_state: DomRefCell::new(MessagePortState::UnManaged), message_port_state: DomRefCell::new(MessagePortState::UnManaged),
broadcast_channel_state: DomRefCell::new(BroadcastChannelState::UnManaged),
blob_state: DomRefCell::new(BlobState::UnManaged), blob_state: DomRefCell::new(BlobState::UnManaged),
eventtarget: EventTarget::new_inherited(), eventtarget: EventTarget::new_inherited(),
crypto: Default::default(), crypto: Default::default(),
@ -613,11 +669,18 @@ impl GlobalScope {
pub fn perform_a_dom_garbage_collection_checkpoint(&self) { pub fn perform_a_dom_garbage_collection_checkpoint(&self) {
self.perform_a_message_port_garbage_collection_checkpoint(); self.perform_a_message_port_garbage_collection_checkpoint();
self.perform_a_blob_garbage_collection_checkpoint(); self.perform_a_blob_garbage_collection_checkpoint();
self.perform_a_broadcast_channel_garbage_collection_checkpoint();
}
/// Remove the routers for ports and broadcast-channels.
pub fn remove_web_messaging_infra(&self) {
self.remove_message_ports_router();
self.remove_broadcast_channel_router();
} }
/// Update our state to un-managed, /// Update our state to un-managed,
/// and tell the constellation to drop the sender to our message-port router. /// and tell the constellation to drop the sender to our message-port router.
pub fn remove_message_ports_router(&self) { fn remove_message_ports_router(&self) {
if let MessagePortState::Managed(router_id, _message_ports) = if let MessagePortState::Managed(router_id, _message_ports) =
&*self.message_port_state.borrow() &*self.message_port_state.borrow()
{ {
@ -628,6 +691,22 @@ impl GlobalScope {
*self.message_port_state.borrow_mut() = MessagePortState::UnManaged; *self.message_port_state.borrow_mut() = MessagePortState::UnManaged;
} }
/// Update our state to un-managed,
/// and tell the constellation to drop the sender to our broadcast router.
fn remove_broadcast_channel_router(&self) {
if let BroadcastChannelState::Managed(router_id, _channels) =
&*self.broadcast_channel_state.borrow()
{
let _ =
self.script_to_constellation_chan()
.send(ScriptMsg::RemoveBroadcastChannelRouter(
router_id.clone(),
self.origin().immutable().clone(),
));
}
*self.broadcast_channel_state.borrow_mut() = BroadcastChannelState::UnManaged;
}
/// <https://html.spec.whatwg.org/multipage/#entangle> /// <https://html.spec.whatwg.org/multipage/#entangle>
pub fn entangle_ports(&self, port1: MessagePortId, port2: MessagePortId) { pub fn entangle_ports(&self, port1: MessagePortId, port2: MessagePortId) {
if let MessagePortState::Managed(_id, message_ports) = if let MessagePortState::Managed(_id, message_ports) =
@ -789,6 +868,115 @@ impl GlobalScope {
.send(ScriptMsg::RerouteMessagePort(port_id, task)); .send(ScriptMsg::RerouteMessagePort(port_id, task));
} }
/// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage>
/// Step 7 and following steps.
pub fn schedule_broadcast(&self, msg: BroadcastMsg, channel_id: &Uuid) {
// First, broadcast locally.
self.broadcast_message_event(msg.clone(), Some(channel_id));
if let BroadcastChannelState::Managed(router_id, _) =
&*self.broadcast_channel_state.borrow()
{
// Second, broadcast to other globals via the constellation.
//
// Note: for globals in the same script-thread,
// we could skip the hop to the constellation.
let _ = self
.script_to_constellation_chan()
.send(ScriptMsg::ScheduleBroadcast(router_id.clone(), msg));
} else {
panic!("Attemps to broadcast a message via global not managing any channels.");
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage>
/// Step 7 and following steps.
pub fn broadcast_message_event(&self, event: BroadcastMsg, channel_id: Option<&Uuid>) {
if let BroadcastChannelState::Managed(_, channels) = &*self.broadcast_channel_state.borrow()
{
let BroadcastMsg {
data,
origin,
channel_name,
} = event;
// Step 7, a few preliminary steps.
// - Check the worker is not closing.
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
if worker.is_closing() {
return;
}
}
// - Check the associated document is fully-active.
if let Some(window) = self.downcast::<Window>() {
if !window.Document().is_fully_active() {
return;
}
}
// - Check for a case-sensitive match for the name of the channel.
let channel_name = DOMString::from_string(channel_name);
if let Some(channels) = channels.get(&channel_name) {
channels
.iter()
.filter(|ref channel| {
// Step 8.
// Filter out the sender.
if let Some(id) = channel_id {
channel.id() != id
} else {
true
}
})
.map(|channel| DomRoot::from_ref(&**channel))
// Step 9, sort by creation order,
// done by using a queue to store channels in creation order.
.for_each(|channel| {
let data = data.clone_for_broadcast();
let origin = origin.clone();
// Step 10: Queue a task on the DOM manipulation task-source,
// to fire the message event
let channel = Trusted::new(&*channel);
let global = Trusted::new(&*self);
let _ = self.dom_manipulation_task_source().queue(
task!(process_pending_port_messages: move || {
let destination = channel.root();
let global = global.root();
// 10.1 Check for closed flag.
if destination.closed() {
return;
}
rooted!(in(*global.get_cx()) let mut message = UndefinedValue());
// Step 10.3 StructuredDeserialize(serialized, targetRealm).
if let Ok(ports) = structuredclone::read(&global, data, message.handle_mut()) {
// Step 10.4, Fire an event named message at destination.
MessageEvent::dispatch_jsval(
&*destination.upcast(),
&global,
message.handle(),
Some(&origin.ascii_serialization()),
None,
ports,
);
} else {
// Step 10.3, fire an event named messageerror at destination.
MessageEvent::dispatch_error(&*destination.upcast(), &global);
}
}),
&self,
);
});
}
}
}
/// Route the task to be handled by the relevant port. /// Route the task to be handled by the relevant port.
pub fn route_task_to_port(&self, port_id: MessagePortId, task: PortMessageTask) { pub fn route_task_to_port(&self, port_id: MessagePortId, task: PortMessageTask) {
let should_dispatch = if let MessagePortState::Managed(_id, message_ports) = let should_dispatch = if let MessagePortState::Managed(_id, message_ports) =
@ -905,6 +1093,93 @@ impl GlobalScope {
} }
} }
/// Remove broadcast-channels that are closed.
/// TODO: Also remove them if they do not have an event-listener.
/// see https://github.com/servo/servo/issues/25772
pub fn perform_a_broadcast_channel_garbage_collection_checkpoint(&self) {
let is_empty = if let BroadcastChannelState::Managed(router_id, ref mut channels) =
&mut *self.broadcast_channel_state.borrow_mut()
{
channels.retain(|name, ref mut channels| {
channels.retain(|ref chan| !chan.closed());
if channels.is_empty() {
let _ = self.script_to_constellation_chan().send(
ScriptMsg::RemoveBroadcastChannelNameInRouter(
router_id.clone(),
name.to_string(),
self.origin().immutable().clone(),
),
);
false
} else {
true
}
});
channels.is_empty()
} else {
false
};
if is_empty {
self.remove_broadcast_channel_router();
}
}
/// Start tracking a broadcast-channel.
pub fn track_broadcast_channel(&self, dom_channel: &BroadcastChannel) {
let mut current_state = self.broadcast_channel_state.borrow_mut();
if let BroadcastChannelState::UnManaged = &*current_state {
// Setup a route for IPC, for broadcasts from the constellation to our channels.
let (broadcast_control_sender, broadcast_control_receiver) =
ipc::channel().expect("ipc channel failure");
let context = Trusted::new(self);
let (task_source, canceller) = (
self.dom_manipulation_task_source(),
self.task_canceller(TaskSourceName::DOMManipulation),
);
let listener = BroadcastListener {
canceller,
task_source,
context,
};
ROUTER.add_route(
broadcast_control_receiver.to_opaque(),
Box::new(move |message| {
let msg = message.to();
match msg {
Ok(msg) => listener.handle(msg),
Err(err) => warn!("Error receiving a BroadcastMsg: {:?}", err),
}
}),
);
let router_id = BroadcastChannelRouterId::new();
*current_state = BroadcastChannelState::Managed(router_id.clone(), HashMap::new());
let _ = self
.script_to_constellation_chan()
.send(ScriptMsg::NewBroadcastChannelRouter(
router_id,
broadcast_control_sender,
self.origin().immutable().clone(),
));
}
if let BroadcastChannelState::Managed(router_id, channels) = &mut *current_state {
let entry = channels.entry(dom_channel.Name()).or_insert_with(|| {
let _ = self.script_to_constellation_chan().send(
ScriptMsg::NewBroadcastChannelNameInRouter(
router_id.clone(),
dom_channel.Name().to_string(),
self.origin().immutable().clone(),
),
);
VecDeque::new()
});
entry.push_back(Dom::from_ref(dom_channel));
} else {
panic!("track_broadcast_channel should have first switched the state to managed.");
}
}
/// Start tracking a message-port /// Start tracking a message-port
pub fn track_message_port(&self, dom_port: &MessagePort, port_impl: Option<MessagePortImpl>) { pub fn track_message_port(&self, dom_port: &MessagePort, port_impl: Option<MessagePortImpl>) {
let mut current_state = self.message_port_state.borrow_mut(); let mut current_state = self.message_port_state.borrow_mut();

View file

@ -239,6 +239,7 @@ pub mod bluetoothremotegattdescriptor;
pub mod bluetoothremotegattserver; pub mod bluetoothremotegattserver;
pub mod bluetoothremotegattservice; pub mod bluetoothremotegattservice;
pub mod bluetoothuuid; pub mod bluetoothuuid;
pub mod broadcastchannel;
pub mod canvasgradient; pub mod canvasgradient;
pub mod canvaspattern; pub mod canvaspattern;
pub mod canvasrenderingcontext2d; pub mod canvasrenderingcontext2d;

View file

@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* The origin of this IDL file is:
* https://html.spec.whatwg.org/multipage/#broadcastchannel
*/
[Exposed=(Window,Worker)]
interface BroadcastChannel : EventTarget {
constructor(DOMString name);
readonly attribute DOMString name;
[Throws] void postMessage(any message);
void close();
attribute EventHandler onmessage;
attribute EventHandler onmessageerror;
};

View file

@ -1407,8 +1407,8 @@ impl Window {
.upcast::<Node>() .upcast::<Node>()
.teardown(self.layout_chan()); .teardown(self.layout_chan());
// Tell the constellation to drop the sender to our message-port router, if there is any. // Remove the infra for managing messageports and broadcast channels.
self.upcast::<GlobalScope>().remove_message_ports_router(); self.upcast::<GlobalScope>().remove_web_messaging_infra();
// Clean up any active promises // Clean up any active promises
// https://github.com/servo/servo/issues/15318 // https://github.com/servo/servo/issues/15318

View file

@ -21,7 +21,7 @@ pub mod serializable;
pub mod transferable; pub mod transferable;
pub mod webdriver_msg; pub mod webdriver_msg;
use crate::serializable::BlobImpl; use crate::serializable::{BlobData, BlobImpl};
use crate::transferable::MessagePortImpl; use crate::transferable::MessagePortImpl;
use crate::webdriver_msg::{LoadStatus, WebDriverScriptCommand}; use crate::webdriver_msg::{LoadStatus, WebDriverScriptCommand};
use bluetooth_traits::BluetoothRequest; use bluetooth_traits::BluetoothRequest;
@ -955,6 +955,48 @@ pub struct StructuredSerializedData {
pub ports: Option<HashMap<MessagePortId, MessagePortImpl>>, pub ports: Option<HashMap<MessagePortId, MessagePortImpl>>,
} }
impl StructuredSerializedData {
/// Clone the serialized data for use with broadcast-channels.
pub fn clone_for_broadcast(&self) -> StructuredSerializedData {
let serialized = self.serialized.clone();
let blobs = if let Some(blobs) = self.blobs.as_ref() {
let mut blob_clones = HashMap::with_capacity(blobs.len());
for (original_id, blob) in blobs.iter() {
let type_string = blob.type_string();
if let BlobData::Memory(ref bytes) = blob.blob_data() {
let blob_clone = BlobImpl::new_from_bytes(bytes.clone(), type_string);
// Note: we insert the blob at the original id,
// otherwise this will not match the storage key as serialized by SM in `serialized`.
// The clone has it's own new Id however.
blob_clones.insert(original_id.clone(), blob_clone);
} else {
// Not panicking only because this is called from the constellation.
warn!("Serialized blob not in memory format(should never happen).");
}
}
Some(blob_clones)
} else {
None
};
if self.ports.is_some() {
// Not panicking only because this is called from the constellation.
warn!("Attempt to broadcast structured serialized data including ports(should never happen).");
}
StructuredSerializedData {
serialized,
blobs,
// Ports cannot be broadcast.
ports: None,
}
}
}
/// A task on the https://html.spec.whatwg.org/multipage/#port-message-queue /// A task on the https://html.spec.whatwg.org/multipage/#port-message-queue
#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] #[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct PortMessageTask { pub struct PortMessageTask {
@ -979,6 +1021,27 @@ pub enum MessagePortMsg {
NewTask(MessagePortId, PortMessageTask), NewTask(MessagePortId, PortMessageTask),
} }
/// Message for communication between the constellation and a global managing broadcast channels.
#[derive(Debug, Deserialize, Serialize)]
pub struct BroadcastMsg {
/// The origin of this message.
pub origin: ImmutableOrigin,
/// The name of the channel.
pub channel_name: String,
/// A data-holder for serialized data.
pub data: StructuredSerializedData,
}
impl Clone for BroadcastMsg {
fn clone(&self) -> BroadcastMsg {
BroadcastMsg {
data: self.data.clone_for_broadcast(),
origin: self.origin.clone(),
channel_name: self.channel_name.clone(),
}
}
}
/// The type of MediaSession action. /// The type of MediaSession action.
/// https://w3c.github.io/mediasession/#enumdef-mediasessionaction /// https://w3c.github.io/mediasession/#enumdef-mediasessionaction
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]

View file

@ -4,6 +4,7 @@
use crate::AnimationState; use crate::AnimationState;
use crate::AuxiliaryBrowsingContextLoadInfo; use crate::AuxiliaryBrowsingContextLoadInfo;
use crate::BroadcastMsg;
use crate::DocumentState; use crate::DocumentState;
use crate::IFrameLoadInfoWithData; use crate::IFrameLoadInfoWithData;
use crate::LayoutControlMsg; use crate::LayoutControlMsg;
@ -22,7 +23,8 @@ use euclid::Size2D;
use gfx_traits::Epoch; use gfx_traits::Epoch;
use ipc_channel::ipc::{IpcReceiver, IpcSender}; use ipc_channel::ipc::{IpcReceiver, IpcSender};
use msg::constellation_msg::{ use msg::constellation_msg::{
BrowsingContextId, MessagePortId, MessagePortRouterId, PipelineId, TopLevelBrowsingContextId, BroadcastChannelRouterId, BrowsingContextId, MessagePortId, MessagePortRouterId, PipelineId,
TopLevelBrowsingContextId,
}; };
use msg::constellation_msg::{HistoryStateId, TraversalDirection}; use msg::constellation_msg::{HistoryStateId, TraversalDirection};
use net_traits::request::RequestBuilder; use net_traits::request::RequestBuilder;
@ -142,6 +144,21 @@ pub enum ScriptMsg {
RemoveMessagePort(MessagePortId), RemoveMessagePort(MessagePortId),
/// Entangle two message-ports. /// Entangle two message-ports.
EntanglePorts(MessagePortId, MessagePortId), EntanglePorts(MessagePortId, MessagePortId),
/// A global has started managing broadcast-channels.
NewBroadcastChannelRouter(
BroadcastChannelRouterId,
IpcSender<BroadcastMsg>,
ImmutableOrigin,
),
/// A global has stopped managing broadcast-channels.
RemoveBroadcastChannelRouter(BroadcastChannelRouterId, ImmutableOrigin),
/// A global started managing broadcast channels for a given channel-name.
NewBroadcastChannelNameInRouter(BroadcastChannelRouterId, String, ImmutableOrigin),
/// A global stopped managing broadcast channels for a given channel-name.
RemoveBroadcastChannelNameInRouter(BroadcastChannelRouterId, String, ImmutableOrigin),
/// Broadcast a message to all same-origin broadcast channels,
/// excluding the source of the broadcast.
ScheduleBroadcast(BroadcastChannelRouterId, BroadcastMsg),
/// Forward a message to the embedder. /// Forward a message to the embedder.
ForwardToEmbedder(EmbedderMsg), ForwardToEmbedder(EmbedderMsg),
/// Requests are sent to constellation and fetches are checked manually /// Requests are sent to constellation and fetches are checked manually
@ -280,6 +297,11 @@ impl fmt::Debug for ScriptMsg {
RemoveMessagePort(..) => "RemoveMessagePort", RemoveMessagePort(..) => "RemoveMessagePort",
MessagePortShipped(..) => "MessagePortShipped", MessagePortShipped(..) => "MessagePortShipped",
EntanglePorts(..) => "EntanglePorts", EntanglePorts(..) => "EntanglePorts",
NewBroadcastChannelRouter(..) => "NewBroadcastChannelRouter",
RemoveBroadcastChannelRouter(..) => "RemoveBroadcastChannelRouter",
RemoveBroadcastChannelNameInRouter(..) => "RemoveBroadcastChannelNameInRouter",
NewBroadcastChannelNameInRouter(..) => "NewBroadcastChannelNameInRouter",
ScheduleBroadcast(..) => "ScheduleBroadcast",
ForwardToEmbedder(..) => "ForwardToEmbedder", ForwardToEmbedder(..) => "ForwardToEmbedder",
InitiateNavigateRequest(..) => "InitiateNavigateRequest", InitiateNavigateRequest(..) => "InitiateNavigateRequest",
BroadcastStorageEvent(..) => "BroadcastStorageEvent", BroadcastStorageEvent(..) => "BroadcastStorageEvent",

View file

@ -1,10 +1,11 @@
[noopener-noreferrer-BarProp.window.html] [noopener-noreferrer-BarProp.window.html]
expected: TIMEOUT
[window.open() with noopener should have all bars visible] [window.open() with noopener should have all bars visible]
expected: FAIL expected: TIMEOUT
[All bars visible] [All bars visible]
expected: FAIL expected: FAIL
[window.open() with noreferrer should have all bars visible] [window.open() with noreferrer should have all bars visible]
expected: FAIL expected: TIMEOUT

View file

@ -1,7 +0,0 @@
[noopener-noreferrer-sizing.window.html]
[window.open() with noreferrer should have equal viewport width and height]
expected: FAIL
[window.open() with noopener should have equal viewport width and height]
expected: FAIL

View file

@ -36,7 +36,6 @@
[window-open-noopener.html?indexed] [window-open-noopener.html?indexed]
expected: ERROR
[window.open() with 'noopener' should not reuse existing target] [window.open() with 'noopener' should not reuse existing target]
expected: FAIL expected: FAIL
@ -49,30 +48,6 @@
[noopener needs to be present as a token on its own yet again] [noopener needs to be present as a token on its own yet again]
expected: NOTRUN expected: NOTRUN
[Trailing noopener should work]
expected: NOTRUN
[Leading noopener should work]
expected: NOTRUN
[Interior noopener should work]
expected: NOTRUN
[noopener=1 means the same as noopener]
expected: NOTRUN
[noopener=0 means lack of noopener]
expected: NOTRUN
[noopener separated only by spaces should work]
expected: NOTRUN
[window.open() with 'noopener' should reuse existing target]
expected: FAIL
[noreferrer should also suppress opener when reusing existing target]
expected: NOTRUN
[window-open-noopener.html?_self] [window-open-noopener.html?_self]
[noopener window.open targeting _self] [noopener window.open targeting _self]

View file

@ -1,4 +0,0 @@
[window-open-noreferrer.html]
[window.open() with "noreferrer" tests]
expected: FAIL

View file

@ -332,18 +332,12 @@
[CanvasRenderingContext2D interface: operation scrollPathIntoView(Path2D)] [CanvasRenderingContext2D interface: operation scrollPathIntoView(Path2D)]
expected: FAIL expected: FAIL
[BroadcastChannel interface: attribute name]
expected: FAIL
[ApplicationCache interface object name] [ApplicationCache interface object name]
expected: FAIL expected: FAIL
[DOMStringList interface: calling item(unsigned long) on location.ancestorOrigins with too few arguments must throw TypeError] [DOMStringList interface: calling item(unsigned long) on location.ancestorOrigins with too few arguments must throw TypeError]
expected: FAIL expected: FAIL
[BroadcastChannel interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[BarProp interface object length] [BarProp interface object length]
expected: FAIL expected: FAIL
@ -704,9 +698,6 @@
[OffscreenCanvasRenderingContext2D interface object name] [OffscreenCanvasRenderingContext2D interface object name]
expected: FAIL expected: FAIL
[BroadcastChannel interface: attribute onmessage]
expected: FAIL
[ElementInternals interface object name] [ElementInternals interface object name]
expected: FAIL expected: FAIL
@ -800,9 +791,6 @@
[ApplicationCache interface: attribute onerror] [ApplicationCache interface: attribute onerror]
expected: FAIL expected: FAIL
[BroadcastChannel interface: existence and properties of interface object]
expected: FAIL
[SVGElement interface: attribute onsubmit] [SVGElement interface: attribute onsubmit]
expected: FAIL expected: FAIL
@ -968,9 +956,6 @@
[SVGElement interface: attribute onkeydown] [SVGElement interface: attribute onkeydown]
expected: FAIL expected: FAIL
[BroadcastChannel interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[Path2D interface: existence and properties of interface prototype object's "constructor" property] [Path2D interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL expected: FAIL
@ -1067,9 +1052,6 @@
[OffscreenCanvas interface object name] [OffscreenCanvas interface object name]
expected: FAIL expected: FAIL
[BroadcastChannel interface: operation close()]
expected: FAIL
[SVGElement interface: attribute onresize] [SVGElement interface: attribute onresize]
expected: FAIL expected: FAIL
@ -1166,9 +1148,6 @@
[ImageBitmap interface: attribute width] [ImageBitmap interface: attribute width]
expected: FAIL expected: FAIL
[BroadcastChannel interface: operation postMessage(any)]
expected: FAIL
[DataTransfer interface: attribute types] [DataTransfer interface: attribute types]
expected: FAIL expected: FAIL
@ -1214,18 +1193,12 @@
[SVGElement interface: attribute onvolumechange] [SVGElement interface: attribute onvolumechange]
expected: FAIL expected: FAIL
[BroadcastChannel interface object name]
expected: FAIL
[CanvasRenderingContext2D interface: attribute textBaseline] [CanvasRenderingContext2D interface: attribute textBaseline]
expected: FAIL expected: FAIL
[ImageBitmapRenderingContext interface object length] [ImageBitmapRenderingContext interface object length]
expected: FAIL expected: FAIL
[BroadcastChannel interface: existence and properties of interface prototype object]
expected: FAIL
[Path2D interface: operation addPath(Path2D, DOMMatrix2DInit)] [Path2D interface: operation addPath(Path2D, DOMMatrix2DInit)]
expected: FAIL expected: FAIL
@ -1307,9 +1280,6 @@
[CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "scrollPathIntoView()" with the proper type] [CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "scrollPathIntoView()" with the proper type]
expected: FAIL expected: FAIL
[BroadcastChannel interface: attribute onmessageerror]
expected: FAIL
[OffscreenCanvas interface: operation convertToBlob(ImageEncodeOptions)] [OffscreenCanvas interface: operation convertToBlob(ImageEncodeOptions)]
expected: FAIL expected: FAIL
@ -1331,9 +1301,6 @@
[ValidityState interface: document.createElement("input").validity must inherit property "valid" with the proper type] [ValidityState interface: document.createElement("input").validity must inherit property "valid" with the proper type]
expected: FAIL expected: FAIL
[BroadcastChannel interface object length]
expected: FAIL
[Location interface: stringifier] [Location interface: stringifier]
expected: FAIL expected: FAIL
@ -4442,4 +4409,3 @@
[HTMLImageElement interface: document.createElement("img") must inherit property "loading" with the proper type] [HTMLImageElement interface: document.createElement("img") must inherit property "loading" with the proper type]
expected: FAIL expected: FAIL

View file

@ -1,15 +1,9 @@
[no-coop-coep.https.any.worker.html] [no-coop-coep.https.any.worker.html]
[SharedArrayBuffer over BroadcastChannel without COOP+COEP]
expected: FAIL
[Bonus: self.crossOriginIsolated] [Bonus: self.crossOriginIsolated]
expected: FAIL expected: FAIL
[no-coop-coep.https.any.html] [no-coop-coep.https.any.html]
[SharedArrayBuffer over BroadcastChannel without COOP+COEP]
expected: FAIL
[Bonus: self.crossOriginIsolated] [Bonus: self.crossOriginIsolated]
expected: FAIL expected: FAIL

View file

@ -1,4 +1,5 @@
[rel-base-target.html] [rel-base-target.html]
expected: TIMEOUT
[<form rel="noreferrer opener"> with <base target>] [<form rel="noreferrer opener"> with <base target>]
expected: FAIL expected: FAIL

View file

@ -1,6 +1,5 @@
[htmlanchorelement_noopener.html] [htmlanchorelement_noopener.html]
type: testharness type: testharness
expected: ERROR
[Check that targeting of rel=noopener with a given name ignores an existing window with that name] [Check that targeting of rel=noopener with a given name ignores an existing window with that name]
expected: NOTRUN expected: NOTRUN
@ -11,7 +10,7 @@
expected: FAIL expected: FAIL
[Check that targeting of rel=noopener with a given name reuses an existing window with that name] [Check that targeting of rel=noopener with a given name reuses an existing window with that name]
expected: NOTRUN expected: FAIL
[Check that rel=noopener with target=_self does a normal load] [Check that rel=noopener with target=_self does a normal load]
expected: FAIL expected: FAIL

View file

@ -1,40 +1,20 @@
[target_blank_implicit_noopener.html] [target_blank_implicit_noopener.html]
[Anchor element with target=_blank with rel=opener+noopener] expected: TIMEOUT
expected: FAIL
[Area element with target=_blank with rel=opener+noopener] [Area element with target=_blank with rel=opener+noopener]
expected: FAIL expected: TIMEOUT
[Anchor element with target=_blank with rel=noopener+opener+noreferrer]
expected: FAIL
[Anchor element with target=_blank with rel=opener]
expected: FAIL
[Anchor element with target=_blank with rel=noopener+opener]
expected: FAIL
[Area element with target=_blank with rel=noopener] [Area element with target=_blank with rel=noopener]
expected: FAIL expected: TIMEOUT
[Area element with target=_blank with rel=opener] [Area element with target=_blank with rel=opener]
expected: FAIL expected: TIMEOUT
[Anchor element with target=_blank with implicit rel=noopener] [Anchor element with target=_blank with implicit rel=noopener]
expected: FAIL expected: FAIL
[Anchor element with target=_blank with rel=opener+noreferrer]
expected: FAIL
[Area element with target=_blank with implicit rel=noopener] [Area element with target=_blank with implicit rel=noopener]
expected: FAIL expected: TIMEOUT
[Anchor element with target=_blank with rel=noreferrer]
expected: FAIL
[Area element with target=_blank with rel=noopener+opener] [Area element with target=_blank with rel=noopener+opener]
expected: FAIL expected: TIMEOUT
[Anchor element with target=_blank with rel=noopener]
expected: FAIL

View file

@ -1,4 +1,5 @@
[target_blank_implicit_noopener_base.html] [target_blank_implicit_noopener_base.html]
expected: TIMEOUT
[Anchor element with base target=_blank with implicit rel=noopener] [Anchor element with base target=_blank with implicit rel=noopener]
expected: FAIL expected: FAIL

View file

@ -1,4 +0,0 @@
[MessageEvent-trusted.html]
[With a BroadcastChannel]
expected: FAIL

View file

@ -1,16 +0,0 @@
[basics.html]
[messages are delivered in port creation order]
expected: FAIL
[closing and creating channels during message delivery works correctly]
expected: FAIL
[messages aren't delivered to a closed port]
expected: FAIL
[Closing a channel in onmessage doesn't cancel already queued events]
expected: FAIL
[postMessage results in correct event]
expected: FAIL

View file

@ -1,7 +0,0 @@
[blobs.html]
[Blobs work with workers on BroadcastChannel]
expected: FAIL
[Blobs work on BroadcastChannel]
expected: FAIL

View file

@ -1,40 +0,0 @@
[interface.html]
[Null name should not throw]
expected: FAIL
[postMessage after close should throw]
expected: FAIL
[Undefined name should not throw]
expected: FAIL
[postMessage should throw with uncloneable data]
expected: FAIL
[close should not throw when called multiple times]
expected: FAIL
[close should not throw]
expected: FAIL
[Non-empty name should not throw]
expected: FAIL
[postMessage with null should not throw]
expected: FAIL
[postMessage should throw InvalidStateError after close, even with uncloneable data]
expected: FAIL
[BroadcastChannel should have an onmessage event]
expected: FAIL
[Non-string name should not throw]
expected: FAIL
[Should throw if no name is provided]
expected: FAIL
[postMessage without parameters should throw]
expected: FAIL

View file

@ -1,5 +0,0 @@
[origin.window.html]
expected: TIMEOUT
[Serialization of BroadcastChannel origin]
expected: TIMEOUT

View file

@ -1,4 +0,0 @@
[sandbox.html]
[Creating BroadcastChannel in an opaque origin]
expected: FAIL

View file

@ -1,17 +1,4 @@
[workers.html] [workers.html]
expected: TIMEOUT
[BroadcastChannel used after a worker self.close()]
expected: FAIL
[BroadcastChannel works in shared workers] [BroadcastChannel works in shared workers]
expected: FAIL expected: FAIL
[BroadcastChannel works in workers]
expected: FAIL
[BroadcastChannel created after a worker self.close()]
expected: TIMEOUT
[Closing and re-opening a channel works.]
expected: FAIL

View file

@ -8,7 +8,3 @@
[Worker has an opaque origin.] [Worker has an opaque origin.]
expected: FAIL expected: FAIL
[Worker can access BroadcastChannel]
expected: FAIL

View file

@ -12,9 +12,6 @@
[The Path2D interface object should be exposed.] [The Path2D interface object should be exposed.]
expected: FAIL expected: FAIL
[The BroadcastChannel interface object should be exposed.]
expected: FAIL
[The ReadableStream interface object should be exposed.] [The ReadableStream interface object should be exposed.]
expected: FAIL expected: FAIL
@ -59,3 +56,4 @@
[The IDBTransaction interface object should be exposed.] [The IDBTransaction interface object should be exposed.]
expected: FAIL expected: FAIL

View file

@ -28,6 +28,7 @@ test_interfaces([
"BeforeUnloadEvent", "BeforeUnloadEvent",
"BiquadFilterNode", "BiquadFilterNode",
"Blob", "Blob",
"BroadcastChannel",
"CanvasGradient", "CanvasGradient",
"CanvasRenderingContext2D", "CanvasRenderingContext2D",
"CanvasPattern", "CanvasPattern",

View file

@ -7,6 +7,7 @@ function test_interfaces(interfaceNamesInGlobalScope) {
"ArrayBuffer", "ArrayBuffer",
"Atomics", "Atomics",
"Boolean", "Boolean",
"BroadcastChannel",
"Crypto", "Crypto",
"DataView", "DataView",
"Date", "Date",

View file

@ -8,6 +8,7 @@ importScripts("interfaces.js");
// IMPORTANT: Do not change the list below without review from a DOM peer! // IMPORTANT: Do not change the list below without review from a DOM peer!
test_interfaces([ test_interfaces([
"Blob", "Blob",
"BroadcastChannel",
"CanvasGradient", "CanvasGradient",
"CanvasPattern", "CanvasPattern",
"CloseEvent", "CloseEvent",