aboutsummaryrefslogtreecommitdiffstats
path: root/components/shared
diff options
context:
space:
mode:
Diffstat (limited to 'components/shared')
-rw-r--r--components/shared/bluetooth/Cargo.toml17
-rw-r--r--components/shared/bluetooth/blocklist.rs112
-rw-r--r--components/shared/bluetooth/lib.rs119
-rw-r--r--components/shared/bluetooth/scanfilter.rs143
-rw-r--r--components/shared/canvas/Cargo.toml33
-rw-r--r--components/shared/canvas/canvas.rs491
-rw-r--r--components/shared/canvas/lib.rs26
-rw-r--r--components/shared/canvas/webgl.rs1431
-rw-r--r--components/shared/canvas/webgl_channel/ipc.rs15
-rw-r--r--components/shared/canvas/webgl_channel/mod.rs149
-rw-r--r--components/shared/canvas/webgl_channel/mpsc.rs62
-rw-r--r--components/shared/compositing/Cargo.toml29
-rw-r--r--components/shared/compositing/constellation_msg.rs120
-rw-r--r--components/shared/compositing/lib.rs215
-rw-r--r--components/shared/devtools/Cargo.toml24
-rw-r--r--components/shared/devtools/lib.rs372
-rw-r--r--components/shared/embedder/Cargo.toml26
-rw-r--r--components/shared/embedder/build.rs23
-rw-r--r--components/shared/embedder/lib.rs347
-rw-r--r--components/shared/embedder/resources.rs154
-rw-r--r--components/shared/gfx/Cargo.toml18
-rw-r--r--components/shared/gfx/lib.rs130
-rw-r--r--components/shared/gfx/print_tree.rs89
-rw-r--r--components/shared/layout/Cargo.toml23
-rw-r--r--components/shared/layout/lib.rs53
-rw-r--r--components/shared/msg/Cargo.toml23
-rw-r--r--components/shared/msg/constellation_msg.rs738
-rw-r--r--components/shared/msg/lib.rs7
-rw-r--r--components/shared/net/Cargo.toml42
-rw-r--r--components/shared/net/blob_url_store.rs75
-rw-r--r--components/shared/net/fetch/headers.rs18
-rw-r--r--components/shared/net/filemanager_thread.rs190
-rw-r--r--components/shared/net/image/base.rs121
-rw-r--r--components/shared/net/image_cache.rs156
-rw-r--r--components/shared/net/lib.rs859
-rw-r--r--components/shared/net/pub_domains.rs159
-rw-r--r--components/shared/net/quality.rs87
-rw-r--r--components/shared/net/request.rs749
-rw-r--r--components/shared/net/response.rs364
-rw-r--r--components/shared/net/storage_thread.rs48
-rw-r--r--components/shared/net/tests/image.rs28
-rw-r--r--components/shared/net/tests/lib.rs212
-rw-r--r--components/shared/net/tests/pub_domains.rs122
-rw-r--r--components/shared/net/tests/whitespace.rs25
-rw-r--r--components/shared/profile/Cargo.toml20
-rw-r--r--components/shared/profile/ipc.rs82
-rw-r--r--components/shared/profile/lib.rs13
-rw-r--r--components/shared/profile/mem.rs211
-rw-r--r--components/shared/profile/time.rs175
-rw-r--r--components/shared/script/Cargo.toml47
-rw-r--r--components/shared/script/compositor.rs306
-rw-r--r--components/shared/script/lib.rs1314
-rw-r--r--components/shared/script/script_msg.rs493
-rw-r--r--components/shared/script/serializable.rs151
-rw-r--r--components/shared/script/tests/compositor.rs179
-rw-r--r--components/shared/script/transferable.rs164
-rw-r--r--components/shared/script/webdriver_msg.rs138
-rw-r--r--components/shared/script_layout/Cargo.toml39
-rw-r--r--components/shared/script_layout/lib.rs164
-rw-r--r--components/shared/script_layout/message.rs260
-rw-r--r--components/shared/script_layout/rpc.rs75
-rw-r--r--components/shared/script_layout/wrapper_traits.rs497
-rw-r--r--components/shared/webrender/Cargo.toml15
-rw-r--r--components/shared/webrender/lib.rs179
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)
+ },
+ };
+ }
+}