diff options
Diffstat (limited to 'components/shared')
64 files changed, 12766 insertions, 0 deletions
diff --git a/components/shared/bluetooth/Cargo.toml b/components/shared/bluetooth/Cargo.toml new file mode 100644 index 00000000000..c83b15c4cdc --- /dev/null +++ b/components/shared/bluetooth/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "bluetooth_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "bluetooth_traits" +path = "lib.rs" + +[dependencies] +embedder_traits = { workspace = true } +ipc-channel = { workspace = true } +regex = { workspace = true } +serde = { workspace = true } diff --git a/components/shared/bluetooth/blocklist.rs b/components/shared/bluetooth/blocklist.rs new file mode 100644 index 00000000000..ca413c41a1e --- /dev/null +++ b/components/shared/bluetooth/blocklist.rs @@ -0,0 +1,112 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::string::String; + +use embedder_traits::resources::{self, Resource}; +use regex::Regex; + +const EXCLUDE_READS: &str = "exclude-reads"; +const EXCLUDE_WRITES: &str = "exclude-writes"; +const VALID_UUID_REGEX: &str = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; + +thread_local!(pub static BLUETOOTH_BLOCKLIST: RefCell<BluetoothBlocklist> = + RefCell::new(BluetoothBlocklist(parse_blocklist()))); + +pub fn uuid_is_blocklisted(uuid: &str, exclude_type: Blocklist) -> bool { + BLUETOOTH_BLOCKLIST.with(|blist| match exclude_type { + Blocklist::All => blist.borrow().is_blocklisted(uuid), + Blocklist::Reads => blist.borrow().is_blocklisted_for_reads(uuid), + Blocklist::Writes => blist.borrow().is_blocklisted_for_writes(uuid), + }) +} + +pub struct BluetoothBlocklist(Option<HashMap<String, Blocklist>>); + +#[derive(Eq, PartialEq)] +pub enum Blocklist { + All, // Read and Write + Reads, + Writes, +} + +impl BluetoothBlocklist { + // https://webbluetoothcg.github.io/web-bluetooth/#blocklisted + pub fn is_blocklisted(&self, uuid: &str) -> bool { + match self.0 { + Some(ref map) => map.get(uuid).map_or(false, |et| et.eq(&Blocklist::All)), + None => false, + } + } + + // https://webbluetoothcg.github.io/web-bluetooth/#blocklisted-for-reads + pub fn is_blocklisted_for_reads(&self, uuid: &str) -> bool { + match self.0 { + Some(ref map) => map.get(uuid).map_or(false, |et| { + et.eq(&Blocklist::All) || et.eq(&Blocklist::Reads) + }), + None => false, + } + } + + // https://webbluetoothcg.github.io/web-bluetooth/#blocklisted-for-writes + pub fn is_blocklisted_for_writes(&self, uuid: &str) -> bool { + match self.0 { + Some(ref map) => map.get(uuid).map_or(false, |et| { + et.eq(&Blocklist::All) || et.eq(&Blocklist::Writes) + }), + None => false, + } + } +} + +// https://webbluetoothcg.github.io/web-bluetooth/#parsing-the-blocklist +fn parse_blocklist() -> Option<HashMap<String, Blocklist>> { + // Step 1 missing, currently we parse ./resources/gatt_blocklist.txt. + let valid_uuid_regex = Regex::new(VALID_UUID_REGEX).unwrap(); + let content = resources::read_string(Resource::BluetoothBlocklist); + // Step 3 + let mut result = HashMap::new(); + // Step 2 and 4 + for line in content.lines() { + // Step 4.1 + if line.is_empty() || line.starts_with('#') { + continue; + } + let mut exclude_type = Blocklist::All; + let mut words = line.split_whitespace(); + let uuid = match words.next() { + Some(uuid) => uuid, + None => continue, + }; + if !valid_uuid_regex.is_match(uuid) { + return None; + } + match words.next() { + // Step 4.2 We already have an initialized exclude_type variable with Blocklist::All. + None => {}, + // Step 4.3 + Some(EXCLUDE_READS) => { + exclude_type = Blocklist::Reads; + }, + Some(EXCLUDE_WRITES) => { + exclude_type = Blocklist::Writes; + }, + // Step 4.4 + _ => { + return None; + }, + } + // Step 4.5 + if result.contains_key(uuid) { + return None; + } + // Step 4.6 + result.insert(uuid.to_string(), exclude_type); + } + // Step 5 + Some(result) +} diff --git a/components/shared/bluetooth/lib.rs b/components/shared/bluetooth/lib.rs new file mode 100644 index 00000000000..5910aa3c284 --- /dev/null +++ b/components/shared/bluetooth/lib.rs @@ -0,0 +1,119 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +pub mod blocklist; +pub mod scanfilter; + +use ipc_channel::ipc::IpcSender; +use serde::{Deserialize, Serialize}; + +use crate::scanfilter::{BluetoothScanfilterSequence, RequestDeviceoptions}; + +#[derive(Debug, Deserialize, Serialize)] +pub enum BluetoothError { + Type(String), + Network, + NotFound, + NotSupported, + Security, + InvalidState, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum GATTType { + PrimaryService, + Characteristic, + IncludedService, + Descriptor, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothDeviceMsg { + // Bluetooth Device properties + pub id: String, + pub name: Option<String>, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothServiceMsg { + pub uuid: String, + pub is_primary: bool, + pub instance_id: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothCharacteristicMsg { + // Characteristic + pub uuid: String, + pub instance_id: String, + // Characteristic properties + pub broadcast: bool, + pub read: bool, + pub write_without_response: bool, + pub write: bool, + pub notify: bool, + pub indicate: bool, + pub authenticated_signed_writes: bool, + pub reliable_write: bool, + pub writable_auxiliaries: bool, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothDescriptorMsg { + pub uuid: String, + pub instance_id: String, +} + +pub type BluetoothServicesMsg = Vec<BluetoothServiceMsg>; + +pub type BluetoothCharacteristicsMsg = Vec<BluetoothCharacteristicMsg>; + +pub type BluetoothDescriptorsMsg = Vec<BluetoothDescriptorMsg>; + +pub type BluetoothResult<T> = Result<T, BluetoothError>; + +pub type BluetoothResponseResult = Result<BluetoothResponse, BluetoothError>; + +#[derive(Debug, Deserialize, Serialize)] +pub enum BluetoothRequest { + RequestDevice(RequestDeviceoptions, IpcSender<BluetoothResponseResult>), + GATTServerConnect(String, IpcSender<BluetoothResponseResult>), + GATTServerDisconnect(String, IpcSender<BluetoothResult<()>>), + GetGATTChildren( + String, + Option<String>, + bool, + GATTType, + IpcSender<BluetoothResponseResult>, + ), + ReadValue(String, IpcSender<BluetoothResponseResult>), + WriteValue(String, Vec<u8>, IpcSender<BluetoothResponseResult>), + EnableNotification(String, bool, IpcSender<BluetoothResponseResult>), + WatchAdvertisements(String, IpcSender<BluetoothResponseResult>), + SetRepresentedToNull(Vec<String>, Vec<String>, Vec<String>), + IsRepresentedDeviceNull(String, IpcSender<bool>), + GetAvailability(IpcSender<BluetoothResponseResult>), + MatchesFilter( + String, + BluetoothScanfilterSequence, + IpcSender<BluetoothResult<bool>>, + ), + Test(String, IpcSender<BluetoothResult<()>>), + Exit, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum BluetoothResponse { + RequestDevice(BluetoothDeviceMsg), + GATTServerConnect(bool), + GetPrimaryServices(BluetoothServicesMsg, bool), + GetIncludedServices(BluetoothServicesMsg, bool), + GetCharacteristics(BluetoothCharacteristicsMsg, bool), + GetDescriptors(BluetoothDescriptorsMsg, bool), + ReadValue(Vec<u8>), + WriteValue(Vec<u8>), + EnableNotification(()), + WatchAdvertisements(()), + GetAvailability(bool), +} diff --git a/components/shared/bluetooth/scanfilter.rs b/components/shared/bluetooth/scanfilter.rs new file mode 100644 index 00000000000..b5590e1aa40 --- /dev/null +++ b/components/shared/bluetooth/scanfilter.rs @@ -0,0 +1,143 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::collections::{HashMap, HashSet}; +use std::slice::Iter; + +use serde::{Deserialize, Serialize}; + +// A device name can never be longer than 29 bytes. An adv packet is at most +// 31 bytes long. The length and identifier of the length field take 2 bytes. +// That leaves 29 bytes for the name. +const MAX_NAME_LENGTH: usize = 29; + +#[derive(Debug, Deserialize, Serialize)] +pub struct ServiceUUIDSequence(Vec<String>); + +impl ServiceUUIDSequence { + pub fn new(vec: Vec<String>) -> ServiceUUIDSequence { + ServiceUUIDSequence(vec) + } + + fn get_services_set(&self) -> HashSet<String> { + self.0.iter().map(String::clone).collect() + } +} + +type ManufacturerData = HashMap<u16, (Vec<u8>, Vec<u8>)>; +type ServiceData = HashMap<String, (Vec<u8>, Vec<u8>)>; + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothScanfilter { + name: Option<String>, + name_prefix: String, + services: ServiceUUIDSequence, + manufacturer_data: Option<ManufacturerData>, + service_data: Option<ServiceData>, +} + +impl BluetoothScanfilter { + pub fn new( + name: Option<String>, + name_prefix: String, + services: Vec<String>, + manufacturer_data: Option<ManufacturerData>, + service_data: Option<ServiceData>, + ) -> BluetoothScanfilter { + BluetoothScanfilter { + name, + name_prefix, + services: ServiceUUIDSequence::new(services), + manufacturer_data: manufacturer_data, + service_data, + } + } + + pub fn get_name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn get_name_prefix(&self) -> &str { + &self.name_prefix + } + + pub fn get_services(&self) -> &[String] { + &self.services.0 + } + + pub fn get_manufacturer_data(&self) -> Option<&ManufacturerData> { + self.manufacturer_data.as_ref() + } + + pub fn get_service_data(&self) -> Option<&ServiceData> { + self.service_data.as_ref() + } + + pub fn is_empty_or_invalid(&self) -> bool { + (self.name.is_none() && + self.name_prefix.is_empty() && + self.get_services().is_empty() && + self.manufacturer_data.is_none() && + self.service_data.is_none()) || + self.get_name().unwrap_or("").len() > MAX_NAME_LENGTH || + self.name_prefix.len() > MAX_NAME_LENGTH + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothScanfilterSequence(Vec<BluetoothScanfilter>); + +impl BluetoothScanfilterSequence { + pub fn new(vec: Vec<BluetoothScanfilter>) -> BluetoothScanfilterSequence { + BluetoothScanfilterSequence(vec) + } + + pub fn has_empty_or_invalid_filter(&self) -> bool { + self.0.iter().any(BluetoothScanfilter::is_empty_or_invalid) + } + + pub fn iter(&self) -> Iter<BluetoothScanfilter> { + self.0.iter() + } + + fn get_services_set(&self) -> HashSet<String> { + self.iter() + .flat_map(|filter| filter.services.get_services_set()) + .collect() + } + + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RequestDeviceoptions { + filters: BluetoothScanfilterSequence, + optional_services: ServiceUUIDSequence, +} + +impl RequestDeviceoptions { + pub fn new( + filters: BluetoothScanfilterSequence, + services: ServiceUUIDSequence, + ) -> RequestDeviceoptions { + RequestDeviceoptions { + filters: filters, + optional_services: services, + } + } + + pub fn get_filters(&self) -> &BluetoothScanfilterSequence { + &self.filters + } + + pub fn get_services_set(&self) -> HashSet<String> { + &self.filters.get_services_set() | &self.optional_services.get_services_set() + } + + pub fn is_accepting_all_devices(&self) -> bool { + self.filters.is_empty() + } +} diff --git a/components/shared/canvas/Cargo.toml b/components/shared/canvas/Cargo.toml new file mode 100644 index 00000000000..6f3eca8c160 --- /dev/null +++ b/components/shared/canvas/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "canvas_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "canvas_traits" +path = "lib.rs" + +[features] +webgl_backtrace = [] +xr-profile = ["webxr-api/profile", "time"] + +[dependencies] +crossbeam-channel = { workspace = true } +cssparser = { workspace = true } +euclid = { workspace = true } +ipc-channel = { workspace = true } +lazy_static = { workspace = true } +malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of_derive = { workspace = true } +pixels = { path = "../../pixels" } +serde = { workspace = true } +serde_bytes = { workspace = true } +servo_config = { path = "../../config" } +sparkle = { workspace = true } +style = { path = "../../style" } +time = { workspace = true, optional = true } +webrender_api = { workspace = true } +webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } diff --git a/components/shared/canvas/canvas.rs b/components/shared/canvas/canvas.rs new file mode 100644 index 00000000000..0ed7faed559 --- /dev/null +++ b/components/shared/canvas/canvas.rs @@ -0,0 +1,491 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::default::Default; +use std::str::FromStr; + +use cssparser::RGBA; +use euclid::default::{Point2D, Rect, Size2D, Transform2D}; +use ipc_channel::ipc::{IpcBytesReceiver, IpcBytesSender, IpcSender, IpcSharedMemory}; +use malloc_size_of_derive::MallocSizeOf; +use serde::{Deserialize, Serialize}; +use serde_bytes::ByteBuf; +use style::properties::style_structs::Font as FontStyleStruct; +use webrender_api::ImageKey; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum FillRule { + Nonzero, + Evenodd, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub struct CanvasId(pub u64); + +#[derive(Deserialize, Serialize)] +pub enum CanvasMsg { + Canvas2d(Canvas2dMsg, CanvasId), + FromLayout(FromLayoutMsg, CanvasId), + FromScript(FromScriptMsg, CanvasId), + Recreate(Size2D<u64>, CanvasId), + Close(CanvasId), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CanvasImageData { + pub image_key: ImageKey, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum Canvas2dMsg { + Arc(Point2D<f32>, f32, f32, f32, bool), + ArcTo(Point2D<f32>, Point2D<f32>, f32), + DrawImage(IpcSharedMemory, Size2D<f64>, Rect<f64>, Rect<f64>, bool), + DrawEmptyImage(Size2D<f64>, Rect<f64>, Rect<f64>), + DrawImageInOther(CanvasId, Size2D<f64>, Rect<f64>, Rect<f64>, bool), + BeginPath, + BezierCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>), + ClearRect(Rect<f32>), + Clip, + ClosePath, + Ellipse(Point2D<f32>, f32, f32, f32, f32, f32, bool), + Fill(FillOrStrokeStyle), + FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle, bool), + FillRect(Rect<f32>, FillOrStrokeStyle), + GetImageData(Rect<u64>, Size2D<u64>, IpcBytesSender), + GetTransform(IpcSender<Transform2D<f32>>), + IsPointInPath(f64, f64, FillRule, IpcSender<bool>), + LineTo(Point2D<f32>), + MoveTo(Point2D<f32>), + PutImageData(Rect<u64>, IpcBytesReceiver), + QuadraticCurveTo(Point2D<f32>, Point2D<f32>), + Rect(Rect<f32>), + RestoreContext, + SaveContext, + StrokeRect(Rect<f32>, FillOrStrokeStyle), + Stroke(FillOrStrokeStyle), + SetLineWidth(f32), + SetLineCap(LineCapStyle), + SetLineJoin(LineJoinStyle), + SetMiterLimit(f32), + SetGlobalAlpha(f32), + SetGlobalComposition(CompositionOrBlending), + SetTransform(Transform2D<f32>), + SetShadowOffsetX(f64), + SetShadowOffsetY(f64), + SetShadowBlur(f64), + SetShadowColor(RGBA), + SetFont(FontStyleStruct), + SetTextAlign(TextAlign), + SetTextBaseline(TextBaseline), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum FromLayoutMsg { + SendData(IpcSender<CanvasImageData>), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum FromScriptMsg { + SendPixels(IpcSender<IpcSharedMemory>), +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct CanvasGradientStop { + pub offset: f64, + pub color: RGBA, +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct LinearGradientStyle { + pub x0: f64, + pub y0: f64, + pub x1: f64, + pub y1: f64, + pub stops: Vec<CanvasGradientStop>, +} + +impl LinearGradientStyle { + pub fn new( + x0: f64, + y0: f64, + x1: f64, + y1: f64, + stops: Vec<CanvasGradientStop>, + ) -> LinearGradientStyle { + LinearGradientStyle { + x0: x0, + y0: y0, + x1: x1, + y1: y1, + stops: stops, + } + } +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct RadialGradientStyle { + pub x0: f64, + pub y0: f64, + pub r0: f64, + pub x1: f64, + pub y1: f64, + pub r1: f64, + pub stops: Vec<CanvasGradientStop>, +} + +impl RadialGradientStyle { + pub fn new( + x0: f64, + y0: f64, + r0: f64, + x1: f64, + y1: f64, + r1: f64, + stops: Vec<CanvasGradientStop>, + ) -> RadialGradientStyle { + RadialGradientStyle { + x0: x0, + y0: y0, + r0: r0, + x1: x1, + y1: y1, + r1: r1, + stops: stops, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SurfaceStyle { + pub surface_data: ByteBuf, + pub surface_size: Size2D<u32>, + pub repeat_x: bool, + pub repeat_y: bool, +} + +impl SurfaceStyle { + pub fn new( + surface_data: Vec<u8>, + surface_size: Size2D<u32>, + repeat_x: bool, + repeat_y: bool, + ) -> Self { + Self { + surface_data: ByteBuf::from(surface_data), + surface_size, + repeat_x, + repeat_y, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum FillOrStrokeStyle { + Color(RGBA), + LinearGradient(LinearGradientStyle), + RadialGradient(RadialGradientStyle), + Surface(SurfaceStyle), +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum LineCapStyle { + Butt = 0, + Round = 1, + Square = 2, +} + +impl FromStr for LineCapStyle { + type Err = (); + + fn from_str(string: &str) -> Result<LineCapStyle, ()> { + match string { + "butt" => Ok(LineCapStyle::Butt), + "round" => Ok(LineCapStyle::Round), + "square" => Ok(LineCapStyle::Square), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum LineJoinStyle { + Round = 0, + Bevel = 1, + Miter = 2, +} + +impl FromStr for LineJoinStyle { + type Err = (); + + fn from_str(string: &str) -> Result<LineJoinStyle, ()> { + match string { + "round" => Ok(LineJoinStyle::Round), + "bevel" => Ok(LineJoinStyle::Bevel), + "miter" => Ok(LineJoinStyle::Miter), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub enum RepetitionStyle { + Repeat, + RepeatX, + RepeatY, + NoRepeat, +} + +impl FromStr for RepetitionStyle { + type Err = (); + + fn from_str(string: &str) -> Result<RepetitionStyle, ()> { + match string { + "repeat" => Ok(RepetitionStyle::Repeat), + "repeat-x" => Ok(RepetitionStyle::RepeatX), + "repeat-y" => Ok(RepetitionStyle::RepeatY), + "no-repeat" => Ok(RepetitionStyle::NoRepeat), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum CompositionStyle { + SrcIn, + SrcOut, + SrcOver, + SrcAtop, + DestIn, + DestOut, + DestOver, + DestAtop, + Copy, + Lighter, + Xor, + Clear, +} + +impl FromStr for CompositionStyle { + type Err = (); + + fn from_str(string: &str) -> Result<CompositionStyle, ()> { + match string { + "source-in" => Ok(CompositionStyle::SrcIn), + "source-out" => Ok(CompositionStyle::SrcOut), + "source-over" => Ok(CompositionStyle::SrcOver), + "source-atop" => Ok(CompositionStyle::SrcAtop), + "destination-in" => Ok(CompositionStyle::DestIn), + "destination-out" => Ok(CompositionStyle::DestOut), + "destination-over" => Ok(CompositionStyle::DestOver), + "destination-atop" => Ok(CompositionStyle::DestAtop), + "copy" => Ok(CompositionStyle::Copy), + "lighter" => Ok(CompositionStyle::Lighter), + "xor" => Ok(CompositionStyle::Xor), + "clear" => Ok(CompositionStyle::Clear), + _ => Err(()), + } + } +} + +impl CompositionStyle { + pub fn to_str(&self) -> &str { + match *self { + CompositionStyle::SrcIn => "source-in", + CompositionStyle::SrcOut => "source-out", + CompositionStyle::SrcOver => "source-over", + CompositionStyle::SrcAtop => "source-atop", + CompositionStyle::DestIn => "destination-in", + CompositionStyle::DestOut => "destination-out", + CompositionStyle::DestOver => "destination-over", + CompositionStyle::DestAtop => "destination-atop", + CompositionStyle::Copy => "copy", + CompositionStyle::Lighter => "lighter", + CompositionStyle::Xor => "xor", + CompositionStyle::Clear => "clear", + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum BlendingStyle { + Multiply, + Screen, + Overlay, + Darken, + Lighten, + ColorDodge, + ColorBurn, + HardLight, + SoftLight, + Difference, + Exclusion, + Hue, + Saturation, + Color, + Luminosity, +} + +impl FromStr for BlendingStyle { + type Err = (); + + fn from_str(string: &str) -> Result<BlendingStyle, ()> { + match string { + "multiply" => Ok(BlendingStyle::Multiply), + "screen" => Ok(BlendingStyle::Screen), + "overlay" => Ok(BlendingStyle::Overlay), + "darken" => Ok(BlendingStyle::Darken), + "lighten" => Ok(BlendingStyle::Lighten), + "color-dodge" => Ok(BlendingStyle::ColorDodge), + "color-burn" => Ok(BlendingStyle::ColorBurn), + "hard-light" => Ok(BlendingStyle::HardLight), + "soft-light" => Ok(BlendingStyle::SoftLight), + "difference" => Ok(BlendingStyle::Difference), + "exclusion" => Ok(BlendingStyle::Exclusion), + "hue" => Ok(BlendingStyle::Hue), + "saturation" => Ok(BlendingStyle::Saturation), + "color" => Ok(BlendingStyle::Color), + "luminosity" => Ok(BlendingStyle::Luminosity), + _ => Err(()), + } + } +} + +impl BlendingStyle { + pub fn to_str(&self) -> &str { + match *self { + BlendingStyle::Multiply => "multiply", + BlendingStyle::Screen => "screen", + BlendingStyle::Overlay => "overlay", + BlendingStyle::Darken => "darken", + BlendingStyle::Lighten => "lighten", + BlendingStyle::ColorDodge => "color-dodge", + BlendingStyle::ColorBurn => "color-burn", + BlendingStyle::HardLight => "hard-light", + BlendingStyle::SoftLight => "soft-light", + BlendingStyle::Difference => "difference", + BlendingStyle::Exclusion => "exclusion", + BlendingStyle::Hue => "hue", + BlendingStyle::Saturation => "saturation", + BlendingStyle::Color => "color", + BlendingStyle::Luminosity => "luminosity", + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum CompositionOrBlending { + Composition(CompositionStyle), + Blending(BlendingStyle), +} + +impl Default for CompositionOrBlending { + fn default() -> CompositionOrBlending { + CompositionOrBlending::Composition(CompositionStyle::SrcOver) + } +} + +impl FromStr for CompositionOrBlending { + type Err = (); + + fn from_str(string: &str) -> Result<CompositionOrBlending, ()> { + if let Ok(op) = CompositionStyle::from_str(string) { + return Ok(CompositionOrBlending::Composition(op)); + } + + if let Ok(op) = BlendingStyle::from_str(string) { + return Ok(CompositionOrBlending::Blending(op)); + } + + Err(()) + } +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum TextAlign { + Start, + End, + Left, + Right, + Center, +} + +impl FromStr for TextAlign { + type Err = (); + + fn from_str(string: &str) -> Result<TextAlign, ()> { + match string { + "start" => Ok(TextAlign::Start), + "end" => Ok(TextAlign::End), + "left" => Ok(TextAlign::Left), + "right" => Ok(TextAlign::Right), + "center" => Ok(TextAlign::Center), + _ => Err(()), + } + } +} + +impl Default for TextAlign { + fn default() -> TextAlign { + TextAlign::Start + } +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum TextBaseline { + Top, + Hanging, + Middle, + Alphabetic, + Ideographic, + Bottom, +} + +impl FromStr for TextBaseline { + type Err = (); + + fn from_str(string: &str) -> Result<TextBaseline, ()> { + match string { + "top" => Ok(TextBaseline::Top), + "hanging" => Ok(TextBaseline::Hanging), + "middle" => Ok(TextBaseline::Middle), + "alphabetic" => Ok(TextBaseline::Alphabetic), + "ideographic" => Ok(TextBaseline::Ideographic), + "bottom" => Ok(TextBaseline::Bottom), + _ => Err(()), + } + } +} + +impl Default for TextBaseline { + fn default() -> TextBaseline { + TextBaseline::Alphabetic + } +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum Direction { + Ltr, + Rtl, + Inherit, +} + +impl FromStr for Direction { + type Err = (); + + fn from_str(string: &str) -> Result<Direction, ()> { + match string { + "ltr" => Ok(Direction::Ltr), + "rtl" => Ok(Direction::Rtl), + "inherit" => Ok(Direction::Inherit), + _ => Err(()), + } + } +} + +impl Default for Direction { + fn default() -> Direction { + Direction::Inherit + } +} diff --git a/components/shared/canvas/lib.rs b/components/shared/canvas/lib.rs new file mode 100644 index 00000000000..e0c4050e7be --- /dev/null +++ b/components/shared/canvas/lib.rs @@ -0,0 +1,26 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#![crate_name = "canvas_traits"] +#![crate_type = "rlib"] +#![deny(unsafe_code)] + +use crossbeam_channel::Sender; +use euclid::default::Size2D; + +use crate::canvas::CanvasId; + +pub mod canvas; +#[macro_use] +pub mod webgl; +mod webgl_channel; + +pub enum ConstellationCanvasMsg { + Create { + id_sender: Sender<CanvasId>, + size: Size2D<u64>, + antialias: bool, + }, + Exit, +} diff --git a/components/shared/canvas/webgl.rs b/components/shared/canvas/webgl.rs new file mode 100644 index 00000000000..2a2e0f0c3b3 --- /dev/null +++ b/components/shared/canvas/webgl.rs @@ -0,0 +1,1431 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::borrow::Cow; +use std::fmt; +use std::num::{NonZeroU32, NonZeroU64}; +use std::ops::Deref; + +use euclid::default::{Rect, Size2D}; +use ipc_channel::ipc::{IpcBytesReceiver, IpcBytesSender, IpcSharedMemory}; +use malloc_size_of_derive::MallocSizeOf; +use pixels::PixelFormat; +use serde::{Deserialize, Serialize}; +use sparkle::gl; +use webrender_api::ImageKey; +use webxr_api::{ + ContextId as WebXRContextId, Error as WebXRError, LayerId as WebXRLayerId, + LayerInit as WebXRLayerInit, SubImages as WebXRSubImages, +}; + +/// Helper function that creates a WebGL channel (WebGLSender, WebGLReceiver) to be used in WebGLCommands. +pub use crate::webgl_channel::webgl_channel; +/// Entry point channel type used for sending WebGLMsg messages to the WebGL renderer. +pub use crate::webgl_channel::WebGLChan; +/// Entry point type used in a Script Pipeline to get the WebGLChan to be used in that thread. +pub use crate::webgl_channel::WebGLPipeline; +/// Receiver type used in WebGLCommands. +pub use crate::webgl_channel::WebGLReceiver; +/// Result type for send()/recv() calls in in WebGLCommands. +pub use crate::webgl_channel::WebGLSendResult; +/// Sender type used in WebGLCommands. +pub use crate::webgl_channel::WebGLSender; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WebGLCommandBacktrace { + #[cfg(feature = "webgl_backtrace")] + pub backtrace: String, + #[cfg(feature = "webgl_backtrace")] + pub js_backtrace: Option<String>, +} + +/// WebGL Threading API entry point that lives in the constellation. +pub struct WebGLThreads(pub WebGLSender<WebGLMsg>); + +impl WebGLThreads { + /// Gets the WebGLThread handle for each script pipeline. + pub fn pipeline(&self) -> WebGLPipeline { + // This mode creates a single thread, so the existing WebGLChan is just cloned. + WebGLPipeline(WebGLChan(self.0.clone())) + } + + /// Sends a exit message to close the WebGLThreads and release all WebGLContexts. + pub fn exit(&self) -> Result<(), &'static str> { + self.0 + .send(WebGLMsg::Exit) + .map_err(|_| "Failed to send Exit message") + } +} + +/// WebGL Message API +#[derive(Debug, Deserialize, Serialize)] +pub enum WebGLMsg { + /// Creates a new WebGLContext. + CreateContext( + WebGLVersion, + Size2D<u32>, + GLContextAttributes, + WebGLSender<Result<WebGLCreateContextResult, String>>, + ), + /// Resizes a WebGLContext. + ResizeContext(WebGLContextId, Size2D<u32>, WebGLSender<Result<(), String>>), + /// Drops a WebGLContext. + RemoveContext(WebGLContextId), + /// Runs a WebGLCommand in a specific WebGLContext. + WebGLCommand(WebGLContextId, WebGLCommand, WebGLCommandBacktrace), + /// Runs a WebXRCommand (WebXR layers need to be created in the WebGL + /// thread, as they may have thread affinity). + WebXRCommand(WebXRCommand), + /// Performs a buffer swap. + /// + /// The third field contains the time (in ns) when the request + /// was initiated. The u64 in the second field will be the time the + /// request is fulfilled + SwapBuffers(Vec<WebGLContextId>, WebGLSender<u64>, u64), + /// Frees all resources and closes the thread. + Exit, +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum GlType { + Gl, + Gles, +} + +/// Contains the WebGLCommand sender and information about a WebGLContext +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WebGLCreateContextResult { + /// Sender instance to send commands to the specific WebGLContext + pub sender: WebGLMsgSender, + /// Information about the internal GL Context. + pub limits: GLLimits, + /// The GLSL version supported by the context. + pub glsl_version: WebGLSLVersion, + /// The GL API used by the context. + pub api_type: GlType, + /// The WebRender image key. + pub image_key: ImageKey, +} + +/// Defines the WebGL version +#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, PartialOrd, Serialize)] +pub enum WebGLVersion { + /// https://www.khronos.org/registry/webgl/specs/1.0.2/ + /// Conforms closely to the OpenGL ES 2.0 API + WebGL1, + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/ + /// Conforms closely to the OpenGL ES 3.0 API + WebGL2, +} + +/// Defines the GLSL version supported by the WebGL backend contexts. +#[derive( + Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize, +)] +pub struct WebGLSLVersion { + /// Major GLSL version + pub major: u32, + /// Minor GLSL version + pub minor: u32, +} + +/// Helper struct to send WebGLCommands to a specific WebGLContext. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct WebGLMsgSender { + ctx_id: WebGLContextId, + #[ignore_malloc_size_of = "channels are hard"] + sender: WebGLChan, +} + +impl WebGLMsgSender { + pub fn new(id: WebGLContextId, sender: WebGLChan) -> Self { + WebGLMsgSender { + ctx_id: id, + sender: sender, + } + } + + /// Returns the WebGLContextId associated to this sender + pub fn context_id(&self) -> WebGLContextId { + self.ctx_id + } + + /// Send a WebGLCommand message + #[inline] + pub fn send(&self, command: WebGLCommand, backtrace: WebGLCommandBacktrace) -> WebGLSendResult { + self.sender + .send(WebGLMsg::WebGLCommand(self.ctx_id, command, backtrace)) + } + + /// Send a resize message + #[inline] + pub fn send_resize( + &self, + size: Size2D<u32>, + sender: WebGLSender<Result<(), String>>, + ) -> WebGLSendResult { + self.sender + .send(WebGLMsg::ResizeContext(self.ctx_id, size, sender)) + } + + #[inline] + pub fn send_remove(&self) -> WebGLSendResult { + self.sender.send(WebGLMsg::RemoveContext(self.ctx_id)) + } +} + +#[derive(Deserialize, Serialize)] +pub struct TruncatedDebug<T>(T); + +impl<T> From<T> for TruncatedDebug<T> { + fn from(v: T) -> TruncatedDebug<T> { + TruncatedDebug(v) + } +} + +impl<T: fmt::Debug> fmt::Debug for TruncatedDebug<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut s = format!("{:?}", self.0); + if s.len() > 20 { + s.truncate(20); + s.push_str("..."); + } + write!(f, "{}", s) + } +} + +impl<T> Deref for TruncatedDebug<T> { + type Target = T; + fn deref(&self) -> &T { + &self.0 + } +} + +/// WebGL Commands for a specific WebGLContext +#[derive(Debug, Deserialize, Serialize)] +pub enum WebGLCommand { + GetContextAttributes(WebGLSender<GLContextAttributes>), + ActiveTexture(u32), + BlendColor(f32, f32, f32, f32), + BlendEquation(u32), + BlendEquationSeparate(u32, u32), + BlendFunc(u32, u32), + BlendFuncSeparate(u32, u32, u32, u32), + AttachShader(WebGLProgramId, WebGLShaderId), + DetachShader(WebGLProgramId, WebGLShaderId), + BindAttribLocation(WebGLProgramId, u32, String), + BufferData(u32, IpcBytesReceiver, u32), + BufferSubData(u32, isize, IpcBytesReceiver), + GetBufferSubData(u32, usize, usize, IpcBytesSender), + CopyBufferSubData(u32, u32, i64, i64, i64), + Clear(u32), + ClearColor(f32, f32, f32, f32), + ClearDepth(f32), + ClearStencil(i32), + ColorMask(bool, bool, bool, bool), + CullFace(u32), + FrontFace(u32), + DepthFunc(u32), + DepthMask(bool), + DepthRange(f32, f32), + Enable(u32), + Disable(u32), + CompileShader(WebGLShaderId, String), + CopyTexImage2D(u32, i32, u32, i32, i32, i32, i32, i32), + CopyTexSubImage2D(u32, i32, i32, i32, i32, i32, i32, i32), + CreateBuffer(WebGLSender<Option<WebGLBufferId>>), + CreateFramebuffer(WebGLSender<Option<WebGLFramebufferId>>), + CreateRenderbuffer(WebGLSender<Option<WebGLRenderbufferId>>), + CreateTexture(WebGLSender<Option<WebGLTextureId>>), + CreateProgram(WebGLSender<Option<WebGLProgramId>>), + CreateShader(u32, WebGLSender<Option<WebGLShaderId>>), + DeleteBuffer(WebGLBufferId), + DeleteFramebuffer(WebGLFramebufferId), + DeleteRenderbuffer(WebGLRenderbufferId), + DeleteTexture(WebGLTextureId), + DeleteProgram(WebGLProgramId), + DeleteShader(WebGLShaderId), + BindBuffer(u32, Option<WebGLBufferId>), + BindFramebuffer(u32, WebGLFramebufferBindingRequest), + BindRenderbuffer(u32, Option<WebGLRenderbufferId>), + BindTexture(u32, Option<WebGLTextureId>), + DisableVertexAttribArray(u32), + EnableVertexAttribArray(u32), + FramebufferRenderbuffer(u32, u32, u32, Option<WebGLRenderbufferId>), + FramebufferTexture2D(u32, u32, u32, Option<WebGLTextureId>, i32), + GetExtensions(WebGLSender<String>), + GetShaderPrecisionFormat(u32, u32, WebGLSender<(i32, i32, i32)>), + GetFragDataLocation(WebGLProgramId, String, WebGLSender<i32>), + GetUniformLocation(WebGLProgramId, String, WebGLSender<i32>), + GetShaderInfoLog(WebGLShaderId, WebGLSender<String>), + GetProgramInfoLog(WebGLProgramId, WebGLSender<String>), + GetFramebufferAttachmentParameter(u32, u32, u32, WebGLSender<i32>), + GetRenderbufferParameter(u32, u32, WebGLSender<i32>), + CreateTransformFeedback(WebGLSender<u32>), + DeleteTransformFeedback(u32), + IsTransformFeedback(u32, WebGLSender<bool>), + BindTransformFeedback(u32, u32), + BeginTransformFeedback(u32), + EndTransformFeedback(), + PauseTransformFeedback(), + ResumeTransformFeedback(), + GetTransformFeedbackVarying(WebGLProgramId, u32, WebGLSender<(i32, u32, String)>), + TransformFeedbackVaryings(WebGLProgramId, Vec<String>, u32), + PolygonOffset(f32, f32), + RenderbufferStorage(u32, u32, i32, i32), + RenderbufferStorageMultisample(u32, i32, u32, i32, i32), + ReadPixels(Rect<u32>, u32, u32, IpcBytesSender), + ReadPixelsPP(Rect<i32>, u32, u32, usize), + SampleCoverage(f32, bool), + Scissor(i32, i32, u32, u32), + StencilFunc(u32, i32, u32), + StencilFuncSeparate(u32, u32, i32, u32), + StencilMask(u32), + StencilMaskSeparate(u32, u32), + StencilOp(u32, u32, u32), + StencilOpSeparate(u32, u32, u32, u32), + FenceSync(WebGLSender<WebGLSyncId>), + IsSync(WebGLSyncId, WebGLSender<bool>), + ClientWaitSync(WebGLSyncId, u32, u64, WebGLSender<u32>), + WaitSync(WebGLSyncId, u32, i64), + GetSyncParameter(WebGLSyncId, u32, WebGLSender<u32>), + DeleteSync(WebGLSyncId), + Hint(u32, u32), + LineWidth(f32), + PixelStorei(u32, i32), + LinkProgram(WebGLProgramId, WebGLSender<ProgramLinkInfo>), + Uniform1f(i32, f32), + Uniform1fv(i32, Vec<f32>), + Uniform1i(i32, i32), + Uniform1ui(i32, u32), + Uniform1iv(i32, Vec<i32>), + Uniform1uiv(i32, Vec<u32>), + Uniform2f(i32, f32, f32), + Uniform2fv(i32, Vec<f32>), + Uniform2i(i32, i32, i32), + Uniform2ui(i32, u32, u32), + Uniform2iv(i32, Vec<i32>), + Uniform2uiv(i32, Vec<u32>), + Uniform3f(i32, f32, f32, f32), + Uniform3fv(i32, Vec<f32>), + Uniform3i(i32, i32, i32, i32), + Uniform3ui(i32, u32, u32, u32), + Uniform3iv(i32, Vec<i32>), + Uniform3uiv(i32, Vec<u32>), + Uniform4f(i32, f32, f32, f32, f32), + Uniform4fv(i32, Vec<f32>), + Uniform4i(i32, i32, i32, i32, i32), + Uniform4ui(i32, u32, u32, u32, u32), + Uniform4iv(i32, Vec<i32>), + Uniform4uiv(i32, Vec<u32>), + UniformMatrix2fv(i32, Vec<f32>), + UniformMatrix3fv(i32, Vec<f32>), + UniformMatrix4fv(i32, Vec<f32>), + UniformMatrix3x2fv(i32, Vec<f32>), + UniformMatrix4x2fv(i32, Vec<f32>), + UniformMatrix2x3fv(i32, Vec<f32>), + UniformMatrix4x3fv(i32, Vec<f32>), + UniformMatrix2x4fv(i32, Vec<f32>), + UniformMatrix3x4fv(i32, Vec<f32>), + UseProgram(Option<WebGLProgramId>), + ValidateProgram(WebGLProgramId), + VertexAttrib(u32, f32, f32, f32, f32), + VertexAttribI(u32, i32, i32, i32, i32), + VertexAttribU(u32, u32, u32, u32, u32), + VertexAttribPointer(u32, i32, u32, bool, i32, u32), + VertexAttribPointer2f(u32, i32, bool, i32, u32), + SetViewport(i32, i32, i32, i32), + TexImage2D { + target: u32, + level: u32, + internal_format: TexFormat, + size: Size2D<u32>, + format: TexFormat, + data_type: TexDataType, + // FIXME(nox): This should be computed on the WebGL thread. + effective_data_type: u32, + unpacking_alignment: u32, + alpha_treatment: Option<AlphaTreatment>, + y_axis_treatment: YAxisTreatment, + pixel_format: Option<PixelFormat>, + data: TruncatedDebug<IpcSharedMemory>, + }, + TexImage2DPBO { + target: u32, + level: u32, + internal_format: TexFormat, + size: Size2D<u32>, + format: TexFormat, + effective_data_type: u32, + unpacking_alignment: u32, + offset: i64, + }, + TexSubImage2D { + target: u32, + level: u32, + xoffset: i32, + yoffset: i32, + size: Size2D<u32>, + format: TexFormat, + data_type: TexDataType, + // FIXME(nox): This should be computed on the WebGL thread. + effective_data_type: u32, + unpacking_alignment: u32, + alpha_treatment: Option<AlphaTreatment>, + y_axis_treatment: YAxisTreatment, + pixel_format: Option<PixelFormat>, + data: TruncatedDebug<IpcSharedMemory>, + }, + CompressedTexImage2D { + target: u32, + level: u32, + internal_format: u32, + size: Size2D<u32>, + data: TruncatedDebug<IpcSharedMemory>, + }, + CompressedTexSubImage2D { + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + size: Size2D<u32>, + format: u32, + data: TruncatedDebug<IpcSharedMemory>, + }, + DrawingBufferWidth(WebGLSender<i32>), + DrawingBufferHeight(WebGLSender<i32>), + Finish(WebGLSender<()>), + Flush, + GenerateMipmap(u32), + CreateVertexArray(WebGLSender<Option<WebGLVertexArrayId>>), + DeleteVertexArray(WebGLVertexArrayId), + BindVertexArray(Option<WebGLVertexArrayId>), + GetParameterBool(ParameterBool, WebGLSender<bool>), + GetParameterBool4(ParameterBool4, WebGLSender<[bool; 4]>), + GetParameterInt(ParameterInt, WebGLSender<i32>), + GetParameterInt2(ParameterInt2, WebGLSender<[i32; 2]>), + GetParameterInt4(ParameterInt4, WebGLSender<[i32; 4]>), + GetParameterFloat(ParameterFloat, WebGLSender<f32>), + GetParameterFloat2(ParameterFloat2, WebGLSender<[f32; 2]>), + GetParameterFloat4(ParameterFloat4, WebGLSender<[f32; 4]>), + GetProgramValidateStatus(WebGLProgramId, WebGLSender<bool>), + GetProgramActiveUniforms(WebGLProgramId, WebGLSender<i32>), + GetCurrentVertexAttrib(u32, WebGLSender<[f32; 4]>), + GetTexParameterFloat(u32, TexParameterFloat, WebGLSender<f32>), + GetTexParameterInt(u32, TexParameterInt, WebGLSender<i32>), + GetTexParameterBool(u32, TexParameterBool, WebGLSender<bool>), + GetInternalFormatIntVec(u32, u32, InternalFormatIntVec, WebGLSender<Vec<i32>>), + TexParameteri(u32, u32, i32), + TexParameterf(u32, u32, f32), + TexStorage2D(u32, u32, TexFormat, u32, u32), + TexStorage3D(u32, u32, TexFormat, u32, u32, u32), + DrawArrays { + mode: u32, + first: i32, + count: i32, + }, + DrawArraysInstanced { + mode: u32, + first: i32, + count: i32, + primcount: i32, + }, + DrawElements { + mode: u32, + count: i32, + type_: u32, + offset: u32, + }, + DrawElementsInstanced { + mode: u32, + count: i32, + type_: u32, + offset: u32, + primcount: i32, + }, + VertexAttribDivisor { + index: u32, + divisor: u32, + }, + GetUniformBool(WebGLProgramId, i32, WebGLSender<bool>), + GetUniformBool2(WebGLProgramId, i32, WebGLSender<[bool; 2]>), + GetUniformBool3(WebGLProgramId, i32, WebGLSender<[bool; 3]>), + GetUniformBool4(WebGLProgramId, i32, WebGLSender<[bool; 4]>), + GetUniformInt(WebGLProgramId, i32, WebGLSender<i32>), + GetUniformInt2(WebGLProgramId, i32, WebGLSender<[i32; 2]>), + GetUniformInt3(WebGLProgramId, i32, WebGLSender<[i32; 3]>), + GetUniformInt4(WebGLProgramId, i32, WebGLSender<[i32; 4]>), + GetUniformUint(WebGLProgramId, i32, WebGLSender<u32>), + GetUniformUint2(WebGLProgramId, i32, WebGLSender<[u32; 2]>), + GetUniformUint3(WebGLProgramId, i32, WebGLSender<[u32; 3]>), + GetUniformUint4(WebGLProgramId, i32, WebGLSender<[u32; 4]>), + GetUniformFloat(WebGLProgramId, i32, WebGLSender<f32>), + GetUniformFloat2(WebGLProgramId, i32, WebGLSender<[f32; 2]>), + GetUniformFloat3(WebGLProgramId, i32, WebGLSender<[f32; 3]>), + GetUniformFloat4(WebGLProgramId, i32, WebGLSender<[f32; 4]>), + GetUniformFloat9(WebGLProgramId, i32, WebGLSender<[f32; 9]>), + GetUniformFloat16(WebGLProgramId, i32, WebGLSender<[f32; 16]>), + GetUniformFloat2x3(WebGLProgramId, i32, WebGLSender<[f32; 2 * 3]>), + GetUniformFloat2x4(WebGLProgramId, i32, WebGLSender<[f32; 2 * 4]>), + GetUniformFloat3x2(WebGLProgramId, i32, WebGLSender<[f32; 3 * 2]>), + GetUniformFloat3x4(WebGLProgramId, i32, WebGLSender<[f32; 3 * 4]>), + GetUniformFloat4x2(WebGLProgramId, i32, WebGLSender<[f32; 4 * 2]>), + GetUniformFloat4x3(WebGLProgramId, i32, WebGLSender<[f32; 4 * 3]>), + GetUniformBlockIndex(WebGLProgramId, String, WebGLSender<u32>), + GetUniformIndices(WebGLProgramId, Vec<String>, WebGLSender<Vec<u32>>), + GetActiveUniforms(WebGLProgramId, Vec<u32>, u32, WebGLSender<Vec<i32>>), + GetActiveUniformBlockName(WebGLProgramId, u32, WebGLSender<String>), + GetActiveUniformBlockParameter(WebGLProgramId, u32, u32, WebGLSender<Vec<i32>>), + UniformBlockBinding(WebGLProgramId, u32, u32), + InitializeFramebuffer { + color: bool, + depth: bool, + stencil: bool, + }, + BeginQuery(u32, WebGLQueryId), + DeleteQuery(WebGLQueryId), + EndQuery(u32), + GenerateQuery(WebGLSender<WebGLQueryId>), + GetQueryState(WebGLSender<u32>, WebGLQueryId, u32), + GenerateSampler(WebGLSender<WebGLSamplerId>), + DeleteSampler(WebGLSamplerId), + BindSampler(u32, WebGLSamplerId), + SetSamplerParameterFloat(WebGLSamplerId, u32, f32), + SetSamplerParameterInt(WebGLSamplerId, u32, i32), + GetSamplerParameterFloat(WebGLSamplerId, u32, WebGLSender<f32>), + GetSamplerParameterInt(WebGLSamplerId, u32, WebGLSender<i32>), + BindBufferBase(u32, u32, Option<WebGLBufferId>), + BindBufferRange(u32, u32, Option<WebGLBufferId>, i64, i64), + ClearBufferfv(u32, i32, Vec<f32>), + ClearBufferiv(u32, i32, Vec<i32>), + ClearBufferuiv(u32, i32, Vec<u32>), + ClearBufferfi(u32, i32, f32, i32), + InvalidateFramebuffer(u32, Vec<u32>), + InvalidateSubFramebuffer(u32, Vec<u32>, i32, i32, i32, i32), + FramebufferTextureLayer(u32, u32, Option<WebGLTextureId>, i32, i32), + ReadBuffer(u32), + DrawBuffers(Vec<u32>), +} + +/// WebXR layer management +#[derive(Debug, Deserialize, Serialize)] +pub enum WebXRCommand { + CreateLayerManager(WebGLSender<Result<WebXRLayerManagerId, WebXRError>>), + DestroyLayerManager(WebXRLayerManagerId), + CreateLayer( + WebXRLayerManagerId, + WebXRContextId, + WebXRLayerInit, + WebGLSender<Result<WebXRLayerId, WebXRError>>, + ), + DestroyLayer(WebXRLayerManagerId, WebXRContextId, WebXRLayerId), + BeginFrame( + WebXRLayerManagerId, + Vec<(WebXRContextId, WebXRLayerId)>, + WebGLSender<Result<Vec<WebXRSubImages>, WebXRError>>, + ), + EndFrame( + WebXRLayerManagerId, + Vec<(WebXRContextId, WebXRLayerId)>, + WebGLSender<Result<(), WebXRError>>, + ), +} + +macro_rules! nonzero_type { + (u32) => { + NonZeroU32 + }; + (u64) => { + NonZeroU64 + }; +} + +macro_rules! define_resource_id { + ($name:ident, $type:tt) => { + #[derive(Clone, Copy, Eq, Hash, PartialEq)] + pub struct $name(nonzero_type!($type)); + + impl $name { + #[allow(unsafe_code)] + #[inline] + pub unsafe fn new(id: $type) -> Self { + $name(<nonzero_type!($type)>::new_unchecked(id)) + } + + #[inline] + pub fn maybe_new(id: $type) -> Option<Self> { + <nonzero_type!($type)>::new(id).map($name) + } + + #[inline] + pub fn get(self) -> $type { + self.0.get() + } + } + + #[allow(unsafe_code)] + impl<'de> ::serde::Deserialize<'de> for $name { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: ::serde::Deserializer<'de>, + { + let id = <$type>::deserialize(deserializer)?; + if id == 0 { + Err(::serde::de::Error::custom("expected a non-zero value")) + } else { + Ok(unsafe { $name::new(id) }) + } + } + } + + impl ::serde::Serialize for $name { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: ::serde::Serializer, + { + self.get().serialize(serializer) + } + } + + impl ::std::fmt::Debug for $name { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + fmt.debug_tuple(stringify!($name)) + .field(&self.get()) + .finish() + } + } + + impl ::std::fmt::Display for $name { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + write!(fmt, "{}", self.get()) + } + } + + impl ::malloc_size_of::MallocSizeOf for $name { + fn size_of(&self, _ops: &mut ::malloc_size_of::MallocSizeOfOps) -> usize { + 0 + } + } + }; +} + +define_resource_id!(WebGLBufferId, u32); +define_resource_id!(WebGLFramebufferId, u32); +define_resource_id!(WebGLRenderbufferId, u32); +define_resource_id!(WebGLTextureId, u32); +define_resource_id!(WebGLProgramId, u32); +define_resource_id!(WebGLQueryId, u32); +define_resource_id!(WebGLSamplerId, u32); +define_resource_id!(WebGLShaderId, u32); +define_resource_id!(WebGLSyncId, u64); +define_resource_id!(WebGLVertexArrayId, u32); +define_resource_id!(WebXRLayerManagerId, u32); + +#[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize, +)] +pub struct WebGLContextId(pub u64); + +impl From<WebXRContextId> for WebGLContextId { + fn from(id: WebXRContextId) -> Self { + Self(id.0) + } +} + +impl From<WebGLContextId> for WebXRContextId { + fn from(id: WebGLContextId) -> Self { + Self(id.0) + } +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub enum WebGLError { + InvalidEnum, + InvalidFramebufferOperation, + InvalidOperation, + InvalidValue, + OutOfMemory, + ContextLost, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum WebGLFramebufferBindingRequest { + Explicit(WebGLFramebufferId), + Default, +} + +pub type WebGLResult<T> = Result<T, WebGLError>; + +/// Information about a WebGL program linking operation. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ProgramLinkInfo { + /// Whether the program was linked successfully. + pub linked: bool, + /// The list of active attributes. + pub active_attribs: Box<[ActiveAttribInfo]>, + /// The list of active uniforms. + pub active_uniforms: Box<[ActiveUniformInfo]>, + /// The list of active uniform blocks. + pub active_uniform_blocks: Box<[ActiveUniformBlockInfo]>, + /// The number of varying variables + pub transform_feedback_length: i32, + /// The buffer mode used when transform feedback is active + pub transform_feedback_mode: i32, +} + +/// Description of a single active attribute. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct ActiveAttribInfo { + /// The name of the attribute. + pub name: String, + /// The size of the attribute. + pub size: i32, + /// The type of the attribute. + pub type_: u32, + /// The location of the attribute. + pub location: i32, +} + +/// Description of a single active uniform. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct ActiveUniformInfo { + /// The base name of the uniform. + pub base_name: Box<str>, + /// The size of the uniform, if it is an array. + pub size: Option<i32>, + /// The type of the uniform. + pub type_: u32, + /// The index of the indexed uniform buffer binding, if it is bound. + pub bind_index: Option<u32>, +} + +impl ActiveUniformInfo { + pub fn name(&self) -> Cow<str> { + if self.size.is_some() { + let mut name = String::from(&*self.base_name); + name.push_str("[0]"); + Cow::Owned(name) + } else { + Cow::Borrowed(&self.base_name) + } + } +} + +/// Description of a single uniform block. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct ActiveUniformBlockInfo { + /// The name of the uniform block. + pub name: String, + /// The size of the uniform block. + pub size: i32, +} + +macro_rules! parameters { + ($name:ident { $( + $variant:ident($kind:ident { $( + $param:ident = gl::$value:ident, + )+ }), + )+ }) => { + #[derive(Clone, Copy, Debug, Deserialize, Serialize)] + pub enum $name { $( + $variant($kind), + )+} + + $( + #[derive(Clone, Copy, Debug, Deserialize, Serialize)] + #[repr(u32)] + pub enum $kind { $( + $param = gl::$value, + )+} + )+ + + impl $name { + pub fn from_u32(value: u32) -> WebGLResult<Self> { + match value { + $($(gl::$value => Ok($name::$variant($kind::$param)),)+)+ + _ => Err(WebGLError::InvalidEnum) + } + } + } + } +} + +parameters! { + Parameter { + Bool(ParameterBool { + DepthWritemask = gl::DEPTH_WRITEMASK, + SampleCoverageInvert = gl::SAMPLE_COVERAGE_INVERT, + TransformFeedbackActive = gl::TRANSFORM_FEEDBACK_ACTIVE, + TransformFeedbackPaused = gl::TRANSFORM_FEEDBACK_PAUSED, + RasterizerDiscard = gl::RASTERIZER_DISCARD, + }), + Bool4(ParameterBool4 { + ColorWritemask = gl::COLOR_WRITEMASK, + }), + Int(ParameterInt { + ActiveTexture = gl::ACTIVE_TEXTURE, + AlphaBits = gl::ALPHA_BITS, + BlendDstAlpha = gl::BLEND_DST_ALPHA, + BlendDstRgb = gl::BLEND_DST_RGB, + BlendEquationAlpha = gl::BLEND_EQUATION_ALPHA, + BlendEquationRgb = gl::BLEND_EQUATION_RGB, + BlendSrcAlpha = gl::BLEND_SRC_ALPHA, + BlendSrcRgb = gl::BLEND_SRC_RGB, + BlueBits = gl::BLUE_BITS, + CullFaceMode = gl::CULL_FACE_MODE, + DepthBits = gl::DEPTH_BITS, + DepthFunc = gl::DEPTH_FUNC, + FragmentShaderDerivativeHint = gl::FRAGMENT_SHADER_DERIVATIVE_HINT, + FrontFace = gl::FRONT_FACE, + GenerateMipmapHint = gl::GENERATE_MIPMAP_HINT, + GreenBits = gl::GREEN_BITS, + RedBits = gl::RED_BITS, + SampleBuffers = gl::SAMPLE_BUFFERS, + Samples = gl::SAMPLES, + StencilBackFail = gl::STENCIL_BACK_FAIL, + StencilBackFunc = gl::STENCIL_BACK_FUNC, + StencilBackPassDepthFail = gl::STENCIL_BACK_PASS_DEPTH_FAIL, + StencilBackPassDepthPass = gl::STENCIL_BACK_PASS_DEPTH_PASS, + StencilBackRef = gl::STENCIL_BACK_REF, + StencilBackValueMask = gl::STENCIL_BACK_VALUE_MASK, + StencilBackWritemask = gl::STENCIL_BACK_WRITEMASK, + StencilBits = gl::STENCIL_BITS, + StencilClearValue = gl::STENCIL_CLEAR_VALUE, + StencilFail = gl::STENCIL_FAIL, + StencilFunc = gl::STENCIL_FUNC, + StencilPassDepthFail = gl::STENCIL_PASS_DEPTH_FAIL, + StencilPassDepthPass = gl::STENCIL_PASS_DEPTH_PASS, + StencilRef = gl::STENCIL_REF, + StencilValueMask = gl::STENCIL_VALUE_MASK, + StencilWritemask = gl::STENCIL_WRITEMASK, + SubpixelBits = gl::SUBPIXEL_BITS, + TransformFeedbackBinding = gl::TRANSFORM_FEEDBACK_BINDING, + MaxTransformFeedbackInterleavedComponents = gl::MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, + MaxTransformFeedbackSeparateAttribs = gl::MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, + MaxTransformFeedbackSeparateComponents = gl::MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS, + TransformFeedbackBufferSize = gl::TRANSFORM_FEEDBACK_BUFFER_SIZE, + TransformFeedbackBufferStart = gl::TRANSFORM_FEEDBACK_BUFFER_START, + PackRowLength = gl::PACK_ROW_LENGTH, + PackSkipPixels = gl::PACK_SKIP_PIXELS, + PackSkipRows = gl::PACK_SKIP_ROWS, + UnpackImageHeight = gl::UNPACK_IMAGE_HEIGHT, + UnpackRowLength = gl::UNPACK_ROW_LENGTH, + UnpackSkipImages = gl::UNPACK_SKIP_IMAGES, + UnpackSkipPixels = gl::UNPACK_SKIP_PIXELS, + UnpackSkipRows = gl::UNPACK_SKIP_ROWS, + }), + Int2(ParameterInt2 { + MaxViewportDims = gl::MAX_VIEWPORT_DIMS, + }), + Int4(ParameterInt4 { + ScissorBox = gl::SCISSOR_BOX, + Viewport = gl::VIEWPORT, + }), + Float(ParameterFloat { + DepthClearValue = gl::DEPTH_CLEAR_VALUE, + LineWidth = gl::LINE_WIDTH, + MaxTextureMaxAnisotropyExt = gl::MAX_TEXTURE_MAX_ANISOTROPY_EXT, + PolygonOffsetFactor = gl::POLYGON_OFFSET_FACTOR, + PolygonOffsetUnits = gl::POLYGON_OFFSET_UNITS, + SampleCoverageValue = gl::SAMPLE_COVERAGE_VALUE, + }), + Float2(ParameterFloat2 { + AliasedPointSizeRange = gl::ALIASED_POINT_SIZE_RANGE, + AliasedLineWidthRange = gl::ALIASED_LINE_WIDTH_RANGE, + DepthRange = gl::DEPTH_RANGE, + }), + Float4(ParameterFloat4 { + BlendColor = gl::BLEND_COLOR, + ColorClearValue = gl::COLOR_CLEAR_VALUE, + }), + } +} + +parameters! { + TexParameter { + Float(TexParameterFloat { + TextureMaxAnisotropyExt = gl::TEXTURE_MAX_ANISOTROPY_EXT, + TextureMaxLod = gl::TEXTURE_MAX_LOD, + TextureMinLod = gl::TEXTURE_MIN_LOD, + }), + Int(TexParameterInt { + TextureWrapS = gl::TEXTURE_WRAP_S, + TextureWrapT = gl::TEXTURE_WRAP_T, + TextureWrapR = gl::TEXTURE_WRAP_R, + TextureBaseLevel = gl::TEXTURE_BASE_LEVEL, + TextureMinFilter = gl::TEXTURE_MIN_FILTER, + TextureMagFilter = gl::TEXTURE_MAG_FILTER, + TextureMaxLevel = gl::TEXTURE_MAX_LEVEL, + TextureCompareFunc = gl::TEXTURE_COMPARE_FUNC, + TextureCompareMode = gl::TEXTURE_COMPARE_MODE, + TextureImmutableLevels = gl::TEXTURE_IMMUTABLE_LEVELS, + }), + Bool(TexParameterBool { + TextureImmutableFormat = gl::TEXTURE_IMMUTABLE_FORMAT, + }), + } +} + +impl TexParameter { + pub fn required_webgl_version(self) -> WebGLVersion { + match self { + Self::Float(TexParameterFloat::TextureMaxAnisotropyExt) | + Self::Int(TexParameterInt::TextureWrapS) | + Self::Int(TexParameterInt::TextureWrapT) => WebGLVersion::WebGL1, + _ => WebGLVersion::WebGL2, + } + } +} + +parameters! { + InternalFormatParameter { + IntVec(InternalFormatIntVec { + Samples = gl::SAMPLES, + }), + } +} + +#[macro_export] +macro_rules! gl_enums { + ($(pub enum $name:ident { $($variant:ident = $mod:ident::$constant:ident,)+ })*) => { + $( + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, malloc_size_of_derive::MallocSizeOf)] + #[derive(PartialEq, Serialize)] + #[repr(u32)] + pub enum $name { $($variant = $mod::$constant,)+ } + + impl $name { + pub fn from_gl_constant(constant: u32) -> Option<Self> { + Some(match constant { + $($mod::$constant => $name::$variant, )+ + _ => return None, + }) + } + + #[inline] + pub fn as_gl_constant(&self) -> u32 { + *self as u32 + } + } + )* + } +} + +// FIXME: These should come from sparkle +mod gl_ext_constants { + use sparkle::gl::types::GLenum; + + pub const COMPRESSED_RGB_S3TC_DXT1_EXT: GLenum = 0x83F0; + pub const COMPRESSED_RGBA_S3TC_DXT1_EXT: GLenum = 0x83F1; + pub const COMPRESSED_RGBA_S3TC_DXT3_EXT: GLenum = 0x83F2; + pub const COMPRESSED_RGBA_S3TC_DXT5_EXT: GLenum = 0x83F3; + pub const COMPRESSED_RGB_ETC1_WEBGL: GLenum = 0x8D64; + + pub static COMPRESSIONS: &'static [GLenum] = &[ + COMPRESSED_RGB_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT3_EXT, + COMPRESSED_RGBA_S3TC_DXT5_EXT, + COMPRESSED_RGB_ETC1_WEBGL, + ]; + + pub const ALPHA16F_ARB: u32 = 0x881C; + pub const ALPHA32F_ARB: u32 = 0x8816; + pub const LUMINANCE16F_ARB: u32 = 0x881E; + pub const LUMINANCE32F_ARB: u32 = 0x8818; + pub const LUMINANCE_ALPHA16F_ARB: u32 = 0x881F; + pub const LUMINANCE_ALPHA32F_ARB: u32 = 0x8819; +} + +gl_enums! { + pub enum TexFormat { + DepthComponent = gl::DEPTH_COMPONENT, + DepthStencil = gl::DEPTH_STENCIL, + Alpha = gl::ALPHA, + Alpha32f = gl_ext_constants::ALPHA32F_ARB, + Alpha16f = gl_ext_constants::ALPHA16F_ARB, + Red = gl::RED, + RedInteger = gl::RED_INTEGER, + RG = gl::RG, + RGInteger = gl::RG_INTEGER, + RGB = gl::RGB, + RGBInteger = gl::RGB_INTEGER, + RGBA = gl::RGBA, + RGBAInteger = gl::RGBA_INTEGER, + Luminance = gl::LUMINANCE, + LuminanceAlpha = gl::LUMINANCE_ALPHA, + Luminance32f = gl_ext_constants::LUMINANCE32F_ARB, + Luminance16f = gl_ext_constants::LUMINANCE16F_ARB, + LuminanceAlpha32f = gl_ext_constants::LUMINANCE_ALPHA32F_ARB, + LuminanceAlpha16f = gl_ext_constants::LUMINANCE_ALPHA16F_ARB, + CompressedRgbS3tcDxt1 = gl_ext_constants::COMPRESSED_RGB_S3TC_DXT1_EXT, + CompressedRgbaS3tcDxt1 = gl_ext_constants::COMPRESSED_RGBA_S3TC_DXT1_EXT, + CompressedRgbaS3tcDxt3 = gl_ext_constants::COMPRESSED_RGBA_S3TC_DXT3_EXT, + CompressedRgbaS3tcDxt5 = gl_ext_constants::COMPRESSED_RGBA_S3TC_DXT5_EXT, + CompressedRgbEtc1 = gl_ext_constants::COMPRESSED_RGB_ETC1_WEBGL, + R8 = gl::R8, + R8SNorm = gl::R8_SNORM, + R16f = gl::R16F, + R32f = gl::R32F, + R8ui = gl::R8UI, + R8i = gl::R8I, + R16ui = gl::R16UI, + R16i = gl::R16I, + R32ui = gl::R32UI, + R32i = gl::R32I, + RG8 = gl::RG8, + RG8SNorm = gl::RG8_SNORM, + RG16f = gl::RG16F, + RG32f = gl::RG32F, + RG8ui = gl::RG8UI, + RG8i = gl::RG8I, + RG16ui = gl::RG16UI, + RG16i = gl::RG16I, + RG32ui = gl::RG32UI, + RG32i = gl::RG32I, + RGB8 = gl::RGB8, + SRGB8 = gl::SRGB8, + RGB565 = gl::RGB565, + RGB8SNorm = gl::RGB8_SNORM, + R11fG11fB10f = gl::R11F_G11F_B10F, + RGB9E5 = gl::RGB9_E5, + RGB16f = gl::RGB16F, + RGB32f = gl::RGB32F, + RGB8ui = gl::RGB8UI, + RGB8i = gl::RGB8I, + RGB16ui = gl::RGB16UI, + RGB16i = gl::RGB16I, + RGB32ui = gl::RGB32UI, + RGB32i = gl::RGB32I, + RGBA8 = gl::RGBA8, + SRGB8Alpha8 = gl::SRGB8_ALPHA8, + RGBA8SNorm = gl::RGBA8_SNORM, + RGB5A1 = gl::RGB5_A1, + RGBA4 = gl::RGBA4, + RGB10A2 = gl::RGB10_A2, + RGBA16f = gl::RGBA16F, + RGBA32f = gl::RGBA32F, + RGBA8ui = gl::RGBA8UI, + RGBA8i = gl::RGBA8I, + RGB10A2ui = gl::RGB10_A2UI, + RGBA16ui = gl::RGBA16UI, + RGBA16i = gl::RGBA16I, + RGBA32i = gl::RGBA32I, + RGBA32ui = gl::RGBA32UI, + DepthComponent16 = gl::DEPTH_COMPONENT16, + DepthComponent24 = gl::DEPTH_COMPONENT24, + DepthComponent32f = gl::DEPTH_COMPONENT32F, + Depth24Stencil8 = gl::DEPTH24_STENCIL8, + Depth32fStencil8 = gl::DEPTH32F_STENCIL8, + } + + pub enum TexDataType { + Byte = gl::BYTE, + Int = gl::INT, + Short = gl::SHORT, + UnsignedByte = gl::UNSIGNED_BYTE, + UnsignedInt = gl::UNSIGNED_INT, + UnsignedInt10f11f11fRev = gl::UNSIGNED_INT_10F_11F_11F_REV, + UnsignedInt2101010Rev = gl::UNSIGNED_INT_2_10_10_10_REV, + UnsignedInt5999Rev = gl::UNSIGNED_INT_5_9_9_9_REV, + UnsignedInt248 = gl::UNSIGNED_INT_24_8, + UnsignedShort = gl::UNSIGNED_SHORT, + UnsignedShort4444 = gl::UNSIGNED_SHORT_4_4_4_4, + UnsignedShort5551 = gl::UNSIGNED_SHORT_5_5_5_1, + UnsignedShort565 = gl::UNSIGNED_SHORT_5_6_5, + Float = gl::FLOAT, + HalfFloat = gl::HALF_FLOAT_OES, + Float32UnsignedInt248Rev = gl::FLOAT_32_UNSIGNED_INT_24_8_REV, + } +} + +impl TexFormat { + /// Returns how many components does this format need. For example, RGBA + /// needs 4 components, while RGB requires 3. + pub fn components(&self) -> u32 { + match self.to_unsized() { + TexFormat::DepthStencil => 2, + TexFormat::LuminanceAlpha => 2, + TexFormat::RG | TexFormat::RGInteger => 2, + TexFormat::RGB | TexFormat::RGBInteger => 3, + TexFormat::RGBA | TexFormat::RGBAInteger => 4, + _ => 1, + } + } + + /// Returns whether this format is a known texture compression format. + pub fn is_compressed(&self) -> bool { + gl_ext_constants::COMPRESSIONS.contains(&self.as_gl_constant()) + } + + /// Returns whether this format is a known sized or unsized format. + pub fn is_sized(&self) -> bool { + match self { + TexFormat::DepthComponent | + TexFormat::DepthStencil | + TexFormat::Alpha | + TexFormat::Red | + TexFormat::RG | + TexFormat::RGB | + TexFormat::RGBA | + TexFormat::Luminance | + TexFormat::LuminanceAlpha => false, + _ => true, + } + } + + pub fn to_unsized(self) -> TexFormat { + match self { + TexFormat::R8 => TexFormat::Red, + TexFormat::R8SNorm => TexFormat::Red, + TexFormat::R16f => TexFormat::Red, + TexFormat::R32f => TexFormat::Red, + TexFormat::R8ui => TexFormat::RedInteger, + TexFormat::R8i => TexFormat::RedInteger, + TexFormat::R16ui => TexFormat::RedInteger, + TexFormat::R16i => TexFormat::RedInteger, + TexFormat::R32ui => TexFormat::RedInteger, + TexFormat::R32i => TexFormat::RedInteger, + TexFormat::RG8 => TexFormat::RG, + TexFormat::RG8SNorm => TexFormat::RG, + TexFormat::RG16f => TexFormat::RG, + TexFormat::RG32f => TexFormat::RG, + TexFormat::RG8ui => TexFormat::RGInteger, + TexFormat::RG8i => TexFormat::RGInteger, + TexFormat::RG16ui => TexFormat::RGInteger, + TexFormat::RG16i => TexFormat::RGInteger, + TexFormat::RG32ui => TexFormat::RGInteger, + TexFormat::RG32i => TexFormat::RGInteger, + TexFormat::RGB8 => TexFormat::RGB, + TexFormat::SRGB8 => TexFormat::RGB, + TexFormat::RGB565 => TexFormat::RGB, + TexFormat::RGB8SNorm => TexFormat::RGB, + TexFormat::R11fG11fB10f => TexFormat::RGB, + TexFormat::RGB9E5 => TexFormat::RGB, + TexFormat::RGB16f => TexFormat::RGB, + TexFormat::RGB32f => TexFormat::RGB, + TexFormat::RGB8ui => TexFormat::RGBInteger, + TexFormat::RGB8i => TexFormat::RGBInteger, + TexFormat::RGB16ui => TexFormat::RGBInteger, + TexFormat::RGB16i => TexFormat::RGBInteger, + TexFormat::RGB32ui => TexFormat::RGBInteger, + TexFormat::RGB32i => TexFormat::RGBInteger, + TexFormat::RGBA8 => TexFormat::RGBA, + TexFormat::SRGB8Alpha8 => TexFormat::RGBA, + TexFormat::RGBA8SNorm => TexFormat::RGBA, + TexFormat::RGB5A1 => TexFormat::RGBA, + TexFormat::RGBA4 => TexFormat::RGBA, + TexFormat::RGB10A2 => TexFormat::RGBA, + TexFormat::RGBA16f => TexFormat::RGBA, + TexFormat::RGBA32f => TexFormat::RGBA, + TexFormat::RGBA8ui => TexFormat::RGBAInteger, + TexFormat::RGBA8i => TexFormat::RGBAInteger, + TexFormat::RGB10A2ui => TexFormat::RGBAInteger, + TexFormat::RGBA16ui => TexFormat::RGBAInteger, + TexFormat::RGBA16i => TexFormat::RGBAInteger, + TexFormat::RGBA32i => TexFormat::RGBAInteger, + TexFormat::RGBA32ui => TexFormat::RGBAInteger, + TexFormat::DepthComponent16 => TexFormat::DepthComponent, + TexFormat::DepthComponent24 => TexFormat::DepthComponent, + TexFormat::DepthComponent32f => TexFormat::DepthComponent, + TexFormat::Depth24Stencil8 => TexFormat::DepthStencil, + TexFormat::Depth32fStencil8 => TexFormat::DepthStencil, + TexFormat::Alpha32f => TexFormat::Alpha, + TexFormat::Alpha16f => TexFormat::Alpha, + TexFormat::Luminance32f => TexFormat::Luminance, + TexFormat::Luminance16f => TexFormat::Luminance, + TexFormat::LuminanceAlpha32f => TexFormat::LuminanceAlpha, + TexFormat::LuminanceAlpha16f => TexFormat::LuminanceAlpha, + _ => self, + } + } + + pub fn compatible_data_types(self) -> &'static [TexDataType] { + match self { + TexFormat::RGB => &[ + TexDataType::UnsignedByte, + TexDataType::UnsignedShort565, + TexDataType::Float, + TexDataType::HalfFloat, + ][..], + TexFormat::RGBA => &[ + TexDataType::UnsignedByte, + TexDataType::UnsignedShort4444, + TexDataType::UnsignedShort5551, + TexDataType::Float, + TexDataType::HalfFloat, + ][..], + TexFormat::LuminanceAlpha => &[ + TexDataType::UnsignedByte, + TexDataType::Float, + TexDataType::HalfFloat, + ][..], + TexFormat::Luminance => &[ + TexDataType::UnsignedByte, + TexDataType::Float, + TexDataType::HalfFloat, + ][..], + TexFormat::Alpha => &[ + TexDataType::UnsignedByte, + TexDataType::Float, + TexDataType::HalfFloat, + ][..], + TexFormat::LuminanceAlpha32f => &[TexDataType::Float][..], + TexFormat::LuminanceAlpha16f => &[TexDataType::HalfFloat][..], + TexFormat::Luminance32f => &[TexDataType::Float][..], + TexFormat::Luminance16f => &[TexDataType::HalfFloat][..], + TexFormat::Alpha32f => &[TexDataType::Float][..], + TexFormat::Alpha16f => &[TexDataType::HalfFloat][..], + TexFormat::R8 => &[TexDataType::UnsignedByte][..], + TexFormat::R8SNorm => &[TexDataType::Byte][..], + TexFormat::R16f => &[TexDataType::HalfFloat, TexDataType::Float][..], + TexFormat::R32f => &[TexDataType::Float][..], + TexFormat::R8ui => &[TexDataType::UnsignedByte][..], + TexFormat::R8i => &[TexDataType::Byte][..], + TexFormat::R16ui => &[TexDataType::UnsignedShort][..], + TexFormat::R16i => &[TexDataType::Short][..], + TexFormat::R32ui => &[TexDataType::UnsignedInt][..], + TexFormat::R32i => &[TexDataType::Int][..], + TexFormat::RG8 => &[TexDataType::UnsignedByte][..], + TexFormat::RG8SNorm => &[TexDataType::Byte][..], + TexFormat::RG16f => &[TexDataType::HalfFloat, TexDataType::Float][..], + TexFormat::RG32f => &[TexDataType::Float][..], + TexFormat::RG8ui => &[TexDataType::UnsignedByte][..], + TexFormat::RG8i => &[TexDataType::Byte][..], + TexFormat::RG16ui => &[TexDataType::UnsignedShort][..], + TexFormat::RG16i => &[TexDataType::Short][..], + TexFormat::RG32ui => &[TexDataType::UnsignedInt][..], + TexFormat::RG32i => &[TexDataType::Int][..], + TexFormat::RGB8 => &[TexDataType::UnsignedByte][..], + TexFormat::SRGB8 => &[TexDataType::UnsignedByte][..], + TexFormat::RGB565 => &[TexDataType::UnsignedByte, TexDataType::UnsignedShort565][..], + TexFormat::RGB8SNorm => &[TexDataType::Byte][..], + TexFormat::R11fG11fB10f => &[ + TexDataType::UnsignedInt10f11f11fRev, + TexDataType::HalfFloat, + TexDataType::Float, + ][..], + TexFormat::RGB9E5 => &[ + TexDataType::UnsignedInt5999Rev, + TexDataType::HalfFloat, + TexDataType::Float, + ][..], + TexFormat::RGB16f => &[TexDataType::HalfFloat, TexDataType::Float][..], + TexFormat::RGB32f => &[TexDataType::Float][..], + TexFormat::RGB8ui => &[TexDataType::UnsignedByte][..], + TexFormat::RGB8i => &[TexDataType::Byte][..], + TexFormat::RGB16ui => &[TexDataType::UnsignedShort][..], + TexFormat::RGB16i => &[TexDataType::Short][..], + TexFormat::RGB32ui => &[TexDataType::UnsignedInt][..], + TexFormat::RGB32i => &[TexDataType::Int][..], + TexFormat::RGBA8 => &[TexDataType::UnsignedByte][..], + TexFormat::SRGB8Alpha8 => &[TexDataType::UnsignedByte][..], + TexFormat::RGBA8SNorm => &[TexDataType::Byte][..], + TexFormat::RGB5A1 => &[ + TexDataType::UnsignedByte, + TexDataType::UnsignedShort5551, + TexDataType::UnsignedInt2101010Rev, + ][..], + TexFormat::RGBA4 => &[TexDataType::UnsignedByte, TexDataType::UnsignedShort4444][..], + TexFormat::RGB10A2 => &[TexDataType::UnsignedInt2101010Rev][..], + TexFormat::RGBA16f => &[TexDataType::HalfFloat, TexDataType::Float][..], + TexFormat::RGBA32f => &[TexDataType::Float][..], + TexFormat::RGBA8ui => &[TexDataType::UnsignedByte][..], + TexFormat::RGBA8i => &[TexDataType::Byte][..], + TexFormat::RGB10A2ui => &[TexDataType::UnsignedInt2101010Rev][..], + TexFormat::RGBA16ui => &[TexDataType::UnsignedShort][..], + TexFormat::RGBA16i => &[TexDataType::Short][..], + TexFormat::RGBA32i => &[TexDataType::Int][..], + TexFormat::RGBA32ui => &[TexDataType::UnsignedInt][..], + TexFormat::DepthComponent16 => { + &[TexDataType::UnsignedShort, TexDataType::UnsignedInt][..] + }, + TexFormat::DepthComponent24 => &[TexDataType::UnsignedInt][..], + TexFormat::DepthComponent32f => &[TexDataType::Float][..], + TexFormat::Depth24Stencil8 => &[TexDataType::UnsignedInt248][..], + TexFormat::Depth32fStencil8 => &[TexDataType::Float32UnsignedInt248Rev][..], + TexFormat::CompressedRgbS3tcDxt1 | + TexFormat::CompressedRgbaS3tcDxt1 | + TexFormat::CompressedRgbaS3tcDxt3 | + TexFormat::CompressedRgbaS3tcDxt5 => &[TexDataType::UnsignedByte][..], + _ => &[][..], + } + } + + pub fn required_webgl_version(self) -> WebGLVersion { + match self { + TexFormat::DepthComponent | + TexFormat::Alpha | + TexFormat::RGB | + TexFormat::RGBA | + TexFormat::Luminance | + TexFormat::LuminanceAlpha | + TexFormat::CompressedRgbS3tcDxt1 | + TexFormat::CompressedRgbaS3tcDxt1 | + TexFormat::CompressedRgbaS3tcDxt3 | + TexFormat::CompressedRgbaS3tcDxt5 => WebGLVersion::WebGL1, + _ => WebGLVersion::WebGL2, + } + } + + pub fn usable_as_internal(self) -> bool { + !self.compatible_data_types().is_empty() + } +} + +#[derive(PartialEq)] +pub enum SizedDataType { + Int8, + Int16, + Int32, + Uint8, + Uint16, + Uint32, + Float32, +} + +impl TexDataType { + /// Returns the compatible sized data type for this texture data type. + pub fn sized_data_type(&self) -> SizedDataType { + match self { + TexDataType::Byte => SizedDataType::Int8, + TexDataType::UnsignedByte => SizedDataType::Uint8, + TexDataType::Short => SizedDataType::Int16, + TexDataType::UnsignedShort | + TexDataType::UnsignedShort4444 | + TexDataType::UnsignedShort5551 | + TexDataType::UnsignedShort565 => SizedDataType::Uint16, + TexDataType::Int => SizedDataType::Int32, + TexDataType::UnsignedInt | + TexDataType::UnsignedInt10f11f11fRev | + TexDataType::UnsignedInt2101010Rev | + TexDataType::UnsignedInt5999Rev | + TexDataType::UnsignedInt248 => SizedDataType::Uint32, + TexDataType::HalfFloat => SizedDataType::Uint16, + TexDataType::Float | TexDataType::Float32UnsignedInt248Rev => SizedDataType::Float32, + } + } + + /// Returns the size in bytes of each element of data. + pub fn element_size(&self) -> u32 { + use self::*; + match *self { + TexDataType::Byte | TexDataType::UnsignedByte => 1, + TexDataType::Short | + TexDataType::UnsignedShort | + TexDataType::UnsignedShort4444 | + TexDataType::UnsignedShort5551 | + TexDataType::UnsignedShort565 => 2, + TexDataType::Int | + TexDataType::UnsignedInt | + TexDataType::UnsignedInt10f11f11fRev | + TexDataType::UnsignedInt2101010Rev | + TexDataType::UnsignedInt5999Rev => 4, + TexDataType::UnsignedInt248 => 4, + TexDataType::Float => 4, + TexDataType::HalfFloat => 2, + TexDataType::Float32UnsignedInt248Rev => 4, + } + } + + /// Returns how many components a single element may hold. For example, a + /// UnsignedShort4444 holds four components, each with 4 bits of data. + pub fn components_per_element(&self) -> u32 { + match *self { + TexDataType::Byte => 1, + TexDataType::UnsignedByte => 1, + TexDataType::Short => 1, + TexDataType::UnsignedShort => 1, + TexDataType::UnsignedShort565 => 3, + TexDataType::UnsignedShort5551 => 4, + TexDataType::UnsignedShort4444 => 4, + TexDataType::Int => 1, + TexDataType::UnsignedInt => 1, + TexDataType::UnsignedInt10f11f11fRev => 3, + TexDataType::UnsignedInt2101010Rev => 4, + TexDataType::UnsignedInt5999Rev => 4, + TexDataType::UnsignedInt248 => 2, + TexDataType::Float => 1, + TexDataType::HalfFloat => 1, + TexDataType::Float32UnsignedInt248Rev => 2, + } + } + + pub fn required_webgl_version(self) -> WebGLVersion { + match self { + TexDataType::UnsignedByte | + TexDataType::UnsignedShort4444 | + TexDataType::UnsignedShort5551 | + TexDataType::UnsignedShort565 | + TexDataType::Float | + TexDataType::HalfFloat => WebGLVersion::WebGL1, + _ => WebGLVersion::WebGL2, + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub enum AlphaTreatment { + Premultiply, + Unmultiply, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub enum YAxisTreatment { + AsIs, + Flipped, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct GLContextAttributes { + pub alpha: bool, + pub depth: bool, + pub stencil: bool, + pub antialias: bool, + pub premultiplied_alpha: bool, + pub preserve_drawing_buffer: bool, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct GLLimits { + pub max_vertex_attribs: u32, + pub max_tex_size: u32, + pub max_cube_map_tex_size: u32, + pub max_combined_texture_image_units: u32, + pub max_fragment_uniform_vectors: u32, + pub max_renderbuffer_size: u32, + pub max_texture_image_units: u32, + pub max_varying_vectors: u32, + pub max_vertex_texture_image_units: u32, + pub max_vertex_uniform_vectors: u32, + pub max_client_wait_timeout_webgl: std::time::Duration, + pub max_transform_feedback_separate_attribs: u32, + pub max_vertex_output_vectors: u32, + pub max_fragment_input_vectors: u32, + pub max_draw_buffers: u32, + pub max_color_attachments: u32, + pub max_uniform_buffer_bindings: u32, + pub min_program_texel_offset: i32, + pub max_program_texel_offset: u32, + pub max_uniform_block_size: u64, + pub max_combined_uniform_blocks: u32, + pub max_combined_vertex_uniform_components: u64, + pub max_combined_fragment_uniform_components: u64, + pub max_vertex_uniform_blocks: u32, + pub max_vertex_uniform_components: u32, + pub max_fragment_uniform_blocks: u32, + pub max_fragment_uniform_components: u32, + pub max_3d_texture_size: u32, + pub max_array_texture_layers: u32, + pub uniform_buffer_offset_alignment: u32, + pub max_element_index: u64, + pub max_elements_indices: u32, + pub max_elements_vertices: u32, + pub max_fragment_input_components: u32, + pub max_samples: u32, + pub max_server_wait_timeout: std::time::Duration, + pub max_texture_lod_bias: f32, + pub max_varying_components: u32, + pub max_vertex_output_components: u32, +} diff --git a/components/shared/canvas/webgl_channel/ipc.rs b/components/shared/canvas/webgl_channel/ipc.rs new file mode 100644 index 00000000000..be320a69624 --- /dev/null +++ b/components/shared/canvas/webgl_channel/ipc.rs @@ -0,0 +1,15 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::io; + +use serde::{Deserialize, Serialize}; + +pub type WebGLSender<T> = ipc_channel::ipc::IpcSender<T>; +pub type WebGLReceiver<T> = ipc_channel::ipc::IpcReceiver<T>; + +pub fn webgl_channel<T: Serialize + for<'de> Deserialize<'de>>( +) -> Result<(WebGLSender<T>, WebGLReceiver<T>), io::Error> { + ipc_channel::ipc::channel() +} diff --git a/components/shared/canvas/webgl_channel/mod.rs b/components/shared/canvas/webgl_channel/mod.rs new file mode 100644 index 00000000000..edfb90c8806 --- /dev/null +++ b/components/shared/canvas/webgl_channel/mod.rs @@ -0,0 +1,149 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! Enum wrappers to be able to select different channel implementations at runtime. + +mod ipc; +mod mpsc; + +use std::fmt; + +use ipc_channel::ipc::IpcSender; +use ipc_channel::router::ROUTER; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use servo_config::opts; + +use crate::webgl::WebGLMsg; + +lazy_static! { + static ref IS_MULTIPROCESS: bool = opts::multiprocess(); +} + +#[derive(Deserialize, Serialize)] +pub enum WebGLSender<T: Serialize> { + Ipc(ipc::WebGLSender<T>), + Mpsc(mpsc::WebGLSender<T>), +} + +impl<T> Clone for WebGLSender<T> +where + T: Serialize, +{ + fn clone(&self) -> Self { + match *self { + WebGLSender::Ipc(ref chan) => WebGLSender::Ipc(chan.clone()), + WebGLSender::Mpsc(ref chan) => WebGLSender::Mpsc(chan.clone()), + } + } +} + +impl<T: Serialize> fmt::Debug for WebGLSender<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "WebGLSender(..)") + } +} + +impl<T: Serialize> WebGLSender<T> { + #[inline] + pub fn send(&self, msg: T) -> WebGLSendResult { + match *self { + WebGLSender::Ipc(ref sender) => sender.send(msg).map_err(|_| ()), + WebGLSender::Mpsc(ref sender) => sender.send(msg).map_err(|_| ()), + } + } +} + +pub type WebGLSendResult = Result<(), ()>; + +pub enum WebGLReceiver<T> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + Ipc(ipc::WebGLReceiver<T>), + Mpsc(mpsc::WebGLReceiver<T>), +} + +impl<T> WebGLReceiver<T> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + pub fn recv(&self) -> Result<T, ()> { + match *self { + WebGLReceiver::Ipc(ref receiver) => receiver.recv().map_err(|_| ()), + WebGLReceiver::Mpsc(ref receiver) => receiver.recv().map_err(|_| ()), + } + } + + pub fn try_recv(&self) -> Result<T, ()> { + match *self { + WebGLReceiver::Ipc(ref receiver) => receiver.try_recv().map_err(|_| ()), + WebGLReceiver::Mpsc(ref receiver) => receiver.try_recv().map_err(|_| ()), + } + } + + pub fn into_inner(self) -> crossbeam_channel::Receiver<T> + where + T: Send + 'static, + { + match self { + WebGLReceiver::Ipc(receiver) => { + ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(receiver) + }, + WebGLReceiver::Mpsc(receiver) => receiver.into_inner(), + } + } +} + +pub fn webgl_channel<T>() -> Result<(WebGLSender<T>, WebGLReceiver<T>), ()> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + if *IS_MULTIPROCESS { + ipc::webgl_channel() + .map(|(tx, rx)| (WebGLSender::Ipc(tx), WebGLReceiver::Ipc(rx))) + .map_err(|_| ()) + } else { + mpsc::webgl_channel().map(|(tx, rx)| (WebGLSender::Mpsc(tx), WebGLReceiver::Mpsc(rx))) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WebGLChan(pub WebGLSender<WebGLMsg>); + +impl WebGLChan { + #[inline] + pub fn send(&self, msg: WebGLMsg) -> WebGLSendResult { + self.0.send(msg) + } + + pub fn to_ipc(&self) -> IpcSender<WebGLMsg> { + match self.0 { + WebGLSender::Ipc(ref sender) => sender.clone(), + WebGLSender::Mpsc(ref mpsc_sender) => { + let (sender, receiver) = + ipc_channel::ipc::channel().expect("IPC Channel creation failed"); + let mpsc_sender = mpsc_sender.clone(); + ipc_channel::router::ROUTER.add_route( + receiver.to_opaque(), + Box::new(move |message| { + if let Ok(message) = message.to() { + let _ = mpsc_sender.send(message); + } + }), + ); + sender + }, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WebGLPipeline(pub WebGLChan); + +impl WebGLPipeline { + pub fn channel(&self) -> WebGLChan { + self.0.clone() + } +} diff --git a/components/shared/canvas/webgl_channel/mpsc.rs b/components/shared/canvas/webgl_channel/mpsc.rs new file mode 100644 index 00000000000..b7d0bfcc3da --- /dev/null +++ b/components/shared/canvas/webgl_channel/mpsc.rs @@ -0,0 +1,62 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +macro_rules! unreachable_serializable { + ($name:ident) => { + impl<T> Serialize for $name<T> { + fn serialize<S: Serializer>(&self, _: S) -> Result<S::Ok, S::Error> { + unreachable!(); + } + } + + impl<'a, T> Deserialize<'a> for $name<T> { + fn deserialize<D>(_: D) -> Result<$name<T>, D::Error> + where + D: Deserializer<'a>, + { + unreachable!(); + } + } + }; +} + +pub struct WebGLSender<T>(crossbeam_channel::Sender<T>); +pub struct WebGLReceiver<T>(crossbeam_channel::Receiver<T>); + +impl<T> Clone for WebGLSender<T> { + fn clone(&self) -> Self { + WebGLSender(self.0.clone()) + } +} + +impl<T> WebGLSender<T> { + #[inline] + pub fn send(&self, data: T) -> Result<(), crossbeam_channel::SendError<T>> { + self.0.send(data) + } +} + +impl<T> WebGLReceiver<T> { + #[inline] + pub fn recv(&self) -> Result<T, crossbeam_channel::RecvError> { + self.0.recv() + } + #[inline] + pub fn try_recv(&self) -> Result<T, crossbeam_channel::TryRecvError> { + self.0.try_recv() + } + pub fn into_inner(self) -> crossbeam_channel::Receiver<T> { + self.0 + } +} + +pub fn webgl_channel<T>() -> Result<(WebGLSender<T>, WebGLReceiver<T>), ()> { + let (sender, receiver) = crossbeam_channel::unbounded(); + Ok((WebGLSender(sender), WebGLReceiver(receiver))) +} + +unreachable_serializable!(WebGLReceiver); +unreachable_serializable!(WebGLSender); diff --git a/components/shared/compositing/Cargo.toml b/components/shared/compositing/Cargo.toml new file mode 100644 index 00000000000..03185ed8e86 --- /dev/null +++ b/components/shared/compositing/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "compositing_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "compositing_traits" +path = "lib.rs" + +[dependencies] +canvas = { path = "../../canvas" } +crossbeam-channel = { workspace = true } +embedder_traits = { workspace = true } +euclid = { workspace = true } +gfx_traits = { workspace = true } +ipc-channel = { workspace = true } +keyboard-types = { workspace = true } +log = { workspace = true } +msg = { workspace = true } +net_traits = { workspace = true } +profile_traits = { workspace = true } +script_traits = { workspace = true } +servo_url = { path = "../../url" } +style_traits = { workspace = true } +webrender_api = { workspace = true } +webrender_surfman = { path = "../../webrender_surfman" } diff --git a/components/shared/compositing/constellation_msg.rs b/components/shared/compositing/constellation_msg.rs new file mode 100644 index 00000000000..49b7353adfb --- /dev/null +++ b/components/shared/compositing/constellation_msg.rs @@ -0,0 +1,120 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use std::fmt; +use std::time::Duration; + +use embedder_traits::Cursor; +use gfx_traits::Epoch; +use ipc_channel::ipc::IpcSender; +use keyboard_types::KeyboardEvent; +use msg::constellation_msg::{ + BrowsingContextId, PipelineId, TopLevelBrowsingContextId, TraversalDirection, +}; +use script_traits::{ + AnimationTickType, CompositorEvent, LogEntry, MediaSessionActionType, WebDriverCommandMsg, + WindowSizeData, WindowSizeType, +}; +use servo_url::ServoUrl; + +/// Messages to the constellation. +pub enum ConstellationMsg { + /// Exit the constellation. + Exit, + /// Request that the constellation send the BrowsingContextId corresponding to the document + /// with the provided pipeline id + GetBrowsingContext(PipelineId, IpcSender<Option<BrowsingContextId>>), + /// Request that the constellation send the current pipeline id for the provided + /// browsing context id, over a provided channel. + GetPipeline(BrowsingContextId, IpcSender<Option<PipelineId>>), + /// Request that the constellation send the current focused top-level browsing context id, + /// over a provided channel. + GetFocusTopLevelBrowsingContext(IpcSender<Option<TopLevelBrowsingContextId>>), + /// Query the constellation to see if the current compositor output is stable + IsReadyToSaveImage(HashMap<PipelineId, Epoch>), + /// Inform the constellation of a key event. + Keyboard(KeyboardEvent), + /// Whether to allow script to navigate. + AllowNavigationResponse(PipelineId, bool), + /// Request to load a page. + LoadUrl(TopLevelBrowsingContextId, ServoUrl), + /// Clear the network cache. + ClearCache, + /// Request to traverse the joint session history of the provided browsing context. + TraverseHistory(TopLevelBrowsingContextId, TraversalDirection), + /// Inform the constellation of a window being resized. + WindowSize(TopLevelBrowsingContextId, WindowSizeData, WindowSizeType), + /// Requests that the constellation instruct layout to begin a new tick of the animation. + TickAnimation(PipelineId, AnimationTickType), + /// Dispatch a webdriver command + WebDriverCommand(WebDriverCommandMsg), + /// Reload a top-level browsing context. + Reload(TopLevelBrowsingContextId), + /// A log entry, with the top-level browsing context id and thread name + LogEntry(Option<TopLevelBrowsingContextId>, Option<String>, LogEntry), + /// Create a new top level browsing context. + NewBrowser(ServoUrl, TopLevelBrowsingContextId), + /// Close a top level browsing context. + CloseBrowser(TopLevelBrowsingContextId), + /// Panic a top level browsing context. + SendError(Option<TopLevelBrowsingContextId>, String), + /// Make browser visible. + SelectBrowser(TopLevelBrowsingContextId), + /// Forward an event to the script task of the given pipeline. + ForwardEvent(PipelineId, CompositorEvent), + /// Requesting a change to the onscreen cursor. + SetCursor(Cursor), + /// Enable the sampling profiler, with a given sampling rate and max total sampling duration. + EnableProfiler(Duration, Duration), + /// Disable the sampling profiler. + DisableProfiler, + /// Request to exit from fullscreen mode + ExitFullScreen(TopLevelBrowsingContextId), + /// Media session action. + MediaSessionAction(MediaSessionActionType), + /// Toggle browser visibility. + ChangeBrowserVisibility(TopLevelBrowsingContextId, bool), + /// Virtual keyboard was dismissed + IMEDismissed, + /// Compositing done, but external code needs to present. + ReadyToPresent(TopLevelBrowsingContextId), +} + +impl fmt::Debug for ConstellationMsg { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + use self::ConstellationMsg::*; + let variant = match *self { + Exit => "Exit", + GetBrowsingContext(..) => "GetBrowsingContext", + GetPipeline(..) => "GetPipeline", + GetFocusTopLevelBrowsingContext(..) => "GetFocusTopLevelBrowsingContext", + IsReadyToSaveImage(..) => "IsReadyToSaveImage", + Keyboard(..) => "Keyboard", + AllowNavigationResponse(..) => "AllowNavigationResponse", + LoadUrl(..) => "LoadUrl", + TraverseHistory(..) => "TraverseHistory", + WindowSize(..) => "WindowSize", + TickAnimation(..) => "TickAnimation", + WebDriverCommand(..) => "WebDriverCommand", + Reload(..) => "Reload", + LogEntry(..) => "LogEntry", + NewBrowser(..) => "NewBrowser", + CloseBrowser(..) => "CloseBrowser", + SendError(..) => "SendError", + SelectBrowser(..) => "SelectBrowser", + ForwardEvent(..) => "ForwardEvent", + SetCursor(..) => "SetCursor", + EnableProfiler(..) => "EnableProfiler", + DisableProfiler => "DisableProfiler", + ExitFullScreen(..) => "ExitFullScreen", + MediaSessionAction(..) => "MediaSessionAction", + ChangeBrowserVisibility(..) => "ChangeBrowserVisibility", + IMEDismissed => "IMEDismissed", + ClearCache => "ClearCache", + ReadyToPresent(..) => "ReadyToPresent", + }; + write!(formatter, "ConstellationMsg::{}", variant) + } +} diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs new file mode 100644 index 00000000000..81ef4ace3b9 --- /dev/null +++ b/components/shared/compositing/lib.rs @@ -0,0 +1,215 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! Communication with the compositor thread. + +mod constellation_msg; + +use std::fmt::{Debug, Error, Formatter}; + +use canvas::canvas_paint_thread::ImageUpdate; +pub use constellation_msg::ConstellationMsg; +use crossbeam_channel::{Receiver, Sender}; +use embedder_traits::EventLoopWaker; +use euclid::Rect; +use gfx_traits::Epoch; +use ipc_channel::ipc::IpcSender; +use log::warn; +use msg::constellation_msg::{PipelineId, TopLevelBrowsingContextId}; +use net_traits::image::base::Image; +use net_traits::NetToCompositorMsg; +use script_traits::{ + AnimationState, ConstellationControlMsg, EventResult, LayoutControlMsg, MouseButton, + MouseEventType, ScriptToCompositorMsg, +}; +use style_traits::CSSPixel; +use webrender_api::units::{DeviceIntPoint, DeviceIntSize}; +use webrender_api::{self, FontInstanceKey, FontKey, ImageKey}; + +/// Why we performed a composite. This is used for debugging. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum CompositingReason { + /// We hit the delayed composition timeout. (See `delayed_composition.rs`.) + DelayedCompositeTimeout, + /// The window has been scrolled and we're starting the first recomposite. + Scroll, + /// A scroll has continued and we need to recomposite again. + ContinueScroll, + /// We're performing the single composite in headless mode. + Headless, + /// We're performing a composite to run an animation. + Animation, + /// A new frame tree has been loaded. + NewFrameTree, + /// New painted buffers have been received. + NewPaintedBuffers, + /// The window has been zoomed. + Zoom, + /// A new WebRender frame has arrived. + NewWebRenderFrame, + /// WebRender has processed a scroll event and has generated a new frame. + NewWebRenderScrollFrame, + /// The window has been resized and will need to be synchronously repainted. + Resize, +} + +/// Sends messages to the compositor. +pub struct CompositorProxy { + pub sender: Sender<CompositorMsg>, + pub event_loop_waker: Box<dyn EventLoopWaker>, +} + +impl CompositorProxy { + pub fn send(&self, msg: CompositorMsg) { + if let Err(err) = self.sender.send(msg) { + warn!("Failed to send response ({:?}).", err); + } + self.event_loop_waker.wake(); + } +} + +impl Clone for CompositorProxy { + fn clone(&self) -> CompositorProxy { + CompositorProxy { + sender: self.sender.clone(), + event_loop_waker: self.event_loop_waker.clone(), + } + } +} + +/// The port that the compositor receives messages on. +pub struct CompositorReceiver { + pub receiver: Receiver<CompositorMsg>, +} + +impl CompositorReceiver { + pub fn try_recv_compositor_msg(&mut self) -> Option<CompositorMsg> { + self.receiver.try_recv().ok() + } + pub fn recv_compositor_msg(&mut self) -> CompositorMsg { + self.receiver.recv().unwrap() + } +} + +impl CompositorProxy { + pub fn recomposite(&self, reason: CompositingReason) { + self.send(CompositorMsg::Recomposite(reason)); + } +} + +/// Messages from (or via) the constellation thread to the compositor. +pub enum CompositorMsg { + /// Informs the compositor that the constellation has completed shutdown. + /// Required because the constellation can have pending calls to make + /// (e.g. SetFrameTree) at the time that we send it an ExitMsg. + ShutdownComplete, + /// Alerts the compositor that the given pipeline has changed whether it is running animations. + ChangeRunningAnimationsState(PipelineId, AnimationState), + /// Replaces the current frame tree, typically called during main frame navigation. + SetFrameTree(SendableFrameTree), + /// Composite. + Recomposite(CompositingReason), + /// Script has handled a touch event, and either prevented or allowed default actions. + TouchEventProcessed(EventResult), + /// Composite to a PNG file and return the Image over a passed channel. + CreatePng(Option<Rect<f32, CSSPixel>>, IpcSender<Option<Image>>), + /// A reply to the compositor asking if the output image is stable. + IsReadyToSaveImageReply(bool), + /// Pipeline visibility changed + PipelineVisibilityChanged(PipelineId, bool), + /// WebRender has successfully processed a scroll. The boolean specifies whether a composite is + /// needed. + NewScrollFrameReady(bool), + /// A pipeline was shut down. + // This message acts as a synchronization point between the constellation, + // when it shuts down a pipeline, to the compositor; when the compositor + // sends a reply on the IpcSender, the constellation knows it's safe to + // tear down the other threads associated with this pipeline. + PipelineExited(PipelineId, IpcSender<()>), + /// Runs a closure in the compositor thread. + /// It's used to dispatch functions from webrender to the main thread's event loop. + /// Required to allow WGL GLContext sharing in Windows. + Dispatch(Box<dyn Fn() + Send>), + /// Indicates to the compositor that it needs to record the time when the frame with + /// the given ID (epoch) is painted and report it to the layout thread of the given + /// pipeline ID. + PendingPaintMetric(PipelineId, Epoch), + /// The load of a page has completed + LoadComplete(TopLevelBrowsingContextId), + /// WebDriver mouse button event + WebDriverMouseButtonEvent(MouseEventType, MouseButton, f32, f32), + /// WebDriver mouse move event + WebDriverMouseMoveEvent(f32, f32), + + /// Get Window Informations size and position. + GetClientWindow(IpcSender<(DeviceIntSize, DeviceIntPoint)>), + /// Get screen size. + GetScreenSize(IpcSender<DeviceIntSize>), + /// Get screen available size. + GetScreenAvailSize(IpcSender<DeviceIntSize>), + + /// Messages forwarded to the compositor by the constellation from other crates. These + /// messages are mainly passed on from the compositor to WebRender. + Forwarded(ForwardedToCompositorMsg), +} + +pub struct SendableFrameTree { + pub pipeline: CompositionPipeline, + pub children: Vec<SendableFrameTree>, +} + +/// The subset of the pipeline that is needed for layer composition. +#[derive(Clone)] +pub struct CompositionPipeline { + pub id: PipelineId, + pub top_level_browsing_context_id: TopLevelBrowsingContextId, + pub script_chan: IpcSender<ConstellationControlMsg>, + pub layout_chan: IpcSender<LayoutControlMsg>, +} + +pub enum FontToCompositorMsg { + AddFontInstance(FontKey, f32, Sender<FontInstanceKey>), + AddFont(gfx_traits::FontData, Sender<FontKey>), +} + +pub enum CanvasToCompositorMsg { + GenerateKey(Sender<ImageKey>), + UpdateImages(Vec<ImageUpdate>), +} + +/// Messages forwarded by the Constellation to the Compositor. +pub enum ForwardedToCompositorMsg { + Layout(ScriptToCompositorMsg), + Net(NetToCompositorMsg), + Font(FontToCompositorMsg), + Canvas(CanvasToCompositorMsg), +} + +impl Debug for CompositorMsg { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + match *self { + CompositorMsg::ShutdownComplete => write!(f, "ShutdownComplete"), + CompositorMsg::ChangeRunningAnimationsState(_, state) => { + write!(f, "ChangeRunningAnimationsState({:?})", state) + }, + CompositorMsg::SetFrameTree(..) => write!(f, "SetFrameTree"), + CompositorMsg::Recomposite(..) => write!(f, "Recomposite"), + CompositorMsg::TouchEventProcessed(..) => write!(f, "TouchEventProcessed"), + CompositorMsg::CreatePng(..) => write!(f, "CreatePng"), + CompositorMsg::IsReadyToSaveImageReply(..) => write!(f, "IsReadyToSaveImageReply"), + CompositorMsg::PipelineVisibilityChanged(..) => write!(f, "PipelineVisibilityChanged"), + CompositorMsg::PipelineExited(..) => write!(f, "PipelineExited"), + CompositorMsg::NewScrollFrameReady(..) => write!(f, "NewScrollFrameReady"), + CompositorMsg::Dispatch(..) => write!(f, "Dispatch"), + CompositorMsg::PendingPaintMetric(..) => write!(f, "PendingPaintMetric"), + CompositorMsg::LoadComplete(..) => write!(f, "LoadComplete"), + CompositorMsg::WebDriverMouseButtonEvent(..) => write!(f, "WebDriverMouseButtonEvent"), + CompositorMsg::WebDriverMouseMoveEvent(..) => write!(f, "WebDriverMouseMoveEvent"), + CompositorMsg::GetClientWindow(..) => write!(f, "GetClientWindow"), + CompositorMsg::GetScreenSize(..) => write!(f, "GetScreenSize"), + CompositorMsg::GetScreenAvailSize(..) => write!(f, "GetScreenAvailSize"), + CompositorMsg::Forwarded(..) => write!(f, "Webrender"), + } + } +} diff --git a/components/shared/devtools/Cargo.toml b/components/shared/devtools/Cargo.toml new file mode 100644 index 00000000000..ff6d1d40dfd --- /dev/null +++ b/components/shared/devtools/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "devtools_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "devtools_traits" +path = "lib.rs" + +[dependencies] +bitflags = { workspace = true } +headers = { workspace = true } +http = { workspace = true } +ipc-channel = { workspace = true } +malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of_derive = { workspace = true } +msg = { workspace = true } +serde = { workspace = true } +servo_url = { path = "../../url" } +time = { workspace = true } +uuid = { workspace = true, features = ["serde"] } diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs new file mode 100644 index 00000000000..57e11ad8784 --- /dev/null +++ b/components/shared/devtools/lib.rs @@ -0,0 +1,372 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! This module contains shared types and messages for use by devtools/script. +//! The traits are here instead of in script so that the devtools crate can be +//! modified independently of the rest of Servo. + +#![crate_name = "devtools_traits"] +#![crate_type = "rlib"] +#![allow(non_snake_case)] +#![deny(unsafe_code)] + +use std::net::TcpStream; + +use bitflags::bitflags; +use http::{HeaderMap, Method}; +use ipc_channel::ipc::IpcSender; +use malloc_size_of_derive::MallocSizeOf; +use msg::constellation_msg::{BrowsingContextId, PipelineId}; +use serde::{Deserialize, Serialize}; +use servo_url::ServoUrl; +use time::{self, Duration, Tm}; +use uuid::Uuid; + +// Information would be attached to NewGlobal to be received and show in devtools. +// Extend these fields if we need more information. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct DevtoolsPageInfo { + pub title: String, + pub url: ServoUrl, +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct CSSError { + pub filename: String, + pub line: u32, + pub column: u32, + pub msg: String, +} + +/// Messages to instruct the devtools server to update its known actors/state +/// according to changes in the browser. +#[derive(Debug)] +pub enum DevtoolsControlMsg { + /// Messages from threads in the chrome process (resource/constellation/devtools) + FromChrome(ChromeToDevtoolsControlMsg), + /// Messages from script threads + FromScript(ScriptToDevtoolsControlMsg), +} + +/// Events that the devtools server must act upon. +#[derive(Debug)] +pub enum ChromeToDevtoolsControlMsg { + /// A new client has connected to the server. + AddClient(TcpStream), + /// The browser is shutting down. + ServerExitMsg, + /// A network event occurred (request, reply, etc.). The actor with the + /// provided name should be notified. + NetworkEvent(String, NetworkEvent), +} + +/// The state of a page navigation. +#[derive(Debug, Deserialize, Serialize)] +pub enum NavigationState { + /// A browsing context is about to navigate to a given URL. + Start(ServoUrl), + /// A browsing context has completed navigating to the provided pipeline. + Stop(PipelineId, DevtoolsPageInfo), +} + +#[derive(Debug, Deserialize, Serialize)] +/// Events that the devtools server must act upon. +pub enum ScriptToDevtoolsControlMsg { + /// A new global object was created, associated with a particular pipeline. + /// The means of communicating directly with it are provided. + NewGlobal( + (BrowsingContextId, PipelineId, Option<WorkerId>), + IpcSender<DevtoolScriptControlMsg>, + DevtoolsPageInfo, + ), + /// The given browsing context is performing a navigation. + Navigate(BrowsingContextId, NavigationState), + /// A particular page has invoked the console API. + ConsoleAPI(PipelineId, ConsoleMessage, Option<WorkerId>), + /// An animation frame with the given timestamp was processed in a script thread. + /// The actor with the provided name should be notified. + FramerateTick(String, f64), + + /// Report a CSS parse error for the given pipeline + ReportCSSError(PipelineId, CSSError), + + /// Report a page error for the given pipeline + ReportPageError(PipelineId, PageError), + + /// Report a page title change + TitleChanged(PipelineId, String), +} + +/// Serialized JS return values +/// TODO: generalize this beyond the EvaluateJS message? +#[derive(Debug, Deserialize, Serialize)] +pub enum EvaluateJSReply { + VoidValue, + NullValue, + BooleanValue(bool), + NumberValue(f64), + StringValue(String), + ActorValue { class: String, uuid: String }, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AttrInfo { + pub namespace: String, + pub name: String, + pub value: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct NodeInfo { + pub uniqueId: String, + pub baseURI: String, + pub parent: String, + pub nodeType: u16, + pub namespaceURI: String, + pub nodeName: String, + pub numChildren: usize, + + pub name: String, + pub publicId: String, + pub systemId: String, + + pub attrs: Vec<AttrInfo>, + + pub isDocumentElement: bool, + + pub shortValue: String, + pub incompleteValue: bool, +} + +pub struct StartedTimelineMarker { + name: String, + start_time: PreciseTime, + start_stack: Option<Vec<()>>, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TimelineMarker { + pub name: String, + pub start_time: PreciseTime, + pub start_stack: Option<Vec<()>>, + pub end_time: PreciseTime, + pub end_stack: Option<Vec<()>>, +} + +#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub enum TimelineMarkerType { + Reflow, + DOMEvent, +} + +/// The properties of a DOM node as computed by layout. +#[derive(Debug, Deserialize, Serialize)] +pub struct ComputedNodeLayout { + pub display: String, + pub position: String, + pub zIndex: String, + pub boxSizing: String, + + pub autoMargins: AutoMargins, + pub marginTop: String, + pub marginRight: String, + pub marginBottom: String, + pub marginLeft: String, + + pub borderTopWidth: String, + pub borderRightWidth: String, + pub borderBottomWidth: String, + pub borderLeftWidth: String, + + pub paddingTop: String, + pub paddingRight: String, + pub paddingBottom: String, + pub paddingLeft: String, + + pub width: f32, + pub height: f32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AutoMargins { + pub top: bool, + pub right: bool, + pub bottom: bool, + pub left: bool, +} + +/// Messages to process in a particular script thread, as instructed by a devtools client. +/// TODO: better error handling, e.g. if pipeline id lookup fails? +#[derive(Debug, Deserialize, Serialize)] +pub enum DevtoolScriptControlMsg { + /// Evaluate a JS snippet in the context of the global for the given pipeline. + EvaluateJS(PipelineId, String, IpcSender<EvaluateJSReply>), + /// Retrieve the details of the root node (ie. the document) for the given pipeline. + GetRootNode(PipelineId, IpcSender<Option<NodeInfo>>), + /// Retrieve the details of the document element for the given pipeline. + GetDocumentElement(PipelineId, IpcSender<Option<NodeInfo>>), + /// Retrieve the details of the child nodes of the given node in the given pipeline. + GetChildren(PipelineId, String, IpcSender<Option<Vec<NodeInfo>>>), + /// Retrieve the computed layout properties of the given node in the given pipeline. + GetLayout(PipelineId, String, IpcSender<Option<ComputedNodeLayout>>), + /// Update a given node's attributes with a list of modifications. + ModifyAttribute(PipelineId, String, Vec<Modification>), + /// Request live console messages for a given pipeline (true if desired, false otherwise). + WantsLiveNotifications(PipelineId, bool), + /// Request live notifications for a given set of timeline events for a given pipeline. + SetTimelineMarkers( + PipelineId, + Vec<TimelineMarkerType>, + IpcSender<Option<TimelineMarker>>, + ), + /// Withdraw request for live timeline notifications for a given pipeline. + DropTimelineMarkers(PipelineId, Vec<TimelineMarkerType>), + /// Request a callback directed at the given actor name from the next animation frame + /// executed in the given pipeline. + RequestAnimationFrame(PipelineId, String), + /// Direct the given pipeline to reload the current page. + Reload(PipelineId), +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Modification { + pub attributeName: String, + pub newValue: Option<String>, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum LogLevel { + Log, + Debug, + Info, + Warn, + Error, + Clear, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ConsoleMessage { + pub message: String, + pub logLevel: LogLevel, + pub filename: String, + pub lineNumber: usize, + pub columnNumber: usize, +} + +bitflags! { + #[derive(Deserialize, Serialize)] + pub struct CachedConsoleMessageTypes: u8 { + const PAGE_ERROR = 1 << 0; + const CONSOLE_API = 1 << 1; + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PageError { + #[serde(rename = "_type")] + pub type_: String, + pub errorMessage: String, + pub sourceName: String, + pub lineText: String, + pub lineNumber: u32, + pub columnNumber: u32, + pub category: String, + pub timeStamp: u64, + pub error: bool, + pub warning: bool, + pub exception: bool, + pub strict: bool, + pub private: bool, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ConsoleAPI { + #[serde(rename = "_type")] + pub type_: String, + pub level: String, + pub filename: String, + pub lineNumber: u32, + pub functionName: String, + pub timeStamp: u64, + pub private: bool, + pub arguments: Vec<String>, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum CachedConsoleMessage { + PageError(PageError), + ConsoleAPI(ConsoleAPI), +} + +#[derive(Debug, PartialEq)] +pub struct HttpRequest { + pub url: ServoUrl, + pub method: Method, + pub headers: HeaderMap, + pub body: Option<Vec<u8>>, + pub pipeline_id: PipelineId, + pub startedDateTime: Tm, + pub timeStamp: i64, + pub connect_time: u64, + pub send_time: u64, + pub is_xhr: bool, +} + +#[derive(Debug, PartialEq)] +pub struct HttpResponse { + pub headers: Option<HeaderMap>, + pub status: Option<(u16, Vec<u8>)>, + pub body: Option<Vec<u8>>, + pub pipeline_id: PipelineId, +} + +#[derive(Debug)] +pub enum NetworkEvent { + HttpRequest(HttpRequest), + HttpResponse(HttpResponse), +} + +impl TimelineMarker { + pub fn start(name: String) -> StartedTimelineMarker { + StartedTimelineMarker { + name, + start_time: PreciseTime::now(), + start_stack: None, + } + } +} + +impl StartedTimelineMarker { + pub fn end(self) -> TimelineMarker { + TimelineMarker { + name: self.name, + start_time: self.start_time, + start_stack: self.start_stack, + end_time: PreciseTime::now(), + end_stack: None, + } + } +} + +/// A replacement for `time::PreciseTime` that isn't opaque, so we can serialize it. +/// +/// The reason why this doesn't go upstream is that `time` is slated to be part of Rust's standard +/// library, which definitely can't have any dependencies on `serde`. But `serde` can't implement +/// `Deserialize` and `Serialize` itself, because `time::PreciseTime` is opaque! A Catch-22. So I'm +/// duplicating the definition here. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct PreciseTime(u64); + +impl PreciseTime { + pub fn now() -> PreciseTime { + PreciseTime(time::precise_time_ns()) + } + + pub fn to(&self, later: PreciseTime) -> Duration { + Duration::nanoseconds((later.0 - self.0) as i64) + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub struct WorkerId(pub Uuid); diff --git a/components/shared/embedder/Cargo.toml b/components/shared/embedder/Cargo.toml new file mode 100644 index 00000000000..41ae0b581bf --- /dev/null +++ b/components/shared/embedder/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "embedder_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "embedder_traits" +path = "lib.rs" + +[dependencies] +cfg-if = { workspace = true } +crossbeam-channel = { workspace = true } +ipc-channel = { workspace = true } +keyboard-types = { workspace = true } +lazy_static = { workspace = true } +log = { workspace = true } +msg = { workspace = true } +num-derive = "0.3" +num-traits = { workspace = true } +serde = { workspace = true } +servo_url = { path = "../../url" } +webrender_api = { workspace = true } +webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } diff --git a/components/shared/embedder/build.rs b/components/shared/embedder/build.rs new file mode 100644 index 00000000000..25c737ffaa9 --- /dev/null +++ b/components/shared/embedder/build.rs @@ -0,0 +1,23 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::error::Error; +use std::path::Path; + +fn main() -> Result<(), Box<dyn Error>> { + // Cargo does not expose the profile name to crates or their build scripts, + // but we can extract it from OUT_DIR and set a custom cfg() ourselves. + let out = std::env::var("OUT_DIR")?; + let out = Path::new(&out); + let krate = out.parent().unwrap(); + let build = krate.parent().unwrap(); + let profile = build.parent().unwrap(); + if profile.file_name().unwrap() == "production" { + println!("cargo:rustc-cfg=servo_production"); + } else { + println!("cargo:rustc-cfg=servo_do_not_use_in_production"); + } + + Ok(()) +} diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs new file mode 100644 index 00000000000..ecaf57a14bb --- /dev/null +++ b/components/shared/embedder/lib.rs @@ -0,0 +1,347 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +pub mod resources; + +use std::fmt::{Debug, Error, Formatter}; + +use crossbeam_channel::{Receiver, Sender}; +use ipc_channel::ipc::IpcSender; +use keyboard_types::KeyboardEvent; +use log::warn; +use msg::constellation_msg::{InputMethodType, PipelineId, TopLevelBrowsingContextId}; +use num_derive::FromPrimitive; +use serde::{Deserialize, Serialize}; +use servo_url::ServoUrl; +use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize}; +pub use webxr_api::MainThreadWaker as EventLoopWaker; + +/// A cursor for the window. This is different from a CSS cursor (see +/// `CursorKind`) in that it has no `Auto` value. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, FromPrimitive, PartialEq, Serialize)] +pub enum Cursor { + None, + Default, + Pointer, + ContextMenu, + Help, + Progress, + Wait, + Cell, + Crosshair, + Text, + VerticalText, + Alias, + Copy, + Move, + NoDrop, + NotAllowed, + Grab, + Grabbing, + EResize, + NResize, + NeResize, + NwResize, + SResize, + SeResize, + SwResize, + WResize, + EwResize, + NsResize, + NeswResize, + NwseResize, + ColResize, + RowResize, + AllScroll, + ZoomIn, + ZoomOut, +} + +/// Sends messages to the embedder. +pub struct EmbedderProxy { + pub sender: Sender<(Option<TopLevelBrowsingContextId>, EmbedderMsg)>, + pub event_loop_waker: Box<dyn EventLoopWaker>, +} + +impl EmbedderProxy { + pub fn send(&self, msg: (Option<TopLevelBrowsingContextId>, EmbedderMsg)) { + // Send a message and kick the OS event loop awake. + if let Err(err) = self.sender.send(msg) { + warn!("Failed to send response ({:?}).", err); + } + self.event_loop_waker.wake(); + } +} + +impl Clone for EmbedderProxy { + fn clone(&self) -> EmbedderProxy { + EmbedderProxy { + sender: self.sender.clone(), + event_loop_waker: self.event_loop_waker.clone(), + } + } +} + +/// The port that the embedder receives messages on. +pub struct EmbedderReceiver { + pub receiver: Receiver<(Option<TopLevelBrowsingContextId>, EmbedderMsg)>, +} + +impl EmbedderReceiver { + pub fn try_recv_embedder_msg( + &mut self, + ) -> Option<(Option<TopLevelBrowsingContextId>, EmbedderMsg)> { + self.receiver.try_recv().ok() + } + pub fn recv_embedder_msg(&mut self) -> (Option<TopLevelBrowsingContextId>, EmbedderMsg) { + self.receiver.recv().unwrap() + } +} + +#[derive(Deserialize, Serialize)] +pub enum ContextMenuResult { + Dismissed, + Ignored, + Selected(usize), +} + +#[derive(Deserialize, Serialize)] +pub enum PromptDefinition { + /// Show a message. + Alert(String, IpcSender<()>), + /// Ask a Ok/Cancel question. + OkCancel(String, IpcSender<PromptResult>), + /// Ask a Yes/No question. + YesNo(String, IpcSender<PromptResult>), + /// Ask the user to enter text. + Input(String, String, IpcSender<Option<String>>), +} + +#[derive(Deserialize, PartialEq, Serialize)] +pub enum PromptOrigin { + /// Prompt is triggered from content (window.prompt/alert/confirm/…). + /// Prompt message is unknown. + Untrusted, + /// Prompt is triggered from Servo (ask for permission, show error,…). + Trusted, +} + +#[derive(Deserialize, PartialEq, Serialize)] +pub enum PromptResult { + /// Prompt was closed by clicking on the primary button (ok/yes) + Primary, + /// Prompt was closed by clicking on the secondary button (cancel/no) + Secondary, + /// Prompt was dismissed + Dismissed, +} + +#[derive(Deserialize, Serialize)] +pub enum EmbedderMsg { + /// A status message to be displayed by the browser chrome. + Status(Option<String>), + /// Alerts the embedder that the current page has changed its title. + ChangePageTitle(Option<String>), + /// Move the window to a point + MoveTo(DeviceIntPoint), + /// Resize the window to size + ResizeTo(DeviceIntSize), + /// Show dialog to user + Prompt(PromptDefinition, PromptOrigin), + /// Show a context menu to the user + ShowContextMenu(IpcSender<ContextMenuResult>, Option<String>, Vec<String>), + /// Whether or not to allow a pipeline to load a url. + AllowNavigationRequest(PipelineId, ServoUrl), + /// Whether or not to allow script to open a new tab/browser + AllowOpeningBrowser(IpcSender<bool>), + /// A new browser was created by script + BrowserCreated(TopLevelBrowsingContextId), + /// Wether or not to unload a document + AllowUnload(IpcSender<bool>), + /// Sends an unconsumed key event back to the embedder. + Keyboard(KeyboardEvent), + /// Gets system clipboard contents + GetClipboardContents(IpcSender<String>), + /// Sets system clipboard contents + SetClipboardContents(String), + /// Changes the cursor. + SetCursor(Cursor), + /// A favicon was detected + NewFavicon(ServoUrl), + /// <head> tag finished parsing + HeadParsed, + /// The history state has changed. + HistoryChanged(Vec<ServoUrl>, usize), + /// Enter or exit fullscreen + SetFullscreenState(bool), + /// The load of a page has begun + LoadStart, + /// The load of a page has completed + LoadComplete, + /// A browser is to be closed + CloseBrowser, + /// A pipeline panicked. First string is the reason, second one is the backtrace. + Panic(String, Option<String>), + /// Open dialog to select bluetooth device. + GetSelectedBluetoothDevice(Vec<String>, IpcSender<Option<String>>), + /// Open file dialog to select files. Set boolean flag to true allows to select multiple files. + SelectFiles(Vec<FilterPattern>, bool, IpcSender<Option<Vec<String>>>), + /// Open interface to request permission specified by prompt. + PromptPermission(PermissionPrompt, IpcSender<PermissionRequest>), + /// Request to present an IME to the user when an editable element is focused. + /// If the input is text, the second parameter defines the pre-existing string + /// text content and the zero-based index into the string locating the insertion point. + /// bool is true for multi-line and false otherwise. + ShowIME(InputMethodType, Option<(String, i32)>, bool, DeviceIntRect), + /// Request to hide the IME when the editable element is blurred. + HideIME, + /// Servo has shut down + Shutdown, + /// Report a complete sampled profile + ReportProfile(Vec<u8>), + /// Notifies the embedder about media session events + /// (i.e. when there is metadata for the active media session, playback state changes...). + MediaSessionEvent(MediaSessionEvent), + /// Report the status of Devtools Server with a token that can be used to bypass the permission prompt. + OnDevtoolsStarted(Result<u16, ()>, String), + /// Compositing done, but external code needs to present. + ReadyToPresent, +} + +impl Debug for EmbedderMsg { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + match *self { + EmbedderMsg::Status(..) => write!(f, "Status"), + EmbedderMsg::ChangePageTitle(..) => write!(f, "ChangePageTitle"), + EmbedderMsg::MoveTo(..) => write!(f, "MoveTo"), + EmbedderMsg::ResizeTo(..) => write!(f, "ResizeTo"), + EmbedderMsg::Prompt(..) => write!(f, "Prompt"), + EmbedderMsg::AllowUnload(..) => write!(f, "AllowUnload"), + EmbedderMsg::AllowNavigationRequest(..) => write!(f, "AllowNavigationRequest"), + EmbedderMsg::Keyboard(..) => write!(f, "Keyboard"), + EmbedderMsg::GetClipboardContents(..) => write!(f, "GetClipboardContents"), + EmbedderMsg::SetClipboardContents(..) => write!(f, "SetClipboardContents"), + EmbedderMsg::SetCursor(..) => write!(f, "SetCursor"), + EmbedderMsg::NewFavicon(..) => write!(f, "NewFavicon"), + EmbedderMsg::HeadParsed => write!(f, "HeadParsed"), + EmbedderMsg::CloseBrowser => write!(f, "CloseBrowser"), + EmbedderMsg::HistoryChanged(..) => write!(f, "HistoryChanged"), + EmbedderMsg::SetFullscreenState(..) => write!(f, "SetFullscreenState"), + EmbedderMsg::LoadStart => write!(f, "LoadStart"), + EmbedderMsg::LoadComplete => write!(f, "LoadComplete"), + EmbedderMsg::Panic(..) => write!(f, "Panic"), + EmbedderMsg::GetSelectedBluetoothDevice(..) => write!(f, "GetSelectedBluetoothDevice"), + EmbedderMsg::SelectFiles(..) => write!(f, "SelectFiles"), + EmbedderMsg::PromptPermission(..) => write!(f, "PromptPermission"), + EmbedderMsg::ShowIME(..) => write!(f, "ShowIME"), + EmbedderMsg::HideIME => write!(f, "HideIME"), + EmbedderMsg::Shutdown => write!(f, "Shutdown"), + EmbedderMsg::AllowOpeningBrowser(..) => write!(f, "AllowOpeningBrowser"), + EmbedderMsg::BrowserCreated(..) => write!(f, "BrowserCreated"), + EmbedderMsg::ReportProfile(..) => write!(f, "ReportProfile"), + EmbedderMsg::MediaSessionEvent(..) => write!(f, "MediaSessionEvent"), + EmbedderMsg::OnDevtoolsStarted(..) => write!(f, "OnDevtoolsStarted"), + EmbedderMsg::ShowContextMenu(..) => write!(f, "ShowContextMenu"), + EmbedderMsg::ReadyToPresent => write!(f, "ReadyToPresent"), + } + } +} + +/// Filter for file selection; +/// the `String` content is expected to be extension (e.g, "doc", without the prefixing ".") +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct FilterPattern(pub String); + +/// https://w3c.github.io/mediasession/#mediametadata +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct MediaMetadata { + /// Title + pub title: String, + /// Artist + pub artist: String, + /// Album + pub album: String, +} + +impl MediaMetadata { + pub fn new(title: String) -> Self { + Self { + title, + artist: "".to_owned(), + album: "".to_owned(), + } + } +} + +/// https://w3c.github.io/mediasession/#enumdef-mediasessionplaybackstate +#[repr(i32)] +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum MediaSessionPlaybackState { + /// The browsing context does not specify whether it’s playing or paused. + None_ = 1, + /// The browsing context is currently playing media and it can be paused. + Playing, + /// The browsing context has paused media and it can be resumed. + Paused, +} + +/// https://w3c.github.io/mediasession/#dictdef-mediapositionstate +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct MediaPositionState { + pub duration: f64, + pub playback_rate: f64, + pub position: f64, +} + +impl MediaPositionState { + pub fn new(duration: f64, playback_rate: f64, position: f64) -> Self { + Self { + duration, + playback_rate, + position, + } + } +} + +/// Type of events sent from script to the embedder about the media session. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum MediaSessionEvent { + /// Indicates that the media metadata is available. + SetMetadata(MediaMetadata), + /// Indicates that the playback state has changed. + PlaybackStateChange(MediaSessionPlaybackState), + /// Indicates that the position state is set. + SetPositionState(MediaPositionState), +} + +/// Enum with variants that match the DOM PermissionName enum +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum PermissionName { + Geolocation, + Notifications, + Push, + Midi, + Camera, + Microphone, + Speaker, + DeviceInfo, + BackgroundSync, + Bluetooth, + PersistentStorage, +} + +/// Information required to display a permission prompt +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum PermissionPrompt { + Insecure(PermissionName), + Request(PermissionName), +} + +/// Status for prompting user for permission. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum PermissionRequest { + Granted, + Denied, +} diff --git a/components/shared/embedder/resources.rs b/components/shared/embedder/resources.rs new file mode 100644 index 00000000000..fd1f44ff455 --- /dev/null +++ b/components/shared/embedder/resources.rs @@ -0,0 +1,154 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::path::PathBuf; +use std::sync::RwLock; + +use cfg_if::cfg_if; +use lazy_static::lazy_static; + +lazy_static! { + static ref RES: RwLock<Option<Box<dyn ResourceReaderMethods + Sync + Send>>> = { + cfg_if! { + if #[cfg(servo_production)] { + RwLock::new(None) + } else { + // Static assert that this is really a non-production build, rather + // than a failure of the build script’s production check. + const _: () = assert!(cfg!(servo_do_not_use_in_production)); + + RwLock::new(Some(resources_for_tests())) + } + } + }; +} + +pub fn set(reader: Box<dyn ResourceReaderMethods + Sync + Send>) { + *RES.write().unwrap() = Some(reader); +} + +pub fn read_bytes(res: Resource) -> Vec<u8> { + RES.read() + .unwrap() + .as_ref() + .expect("Resource reader not set.") + .read(res) +} + +pub fn read_string(res: Resource) -> String { + String::from_utf8(read_bytes(res)).unwrap() +} + +pub fn sandbox_access_files() -> Vec<PathBuf> { + RES.read() + .unwrap() + .as_ref() + .expect("Resource reader not set.") + .sandbox_access_files() +} + +pub fn sandbox_access_files_dirs() -> Vec<PathBuf> { + RES.read() + .unwrap() + .as_ref() + .expect("Resource reader not set.") + .sandbox_access_files_dirs() +} + +pub enum Resource { + Preferences, + BluetoothBlocklist, + DomainList, + HstsPreloadList, + BadCertHTML, + NetErrorHTML, + UserAgentCSS, + ServoCSS, + PresentationalHintsCSS, + QuirksModeCSS, + RippyPNG, + MediaControlsCSS, + MediaControlsJS, + CrashHTML, +} + +impl Resource { + pub fn filename(&self) -> &'static str { + match self { + Resource::Preferences => "prefs.json", + Resource::BluetoothBlocklist => "gatt_blocklist.txt", + Resource::DomainList => "public_domains.txt", + Resource::HstsPreloadList => "hsts_preload.json", + Resource::BadCertHTML => "badcert.html", + Resource::NetErrorHTML => "neterror.html", + Resource::UserAgentCSS => "user-agent.css", + Resource::ServoCSS => "servo.css", + Resource::PresentationalHintsCSS => "presentational-hints.css", + Resource::QuirksModeCSS => "quirks-mode.css", + Resource::RippyPNG => "rippy.png", + Resource::MediaControlsCSS => "media-controls.css", + Resource::MediaControlsJS => "media-controls.js", + Resource::CrashHTML => "crash.html", + } + } +} + +pub trait ResourceReaderMethods { + fn read(&self, res: Resource) -> Vec<u8>; + fn sandbox_access_files(&self) -> Vec<PathBuf>; + fn sandbox_access_files_dirs(&self) -> Vec<PathBuf>; +} + +/// Bake all of our resources into this crate for tests, unless we are `cfg!(servo_production)`. +/// +/// Local non-production embedder builds (e.g. servoshell) can still override these with [`set`], +/// if runtime loading of prefs.json and other resources is needed. +/// +/// In theory this can be `#[cfg(servo_production)]`, but omitting the attribute ensures that the +/// code is always checked by the compiler, even if it later gets optimised out as dead code. +fn resources_for_tests() -> Box<dyn ResourceReaderMethods + Sync + Send> { + struct ResourceReader; + impl ResourceReaderMethods for ResourceReader { + fn sandbox_access_files(&self) -> Vec<PathBuf> { + vec![] + } + fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> { + vec![] + } + fn read(&self, file: Resource) -> Vec<u8> { + match file { + Resource::Preferences => &include_bytes!("../../../resources/prefs.json")[..], + Resource::BluetoothBlocklist => { + &include_bytes!("../../../resources/gatt_blocklist.txt")[..] + }, + Resource::DomainList => { + &include_bytes!("../../../resources/public_domains.txt")[..] + }, + Resource::HstsPreloadList => { + &include_bytes!("../../../resources/hsts_preload.json")[..] + }, + Resource::BadCertHTML => &include_bytes!("../../../resources/badcert.html")[..], + Resource::NetErrorHTML => &include_bytes!("../../../resources/neterror.html")[..], + Resource::UserAgentCSS => &include_bytes!("../../../resources/user-agent.css")[..], + Resource::ServoCSS => &include_bytes!("../../../resources/servo.css")[..], + Resource::PresentationalHintsCSS => { + &include_bytes!("../../../resources/presentational-hints.css")[..] + }, + Resource::QuirksModeCSS => { + &include_bytes!("../../../resources/quirks-mode.css")[..] + }, + Resource::RippyPNG => &include_bytes!("../../../resources/rippy.png")[..], + Resource::MediaControlsCSS => { + &include_bytes!("../../../resources/media-controls.css")[..] + }, + Resource::MediaControlsJS => { + &include_bytes!("../../../resources/media-controls.js")[..] + }, + Resource::CrashHTML => &include_bytes!("../../../resources/crash.html")[..], + } + .to_owned() + } + } + Box::new(ResourceReader) +} diff --git a/components/shared/gfx/Cargo.toml b/components/shared/gfx/Cargo.toml new file mode 100644 index 00000000000..f94b5870c35 --- /dev/null +++ b/components/shared/gfx/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "gfx_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "gfx_traits" +path = "lib.rs" + +[dependencies] +malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of_derive = { workspace = true } +range = { path = "../../range" } +serde = { workspace = true } +webrender_api = { workspace = true } diff --git a/components/shared/gfx/lib.rs b/components/shared/gfx/lib.rs new file mode 100644 index 00000000000..7cac7ef1fb1 --- /dev/null +++ b/components/shared/gfx/lib.rs @@ -0,0 +1,130 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#![crate_name = "gfx_traits"] +#![crate_type = "rlib"] +#![deny(unsafe_code)] + +pub mod print_tree; + +use std::sync::atomic::{AtomicUsize, Ordering}; + +use malloc_size_of_derive::MallocSizeOf; +use range::{int_range_index, RangeIndex}; +use serde::{Deserialize, Serialize}; +use webrender_api::{Epoch as WebRenderEpoch, FontInstanceKey, FontKey, NativeFontHandle}; + +/// A newtype struct for denoting the age of messages; prevents race conditions. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +pub struct Epoch(pub u32); + +impl Epoch { + pub fn next(&mut self) { + self.0 += 1; + } +} + +impl Into<WebRenderEpoch> for Epoch { + fn into(self) -> WebRenderEpoch { + WebRenderEpoch(self.0) + } +} + +pub trait WebRenderEpochToU16 { + fn as_u16(&self) -> u16; +} + +impl WebRenderEpochToU16 for WebRenderEpoch { + /// The value of this [`Epoch`] as a u16 value. Note that if this Epoch's + /// value is more than u16::MAX, then the return value will be modulo + /// u16::MAX. + fn as_u16(&self) -> u16 { + (self.0 % u16::MAX as u32) as u16 + } +} + +/// A unique ID for every stacking context. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub struct StackingContextId( + /// The identifier for this StackingContext, derived from the Flow's memory address + /// and fragment type. As a space optimization, these are combined into a single word. + pub u64, +); + +impl StackingContextId { + /// Returns the stacking context ID for the outer document/layout root. + #[inline] + pub fn root() -> StackingContextId { + StackingContextId(0) + } + + pub fn next(&self) -> StackingContextId { + let StackingContextId(id) = *self; + StackingContextId(id + 1) + } +} + +int_range_index! { + #[derive(Deserialize, MallocSizeOf, Serialize)] + /// An index that refers to a byte offset in a text run. This could + /// the middle of a glyph. + struct ByteIndex(isize) +} + +/// The type of fragment that a scroll root is created for. +/// +/// This can only ever grow to maximum 4 entries. That's because we cram the value of this enum +/// into the lower 2 bits of the `ScrollRootId`, which otherwise contains a 32-bit-aligned +/// heap address. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub enum FragmentType { + /// A StackingContext for the fragment body itself. + FragmentBody, + /// A StackingContext created to contain ::before pseudo-element content. + BeforePseudoContent, + /// A StackingContext created to contain ::after pseudo-element content. + AfterPseudoContent, +} + +/// The next ID that will be used for a special scroll root id. +/// +/// A special scroll root is a scroll root that is created for generated content. +static NEXT_SPECIAL_SCROLL_ROOT_ID: AtomicUsize = AtomicUsize::new(0); + +/// If none of the bits outside this mask are set, the scroll root is a special scroll root. +/// Note that we assume that the top 16 bits of the address space are unused on the platform. +const SPECIAL_SCROLL_ROOT_ID_MASK: usize = 0xffff; + +/// Returns a new scroll root ID for a scroll root. +fn next_special_id() -> usize { + // We shift this left by 2 to make room for the fragment type ID. + ((NEXT_SPECIAL_SCROLL_ROOT_ID.fetch_add(1, Ordering::SeqCst) + 1) << 2) & + SPECIAL_SCROLL_ROOT_ID_MASK +} + +pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> usize { + debug_assert_eq!(id & (fragment_type as usize), 0); + if fragment_type == FragmentType::FragmentBody { + id + } else { + next_special_id() | (fragment_type as usize) + } +} + +pub fn node_id_from_scroll_id(id: usize) -> Option<usize> { + if (id & !SPECIAL_SCROLL_ROOT_ID_MASK) != 0 { + return Some((id & !3) as usize); + } + None +} + +pub enum FontData { + Raw(Vec<u8>), + Native(NativeFontHandle), +} + +pub trait WebrenderApi { + fn add_font_instance(&self, font_key: FontKey, size: f32) -> FontInstanceKey; + fn add_font(&self, data: FontData) -> FontKey; +} diff --git a/components/shared/gfx/print_tree.rs b/components/shared/gfx/print_tree.rs new file mode 100644 index 00000000000..60201733c66 --- /dev/null +++ b/components/shared/gfx/print_tree.rs @@ -0,0 +1,89 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +/// A struct that makes it easier to print out a pretty tree of data, which +/// can be visually scanned more easily. +pub struct PrintTree { + /// The current level of recursion. + level: u32, + + /// An item which is queued up, so that we can determine if we need + /// a mid-tree prefix or a branch ending prefix. + queued_item: Option<String>, +} + +impl PrintTree { + pub fn new(title: String) -> PrintTree { + println!("\u{250c} {}", title); + PrintTree { + level: 1, + queued_item: None, + } + } + + /// Descend one level in the tree with the given title string. + pub fn new_level(&mut self, queued_title: String) { + self.flush_queued_item("\u{251C}\u{2500}"); + + self.print_level_prefix(); + + let items: Vec<&str> = queued_title.split("\n").collect(); + println!("\u{251C}\u{2500} {}", items[0]); + for i in 1..items.len() { + self.print_level_child_indentation(); + print!("{}", items[i]); + if i < items.len() { + print!("\n"); + } + } + + self.level = self.level + 1; + } + + /// Ascend one level in the tree. + pub fn end_level(&mut self) { + self.flush_queued_item("\u{2514}\u{2500}"); + self.level -= 1; + } + + /// Add an item to the current level in the tree. + pub fn add_item(&mut self, text: String) { + self.flush_queued_item("\u{251C}\u{2500}"); + self.queued_item = Some(text); + } + + fn print_level_prefix(&self) { + for _ in 0..self.level { + print!("\u{2502} "); + } + } + + fn print_level_child_indentation(&self) { + for _ in 0..(self.level + 1) { + print!("\u{2502} "); + } + print!("{}", " ".repeat(7)); + } + + fn flush_queued_item(&mut self, prefix: &str) { + if let Some(queued_item) = self.queued_item.take() { + self.print_level_prefix(); + let items: Vec<&str> = queued_item.split("\n").collect(); + println!("{} {}", prefix, items[0]); + for i in 1..items.len() { + self.print_level_child_indentation(); + print!("{}", items[i]); + if i < items.len() { + print!("\n"); + } + } + } + } +} + +impl Drop for PrintTree { + fn drop(&mut self) { + self.flush_queued_item("\u{2514}\u{2500}"); + } +} diff --git a/components/shared/layout/Cargo.toml b/components/shared/layout/Cargo.toml new file mode 100644 index 00000000000..c81ed04454c --- /dev/null +++ b/components/shared/layout/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "layout_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "layout_traits" +path = "lib.rs" + +[dependencies] +crossbeam-channel = { workspace = true } +gfx = { path = "../../gfx" } +ipc-channel = { workspace = true } +metrics = { path = "../../metrics" } +msg = { workspace = true } +net_traits = { workspace = true } +profile_traits = { workspace = true } +script_traits = { workspace = true } +servo_url = { path = "../../url" } +webrender_api = { workspace = true } diff --git a/components/shared/layout/lib.rs b/components/shared/layout/lib.rs new file mode 100644 index 00000000000..1e1783e489f --- /dev/null +++ b/components/shared/layout/lib.rs @@ -0,0 +1,53 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#![deny(unsafe_code)] + +// This module contains traits in layout used generically +// in the rest of Servo. +// The traits are here instead of in layout so +// that these modules won't have to depend on layout. + +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use crossbeam_channel::{Receiver, Sender}; +use gfx::font_cache_thread::FontCacheThread; +use ipc_channel::ipc::{IpcReceiver, IpcSender}; +use metrics::PaintTimeMetrics; +use msg::constellation_msg::{ + BackgroundHangMonitorRegister, PipelineId, TopLevelBrowsingContextId, +}; +use net_traits::image_cache::ImageCache; +use profile_traits::{mem, time}; +use script_traits::{ + ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg, WebrenderIpcSender, + WindowSizeData, +}; +use servo_url::ServoUrl; + +// A static method creating a layout thread +// Here to remove the compositor -> layout dependency +pub trait LayoutThreadFactory { + type Message; + fn create( + id: PipelineId, + top_level_browsing_context_id: TopLevelBrowsingContextId, + url: ServoUrl, + is_iframe: bool, + chan: (Sender<Self::Message>, Receiver<Self::Message>), + pipeline_port: IpcReceiver<LayoutControlMsg>, + background_hang_monitor: Box<dyn BackgroundHangMonitorRegister>, + constellation_chan: IpcSender<ConstellationMsg>, + script_chan: IpcSender<ConstellationControlMsg>, + image_cache: Arc<dyn ImageCache>, + font_cache_thread: FontCacheThread, + time_profiler_chan: time::ProfilerChan, + mem_profiler_chan: mem::ProfilerChan, + webrender_api_sender: WebrenderIpcSender, + paint_time_metrics: PaintTimeMetrics, + busy: Arc<AtomicBool>, + window_size: WindowSizeData, + ); +} diff --git a/components/shared/msg/Cargo.toml b/components/shared/msg/Cargo.toml new file mode 100644 index 00000000000..d64880b5c6f --- /dev/null +++ b/components/shared/msg/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "msg" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "msg" +path = "lib.rs" +test = false +doctest = false + +[dependencies] +ipc-channel = { workspace = true } +lazy_static = { workspace = true } +malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of_derive = { workspace = true } +parking_lot = { workspace = true } +serde = { workspace = true } +size_of_test = { path = "../../size_of_test" } +webrender_api = { workspace = true } diff --git a/components/shared/msg/constellation_msg.rs b/components/shared/msg/constellation_msg.rs new file mode 100644 index 00000000000..335fffe5f62 --- /dev/null +++ b/components/shared/msg/constellation_msg.rs @@ -0,0 +1,738 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! The high-level interface from script to constellation. Using this abstract interface helps +//! reduce coupling between these two components. + +use std::cell::Cell; +use std::num::NonZeroU32; +use std::sync::Arc; +use std::time::Duration; +use std::{fmt, mem}; + +use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use lazy_static::lazy_static; +use malloc_size_of::malloc_size_of_is_0; +use malloc_size_of_derive::MallocSizeOf; +use parking_lot::Mutex; +use serde::{Deserialize, Serialize}; +use size_of_test::size_of_test; +use webrender_api::{ExternalScrollId, PipelineId as WebRenderPipelineId}; + +macro_rules! namespace_id_method { + ($func_name:ident, $func_return_data_type:ident, $self:ident, $index_name:ident) => { + fn $func_name(&mut $self) -> $func_return_data_type { + $func_return_data_type { + namespace_id: $self.id, + index: $index_name($self.next_index()), + } + } + }; +} + +macro_rules! namespace_id { + ($id_name:ident, $index_name:ident) => { + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, + )] + pub struct $index_name(pub NonZeroU32); + malloc_size_of_is_0!($index_name); + + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + MallocSizeOf, + Ord, + PartialEq, + PartialOrd, + Serialize, + )] + pub struct $id_name { + pub namespace_id: PipelineNamespaceId, + pub index: $index_name, + } + }; +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum TraversalDirection { + Forward(usize), + Back(usize), +} + +#[derive(Debug, Deserialize, Serialize)] +/// Request a pipeline-namespace id from the constellation. +pub struct PipelineNamespaceRequest(pub IpcSender<PipelineNamespaceId>); + +/// A per-process installer of pipeline-namespaces. +pub struct PipelineNamespaceInstaller { + request_sender: Option<IpcSender<PipelineNamespaceRequest>>, + namespace_sender: IpcSender<PipelineNamespaceId>, + namespace_receiver: IpcReceiver<PipelineNamespaceId>, +} + +impl PipelineNamespaceInstaller { + pub fn new() -> Self { + let (namespace_sender, namespace_receiver) = + ipc::channel().expect("PipelineNamespaceInstaller ipc channel failure"); + PipelineNamespaceInstaller { + request_sender: None, + namespace_sender: namespace_sender, + namespace_receiver: namespace_receiver, + } + } + + /// Provide a request sender to send requests to the constellation. + pub fn set_sender(&mut self, sender: IpcSender<PipelineNamespaceRequest>) { + self.request_sender = Some(sender); + } + + /// Install a namespace, requesting a new Id from the constellation. + pub fn install_namespace(&self) { + match self.request_sender.as_ref() { + Some(sender) => { + let _ = sender.send(PipelineNamespaceRequest(self.namespace_sender.clone())); + let namespace_id = self + .namespace_receiver + .recv() + .expect("The constellation to make a pipeline namespace id available"); + PipelineNamespace::install(namespace_id); + }, + None => unreachable!("PipelineNamespaceInstaller should have a request_sender setup"), + } + } +} + +lazy_static! { + /// A per-process unique pipeline-namespace-installer. + /// Accessible via PipelineNamespace. + /// + /// Use PipelineNamespace::set_installer_sender to initiate with a sender to the constellation, + /// when a new process has been created. + /// + /// Use PipelineNamespace::fetch_install to install a unique pipeline-namespace from the calling thread. + static ref PIPELINE_NAMESPACE_INSTALLER: Arc<Mutex<PipelineNamespaceInstaller>> = + Arc::new(Mutex::new(PipelineNamespaceInstaller::new())); +} + +/// Each pipeline ID needs to be unique. However, it also needs to be possible to +/// generate the pipeline ID from an iframe element (this simplifies a lot of other +/// code that makes use of pipeline IDs). +/// +/// To achieve this, each pipeline index belongs to a particular namespace. There is +/// a namespace for the constellation thread, and also one for every script thread. +/// +/// A namespace can be installed for any other thread in a process +/// where an pipeline-installer has been initialized. +/// +/// This allows pipeline IDs to be generated by any of those threads without conflicting +/// with pipeline IDs created by other script threads or the constellation. The +/// constellation is the only code that is responsible for creating new *namespaces*. +/// This ensures that namespaces are always unique, even when using multi-process mode. +/// +/// It may help conceptually to think of the namespace ID as an identifier for the +/// thread that created this pipeline ID - however this is really an implementation +/// detail so shouldn't be relied upon in code logic. It's best to think of the +/// pipeline ID as a simple unique identifier that doesn't convey any more information. +#[derive(Clone, Copy)] +pub struct PipelineNamespace { + id: PipelineNamespaceId, + index: u32, +} + +impl PipelineNamespace { + /// Install a namespace for a given Id. + pub fn install(namespace_id: PipelineNamespaceId) { + PIPELINE_NAMESPACE.with(|tls| { + assert!(tls.get().is_none()); + tls.set(Some(PipelineNamespace { + id: namespace_id, + index: 0, + })); + }); + } + + /// Setup the pipeline-namespace-installer, by providing it with a sender to the constellation. + /// Idempotent in single-process mode. + pub fn set_installer_sender(sender: IpcSender<PipelineNamespaceRequest>) { + PIPELINE_NAMESPACE_INSTALLER.lock().set_sender(sender); + } + + /// Install a namespace in the current thread, without requiring having a namespace Id ready. + /// Panics if called more than once per thread. + pub fn auto_install() { + // Note that holding the lock for the duration of the call is irrelevant to performance, + // since a thread would have to block on the ipc-response from the constellation, + // with the constellation already acting as a global lock on namespace ids, + // and only being able to handle one request at a time. + // + // Hence, any other thread attempting to concurrently install a namespace + // would have to wait for the current call to finish, regardless of the lock held here. + PIPELINE_NAMESPACE_INSTALLER.lock().install_namespace(); + } + + fn next_index(&mut self) -> NonZeroU32 { + self.index += 1; + NonZeroU32::new(self.index).expect("pipeline id index wrapped!") + } + + namespace_id_method! {next_pipeline_id, PipelineId, self, PipelineIndex} + namespace_id_method! {next_browsing_context_id, BrowsingContextId, self, BrowsingContextIndex} + namespace_id_method! {next_history_state_id, HistoryStateId, self, HistoryStateIndex} + namespace_id_method! {next_message_port_id, MessagePortId, self, MessagePortIndex} + namespace_id_method! {next_message_port_router_id, MessagePortRouterId, self, MessagePortRouterIndex} + namespace_id_method! {next_broadcast_channel_router_id, BroadcastChannelRouterId, self, BroadcastChannelRouterIndex} + namespace_id_method! {next_service_worker_id, ServiceWorkerId, self, ServiceWorkerIndex} + namespace_id_method! {next_service_worker_registration_id, ServiceWorkerRegistrationId, + self, ServiceWorkerRegistrationIndex} + namespace_id_method! {next_blob_id, BlobId, self, BlobIndex} +} + +thread_local!(pub static PIPELINE_NAMESPACE: Cell<Option<PipelineNamespace>> = Cell::new(None)); + +#[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize, +)] +pub struct PipelineNamespaceId(pub u32); + +namespace_id! {PipelineId, PipelineIndex} + +size_of_test!(PipelineId, 8); +size_of_test!(Option<PipelineId>, 8); + +impl PipelineId { + pub fn new() -> PipelineId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let new_pipeline_id = namespace.next_pipeline_id(); + tls.set(Some(namespace)); + new_pipeline_id + }) + } + + pub fn to_webrender(&self) -> WebRenderPipelineId { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let PipelineIndex(index) = self.index; + WebRenderPipelineId(namespace_id, index.get()) + } + + #[allow(unsafe_code)] + pub fn from_webrender(pipeline: WebRenderPipelineId) -> PipelineId { + let WebRenderPipelineId(namespace_id, index) = pipeline; + unsafe { + PipelineId { + namespace_id: PipelineNamespaceId(namespace_id), + index: PipelineIndex(NonZeroU32::new_unchecked(index)), + } + } + } + + pub fn root_scroll_id(&self) -> webrender_api::ExternalScrollId { + ExternalScrollId(0, self.to_webrender()) + } +} + +impl fmt::Display for PipelineId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let PipelineIndex(index) = self.index; + write!(fmt, "({},{})", namespace_id, index.get()) + } +} + +namespace_id! {BrowsingContextId, BrowsingContextIndex} + +size_of_test!(BrowsingContextId, 8); +size_of_test!(Option<BrowsingContextId>, 8); + +impl BrowsingContextId { + pub fn new() -> BrowsingContextId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let new_browsing_context_id = namespace.next_browsing_context_id(); + tls.set(Some(namespace)); + new_browsing_context_id + }) + } +} + +impl fmt::Display for BrowsingContextId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let BrowsingContextIndex(index) = self.index; + write!(fmt, "({},{})", namespace_id, index.get()) + } +} + +#[derive(Clone, Default, Eq, Hash, PartialEq)] +pub struct BrowsingContextGroupId(pub u32); + +thread_local!(pub static TOP_LEVEL_BROWSING_CONTEXT_ID: Cell<Option<TopLevelBrowsingContextId>> = Cell::new(None)); + +#[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize, +)] +pub struct TopLevelBrowsingContextId(pub BrowsingContextId); + +size_of_test!(TopLevelBrowsingContextId, 8); +size_of_test!(Option<TopLevelBrowsingContextId>, 8); + +impl TopLevelBrowsingContextId { + pub fn new() -> TopLevelBrowsingContextId { + TopLevelBrowsingContextId(BrowsingContextId::new()) + } + /// Each script and layout thread should have the top-level browsing context id installed, + /// since it is used by crash reporting. + pub fn install(id: TopLevelBrowsingContextId) { + TOP_LEVEL_BROWSING_CONTEXT_ID.with(|tls| tls.set(Some(id))) + } + + pub fn installed() -> Option<TopLevelBrowsingContextId> { + TOP_LEVEL_BROWSING_CONTEXT_ID.with(|tls| tls.get()) + } +} + +impl fmt::Display for TopLevelBrowsingContextId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(fmt) + } +} + +impl From<TopLevelBrowsingContextId> for BrowsingContextId { + fn from(id: TopLevelBrowsingContextId) -> BrowsingContextId { + id.0 + } +} + +impl PartialEq<TopLevelBrowsingContextId> for BrowsingContextId { + fn eq(&self, rhs: &TopLevelBrowsingContextId) -> bool { + self.eq(&rhs.0) + } +} + +impl PartialEq<BrowsingContextId> for TopLevelBrowsingContextId { + fn eq(&self, rhs: &BrowsingContextId) -> bool { + self.0.eq(rhs) + } +} + +namespace_id! {MessagePortId, MessagePortIndex} + +impl MessagePortId { + pub fn new() -> MessagePortId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let next_message_port_id = namespace.next_message_port_id(); + tls.set(Some(namespace)); + next_message_port_id + }) + } +} + +impl fmt::Display for MessagePortId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let MessagePortIndex(index) = self.index; + write!(fmt, "({},{})", namespace_id, index.get()) + } +} + +namespace_id! {MessagePortRouterId, MessagePortRouterIndex} + +impl MessagePortRouterId { + pub fn new() -> MessagePortRouterId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let next_message_port_router_id = namespace.next_message_port_router_id(); + tls.set(Some(namespace)); + next_message_port_router_id + }) + } +} + +impl fmt::Display for MessagePortRouterId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let MessagePortRouterIndex(index) = self.index; + write!(fmt, "({},{})", namespace_id, index.get()) + } +} + +namespace_id! {BroadcastChannelRouterId, 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() + ) + } +} + +namespace_id! {ServiceWorkerId, ServiceWorkerIndex} + +impl ServiceWorkerId { + pub fn new() -> ServiceWorkerId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let next_service_worker_id = namespace.next_service_worker_id(); + tls.set(Some(namespace)); + next_service_worker_id + }) + } +} + +impl fmt::Display for ServiceWorkerId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let ServiceWorkerIndex(index) = self.index; + write!(fmt, "(ServiceWorkerId{},{})", namespace_id, index.get()) + } +} + +namespace_id! {ServiceWorkerRegistrationId, ServiceWorkerRegistrationIndex} + +impl ServiceWorkerRegistrationId { + pub fn new() -> ServiceWorkerRegistrationId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let next_service_worker_registration_id = + namespace.next_service_worker_registration_id(); + tls.set(Some(namespace)); + next_service_worker_registration_id + }) + } +} + +impl fmt::Display for ServiceWorkerRegistrationId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let ServiceWorkerRegistrationIndex(index) = self.index; + write!( + fmt, + "(ServiceWorkerRegistrationId{},{})", + namespace_id, + index.get() + ) + } +} + +namespace_id! {BlobId, BlobIndex} + +impl BlobId { + pub fn new() -> BlobId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let next_blob_id = namespace.next_blob_id(); + tls.set(Some(namespace)); + next_blob_id + }) + } +} + +impl fmt::Display for BlobId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let BlobIndex(index) = self.index; + write!(fmt, "({},{})", namespace_id, index.get()) + } +} + +namespace_id! {HistoryStateId, HistoryStateIndex} + +impl HistoryStateId { + pub fn new() -> HistoryStateId { + PIPELINE_NAMESPACE.with(|tls| { + let mut namespace = tls.get().expect("No namespace set for this thread!"); + let next_history_state_id = namespace.next_history_state_id(); + tls.set(Some(namespace)); + next_history_state_id + }) + } +} + +impl fmt::Display for HistoryStateId { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let PipelineNamespaceId(namespace_id) = self.namespace_id; + let HistoryStateIndex(index) = self.index; + write!(fmt, "({},{})", namespace_id, index.get()) + } +} + +// We provide ids just for unit testing. +pub const TEST_NAMESPACE: PipelineNamespaceId = PipelineNamespaceId(1234); +#[allow(unsafe_code)] +pub const TEST_PIPELINE_INDEX: PipelineIndex = + unsafe { PipelineIndex(NonZeroU32::new_unchecked(5678)) }; +pub const TEST_PIPELINE_ID: PipelineId = PipelineId { + namespace_id: TEST_NAMESPACE, + index: TEST_PIPELINE_INDEX, +}; +#[allow(unsafe_code)] +pub const TEST_BROWSING_CONTEXT_INDEX: BrowsingContextIndex = + unsafe { BrowsingContextIndex(NonZeroU32::new_unchecked(8765)) }; +pub const TEST_BROWSING_CONTEXT_ID: BrowsingContextId = BrowsingContextId { + namespace_id: TEST_NAMESPACE, + index: TEST_BROWSING_CONTEXT_INDEX, +}; + +// Used to specify the kind of input method editor appropriate to edit a field. +// This is a subset of htmlinputelement::InputType because some variants of InputType +// don't make sense in this context. +#[derive(Debug, Deserialize, Serialize)] +pub enum InputMethodType { + Color, + Date, + DatetimeLocal, + Email, + Month, + Number, + Password, + Search, + Tel, + Text, + Time, + Url, + Week, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +/// The equivalent of script_layout_interface::message::Msg +pub enum LayoutHangAnnotation { + AddStylesheet, + RemoveStylesheet, + SetQuirksMode, + Reflow, + GetRPC, + CollectReports, + PrepareToExit, + ExitNow, + GetCurrentEpoch, + GetWebFontLoadState, + CreateLayoutThread, + SetFinalUrl, + SetScrollStates, + UpdateScrollStateFromScript, + RegisterPaint, + SetNavigationStart, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +/// The equivalent of script::script_runtime::ScriptEventCategory +pub enum ScriptHangAnnotation { + AttachLayout, + ConstellationMsg, + DevtoolsMsg, + DocumentEvent, + DomEvent, + FileRead, + FormPlannedNavigation, + ImageCacheMsg, + InputEvent, + HistoryEvent, + NetworkEvent, + Resize, + ScriptEvent, + SetScrollState, + SetViewport, + StylesheetLoad, + TimerEvent, + UpdateReplacedElement, + WebSocketEvent, + WorkerEvent, + WorkletEvent, + ServiceWorkerEvent, + EnterFullscreen, + ExitFullscreen, + WebVREvent, + PerformanceTimelineTask, + PortMessage, + WebGPUMsg, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub enum HangAnnotation { + Layout(LayoutHangAnnotation), + Script(ScriptHangAnnotation), +} + +/// Hang-alerts are sent by the monitor to the constellation. +#[derive(Deserialize, Serialize)] +pub enum HangMonitorAlert { + /// A component hang has been detected. + Hang(HangAlert), + /// Report a completed sampled profile. + Profile(Vec<u8>), +} + +impl fmt::Debug for HangMonitorAlert { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + HangMonitorAlert::Hang(..) => write!(fmt, "Hang"), + HangMonitorAlert::Profile(..) => write!(fmt, "Profile"), + } + } +} + +/// Hang-alerts are sent by the monitor to the constellation. +#[derive(Deserialize, Serialize)] +pub enum HangAlert { + /// Report a transient hang. + Transient(MonitoredComponentId, HangAnnotation), + /// Report a permanent hang. + Permanent(MonitoredComponentId, HangAnnotation, Option<HangProfile>), +} + +impl fmt::Debug for HangAlert { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let (annotation, profile) = match self { + HangAlert::Transient(component_id, annotation) => { + write!( + fmt, + "\n The following component is experiencing a transient hang: \n {:?}", + component_id + )?; + (annotation.clone(), None) + }, + HangAlert::Permanent(component_id, annotation, profile) => { + write!( + fmt, + "\n The following component is experiencing a permanent hang: \n {:?}", + component_id + )?; + (annotation.clone(), profile.clone()) + }, + }; + + write!(fmt, "\n Annotation for the hang:\n{:?}", annotation)?; + if let Some(profile) = profile { + write!(fmt, "\n {:?}", profile)?; + } + + Ok(()) + } +} + +#[derive(Clone, Deserialize, Serialize)] +pub struct HangProfileSymbol { + pub name: Option<String>, + pub filename: Option<String>, + pub lineno: Option<u32>, +} + +#[derive(Clone, Deserialize, Serialize)] +/// Info related to the activity of an hanging component. +pub struct HangProfile { + pub backtrace: Vec<HangProfileSymbol>, +} + +impl fmt::Debug for HangProfile { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let hex_width = mem::size_of::<usize>() * 2 + 2; + + write!(fmt, "HangProfile backtrace:")?; + + if self.backtrace.is_empty() { + write!(fmt, "backtrace failed to resolve")?; + return Ok(()); + } + + for symbol in self.backtrace.iter() { + write!(fmt, "\n {:1$}", "", hex_width)?; + + if let Some(ref name) = symbol.name { + write!(fmt, " - {}", name)?; + } else { + write!(fmt, " - <unknown>")?; + } + + if let (Some(ref file), Some(ref line)) = (symbol.filename.as_ref(), symbol.lineno) { + write!(fmt, "\n {:3$}at {}:{}", "", file, line, hex_width)?; + } + } + + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum MonitoredComponentType { + Layout, + Script, +} + +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct MonitoredComponentId(pub PipelineId, pub MonitoredComponentType); + +/// A handle to register components for hang monitoring, +/// and to receive a means to communicate with the underlying hang monitor worker. +pub trait BackgroundHangMonitorRegister: BackgroundHangMonitorClone + Send { + /// Register a component for hang monitoring: + /// to be called from within the thread to be monitored for hangs. + fn register_component( + &self, + component: MonitoredComponentId, + transient_hang_timeout: Duration, + permanent_hang_timeout: Duration, + exit_signal: Option<Box<dyn BackgroundHangMonitorExitSignal>>, + ) -> Box<dyn BackgroundHangMonitor>; +} + +impl Clone for Box<dyn BackgroundHangMonitorRegister> { + fn clone(&self) -> Box<dyn BackgroundHangMonitorRegister> { + self.clone_box() + } +} + +pub trait BackgroundHangMonitorClone { + fn clone_box(&self) -> Box<dyn BackgroundHangMonitorRegister>; +} + +/// Proxy methods to communicate with the background hang monitor +pub trait BackgroundHangMonitor { + /// Notify the start of handling an event. + fn notify_activity(&self, annotation: HangAnnotation); + /// Notify the start of waiting for a new event to come in. + fn notify_wait(&self); + /// Unregister the component from monitor. + fn unregister(&self); +} + +/// A means for the BHM to signal a monitored component to exit. +/// Useful when the component is hanging, and cannot be notified via the usual way. +/// The component should implement this in a way allowing for the signal to be received when hanging, +/// if at all. +pub trait BackgroundHangMonitorExitSignal: Send { + /// Called by the BHM, to notify the monitored component to exit. + fn signal_to_exit(&self); +} + +/// Messages to control the sampling profiler. +#[derive(Deserialize, Serialize)] +pub enum BackgroundHangMonitorControlMsg { + /// Enable the sampler, with a given sampling rate and max total sampling duration. + EnableSampler(Duration, Duration), + DisableSampler, + /// Exit, and propagate the signal to monitored components. + Exit(IpcSender<()>), +} diff --git a/components/shared/msg/lib.rs b/components/shared/msg/lib.rs new file mode 100644 index 00000000000..b2ce3a52c2c --- /dev/null +++ b/components/shared/msg/lib.rs @@ -0,0 +1,7 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#![deny(unsafe_code)] + +pub mod constellation_msg; diff --git a/components/shared/net/Cargo.toml b/components/shared/net/Cargo.toml new file mode 100644 index 00000000000..432c8676ccb --- /dev/null +++ b/components/shared/net/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "net_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "net_traits" +path = "lib.rs" +test = false +doctest = false + +[dependencies] +content-security-policy = { workspace = true } +cookie = { workspace = true } +embedder_traits = { workspace = true } +headers = { workspace = true } +http = { workspace = true } +hyper = { workspace = true } +hyper_serde = { workspace = true } +image = { workspace = true } +ipc-channel = { workspace = true } +lazy_static = { workspace = true } +log = { workspace = true } +malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of_derive = { workspace = true } +mime = { workspace = true } +msg = { workspace = true } +num-traits = { workspace = true } +percent-encoding = { workspace = true } +pixels = { path = "../../pixels" } +rustls = { workspace = true } +serde = { workspace = true } +servo_arc = { path = "../../servo_arc" } +servo_rand = { path = "../../rand" } +servo_url = { path = "../../url" } +time = { workspace = true } +url = { workspace = true } +uuid = { workspace = true } +webrender_api = { git = "https://github.com/servo/webrender" } diff --git a/components/shared/net/blob_url_store.rs b/components/shared/net/blob_url_store.rs new file mode 100644 index 00000000000..ab853b702c9 --- /dev/null +++ b/components/shared/net/blob_url_store.rs @@ -0,0 +1,75 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use servo_url::ServoUrl; +use url::Url; +use uuid::Uuid; + +use crate::filemanager_thread::FileOrigin; + +/// Errors returned to Blob URL Store request +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub enum BlobURLStoreError { + /// Invalid File UUID + InvalidFileID, + /// Invalid URL origin + InvalidOrigin, + /// Invalid entry content + InvalidEntry, + /// Invalid range + InvalidRange, + /// External error, from like file system, I/O etc. + External(String), +} + +/// Standalone blob buffer object +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BlobBuf { + pub filename: Option<String>, + /// MIME type string + pub type_string: String, + /// Size of content in bytes + pub size: u64, + /// Content of blob + pub bytes: Vec<u8>, +} + +/// Parse URL as Blob URL scheme's definition +/// +/// <https://w3c.github.io/FileAPI/#DefinitionOfScheme> +pub fn parse_blob_url(url: &ServoUrl) -> Result<(Uuid, FileOrigin), ()> { + let url_inner = Url::parse(url.path()).map_err(|_| ())?; + let segs = url_inner + .path_segments() + .map(|c| c.collect::<Vec<_>>()) + .ok_or(())?; + + if url.query().is_some() || segs.len() > 1 { + return Err(()); + } + + let id = { + let id = segs.first().ok_or(())?; + Uuid::from_str(id).map_err(|_| ())? + }; + Ok((id, get_blob_origin(&ServoUrl::from_url(url_inner)))) +} + +/// Given an URL, returning the Origin that a Blob created under this +/// URL should have. +/// +/// HACK(izgzhen): Not well-specified on spec, and it is a bit a hack +/// both due to ambiguity of spec and that we have to serialization the +/// Origin here. +pub fn get_blob_origin(url: &ServoUrl) -> FileOrigin { + if url.scheme() == "file" { + // NOTE: by default this is "null" (Opaque), which is not ideal + "file://".to_string() + } else { + url.origin().ascii_serialization() + } +} diff --git a/components/shared/net/fetch/headers.rs b/components/shared/net/fetch/headers.rs new file mode 100644 index 00000000000..ae95066bcf5 --- /dev/null +++ b/components/shared/net/fetch/headers.rs @@ -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 https://mozilla.org/MPL/2.0/. */ + +use headers::HeaderMap; + +/// <https://fetch.spec.whatwg.org/#concept-header-list-get> +pub fn get_value_from_header_list(name: &str, headers: &HeaderMap) -> Option<Vec<u8>> { + let values = headers.get_all(name).iter().map(|val| val.as_bytes()); + + // Step 1 + if values.size_hint() == (0, Some(0)) { + return None; + } + + // Step 2 + return Some(values.collect::<Vec<&[u8]>>().join(&[0x2C, 0x20][..])); +} diff --git a/components/shared/net/filemanager_thread.rs b/components/shared/net/filemanager_thread.rs new file mode 100644 index 00000000000..ee18e295393 --- /dev/null +++ b/components/shared/net/filemanager_thread.rs @@ -0,0 +1,190 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::cmp::{max, min}; +use std::ops::Range; +use std::path::PathBuf; + +use embedder_traits::FilterPattern; +use ipc_channel::ipc::IpcSender; +use malloc_size_of_derive::MallocSizeOf; +use num_traits::ToPrimitive; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::blob_url_store::{BlobBuf, BlobURLStoreError}; + +// HACK: Not really process-safe now, we should send Origin +// directly instead of this in future, blocked on #11722 +/// File manager store entry's origin +pub type FileOrigin = String; + +/// A token modulating access to a file for a blob URL. +pub enum FileTokenCheck { + /// Checking against a token not required, + /// used for accessing a file + /// that isn't linked to from a blob URL. + NotRequired, + /// Checking against token required. + Required(Uuid), + /// Request should always fail, + /// used for cases when a check is required, + /// but no token could be acquired. + ShouldFail, +} + +/// Relative slice positions of a sequence, +/// whose semantic should be consistent with (start, end) parameters in +/// <https://w3c.github.io/FileAPI/#dfn-slice> +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct RelativePos { + /// Relative to first byte if non-negative, + /// relative to one past last byte if negative, + pub start: i64, + /// Relative offset from first byte if Some(non-negative), + /// relative to one past last byte if Some(negative), + /// None if one past last byte + pub end: Option<i64>, +} + +impl RelativePos { + /// Full range from start to end + pub fn full_range() -> RelativePos { + RelativePos { + start: 0, + end: None, + } + } + + /// Instantiate optional slice position parameters + pub fn from_opts(start: Option<i64>, end: Option<i64>) -> RelativePos { + RelativePos { + start: start.unwrap_or(0), + end, + } + } + + /// Slice the inner sliced range by repositioning + pub fn slice_inner(&self, rel_pos: &RelativePos) -> RelativePos { + RelativePos { + start: self.start + rel_pos.start, + end: match (self.end, rel_pos.end) { + (Some(old_end), Some(rel_end)) => Some(old_end + rel_end), + (old, None) => old, + (None, rel) => rel, + }, + } + } + + /// Compute absolute range by giving the total size + /// <https://w3c.github.io/FileAPI/#slice-method-algo> + pub fn to_abs_range(&self, size: usize) -> Range<usize> { + let size = size as i64; + + let start = { + if self.start < 0 { + max(size + self.start, 0) + } else { + min(self.start, size) + } + }; + + let end = match self.end { + Some(rel_end) => { + if rel_end < 0 { + max(size + rel_end, 0) + } else { + min(rel_end, size) + } + }, + None => size, + }; + + let span: i64 = max(end - start, 0); + + Range { + start: start.to_usize().unwrap(), + end: (start + span).to_usize().unwrap(), + } + } +} + +/// Response to file selection request +#[derive(Debug, Deserialize, Serialize)] +pub struct SelectedFile { + pub id: Uuid, + pub filename: PathBuf, + pub modified: u64, + pub size: u64, + // https://w3c.github.io/FileAPI/#dfn-type + pub type_string: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum FileManagerThreadMsg { + /// Select a single file. Last field is pre-selected file path for testing + SelectFile( + Vec<FilterPattern>, + IpcSender<FileManagerResult<SelectedFile>>, + FileOrigin, + Option<String>, + ), + + /// Select multiple files. Last field is pre-selected file paths for testing + SelectFiles( + Vec<FilterPattern>, + IpcSender<FileManagerResult<Vec<SelectedFile>>>, + FileOrigin, + Option<Vec<String>>, + ), + + /// Read FileID-indexed file in chunks, optionally check URL validity based on boolean flag + ReadFile( + IpcSender<FileManagerResult<ReadFileProgress>>, + Uuid, + FileOrigin, + ), + + /// Add an entry as promoted memory-based blob + PromoteMemory(Uuid, BlobBuf, bool, FileOrigin), + + /// Add a sliced entry pointing to the parent FileID, and send back the associated FileID + /// as part of a valid Blob URL + AddSlicedURLEntry( + Uuid, + RelativePos, + IpcSender<Result<Uuid, BlobURLStoreError>>, + FileOrigin, + ), + + /// Decrease reference count and send back the acknowledgement + DecRef(Uuid, FileOrigin, IpcSender<Result<(), BlobURLStoreError>>), + + /// Activate an internal FileID so it becomes valid as part of a Blob URL + ActivateBlobURL(Uuid, IpcSender<Result<(), BlobURLStoreError>>, FileOrigin), + + /// Revoke Blob URL and send back the acknowledgement + RevokeBlobURL(Uuid, FileOrigin, IpcSender<Result<(), BlobURLStoreError>>), +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum ReadFileProgress { + Meta(BlobBuf), + Partial(Vec<u8>), + EOF, +} + +pub type FileManagerResult<T> = Result<T, FileManagerThreadError>; + +#[derive(Debug, Deserialize, Serialize)] +pub enum FileManagerThreadError { + /// The selection action is invalid due to exceptional reason + InvalidSelection, + /// The selection action is cancelled by user + UserCancelled, + /// Errors returned from file system request + FileSystemError(String), + /// Blob URL Store error + BlobURLStoreError(BlobURLStoreError), +} diff --git a/components/shared/net/image/base.rs b/components/shared/net/image/base.rs new file mode 100644 index 00000000000..74e2d8823dc --- /dev/null +++ b/components/shared/net/image/base.rs @@ -0,0 +1,121 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::fmt; + +use image::ImageFormat; +use ipc_channel::ipc::IpcSharedMemory; +use log::debug; +use malloc_size_of_derive::MallocSizeOf; +use pixels::PixelFormat; +use serde::{Deserialize, Serialize}; +use webrender_api::ImageKey; + +use crate::image_cache::CorsStatus; + +#[derive(Clone, Deserialize, MallocSizeOf, Serialize)] +pub struct Image { + pub width: u32, + pub height: u32, + pub format: PixelFormat, + #[ignore_malloc_size_of = "Defined in ipc-channel"] + pub bytes: IpcSharedMemory, + #[ignore_malloc_size_of = "Defined in webrender_api"] + pub id: Option<ImageKey>, + pub cors_status: CorsStatus, +} + +impl fmt::Debug for Image { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Image {{ width: {}, height: {}, format: {:?}, ..., id: {:?} }}", + self.width, self.height, self.format, self.id + ) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)] +pub struct ImageMetadata { + pub width: u32, + pub height: u32, +} + +// FIXME: Images must not be copied every frame. Instead we should atomically +// reference count them. + +pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> { + if buffer.is_empty() { + return None; + } + + let image_fmt_result = detect_image_format(buffer); + match image_fmt_result { + Err(msg) => { + debug!("{}", msg); + None + }, + Ok(_) => match image::load_from_memory(buffer) { + Ok(image) => { + let mut rgba = image.into_rgba8(); + pixels::rgba8_byte_swap_colors_inplace(&mut *rgba); + Some(Image { + width: rgba.width(), + height: rgba.height(), + format: PixelFormat::BGRA8, + bytes: IpcSharedMemory::from_bytes(&*rgba), + id: None, + cors_status, + }) + }, + Err(e) => { + debug!("Image decoding error: {:?}", e); + None + }, + }, + } +} + +// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img +pub fn detect_image_format(buffer: &[u8]) -> Result<ImageFormat, &str> { + if is_gif(buffer) { + Ok(ImageFormat::Gif) + } else if is_jpeg(buffer) { + Ok(ImageFormat::Jpeg) + } else if is_png(buffer) { + Ok(ImageFormat::Png) + } else if is_webp(buffer) { + Ok(ImageFormat::WebP) + } else if is_bmp(buffer) { + Ok(ImageFormat::Bmp) + } else if is_ico(buffer) { + Ok(ImageFormat::Ico) + } else { + Err("Image Format Not Supported") + } +} + +fn is_gif(buffer: &[u8]) -> bool { + buffer.starts_with(b"GIF87a") || buffer.starts_with(b"GIF89a") +} + +fn is_jpeg(buffer: &[u8]) -> bool { + buffer.starts_with(&[0xff, 0xd8, 0xff]) +} + +fn is_png(buffer: &[u8]) -> bool { + buffer.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) +} + +fn is_bmp(buffer: &[u8]) -> bool { + buffer.starts_with(&[0x42, 0x4D]) +} + +fn is_ico(buffer: &[u8]) -> bool { + buffer.starts_with(&[0x00, 0x00, 0x01, 0x00]) +} + +fn is_webp(buffer: &[u8]) -> bool { + buffer.starts_with(b"RIFF") && buffer.len() >= 14 && &buffer[8..14] == b"WEBPVP" +} diff --git a/components/shared/net/image_cache.rs b/components/shared/net/image_cache.rs new file mode 100644 index 00000000000..81d34964db4 --- /dev/null +++ b/components/shared/net/image_cache.rs @@ -0,0 +1,156 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::sync::Arc; + +use ipc_channel::ipc::IpcSender; +use log::debug; +use malloc_size_of_derive::MallocSizeOf; +use serde::{Deserialize, Serialize}; +use servo_url::{ImmutableOrigin, ServoUrl}; + +use crate::image::base::{Image, ImageMetadata}; +use crate::request::CorsSettings; +use crate::{FetchResponseMsg, WebrenderIpcSender}; + +// ====================================================================== +// Aux structs and enums. +// ====================================================================== + +/// Indicating either entire image or just metadata availability +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum ImageOrMetadataAvailable { + ImageAvailable { + #[ignore_malloc_size_of = "Arc"] + image: Arc<Image>, + url: ServoUrl, + is_placeholder: bool, + }, + MetadataAvailable(ImageMetadata), +} + +/// This is optionally passed to the image cache when requesting +/// and image, and returned to the specified event loop when the +/// image load completes. It is typically used to trigger a reflow +/// and/or repaint. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ImageResponder { + id: PendingImageId, + sender: IpcSender<PendingImageResponse>, +} + +impl ImageResponder { + pub fn new(sender: IpcSender<PendingImageResponse>, id: PendingImageId) -> ImageResponder { + ImageResponder { + sender: sender, + id: id, + } + } + + pub fn respond(&self, response: ImageResponse) { + debug!("Notifying listener"); + // This send can fail if thread waiting for this notification has panicked. + // That's not a case that's worth warning about. + // TODO(#15501): are there cases in which we should perform cleanup? + let _ = self.sender.send(PendingImageResponse { + response: response, + id: self.id, + }); + } +} + +/// The returned image. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum ImageResponse { + /// The requested image was loaded. + Loaded(#[ignore_malloc_size_of = "Arc"] Arc<Image>, ServoUrl), + /// The request image metadata was loaded. + MetadataLoaded(ImageMetadata), + /// The requested image failed to load, so a placeholder was loaded instead. + PlaceholderLoaded(#[ignore_malloc_size_of = "Arc"] Arc<Image>, ServoUrl), + /// Neither the requested image nor the placeholder could be loaded. + None, +} + +/// The unique id for an image that has previously been requested. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub struct PendingImageId(pub u64); + +#[derive(Debug, Deserialize, Serialize)] +pub struct PendingImageResponse { + pub response: ImageResponse, + pub id: PendingImageId, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum UsePlaceholder { + No, + Yes, +} + +// ====================================================================== +// ImageCache public API. +// ====================================================================== + +pub enum ImageCacheResult { + Available(ImageOrMetadataAvailable), + LoadError, + Pending(PendingImageId), + ReadyForRequest(PendingImageId), +} + +pub trait ImageCache: Sync + Send { + fn new(webrender_api: WebrenderIpcSender) -> Self + where + Self: Sized; + + /// Definitively check whether there is a cached, fully loaded image available. + fn get_image( + &self, + url: ServoUrl, + origin: ImmutableOrigin, + cors_setting: Option<CorsSettings>, + ) -> Option<Arc<Image>>; + + fn get_cached_image_status( + &self, + url: ServoUrl, + origin: ImmutableOrigin, + cors_setting: Option<CorsSettings>, + use_placeholder: UsePlaceholder, + ) -> ImageCacheResult; + + /// Add a listener for the provided pending image id, eventually called by + /// ImageCacheStore::complete_load. + /// If only metadata is available, Available(ImageOrMetadataAvailable) will + /// be returned. + /// If Available(ImageOrMetadataAvailable::Image) or LoadError is the final value, + /// the provided listener will be dropped (consumed & not added to PendingLoad). + fn track_image( + &self, + url: ServoUrl, + origin: ImmutableOrigin, + cors_setting: Option<CorsSettings>, + sender: IpcSender<PendingImageResponse>, + use_placeholder: UsePlaceholder, + ) -> ImageCacheResult; + + /// Add a new listener for the given pending image id. If the image is already present, + /// the responder will still receive the expected response. + fn add_listener(&self, id: PendingImageId, listener: ImageResponder); + + /// Inform the image cache about a response for a pending request. + fn notify_pending_response(&self, id: PendingImageId, action: FetchResponseMsg); +} + +/// Whether this response passed any CORS checks, and is thus safe to read from +/// in cross-origin environments. +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum CorsStatus { + /// The response is either same-origin or cross-origin but passed CORS checks. + Safe, + /// The response is cross-origin and did not pass CORS checks. It is unsafe + /// to expose pixel data to the requesting environment. + Unsafe, +} diff --git a/components/shared/net/lib.rs b/components/shared/net/lib.rs new file mode 100644 index 00000000000..b8b32725a25 --- /dev/null +++ b/components/shared/net/lib.rs @@ -0,0 +1,859 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#![deny(unsafe_code)] + +use cookie::Cookie; +use headers::{ContentType, HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader}; +use http::{Error as HttpError, HeaderMap, StatusCode}; +use hyper::Error as HyperError; +use hyper_serde::Serde; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::router::ROUTER; +use ipc_channel::Error as IpcError; +use lazy_static::lazy_static; +use log::warn; +use malloc_size_of::malloc_size_of_is_0; +use malloc_size_of_derive::MallocSizeOf; +use mime::Mime; +use msg::constellation_msg::HistoryStateId; +use rustls::Certificate; +use serde::{Deserialize, Serialize}; +use servo_rand::RngCore; +use servo_url::{ImmutableOrigin, ServoUrl}; +use time::precise_time_ns; +use webrender_api::{ImageData, ImageDescriptor, ImageKey}; + +use crate::filemanager_thread::FileManagerThreadMsg; +use crate::request::{Request, RequestBuilder}; +use crate::response::{HttpsState, Response, ResponseInit}; +use crate::storage_thread::StorageThreadMsg; + +pub mod blob_url_store; +pub mod filemanager_thread; +pub mod image_cache; +pub mod pub_domains; +pub mod quality; +pub mod request; +pub mod response; +pub mod storage_thread; + +/// Image handling. +/// +/// It may be surprising that this goes in the network crate as opposed to the graphics crate. +/// However, image handling is generally very integrated with the network stack (especially where +/// caching is involved) and as a result it must live in here. +pub mod image { + pub mod base; +} + +/// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/) +pub mod fetch { + pub mod headers; +} + +/// A loading context, for context-specific sniffing, as defined in +/// <https://mimesniff.spec.whatwg.org/#context-specific-sniffing> +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum LoadContext { + Browsing, + Image, + AudioVideo, + Plugin, + Style, + Script, + Font, + TextTrack, + CacheManifest, +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct CustomResponse { + #[ignore_malloc_size_of = "Defined in hyper"] + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + pub headers: HeaderMap, + #[ignore_malloc_size_of = "Defined in hyper"] + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + pub raw_status: (StatusCode, String), + pub body: Vec<u8>, +} + +impl CustomResponse { + pub fn new( + headers: HeaderMap, + raw_status: (StatusCode, String), + body: Vec<u8>, + ) -> CustomResponse { + CustomResponse { + headers: headers, + raw_status: raw_status, + body: body, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CustomResponseMediator { + pub response_chan: IpcSender<Option<CustomResponse>>, + pub load_url: ServoUrl, +} + +/// [Policies](https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-states) +/// for providing a referrer header for a request +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum ReferrerPolicy { + /// "no-referrer" + NoReferrer, + /// "no-referrer-when-downgrade" + NoReferrerWhenDowngrade, + /// "origin" + Origin, + /// "same-origin" + SameOrigin, + /// "origin-when-cross-origin" + OriginWhenCrossOrigin, + /// "unsafe-url" + UnsafeUrl, + /// "strict-origin" + StrictOrigin, + /// "strict-origin-when-cross-origin" + StrictOriginWhenCrossOrigin, +} + +impl ToString for ReferrerPolicy { + fn to_string(&self) -> String { + match self { + ReferrerPolicy::NoReferrer => "no-referrer", + ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade", + ReferrerPolicy::Origin => "origin", + ReferrerPolicy::SameOrigin => "same-origin", + ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin", + ReferrerPolicy::UnsafeUrl => "unsafe-url", + ReferrerPolicy::StrictOrigin => "strict-origin", + ReferrerPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin", + } + .to_string() + } +} + +impl From<ReferrerPolicyHeader> for ReferrerPolicy { + fn from(policy: ReferrerPolicyHeader) -> Self { + match policy { + ReferrerPolicyHeader::NO_REFERRER => ReferrerPolicy::NoReferrer, + ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE => { + ReferrerPolicy::NoReferrerWhenDowngrade + }, + ReferrerPolicyHeader::SAME_ORIGIN => ReferrerPolicy::SameOrigin, + ReferrerPolicyHeader::ORIGIN => ReferrerPolicy::Origin, + ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::OriginWhenCrossOrigin, + ReferrerPolicyHeader::UNSAFE_URL => ReferrerPolicy::UnsafeUrl, + ReferrerPolicyHeader::STRICT_ORIGIN => ReferrerPolicy::StrictOrigin, + ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN => { + ReferrerPolicy::StrictOriginWhenCrossOrigin + }, + } + } +} + +impl From<ReferrerPolicy> for ReferrerPolicyHeader { + fn from(referrer_policy: ReferrerPolicy) -> Self { + match referrer_policy { + ReferrerPolicy::NoReferrer => ReferrerPolicyHeader::NO_REFERRER, + ReferrerPolicy::NoReferrerWhenDowngrade => { + ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE + }, + ReferrerPolicy::SameOrigin => ReferrerPolicyHeader::SAME_ORIGIN, + ReferrerPolicy::Origin => ReferrerPolicyHeader::ORIGIN, + ReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN, + ReferrerPolicy::UnsafeUrl => ReferrerPolicyHeader::UNSAFE_URL, + ReferrerPolicy::StrictOrigin => ReferrerPolicyHeader::STRICT_ORIGIN, + ReferrerPolicy::StrictOriginWhenCrossOrigin => { + ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN + }, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum FetchResponseMsg { + // todo: should have fields for transmitted/total bytes + ProcessRequestBody, + ProcessRequestEOF, + // todo: send more info about the response (or perhaps the entire Response) + ProcessResponse(Result<FetchMetadata, NetworkError>), + ProcessResponseChunk(Vec<u8>), + ProcessResponseEOF(Result<ResourceFetchTiming, NetworkError>), +} + +pub trait FetchTaskTarget { + /// <https://fetch.spec.whatwg.org/#process-request-body> + /// + /// Fired when a chunk of the request body is transmitted + fn process_request_body(&mut self, request: &Request); + + /// <https://fetch.spec.whatwg.org/#process-request-end-of-file> + /// + /// Fired when the entire request finishes being transmitted + fn process_request_eof(&mut self, request: &Request); + + /// <https://fetch.spec.whatwg.org/#process-response> + /// + /// Fired when headers are received + fn process_response(&mut self, response: &Response); + + /// Fired when a chunk of response content is received + fn process_response_chunk(&mut self, chunk: Vec<u8>); + + /// <https://fetch.spec.whatwg.org/#process-response-end-of-file> + /// + /// Fired when the response is fully fetched + fn process_response_eof(&mut self, response: &Response); +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum FilteredMetadata { + Basic(Metadata), + Cors(Metadata), + Opaque, + OpaqueRedirect(ServoUrl), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum FetchMetadata { + Unfiltered(Metadata), + Filtered { + filtered: FilteredMetadata, + unsafe_: Metadata, + }, +} + +pub trait FetchResponseListener { + fn process_request_body(&mut self); + fn process_request_eof(&mut self); + fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>); + fn process_response_chunk(&mut self, chunk: Vec<u8>); + fn process_response_eof(&mut self, response: Result<ResourceFetchTiming, NetworkError>); + fn resource_timing(&self) -> &ResourceFetchTiming; + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming; + fn submit_resource_timing(&mut self); +} + +impl FetchTaskTarget for IpcSender<FetchResponseMsg> { + fn process_request_body(&mut self, _: &Request) { + let _ = self.send(FetchResponseMsg::ProcessRequestBody); + } + + fn process_request_eof(&mut self, _: &Request) { + let _ = self.send(FetchResponseMsg::ProcessRequestEOF); + } + + fn process_response(&mut self, response: &Response) { + let _ = self.send(FetchResponseMsg::ProcessResponse(response.metadata())); + } + + fn process_response_chunk(&mut self, chunk: Vec<u8>) { + let _ = self.send(FetchResponseMsg::ProcessResponseChunk(chunk)); + } + + fn process_response_eof(&mut self, response: &Response) { + if let Some(e) = response.get_network_error() { + let _ = self.send(FetchResponseMsg::ProcessResponseEOF(Err(e.clone()))); + } else { + let _ = self.send(FetchResponseMsg::ProcessResponseEOF(Ok(response + .get_resource_timing() + .lock() + .unwrap() + .clone()))); + } + } +} + +/// A fetch task that discards all data it's sent, +/// useful when speculatively prefetching data that we don't need right +/// now, but might need in the future. +pub struct DiscardFetch; + +impl FetchTaskTarget for DiscardFetch { + fn process_request_body(&mut self, _: &Request) {} + + fn process_request_eof(&mut self, _: &Request) {} + + fn process_response(&mut self, _: &Response) {} + + fn process_response_chunk(&mut self, _: Vec<u8>) {} + + fn process_response_eof(&mut self, _: &Response) {} +} + +pub trait Action<Listener> { + fn process(self, listener: &mut Listener); +} + +impl<T: FetchResponseListener> Action<T> for FetchResponseMsg { + /// Execute the default action on a provided listener. + fn process(self, listener: &mut T) { + match self { + FetchResponseMsg::ProcessRequestBody => listener.process_request_body(), + FetchResponseMsg::ProcessRequestEOF => listener.process_request_eof(), + FetchResponseMsg::ProcessResponse(meta) => listener.process_response(meta), + FetchResponseMsg::ProcessResponseChunk(data) => listener.process_response_chunk(data), + FetchResponseMsg::ProcessResponseEOF(data) => { + match data { + Ok(ref response_resource_timing) => { + // update listener with values from response + *listener.resource_timing_mut() = response_resource_timing.clone(); + listener.process_response_eof(Ok(response_resource_timing.clone())); + // TODO timing check https://w3c.github.io/resource-timing/#dfn-timing-allow-check + + listener.submit_resource_timing(); + }, + // TODO Resources for which the fetch was initiated, but was later aborted + // (e.g. due to a network error) MAY be included as PerformanceResourceTiming + // objects in the Performance Timeline and MUST contain initialized attribute + // values for processed substeps of the processing model. + Err(e) => listener.process_response_eof(Err(e)), + } + }, + } + } +} + +/// Handle to a resource thread +pub type CoreResourceThread = IpcSender<CoreResourceMsg>; + +pub type IpcSendResult = Result<(), IpcError>; + +/// Abstraction of the ability to send a particular type of message, +/// used by net_traits::ResourceThreads to ease the use its IpcSender sub-fields +/// XXX: If this trait will be used more in future, some auto derive might be appealing +pub trait IpcSend<T> +where + T: serde::Serialize + for<'de> serde::Deserialize<'de>, +{ + /// send message T + fn send(&self, _: T) -> IpcSendResult; + /// get underlying sender + fn sender(&self) -> IpcSender<T>; +} + +// FIXME: Originally we will construct an Arc<ResourceThread> from ResourceThread +// in script_thread to avoid some performance pitfall. Now we decide to deal with +// the "Arc" hack implicitly in future. +// See discussion: http://logs.glob.uno/?c=mozilla%23servo&s=16+May+2016&e=16+May+2016#c430412 +// See also: https://github.com/servo/servo/blob/735480/components/script/script_thread.rs#L313 +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ResourceThreads { + core_thread: CoreResourceThread, + storage_thread: IpcSender<StorageThreadMsg>, +} + +impl ResourceThreads { + pub fn new(c: CoreResourceThread, s: IpcSender<StorageThreadMsg>) -> ResourceThreads { + ResourceThreads { + core_thread: c, + storage_thread: s, + } + } + + pub fn clear_cache(&self) { + let _ = self.core_thread.send(CoreResourceMsg::ClearCache); + } +} + +impl IpcSend<CoreResourceMsg> for ResourceThreads { + fn send(&self, msg: CoreResourceMsg) -> IpcSendResult { + self.core_thread.send(msg) + } + + fn sender(&self) -> IpcSender<CoreResourceMsg> { + self.core_thread.clone() + } +} + +impl IpcSend<StorageThreadMsg> for ResourceThreads { + fn send(&self, msg: StorageThreadMsg) -> IpcSendResult { + self.storage_thread.send(msg) + } + + fn sender(&self) -> IpcSender<StorageThreadMsg> { + self.storage_thread.clone() + } +} + +// Ignore the sub-fields +malloc_size_of_is_0!(ResourceThreads); + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub enum IncludeSubdomains { + Included, + NotIncluded, +} + +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum MessageData { + Text(String), + Binary(Vec<u8>), +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum WebSocketDomAction { + SendMessage(MessageData), + Close(Option<u16>, Option<String>), +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum WebSocketNetworkEvent { + ConnectionEstablished { protocol_in_use: Option<String> }, + MessageReceived(MessageData), + Close(Option<u16>, String), + Fail, +} + +#[derive(Debug, Deserialize, Serialize)] +/// IPC channels to communicate with the script thread about network or DOM events. +pub enum FetchChannels { + ResponseMsg( + IpcSender<FetchResponseMsg>, + /* cancel_chan */ Option<IpcReceiver<()>>, + ), + WebSocket { + event_sender: IpcSender<WebSocketNetworkEvent>, + action_receiver: IpcReceiver<WebSocketDomAction>, + }, + /// If the fetch is just being done to populate the cache, + /// not because the data is needed now. + Prefetch, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum CoreResourceMsg { + Fetch(RequestBuilder, FetchChannels), + /// Initiate a fetch in response to processing a redirection + FetchRedirect( + RequestBuilder, + ResponseInit, + IpcSender<FetchResponseMsg>, + /* cancel_chan */ Option<IpcReceiver<()>>, + ), + /// Store a cookie for a given originating URL + SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource), + /// Store a set of cookies for a given originating URL + SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource), + /// Retrieve the stored cookies for a given URL + GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource), + /// Get a cookie by name for a given originating URL + GetCookiesDataForUrl( + ServoUrl, + IpcSender<Vec<Serde<Cookie<'static>>>>, + CookieSource, + ), + DeleteCookies(ServoUrl), + /// Get a history state by a given history state id + GetHistoryState(HistoryStateId, IpcSender<Option<Vec<u8>>>), + /// Set a history state for a given history state id + SetHistoryState(HistoryStateId, Vec<u8>), + /// Removes history states for the given ids + RemoveHistoryStates(Vec<HistoryStateId>), + /// Synchronization message solely for knowing the state of the ResourceChannelManager loop + Synchronize(IpcSender<()>), + /// Clear the network cache. + ClearCache, + /// Send the service worker network mediator for an origin to CoreResourceThread + NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin), + /// Message forwarded to file manager's handler + ToFileManager(FileManagerThreadMsg), + /// Break the load handler loop, send a reply when done cleaning up local resources + /// and exit + Exit(IpcSender<()>), +} + +/// Instruct the resource thread to make a new request. +pub fn fetch_async<F>(request: RequestBuilder, core_resource_thread: &CoreResourceThread, f: F) +where + F: Fn(FetchResponseMsg) + Send + 'static, +{ + let (action_sender, action_receiver) = ipc::channel().unwrap(); + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| f(message.to().unwrap())), + ); + core_resource_thread + .send(CoreResourceMsg::Fetch( + request, + FetchChannels::ResponseMsg(action_sender, None), + )) + .unwrap(); +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct ResourceCorsData { + /// CORS Preflight flag + pub preflight: bool, + /// Origin of CORS Request + pub origin: ServoUrl, +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct ResourceFetchTiming { + pub domain_lookup_start: u64, + pub timing_check_passed: bool, + pub timing_type: ResourceTimingType, + /// Number of redirects until final resource (currently limited to 20) + pub redirect_count: u16, + pub request_start: u64, + pub secure_connection_start: u64, + pub response_start: u64, + pub fetch_start: u64, + pub response_end: u64, + pub redirect_start: u64, + pub redirect_end: u64, + pub connect_start: u64, + pub connect_end: u64, + pub start_time: u64, +} + +pub enum RedirectStartValue { + #[allow(dead_code)] + Zero, + FetchStart, +} + +pub enum RedirectEndValue { + Zero, + ResponseEnd, +} + +// TODO: refactor existing code to use this enum for setting time attributes +// suggest using this with all time attributes in the future +pub enum ResourceTimeValue { + Zero, + Now, + FetchStart, + RedirectStart, +} + +pub enum ResourceAttribute { + RedirectCount(u16), + DomainLookupStart, + RequestStart, + ResponseStart, + RedirectStart(RedirectStartValue), + RedirectEnd(RedirectEndValue), + FetchStart, + ConnectStart(u64), + ConnectEnd(u64), + SecureConnectionStart, + ResponseEnd, + StartTime(ResourceTimeValue), +} + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum ResourceTimingType { + Resource, + Navigation, + Error, + None, +} + +impl ResourceFetchTiming { + pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming { + ResourceFetchTiming { + timing_type: timing_type, + timing_check_passed: true, + domain_lookup_start: 0, + redirect_count: 0, + secure_connection_start: 0, + request_start: 0, + response_start: 0, + fetch_start: 0, + redirect_start: 0, + redirect_end: 0, + connect_start: 0, + connect_end: 0, + response_end: 0, + start_time: 0, + } + } + + // TODO currently this is being set with precise time ns when it should be time since + // time origin (as described in Performance::now) + pub fn set_attribute(&mut self, attribute: ResourceAttribute) { + let should_attribute_always_be_updated = match attribute { + ResourceAttribute::FetchStart | + ResourceAttribute::ResponseEnd | + ResourceAttribute::StartTime(_) => true, + _ => false, + }; + if !self.timing_check_passed && !should_attribute_always_be_updated { + return; + } + match attribute { + ResourceAttribute::DomainLookupStart => self.domain_lookup_start = precise_time_ns(), + ResourceAttribute::RedirectCount(count) => self.redirect_count = count, + ResourceAttribute::RequestStart => self.request_start = precise_time_ns(), + ResourceAttribute::ResponseStart => self.response_start = precise_time_ns(), + ResourceAttribute::RedirectStart(val) => match val { + RedirectStartValue::Zero => self.redirect_start = 0, + RedirectStartValue::FetchStart => { + if self.redirect_start == 0 { + self.redirect_start = self.fetch_start + } + }, + }, + ResourceAttribute::RedirectEnd(val) => match val { + RedirectEndValue::Zero => self.redirect_end = 0, + RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end, + }, + ResourceAttribute::FetchStart => self.fetch_start = precise_time_ns(), + ResourceAttribute::ConnectStart(val) => self.connect_start = val, + ResourceAttribute::ConnectEnd(val) => self.connect_end = val, + ResourceAttribute::SecureConnectionStart => { + self.secure_connection_start = precise_time_ns() + }, + ResourceAttribute::ResponseEnd => self.response_end = precise_time_ns(), + ResourceAttribute::StartTime(val) => match val { + ResourceTimeValue::RedirectStart + if self.redirect_start == 0 || !self.timing_check_passed => {}, + _ => self.start_time = self.get_time_value(val), + }, + } + } + + fn get_time_value(&self, time: ResourceTimeValue) -> u64 { + match time { + ResourceTimeValue::Zero => 0, + ResourceTimeValue::Now => precise_time_ns(), + ResourceTimeValue::FetchStart => self.fetch_start, + ResourceTimeValue::RedirectStart => self.redirect_start, + } + } + + pub fn mark_timing_check_failed(&mut self) { + self.timing_check_passed = false; + self.domain_lookup_start = 0; + self.redirect_count = 0; + self.request_start = 0; + self.response_start = 0; + self.redirect_start = 0; + self.connect_start = 0; + self.connect_end = 0; + } +} + +/// Metadata about a loaded resource, such as is obtained from HTTP headers. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct Metadata { + /// Final URL after redirects. + pub final_url: ServoUrl, + + /// Location URL from the response headers. + pub location_url: Option<Result<ServoUrl, String>>, + + #[ignore_malloc_size_of = "Defined in hyper"] + /// MIME type / subtype. + pub content_type: Option<Serde<ContentType>>, + + /// Character set. + pub charset: Option<String>, + + #[ignore_malloc_size_of = "Defined in hyper"] + /// Headers + pub headers: Option<Serde<HeaderMap>>, + + /// HTTP Status + pub status: Option<(u16, Vec<u8>)>, + + /// Is successful HTTPS connection + pub https_state: HttpsState, + + /// Referrer Url + pub referrer: Option<ServoUrl>, + + /// Referrer Policy of the Request used to obtain Response + pub referrer_policy: Option<ReferrerPolicy>, + /// Performance information for navigation events + pub timing: Option<ResourceFetchTiming>, + /// True if the request comes from a redirection + pub redirected: bool, +} + +impl Metadata { + /// Metadata with defaults for everything optional. + pub fn default(url: ServoUrl) -> Self { + Metadata { + final_url: url, + location_url: None, + content_type: None, + charset: None, + headers: None, + // https://fetch.spec.whatwg.org/#concept-response-status-message + status: Some((200, b"".to_vec())), + https_state: HttpsState::None, + referrer: None, + referrer_policy: None, + timing: None, + redirected: false, + } + } + + /// Extract the parts of a Mime that we care about. + pub fn set_content_type(&mut self, content_type: Option<&Mime>) { + if self.headers.is_none() { + self.headers = Some(Serde(HeaderMap::new())); + } + + if let Some(mime) = content_type { + self.headers + .as_mut() + .unwrap() + .typed_insert(ContentType::from(mime.clone())); + if let Some(charset) = mime.get_param(mime::CHARSET) { + self.charset = Some(charset.to_string()); + } + self.content_type = Some(Serde(ContentType::from(mime.clone()))); + } + } + + /// Set the referrer policy associated with the loaded resource. + pub fn set_referrer_policy(&mut self, referrer_policy: Option<ReferrerPolicy>) { + if self.headers.is_none() { + self.headers = Some(Serde(HeaderMap::new())); + } + + self.referrer_policy = referrer_policy; + if let Some(referrer_policy) = referrer_policy { + self.headers + .as_mut() + .unwrap() + .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into()); + } + } +} + +/// The creator of a given cookie +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub enum CookieSource { + /// An HTTP API + HTTP, + /// A non-HTTP API + NonHTTP, +} + +/// Network errors that have to be exported out of the loaders +#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)] +pub enum NetworkError { + /// Could be any of the internal errors, like unsupported scheme, connection errors, etc. + Internal(String), + LoadCancelled, + /// SSL validation error, to be converted to Resource::BadCertHTML in the HTML parser. + SslValidation(String, Vec<u8>), + /// Crash error, to be converted to Resource::Crash in the HTML parser. + Crash(String), +} + +impl NetworkError { + pub fn from_hyper_error(error: &HyperError, certificate: Option<Certificate>) -> Self { + let error_string = error.to_string(); + match certificate { + Some(certificate) => NetworkError::SslValidation(error_string, certificate.0), + _ => NetworkError::Internal(error_string), + } + } + + pub fn from_http_error(error: &HttpError) -> Self { + NetworkError::Internal(error.to_string()) + } +} + +/// Normalize `slice`, as defined by +/// [the Fetch Spec](https://fetch.spec.whatwg.org/#concept-header-value-normalize). +pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] { + const HTTP_WS_BYTES: &'static [u8] = b"\x09\x0A\x0D\x20"; + + loop { + match slice.split_first() { + Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder, + _ => break, + } + } + + loop { + match slice.split_last() { + Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder, + _ => break, + } + } + + slice +} + +pub fn http_percent_encode(bytes: &[u8]) -> String { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS + .add(b' ') + .add(b'"') + .add(b'%') + .add(b'\'') + .add(b'(') + .add(b')') + .add(b'*') + .add(b',') + .add(b'/') + .add(b':') + .add(b';') + .add(b'<') + .add(b'-') + .add(b'>') + .add(b'?') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'{') + .add(b'}'); + + percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string() +} + +#[derive(Deserialize, Serialize)] +pub enum NetToCompositorMsg { + AddImage(ImageKey, ImageDescriptor, ImageData), + GenerateImageKey(IpcSender<ImageKey>), +} + +#[derive(Clone, Deserialize, Serialize)] +pub struct WebrenderIpcSender(IpcSender<NetToCompositorMsg>); + +impl WebrenderIpcSender { + pub fn new(sender: IpcSender<NetToCompositorMsg>) -> Self { + Self(sender) + } + + pub fn generate_image_key(&self) -> ImageKey { + let (sender, receiver) = ipc::channel().unwrap(); + self.0 + .send(NetToCompositorMsg::GenerateImageKey(sender)) + .expect("error sending image key generation"); + receiver.recv().expect("error receiving image key result") + } + + pub fn add_image(&self, key: ImageKey, descriptor: ImageDescriptor, data: ImageData) { + if let Err(e) = self + .0 + .send(NetToCompositorMsg::AddImage(key, descriptor, data)) + { + warn!("Error sending image update: {}", e); + } + } +} + +lazy_static! { + pub static ref PRIVILEGED_SECRET: u32 = servo_rand::ServoRng::new().next_u32(); +} diff --git a/components/shared/net/pub_domains.rs b/components/shared/net/pub_domains.rs new file mode 100644 index 00000000000..1efb8faf56d --- /dev/null +++ b/components/shared/net/pub_domains.rs @@ -0,0 +1,159 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! Implementation of public domain matching. +//! +//! The list is a file located on the `resources` folder and loaded once on first need. +//! +//! The list can be updated with `./mach update-pub-domains` from this source: +//! <https://publicsuffix.org/list/> +//! +//! This implementation is not strictly following the specification of the list. Wildcards are not +//! restricted to appear only in the leftmost position, but the current list has no such cases so +//! we don't need to make the code more complex for it. The `mach` update command makes sure that +//! those cases are not present. + +use std::collections::HashSet; +use std::iter::FromIterator; + +use embedder_traits::resources::{self, Resource}; +use lazy_static::lazy_static; +use servo_url::{Host, ImmutableOrigin, ServoUrl}; + +#[derive(Clone, Debug)] +pub struct PubDomainRules { + rules: HashSet<String>, + wildcards: HashSet<String>, + exceptions: HashSet<String>, +} + +lazy_static! { + static ref PUB_DOMAINS: PubDomainRules = load_pub_domains(); +} + +impl<'a> FromIterator<&'a str> for PubDomainRules { + fn from_iter<T>(iter: T) -> Self + where + T: IntoIterator<Item = &'a str>, + { + let mut result = PubDomainRules::new(); + for item in iter { + if item.starts_with("!") { + result.exceptions.insert(String::from(&item[1..])); + } else if item.starts_with("*.") { + result.wildcards.insert(String::from(&item[2..])); + } else { + result.rules.insert(String::from(item)); + } + } + result + } +} + +impl PubDomainRules { + pub fn new() -> PubDomainRules { + PubDomainRules { + rules: HashSet::new(), + wildcards: HashSet::new(), + exceptions: HashSet::new(), + } + } + pub fn parse(content: &str) -> PubDomainRules { + content + .lines() + .map(str::trim) + .filter(|s| !s.is_empty()) + .filter(|s| !s.starts_with("//")) + .collect() + } + fn suffix_pair<'a>(&self, domain: &'a str) -> (&'a str, &'a str) { + let domain = domain.trim_start_matches("."); + let mut suffix = domain; + let mut prev_suffix = domain; + for (index, _) in domain.match_indices(".") { + let next_suffix = &domain[index + 1..]; + if self.exceptions.contains(suffix) { + return (next_suffix, suffix); + } else if self.wildcards.contains(next_suffix) { + return (suffix, prev_suffix); + } else if self.rules.contains(suffix) { + return (suffix, prev_suffix); + } else { + prev_suffix = suffix; + suffix = next_suffix; + } + } + return (suffix, prev_suffix); + } + pub fn public_suffix<'a>(&self, domain: &'a str) -> &'a str { + let (public, _) = self.suffix_pair(domain); + public + } + pub fn registrable_suffix<'a>(&self, domain: &'a str) -> &'a str { + let (_, registrable) = self.suffix_pair(domain); + registrable + } + pub fn is_public_suffix(&self, domain: &str) -> bool { + // Speeded-up version of + // domain != "" && + // self.public_suffix(domain) == domain. + let domain = domain.trim_start_matches("."); + match domain.find(".") { + None => !domain.is_empty(), + Some(index) => { + !self.exceptions.contains(domain) && self.wildcards.contains(&domain[index + 1..]) || + self.rules.contains(domain) + }, + } + } + pub fn is_registrable_suffix(&self, domain: &str) -> bool { + // Speeded-up version of + // self.public_suffix(domain) != domain && + // self.registrable_suffix(domain) == domain. + let domain = domain.trim_start_matches("."); + match domain.find(".") { + None => false, + Some(index) => { + self.exceptions.contains(domain) || + !self.wildcards.contains(&domain[index + 1..]) && + !self.rules.contains(domain) && + self.is_public_suffix(&domain[index + 1..]) + }, + } + } +} + +fn load_pub_domains() -> PubDomainRules { + PubDomainRules::parse(&resources::read_string(Resource::DomainList)) +} + +pub fn pub_suffix(domain: &str) -> &str { + PUB_DOMAINS.public_suffix(domain) +} + +pub fn reg_suffix(domain: &str) -> &str { + PUB_DOMAINS.registrable_suffix(domain) +} + +pub fn is_pub_domain(domain: &str) -> bool { + PUB_DOMAINS.is_public_suffix(domain) +} + +pub fn is_reg_domain(domain: &str) -> bool { + PUB_DOMAINS.is_registrable_suffix(domain) +} + +/// The registered domain name (aka eTLD+1) for a URL. +/// Returns None if the URL has no host name. +/// Returns the registered suffix for the host name if it is a domain. +/// Leaves the host name alone if it is an IP address. +pub fn reg_host(url: &ServoUrl) -> Option<Host> { + match url.origin() { + ImmutableOrigin::Tuple(_, Host::Domain(domain), _) => { + Some(Host::Domain(String::from(reg_suffix(&*domain)))) + }, + ImmutableOrigin::Tuple(_, ip, _) => Some(ip), + ImmutableOrigin::Opaque(_) => None, + } +} diff --git a/components/shared/net/quality.rs b/components/shared/net/quality.rs new file mode 100644 index 00000000000..095cd121bad --- /dev/null +++ b/components/shared/net/quality.rs @@ -0,0 +1,87 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//TODO(eijebong): Remove this once typed headers figure out quality +// This is copy pasted from the old hyper headers to avoid hardcoding everything +// (I would probably also make some silly mistakes while migrating...) + +use std::{fmt, str}; + +use http::header::HeaderValue; +use mime::Mime; + +/// A quality value, as specified in [RFC7231]. +/// +/// Quality values are decimal numbers between 0 and 1 (inclusive) with up to 3 fractional digits of precision. +/// +/// [RFC7231]: https://tools.ietf.org/html/rfc7231#section-5.3.1 +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Quality(u16); + +impl Quality { + /// Creates a quality value from a value between 0 and 1000 inclusive. + /// + /// This is semantically divided by 1000 to produce a value between 0 and 1. + /// + /// # Panics + /// + /// Panics if the value is greater than 1000. + pub fn from_u16(quality: u16) -> Quality { + assert!(quality <= 1000); + Quality(quality) + } +} + +/// A value paired with its "quality" as defined in [RFC7231]. +/// +/// Quality items are used in content negotiation headers such as `Accept` and `Accept-Encoding`. +/// +/// [RFC7231]: https://tools.ietf.org/html/rfc7231#section-5.3 +#[derive(Clone, Debug, PartialEq)] +pub struct QualityItem<T> { + pub item: T, + pub quality: Quality, +} + +impl<T> QualityItem<T> { + /// Creates a new quality item. + pub fn new(item: T, quality: Quality) -> QualityItem<T> { + QualityItem { item, quality } + } +} + +impl<T> fmt::Display for QualityItem<T> +where + T: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.item, fmt)?; + match self.quality.0 { + 1000 => Ok(()), + 0 => fmt.write_str("; q=0"), + mut x => { + fmt.write_str("; q=0.")?; + let mut digits = *b"000"; + digits[2] = (x % 10) as u8 + b'0'; + x /= 10; + digits[1] = (x % 10) as u8 + b'0'; + x /= 10; + digits[0] = (x % 10) as u8 + b'0'; + + let s = str::from_utf8(&digits[..]).unwrap(); + fmt.write_str(s.trim_end_matches('0')) + }, + } + } +} + +pub fn quality_to_value(q: Vec<QualityItem<Mime>>) -> HeaderValue { + HeaderValue::from_str( + &q.iter() + .map(|q| q.to_string()) + .collect::<Vec<String>>() + .join(", "), + ) + .unwrap() +} diff --git a/components/shared/net/request.rs b/components/shared/net/request.rs new file mode 100644 index 00000000000..1f9f0abf986 --- /dev/null +++ b/components/shared/net/request.rs @@ -0,0 +1,749 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::sync::{Arc, Mutex}; + +use content_security_policy::{self as csp, CspList}; +use http::header::{HeaderName, AUTHORIZATION}; +use http::{HeaderMap, Method}; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use malloc_size_of_derive::MallocSizeOf; +use mime::Mime; +use msg::constellation_msg::PipelineId; +use serde::{Deserialize, Serialize}; +use servo_url::{ImmutableOrigin, ServoUrl}; + +use crate::response::HttpsState; +use crate::{ReferrerPolicy, ResourceTimingType}; + +/// An [initiator](https://fetch.spec.whatwg.org/#concept-request-initiator) +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum Initiator { + None, + Download, + ImageSet, + Manifest, + XSLT, +} + +/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination) +pub use csp::Destination; + +/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin) +#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum Origin { + Client, + Origin(ImmutableOrigin), +} + +/// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer) +#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum Referrer { + NoReferrer, + /// Contains the url that "client" would be resolved to. See + /// [https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer](https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer) + /// + /// If you are unsure you should probably use + /// [`GlobalScope::get_referrer`](https://doc.servo.org/script/dom/globalscope/struct.GlobalScope.html#method.get_referrer) + Client(ServoUrl), + ReferrerUrl(ServoUrl), +} + +/// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode) +#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum RequestMode { + Navigate, + SameOrigin, + NoCors, + CorsMode, + WebSocket { protocols: Vec<String> }, +} + +/// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode) +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum CredentialsMode { + Omit, + CredentialsSameOrigin, + Include, +} + +/// [Cache mode](https://fetch.spec.whatwg.org/#concept-request-cache-mode) +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum CacheMode { + Default, + NoStore, + Reload, + NoCache, + ForceCache, + OnlyIfCached, +} + +/// [Service-workers mode](https://fetch.spec.whatwg.org/#request-service-workers-mode) +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum ServiceWorkersMode { + All, + None, +} + +/// [Redirect mode](https://fetch.spec.whatwg.org/#concept-request-redirect-mode) +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum RedirectMode { + Follow, + Error, + Manual, +} + +/// [Response tainting](https://fetch.spec.whatwg.org/#concept-request-response-tainting) +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum ResponseTainting { + Basic, + CorsTainting, + Opaque, +} + +/// [Window](https://fetch.spec.whatwg.org/#concept-request-window) +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] +pub enum Window { + NoWindow, + Client, // TODO: Environmental settings object +} + +/// [CORS settings attribute](https://html.spec.whatwg.org/multipage/#attr-crossorigin-anonymous) +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum CorsSettings { + Anonymous, + UseCredentials, +} + +/// [Parser Metadata](https://fetch.spec.whatwg.org/#concept-request-parser-metadata) +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum ParserMetadata { + Default, + ParserInserted, + NotParserInserted, +} + +/// <https://fetch.spec.whatwg.org/#concept-body-source> +#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum BodySource { + Null, + Object, +} + +/// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body> +/// which are sent from script to net. +#[derive(Debug, Deserialize, Serialize)] +pub enum BodyChunkResponse { + /// A chunk of bytes. + Chunk(Vec<u8>), + /// The body is done. + Done, + /// There was an error streaming the body, + /// terminate fetch. + Error, +} + +/// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body> +/// which are sent from net to script +/// (with the exception of Done, which is sent from script to script). +#[derive(Debug, Deserialize, Serialize)] +pub enum BodyChunkRequest { + /// Connect a fetch in `net`, with a stream of bytes from `script`. + Connect(IpcSender<BodyChunkResponse>), + /// Re-extract a new stream from the source, following a redirect. + Extract(IpcReceiver<BodyChunkRequest>), + /// Ask for another chunk. + Chunk, + /// Signal the stream is done(sent from script to script). + Done, + /// Signal the stream has errored(sent from script to script). + Error, +} + +/// The net component's view into <https://fetch.spec.whatwg.org/#bodies> +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct RequestBody { + /// Net's channel to communicate with script re this body. + #[ignore_malloc_size_of = "Channels are hard"] + chan: Arc<Mutex<IpcSender<BodyChunkRequest>>>, + /// <https://fetch.spec.whatwg.org/#concept-body-source> + source: BodySource, + /// <https://fetch.spec.whatwg.org/#concept-body-total-bytes> + total_bytes: Option<usize>, +} + +impl RequestBody { + pub fn new( + chan: IpcSender<BodyChunkRequest>, + source: BodySource, + total_bytes: Option<usize>, + ) -> Self { + RequestBody { + chan: Arc::new(Mutex::new(chan)), + source, + total_bytes, + } + } + + /// Step 12 of https://fetch.spec.whatwg.org/#concept-http-redirect-fetch + pub fn extract_source(&mut self) { + match self.source { + BodySource::Null => panic!("Null sources should never be re-directed."), + BodySource::Object => { + let (chan, port) = ipc::channel().unwrap(); + let mut selfchan = self.chan.lock().unwrap(); + let _ = selfchan.send(BodyChunkRequest::Extract(port)); + *selfchan = chan; + }, + } + } + + pub fn take_stream(&self) -> Arc<Mutex<IpcSender<BodyChunkRequest>>> { + self.chan.clone() + } + + pub fn source_is_null(&self) -> bool { + self.source == BodySource::Null + } + + pub fn len(&self) -> Option<usize> { + self.total_bytes.clone() + } +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct RequestBuilder { + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + #[ignore_malloc_size_of = "Defined in hyper"] + pub method: Method, + pub url: ServoUrl, + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + #[ignore_malloc_size_of = "Defined in hyper"] + pub headers: HeaderMap, + pub unsafe_request: bool, + pub body: Option<RequestBody>, + pub service_workers_mode: ServiceWorkersMode, + // TODO: client object + pub destination: Destination, + pub synchronous: bool, + pub mode: RequestMode, + pub cache_mode: CacheMode, + pub use_cors_preflight: bool, + pub credentials_mode: CredentialsMode, + pub use_url_credentials: bool, + pub origin: ImmutableOrigin, + // XXXManishearth these should be part of the client object + pub referrer: Referrer, + pub referrer_policy: Option<ReferrerPolicy>, + pub pipeline_id: Option<PipelineId>, + pub redirect_mode: RedirectMode, + pub integrity_metadata: String, + // This is nominally a part of the client's global object. + // It is copied here to avoid having to reach across the thread + // boundary every time a redirect occurs. + #[ignore_malloc_size_of = "Defined in rust-content-security-policy"] + pub csp_list: Option<CspList>, + // to keep track of redirects + pub url_list: Vec<ServoUrl>, + pub parser_metadata: ParserMetadata, + pub initiator: Initiator, + pub https_state: HttpsState, + pub response_tainting: ResponseTainting, + /// Servo internal: if crash details are present, trigger a crash error page with these details. + pub crash: Option<String>, +} + +impl RequestBuilder { + pub fn new(url: ServoUrl, referrer: Referrer) -> RequestBuilder { + RequestBuilder { + method: Method::GET, + url: url, + headers: HeaderMap::new(), + unsafe_request: false, + body: None, + service_workers_mode: ServiceWorkersMode::All, + destination: Destination::None, + synchronous: false, + mode: RequestMode::NoCors, + cache_mode: CacheMode::Default, + use_cors_preflight: false, + credentials_mode: CredentialsMode::CredentialsSameOrigin, + use_url_credentials: false, + origin: ImmutableOrigin::new_opaque(), + referrer: referrer, + referrer_policy: None, + pipeline_id: None, + redirect_mode: RedirectMode::Follow, + integrity_metadata: "".to_owned(), + url_list: vec![], + parser_metadata: ParserMetadata::Default, + initiator: Initiator::None, + csp_list: None, + https_state: HttpsState::None, + response_tainting: ResponseTainting::Basic, + crash: None, + } + } + + pub fn initiator(mut self, initiator: Initiator) -> RequestBuilder { + self.initiator = initiator; + self + } + + pub fn method(mut self, method: Method) -> RequestBuilder { + self.method = method; + self + } + + pub fn headers(mut self, headers: HeaderMap) -> RequestBuilder { + self.headers = headers; + self + } + + pub fn unsafe_request(mut self, unsafe_request: bool) -> RequestBuilder { + self.unsafe_request = unsafe_request; + self + } + + pub fn body(mut self, body: Option<RequestBody>) -> RequestBuilder { + self.body = body; + self + } + + pub fn destination(mut self, destination: Destination) -> RequestBuilder { + self.destination = destination; + self + } + + pub fn synchronous(mut self, synchronous: bool) -> RequestBuilder { + self.synchronous = synchronous; + self + } + + pub fn mode(mut self, mode: RequestMode) -> RequestBuilder { + self.mode = mode; + self + } + + pub fn use_cors_preflight(mut self, use_cors_preflight: bool) -> RequestBuilder { + self.use_cors_preflight = use_cors_preflight; + self + } + + pub fn credentials_mode(mut self, credentials_mode: CredentialsMode) -> RequestBuilder { + self.credentials_mode = credentials_mode; + self + } + + pub fn use_url_credentials(mut self, use_url_credentials: bool) -> RequestBuilder { + self.use_url_credentials = use_url_credentials; + self + } + + pub fn origin(mut self, origin: ImmutableOrigin) -> RequestBuilder { + self.origin = origin; + self + } + + pub fn referrer_policy(mut self, referrer_policy: Option<ReferrerPolicy>) -> RequestBuilder { + self.referrer_policy = referrer_policy; + self + } + + pub fn pipeline_id(mut self, pipeline_id: Option<PipelineId>) -> RequestBuilder { + self.pipeline_id = pipeline_id; + self + } + + pub fn redirect_mode(mut self, redirect_mode: RedirectMode) -> RequestBuilder { + self.redirect_mode = redirect_mode; + self + } + + pub fn integrity_metadata(mut self, integrity_metadata: String) -> RequestBuilder { + self.integrity_metadata = integrity_metadata; + self + } + + pub fn parser_metadata(mut self, parser_metadata: ParserMetadata) -> RequestBuilder { + self.parser_metadata = parser_metadata; + self + } + + pub fn https_state(mut self, https_state: HttpsState) -> RequestBuilder { + self.https_state = https_state; + self + } + + pub fn response_tainting(mut self, response_tainting: ResponseTainting) -> RequestBuilder { + self.response_tainting = response_tainting; + self + } + + pub fn crash(mut self, crash: Option<String>) -> Self { + self.crash = crash; + self + } + + pub fn build(self) -> Request { + let mut request = Request::new( + self.url.clone(), + Some(Origin::Origin(self.origin)), + self.referrer, + self.pipeline_id, + self.https_state, + ); + request.initiator = self.initiator; + request.method = self.method; + request.headers = self.headers; + request.unsafe_request = self.unsafe_request; + request.body = self.body; + request.service_workers_mode = self.service_workers_mode; + request.destination = self.destination; + request.synchronous = self.synchronous; + request.mode = self.mode; + request.use_cors_preflight = self.use_cors_preflight; + request.credentials_mode = self.credentials_mode; + request.use_url_credentials = self.use_url_credentials; + request.cache_mode = self.cache_mode; + request.referrer_policy = self.referrer_policy; + request.redirect_mode = self.redirect_mode; + let mut url_list = self.url_list; + if url_list.is_empty() { + url_list.push(self.url); + } + request.redirect_count = url_list.len() as u32 - 1; + request.url_list = url_list; + request.integrity_metadata = self.integrity_metadata; + request.parser_metadata = self.parser_metadata; + request.csp_list = self.csp_list; + request.response_tainting = self.response_tainting; + request.crash = self.crash; + request + } +} + +/// A [Request](https://fetch.spec.whatwg.org/#concept-request) as defined by +/// the Fetch spec. +#[derive(Clone, MallocSizeOf)] +pub struct Request { + /// <https://fetch.spec.whatwg.org/#concept-request-method> + #[ignore_malloc_size_of = "Defined in hyper"] + pub method: Method, + /// <https://fetch.spec.whatwg.org/#local-urls-only-flag> + pub local_urls_only: bool, + /// <https://fetch.spec.whatwg.org/#sandboxed-storage-area-urls-flag> + pub sandboxed_storage_area_urls: bool, + /// <https://fetch.spec.whatwg.org/#concept-request-header-list> + #[ignore_malloc_size_of = "Defined in hyper"] + pub headers: HeaderMap, + /// <https://fetch.spec.whatwg.org/#unsafe-request-flag> + pub unsafe_request: bool, + /// <https://fetch.spec.whatwg.org/#concept-request-body> + pub body: Option<RequestBody>, + // TODO: client object + pub window: Window, + // TODO: target browsing context + /// <https://fetch.spec.whatwg.org/#request-keepalive-flag> + pub keep_alive: bool, + /// <https://fetch.spec.whatwg.org/#request-service-workers-mode> + pub service_workers_mode: ServiceWorkersMode, + /// <https://fetch.spec.whatwg.org/#concept-request-initiator> + pub initiator: Initiator, + /// <https://fetch.spec.whatwg.org/#concept-request-destination> + pub destination: Destination, + // TODO: priority object + /// <https://fetch.spec.whatwg.org/#concept-request-origin> + pub origin: Origin, + /// <https://fetch.spec.whatwg.org/#concept-request-referrer> + pub referrer: Referrer, + /// <https://fetch.spec.whatwg.org/#concept-request-referrer-policy> + pub referrer_policy: Option<ReferrerPolicy>, + pub pipeline_id: Option<PipelineId>, + /// <https://fetch.spec.whatwg.org/#synchronous-flag> + pub synchronous: bool, + /// <https://fetch.spec.whatwg.org/#concept-request-mode> + pub mode: RequestMode, + /// <https://fetch.spec.whatwg.org/#use-cors-preflight-flag> + pub use_cors_preflight: bool, + /// <https://fetch.spec.whatwg.org/#concept-request-credentials-mode> + pub credentials_mode: CredentialsMode, + /// <https://fetch.spec.whatwg.org/#concept-request-use-url-credentials-flag> + pub use_url_credentials: bool, + /// <https://fetch.spec.whatwg.org/#concept-request-cache-mode> + pub cache_mode: CacheMode, + /// <https://fetch.spec.whatwg.org/#concept-request-redirect-mode> + pub redirect_mode: RedirectMode, + /// <https://fetch.spec.whatwg.org/#concept-request-integrity-metadata> + pub integrity_metadata: String, + // Use the last method on url_list to act as spec current url field, and + // first method to act as spec url field + /// <https://fetch.spec.whatwg.org/#concept-request-url-list> + pub url_list: Vec<ServoUrl>, + /// <https://fetch.spec.whatwg.org/#concept-request-redirect-count> + pub redirect_count: u32, + /// <https://fetch.spec.whatwg.org/#concept-request-response-tainting> + pub response_tainting: ResponseTainting, + /// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata> + pub parser_metadata: ParserMetadata, + // This is nominally a part of the client's global object. + // It is copied here to avoid having to reach across the thread + // boundary every time a redirect occurs. + #[ignore_malloc_size_of = "Defined in rust-content-security-policy"] + pub csp_list: Option<CspList>, + pub https_state: HttpsState, + /// Servo internal: if crash details are present, trigger a crash error page with these details. + pub crash: Option<String>, +} + +impl Request { + pub fn new( + url: ServoUrl, + origin: Option<Origin>, + referrer: Referrer, + pipeline_id: Option<PipelineId>, + https_state: HttpsState, + ) -> Request { + Request { + method: Method::GET, + local_urls_only: false, + sandboxed_storage_area_urls: false, + headers: HeaderMap::new(), + unsafe_request: false, + body: None, + window: Window::Client, + keep_alive: false, + service_workers_mode: ServiceWorkersMode::All, + initiator: Initiator::None, + destination: Destination::None, + origin: origin.unwrap_or(Origin::Client), + referrer: referrer, + referrer_policy: None, + pipeline_id: pipeline_id, + synchronous: false, + mode: RequestMode::NoCors, + use_cors_preflight: false, + credentials_mode: CredentialsMode::CredentialsSameOrigin, + use_url_credentials: false, + cache_mode: CacheMode::Default, + redirect_mode: RedirectMode::Follow, + integrity_metadata: String::new(), + url_list: vec![url], + parser_metadata: ParserMetadata::Default, + redirect_count: 0, + response_tainting: ResponseTainting::Basic, + csp_list: None, + https_state: https_state, + crash: None, + } + } + + /// <https://fetch.spec.whatwg.org/#concept-request-url> + pub fn url(&self) -> ServoUrl { + self.url_list.first().unwrap().clone() + } + + /// <https://fetch.spec.whatwg.org/#concept-request-current-url> + pub fn current_url(&self) -> ServoUrl { + self.url_list.last().unwrap().clone() + } + + /// <https://fetch.spec.whatwg.org/#concept-request-current-url> + pub fn current_url_mut(&mut self) -> &mut ServoUrl { + self.url_list.last_mut().unwrap() + } + + /// <https://fetch.spec.whatwg.org/#navigation-request> + pub fn is_navigation_request(&self) -> bool { + self.destination == Destination::Document + } + + /// <https://fetch.spec.whatwg.org/#subresource-request> + pub fn is_subresource_request(&self) -> bool { + match self.destination { + Destination::Audio | + Destination::Font | + Destination::Image | + Destination::Manifest | + Destination::Script | + Destination::Style | + Destination::Track | + Destination::Video | + Destination::Xslt | + Destination::None => true, + _ => false, + } + } + + pub fn timing_type(&self) -> ResourceTimingType { + if self.is_navigation_request() { + ResourceTimingType::Navigation + } else { + ResourceTimingType::Resource + } + } +} + +impl Referrer { + pub fn to_url(&self) -> Option<&ServoUrl> { + match *self { + Referrer::NoReferrer => None, + Referrer::Client(ref url) => Some(url), + Referrer::ReferrerUrl(ref url) => Some(url), + } + } +} + +// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte +// TODO: values in the control-code range are being quietly stripped out by +// HeaderMap and never reach this function to be loudly rejected! +fn is_cors_unsafe_request_header_byte(value: &u8) -> bool { + match value { + 0x00..=0x08 | + 0x10..=0x19 | + 0x22 | + 0x28 | + 0x29 | + 0x3A | + 0x3C | + 0x3E | + 0x3F | + 0x40 | + 0x5B | + 0x5C | + 0x5D | + 0x7B | + 0x7D | + 0x7F => true, + _ => false, + } +} + +// https://fetch.spec.whatwg.org/#cors-safelisted-request-header +// subclause `accept` +fn is_cors_safelisted_request_accept(value: &[u8]) -> bool { + !(value.iter().any(is_cors_unsafe_request_header_byte)) +} + +// https://fetch.spec.whatwg.org/#cors-safelisted-request-header +// subclauses `accept-language`, `content-language` +fn is_cors_safelisted_language(value: &[u8]) -> bool { + value.iter().all(|&x| match x { + 0x30..=0x39 | + 0x41..=0x5A | + 0x61..=0x7A | + 0x20 | + 0x2A | + 0x2C | + 0x2D | + 0x2E | + 0x3B | + 0x3D => true, + _ => false, + }) +} + +// https://fetch.spec.whatwg.org/#cors-safelisted-request-header +// subclause `content-type` +fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool { + // step 1 + if value.iter().any(is_cors_unsafe_request_header_byte) { + return false; + } + // step 2 + let value_string = if let Ok(s) = std::str::from_utf8(value) { + s + } else { + return false; + }; + let value_mime_result: Result<Mime, _> = value_string.parse(); + match value_mime_result { + Err(_) => false, // step 3 + Ok(value_mime) => match (value_mime.type_(), value_mime.subtype()) { + (mime::APPLICATION, mime::WWW_FORM_URLENCODED) | + (mime::MULTIPART, mime::FORM_DATA) | + (mime::TEXT, mime::PLAIN) => true, + _ => false, // step 4 + }, + } +} + +// TODO: "DPR", "Downlink", "Save-Data", "Viewport-Width", "Width": +// ... once parsed, the value should not be failure. +// https://fetch.spec.whatwg.org/#cors-safelisted-request-header +pub fn is_cors_safelisted_request_header<N: AsRef<str>, V: AsRef<[u8]>>( + name: &N, + value: &V, +) -> bool { + let name: &str = name.as_ref(); + let value: &[u8] = value.as_ref(); + if value.len() > 128 { + return false; + } + match name { + "accept" => is_cors_safelisted_request_accept(value), + "accept-language" | "content-language" => is_cors_safelisted_language(value), + "content-type" => is_cors_safelisted_request_content_type(value), + _ => false, + } +} + +/// <https://fetch.spec.whatwg.org/#cors-safelisted-method> +pub fn is_cors_safelisted_method(m: &Method) -> bool { + match *m { + Method::GET | Method::HEAD | Method::POST => true, + _ => false, + } +} + +/// <https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name> +pub fn is_cors_non_wildcard_request_header_name(name: &HeaderName) -> bool { + name == AUTHORIZATION +} + +/// <https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names> +pub fn get_cors_unsafe_header_names(headers: &HeaderMap) -> Vec<HeaderName> { + // Step 1 + let mut unsafe_names: Vec<&HeaderName> = vec![]; + // Step 2 + let mut potentillay_unsafe_names: Vec<&HeaderName> = vec![]; + // Step 3 + let mut safelist_value_size = 0; + + // Step 4 + for (name, value) in headers.iter() { + if !is_cors_safelisted_request_header(&name, &value) { + unsafe_names.push(name); + } else { + potentillay_unsafe_names.push(name); + safelist_value_size += value.as_ref().len(); + } + } + + // Step 5 + if safelist_value_size > 1024 { + unsafe_names.extend_from_slice(&potentillay_unsafe_names); + } + + // Step 6 + return convert_header_names_to_sorted_lowercase_set(unsafe_names); +} + +/// <https://fetch.spec.whatwg.org/#ref-for-convert-header-names-to-a-sorted-lowercase-set> +pub fn convert_header_names_to_sorted_lowercase_set( + header_names: Vec<&HeaderName>, +) -> Vec<HeaderName> { + // HeaderName does not implement the needed traits to use a BTreeSet + // So create a new Vec, sort, then dedup + let mut ordered_set = header_names.to_vec(); + ordered_set.sort_by(|a, b| a.as_str().partial_cmp(b.as_str()).unwrap()); + ordered_set.dedup(); + return ordered_set.into_iter().cloned().collect(); +} diff --git a/components/shared/net/response.rs b/components/shared/net/response.rs new file mode 100644 index 00000000000..552c0057c50 --- /dev/null +++ b/components/shared/net/response.rs @@ -0,0 +1,364 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! The [Response](https://fetch.spec.whatwg.org/#responses) object +//! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch) +use std::sync::atomic::AtomicBool; +use std::sync::Mutex; + +use headers::{ContentType, HeaderMapExt}; +use http::{HeaderMap, StatusCode}; +use hyper_serde::Serde; +use malloc_size_of_derive::MallocSizeOf; +use serde::{Deserialize, Serialize}; +use servo_arc::Arc; +use servo_url::ServoUrl; + +use crate::{ + FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming, + ResourceTimingType, +}; + +/// [Response type](https://fetch.spec.whatwg.org/#concept-response-type) +#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum ResponseType { + Basic, + Cors, + Default, + Error(NetworkError), + Opaque, + OpaqueRedirect, +} + +/// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason) +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum TerminationReason { + EndUserAbort, + Fatal, + Timeout, +} + +/// The response body can still be pushed to after fetch +/// This provides a way to store unfinished response bodies +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] +pub enum ResponseBody { + Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough? + Receiving(Vec<u8>), + Done(Vec<u8>), +} + +impl ResponseBody { + pub fn is_done(&self) -> bool { + match *self { + ResponseBody::Done(..) => true, + ResponseBody::Empty | ResponseBody::Receiving(..) => false, + } + } +} + +/// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state) +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum CacheState { + None, + Local, + Validated, + Partial, +} + +/// [Https state](https://fetch.spec.whatwg.org/#concept-response-https-state) +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum HttpsState { + None, + Deprecated, + Modern, +} + +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct ResponseInit { + pub url: ServoUrl, + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + #[ignore_malloc_size_of = "Defined in hyper"] + pub headers: HeaderMap, + pub status_code: u16, + pub referrer: Option<ServoUrl>, + pub location_url: Option<Result<ServoUrl, String>>, +} + +/// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec +#[derive(Clone, Debug, MallocSizeOf)] +pub struct Response { + pub response_type: ResponseType, + pub termination_reason: Option<TerminationReason>, + url: Option<ServoUrl>, + pub url_list: Vec<ServoUrl>, + /// `None` can be considered a StatusCode of `0`. + #[ignore_malloc_size_of = "Defined in hyper"] + pub status: Option<(StatusCode, String)>, + pub raw_status: Option<(u16, Vec<u8>)>, + #[ignore_malloc_size_of = "Defined in hyper"] + pub headers: HeaderMap, + #[ignore_malloc_size_of = "Mutex heap size undefined"] + pub body: Arc<Mutex<ResponseBody>>, + pub cache_state: CacheState, + pub https_state: HttpsState, + pub referrer: Option<ServoUrl>, + pub referrer_policy: Option<ReferrerPolicy>, + /// [CORS-exposed header-name list](https://fetch.spec.whatwg.org/#concept-response-cors-exposed-header-name-list) + pub cors_exposed_header_name_list: Vec<String>, + /// [Location URL](https://fetch.spec.whatwg.org/#concept-response-location-url) + pub location_url: Option<Result<ServoUrl, String>>, + /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response + /// is a filtered response + pub internal_response: Option<Box<Response>>, + /// whether or not to try to return the internal_response when asked for actual_response + pub return_internal: bool, + /// https://fetch.spec.whatwg.org/#concept-response-aborted + #[ignore_malloc_size_of = "AtomicBool heap size undefined"] + pub aborted: Arc<AtomicBool>, + /// track network metrics + #[ignore_malloc_size_of = "Mutex heap size undefined"] + pub resource_timing: Arc<Mutex<ResourceFetchTiming>>, +} + +impl Response { + pub fn new(url: ServoUrl, resource_timing: ResourceFetchTiming) -> Response { + Response { + response_type: ResponseType::Default, + termination_reason: None, + url: Some(url), + url_list: vec![], + status: Some((StatusCode::OK, "".to_string())), + raw_status: Some((200, b"".to_vec())), + headers: HeaderMap::new(), + body: Arc::new(Mutex::new(ResponseBody::Empty)), + cache_state: CacheState::None, + https_state: HttpsState::None, + referrer: None, + referrer_policy: None, + cors_exposed_header_name_list: vec![], + location_url: None, + internal_response: None, + return_internal: true, + aborted: Arc::new(AtomicBool::new(false)), + resource_timing: Arc::new(Mutex::new(resource_timing)), + } + } + + pub fn from_init(init: ResponseInit, resource_timing_type: ResourceTimingType) -> Response { + let mut res = Response::new(init.url, ResourceFetchTiming::new(resource_timing_type)); + res.location_url = init.location_url; + res.headers = init.headers; + res.referrer = init.referrer; + res.status = StatusCode::from_u16(init.status_code) + .map(|s| (s, s.to_string())) + .ok(); + res + } + + pub fn network_error(e: NetworkError) -> Response { + Response { + response_type: ResponseType::Error(e), + termination_reason: None, + url: None, + url_list: vec![], + status: None, + raw_status: None, + headers: HeaderMap::new(), + body: Arc::new(Mutex::new(ResponseBody::Empty)), + cache_state: CacheState::None, + https_state: HttpsState::None, + referrer: None, + referrer_policy: None, + cors_exposed_header_name_list: vec![], + location_url: None, + internal_response: None, + return_internal: true, + aborted: Arc::new(AtomicBool::new(false)), + resource_timing: Arc::new(Mutex::new(ResourceFetchTiming::new( + ResourceTimingType::Error, + ))), + } + } + + pub fn url(&self) -> Option<&ServoUrl> { + self.url.as_ref() + } + + pub fn is_network_error(&self) -> bool { + match self.response_type { + ResponseType::Error(..) => true, + _ => false, + } + } + + pub fn get_network_error(&self) -> Option<&NetworkError> { + match self.response_type { + ResponseType::Error(ref e) => Some(e), + _ => None, + } + } + + pub fn actual_response(&self) -> &Response { + if self.return_internal && self.internal_response.is_some() { + &**self.internal_response.as_ref().unwrap() + } else { + self + } + } + + pub fn actual_response_mut(&mut self) -> &mut Response { + if self.return_internal && self.internal_response.is_some() { + &mut **self.internal_response.as_mut().unwrap() + } else { + self + } + } + + pub fn to_actual(self) -> Response { + if self.return_internal && self.internal_response.is_some() { + *self.internal_response.unwrap() + } else { + self + } + } + + pub fn get_resource_timing(&self) -> Arc<Mutex<ResourceFetchTiming>> { + Arc::clone(&self.resource_timing) + } + + /// Convert to a filtered response, of type `filter_type`. + /// Do not use with type Error or Default + #[rustfmt::skip] + pub fn to_filtered(self, filter_type: ResponseType) -> Response { + match filter_type { + ResponseType::Default | + ResponseType::Error(..) => panic!(), + _ => (), + } + + let old_response = self.to_actual(); + + if let ResponseType::Error(e) = old_response.response_type { + return Response::network_error(e); + } + + let old_headers = old_response.headers.clone(); + let exposed_headers = old_response.cors_exposed_header_name_list.clone(); + let mut response = old_response.clone(); + response.internal_response = Some(Box::new(old_response)); + response.response_type = filter_type; + + match response.response_type { + ResponseType::Default | + ResponseType::Error(..) => unreachable!(), + + ResponseType::Basic => { + let headers = old_headers.iter().filter(|(name, _)| { + match &*name.as_str().to_ascii_lowercase() { + "set-cookie" | "set-cookie2" => false, + _ => true + } + }).map(|(n, v)| (n.clone(), v.clone())).collect(); + response.headers = headers; + }, + + ResponseType::Cors => { + let headers = old_headers.iter().filter(|(name, _)| { + match &*name.as_str().to_ascii_lowercase() { + "cache-control" | "content-language" | "content-type" | + "expires" | "last-modified" | "pragma" => true, + "set-cookie" | "set-cookie2" => false, + header => { + exposed_headers.iter().any(|h| *header == h.as_str().to_ascii_lowercase()) + } + } + }).map(|(n, v)| (n.clone(), v.clone())).collect(); + response.headers = headers; + }, + + ResponseType::Opaque => { + response.url_list = vec![]; + response.url = None; + response.headers = HeaderMap::new(); + response.status = None; + response.body = Arc::new(Mutex::new(ResponseBody::Empty)); + response.cache_state = CacheState::None; + }, + + ResponseType::OpaqueRedirect => { + response.headers = HeaderMap::new(); + response.status = None; + response.body = Arc::new(Mutex::new(ResponseBody::Empty)); + response.cache_state = CacheState::None; + }, + } + + response + } + + pub fn metadata(&self) -> Result<FetchMetadata, NetworkError> { + fn init_metadata(response: &Response, url: &ServoUrl) -> Metadata { + let mut metadata = Metadata::default(url.clone()); + metadata.set_content_type( + response + .headers + .typed_get::<ContentType>() + .map(|v| v.into()) + .as_ref(), + ); + metadata.location_url = response.location_url.clone(); + metadata.headers = Some(Serde(response.headers.clone())); + metadata.status = response.raw_status.clone(); + metadata.https_state = response.https_state; + metadata.referrer = response.referrer.clone(); + metadata.referrer_policy = response.referrer_policy.clone(); + metadata.redirected = response.actual_response().url_list.len() > 1; + metadata + } + + if let Some(error) = self.get_network_error() { + return Err(error.clone()); + } + + let metadata = self.url.as_ref().map(|url| init_metadata(self, url)); + + if let Some(ref response) = self.internal_response { + match response.url { + Some(ref url) => { + let unsafe_metadata = init_metadata(response, url); + + match self.response_type { + ResponseType::Basic => Ok(FetchMetadata::Filtered { + filtered: FilteredMetadata::Basic(metadata.unwrap()), + unsafe_: unsafe_metadata, + }), + ResponseType::Cors => Ok(FetchMetadata::Filtered { + filtered: FilteredMetadata::Cors(metadata.unwrap()), + unsafe_: unsafe_metadata, + }), + ResponseType::Default => unreachable!(), + ResponseType::Error(ref network_err) => Err(network_err.clone()), + ResponseType::Opaque => Ok(FetchMetadata::Filtered { + filtered: FilteredMetadata::Opaque, + unsafe_: unsafe_metadata, + }), + ResponseType::OpaqueRedirect => Ok(FetchMetadata::Filtered { + filtered: FilteredMetadata::OpaqueRedirect(url.clone()), + unsafe_: unsafe_metadata, + }), + } + }, + None => Err(NetworkError::Internal( + "No url found in unsafe response".to_owned(), + )), + } + } else { + assert_eq!(self.response_type, ResponseType::Default); + Ok(FetchMetadata::Unfiltered(metadata.unwrap())) + } + } +} diff --git a/components/shared/net/storage_thread.rs b/components/shared/net/storage_thread.rs new file mode 100644 index 00000000000..0253603016e --- /dev/null +++ b/components/shared/net/storage_thread.rs @@ -0,0 +1,48 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use ipc_channel::ipc::IpcSender; +use malloc_size_of_derive::MallocSizeOf; +use serde::{Deserialize, Serialize}; +use servo_url::ServoUrl; + +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum StorageType { + Session, + Local, +} + +/// Request operations on the storage data associated with a particular url +#[derive(Debug, Deserialize, Serialize)] +pub enum StorageThreadMsg { + /// gets the number of key/value pairs present in the associated storage data + Length(IpcSender<usize>, ServoUrl, StorageType), + + /// gets the name of the key at the specified index in the associated storage data + Key(IpcSender<Option<String>>, ServoUrl, StorageType, u32), + + /// Gets the available keys in the associated storage data + Keys(IpcSender<Vec<String>>, ServoUrl, StorageType), + + /// gets the value associated with the given key in the associated storage data + GetItem(IpcSender<Option<String>>, ServoUrl, StorageType, String), + + /// sets the value of the given key in the associated storage data + SetItem( + IpcSender<Result<(bool, Option<String>), ()>>, + ServoUrl, + StorageType, + String, + String, + ), + + /// removes the key/value pair for the given key in the associated storage data + RemoveItem(IpcSender<Option<String>>, ServoUrl, StorageType, String), + + /// clears the associated storage data by removing all the key/value pairs + Clear(IpcSender<bool>, ServoUrl, StorageType), + + /// send a reply when done cleaning up thread resources and then shut it down + Exit(IpcSender<()>), +} diff --git a/components/shared/net/tests/image.rs b/components/shared/net/tests/image.rs new file mode 100644 index 00000000000..a4963702b57 --- /dev/null +++ b/components/shared/net/tests/image.rs @@ -0,0 +1,28 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use net_traits::image::base::detect_image_format; + +#[test] +fn test_supported_images() { + let gif1 = [b'G', b'I', b'F', b'8', b'7', b'a']; + let gif2 = [b'G', b'I', b'F', b'8', b'9', b'a']; + let jpeg = [0xff, 0xd8, 0xff]; + let png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; + let webp = [ + b'R', b'I', b'F', b'F', 0x01, 0x02, 0x03, 0x04, b'W', b'E', b'B', b'P', b'V', b'P', + ]; + let bmp = [0x42, 0x4D]; + let ico = [0x00, 0x00, 0x01, 0x00]; + let junk_format = [0x01, 0x02, 0x03, 0x04, 0x05]; + + assert!(detect_image_format(&gif1).is_ok()); + assert!(detect_image_format(&gif2).is_ok()); + assert!(detect_image_format(&jpeg).is_ok()); + assert!(detect_image_format(&png).is_ok()); + assert!(detect_image_format(&webp).is_ok()); + assert!(detect_image_format(&bmp).is_ok()); + assert!(detect_image_format(&ico).is_ok()); + assert!(detect_image_format(&junk_format).is_err()); +} diff --git a/components/shared/net/tests/lib.rs b/components/shared/net/tests/lib.rs new file mode 100644 index 00000000000..290ca902935 --- /dev/null +++ b/components/shared/net/tests/lib.rs @@ -0,0 +1,212 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use net_traits::{ResourceAttribute, ResourceFetchTiming, ResourceTimeValue, ResourceTimingType}; + +#[test] +fn test_set_start_time_to_fetch_start_if_nonzero_tao() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + resource_timing.fetch_start = 1; + assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + assert!( + resource_timing.fetch_start > 0, + "`fetch_start` should have a positive value" + ); + + // verify that setting `start_time` to `fetch_start` succeeds + resource_timing.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::FetchStart)); + assert_eq!( + resource_timing.start_time, resource_timing.fetch_start, + "`start_time` should equal `fetch_start`" + ); +} + +#[test] +fn test_set_start_time_to_fetch_start_if_zero_tao() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + resource_timing.start_time = 1; + assert!( + resource_timing.start_time > 0, + "`start_time` should have a positive value" + ); + assert_eq!( + resource_timing.fetch_start, 0, + "`fetch_start` should be zero" + ); + + // verify that setting `start_time` to `fetch_start` succeeds even when `fetch_start` == zero + resource_timing.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::FetchStart)); + assert_eq!( + resource_timing.start_time, resource_timing.fetch_start, + "`start_time` should equal `fetch_start`" + ); +} + +#[test] +fn test_set_start_time_to_fetch_start_if_nonzero_no_tao() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + resource_timing.mark_timing_check_failed(); + resource_timing.fetch_start = 1; + assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + assert!( + resource_timing.fetch_start > 0, + "`fetch_start` should have a positive value" + ); + + // verify that setting `start_time` to `fetch_start` succeeds even when TAO check failed + resource_timing.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::FetchStart)); + assert_eq!( + resource_timing.start_time, resource_timing.fetch_start, + "`start_time` should equal `fetch_start`" + ); +} + +#[test] +fn test_set_start_time_to_fetch_start_if_zero_no_tao() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + resource_timing.mark_timing_check_failed(); + resource_timing.start_time = 1; + assert!( + resource_timing.start_time > 0, + "`start_time` should have a positive value" + ); + assert_eq!( + resource_timing.fetch_start, 0, + "`fetch_start` should be zero" + ); + + // verify that setting `start_time` to `fetch_start` succeeds even when `fetch_start`==0 and no TAO + resource_timing.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::FetchStart)); + assert_eq!( + resource_timing.start_time, resource_timing.fetch_start, + "`start_time` should equal `fetch_start`" + ); +} + +#[test] +fn test_set_start_time_to_redirect_start_if_nonzero_tao() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + resource_timing.redirect_start = 1; + assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + assert!( + resource_timing.redirect_start > 0, + "`redirect_start` should have a positive value" + ); + + // verify that setting `start_time` to `redirect_start` succeeds for nonzero `redirect_start`, TAO pass + resource_timing.set_attribute(ResourceAttribute::StartTime( + ResourceTimeValue::RedirectStart, + )); + assert_eq!( + resource_timing.start_time, resource_timing.redirect_start, + "`start_time` should equal `redirect_start`" + ); +} + +#[test] +fn test_not_set_start_time_to_redirect_start_if_zero_tao() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + resource_timing.start_time = 1; + assert!( + resource_timing.start_time > 0, + "`start_time` should have a positive value" + ); + assert_eq!( + resource_timing.redirect_start, 0, + "`redirect_start` should be zero" + ); + + // verify that setting `start_time` to `redirect_start` fails if `redirect_start` == 0 + resource_timing.set_attribute(ResourceAttribute::StartTime( + ResourceTimeValue::RedirectStart, + )); + assert_ne!( + resource_timing.start_time, resource_timing.redirect_start, + "`start_time` should *not* equal `redirect_start`" + ); +} + +#[test] +fn test_not_set_start_time_to_redirect_start_if_nonzero_no_tao() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + resource_timing.mark_timing_check_failed(); + // Note: properly-behaved redirect_start should never be nonzero once TAO check has failed + resource_timing.redirect_start = 1; + assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + assert!( + resource_timing.redirect_start > 0, + "`redirect_start` should have a positive value" + ); + + // verify that setting `start_time` to `redirect_start` fails if TAO check fails + resource_timing.set_attribute(ResourceAttribute::StartTime( + ResourceTimeValue::RedirectStart, + )); + assert_ne!( + resource_timing.start_time, resource_timing.redirect_start, + "`start_time` should *not* equal `redirect_start`" + ); +} + +#[test] +fn test_not_set_start_time_to_redirect_start_if_zero_no_tao() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + resource_timing.mark_timing_check_failed(); + resource_timing.start_time = 1; + assert!( + resource_timing.start_time > 0, + "`start_time` should have a positive value" + ); + assert_eq!( + resource_timing.redirect_start, 0, + "`redirect_start` should be zero" + ); + + // verify that setting `start_time` to `redirect_start` fails if `redirect_start`==0 and no TAO + resource_timing.set_attribute(ResourceAttribute::StartTime( + ResourceTimeValue::RedirectStart, + )); + assert_ne!( + resource_timing.start_time, resource_timing.redirect_start, + "`start_time` should *not* equal `redirect_start`" + ); +} + +#[test] +fn test_set_start_time() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + + // verify setting `start_time` to current time succeeds + resource_timing.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::Now)); + assert!(resource_timing.start_time > 0, "failed to set `start_time`"); +} +#[test] +fn test_reset_start_time() { + let mut resource_timing: ResourceFetchTiming = + ResourceFetchTiming::new(ResourceTimingType::Resource); + assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + + resource_timing.start_time = 1; + assert!( + resource_timing.start_time > 0, + "`start_time` should have a positive value" + ); + + // verify resetting `start_time` (to zero) succeeds + resource_timing.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::Zero)); + assert_eq!( + resource_timing.start_time, 0, + "failed to reset `start_time`" + ); +} diff --git a/components/shared/net/tests/pub_domains.rs b/components/shared/net/tests/pub_domains.rs new file mode 100644 index 00000000000..ea58e7650e3 --- /dev/null +++ b/components/shared/net/tests/pub_domains.rs @@ -0,0 +1,122 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use net_traits::pub_domains::{is_pub_domain, is_reg_domain, pub_suffix, reg_suffix}; + +// These tests may need to be updated if the PSL changes. + +#[test] +fn test_is_pub_domain_plain() { + assert!(is_pub_domain("com")); + assert!(is_pub_domain(".org")); + assert!(is_pub_domain("za.org")); + assert!(is_pub_domain("xn--od0alg.hk")); + assert!(is_pub_domain("xn--krdsherad-m8a.no")); +} + +#[test] +fn test_is_pub_domain_wildcard() { + assert!(is_pub_domain("hello.bd")); + assert!(is_pub_domain("world.jm")); + assert!(is_pub_domain("toto.kobe.jp")); +} + +#[test] +fn test_is_pub_domain_exception() { + assert_eq!(is_pub_domain("www.ck"), false); + assert_eq!(is_pub_domain("city.kawasaki.jp"), false); + assert_eq!(is_pub_domain("city.nagoya.jp"), false); + assert_eq!(is_pub_domain("teledata.mz"), false); +} + +#[test] +fn test_is_pub_domain_not() { + assert_eq!(is_pub_domain(""), false); + assert_eq!(is_pub_domain("."), false); + assert_eq!(is_pub_domain("..."), false); + assert_eq!(is_pub_domain(".servo.org"), false); + assert_eq!(is_pub_domain("www.mozilla.org"), false); + assert_eq!(is_pub_domain("publicsuffix.org"), false); + assert_eq!(is_pub_domain("hello.world.jm"), false); + assert_eq!(is_pub_domain("toto.toto.kobe.jp"), false); +} + +#[test] +fn test_is_pub_domain() { + assert!(!is_pub_domain("city.yokohama.jp")); + assert!(!is_pub_domain("foo.bar.baz.yokohama.jp")); + assert!(!is_pub_domain("foo.bar.city.yokohama.jp")); + assert!(!is_pub_domain("foo.bar.com")); + assert!(!is_pub_domain("foo.bar.tokyo.jp")); + assert!(!is_pub_domain("foo.bar.yokohama.jp")); + assert!(!is_pub_domain("foo.city.yokohama.jp")); + assert!(!is_pub_domain("foo.com")); + assert!(!is_pub_domain("foo.tokyo.jp")); + assert!(!is_pub_domain("yokohama.jp")); + assert!(is_pub_domain("com")); + assert!(is_pub_domain("foo.yokohama.jp")); + assert!(is_pub_domain("jp")); + assert!(is_pub_domain("tokyo.jp")); +} + +#[test] +fn test_is_reg_domain() { + assert!(!is_reg_domain("com")); + assert!(!is_reg_domain("foo.bar.baz.yokohama.jp")); + assert!(!is_reg_domain("foo.bar.com")); + assert!(!is_reg_domain("foo.bar.tokyo.jp")); + assert!(!is_reg_domain("foo.city.yokohama.jp")); + assert!(!is_reg_domain("foo.yokohama.jp")); + assert!(!is_reg_domain("jp")); + assert!(!is_reg_domain("tokyo.jp")); + assert!(is_reg_domain("city.yokohama.jp")); + assert!(is_reg_domain("foo.bar.yokohama.jp")); + assert!(is_reg_domain("foo.com")); + assert!(is_reg_domain("foo.tokyo.jp")); + assert!(is_reg_domain("yokohama.jp")); +} + +#[test] +fn test_pub_suffix() { + assert_eq!(pub_suffix("city.yokohama.jp"), "yokohama.jp"); + assert_eq!(pub_suffix("com"), "com"); + assert_eq!(pub_suffix("foo.bar.baz.yokohama.jp"), "baz.yokohama.jp"); + assert_eq!(pub_suffix("foo.bar.com"), "com"); + assert_eq!(pub_suffix("foo.bar.tokyo.jp"), "tokyo.jp"); + assert_eq!(pub_suffix("foo.bar.yokohama.jp"), "bar.yokohama.jp"); + assert_eq!(pub_suffix("foo.city.yokohama.jp"), "yokohama.jp"); + assert_eq!(pub_suffix("foo.com"), "com"); + assert_eq!(pub_suffix("foo.tokyo.jp"), "tokyo.jp"); + assert_eq!(pub_suffix("foo.yokohama.jp"), "foo.yokohama.jp"); + assert_eq!(pub_suffix("jp"), "jp"); + assert_eq!(pub_suffix("tokyo.jp"), "tokyo.jp"); + assert_eq!(pub_suffix("yokohama.jp"), "jp"); +} + +#[test] +fn test_reg_suffix() { + assert_eq!(reg_suffix("city.yokohama.jp"), "city.yokohama.jp"); + assert_eq!(reg_suffix("com"), "com"); + assert_eq!(reg_suffix("foo.bar.baz.yokohama.jp"), "bar.baz.yokohama.jp"); + assert_eq!(reg_suffix("foo.bar.com"), "bar.com"); + assert_eq!(reg_suffix("foo.bar.tokyo.jp"), "bar.tokyo.jp"); + assert_eq!(reg_suffix("foo.bar.yokohama.jp"), "foo.bar.yokohama.jp"); + assert_eq!(reg_suffix("foo.city.yokohama.jp"), "city.yokohama.jp"); + assert_eq!(reg_suffix("foo.com"), "foo.com"); + assert_eq!(reg_suffix("foo.tokyo.jp"), "foo.tokyo.jp"); + assert_eq!(reg_suffix("foo.yokohama.jp"), "foo.yokohama.jp"); + assert_eq!(reg_suffix("jp"), "jp"); + assert_eq!(reg_suffix("tokyo.jp"), "tokyo.jp"); + assert_eq!(reg_suffix("yokohama.jp"), "yokohama.jp"); +} + +#[test] +fn test_weirdness() { + // These are weird results, but AFAICT they are spec-compliant. + assert_ne!( + pub_suffix("city.yokohama.jp"), + pub_suffix(pub_suffix("city.yokohama.jp")) + ); + assert!(!is_pub_domain(pub_suffix("city.yokohama.jp"))); +} diff --git a/components/shared/net/tests/whitespace.rs b/components/shared/net/tests/whitespace.rs new file mode 100644 index 00000000000..d1e6b7a2ac8 --- /dev/null +++ b/components/shared/net/tests/whitespace.rs @@ -0,0 +1,25 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#[test] +fn test_trim_http_whitespace() { + fn test_trim(in_: &[u8], out: &[u8]) { + let b = net_traits::trim_http_whitespace(in_); + assert_eq!(b, out); + } + + test_trim(b"", b""); + + test_trim(b" ", b""); + test_trim(b"a", b"a"); + test_trim(b" a", b"a"); + test_trim(b"a ", b"a"); + test_trim(b" a ", b"a"); + + test_trim(b"\t", b""); + test_trim(b"a", b"a"); + test_trim(b"\ta", b"a"); + test_trim(b"a\t", b"a"); + test_trim(b"\ta\t", b"a"); +} diff --git a/components/shared/profile/Cargo.toml b/components/shared/profile/Cargo.toml new file mode 100644 index 00000000000..7e3650bed0e --- /dev/null +++ b/components/shared/profile/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "profile_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "profile_traits" +path = "lib.rs" + +[dependencies] +crossbeam-channel = { workspace = true } +ipc-channel = { workspace = true } +log = { workspace = true } +serde = { workspace = true } +servo_config = { path = "../../config" } +signpost = { git = "https://github.com/pcwalton/signpost.git" } +time = { workspace = true } diff --git a/components/shared/profile/ipc.rs b/components/shared/profile/ipc.rs new file mode 100644 index 00000000000..7a005939a98 --- /dev/null +++ b/components/shared/profile/ipc.rs @@ -0,0 +1,82 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::io::Error; + +use ipc_channel::ipc; +use serde::{Deserialize, Serialize}; + +use crate::time; +use crate::time::{ProfilerCategory, ProfilerChan}; + +pub struct IpcReceiver<T> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + ipc_receiver: ipc::IpcReceiver<T>, + time_profile_chan: ProfilerChan, +} + +impl<T> IpcReceiver<T> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + pub fn recv(&self) -> Result<T, ipc::IpcError> { + time::profile( + ProfilerCategory::IpcReceiver, + None, + self.time_profile_chan.clone(), + move || self.ipc_receiver.recv(), + ) + } + + pub fn try_recv(&self) -> Result<T, ipc::TryRecvError> { + self.ipc_receiver.try_recv() + } + + pub fn to_opaque(self) -> ipc::OpaqueIpcReceiver { + self.ipc_receiver.to_opaque() + } +} + +pub fn channel<T>( + time_profile_chan: ProfilerChan, +) -> Result<(ipc::IpcSender<T>, IpcReceiver<T>), Error> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + let (ipc_sender, ipc_receiver) = ipc::channel()?; + let profiled_ipc_receiver = IpcReceiver { + ipc_receiver, + time_profile_chan, + }; + Ok((ipc_sender, profiled_ipc_receiver)) +} + +pub struct IpcBytesReceiver { + ipc_bytes_receiver: ipc::IpcBytesReceiver, + time_profile_chan: ProfilerChan, +} + +impl IpcBytesReceiver { + pub fn recv(&self) -> Result<Vec<u8>, ipc::IpcError> { + time::profile( + ProfilerCategory::IpcBytesReceiver, + None, + self.time_profile_chan.clone(), + move || self.ipc_bytes_receiver.recv(), + ) + } +} + +pub fn bytes_channel( + time_profile_chan: ProfilerChan, +) -> Result<(ipc::IpcBytesSender, IpcBytesReceiver), Error> { + let (ipc_bytes_sender, ipc_bytes_receiver) = ipc::bytes_channel()?; + let profiled_ipc_bytes_receiver = IpcBytesReceiver { + ipc_bytes_receiver, + time_profile_chan, + }; + Ok((ipc_bytes_sender, profiled_ipc_bytes_receiver)) +} diff --git a/components/shared/profile/lib.rs b/components/shared/profile/lib.rs new file mode 100644 index 00000000000..7be3d265d6c --- /dev/null +++ b/components/shared/profile/lib.rs @@ -0,0 +1,13 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! This module contains APIs for the `profile` crate used generically in the +//! rest of Servo. These APIs are here instead of in `profile` so that these +//! modules won't have to depend on `profile`. + +#![deny(unsafe_code)] + +pub mod ipc; +pub mod mem; +pub mod time; diff --git a/components/shared/profile/mem.rs b/components/shared/profile/mem.rs new file mode 100644 index 00000000000..87b6f26597c --- /dev/null +++ b/components/shared/profile/mem.rs @@ -0,0 +1,211 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! APIs for memory profiling. + +#![deny(missing_docs)] + +use std::marker::Send; + +use crossbeam_channel::Sender; +use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::router::ROUTER; +use log::warn; +use serde::{Deserialize, Serialize}; + +/// A trait to abstract away the various kinds of message senders we use. +pub trait OpaqueSender<T> { + /// Send a message. + fn send(&self, message: T); +} + +impl<T> OpaqueSender<T> for Sender<T> { + fn send(&self, message: T) { + if let Err(e) = Sender::send(self, message) { + warn!( + "Error communicating with the target thread from the profiler: {:?}", + e + ); + } + } +} + +impl<T> OpaqueSender<T> for IpcSender<T> +where + T: serde::Serialize, +{ + fn send(&self, message: T) { + if let Err(e) = IpcSender::send(self, message) { + warn!( + "Error communicating with the target thread from the profiler: {}", + e + ); + } + } +} + +/// Front-end representation of the profiler used to communicate with the +/// profiler. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ProfilerChan(pub IpcSender<ProfilerMsg>); + +impl ProfilerChan { + /// Send `msg` on this `IpcSender`. + /// + /// Warns if the send fails. + pub fn send(&self, msg: ProfilerMsg) { + if let Err(e) = self.0.send(msg) { + warn!("Error communicating with the memory profiler thread: {}", e); + } + } + + /// Runs `f()` with memory profiling. + pub fn run_with_memory_reporting<F, M, T, C>( + &self, + f: F, + reporter_name: String, + channel_for_reporter: C, + msg: M, + ) where + F: FnOnce(), + M: Fn(ReportsChan) -> T + Send + 'static, + T: Send + 'static, + C: OpaqueSender<T> + Send + 'static, + { + // Register the memory reporter. + let (reporter_sender, reporter_receiver) = ipc::channel().unwrap(); + ROUTER.add_route( + reporter_receiver.to_opaque(), + Box::new(move |message| { + // Just injects an appropriate event into the paint thread's queue. + let request: ReporterRequest = message.to().unwrap(); + channel_for_reporter.send(msg(request.reports_channel)); + }), + ); + self.send(ProfilerMsg::RegisterReporter( + reporter_name.clone(), + Reporter(reporter_sender), + )); + + f(); + + self.send(ProfilerMsg::UnregisterReporter(reporter_name)); + } +} + +/// The various kinds of memory measurement. +/// +/// Here "explicit" means explicit memory allocations done by the application. It includes +/// allocations made at the OS level (via functions such as VirtualAlloc, vm_allocate, and mmap), +/// allocations made at the heap allocation level (via functions such as malloc, calloc, realloc, +/// memalign, operator new, and operator new[]) and where possible, the overhead of the heap +/// allocator itself. It excludes memory that is mapped implicitly such as code and data segments, +/// and thread stacks. "explicit" is not guaranteed to cover every explicit allocation, but it does +/// cover most (including the entire heap), and therefore it is the single best number to focus on +/// when trying to reduce memory usage. +#[derive(Debug, Deserialize, Serialize)] +pub enum ReportKind { + /// A size measurement for an explicit allocation on the jemalloc heap. This should be used + /// for any measurements done via the `MallocSizeOf` trait. + ExplicitJemallocHeapSize, + + /// A size measurement for an explicit allocation on the system heap. Only likely to be used + /// for external C or C++ libraries that don't use jemalloc. + ExplicitSystemHeapSize, + + /// A size measurement for an explicit allocation not on the heap, e.g. via mmap(). + ExplicitNonHeapSize, + + /// A size measurement for an explicit allocation whose location is unknown or uncertain. + ExplicitUnknownLocationSize, + + /// A size measurement for a non-explicit allocation. This kind is used for global + /// measurements such as "resident" and "vsize", and also for measurements that cross-cut the + /// measurements grouped under "explicit", e.g. by grouping those measurements in a way that's + /// different to how they are grouped under "explicit". + NonExplicitSize, +} + +/// A single memory-related measurement. +#[derive(Debug, Deserialize, Serialize)] +pub struct Report { + /// The identifying path for this report. + pub path: Vec<String>, + + /// The report kind. + pub kind: ReportKind, + + /// The size, in bytes. + pub size: usize, +} + +/// A channel through which memory reports can be sent. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ReportsChan(pub IpcSender<Vec<Report>>); + +impl ReportsChan { + /// Send `report` on this `IpcSender`. + /// + /// Panics if the send fails. + pub fn send(&self, report: Vec<Report>) { + self.0.send(report).unwrap(); + } +} + +/// The protocol used to send reporter requests. +#[derive(Debug, Deserialize, Serialize)] +pub struct ReporterRequest { + /// The channel on which reports are to be sent. + pub reports_channel: ReportsChan, +} + +/// A memory reporter is capable of measuring some data structure of interest. It's structured as +/// an IPC sender that a `ReporterRequest` in transmitted over. `ReporterRequest` objects in turn +/// encapsulate the channel on which the memory profiling information is to be sent. +/// +/// In many cases, clients construct `Reporter` objects by creating an IPC sender/receiver pair and +/// registering the receiving end with the router so that messages from the memory profiler end up +/// injected into the client's event loop. +#[derive(Debug, Deserialize, Serialize)] +pub struct Reporter(pub IpcSender<ReporterRequest>); + +impl Reporter { + /// Collect one or more memory reports. Returns true on success, and false on failure. + pub fn collect_reports(&self, reports_chan: ReportsChan) { + self.0 + .send(ReporterRequest { + reports_channel: reports_chan, + }) + .unwrap() + } +} + +/// An easy way to build a path for a report. +#[macro_export] +macro_rules! path { + ($($x:expr),*) => {{ + use std::borrow::ToOwned; + vec![$( $x.to_owned() ),*] + }} +} + +/// Messages that can be sent to the memory profiler thread. +#[derive(Debug, Deserialize, Serialize)] +pub enum ProfilerMsg { + /// Register a Reporter with the memory profiler. The String is only used to identify the + /// reporter so it can be unregistered later. The String must be distinct from that used by any + /// other registered reporter otherwise a panic will occur. + RegisterReporter(String, Reporter), + + /// Unregister a Reporter with the memory profiler. The String must match the name given when + /// the reporter was registered. If the String does not match the name of a registered reporter + /// a panic will occur. + UnregisterReporter(String), + + /// Triggers printing of the memory profiling metrics. + Print, + + /// Tells the memory profiler to shut down. + Exit, +} diff --git a/components/shared/profile/time.rs b/components/shared/profile/time.rs new file mode 100644 index 00000000000..2ea952a93ea --- /dev/null +++ b/components/shared/profile/time.rs @@ -0,0 +1,175 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::time::{SystemTime, UNIX_EPOCH}; + +use ipc_channel::ipc::IpcSender; +use log::warn; +use serde::{Deserialize, Serialize}; +use servo_config::opts; + +#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub struct TimerMetadata { + pub url: String, + pub iframe: TimerMetadataFrameType, + pub incremental: TimerMetadataReflowType, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ProfilerChan(pub IpcSender<ProfilerMsg>); + +impl ProfilerChan { + pub fn send(&self, msg: ProfilerMsg) { + if let Err(e) = self.0.send(msg) { + warn!("Error communicating with the time profiler thread: {}", e); + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum ProfilerData { + NoRecords, + Record(Vec<f64>), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum ProfilerMsg { + /// Normal message used for reporting time + Time((ProfilerCategory, Option<TimerMetadata>), (u64, u64)), + /// Message used to get time spend entries for a particular ProfilerBuckets (in nanoseconds) + Get( + (ProfilerCategory, Option<TimerMetadata>), + IpcSender<ProfilerData>, + ), + /// Message used to force print the profiling metrics + Print, + + /// Report a layout query that could not be processed immediately for a particular URL. + BlockedLayoutQuery(String), + + /// Tells the profiler to shut down. + Exit(IpcSender<()>), +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +pub enum ProfilerCategory { + Compositing = 0x00, + LayoutPerform = 0x10, + LayoutStyleRecalc = 0x11, + LayoutTextShaping = 0x12, + LayoutRestyleDamagePropagation = 0x13, + LayoutNonIncrementalReset = 0x14, + LayoutSelectorMatch = 0x15, + LayoutTreeBuilder = 0x16, + LayoutDamagePropagate = 0x17, + LayoutGeneratedContent = 0x18, + LayoutDisplayListSorting = 0x19, + LayoutFloatPlacementSpeculation = 0x1a, + LayoutMain = 0x1b, + LayoutStoreOverflow = 0x1c, + LayoutParallelWarmup = 0x1d, + LayoutDispListBuild = 0x1e, + NetHTTPRequestResponse = 0x30, + PaintingPerTile = 0x41, + PaintingPrepBuff = 0x42, + Painting = 0x43, + ImageDecoding = 0x50, + ImageSaving = 0x51, + ScriptAttachLayout = 0x60, + ScriptConstellationMsg = 0x61, + ScriptDevtoolsMsg = 0x62, + ScriptDocumentEvent = 0x63, + ScriptDomEvent = 0x64, + ScriptEvaluate = 0x65, + ScriptEvent = 0x66, + ScriptFileRead = 0x67, + ScriptImageCacheMsg = 0x68, + ScriptInputEvent = 0x69, + ScriptNetworkEvent = 0x6a, + ScriptParseHTML = 0x6b, + ScriptPlannedNavigation = 0x6c, + ScriptResize = 0x6d, + ScriptSetScrollState = 0x6e, + ScriptSetViewport = 0x6f, + ScriptTimerEvent = 0x70, + ScriptStylesheetLoad = 0x71, + ScriptUpdateReplacedElement = 0x72, + ScriptWebSocketEvent = 0x73, + ScriptWorkerEvent = 0x74, + ScriptServiceWorkerEvent = 0x75, + ScriptParseXML = 0x76, + ScriptEnterFullscreen = 0x77, + ScriptExitFullscreen = 0x78, + ScriptWebVREvent = 0x79, + ScriptWorkletEvent = 0x7a, + ScriptPerformanceEvent = 0x7b, + ScriptHistoryEvent = 0x7c, + ScriptPortMessage = 0x7d, + ScriptWebGPUMsg = 0x7e, + TimeToFirstPaint = 0x80, + TimeToFirstContentfulPaint = 0x81, + TimeToInteractive = 0x82, + IpcReceiver = 0x83, + IpcBytesReceiver = 0x84, +} + +#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub enum TimerMetadataFrameType { + RootWindow, + IFrame, +} + +#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub enum TimerMetadataReflowType { + Incremental, + FirstReflow, +} + +pub fn profile<T, F>( + category: ProfilerCategory, + meta: Option<TimerMetadata>, + profiler_chan: ProfilerChan, + callback: F, +) -> T +where + F: FnOnce() -> T, +{ + if opts::get().debug.signpost { + signpost::start(category as u32, &[0, 0, 0, (category as usize) >> 4]); + } + let start_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(); + + let val = callback(); + + let end_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(); + if opts::get().debug.signpost { + signpost::end(category as u32, &[0, 0, 0, (category as usize) >> 4]); + } + + send_profile_data( + category, + meta, + &profiler_chan, + start_time as u64, + end_time as u64, + ); + val +} + +pub fn send_profile_data( + category: ProfilerCategory, + meta: Option<TimerMetadata>, + profiler_chan: &ProfilerChan, + start_time: u64, + end_time: u64, +) { + profiler_chan.send(ProfilerMsg::Time((category, meta), (start_time, end_time))); +} diff --git a/components/shared/script/Cargo.toml b/components/shared/script/Cargo.toml new file mode 100644 index 00000000000..c27dd185d0b --- /dev/null +++ b/components/shared/script/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "script_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "script_traits" +path = "lib.rs" + +[dependencies] +bitflags = { workspace = true } +bluetooth_traits = { workspace = true } +canvas_traits = { workspace = true } +cookie = { workspace = true } +crossbeam-channel = { workspace = true } +devtools_traits = { workspace = true } +embedder_traits = { workspace = true } +euclid = { workspace = true } +gfx_traits = { workspace = true } +headers = { workspace = true } +http = { workspace = true } +hyper_serde = { workspace = true } +ipc-channel = { workspace = true } +keyboard-types = { workspace = true } +libc = { workspace = true } +log = { workspace = true } +malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of_derive = { workspace = true } +media = { path = "../../media" } +msg = { workspace = true } +net_traits = { workspace = true } +pixels = { path = "../../pixels" } +profile_traits = { workspace = true } +serde = { workspace = true } +servo_atoms = { path = "../../atoms" } +servo_url = { path = "../../url" } +smallvec = { workspace = true } +style_traits = { workspace = true } +time = { workspace = true } +uuid = { workspace = true } +webdriver = { workspace = true } +webgpu = { path = "../../webgpu" } +webrender_api = { workspace = true } +webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } diff --git a/components/shared/script/compositor.rs b/components/shared/script/compositor.rs new file mode 100644 index 00000000000..43ab572e3bc --- /dev/null +++ b/components/shared/script/compositor.rs @@ -0,0 +1,306 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! Defines data structures which are consumed by the Compositor. + +use embedder_traits::Cursor; +use serde::{Deserialize, Serialize}; +use webrender_api::units::{LayoutSize, LayoutVector2D}; +use webrender_api::{ + Epoch, ExternalScrollId, PipelineId, ScrollLocation, ScrollSensitivity, SpatialId, +}; + +/// Information that Servo keeps alongside WebRender display items +/// in order to add more context to hit test results. +#[derive(Debug, Deserialize, Serialize)] +pub struct HitTestInfo { + /// The id of the node of this hit test item. + pub node: u64, + + /// The cursor of this node's hit test item. + pub cursor: Option<Cursor>, + + /// The id of the [ScrollTree] associated with this hit test item. + pub scroll_tree_node: ScrollTreeNodeId, +} + +/// An id for a ScrollTreeNode in the ScrollTree. This contains both the index +/// to the node in the tree's array of nodes as well as the corresponding SpatialId +/// for the SpatialNode in the WebRender display list. +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct ScrollTreeNodeId { + /// The index of this scroll tree node in the tree's array of nodes. + pub index: usize, + + /// The WebRender spatial id of this scroll tree node. + pub spatial_id: SpatialId, +} + +/// Data stored for nodes in the [ScrollTree] that actually scroll, +/// as opposed to reference frames and sticky nodes which do not. +#[derive(Debug, Deserialize, Serialize)] +pub struct ScrollableNodeInfo { + /// The external scroll id of this node, used to track + /// it between successive re-layouts. + pub external_id: ExternalScrollId, + + /// Amount that this `ScrollableNode` can scroll in both directions. + pub scrollable_size: LayoutSize, + + /// Whether this `ScrollableNode` is sensitive to input events. + pub scroll_sensitivity: ScrollSensitivity, + + /// The current offset of this scroll node. + pub offset: LayoutVector2D, +} + +#[derive(Debug, Deserialize, Serialize)] +/// A node in a tree of scroll nodes. This may either be a scrollable +/// node which responds to scroll events or a non-scrollable one. +pub struct ScrollTreeNode { + /// The index of the parent of this node in the tree. If this is + /// None then this is the root node. + pub parent: Option<ScrollTreeNodeId>, + + /// Scrolling data which will not be None if this is a scrolling node. + pub scroll_info: Option<ScrollableNodeInfo>, +} + +impl ScrollTreeNode { + /// Get the external id of this node. + pub fn external_id(&self) -> Option<ExternalScrollId> { + self.scroll_info.as_ref().map(|info| info.external_id) + } + + /// Get the offset id of this node if it applies. + pub fn offset(&self) -> Option<LayoutVector2D> { + self.scroll_info.as_ref().map(|info| info.offset) + } + + /// Set the offset for this node, returns false if this was a + /// non-scrolling node for which you cannot set the offset. + pub fn set_offset(&mut self, new_offset: LayoutVector2D) -> bool { + match self.scroll_info { + Some(ref mut info) => { + info.offset = new_offset; + true + }, + _ => false, + } + } + + /// Scroll this node given a WebRender ScrollLocation. Returns a tuple that can + /// be used to scroll an individual WebRender scroll frame if the operation + /// actually changed an offset. + pub fn scroll( + &mut self, + scroll_location: ScrollLocation, + ) -> Option<(ExternalScrollId, LayoutVector2D)> { + let mut info = match self.scroll_info { + Some(ref mut data) => data, + None => return None, + }; + + if info.scroll_sensitivity != ScrollSensitivity::ScriptAndInputEvents { + return None; + } + + let delta = match scroll_location { + ScrollLocation::Delta(delta) => delta, + ScrollLocation::Start => { + if info.offset.y.round() >= 0.0 { + // Nothing to do on this layer. + return None; + } + + info.offset.y = 0.0; + return Some((info.external_id, info.offset)); + }, + ScrollLocation::End => { + let end_pos = -info.scrollable_size.height; + if info.offset.y.round() <= end_pos { + // Nothing to do on this layer. + return None; + } + + info.offset.y = end_pos; + return Some((info.external_id, info.offset)); + }, + }; + + let scrollable_width = info.scrollable_size.width; + let scrollable_height = info.scrollable_size.height; + let original_layer_scroll_offset = info.offset.clone(); + + if scrollable_width > 0. { + info.offset.x = (info.offset.x + delta.x).min(0.0).max(-scrollable_width); + } + + if scrollable_height > 0. { + info.offset.y = (info.offset.y + delta.y).min(0.0).max(-scrollable_height); + } + + if info.offset != original_layer_scroll_offset { + Some((info.external_id, info.offset)) + } else { + None + } + } +} + +/// A tree of spatial nodes, which mirrors the spatial nodes in the WebRender +/// display list, except these are used to scrolling in the compositor so that +/// new offsets can be sent to WebRender. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct ScrollTree { + /// A list of compositor-side scroll nodes that describe the tree + /// of WebRender spatial nodes, used by the compositor to scroll the + /// contents of the display list. + pub nodes: Vec<ScrollTreeNode>, +} + +impl ScrollTree { + /// Add a scroll node to this ScrollTree returning the id of the new node. + pub fn add_scroll_tree_node( + &mut self, + parent: Option<&ScrollTreeNodeId>, + spatial_id: SpatialId, + scroll_info: Option<ScrollableNodeInfo>, + ) -> ScrollTreeNodeId { + self.nodes.push(ScrollTreeNode { + parent: parent.cloned(), + scroll_info, + }); + return ScrollTreeNodeId { + index: self.nodes.len() - 1, + spatial_id, + }; + } + + /// Get a mutable reference to the node with the given index. + pub fn get_node_mut(&mut self, id: &ScrollTreeNodeId) -> &mut ScrollTreeNode { + &mut self.nodes[id.index] + } + + /// Get an immutable reference to the node with the given index. + pub fn get_node(&mut self, id: &ScrollTreeNodeId) -> &ScrollTreeNode { + &self.nodes[id.index] + } + + /// Scroll the given scroll node on this scroll tree. If the node cannot be scrolled, + /// because it isn't a scrollable node or it's already scrolled to the maximum scroll + /// extent, try to scroll an ancestor of this node. Returns the node scrolled and the + /// new offset if a scroll was performed, otherwise returns None. + pub fn scroll_node_or_ancestor( + &mut self, + scroll_node_id: &ScrollTreeNodeId, + scroll_location: ScrollLocation, + ) -> Option<(ExternalScrollId, LayoutVector2D)> { + let parent = { + let ref mut node = self.get_node_mut(scroll_node_id); + let result = node.scroll(scroll_location); + if result.is_some() { + return result; + } + node.parent + }; + + parent.and_then(|parent| self.scroll_node_or_ancestor(&parent, scroll_location)) + } +} + +/// A data structure which stores compositor-side information about +/// display lists sent to the compositor. +#[derive(Debug, Deserialize, Serialize)] +pub struct CompositorDisplayListInfo { + /// The WebRender [PipelineId] of this display list. + pub pipeline_id: PipelineId, + + /// The size of the viewport that this display list renders into. + pub viewport_size: LayoutSize, + + /// The size of this display list's content. + pub content_size: LayoutSize, + + /// The epoch of the display list. + pub epoch: Epoch, + + /// An array of `HitTestInfo` which is used to store information + /// to assist the compositor to take various actions (set the cursor, + /// scroll without layout) using a WebRender hit test result. + pub hit_test_info: Vec<HitTestInfo>, + + /// A ScrollTree used by the compositor to scroll the contents of the + /// display list. + pub scroll_tree: ScrollTree, + + /// The `ScrollTreeNodeId` of the root reference frame of this info's scroll + /// tree. + pub root_reference_frame_id: ScrollTreeNodeId, + + /// The `ScrollTreeNodeId` of the topmost scrolling frame of this info's scroll + /// tree. + pub root_scroll_node_id: ScrollTreeNodeId, +} + +impl CompositorDisplayListInfo { + /// Create a new CompositorDisplayListInfo with the root reference frame + /// and scroll frame already added to the scroll tree. + pub fn new( + viewport_size: LayoutSize, + content_size: LayoutSize, + pipeline_id: PipelineId, + epoch: Epoch, + ) -> Self { + let mut scroll_tree = ScrollTree::default(); + let root_reference_frame_id = scroll_tree.add_scroll_tree_node( + None, + SpatialId::root_reference_frame(pipeline_id), + None, + ); + let root_scroll_node_id = scroll_tree.add_scroll_tree_node( + Some(&root_reference_frame_id), + SpatialId::root_scroll_node(pipeline_id), + Some(ScrollableNodeInfo { + external_id: ExternalScrollId(0, pipeline_id), + scrollable_size: content_size - viewport_size, + scroll_sensitivity: ScrollSensitivity::ScriptAndInputEvents, + offset: LayoutVector2D::zero(), + }), + ); + + CompositorDisplayListInfo { + pipeline_id, + viewport_size, + content_size, + epoch, + hit_test_info: Default::default(), + scroll_tree, + root_reference_frame_id, + root_scroll_node_id, + } + } + + /// Add or re-use a duplicate HitTestInfo entry in this `CompositorHitTestInfo` + /// and return the index. + pub fn add_hit_test_info( + &mut self, + node: u64, + cursor: Option<Cursor>, + scroll_tree_node: ScrollTreeNodeId, + ) -> usize { + if let Some(last) = self.hit_test_info.last() { + if node == last.node && cursor == last.cursor { + return self.hit_test_info.len() - 1; + } + } + + self.hit_test_info.push(HitTestInfo { + node, + cursor, + scroll_tree_node, + }); + self.hit_test_info.len() - 1 + } +} diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs new file mode 100644 index 00000000000..77782255a4c --- /dev/null +++ b/components/shared/script/lib.rs @@ -0,0 +1,1314 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! This module contains traits in script used generically in the rest of Servo. +//! The traits are here instead of in script so that these modules won't have +//! to depend on script. + +#![deny(missing_docs)] +#![deny(unsafe_code)] + +pub mod compositor; +mod script_msg; +pub mod serializable; +pub mod transferable; +pub mod webdriver_msg; + +use std::borrow::Cow; +use std::collections::{HashMap, VecDeque}; +use std::fmt; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use bitflags::bitflags; +use bluetooth_traits::BluetoothRequest; +use canvas_traits::webgl::WebGLPipeline; +use compositor::ScrollTreeNodeId; +use crossbeam_channel::{Receiver, RecvTimeoutError, Sender}; +use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; +use embedder_traits::Cursor; +use euclid::default::Point2D; +use euclid::{Length, Rect, Scale, Size2D, UnknownUnit, Vector2D}; +use gfx_traits::Epoch; +use http::{HeaderMap, Method}; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::Error as IpcError; +use keyboard_types::webdriver::Event as WebDriverInputEvent; +use keyboard_types::{CompositionEvent, KeyboardEvent}; +use libc::c_void; +use log::warn; +use malloc_size_of::malloc_size_of_is_0; +use malloc_size_of_derive::MallocSizeOf; +use media::WindowGLContext; +use msg::constellation_msg::{ + BackgroundHangMonitorRegister, BlobId, BrowsingContextId, HistoryStateId, MessagePortId, + PipelineId, PipelineNamespaceId, TopLevelBrowsingContextId, +}; +use net_traits::image::base::Image; +use net_traits::image_cache::ImageCache; +use net_traits::request::{Referrer, RequestBody}; +use net_traits::storage_thread::StorageType; +use net_traits::{FetchResponseMsg, ReferrerPolicy, ResourceThreads}; +use pixels::PixelFormat; +use profile_traits::{mem, time as profile_time}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use servo_atoms::Atom; +use servo_url::{ImmutableOrigin, ServoUrl}; +use style_traits::{CSSPixel, SpeculativePainter}; +use webgpu::identity::WebGPUMsg; +use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel, LayoutPoint, WorldPoint}; +use webrender_api::{ + BuiltDisplayList, BuiltDisplayListDescriptor, DocumentId, ExternalImageData, ExternalScrollId, + HitTestFlags, ImageData, ImageDescriptor, ImageKey, PipelineId as WebRenderPipelineId, +}; + +use crate::compositor::CompositorDisplayListInfo; +pub use crate::script_msg::{ + DOMMessage, EventResult, HistoryEntryReplacement, IFrameSizeMsg, Job, JobError, JobResult, + JobResultValue, JobType, LayoutMsg, LogEntry, SWManagerMsg, SWManagerSenders, ScopeThings, + ScriptMsg, ServiceWorkerMsg, +}; +use crate::serializable::{BlobData, BlobImpl}; +use crate::transferable::MessagePortImpl; +use crate::webdriver_msg::{LoadStatus, WebDriverScriptCommand}; + +/// The address of a node. Layout sends these back. They must be validated via +/// `from_untrusted_node_address` before they can be used, because we do not trust layout. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct UntrustedNodeAddress(pub *const c_void); + +malloc_size_of_is_0!(UntrustedNodeAddress); + +#[allow(unsafe_code)] +unsafe impl Send for UntrustedNodeAddress {} + +impl From<style_traits::dom::OpaqueNode> for UntrustedNodeAddress { + fn from(o: style_traits::dom::OpaqueNode) -> Self { + UntrustedNodeAddress(o.0 as *const c_void) + } +} + +impl Serialize for UntrustedNodeAddress { + fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { + (self.0 as usize).serialize(s) + } +} + +impl<'de> Deserialize<'de> for UntrustedNodeAddress { + fn deserialize<D: Deserializer<'de>>(d: D) -> Result<UntrustedNodeAddress, D::Error> { + let value: usize = Deserialize::deserialize(d)?; + Ok(UntrustedNodeAddress::from_id(value)) + } +} + +impl UntrustedNodeAddress { + /// Creates an `UntrustedNodeAddress` from the given pointer address value. + #[inline] + pub fn from_id(id: usize) -> UntrustedNodeAddress { + UntrustedNodeAddress(id as *const c_void) + } +} + +/// Messages sent to the layout thread from the constellation and/or compositor. +#[derive(Debug, Deserialize, Serialize)] +pub enum LayoutControlMsg { + /// Requests that this layout thread exit. + ExitNow, + /// Requests the current epoch (layout counter) from this layout. + GetCurrentEpoch(IpcSender<Epoch>), + /// Tells layout about the new scrolling offsets of each scrollable stacking context. + SetScrollStates(Vec<ScrollState>), + /// Requests the current load state of Web fonts. `true` is returned if fonts are still loading + /// and `false` is returned if all fonts have loaded. + GetWebFontLoadState(IpcSender<bool>), + /// Send the paint time for a specific epoch to the layout thread. + PaintMetric(Epoch, u64), +} + +/// The origin where a given load was initiated. +/// Useful for origin checks, for example before evaluation a JS URL. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum LoadOrigin { + /// A load originating in the constellation. + Constellation, + /// A load originating in webdriver. + WebDriver, + /// A load originating in script. + Script(ImmutableOrigin), +} + +/// can be passed to `LoadUrl` to load a page with GET/POST +/// parameters or headers +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct LoadData { + /// The origin where the load started. + pub load_origin: LoadOrigin, + /// The URL. + pub url: ServoUrl, + /// The creator pipeline id if this is an about:blank load. + pub creator_pipeline_id: Option<PipelineId>, + /// The method. + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + pub method: Method, + /// The headers. + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + pub headers: HeaderMap, + /// The data that will be used as the body of the request. + pub data: Option<RequestBody>, + /// The result of evaluating a javascript scheme url. + pub js_eval_result: Option<JsEvalResult>, + /// The referrer. + pub referrer: Referrer, + /// The referrer policy. + pub referrer_policy: Option<ReferrerPolicy>, + + /// The source to use instead of a network response for a srcdoc document. + pub srcdoc: String, + /// The inherited context is Secure, None if not inherited + pub inherited_secure_context: Option<bool>, + + /// Servo internal: if crash details are present, trigger a crash error page with these details. + pub crash: Option<String>, +} + +/// The result of evaluating a javascript scheme url. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum JsEvalResult { + /// The js evaluation had a non-string result, 204 status code. + /// <https://html.spec.whatwg.org/multipage/#navigate> 12.11 + NoContent, + /// The js evaluation had a string result. + Ok(Vec<u8>), +} + +impl LoadData { + /// Create a new `LoadData` object. + pub fn new( + load_origin: LoadOrigin, + url: ServoUrl, + creator_pipeline_id: Option<PipelineId>, + referrer: Referrer, + referrer_policy: Option<ReferrerPolicy>, + inherited_secure_context: Option<bool>, + ) -> LoadData { + LoadData { + load_origin, + url: url, + creator_pipeline_id: creator_pipeline_id, + method: Method::GET, + headers: HeaderMap::new(), + data: None, + js_eval_result: None, + referrer: referrer, + referrer_policy: referrer_policy, + srcdoc: "".to_string(), + inherited_secure_context, + crash: None, + } + } +} + +/// The initial data required to create a new layout attached to an existing script thread. +#[derive(Debug, Deserialize, Serialize)] +pub struct NewLayoutInfo { + /// The ID of the parent pipeline and frame type, if any. + /// If `None`, this is a root pipeline. + pub parent_info: Option<PipelineId>, + /// Id of the newly-created pipeline. + pub new_pipeline_id: PipelineId, + /// Id of the browsing context associated with this pipeline. + pub browsing_context_id: BrowsingContextId, + /// Id of the top-level browsing context associated with this pipeline. + pub top_level_browsing_context_id: TopLevelBrowsingContextId, + /// Id of the opener, if any + pub opener: Option<BrowsingContextId>, + /// Network request data which will be initiated by the script thread. + pub load_data: LoadData, + /// Information about the initial window size. + pub window_size: WindowSizeData, + /// A port on which layout can receive messages from the pipeline. + pub pipeline_port: IpcReceiver<LayoutControlMsg>, +} + +/// When a pipeline is closed, should its browsing context be discarded too? +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum DiscardBrowsingContext { + /// Discard the browsing context + Yes, + /// Don't discard the browsing context + No, +} + +/// Is a document fully active, active or inactive? +/// A document is active if it is the current active document in its session history, +/// it is fuly active if it is active and all of its ancestors are active, +/// and it is inactive otherwise. +/// +/// * <https://html.spec.whatwg.org/multipage/#active-document> +/// * <https://html.spec.whatwg.org/multipage/#fully-active> +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub enum DocumentActivity { + /// An inactive document + Inactive, + /// An active but not fully active document + Active, + /// A fully active document + FullyActive, +} + +/// Type of recorded progressive web metric +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub enum ProgressiveWebMetricType { + /// Time to first Paint + FirstPaint, + /// Time to first contentful paint + FirstContentfulPaint, + /// Time to interactive + TimeToInteractive, +} + +/// The reason why the pipeline id of an iframe is being updated. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub enum UpdatePipelineIdReason { + /// The pipeline id is being updated due to a navigation. + Navigation, + /// The pipeline id is being updated due to a history traversal. + Traversal, +} + +/// Messages sent from the constellation or layout to the script thread. +#[derive(Deserialize, Serialize)] +pub enum ConstellationControlMsg { + /// Takes the associated window proxy out of "delaying-load-events-mode", + /// used if a scheduled navigated was refused by the embedder. + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + StopDelayingLoadEventsMode(PipelineId), + /// Sends the final response to script thread for fetching after all redirections + /// have been resolved + NavigationResponse(PipelineId, FetchResponseMsg), + /// Gives a channel and ID to a layout thread, as well as the ID of that layout's parent + AttachLayout(NewLayoutInfo), + /// Window resized. Sends a DOM event eventually, but first we combine events. + Resize(PipelineId, WindowSizeData, WindowSizeType), + /// Notifies script that window has been resized but to not take immediate action. + ResizeInactive(PipelineId, WindowSizeData), + /// Window switched from fullscreen mode. + ExitFullScreen(PipelineId), + /// Notifies the script that the document associated with this pipeline should 'unload'. + UnloadDocument(PipelineId), + /// Notifies the script that a pipeline should be closed. + ExitPipeline(PipelineId, DiscardBrowsingContext), + /// Notifies the script that the whole thread should be closed. + ExitScriptThread, + /// Sends a DOM event. + SendEvent(PipelineId, CompositorEvent), + /// Notifies script of the viewport. + Viewport(PipelineId, Rect<f32, UnknownUnit>), + /// Notifies script of a new set of scroll offsets. + SetScrollState( + PipelineId, + Vec<(UntrustedNodeAddress, Vector2D<f32, LayoutPixel>)>, + ), + /// Requests that the script thread immediately send the constellation the title of a pipeline. + GetTitle(PipelineId), + /// Notifies script thread of a change to one of its document's activity + SetDocumentActivity(PipelineId, DocumentActivity), + /// Notifies script thread whether frame is visible + ChangeFrameVisibilityStatus(PipelineId, bool), + /// Notifies script thread that frame visibility change is complete + /// PipelineId is for the parent, BrowsingContextId is for the nested browsing context + NotifyVisibilityChange(PipelineId, BrowsingContextId, bool), + /// Notifies script thread that a url should be loaded in this iframe. + /// PipelineId is for the parent, BrowsingContextId is for the nested browsing context + NavigateIframe( + PipelineId, + BrowsingContextId, + LoadData, + HistoryEntryReplacement, + ), + /// Post a message to a given window. + PostMessage { + /// The target of the message. + target: PipelineId, + /// The source of the message. + source: PipelineId, + /// The top level browsing context associated with the source pipeline. + source_browsing_context: TopLevelBrowsingContextId, + /// The expected origin of the target. + target_origin: Option<ImmutableOrigin>, + /// The source origin of the message. + /// https://html.spec.whatwg.org/multipage/#dom-messageevent-origin + source_origin: ImmutableOrigin, + /// The data to be posted. + data: StructuredSerializedData, + }, + /// Updates the current pipeline ID of a given iframe. + /// First PipelineId is for the parent, second is the new PipelineId for the frame. + UpdatePipelineId( + PipelineId, + BrowsingContextId, + TopLevelBrowsingContextId, + PipelineId, + UpdatePipelineIdReason, + ), + /// Updates the history state and url of a given pipeline. + UpdateHistoryState(PipelineId, Option<HistoryStateId>, ServoUrl), + /// Removes inaccesible history states. + RemoveHistoryStates(PipelineId, Vec<HistoryStateId>), + /// Set an iframe to be focused. Used when an element in an iframe gains focus. + /// PipelineId is for the parent, BrowsingContextId is for the nested browsing context + FocusIFrame(PipelineId, BrowsingContextId), + /// Passes a webdriver command to the script thread for execution + WebDriverScriptCommand(PipelineId, WebDriverScriptCommand), + /// Notifies script thread that all animations are done + TickAllAnimations(PipelineId, AnimationTickType), + /// Notifies the script thread that a new Web font has been loaded, and thus the page should be + /// reflowed. + WebFontLoaded(PipelineId), + /// Cause a `load` event to be dispatched at the appropriate iframe element. + DispatchIFrameLoadEvent { + /// The frame that has been marked as loaded. + target: BrowsingContextId, + /// The pipeline that contains a frame loading the target pipeline. + parent: PipelineId, + /// The pipeline that has completed loading. + child: PipelineId, + }, + /// Cause a `storage` event to be dispatched at the appropriate window. + /// The strings are key, old value and new value. + DispatchStorageEvent( + PipelineId, + StorageType, + ServoUrl, + Option<String>, + Option<String>, + Option<String>, + ), + /// Report an error from a CSS parser for the given pipeline + ReportCSSError(PipelineId, String, u32, u32, String), + /// Reload the given page. + Reload(PipelineId), + /// Notifies the script thread about a new recorded paint metric. + PaintMetric(PipelineId, ProgressiveWebMetricType, u64), + /// Notifies the media session about a user requested media session action. + MediaSessionAction(PipelineId, MediaSessionActionType), + /// Notifies script thread that WebGPU server has started + SetWebGPUPort(IpcReceiver<WebGPUMsg>), +} + +impl fmt::Debug for ConstellationControlMsg { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + use self::ConstellationControlMsg::*; + let variant = match *self { + StopDelayingLoadEventsMode(..) => "StopDelayingLoadsEventMode", + NavigationResponse(..) => "NavigationResponse", + AttachLayout(..) => "AttachLayout", + Resize(..) => "Resize", + ResizeInactive(..) => "ResizeInactive", + UnloadDocument(..) => "UnloadDocument", + ExitPipeline(..) => "ExitPipeline", + ExitScriptThread => "ExitScriptThread", + SendEvent(..) => "SendEvent", + Viewport(..) => "Viewport", + SetScrollState(..) => "SetScrollState", + GetTitle(..) => "GetTitle", + SetDocumentActivity(..) => "SetDocumentActivity", + ChangeFrameVisibilityStatus(..) => "ChangeFrameVisibilityStatus", + NotifyVisibilityChange(..) => "NotifyVisibilityChange", + NavigateIframe(..) => "NavigateIframe", + PostMessage { .. } => "PostMessage", + UpdatePipelineId(..) => "UpdatePipelineId", + UpdateHistoryState(..) => "UpdateHistoryState", + RemoveHistoryStates(..) => "RemoveHistoryStates", + FocusIFrame(..) => "FocusIFrame", + WebDriverScriptCommand(..) => "WebDriverScriptCommand", + TickAllAnimations(..) => "TickAllAnimations", + WebFontLoaded(..) => "WebFontLoaded", + DispatchIFrameLoadEvent { .. } => "DispatchIFrameLoadEvent", + DispatchStorageEvent(..) => "DispatchStorageEvent", + ReportCSSError(..) => "ReportCSSError", + Reload(..) => "Reload", + PaintMetric(..) => "PaintMetric", + ExitFullScreen(..) => "ExitFullScreen", + MediaSessionAction(..) => "MediaSessionAction", + SetWebGPUPort(..) => "SetWebGPUPort", + }; + write!(formatter, "ConstellationControlMsg::{}", variant) + } +} + +/// Used to determine if a script has any pending asynchronous activity. +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub enum DocumentState { + /// The document has been loaded and is idle. + Idle, + /// The document is either loading or waiting on an event. + Pending, +} + +/// For a given pipeline, whether any animations are currently running +/// and any animation callbacks are queued +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum AnimationState { + /// Animations are active but no callbacks are queued + AnimationsPresent, + /// Animations are active and callbacks are queued + AnimationCallbacksPresent, + /// No animations are active and no callbacks are queued + NoAnimationsPresent, + /// No animations are active but callbacks are queued + NoAnimationCallbacksPresent, +} + +/// The type of input represented by a multi-touch event. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub enum TouchEventType { + /// A new touch point came in contact with the screen. + Down, + /// An existing touch point changed location. + Move, + /// A touch point was removed from the screen. + Up, + /// The system stopped tracking a touch point. + Cancel, +} + +/// An opaque identifier for a touch point. +/// +/// <http://w3c.github.io/touch-events/#widl-Touch-identifier> +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct TouchId(pub i32); + +/// The mouse button involved in the event. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub enum MouseButton { + /// The left mouse button. + Left = 1, + /// The right mouse button. + Right = 2, + /// The middle mouse button. + Middle = 4, +} + +/// The types of mouse events +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum MouseEventType { + /// Mouse button clicked + Click, + /// Mouse button down + MouseDown, + /// Mouse button up + MouseUp, +} + +/// Mode to measure WheelDelta floats in +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub enum WheelMode { + /// Delta values are specified in pixels + DeltaPixel = 0x00, + /// Delta values are specified in lines + DeltaLine = 0x01, + /// Delta values are specified in pages + DeltaPage = 0x02, +} + +/// The Wheel event deltas in every direction +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub struct WheelDelta { + /// Delta in the left/right direction + pub x: f64, + /// Delta in the up/down direction + pub y: f64, + /// Delta in the direction going into/out of the screen + pub z: f64, + /// Mode to measure the floats in + pub mode: WheelMode, +} + +/// Events from the compositor that the script thread needs to know about +#[derive(Debug, Deserialize, Serialize)] +pub enum CompositorEvent { + /// The window was resized. + ResizeEvent(WindowSizeData, WindowSizeType), + /// A mouse button state changed. + MouseButtonEvent( + MouseEventType, + MouseButton, + Point2D<f32>, + Option<UntrustedNodeAddress>, + Option<Point2D<f32>>, + // Bitmask of MouseButton values representing the currently pressed buttons + u16, + ), + /// The mouse was moved over a point (or was moved out of the recognizable region). + MouseMoveEvent( + Point2D<f32>, + Option<UntrustedNodeAddress>, + // Bitmask of MouseButton values representing the currently pressed buttons + u16, + ), + /// A touch event was generated with a touch ID and location. + TouchEvent( + TouchEventType, + TouchId, + Point2D<f32>, + Option<UntrustedNodeAddress>, + ), + /// A wheel event was generated with a delta in the X, Y, and/or Z directions + WheelEvent(WheelDelta, Point2D<f32>, Option<UntrustedNodeAddress>), + /// A key was pressed. + KeyboardEvent(KeyboardEvent), + /// An event from the IME is dispatched. + CompositionEvent(CompositionEvent), + /// Virtual keyboard was dismissed + IMEDismissedEvent, +} + +/// Requests a TimerEvent-Message be sent after the given duration. +#[derive(Debug, Deserialize, Serialize)] +pub struct TimerEventRequest( + pub IpcSender<TimerEvent>, + pub TimerSource, + pub TimerEventId, + pub MsDuration, +); + +/// The message used to send a request to the timer scheduler. +#[derive(Debug, Deserialize, Serialize)] +pub struct TimerSchedulerMsg(pub TimerEventRequest); + +/// Notifies the script thread to fire due timers. +/// `TimerSource` must be `FromWindow` when dispatched to `ScriptThread` and +/// must be `FromWorker` when dispatched to a `DedicatedGlobalWorkerScope` +#[derive(Debug, Deserialize, Serialize)] +pub struct TimerEvent(pub TimerSource, pub TimerEventId); + +/// Describes the thread that requested the TimerEvent. +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum TimerSource { + /// The event was requested from a window (ScriptThread). + FromWindow(PipelineId), + /// The event was requested from a worker (DedicatedGlobalWorkerScope). + FromWorker, +} + +/// The id to be used for a `TimerEvent` is defined by the corresponding `TimerEventRequest`. +#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)] +pub struct TimerEventId(pub u32); + +/// Unit of measurement. +#[derive(Clone, Copy, MallocSizeOf)] +pub enum Milliseconds {} +/// Unit of measurement. +#[derive(Clone, Copy, MallocSizeOf)] +pub enum Nanoseconds {} + +/// Amount of milliseconds. +pub type MsDuration = Length<u64, Milliseconds>; +/// Amount of nanoseconds. +pub type NsDuration = Length<u64, Nanoseconds>; + +/// Returns the duration since an unspecified epoch measured in ms. +pub fn precise_time_ms() -> MsDuration { + Length::new(time::precise_time_ns() / (1000 * 1000)) +} + +/// Data needed to construct a script thread. +/// +/// NB: *DO NOT* add any Senders or Receivers here! pcwalton will have to rewrite your code if you +/// do! Use IPC senders and receivers instead. +pub struct InitialScriptState { + /// The ID of the pipeline with which this script thread is associated. + pub id: PipelineId, + /// The subpage ID of this pipeline to create in its pipeline parent. + /// If `None`, this is the root. + pub parent_info: Option<PipelineId>, + /// The ID of the browsing context this script is part of. + pub browsing_context_id: BrowsingContextId, + /// The ID of the top-level browsing context this script is part of. + pub top_level_browsing_context_id: TopLevelBrowsingContextId, + /// The ID of the opener, if any. + pub opener: Option<BrowsingContextId>, + /// Loading into a Secure Context + pub inherited_secure_context: Option<bool>, + /// A channel with which messages can be sent to us (the script thread). + pub control_chan: IpcSender<ConstellationControlMsg>, + /// A port on which messages sent by the constellation to script can be received. + pub control_port: IpcReceiver<ConstellationControlMsg>, + /// A channel on which messages can be sent to the constellation from script. + pub script_to_constellation_chan: ScriptToConstellationChan, + /// A handle to register script-(and associated layout-)threads for hang monitoring. + pub background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, + /// A sender for the layout thread to communicate to the constellation. + pub layout_to_constellation_chan: IpcSender<LayoutMsg>, + /// A channel to schedule timer events. + pub scheduler_chan: IpcSender<TimerSchedulerMsg>, + /// A channel to the resource manager thread. + pub resource_threads: ResourceThreads, + /// A channel to the bluetooth thread. + pub bluetooth_thread: IpcSender<BluetoothRequest>, + /// The image cache for this script thread. + pub image_cache: Arc<dyn ImageCache>, + /// A channel to the time profiler thread. + pub time_profiler_chan: profile_traits::time::ProfilerChan, + /// A channel to the memory profiler thread. + pub mem_profiler_chan: mem::ProfilerChan, + /// A channel to the developer tools, if applicable. + pub devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, + /// Information about the initial window size. + pub window_size: WindowSizeData, + /// The ID of the pipeline namespace for this script thread. + pub pipeline_namespace_id: PipelineNamespaceId, + /// A ping will be sent on this channel once the script thread shuts down. + pub content_process_shutdown_chan: Sender<()>, + /// A channel to the WebGL thread used in this pipeline. + pub webgl_chan: Option<WebGLPipeline>, + /// The XR device registry + pub webxr_registry: webxr_api::Registry, + /// The Webrender document ID associated with this thread. + pub webrender_document: DocumentId, + /// FIXME(victor): The Webrender API sender in this constellation's pipeline + pub webrender_api_sender: WebrenderIpcSender, + /// Flag to indicate if the layout thread is busy handling a request. + pub layout_is_busy: Arc<AtomicBool>, + /// Application window's GL Context for Media player + pub player_context: WindowGLContext, +} + +/// This trait allows creating a `ScriptThread` without depending on the `script` +/// crate. +pub trait ScriptThreadFactory { + /// Type of message sent from script to layout. + type Message; + /// Create a `ScriptThread`. + fn create( + state: InitialScriptState, + load_data: LoadData, + user_agent: Cow<'static, str>, + ) -> (Sender<Self::Message>, Receiver<Self::Message>); +} + +/// This trait allows creating a `ServiceWorkerManager` without depending on the `script` +/// crate. +pub trait ServiceWorkerManagerFactory { + /// Create a `ServiceWorkerManager`. + fn create(sw_senders: SWManagerSenders, origin: ImmutableOrigin); +} + +/// Whether the sandbox attribute is present for an iframe element +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum IFrameSandboxState { + /// Sandbox attribute is present + IFrameSandboxed, + /// Sandbox attribute is not present + IFrameUnsandboxed, +} + +/// Specifies the information required to load an auxiliary browsing context. +#[derive(Debug, Deserialize, Serialize)] +pub struct AuxiliaryBrowsingContextLoadInfo { + /// Load data containing the url to load + pub load_data: LoadData, + /// The pipeline opener browsing context. + pub opener_pipeline_id: PipelineId, + /// The new top-level ID for the auxiliary. + pub new_top_level_browsing_context_id: TopLevelBrowsingContextId, + /// The new browsing context ID. + pub new_browsing_context_id: BrowsingContextId, + /// The new pipeline ID for the auxiliary. + pub new_pipeline_id: PipelineId, +} + +/// Specifies the information required to load an iframe. +#[derive(Debug, Deserialize, Serialize)] +pub struct IFrameLoadInfo { + /// Pipeline ID of the parent of this iframe + pub parent_pipeline_id: PipelineId, + /// The ID for this iframe's nested browsing context. + pub browsing_context_id: BrowsingContextId, + /// The ID for the top-level ancestor browsing context of this iframe's nested browsing context. + pub top_level_browsing_context_id: TopLevelBrowsingContextId, + /// The new pipeline ID that the iframe has generated. + pub new_pipeline_id: PipelineId, + /// Whether this iframe should be considered private + pub is_private: bool, + /// Whether this iframe should be considered secure + pub inherited_secure_context: Option<bool>, + /// Wether this load should replace the current entry (reload). If true, the current + /// entry will be replaced instead of a new entry being added. + pub replace: HistoryEntryReplacement, +} + +/// Specifies the information required to load a URL in an iframe. +#[derive(Debug, Deserialize, Serialize)] +pub struct IFrameLoadInfoWithData { + /// The information required to load an iframe. + pub info: IFrameLoadInfo, + /// Load data containing the url to load + pub load_data: LoadData, + /// The old pipeline ID for this iframe, if a page was previously loaded. + pub old_pipeline_id: Option<PipelineId>, + /// Sandbox type of this iframe + pub sandbox: IFrameSandboxState, + /// The initial viewport size for this iframe. + pub window_size: WindowSizeData, +} + +bitflags! { + #[derive(Deserialize, Serialize)] + /// Specifies if rAF should be triggered and/or CSS Animations and Transitions. + pub struct AnimationTickType: u8 { + /// Trigger a call to requestAnimationFrame. + const REQUEST_ANIMATION_FRAME = 0b001; + /// Trigger restyles for CSS Animations and Transitions. + const CSS_ANIMATIONS_AND_TRANSITIONS = 0b010; + } +} + +/// The scroll state of a stacking context. +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub struct ScrollState { + /// The ID of the scroll root. + pub scroll_id: ExternalScrollId, + /// The scrolling offset of this stacking context. + pub scroll_offset: Vector2D<f32, LayoutPixel>, +} + +/// Data about the window size. +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub struct WindowSizeData { + /// The size of the initial layout viewport, before parsing an + /// <http://www.w3.org/TR/css-device-adapt/#initial-viewport> + pub initial_viewport: Size2D<f32, CSSPixel>, + + /// The resolution of the window in dppx, not including any "pinch zoom" factor. + pub device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>, +} + +/// The type of window size change. +#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)] +pub enum WindowSizeType { + /// Initial load. + Initial, + /// Window resize. + Resize, +} + +/// Messages to the constellation originating from the WebDriver server. +#[derive(Debug, Deserialize, Serialize)] +pub enum WebDriverCommandMsg { + /// Get the window size. + GetWindowSize(TopLevelBrowsingContextId, IpcSender<WindowSizeData>), + /// Load a URL in the top-level browsing context with the given ID. + LoadUrl(TopLevelBrowsingContextId, LoadData, IpcSender<LoadStatus>), + /// Refresh the top-level browsing context with the given ID. + Refresh(TopLevelBrowsingContextId, IpcSender<LoadStatus>), + /// Pass a webdriver command to the script thread of the current pipeline + /// of a browsing context. + ScriptCommand(BrowsingContextId, WebDriverScriptCommand), + /// Act as if keys were pressed in the browsing context with the given ID. + SendKeys(BrowsingContextId, Vec<WebDriverInputEvent>), + /// Act as if keys were pressed or release in the browsing context with the given ID. + KeyboardAction(BrowsingContextId, KeyboardEvent), + /// Act as if the mouse was clicked in the browsing context with the given ID. + MouseButtonAction(MouseEventType, MouseButton, f32, f32), + /// Act as if the mouse was moved in the browsing context with the given ID. + MouseMoveAction(f32, f32), + /// Set the window size. + SetWindowSize( + TopLevelBrowsingContextId, + DeviceIntSize, + IpcSender<WindowSizeData>, + ), + /// Take a screenshot of the window. + TakeScreenshot( + TopLevelBrowsingContextId, + Option<Rect<f32, CSSPixel>>, + IpcSender<Option<Image>>, + ), +} + +/// Resources required by workerglobalscopes +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WorkerGlobalScopeInit { + /// Chan to a resource thread + pub resource_threads: ResourceThreads, + /// Chan to the memory profiler + pub mem_profiler_chan: mem::ProfilerChan, + /// Chan to the time profiler + pub time_profiler_chan: profile_time::ProfilerChan, + /// To devtools sender + pub to_devtools_sender: Option<IpcSender<ScriptToDevtoolsControlMsg>>, + /// From devtools sender + pub from_devtools_sender: Option<IpcSender<DevtoolScriptControlMsg>>, + /// Messages to send to constellation + pub script_to_constellation_chan: ScriptToConstellationChan, + /// Message to send to the scheduler + pub scheduler_chan: IpcSender<TimerSchedulerMsg>, + /// The worker id + pub worker_id: WorkerId, + /// The pipeline id + pub pipeline_id: PipelineId, + /// The origin + pub origin: ImmutableOrigin, + /// The creation URL + pub creation_url: Option<ServoUrl>, + /// True if headless mode + pub is_headless: bool, + /// An optional string allowing the user agnet to be set for testing. + pub user_agent: Cow<'static, str>, + /// True if secure context + pub inherited_secure_context: Option<bool>, +} + +/// Common entities representing a network load origin +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct WorkerScriptLoadOrigin { + /// referrer url + pub referrer_url: Option<ServoUrl>, + /// the referrer policy which is used + pub referrer_policy: Option<ReferrerPolicy>, + /// the pipeline id of the entity requesting the load + pub pipeline_id: PipelineId, +} + +/// Errors from executing a paint worklet +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum PaintWorkletError { + /// Execution timed out. + Timeout, + /// No such worklet. + WorkletNotFound, +} + +impl From<RecvTimeoutError> for PaintWorkletError { + fn from(_: RecvTimeoutError) -> PaintWorkletError { + PaintWorkletError::Timeout + } +} + +/// Execute paint code in the worklet thread pool. +pub trait Painter: SpeculativePainter { + /// <https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image> + fn draw_a_paint_image( + &self, + size: Size2D<f32, CSSPixel>, + zoom: Scale<f32, CSSPixel, DevicePixel>, + properties: Vec<(Atom, String)>, + arguments: Vec<String>, + ) -> Result<DrawAPaintImageResult, PaintWorkletError>; +} + +impl fmt::Debug for dyn Painter { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_tuple("Painter") + .field(&format_args!("..")) + .finish() + } +} + +/// The result of executing paint code: the image together with any image URLs that need to be loaded. +/// +/// TODO: this should return a WR display list. <https://github.com/servo/servo/issues/17497> +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct DrawAPaintImageResult { + /// The image height + pub width: u32, + /// The image width + pub height: u32, + /// The image format + pub format: PixelFormat, + /// The image drawn, or None if an invalid paint image was drawn + pub image_key: Option<ImageKey>, + /// Drawing the image might have requested loading some image URLs. + pub missing_image_urls: Vec<ServoUrl>, +} + +/// A Script to Constellation channel. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ScriptToConstellationChan { + /// Sender for communicating with constellation thread. + pub sender: IpcSender<(PipelineId, ScriptMsg)>, + /// Used to identify the origin of the message. + pub pipeline_id: PipelineId, +} + +impl ScriptToConstellationChan { + /// Send ScriptMsg and attach the pipeline_id to the message. + pub fn send(&self, msg: ScriptMsg) -> Result<(), IpcError> { + self.sender.send((self.pipeline_id, msg)) + } +} + +/// A data-holder for serialized data and transferred objects. +/// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer> +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct StructuredSerializedData { + /// Data serialized by SpiderMonkey. + pub serialized: Vec<u8>, + /// Serialized in a structured callback, + pub blobs: Option<HashMap<BlobId, BlobImpl>>, + /// Transferred objects. + 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 +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct PortMessageTask { + /// The origin of this task. + pub origin: ImmutableOrigin, + /// A data-holder for serialized data and transferred objects. + pub data: StructuredSerializedData, +} + +/// Messages for communication between the constellation and a global managing ports. +#[derive(Debug, Deserialize, Serialize)] +pub enum MessagePortMsg { + /// Complete the transfer for a batch of ports. + CompleteTransfer(HashMap<MessagePortId, VecDeque<PortMessageTask>>), + /// Complete the transfer of a single port, + /// whose transfer was pending because it had been requested + /// while a previous failed transfer was being rolled-back. + CompletePendingTransfer(MessagePortId, VecDeque<PortMessageTask>), + /// Remove a port, the entangled one doesn't exists anymore. + RemoveMessagePort(MessagePortId), + /// Handle a new port-message-task. + 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. +/// https://w3c.github.io/mediasession/#enumdef-mediasessionaction +#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub enum MediaSessionActionType { + /// The action intent is to resume playback. + Play, + /// The action intent is to pause the currently active playback. + Pause, + /// The action intent is to move the playback time backward by a short period (i.e. a few + /// seconds). + SeekBackward, + /// The action intent is to move the playback time forward by a short period (i.e. a few + /// seconds). + SeekForward, + /// The action intent is to either start the current playback from the beginning if the + /// playback has a notion, of beginning, or move to the previous item in the playlist if the + /// playback has a notion of playlist. + PreviousTrack, + /// The action is to move to the playback to the next item in the playlist if the playback has + /// a notion of playlist. + NextTrack, + /// The action intent is to skip the advertisement that is currently playing. + SkipAd, + /// The action intent is to stop the playback and clear the state if appropriate. + Stop, + /// The action intent is to move the playback time to a specific time. + SeekTo, +} + +impl From<i32> for MediaSessionActionType { + fn from(value: i32) -> MediaSessionActionType { + match value { + 1 => MediaSessionActionType::Play, + 2 => MediaSessionActionType::Pause, + 3 => MediaSessionActionType::SeekBackward, + 4 => MediaSessionActionType::SeekForward, + 5 => MediaSessionActionType::PreviousTrack, + 6 => MediaSessionActionType::NextTrack, + 7 => MediaSessionActionType::SkipAd, + 8 => MediaSessionActionType::Stop, + 9 => MediaSessionActionType::SeekTo, + _ => panic!("Unknown MediaSessionActionType"), + } + } +} + +/// The result of a hit test in the compositor. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CompositorHitTestResult { + /// The pipeline id of the resulting item. + pub pipeline_id: PipelineId, + + /// The hit test point in the item's viewport. + pub point_in_viewport: euclid::default::Point2D<f32>, + + /// The hit test point relative to the item itself. + pub point_relative_to_item: euclid::default::Point2D<f32>, + + /// The node address of the hit test result. + pub node: UntrustedNodeAddress, + + /// The cursor that should be used when hovering the item hit by the hit test. + pub cursor: Option<Cursor>, + + /// The scroll tree node associated with this hit test item. + pub scroll_tree_node: ScrollTreeNodeId, +} + +/// The set of WebRender operations that can be initiated by the content process. +#[derive(Deserialize, Serialize)] +pub enum ScriptToCompositorMsg { + /// Inform WebRender of the existence of this pipeline. + SendInitialTransaction(WebRenderPipelineId), + /// Perform a scroll operation. + SendScrollNode(LayoutPoint, ExternalScrollId), + /// Inform WebRender of a new display list for the given pipeline. + SendDisplayList { + /// The [CompositorDisplayListInfo] that describes the display list being sent. + display_list_info: CompositorDisplayListInfo, + /// A descriptor of this display list used to construct this display list from raw data. + display_list_descriptor: BuiltDisplayListDescriptor, + /// An [ipc::IpcBytesReceiver] used to send the raw data of the display list. + display_list_receiver: ipc::IpcBytesReceiver, + }, + /// Perform a hit test operation. The result will be returned via + /// the provided channel sender. + HitTest( + Option<WebRenderPipelineId>, + WorldPoint, + HitTestFlags, + IpcSender<Vec<CompositorHitTestResult>>, + ), + /// Create a new image key. The result will be returned via the + /// provided channel sender. + GenerateImageKey(IpcSender<ImageKey>), + /// Perform a resource update operation. + UpdateImages(Vec<SerializedImageUpdate>), +} + +#[derive(Clone, Deserialize, Serialize)] +/// A mechanism to communicate with the parent process' WebRender instance. +pub struct WebrenderIpcSender(IpcSender<ScriptToCompositorMsg>); + +impl WebrenderIpcSender { + /// Create a new WebrenderIpcSender object that wraps the provided channel sender. + pub fn new(sender: IpcSender<ScriptToCompositorMsg>) -> Self { + Self(sender) + } + + /// Inform WebRender of the existence of this pipeline. + pub fn send_initial_transaction(&self, pipeline: WebRenderPipelineId) { + if let Err(e) = self + .0 + .send(ScriptToCompositorMsg::SendInitialTransaction(pipeline)) + { + warn!("Error sending initial transaction: {}", e); + } + } + + /// Perform a scroll operation. + pub fn send_scroll_node(&self, point: LayoutPoint, scroll_id: ExternalScrollId) { + if let Err(e) = self + .0 + .send(ScriptToCompositorMsg::SendScrollNode(point, scroll_id)) + { + warn!("Error sending scroll node: {}", e); + } + } + + /// Inform WebRender of a new display list for the given pipeline. + pub fn send_display_list( + &self, + display_list_info: CompositorDisplayListInfo, + list: BuiltDisplayList, + ) { + let (display_list_data, display_list_descriptor) = list.into_data(); + let (display_list_sender, display_list_receiver) = ipc::bytes_channel().unwrap(); + if let Err(e) = self.0.send(ScriptToCompositorMsg::SendDisplayList { + display_list_info, + display_list_descriptor, + display_list_receiver, + }) { + warn!("Error sending display list: {}", e); + } + + if let Err(e) = display_list_sender.send(&display_list_data) { + warn!("Error sending display data: {}", e); + } + } + + /// Perform a hit test operation. Blocks until the operation is complete and + /// and a result is available. + pub fn hit_test( + &self, + pipeline: Option<WebRenderPipelineId>, + point: WorldPoint, + flags: HitTestFlags, + ) -> Vec<CompositorHitTestResult> { + let (sender, receiver) = ipc::channel().unwrap(); + self.0 + .send(ScriptToCompositorMsg::HitTest( + pipeline, point, flags, sender, + )) + .expect("error sending hit test"); + receiver.recv().expect("error receiving hit test result") + } + + /// Create a new image key. Blocks until the key is available. + pub fn generate_image_key(&self) -> Result<ImageKey, ()> { + let (sender, receiver) = ipc::channel().unwrap(); + self.0 + .send(ScriptToCompositorMsg::GenerateImageKey(sender)) + .map_err(|_| ())?; + receiver.recv().map_err(|_| ()) + } + + /// Perform a resource update operation. + pub fn update_images(&self, updates: Vec<ImageUpdate>) { + let mut senders = Vec::new(); + // Convert `ImageUpdate` to `SerializedImageUpdate` because `ImageData` may contain large + // byes. With this conversion, we send `IpcBytesReceiver` instead and use it to send the + // actual bytes. + let updates = updates + .into_iter() + .map(|update| match update { + ImageUpdate::AddImage(k, d, data) => { + let data = match data { + ImageData::Raw(r) => { + let (sender, receiver) = ipc::bytes_channel().unwrap(); + senders.push((sender, r)); + SerializedImageData::Raw(receiver) + }, + ImageData::External(e) => SerializedImageData::External(e), + }; + SerializedImageUpdate::AddImage(k, d, data) + }, + ImageUpdate::DeleteImage(k) => SerializedImageUpdate::DeleteImage(k), + ImageUpdate::UpdateImage(k, d, data) => { + let data = match data { + ImageData::Raw(r) => { + let (sender, receiver) = ipc::bytes_channel().unwrap(); + senders.push((sender, r)); + SerializedImageData::Raw(receiver) + }, + ImageData::External(e) => SerializedImageData::External(e), + }; + SerializedImageUpdate::UpdateImage(k, d, data) + }, + }) + .collect(); + + if let Err(e) = self.0.send(ScriptToCompositorMsg::UpdateImages(updates)) { + warn!("error sending image updates: {}", e); + } + + senders.into_iter().for_each(|(tx, data)| { + if let Err(e) = tx.send(&*data) { + warn!("error sending image data: {}", e); + } + }); + } +} + +#[derive(Deserialize, Serialize)] +/// Serializable image updates that must be performed by WebRender. +pub enum ImageUpdate { + /// Register a new image. + AddImage(ImageKey, ImageDescriptor, ImageData), + /// Delete a previously registered image registration. + DeleteImage(ImageKey), + /// Update an existing image registration. + UpdateImage(ImageKey, ImageDescriptor, ImageData), +} + +#[derive(Deserialize, Serialize)] +/// Serialized `ImageUpdate`. +pub enum SerializedImageUpdate { + /// Register a new image. + AddImage(ImageKey, ImageDescriptor, SerializedImageData), + /// Delete a previously registered image registration. + DeleteImage(ImageKey), + /// Update an existing image registration. + UpdateImage(ImageKey, ImageDescriptor, SerializedImageData), +} + +#[derive(Debug, Deserialize, Serialize)] +/// Serialized `ImageData`. It contains IPC byte channel receiver to prevent from loading bytes too +/// slow. +pub enum SerializedImageData { + /// A simple series of bytes, provided by the embedding and owned by WebRender. + /// The format is stored out-of-band, currently in ImageDescriptor. + Raw(ipc::IpcBytesReceiver), + /// An image owned by the embedding, and referenced by WebRender. This may + /// take the form of a texture or a heap-allocated buffer. + External(ExternalImageData), +} + +impl SerializedImageData { + /// Convert to ``ImageData`. + pub fn to_image_data(&self) -> Result<ImageData, ipc::IpcError> { + match self { + SerializedImageData::Raw(rx) => rx.recv().map(|data| ImageData::new(data)), + SerializedImageData::External(image) => Ok(ImageData::External(image.clone())), + } + } +} diff --git a/components/shared/script/script_msg.rs b/components/shared/script/script_msg.rs new file mode 100644 index 00000000000..42be2685ccf --- /dev/null +++ b/components/shared/script/script_msg.rs @@ -0,0 +1,493 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::collections::{HashMap, VecDeque}; +use std::fmt; + +use canvas_traits::canvas::{CanvasId, CanvasMsg}; +use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId}; +use embedder_traits::{EmbedderMsg, MediaSessionEvent}; +use euclid::default::Size2D as UntypedSize2D; +use euclid::Size2D; +use gfx_traits::Epoch; +use ipc_channel::ipc::{IpcReceiver, IpcSender}; +use msg::constellation_msg::{ + BroadcastChannelRouterId, BrowsingContextId, HistoryStateId, MessagePortId, + MessagePortRouterId, PipelineId, ServiceWorkerId, ServiceWorkerRegistrationId, + TopLevelBrowsingContextId, TraversalDirection, +}; +use net_traits::request::RequestBuilder; +use net_traits::storage_thread::StorageType; +use net_traits::CoreResourceMsg; +use serde::{Deserialize, Serialize}; +use servo_url::{ImmutableOrigin, ServoUrl}; +use smallvec::SmallVec; +use style_traits::CSSPixel; +use webgpu::{wgpu, WebGPU, WebGPUResponseResult}; +use webrender_api::units::{DeviceIntPoint, DeviceIntSize}; + +use crate::{ + AnimationState, AuxiliaryBrowsingContextLoadInfo, BroadcastMsg, DocumentState, + IFrameLoadInfoWithData, LayoutControlMsg, LoadData, MessagePortMsg, PortMessageTask, + StructuredSerializedData, WindowSizeType, WorkerGlobalScopeInit, WorkerScriptLoadOrigin, +}; + +/// An iframe sizing operation. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct IFrameSizeMsg { + /// The child browsing context for this iframe. + pub browsing_context_id: BrowsingContextId, + /// The size of the iframe. + pub size: Size2D<f32, CSSPixel>, + /// The kind of sizing operation. + pub type_: WindowSizeType, +} + +/// Messages from the layout to the constellation. +#[derive(Deserialize, Serialize)] +pub enum LayoutMsg { + /// Inform the constellation of the size of the iframe's viewport. + IFrameSizes(Vec<IFrameSizeMsg>), + /// Requests that the constellation inform the compositor that it needs to record + /// the time when the frame with the given ID (epoch) is painted. + PendingPaintMetric(PipelineId, Epoch), +} + +impl fmt::Debug for LayoutMsg { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + use self::LayoutMsg::*; + let variant = match *self { + IFrameSizes(..) => "IFrameSizes", + PendingPaintMetric(..) => "PendingPaintMetric", + }; + write!(formatter, "LayoutMsg::{}", variant) + } +} + +/// Whether a DOM event was prevented by web content +#[derive(Debug, Deserialize, Serialize)] +pub enum EventResult { + /// Allowed by web content + DefaultAllowed, + /// Prevented by web content + DefaultPrevented, +} + +/// A log entry reported to the constellation +/// We don't report all log entries, just serious ones. +/// We need a separate type for this because `LogLevel` isn't serializable. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum LogEntry { + /// Panic, with a reason and backtrace + Panic(String, String), + /// Error, with a reason + Error(String), + /// warning, with a reason + Warn(String), +} + +/// https://html.spec.whatwg.org/multipage/#replacement-enabled +#[derive(Debug, Deserialize, Serialize)] +pub enum HistoryEntryReplacement { + /// Traverse the history with replacement enabled. + Enabled, + /// Traverse the history with replacement disabled. + Disabled, +} + +/// Messages from the script to the constellation. +#[derive(Deserialize, Serialize)] +pub enum ScriptMsg { + /// Request to complete the transfer of a set of ports to a router. + CompleteMessagePortTransfer(MessagePortRouterId, Vec<MessagePortId>), + /// The results of attempting to complete the transfer of a batch of ports. + MessagePortTransferResult( + /* The router whose transfer of ports succeeded, if any */ + Option<MessagePortRouterId>, + /* The ids of ports transferred successfully */ + Vec<MessagePortId>, + /* The ids, and buffers, of ports whose transfer failed */ + HashMap<MessagePortId, VecDeque<PortMessageTask>>, + ), + /// A new message-port was created or transferred, with corresponding control-sender. + NewMessagePort(MessagePortRouterId, MessagePortId), + /// A global has started managing message-ports + NewMessagePortRouter(MessagePortRouterId, IpcSender<MessagePortMsg>), + /// A global has stopped managing message-ports + RemoveMessagePortRouter(MessagePortRouterId), + /// A task requires re-routing to an already shipped message-port. + RerouteMessagePort(MessagePortId, PortMessageTask), + /// A message-port was shipped, let the entangled port know. + MessagePortShipped(MessagePortId), + /// A message-port has been discarded by script. + RemoveMessagePort(MessagePortId), + /// Entangle two message-ports. + 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. + ForwardToEmbedder(EmbedderMsg), + /// Requests are sent to constellation and fetches are checked manually + /// for cross-origin loads + InitiateNavigateRequest(RequestBuilder, /* cancellation_chan */ IpcReceiver<()>), + /// Broadcast a storage event to every same-origin pipeline. + /// The strings are key, old value and new value. + BroadcastStorageEvent( + StorageType, + ServoUrl, + Option<String>, + Option<String>, + Option<String>, + ), + /// Indicates whether this pipeline is currently running animations. + ChangeRunningAnimationsState(AnimationState), + /// Requests that a new 2D canvas thread be created. (This is done in the constellation because + /// 2D canvases may use the GPU and we don't want to give untrusted content access to the GPU.) + CreateCanvasPaintThread( + UntypedSize2D<u64>, + IpcSender<(IpcSender<CanvasMsg>, CanvasId)>, + ), + /// Notifies the constellation that this frame has received focus. + Focus, + /// Get the top-level browsing context info for a given browsing context. + GetTopForBrowsingContext( + BrowsingContextId, + IpcSender<Option<TopLevelBrowsingContextId>>, + ), + /// Get the browsing context id of the browsing context in which pipeline is + /// embedded and the parent pipeline id of that browsing context. + GetBrowsingContextInfo( + PipelineId, + IpcSender<Option<(BrowsingContextId, Option<PipelineId>)>>, + ), + /// Get the nth child browsing context ID for a given browsing context, sorted in tree order. + GetChildBrowsingContextId( + BrowsingContextId, + usize, + IpcSender<Option<BrowsingContextId>>, + ), + /// All pending loads are complete, and the `load` event for this pipeline + /// has been dispatched. + LoadComplete, + /// A new load has been requested, with an option to replace the current entry once loaded + /// instead of adding a new entry. + LoadUrl(LoadData, HistoryEntryReplacement), + /// Abort loading after sending a LoadUrl message. + AbortLoadUrl, + /// Post a message to the currently active window of a given browsing context. + PostMessage { + /// The target of the posted message. + target: BrowsingContextId, + /// The source of the posted message. + source: PipelineId, + /// The expected origin of the target. + target_origin: Option<ImmutableOrigin>, + /// The source origin of the message. + /// https://html.spec.whatwg.org/multipage/#dom-messageevent-origin + source_origin: ImmutableOrigin, + /// The data to be posted. + data: StructuredSerializedData, + }, + /// Inform the constellation that a fragment was navigated to and whether or not it was a replacement navigation. + NavigatedToFragment(ServoUrl, HistoryEntryReplacement), + /// HTMLIFrameElement Forward or Back traversal. + TraverseHistory(TraversalDirection), + /// Inform the constellation of a pushed history state. + PushHistoryState(HistoryStateId, ServoUrl), + /// Inform the constellation of a replaced history state. + ReplaceHistoryState(HistoryStateId, ServoUrl), + /// Gets the length of the joint session history from the constellation. + JointSessionHistoryLength(IpcSender<u32>), + /// Notification that this iframe should be removed. + /// Returns a list of pipelines which were closed. + RemoveIFrame(BrowsingContextId, IpcSender<Vec<PipelineId>>), + /// Notifies constellation that an iframe's visibility has been changed. + VisibilityChangeComplete(bool), + /// A load has been requested in an IFrame. + ScriptLoadedURLInIFrame(IFrameLoadInfoWithData), + /// A load of the initial `about:blank` has been completed in an IFrame. + ScriptNewIFrame(IFrameLoadInfoWithData, IpcSender<LayoutControlMsg>), + /// Script has opened a new auxiliary browsing context. + ScriptNewAuxiliary( + AuxiliaryBrowsingContextLoadInfo, + IpcSender<LayoutControlMsg>, + ), + /// Mark a new document as active + ActivateDocument, + /// Set the document state for a pipeline (used by screenshot / reftests) + SetDocumentState(DocumentState), + /// Update the pipeline Url, which can change after redirections. + SetFinalUrl(ServoUrl), + /// Script has handled a touch event, and either prevented or allowed default actions. + TouchEventProcessed(EventResult), + /// A log entry, with the top-level browsing context id and thread name + LogEntry(Option<String>, LogEntry), + /// Discard the document. + DiscardDocument, + /// Discard the browsing context. + DiscardTopLevelBrowsingContext, + /// Notifies the constellation that this pipeline has exited. + PipelineExited, + /// Send messages from postMessage calls from serviceworker + /// to constellation for storing in service worker manager + ForwardDOMMessage(DOMMessage, ServoUrl), + /// https://w3c.github.io/ServiceWorker/#schedule-job-algorithm. + ScheduleJob(Job), + /// Get Window Informations size and position + GetClientWindow(IpcSender<(DeviceIntSize, DeviceIntPoint)>), + /// Get the screen size (pixel) + GetScreenSize(IpcSender<DeviceIntSize>), + /// Get the available screen size (pixel) + GetScreenAvailSize(IpcSender<DeviceIntSize>), + /// Notifies the constellation about media session events + /// (i.e. when there is metadata for the active media session, playback state changes...). + MediaSessionEvent(PipelineId, MediaSessionEvent), + /// Create a WebGPU Adapter instance + RequestAdapter( + IpcSender<WebGPUResponseResult>, + wgpu::instance::RequestAdapterOptions, + SmallVec<[wgpu::id::AdapterId; 4]>, + ), + /// Get WebGPU channel + GetWebGPUChan(IpcSender<WebGPU>), + /// Notify the constellation of a pipeline's document's title. + TitleChanged(PipelineId, String), +} + +impl fmt::Debug for ScriptMsg { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + use self::ScriptMsg::*; + let variant = match *self { + CompleteMessagePortTransfer(..) => "CompleteMessagePortTransfer", + MessagePortTransferResult(..) => "MessagePortTransferResult", + NewMessagePortRouter(..) => "NewMessagePortRouter", + RemoveMessagePortRouter(..) => "RemoveMessagePortRouter", + NewMessagePort(..) => "NewMessagePort", + RerouteMessagePort(..) => "RerouteMessagePort", + RemoveMessagePort(..) => "RemoveMessagePort", + MessagePortShipped(..) => "MessagePortShipped", + EntanglePorts(..) => "EntanglePorts", + NewBroadcastChannelRouter(..) => "NewBroadcastChannelRouter", + RemoveBroadcastChannelRouter(..) => "RemoveBroadcastChannelRouter", + RemoveBroadcastChannelNameInRouter(..) => "RemoveBroadcastChannelNameInRouter", + NewBroadcastChannelNameInRouter(..) => "NewBroadcastChannelNameInRouter", + ScheduleBroadcast(..) => "ScheduleBroadcast", + ForwardToEmbedder(..) => "ForwardToEmbedder", + InitiateNavigateRequest(..) => "InitiateNavigateRequest", + BroadcastStorageEvent(..) => "BroadcastStorageEvent", + ChangeRunningAnimationsState(..) => "ChangeRunningAnimationsState", + CreateCanvasPaintThread(..) => "CreateCanvasPaintThread", + Focus => "Focus", + GetBrowsingContextInfo(..) => "GetBrowsingContextInfo", + GetTopForBrowsingContext(..) => "GetParentBrowsingContext", + GetChildBrowsingContextId(..) => "GetChildBrowsingContextId", + LoadComplete => "LoadComplete", + LoadUrl(..) => "LoadUrl", + AbortLoadUrl => "AbortLoadUrl", + PostMessage { .. } => "PostMessage", + NavigatedToFragment(..) => "NavigatedToFragment", + TraverseHistory(..) => "TraverseHistory", + PushHistoryState(..) => "PushHistoryState", + ReplaceHistoryState(..) => "ReplaceHistoryState", + JointSessionHistoryLength(..) => "JointSessionHistoryLength", + RemoveIFrame(..) => "RemoveIFrame", + VisibilityChangeComplete(..) => "VisibilityChangeComplete", + ScriptLoadedURLInIFrame(..) => "ScriptLoadedURLInIFrame", + ScriptNewIFrame(..) => "ScriptNewIFrame", + ScriptNewAuxiliary(..) => "ScriptNewAuxiliary", + ActivateDocument => "ActivateDocument", + SetDocumentState(..) => "SetDocumentState", + SetFinalUrl(..) => "SetFinalUrl", + TouchEventProcessed(..) => "TouchEventProcessed", + LogEntry(..) => "LogEntry", + DiscardDocument => "DiscardDocument", + DiscardTopLevelBrowsingContext => "DiscardTopLevelBrowsingContext", + PipelineExited => "PipelineExited", + ForwardDOMMessage(..) => "ForwardDOMMessage", + ScheduleJob(..) => "ScheduleJob", + GetClientWindow(..) => "GetClientWindow", + GetScreenSize(..) => "GetScreenSize", + GetScreenAvailSize(..) => "GetScreenAvailSize", + MediaSessionEvent(..) => "MediaSessionEvent", + RequestAdapter(..) => "RequestAdapter", + GetWebGPUChan(..) => "GetWebGPUChan", + TitleChanged(..) => "TitleChanged", + }; + write!(formatter, "ScriptMsg::{}", variant) + } +} + +/// Entities required to spawn service workers +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ScopeThings { + /// script resource url + pub script_url: ServoUrl, + /// network load origin of the resource + pub worker_load_origin: WorkerScriptLoadOrigin, + /// base resources required to create worker global scopes + pub init: WorkerGlobalScopeInit, + /// the port to receive devtools message from + pub devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, + /// service worker id + pub worker_id: WorkerId, +} + +/// Message that gets passed to service worker scope on postMessage +#[derive(Debug, Deserialize, Serialize)] +pub struct DOMMessage { + /// The origin of the message + pub origin: ImmutableOrigin, + /// The payload of the message + pub data: StructuredSerializedData, +} + +/// Channels to allow service worker manager to communicate with constellation and resource thread +#[derive(Deserialize, Serialize)] +pub struct SWManagerSenders { + /// Sender of messages to the constellation. + pub swmanager_sender: IpcSender<SWManagerMsg>, + /// Sender for communicating with resource thread. + pub resource_sender: IpcSender<CoreResourceMsg>, + /// Sender of messages to the manager. + pub own_sender: IpcSender<ServiceWorkerMsg>, + /// Receiver of messages from the constellation. + pub receiver: IpcReceiver<ServiceWorkerMsg>, +} + +/// Messages sent to Service Worker Manager thread +#[derive(Debug, Deserialize, Serialize)] +pub enum ServiceWorkerMsg { + /// Timeout message sent by active service workers + Timeout(ServoUrl), + /// Message sent by constellation to forward to a running service worker + ForwardDOMMessage(DOMMessage, ServoUrl), + /// https://w3c.github.io/ServiceWorker/#schedule-job-algorithm + ScheduleJob(Job), + /// Exit the service worker manager + Exit, +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +/// https://w3c.github.io/ServiceWorker/#dfn-job-type +pub enum JobType { + /// <https://w3c.github.io/ServiceWorker/#register> + Register, + /// <https://w3c.github.io/ServiceWorker/#unregister-algorithm> + Unregister, + /// <https://w3c.github.io/ServiceWorker/#update-algorithm + Update, +} + +#[derive(Debug, Deserialize, Serialize)] +/// The kind of error the job promise should be rejected with. +pub enum JobError { + /// https://w3c.github.io/ServiceWorker/#reject-job-promise + TypeError, + /// https://w3c.github.io/ServiceWorker/#reject-job-promise + SecurityError, +} + +#[derive(Debug, Deserialize, Serialize)] +/// Messages sent from Job algorithms steps running in the SW manager, +/// in order to resolve or reject the job promise. +pub enum JobResult { + /// https://w3c.github.io/ServiceWorker/#reject-job-promise + RejectPromise(JobError), + /// https://w3c.github.io/ServiceWorker/#resolve-job-promise + ResolvePromise(Job, JobResultValue), +} + +#[derive(Debug, Deserialize, Serialize)] +/// Jobs are resolved with the help of various values. +pub enum JobResultValue { + /// Data representing a serviceworker registration. + Registration { + /// The Id of the registration. + id: ServiceWorkerRegistrationId, + /// The installing worker, if any. + installing_worker: Option<ServiceWorkerId>, + /// The waiting worker, if any. + waiting_worker: Option<ServiceWorkerId>, + /// The active worker, if any. + active_worker: Option<ServiceWorkerId>, + }, +} + +#[derive(Debug, Deserialize, Serialize)] +/// https://w3c.github.io/ServiceWorker/#dfn-job +pub struct Job { + /// <https://w3c.github.io/ServiceWorker/#dfn-job-type> + pub job_type: JobType, + /// <https://w3c.github.io/ServiceWorker/#dfn-job-scope-url> + pub scope_url: ServoUrl, + /// <https://w3c.github.io/ServiceWorker/#dfn-job-script-url> + pub script_url: ServoUrl, + /// <https://w3c.github.io/ServiceWorker/#dfn-job-client> + pub client: IpcSender<JobResult>, + /// <https://w3c.github.io/ServiceWorker/#job-referrer> + pub referrer: ServoUrl, + /// Various data needed to process job. + pub scope_things: Option<ScopeThings>, +} + +impl Job { + /// https://w3c.github.io/ServiceWorker/#create-job-algorithm + pub fn create_job( + job_type: JobType, + scope_url: ServoUrl, + script_url: ServoUrl, + client: IpcSender<JobResult>, + referrer: ServoUrl, + scope_things: Option<ScopeThings>, + ) -> Job { + Job { + job_type, + scope_url, + script_url, + client, + referrer, + scope_things, + } + } +} + +impl PartialEq for Job { + /// Equality criteria as described in https://w3c.github.io/ServiceWorker/#dfn-job-equivalent + fn eq(&self, other: &Self) -> bool { + // TODO: match on job type, take worker type and `update_via_cache_mode` into account. + let same_job = self.job_type == other.job_type; + if same_job { + match self.job_type { + JobType::Register | JobType::Update => { + self.scope_url == other.scope_url && self.script_url == other.script_url + }, + JobType::Unregister => self.scope_url == other.scope_url, + } + } else { + false + } + } +} + +/// Messages outgoing from the Service Worker Manager thread to constellation +#[derive(Debug, Deserialize, Serialize)] +pub enum SWManagerMsg { + /// Placeholder to keep the enum, + /// as it will be needed when implementing + /// https://github.com/servo/servo/issues/24660 + PostMessageToClient, +} diff --git a/components/shared/script/serializable.rs b/components/shared/script/serializable.rs new file mode 100644 index 00000000000..1b19201a73b --- /dev/null +++ b/components/shared/script/serializable.rs @@ -0,0 +1,151 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! This module contains implementations in script that are serializable, +//! as per https://html.spec.whatwg.org/multipage/#serializable-objects. +//! The implementations are here instead of in script +//! so that the other modules involved in the serialization don't have +//! to depend on script. + +use std::cell::RefCell; +use std::path::PathBuf; + +use malloc_size_of_derive::MallocSizeOf; +use msg::constellation_msg::BlobId; +use net_traits::filemanager_thread::RelativePos; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// File-based blob +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct FileBlob { + #[ignore_malloc_size_of = "Uuid are hard(not really)"] + id: Uuid, + #[ignore_malloc_size_of = "PathBuf are hard"] + name: Option<PathBuf>, + cache: RefCell<Option<Vec<u8>>>, + size: u64, +} + +impl FileBlob { + /// Create a new file blob. + pub fn new(id: Uuid, name: Option<PathBuf>, cache: Option<Vec<u8>>, size: u64) -> FileBlob { + FileBlob { + id, + name, + cache: RefCell::new(cache), + size, + } + } + + /// Get the size of the file. + pub fn get_size(&self) -> u64 { + self.size.clone() + } + + /// Get the cached file data, if any. + pub fn get_cache(&self) -> Option<Vec<u8>> { + self.cache.borrow().clone() + } + + /// Cache data. + pub fn cache_bytes(&self, bytes: Vec<u8>) { + *self.cache.borrow_mut() = Some(bytes); + } + + /// Get the file id. + pub fn get_id(&self) -> Uuid { + self.id.clone() + } +} + +/// The data backing a DOM Blob. +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub struct BlobImpl { + /// UUID of the blob. + blob_id: BlobId, + /// Content-type string + type_string: String, + /// Blob data-type. + blob_data: BlobData, + /// Sliced blobs referring to this one. + slices: Vec<BlobId>, +} + +/// Different backends of Blob +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +pub enum BlobData { + /// File-based blob, whose content lives in the net process + File(FileBlob), + /// Memory-based blob, whose content lives in the script process + Memory(Vec<u8>), + /// Sliced blob, including parent blob-id and + /// relative positions of current slicing range, + /// IMPORTANT: The depth of tree is only two, i.e. the parent Blob must be + /// either File-based or Memory-based + Sliced(BlobId, RelativePos), +} + +impl BlobImpl { + /// Construct memory-backed BlobImpl + pub fn new_from_bytes(bytes: Vec<u8>, type_string: String) -> BlobImpl { + let blob_id = BlobId::new(); + let blob_data = BlobData::Memory(bytes); + BlobImpl { + blob_id, + type_string, + blob_data, + slices: vec![], + } + } + + /// Construct file-backed BlobImpl from File ID + pub fn new_from_file(file_id: Uuid, name: PathBuf, size: u64, type_string: String) -> BlobImpl { + let blob_id = BlobId::new(); + let blob_data = BlobData::File(FileBlob { + id: file_id, + name: Some(name), + cache: RefCell::new(None), + size: size, + }); + BlobImpl { + blob_id, + type_string, + blob_data, + slices: vec![], + } + } + + /// Construct a BlobImpl from a slice of a parent. + pub fn new_sliced(rel_pos: RelativePos, parent: BlobId, type_string: String) -> BlobImpl { + let blob_id = BlobId::new(); + let blob_data = BlobData::Sliced(parent, rel_pos); + BlobImpl { + blob_id, + type_string, + blob_data, + slices: vec![], + } + } + + /// Get a clone of the blob-id + pub fn blob_id(&self) -> BlobId { + self.blob_id.clone() + } + + /// Get a clone of the type-string + pub fn type_string(&self) -> String { + self.type_string.clone() + } + + /// Get a mutable ref to the data + pub fn blob_data(&self) -> &BlobData { + &self.blob_data + } + + /// Get a mutable ref to the data + pub fn blob_data_mut(&mut self) -> &mut BlobData { + &mut self.blob_data + } +} diff --git a/components/shared/script/tests/compositor.rs b/components/shared/script/tests/compositor.rs new file mode 100644 index 00000000000..a6685c3a76c --- /dev/null +++ b/components/shared/script/tests/compositor.rs @@ -0,0 +1,179 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use euclid::Size2D; +use script_traits::compositor::{ScrollTree, ScrollTreeNodeId, ScrollableNodeInfo}; +use webrender_api::units::LayoutVector2D; +use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation, ScrollSensitivity, SpatialId}; + +fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId { + let pipeline_id = PipelineId(0, 0); + let num_nodes = tree.nodes.len(); + let parent = if num_nodes > 0 { + Some(ScrollTreeNodeId { + index: num_nodes - 1, + spatial_id: SpatialId::new(num_nodes - 1, pipeline_id), + }) + } else { + None + }; + + tree.add_scroll_tree_node( + parent.as_ref(), + SpatialId::new(num_nodes, pipeline_id), + Some(ScrollableNodeInfo { + external_id: ExternalScrollId(num_nodes as u64, pipeline_id), + scrollable_size: Size2D::new(100.0, 100.0), + scroll_sensitivity: ScrollSensitivity::ScriptAndInputEvents, + offset: LayoutVector2D::zero(), + }), + ) +} + +#[test] +fn test_scroll_tree_simple_scroll() { + let mut scroll_tree = ScrollTree::default(); + let pipeline_id = PipelineId(0, 0); + let id = add_mock_scroll_node(&mut scroll_tree); + + let (scrolled_id, offset) = scroll_tree + .scroll_node_or_ancestor( + &id, + ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)), + ) + .unwrap(); + let expected_offset = LayoutVector2D::new(-20.0, -40.0); + assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(offset, expected_offset); + assert_eq!(scroll_tree.get_node(&id).offset(), Some(expected_offset)); + + let (scrolled_id, offset) = scroll_tree + .scroll_node_or_ancestor(&id, ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0))) + .unwrap(); + let expected_offset = LayoutVector2D::new(0.0, 0.0); + assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(offset, expected_offset); + assert_eq!(scroll_tree.get_node(&id).offset(), Some(expected_offset)); + + // Scroll offsets must be negative. + let result = scroll_tree + .scroll_node_or_ancestor(&id, ScrollLocation::Delta(LayoutVector2D::new(20.0, 40.0))); + assert!(result.is_none()); + assert_eq!( + scroll_tree.get_node(&id).offset(), + Some(LayoutVector2D::new(0.0, 0.0)) + ); +} + +#[test] +fn test_scroll_tree_simple_scroll_chaining() { + let mut scroll_tree = ScrollTree::default(); + + let pipeline_id = PipelineId(0, 0); + let parent_id = add_mock_scroll_node(&mut scroll_tree); + let unscrollable_child_id = + scroll_tree.add_scroll_tree_node(Some(&parent_id), SpatialId::new(1, pipeline_id), None); + + let (scrolled_id, offset) = scroll_tree + .scroll_node_or_ancestor( + &unscrollable_child_id, + ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)), + ) + .unwrap(); + let expected_offset = LayoutVector2D::new(-20.0, -40.0); + assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(offset, expected_offset); + assert_eq!( + scroll_tree.get_node(&parent_id).offset(), + Some(expected_offset) + ); + + let (scrolled_id, offset) = scroll_tree + .scroll_node_or_ancestor( + &unscrollable_child_id, + ScrollLocation::Delta(LayoutVector2D::new(-10.0, -15.0)), + ) + .unwrap(); + let expected_offset = LayoutVector2D::new(-30.0, -55.0); + assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(offset, expected_offset); + assert_eq!( + scroll_tree.get_node(&parent_id).offset(), + Some(expected_offset) + ); + assert_eq!(scroll_tree.get_node(&unscrollable_child_id).offset(), None); +} + +#[test] +fn test_scroll_tree_chain_when_at_extent() { + let mut scroll_tree = ScrollTree::default(); + + let pipeline_id = PipelineId(0, 0); + let parent_id = add_mock_scroll_node(&mut scroll_tree); + let child_id = add_mock_scroll_node(&mut scroll_tree); + + let (scrolled_id, offset) = scroll_tree + .scroll_node_or_ancestor(&child_id, ScrollLocation::End) + .unwrap(); + + let expected_offset = LayoutVector2D::new(0.0, -100.0); + assert_eq!(scrolled_id, ExternalScrollId(1, pipeline_id)); + assert_eq!(offset, expected_offset); + assert_eq!( + scroll_tree.get_node(&child_id).offset(), + Some(expected_offset) + ); + + // The parent will have scrolled because the child is already at the extent + // of its scroll area in the y axis. + let (scrolled_id, offset) = scroll_tree + .scroll_node_or_ancestor( + &child_id, + ScrollLocation::Delta(LayoutVector2D::new(0.0, -10.0)), + ) + .unwrap(); + let expected_offset = LayoutVector2D::new(0.0, -10.0); + assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(offset, expected_offset); + assert_eq!( + scroll_tree.get_node(&parent_id).offset(), + Some(expected_offset) + ); +} + +#[test] +fn test_scroll_tree_chain_through_overflow_hidden() { + let mut scroll_tree = ScrollTree::default(); + + // Create a tree with a scrollable leaf, but make its `scroll_sensitivity` + // reflect `overflow: hidden` ie not responsive to non-script scroll events. + let pipeline_id = PipelineId(0, 0); + let parent_id = add_mock_scroll_node(&mut scroll_tree); + let overflow_hidden_id = add_mock_scroll_node(&mut scroll_tree); + scroll_tree + .get_node_mut(&overflow_hidden_id) + .scroll_info + .as_mut() + .map(|mut info| { + info.scroll_sensitivity = ScrollSensitivity::Script; + }); + + let (scrolled_id, offset) = scroll_tree + .scroll_node_or_ancestor( + &overflow_hidden_id, + ScrollLocation::Delta(LayoutVector2D::new(-20.0, -40.0)), + ) + .unwrap(); + let expected_offset = LayoutVector2D::new(-20.0, -40.0); + assert_eq!(scrolled_id, ExternalScrollId(0, pipeline_id)); + assert_eq!(offset, expected_offset); + assert_eq!( + scroll_tree.get_node(&parent_id).offset(), + Some(expected_offset) + ); + assert_eq!( + scroll_tree.get_node(&overflow_hidden_id).offset(), + Some(LayoutVector2D::new(0.0, 0.0)) + ); +} diff --git a/components/shared/script/transferable.rs b/components/shared/script/transferable.rs new file mode 100644 index 00000000000..1b579899b61 --- /dev/null +++ b/components/shared/script/transferable.rs @@ -0,0 +1,164 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! This module contains implementations in script that are transferable. +//! The implementations are here instead of in script +//! so that the other modules involved in the transfer don't have +//! to depend on script. + +use std::collections::VecDeque; + +use malloc_size_of_derive::MallocSizeOf; +use msg::constellation_msg::MessagePortId; +use serde::{Deserialize, Serialize}; + +use crate::PortMessageTask; + +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +enum MessagePortState { + /// <https://html.spec.whatwg.org/multipage/#detached> + Detached, + /// <https://html.spec.whatwg.org/multipage/#port-message-queue> + /// The message-queue of this port is enabled, + /// the boolean represents awaiting completion of a transfer. + Enabled(bool), + /// <https://html.spec.whatwg.org/multipage/#port-message-queue> + /// The message-queue of this port is disabled, + /// the boolean represents awaiting completion of a transfer. + Disabled(bool), +} + +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] +/// The data and logic backing the DOM managed MessagePort. +pub struct MessagePortImpl { + /// The current state of the port. + state: MessagePortState, + + /// <https://html.spec.whatwg.org/multipage/#entangle> + entangled_port: Option<MessagePortId>, + + /// <https://html.spec.whatwg.org/multipage/#port-message-queue> + message_buffer: Option<VecDeque<PortMessageTask>>, + + /// The UUID of this port. + message_port_id: MessagePortId, +} + +impl MessagePortImpl { + /// Create a new messageport impl. + pub fn new(port_id: MessagePortId) -> MessagePortImpl { + MessagePortImpl { + state: MessagePortState::Disabled(false), + entangled_port: None, + message_buffer: None, + message_port_id: port_id, + } + } + + /// Get the Id. + pub fn message_port_id(&self) -> &MessagePortId { + &self.message_port_id + } + + /// Maybe get the Id of the entangled port. + pub fn entangled_port_id(&self) -> Option<MessagePortId> { + self.entangled_port.clone() + } + + /// Entanged this port with another. + pub fn entangle(&mut self, other_id: MessagePortId) { + self.entangled_port = Some(other_id); + } + + /// Is this port enabled? + pub fn enabled(&self) -> bool { + match self.state { + MessagePortState::Enabled(_) => true, + _ => false, + } + } + + /// Mark this port as having been shipped. + /// <https://html.spec.whatwg.org/multipage/#has-been-shipped> + pub fn set_has_been_shipped(&mut self) { + match self.state { + MessagePortState::Detached => { + panic!("Messageport set_has_been_shipped called in detached state") + }, + MessagePortState::Enabled(_) => self.state = MessagePortState::Enabled(true), + MessagePortState::Disabled(_) => self.state = MessagePortState::Disabled(true), + } + } + + /// Handle the completion of the transfer, + /// this is data received from the constellation. + pub fn complete_transfer(&mut self, mut tasks: VecDeque<PortMessageTask>) { + match self.state { + MessagePortState::Detached => return, + MessagePortState::Enabled(_) => self.state = MessagePortState::Enabled(false), + MessagePortState::Disabled(_) => self.state = MessagePortState::Disabled(false), + } + + // Note: these are the tasks that were buffered while the transfer was ongoing, + // hence they need to execute first. + // The global will call `start` if we are enabled, + // which will add tasks on the event-loop to dispatch incoming messages. + match self.message_buffer { + Some(ref mut incoming_buffer) => { + while let Some(task) = tasks.pop_back() { + incoming_buffer.push_front(task); + } + }, + None => self.message_buffer = Some(tasks), + } + } + + /// A message was received from our entangled port, + /// returns an optional task to be dispatched. + pub fn handle_incoming(&mut self, task: PortMessageTask) -> Option<PortMessageTask> { + let should_dispatch = match self.state { + MessagePortState::Detached => return None, + MessagePortState::Enabled(in_transfer) => !in_transfer, + MessagePortState::Disabled(_) => false, + }; + + if should_dispatch { + Some(task) + } else { + match self.message_buffer { + Some(ref mut buffer) => { + buffer.push_back(task); + }, + None => { + let mut queue = VecDeque::new(); + queue.push_back(task); + self.message_buffer = Some(queue); + }, + } + None + } + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start> + /// returns an optional queue of tasks that were buffered while the port was disabled. + pub fn start(&mut self) -> Option<VecDeque<PortMessageTask>> { + match self.state { + MessagePortState::Detached => return None, + MessagePortState::Enabled(_) => {}, + MessagePortState::Disabled(in_transfer) => { + self.state = MessagePortState::Enabled(in_transfer); + }, + } + if let MessagePortState::Enabled(true) = self.state { + return None; + } + self.message_buffer.take() + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-close> + pub fn close(&mut self) { + // Step 1 + self.state = MessagePortState::Detached; + } +} diff --git a/components/shared/script/webdriver_msg.rs b/components/shared/script/webdriver_msg.rs new file mode 100644 index 00000000000..d72d93b61cb --- /dev/null +++ b/components/shared/script/webdriver_msg.rs @@ -0,0 +1,138 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#![allow(missing_docs)] + +use std::collections::HashMap; + +use cookie::Cookie; +use euclid::default::Rect; +use hyper_serde::Serde; +use ipc_channel::ipc::IpcSender; +use msg::constellation_msg::BrowsingContextId; +use serde::{Deserialize, Serialize}; +use servo_url::ServoUrl; +use webdriver::common::{WebElement, WebFrame, WebWindow}; +use webdriver::error::ErrorStatus; + +#[derive(Debug, Deserialize, Serialize)] +pub enum WebDriverScriptCommand { + AddCookie( + #[serde( + deserialize_with = "::hyper_serde::deserialize", + serialize_with = "::hyper_serde::serialize" + )] + Cookie<'static>, + IpcSender<Result<(), WebDriverCookieError>>, + ), + DeleteCookies(IpcSender<Result<(), ErrorStatus>>), + ExecuteScript(String, IpcSender<WebDriverJSResult>), + ExecuteAsyncScript(String, IpcSender<WebDriverJSResult>), + FindElementCSS(String, IpcSender<Result<Option<String>, ErrorStatus>>), + FindElementLinkText(String, bool, IpcSender<Result<Option<String>, ErrorStatus>>), + FindElementTagName(String, IpcSender<Result<Option<String>, ErrorStatus>>), + FindElementsCSS(String, IpcSender<Result<Vec<String>, ErrorStatus>>), + FindElementsLinkText(String, bool, IpcSender<Result<Vec<String>, ErrorStatus>>), + FindElementsTagName(String, IpcSender<Result<Vec<String>, ErrorStatus>>), + FindElementElementCSS( + String, + String, + IpcSender<Result<Option<String>, ErrorStatus>>, + ), + FindElementElementLinkText( + String, + String, + bool, + IpcSender<Result<Option<String>, ErrorStatus>>, + ), + FindElementElementTagName( + String, + String, + IpcSender<Result<Option<String>, ErrorStatus>>, + ), + FindElementElementsCSS(String, String, IpcSender<Result<Vec<String>, ErrorStatus>>), + FindElementElementsLinkText( + String, + String, + bool, + IpcSender<Result<Vec<String>, ErrorStatus>>, + ), + FindElementElementsTagName(String, String, IpcSender<Result<Vec<String>, ErrorStatus>>), + FocusElement(String, IpcSender<Result<(), ErrorStatus>>), + ElementClick(String, IpcSender<Result<Option<String>, ErrorStatus>>), + GetActiveElement(IpcSender<Option<String>>), + GetCookie(String, IpcSender<Vec<Serde<Cookie<'static>>>>), + GetCookies(IpcSender<Vec<Serde<Cookie<'static>>>>), + GetElementAttribute( + String, + String, + IpcSender<Result<Option<String>, ErrorStatus>>, + ), + GetElementProperty( + String, + String, + IpcSender<Result<WebDriverJSValue, ErrorStatus>>, + ), + GetElementCSS(String, String, IpcSender<Result<String, ErrorStatus>>), + GetElementRect(String, IpcSender<Result<Rect<f64>, ErrorStatus>>), + GetElementTagName(String, IpcSender<Result<String, ErrorStatus>>), + GetElementText(String, IpcSender<Result<String, ErrorStatus>>), + GetElementInViewCenterPoint(String, IpcSender<Result<Option<(i64, i64)>, ErrorStatus>>), + GetBoundingClientRect(String, IpcSender<Result<Rect<f32>, ErrorStatus>>), + GetBrowsingContextId( + WebDriverFrameId, + IpcSender<Result<BrowsingContextId, ErrorStatus>>, + ), + GetUrl(IpcSender<ServoUrl>), + GetPageSource(IpcSender<Result<String, ErrorStatus>>), + IsEnabled(String, IpcSender<Result<bool, ErrorStatus>>), + IsSelected(String, IpcSender<Result<bool, ErrorStatus>>), + GetTitle(IpcSender<String>), +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum WebDriverCookieError { + InvalidDomain, + UnableToSetCookie, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum WebDriverJSValue { + Undefined, + Null, + Boolean(bool), + Number(f64), + String(String), + Element(WebElement), + Frame(WebFrame), + Window(WebWindow), + ArrayLike(Vec<WebDriverJSValue>), + Object(HashMap<String, WebDriverJSValue>), +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum WebDriverJSError { + /// Occurs when handler received an event message for a layout channel that is not + /// associated with the current script thread + BrowsingContextNotFound, + JSError, + StaleElementReference, + Timeout, + UnknownType, +} + +pub type WebDriverJSResult = Result<WebDriverJSValue, WebDriverJSError>; + +#[derive(Debug, Deserialize, Serialize)] +pub enum WebDriverFrameId { + Short(u16), + Element(String), + Parent, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum LoadStatus { + LoadComplete, + LoadTimeout, +} diff --git a/components/shared/script_layout/Cargo.toml b/components/shared/script_layout/Cargo.toml new file mode 100644 index 00000000000..553a773afac --- /dev/null +++ b/components/shared/script_layout/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "script_layout_interface" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "script_layout_interface" +path = "lib.rs" + +[dependencies] +app_units = { workspace = true } +atomic_refcell = { workspace = true } +canvas_traits = { workspace = true } +crossbeam-channel = { workspace = true } +euclid = { workspace = true } +fxhash = { workspace = true } +gfx_traits = { workspace = true } +html5ever = { workspace = true } +ipc-channel = { workspace = true } +libc = { workspace = true } +malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of_derive = { workspace = true } +metrics = { path = "../../metrics" } +msg = { workspace = true } +net_traits = { workspace = true } +parking_lot = { workspace = true } +profile_traits = { workspace = true } +range = { path = "../../range" } +script_traits = { workspace = true } +selectors = { path = "../../selectors", features = ["shmem"] } +servo_arc = { path = "../../servo_arc" } +servo_atoms = { path = "../../atoms" } +servo_url = { path = "../../url" } +style = { path = "../../style", features = ["servo"] } +style_traits = { workspace = true } +webrender_api = { git = "https://github.com/servo/webrender" } diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs new file mode 100644 index 00000000000..0db42229678 --- /dev/null +++ b/components/shared/script_layout/lib.rs @@ -0,0 +1,164 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! This module contains traits in script used generically in the rest of Servo. +//! The traits are here instead of in script so that these modules won't have +//! to depend on script. + +#![deny(unsafe_code)] + +pub mod message; +pub mod rpc; +pub mod wrapper_traits; + +use std::any::Any; +use std::sync::atomic::AtomicIsize; + +use atomic_refcell::AtomicRefCell; +use canvas_traits::canvas::{CanvasId, CanvasMsg}; +use ipc_channel::ipc::IpcSender; +use libc::c_void; +use malloc_size_of_derive::MallocSizeOf; +use net_traits::image_cache::PendingImageId; +use script_traits::UntrustedNodeAddress; +use servo_url::{ImmutableOrigin, ServoUrl}; +use style::data::ElementData; +use webrender_api::ImageKey; + +#[derive(MallocSizeOf)] +pub struct StyleData { + /// Data that the style system associates with a node. When the + /// style system is being used standalone, this is all that hangs + /// off the node. This must be first to permit the various + /// transmutations between ElementData and PersistentLayoutData. + #[ignore_malloc_size_of = "This probably should not be ignored"] + pub element_data: AtomicRefCell<ElementData>, + + /// Information needed during parallel traversals. + pub parallel: DomParallelInfo, +} + +impl StyleData { + pub fn new() -> Self { + Self { + element_data: AtomicRefCell::new(ElementData::default()), + parallel: DomParallelInfo::new(), + } + } +} + +pub type StyleAndOpaqueLayoutData = StyleAndGenericData<dyn Any + Send + Sync>; + +#[derive(MallocSizeOf)] +pub struct StyleAndGenericData<T> +where + T: ?Sized, +{ + /// The style data. + pub style_data: StyleData, + /// The opaque layout data. + #[ignore_malloc_size_of = "Trait objects are hard"] + pub generic_data: T, +} + +impl StyleAndOpaqueLayoutData { + #[inline] + pub fn new<T>(style_data: StyleData, layout_data: T) -> Box<Self> + where + T: Any + Send + Sync, + { + Box::new(StyleAndGenericData { + style_data, + generic_data: layout_data, + }) + } +} + +/// Information that we need stored in each DOM node. +#[derive(MallocSizeOf)] +pub struct DomParallelInfo { + /// The number of children remaining to process during bottom-up traversal. + pub children_to_process: AtomicIsize, +} + +impl DomParallelInfo { + pub fn new() -> DomParallelInfo { + DomParallelInfo { + children_to_process: AtomicIsize::new(0), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum LayoutNodeType { + Element(LayoutElementType), + Text, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum LayoutElementType { + Element, + HTMLBodyElement, + HTMLBRElement, + HTMLCanvasElement, + HTMLHtmlElement, + HTMLIFrameElement, + HTMLImageElement, + HTMLInputElement, + HTMLMediaElement, + HTMLObjectElement, + HTMLParagraphElement, + HTMLTableCellElement, + HTMLTableColElement, + HTMLTableElement, + HTMLTableRowElement, + HTMLTableSectionElement, + HTMLTextAreaElement, + SVGSVGElement, +} + +pub enum HTMLCanvasDataSource { + WebGL(ImageKey), + Image(Option<IpcSender<CanvasMsg>>), + WebGPU(ImageKey), +} + +pub struct HTMLCanvasData { + pub source: HTMLCanvasDataSource, + pub width: u32, + pub height: u32, + pub canvas_id: CanvasId, +} + +pub struct SVGSVGData { + pub width: u32, + pub height: u32, +} + +/// The address of a node known to be valid. These are sent from script to layout. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct TrustedNodeAddress(pub *const c_void); + +#[allow(unsafe_code)] +unsafe impl Send for TrustedNodeAddress {} + +/// Whether the pending image needs to be fetched or is waiting on an existing fetch. +pub enum PendingImageState { + Unrequested(ServoUrl), + PendingResponse, +} + +/// The data associated with an image that is not yet present in the image cache. +/// Used by the script thread to hold on to DOM elements that need to be repainted +/// when an image fetch is complete. +pub struct PendingImage { + pub state: PendingImageState, + pub node: UntrustedNodeAddress, + pub id: PendingImageId, + pub origin: ImmutableOrigin, +} + +pub struct HTMLMediaData { + pub current_frame: Option<(ImageKey, i32, i32)>, +} diff --git a/components/shared/script_layout/message.rs b/components/shared/script_layout/message.rs new file mode 100644 index 00000000000..01c5d58e659 --- /dev/null +++ b/components/shared/script_layout/message.rs @@ -0,0 +1,260 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use app_units::Au; +use crossbeam_channel::{Receiver, Sender}; +use euclid::default::{Point2D, Rect}; +use gfx_traits::Epoch; +use ipc_channel::ipc::{IpcReceiver, IpcSender}; +use malloc_size_of_derive::MallocSizeOf; +use metrics::PaintTimeMetrics; +use msg::constellation_msg::{BackgroundHangMonitorRegister, BrowsingContextId, PipelineId}; +use net_traits::image_cache::ImageCache; +use profile_traits::mem::ReportsChan; +use script_traits::{ + ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg, Painter, ScrollState, + WindowSizeData, +}; +use servo_arc::Arc as ServoArc; +use servo_atoms::Atom; +use servo_url::{ImmutableOrigin, ServoUrl}; +use style::animation::DocumentAnimationSet; +use style::context::QuirksMode; +use style::dom::OpaqueNode; +use style::invalidation::element::restyle_hints::RestyleHint; +use style::properties::PropertyId; +use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot}; +use style::stylesheets::Stylesheet; + +use crate::rpc::LayoutRPC; +use crate::{PendingImage, TrustedNodeAddress}; + +/// Asynchronous messages that script can send to layout. +pub enum Msg { + /// Adds the given stylesheet to the document. The second stylesheet is the + /// insertion point (if it exists, the sheet needs to be inserted before + /// it). + AddStylesheet(ServoArc<Stylesheet>, Option<ServoArc<Stylesheet>>), + + /// Removes a stylesheet from the document. + RemoveStylesheet(ServoArc<Stylesheet>), + + /// Change the quirks mode. + SetQuirksMode(QuirksMode), + + /// Requests a reflow. + Reflow(ScriptReflow), + + /// Get an RPC interface. + GetRPC(Sender<Box<dyn LayoutRPC + Send>>), + + /// Requests that the layout thread measure its memory usage. The resulting reports are sent back + /// via the supplied channel. + CollectReports(ReportsChan), + + /// Requests that the layout thread enter a quiescent state in which no more messages are + /// accepted except `ExitMsg`. A response message will be sent on the supplied channel when + /// this happens. + PrepareToExit(Sender<()>), + + /// Requests that the layout thread immediately shut down. There must be no more nodes left after + /// this, or layout will crash. + ExitNow, + + /// Get the last epoch counter for this layout thread. + GetCurrentEpoch(IpcSender<Epoch>), + + /// Asks the layout thread whether any Web fonts have yet to load (if true, loads are pending; + /// false otherwise). + GetWebFontLoadState(IpcSender<bool>), + + /// Creates a new layout thread. + /// + /// This basically exists to keep the script-layout dependency one-way. + CreateLayoutThread(LayoutThreadInit), + + /// Set the final Url. + SetFinalUrl(ServoUrl), + + /// Tells layout about the new scrolling offsets of each scrollable stacking context. + SetScrollStates(Vec<ScrollState>), + + /// Tells layout that script has added some paint worklet modules. + RegisterPaint(Atom, Vec<Atom>, Box<dyn Painter>), + + /// Send to layout the precise time when the navigation started. + SetNavigationStart(u64), +} + +#[derive(Debug, PartialEq)] +pub enum NodesFromPointQueryType { + All, + Topmost, +} + +#[derive(Debug, PartialEq)] +pub enum QueryMsg { + ContentBoxQuery(OpaqueNode), + ContentBoxesQuery(OpaqueNode), + ClientRectQuery(OpaqueNode), + ScrollingAreaQuery(Option<OpaqueNode>), + OffsetParentQuery(OpaqueNode), + TextIndexQuery(OpaqueNode, Point2D<f32>), + NodesFromPointQuery(Point2D<f32>, NodesFromPointQueryType), + + // FIXME(nox): The following queries use the TrustedNodeAddress to + // access actual DOM nodes, but those values can be constructed from + // garbage values such as `0xdeadbeef as *const _`, this is unsound. + NodeScrollIdQuery(TrustedNodeAddress), + ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, PropertyId), + StyleQuery, + ElementInnerTextQuery(TrustedNodeAddress), + ResolvedFontStyleQuery(TrustedNodeAddress, PropertyId, String), + InnerWindowDimensionsQuery(BrowsingContextId), +} + +/// Any query to perform with this reflow. +#[derive(Debug, PartialEq)] +pub enum ReflowGoal { + Full, + TickAnimations, + LayoutQuery(QueryMsg, u64), + + /// Tells layout about a single new scrolling offset from the script. The rest will + /// remain untouched and layout won't forward this back to script. + UpdateScrollNode(ScrollState), +} + +impl ReflowGoal { + /// Returns true if the given ReflowQuery needs a full, up-to-date display list to + /// be present or false if it only needs stacking-relative positions. + pub fn needs_display_list(&self) -> bool { + match *self { + ReflowGoal::Full | ReflowGoal::TickAnimations | ReflowGoal::UpdateScrollNode(_) => true, + ReflowGoal::LayoutQuery(ref querymsg, _) => match *querymsg { + QueryMsg::NodesFromPointQuery(..) | + QueryMsg::TextIndexQuery(..) | + QueryMsg::InnerWindowDimensionsQuery(_) | + QueryMsg::ElementInnerTextQuery(_) => true, + QueryMsg::ContentBoxQuery(_) | + QueryMsg::ContentBoxesQuery(_) | + QueryMsg::ClientRectQuery(_) | + QueryMsg::ScrollingAreaQuery(_) | + QueryMsg::NodeScrollIdQuery(_) | + QueryMsg::ResolvedStyleQuery(..) | + QueryMsg::ResolvedFontStyleQuery(..) | + QueryMsg::OffsetParentQuery(_) | + QueryMsg::StyleQuery => false, + }, + } + } + + /// Returns true if the given ReflowQuery needs its display list send to WebRender or + /// false if a layout_thread display list is sufficient. + pub fn needs_display(&self) -> bool { + match *self { + ReflowGoal::Full | ReflowGoal::TickAnimations | ReflowGoal::UpdateScrollNode(_) => true, + ReflowGoal::LayoutQuery(ref querymsg, _) => match *querymsg { + QueryMsg::NodesFromPointQuery(..) | + QueryMsg::TextIndexQuery(..) | + QueryMsg::ElementInnerTextQuery(_) => true, + QueryMsg::ContentBoxQuery(_) | + QueryMsg::ContentBoxesQuery(_) | + QueryMsg::ClientRectQuery(_) | + QueryMsg::ScrollingAreaQuery(_) | + QueryMsg::NodeScrollIdQuery(_) | + QueryMsg::ResolvedStyleQuery(..) | + QueryMsg::ResolvedFontStyleQuery(..) | + QueryMsg::OffsetParentQuery(_) | + QueryMsg::InnerWindowDimensionsQuery(_) | + QueryMsg::StyleQuery => false, + }, + } + } +} + +/// Information needed for a reflow. +pub struct Reflow { + /// A clipping rectangle for the page, an enlarged rectangle containing the viewport. + pub page_clip_rect: Rect<Au>, +} + +/// Information derived from a layout pass that needs to be returned to the script thread. +#[derive(Default)] +pub struct ReflowComplete { + /// The list of images that were encountered that are in progress. + pub pending_images: Vec<PendingImage>, +} + +/// Information needed for a script-initiated reflow. +pub struct ScriptReflow { + /// General reflow data. + pub reflow_info: Reflow, + /// The document node. + pub document: TrustedNodeAddress, + /// The dirty root from which to restyle. + pub dirty_root: Option<TrustedNodeAddress>, + /// Whether the document's stylesheets have changed since the last script reflow. + pub stylesheets_changed: bool, + /// The current window size. + pub window_size: WindowSizeData, + /// The channel that we send a notification to. + pub script_join_chan: Sender<ReflowComplete>, + /// The goal of this reflow. + pub reflow_goal: ReflowGoal, + /// The number of objects in the dom #10110 + pub dom_count: u32, + /// The current window origin + pub origin: ImmutableOrigin, + /// Restyle snapshot map. + pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>, + /// The current animation timeline value. + pub animation_timeline_value: f64, + /// The set of animations for this document. + pub animations: DocumentAnimationSet, +} + +pub struct LayoutThreadInit { + pub id: PipelineId, + pub url: ServoUrl, + pub is_parent: bool, + pub layout_pair: (Sender<Msg>, Receiver<Msg>), + pub pipeline_port: IpcReceiver<LayoutControlMsg>, + pub background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, + pub constellation_chan: IpcSender<ConstellationMsg>, + pub script_chan: IpcSender<ConstellationControlMsg>, + pub image_cache: Arc<dyn ImageCache>, + pub paint_time_metrics: PaintTimeMetrics, + pub layout_is_busy: Arc<AtomicBool>, + pub window_size: WindowSizeData, +} + +/// A pending restyle. +#[derive(Debug, MallocSizeOf)] +pub struct PendingRestyle { + /// If this element had a state or attribute change since the last restyle, track + /// the original condition of the element. + pub snapshot: Option<Snapshot>, + + /// Any explicit restyles hints that have been accumulated for this element. + pub hint: RestyleHint, + + /// Any explicit restyles damage that have been accumulated for this element. + pub damage: RestyleDamage, +} + +impl PendingRestyle { + /// Creates a new empty pending restyle. + #[inline] + pub fn new() -> Self { + PendingRestyle { + snapshot: None, + hint: RestyleHint::empty(), + damage: RestyleDamage::empty(), + } + } +} diff --git a/components/shared/script_layout/rpc.rs b/components/shared/script_layout/rpc.rs new file mode 100644 index 00000000000..3778d8f975a --- /dev/null +++ b/components/shared/script_layout/rpc.rs @@ -0,0 +1,75 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use app_units::Au; +use euclid::default::Rect; +use euclid::Size2D; +use script_traits::UntrustedNodeAddress; +use servo_arc::Arc; +use style::properties::style_structs::Font; +use style_traits::CSSPixel; +use webrender_api::ExternalScrollId; + +/// Synchronous messages that script can send to layout. +/// +/// In general, you should use messages to talk to Layout. Use the RPC interface +/// if and only if the work is +/// +/// 1) read-only with respect to LayoutThreadData, +/// 2) small, +/// 3) and really needs to be fast. +pub trait LayoutRPC { + /// Requests the dimensions of the content box, as in the `getBoundingClientRect()` call. + fn content_box(&self) -> ContentBoxResponse; + /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call. + fn content_boxes(&self) -> ContentBoxesResponse; + /// Requests the geometry of this node. Used by APIs such as `clientTop`. + fn node_geometry(&self) -> NodeGeometryResponse; + /// Requests the scroll geometry of this node. Used by APIs such as `scrollTop`. + fn scrolling_area(&self) -> NodeGeometryResponse; + /// Requests the scroll id of this node. Used by APIs such as `scrollTop` + fn node_scroll_id(&self) -> NodeScrollIdResponse; + /// Query layout for the resolved value of a given CSS property + fn resolved_style(&self) -> ResolvedStyleResponse; + /// Query layout to get the resolved font style for canvas. + fn resolved_font_style(&self) -> Option<Arc<Font>>; + fn offset_parent(&self) -> OffsetParentResponse; + fn text_index(&self) -> TextIndexResponse; + /// Requests the list of nodes from the given point. + fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress>; + /// Query layout to get the inner text for a given element. + fn element_inner_text(&self) -> String; + /// Get the dimensions of an iframe's inner window. + fn inner_window_dimensions(&self) -> Option<Size2D<f32, CSSPixel>>; +} + +pub struct ContentBoxResponse(pub Option<Rect<Au>>); + +pub struct ContentBoxesResponse(pub Vec<Rect<Au>>); + +pub struct NodeGeometryResponse { + pub client_rect: Rect<i32>, +} + +pub struct NodeScrollIdResponse(pub ExternalScrollId); + +pub struct ResolvedStyleResponse(pub String); + +#[derive(Clone)] +pub struct OffsetParentResponse { + pub node_address: Option<UntrustedNodeAddress>, + pub rect: Rect<Au>, +} + +impl OffsetParentResponse { + pub fn empty() -> OffsetParentResponse { + OffsetParentResponse { + node_address: None, + rect: Rect::zero(), + } + } +} + +#[derive(Clone)] +pub struct TextIndexResponse(pub Option<usize>); diff --git a/components/shared/script_layout/wrapper_traits.rs b/components/shared/script_layout/wrapper_traits.rs new file mode 100644 index 00000000000..1cc20c5f6ab --- /dev/null +++ b/components/shared/script_layout/wrapper_traits.rs @@ -0,0 +1,497 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#![allow(unsafe_code)] + +use std::borrow::Cow; +use std::fmt::Debug; +use std::sync::Arc as StdArc; + +use atomic_refcell::AtomicRef; +use gfx_traits::{combine_id_with_fragment_type, ByteIndex, FragmentType}; +use html5ever::{local_name, namespace_url, ns, LocalName, Namespace}; +use msg::constellation_msg::{BrowsingContextId, PipelineId}; +use net_traits::image::base::{Image, ImageMetadata}; +use range::Range; +use servo_arc::Arc; +use servo_url::ServoUrl; +use style::attr::AttrValue; +use style::context::SharedStyleContext; +use style::data::ElementData; +use style::dom::{LayoutIterator, NodeInfo, OpaqueNode, TElement, TNode}; +use style::properties::ComputedValues; +use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorImpl}; +use style::stylist::RuleInclusion; +use webrender_api::ExternalScrollId; + +use crate::{HTMLCanvasData, HTMLMediaData, LayoutNodeType, SVGSVGData, StyleAndOpaqueLayoutData}; + +pub trait LayoutDataTrait: Default + Send + Sync + 'static {} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PseudoElementType { + Normal, + Before, + After, + DetailsSummary, + DetailsContent, +} + +impl PseudoElementType { + pub fn fragment_type(&self) -> FragmentType { + match *self { + PseudoElementType::Normal => FragmentType::FragmentBody, + PseudoElementType::Before => FragmentType::BeforePseudoContent, + PseudoElementType::After => FragmentType::AfterPseudoContent, + PseudoElementType::DetailsSummary => FragmentType::FragmentBody, + PseudoElementType::DetailsContent => FragmentType::FragmentBody, + } + } + + pub fn is_before(&self) -> bool { + match *self { + PseudoElementType::Before => true, + _ => false, + } + } + + pub fn is_replaced_content(&self) -> bool { + match *self { + PseudoElementType::Before | PseudoElementType::After => true, + _ => false, + } + } + + pub fn style_pseudo_element(&self) -> PseudoElement { + match *self { + PseudoElementType::Normal => { + unreachable!("style_pseudo_element called with PseudoElementType::Normal") + }, + PseudoElementType::Before => PseudoElement::Before, + PseudoElementType::After => PseudoElement::After, + PseudoElementType::DetailsSummary => PseudoElement::DetailsSummary, + PseudoElementType::DetailsContent => PseudoElement::DetailsContent, + } + } +} + +/// Trait to abstract access to layout data across various data structures. +pub trait GetStyleAndOpaqueLayoutData<'dom> { + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData>; +} + +/// A wrapper so that layout can access only the methods that it should have access to. Layout must +/// only ever see these and must never see instances of `LayoutDom`. +/// FIXME(mrobinson): `Send + Sync` is required here for Layout 2020, but eventually it +/// should stop sending LayoutNodes to other threads and rely on ThreadSafeLayoutNode +/// or some other mechanism to ensure thread safety. +pub trait LayoutNode<'dom>: + Copy + Debug + GetStyleAndOpaqueLayoutData<'dom> + TNode + Send + Sync +{ + type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'dom>; + fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode; + + /// Returns the type ID of this node. + fn type_id(&self) -> LayoutNodeType; + + unsafe fn initialize_data(&self); + unsafe fn init_style_and_opaque_layout_data(&self, data: Box<StyleAndOpaqueLayoutData>); + unsafe fn take_style_and_opaque_layout_data(&self) -> Box<StyleAndOpaqueLayoutData>; + + fn rev_children(self) -> LayoutIterator<ReverseChildrenIterator<Self>> { + LayoutIterator(ReverseChildrenIterator { + current: self.last_child(), + }) + } + + fn traverse_preorder(self) -> TreeIterator<Self> { + TreeIterator::new(self) + } + + /// Returns whether the node is connected. + fn is_connected(&self) -> bool; +} + +pub struct ReverseChildrenIterator<ConcreteNode> { + current: Option<ConcreteNode>, +} + +impl<'dom, ConcreteNode> Iterator for ReverseChildrenIterator<ConcreteNode> +where + ConcreteNode: LayoutNode<'dom>, +{ + type Item = ConcreteNode; + fn next(&mut self) -> Option<ConcreteNode> { + let node = self.current; + self.current = node.and_then(|node| node.prev_sibling()); + node + } +} + +pub struct TreeIterator<ConcreteNode> { + stack: Vec<ConcreteNode>, +} + +impl<'dom, ConcreteNode> TreeIterator<ConcreteNode> +where + ConcreteNode: LayoutNode<'dom>, +{ + fn new(root: ConcreteNode) -> TreeIterator<ConcreteNode> { + let mut stack = vec![]; + stack.push(root); + TreeIterator { stack: stack } + } + + pub fn next_skipping_children(&mut self) -> Option<ConcreteNode> { + self.stack.pop() + } +} + +impl<'dom, ConcreteNode> Iterator for TreeIterator<ConcreteNode> +where + ConcreteNode: LayoutNode<'dom>, +{ + type Item = ConcreteNode; + fn next(&mut self) -> Option<ConcreteNode> { + let ret = self.stack.pop(); + ret.map(|node| self.stack.extend(node.rev_children())); + ret + } +} + +/// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout +/// node does not allow any parents or siblings of nodes to be accessed, to avoid races. +pub trait ThreadSafeLayoutNode<'dom>: + Clone + Copy + Debug + GetStyleAndOpaqueLayoutData<'dom> + NodeInfo + PartialEq + Sized +{ + type ConcreteNode: LayoutNode<'dom, ConcreteThreadSafeLayoutNode = Self>; + type ConcreteElement: TElement; + + type ConcreteThreadSafeLayoutElement: ThreadSafeLayoutElement<'dom, ConcreteThreadSafeLayoutNode = Self> + + ::selectors::Element<Impl = SelectorImpl>; + type ChildrenIterator: Iterator<Item = Self> + Sized; + + /// Converts self into an `OpaqueNode`. + fn opaque(&self) -> OpaqueNode; + + /// Returns the type ID of this node. + /// Returns `None` if this is a pseudo-element; otherwise, returns `Some`. + fn type_id(&self) -> Option<LayoutNodeType>; + + /// Returns the style for a text node. This is computed on the fly from the + /// parent style to avoid traversing text nodes in the style system. + /// + /// Note that this does require accessing the parent, which this interface + /// technically forbids. But accessing the parent is only unsafe insofar as + /// it can be used to reach siblings and cousins. A simple immutable borrow + /// of the parent data is fine, since the bottom-up traversal will not process + /// the parent until all the children have been processed. + fn parent_style(&self) -> Arc<ComputedValues>; + + fn get_before_pseudo(&self) -> Option<Self> { + self.as_element() + .and_then(|el| el.get_before_pseudo()) + .map(|el| el.as_node()) + } + + fn get_after_pseudo(&self) -> Option<Self> { + self.as_element() + .and_then(|el| el.get_after_pseudo()) + .map(|el| el.as_node()) + } + + fn get_details_summary_pseudo(&self) -> Option<Self> { + self.as_element() + .and_then(|el| el.get_details_summary_pseudo()) + .map(|el| el.as_node()) + } + + fn get_details_content_pseudo(&self) -> Option<Self> { + self.as_element() + .and_then(|el| el.get_details_content_pseudo()) + .map(|el| el.as_node()) + } + + fn debug_id(self) -> usize; + + /// Returns an iterator over this node's children. + fn children(&self) -> LayoutIterator<Self::ChildrenIterator>; + + /// Returns a ThreadSafeLayoutElement if this is an element, None otherwise. + fn as_element(&self) -> Option<Self::ConcreteThreadSafeLayoutElement>; + + #[inline] + fn get_pseudo_element_type(&self) -> PseudoElementType { + self.as_element() + .map_or(PseudoElementType::Normal, |el| el.get_pseudo_element_type()) + } + + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData>; + + fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> { + if let Some(el) = self.as_element() { + el.style(context) + } else { + // Text nodes are not styled during traversal,instead we simply + // return parent style here and do cascading during layout. + debug_assert!(self.is_text_node()); + self.parent_style() + } + } + + fn selected_style(&self) -> Arc<ComputedValues> { + if let Some(el) = self.as_element() { + el.selected_style() + } else { + debug_assert!(self.is_text_node()); + // TODO(stshine): What should the selected style be for text? + self.parent_style() + } + } + + fn is_ignorable_whitespace(&self, context: &SharedStyleContext) -> bool; + + /// Returns true if this node contributes content. This is used in the implementation of + /// `empty_cells` per CSS 2.1 § 17.6.1.1. + fn is_content(&self) -> bool { + self.type_id().is_some() + } + + /// Returns access to the underlying LayoutNode. This is breaks the abstraction + /// barrier of ThreadSafeLayout wrapper layer, and can lead to races if not used + /// carefully. + /// + /// We need this because the implementation of some methods need to access the layout + /// data flags, and we have this annoying trait separation between script and layout :-( + unsafe fn unsafe_get(self) -> Self::ConcreteNode; + + fn node_text_content(self) -> Cow<'dom, str>; + + /// If the insertion point is within this node, returns it. Otherwise, returns `None`. + fn selection(&self) -> Option<Range<ByteIndex>>; + + /// If this is an image element, returns its URL. If this is not an image element, fails. + fn image_url(&self) -> Option<ServoUrl>; + + /// If this is an image element, returns its current-pixel-density. If this is not an image element, fails. + fn image_density(&self) -> Option<f64>; + + /// If this is an image element, returns its image data. Otherwise, returns `None`. + fn image_data(&self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)>; + + fn canvas_data(&self) -> Option<HTMLCanvasData>; + + fn svg_data(&self) -> Option<SVGSVGData>; + + fn media_data(&self) -> Option<HTMLMediaData>; + + /// If this node is an iframe element, returns its browsing context ID. If this node is + /// not an iframe element, fails. Returns None if there is no nested browsing context. + fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId>; + + /// If this node is an iframe element, returns its pipeline ID. If this node is + /// not an iframe element, fails. Returns None if there is no nested browsing context. + fn iframe_pipeline_id(&self) -> Option<PipelineId>; + + fn get_colspan(&self) -> u32; + + fn get_rowspan(&self) -> u32; + + fn fragment_type(&self) -> FragmentType { + self.get_pseudo_element_type().fragment_type() + } + + fn generate_scroll_id(&self, pipeline_id: PipelineId) -> ExternalScrollId { + let id = combine_id_with_fragment_type(self.opaque().id(), self.fragment_type()); + ExternalScrollId(id as u64, pipeline_id.to_webrender()) + } +} + +pub trait ThreadSafeLayoutElement<'dom>: + Clone + + Copy + + Sized + + Debug + + ::selectors::Element<Impl = SelectorImpl> + + GetStyleAndOpaqueLayoutData<'dom> +{ + type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode< + 'dom, + ConcreteThreadSafeLayoutElement = Self, + >; + + /// This type alias is just a work-around to avoid writing + /// + /// <Self::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteElement + /// + type ConcreteElement: TElement; + + fn as_node(&self) -> Self::ConcreteThreadSafeLayoutNode; + + /// Creates a new `ThreadSafeLayoutElement` for the same `LayoutElement` + /// with a different pseudo-element type. + fn with_pseudo(&self, pseudo: PseudoElementType) -> Self; + + /// Returns the type ID of this node. + /// Returns `None` if this is a pseudo-element; otherwise, returns `Some`. + fn type_id(&self) -> Option<LayoutNodeType>; + + /// Returns access to the underlying TElement. This is breaks the abstraction + /// barrier of ThreadSafeLayout wrapper layer, and can lead to races if not used + /// carefully. + /// + /// We need this so that the functions defined on this trait can call + /// lazily_compute_pseudo_element_style, which operates on TElement. + unsafe fn unsafe_get(self) -> Self::ConcreteElement; + + fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str>; + + fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue>; + + fn style_data(&self) -> AtomicRef<ElementData>; + + fn get_pseudo_element_type(&self) -> PseudoElementType; + + #[inline] + fn get_before_pseudo(&self) -> Option<Self> { + if self + .style_data() + .styles + .pseudos + .get(&PseudoElement::Before) + .is_some() + { + Some(self.with_pseudo(PseudoElementType::Before)) + } else { + None + } + } + + #[inline] + fn get_after_pseudo(&self) -> Option<Self> { + if self + .style_data() + .styles + .pseudos + .get(&PseudoElement::After) + .is_some() + { + Some(self.with_pseudo(PseudoElementType::After)) + } else { + None + } + } + + #[inline] + fn get_details_summary_pseudo(&self) -> Option<Self> { + if self.has_local_name(&local_name!("details")) && self.has_namespace(&ns!(html)) { + Some(self.with_pseudo(PseudoElementType::DetailsSummary)) + } else { + None + } + } + + #[inline] + fn get_details_content_pseudo(&self) -> Option<Self> { + if self.has_local_name(&local_name!("details")) && + self.has_namespace(&ns!(html)) && + self.get_attr(&ns!(), &local_name!("open")).is_some() + { + Some(self.with_pseudo(PseudoElementType::DetailsContent)) + } else { + None + } + } + + /// Returns the style results for the given node. If CSS selector matching + /// has not yet been performed, fails. + /// + /// Unlike the version on TNode, this handles pseudo-elements. + #[inline] + fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> { + let data = self.style_data(); + match self.get_pseudo_element_type() { + PseudoElementType::Normal => data.styles.primary().clone(), + other => { + // Precompute non-eagerly-cascaded pseudo-element styles if not + // cached before. + let style_pseudo = other.style_pseudo_element(); + match style_pseudo.cascade_type() { + // Already computed during the cascade. + PseudoElementCascadeType::Eager => self + .style_data() + .styles + .pseudos + .get(&style_pseudo) + .unwrap() + .clone(), + PseudoElementCascadeType::Precomputed => context + .stylist + .precomputed_values_for_pseudo::<Self::ConcreteElement>( + &context.guards, + &style_pseudo, + Some(data.styles.primary()), + ), + PseudoElementCascadeType::Lazy => { + context + .stylist + .lazily_compute_pseudo_element_style( + &context.guards, + unsafe { self.unsafe_get() }, + &style_pseudo, + RuleInclusion::All, + data.styles.primary(), + /* is_probe = */ false, + /* matching_func = */ None, + ) + .unwrap() + }, + } + }, + } + } + + #[inline] + fn selected_style(&self) -> Arc<ComputedValues> { + let data = self.style_data(); + data.styles + .pseudos + .get(&PseudoElement::Selection) + .unwrap_or(data.styles.primary()) + .clone() + } + + /// Returns the already resolved style of the node. + /// + /// This differs from `style(ctx)` in that if the pseudo-element has not yet + /// been computed it would panic. + /// + /// This should be used just for querying layout, or when we know the + /// element style is precomputed, not from general layout itself. + #[inline] + fn resolved_style(&self) -> Arc<ComputedValues> { + let data = self.style_data(); + match self.get_pseudo_element_type() { + PseudoElementType::Normal => data.styles.primary().clone(), + other => data + .styles + .pseudos + .get(&other.style_pseudo_element()) + .unwrap() + .clone(), + } + } + + fn is_shadow_host(&self) -> bool; + + /// Returns whether this node is a body element of an html element root + /// in an HTML element document. + /// + /// Note that this does require accessing the parent, which this interface + /// technically forbids. But accessing the parent is only unsafe insofar as + /// it can be used to reach siblings and cousins. A simple immutable borrow + /// of the parent data is fine, since the bottom-up traversal will not process + /// the parent until all the children have been processed. + fn is_body_element_of_html_element_root(&self) -> bool; +} diff --git a/components/shared/webrender/Cargo.toml b/components/shared/webrender/Cargo.toml new file mode 100644 index 00000000000..f078b96101d --- /dev/null +++ b/components/shared/webrender/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "webrender_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "webrender_traits" +path = "lib.rs" + +[dependencies] +euclid = { workspace = true } +webrender_api = { workspace = true } diff --git a/components/shared/webrender/lib.rs b/components/shared/webrender/lib.rs new file mode 100644 index 00000000000..8e48d94e439 --- /dev/null +++ b/components/shared/webrender/lib.rs @@ -0,0 +1,179 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#![deny(unsafe_code)] + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +use euclid::default::Size2D; +use webrender_api::units::TexelRect; +use webrender_api::{ + ExternalImage, ExternalImageHandler, ExternalImageId, ExternalImageSource, ImageRendering, +}; + +/// This trait is used as a bridge between the different GL clients +/// in Servo that handles WebRender ExternalImages and the WebRender +/// ExternalImageHandler API. +// +/// This trait is used to notify lock/unlock messages and get the +/// required info that WR needs. +pub trait WebrenderExternalImageApi { + fn lock(&mut self, id: u64) -> (WebrenderImageSource, Size2D<i32>); + fn unlock(&mut self, id: u64); +} + +pub enum WebrenderImageSource<'a> { + TextureHandle(u32), + Raw(&'a [u8]), +} + +/// Type of Webrender External Image Handler. +pub enum WebrenderImageHandlerType { + WebGL, + Media, + WebGPU, +} + +/// List of Webrender external images to be shared among all external image +/// consumers (WebGL, Media, WebGPU). +/// It ensures that external image identifiers are unique. +pub struct WebrenderExternalImageRegistry { + /// Map of all generated external images. + external_images: HashMap<ExternalImageId, WebrenderImageHandlerType>, + /// Id generator for the next external image identifier. + next_image_id: u64, +} + +impl WebrenderExternalImageRegistry { + pub fn new() -> Self { + Self { + external_images: HashMap::new(), + next_image_id: 0, + } + } + + pub fn next_id(&mut self, handler_type: WebrenderImageHandlerType) -> ExternalImageId { + self.next_image_id += 1; + let key = ExternalImageId(self.next_image_id); + self.external_images.insert(key, handler_type); + key + } + + pub fn remove(&mut self, key: &ExternalImageId) { + self.external_images.remove(key); + } + + pub fn get(&self, key: &ExternalImageId) -> Option<&WebrenderImageHandlerType> { + self.external_images.get(key) + } +} + +/// WebRender External Image Handler implementation. +pub struct WebrenderExternalImageHandlers { + /// WebGL handler. + webgl_handler: Option<Box<dyn WebrenderExternalImageApi>>, + /// Media player handler. + media_handler: Option<Box<dyn WebrenderExternalImageApi>>, + /// WebGPU handler. + webgpu_handler: Option<Box<dyn WebrenderExternalImageApi>>, + /// Webrender external images. + external_images: Arc<Mutex<WebrenderExternalImageRegistry>>, +} + +impl WebrenderExternalImageHandlers { + pub fn new() -> (Self, Arc<Mutex<WebrenderExternalImageRegistry>>) { + let external_images = Arc::new(Mutex::new(WebrenderExternalImageRegistry::new())); + ( + Self { + webgl_handler: None, + media_handler: None, + webgpu_handler: None, + external_images: external_images.clone(), + }, + external_images, + ) + } + + pub fn set_handler( + &mut self, + handler: Box<dyn WebrenderExternalImageApi>, + handler_type: WebrenderImageHandlerType, + ) { + match handler_type { + WebrenderImageHandlerType::WebGL => self.webgl_handler = Some(handler), + WebrenderImageHandlerType::Media => self.media_handler = Some(handler), + WebrenderImageHandlerType::WebGPU => self.webgpu_handler = Some(handler), + } + } +} + +impl ExternalImageHandler for WebrenderExternalImageHandlers { + /// Lock the external image. Then, WR could start to read the + /// image content. + /// The WR client should not change the image content until the + /// unlock() call. + fn lock( + &mut self, + key: ExternalImageId, + _channel_index: u8, + _rendering: ImageRendering, + ) -> ExternalImage { + let external_images = self.external_images.lock().unwrap(); + let handler_type = external_images + .get(&key) + .expect("Tried to get unknown external image"); + match handler_type { + WebrenderImageHandlerType::WebGL => { + let (source, size) = self.webgl_handler.as_mut().unwrap().lock(key.0); + let texture_id = match source { + WebrenderImageSource::TextureHandle(b) => b, + _ => panic!("Wrong type"), + }; + ExternalImage { + uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0), + source: ExternalImageSource::NativeTexture(texture_id), + } + }, + WebrenderImageHandlerType::Media => { + let (source, size) = self.media_handler.as_mut().unwrap().lock(key.0); + let texture_id = match source { + WebrenderImageSource::TextureHandle(b) => b, + _ => panic!("Wrong type"), + }; + ExternalImage { + uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0), + source: ExternalImageSource::NativeTexture(texture_id), + } + }, + WebrenderImageHandlerType::WebGPU => { + let (source, size) = self.webgpu_handler.as_mut().unwrap().lock(key.0); + let buffer = match source { + WebrenderImageSource::Raw(b) => b, + _ => panic!("Wrong type"), + }; + ExternalImage { + uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0), + source: ExternalImageSource::RawData(buffer), + } + }, + } + } + + /// Unlock the external image. The WR should not read the image + /// content after this call. + fn unlock(&mut self, key: ExternalImageId, _channel_index: u8) { + let external_images = self.external_images.lock().unwrap(); + let handler_type = external_images + .get(&key) + .expect("Tried to get unknown external image"); + match handler_type { + WebrenderImageHandlerType::WebGL => self.webgl_handler.as_mut().unwrap().unlock(key.0), + WebrenderImageHandlerType::Media => self.media_handler.as_mut().unwrap().unlock(key.0), + WebrenderImageHandlerType::WebGPU => { + self.webgpu_handler.as_mut().unwrap().unlock(key.0) + }, + }; + } +} |