diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 015b7e9a1791..7a5a282620e2 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -13,7 +13,7 @@ path = "lib.rs" doctest = false [features] -gecko = [] +gecko = ["nsstring_vendor"] servo = ["serde/unstable", "serde", "serde_derive", "heapsize_plugin", "style_traits/servo", "app_units/plugins", "cssparser/heap_size", "cssparser/serde-serialization", @@ -36,6 +36,7 @@ lazy_static = "0.2" log = "0.3.5" libc = "0.2" matches = "0.1" +nsstring_vendor = {path = "gecko_bindings/nsstring_vendor", optional = true} num-integer = "0.1.32" num-traits = "0.1.32" num_cpus = "0.2.2" diff --git a/components/style/binding_tools/regen.py b/components/style/binding_tools/regen.py index e3895bf68913..d179cc879f42 100755 --- a/components/style/binding_tools/regen.py +++ b/components/style/binding_tools/regen.py @@ -68,7 +68,9 @@ "raw_lines": [ # We can get rid of this when the bindings move into the style crate. "pub enum OpaqueStyleData {}", + "pub use nsstring::nsStringRepr as nsString;" ], + "blacklist_types": ["nsString"], "whitelist_vars": [ "NS_THEME_.*", "NODE_.*", @@ -426,6 +428,11 @@ def zero_size_type(ty, flags): flags.append("--opaque-type") flags.append(ty) + if "blacklist_types" in current_target: + for ty in current_target["blacklist_types"]: + flags.append("--blacklist-type") + flags.append(ty) + if "servo_nullable_arc_types" in current_target: for ty in current_target["servo_nullable_arc_types"]: flags.append("--blacklist-type") diff --git a/components/style/gecko_bindings/nsstring_vendor/Cargo.toml b/components/style/gecko_bindings/nsstring_vendor/Cargo.toml new file mode 100644 index 000000000000..035bae1b095e --- /dev/null +++ b/components/style/gecko_bindings/nsstring_vendor/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nsstring_vendor" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Rust bindings to xpcom string types" + + +# Revendoring nsstring from m-c into Servo + +[dependencies] + diff --git a/components/style/gecko_bindings/nsstring_vendor/src/lib.rs b/components/style/gecko_bindings/nsstring_vendor/src/lib.rs new file mode 100644 index 000000000000..8f712c4b858f --- /dev/null +++ b/components/style/gecko_bindings/nsstring_vendor/src/lib.rs @@ -0,0 +1,842 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! This module provides rust bindings for the XPCOM string types. +//! +//! # TL;DR (what types should I use) +//! +//! Use `&{mut,} nsA[C]String` for functions in rust which wish to take or +//! mutate XPCOM strings. The other string types `Deref` to this type. +//! +//! Use `ns[C]String<'a>` for string struct members which don't leave rust, and +//! as an intermediate between rust string data structures (such as `String`, +//! `Vec`, `&str`, and `&[u16]`) and `&{mut,} nsA[C]String` (using +//! `ns[C]String::from(value)`). These conversions, when possible, will not +//! perform any allocations. +//! +//! Use `nsFixed[C]String` or `ns_auto_[c]string!` for dynamic stack allocated +//! strings which are expected to hold short string values. +//! +//! Use `*{const,mut} nsA[C]String` (`{const,} nsA[C]String*` in C++) for +//! function arguments passed across the rust/C++ language boundary. +//! +//! Use `ns[C]StringRepr` for string struct members which are shared between +//! rust and C++, but be careful, because this type lacks a `Drop` +//! implementation. +//! +//! # String Types +//! +//! ## `nsA[C]String` +//! +//! The core types in this module are `nsAString` and `nsACString`. These types +//! are zero-sized as far as rust is concerned, and are safe to pass around +//! behind both references (in rust code), and pointers (in C++ code). They +//! represent a handle to a XPCOM string which holds either `u16` or `u8` +//! characters respectively. The backing character buffer is guaranteed to live +//! as long as the reference to the `nsAString` or `nsACString`. +//! +//! These types in rust are simply used as dummy types. References to them +//! represent a pointer to the beginning of a variable-sized `#[repr(C)]` struct +//! which is common between both C++ and Rust implementations. In C++, their +//! corresponding types are also named `nsAString` or `nsACString`, and they are +//! defined within the `nsTSubstring.{cpp,h}` file. +//! +//! ### Valid Operations +//! +//! An `&nsA[C]String` acts like rust's `&str`, in that it is a borrowed +//! reference to the backing data. When used as an argument to other functions +//! on `&mut nsA[C]String`, optimizations can be performed to avoid copying +//! buffers, as information about the backing storage is preserved. +//! +//! An `&mut nsA[C]String` acts like rust's `&mut Cow`, in that it is a +//! mutable reference to a potentially borrowed string, which when modified will +//! ensure that it owns its own backing storage. This type can be appended to +//! with the methods `.append`, `.append_utf{8,16}`, and with the `write!` +//! macro, and can be assigned to with `.assign`. +//! +//! ## `ns[C]String<'a>` +//! +//! This type is an maybe-owned string type. It acts similarially to a +//! `Cow<[{u8,u16}]>`. This type provides `Deref` and `DerefMut` implementations +//! to `nsA[C]String`, which provides the methods for manipulating this type. +//! This type's lifetime parameter, `'a`, represents the lifetime of the backing +//! storage. When modified this type may re-allocate in order to ensure that it +//! does not mutate its backing storage. +//! +//! `ns[C]String`s can be constructed either with `ns[C]String::new()`, which +//! creates an empty `ns[C]String<'static>`, or through one of the provided +//! `From` implementations. Both string types may be constructed `From<&'a +//! str>`, with `nsCString` having a `'a` lifetime, as the storage is shared +//! with the `str`, while `nsString` has a `'static` lifetime, as its storage +//! has to be transcoded. +//! +//! When passing this type by reference, prefer passing a `&nsA[C]String` or +//! `&mut nsA[C]String`. to passing this type. +//! +//! This type is _not_ `#[repr(C)]`, as it has a `Drop` impl, which in versions +//! of `rustc < 1.13` adds drop flags to the struct, which messes up the layout, +//! making it unsafe to pass across the FFI boundary. The rust compiler will +//! warn if this type appears in `extern "C"` function definitions. +//! +//! When passing this type across the language boundary, pass it as `*const +//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a +//! mutable reference. +//! +//! This type is similar to the C++ type of the same name. +//! +//! ## `nsFixed[C]String<'a>` +//! +//! This type is a string type with fixed backing storage. It is created with +//! `nsFixed[C]String::new(buffer)`, passing a mutable reference to a buffer as +//! the argument. This buffer will be used as backing storage whenever the +//! resulting string will fit within it, falling back to heap allocations only +//! when the string size exceeds that of the backing buffer. +//! +//! Like `ns[C]String`, this type dereferences to `nsA[C]String` which provides +//! the methods for manipulating the type, and is not `#[repr(C)]`. +//! +//! When passing this type by reference, prefer passing a `&nsA[C]String` or +//! `&mut nsA[C]String`. to passing this type. +//! +//! This type is _not_ `#[repr(C)]`, as it has a `Drop` impl, which in versions +//! of `rustc < 1.13` adds drop flags to the struct, which messes up the layout, +//! making it unsafe to pass across the FFI boundary. The rust compiler will +//! warn if this type appears in `extern "C"` function definitions. +//! +//! When passing this type across the language boundary, pass it as `*const +//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a +//! mutable reference. +//! +//! This type is similar to the C++ type of the same name. +//! +//! ## `ns_auto_[c]string!($name)` +//! +//! This is a helper macro which defines a fixed size, (currently 64 character), +//! backing array on the stack, and defines a local variable with name `$name` +//! which is a `nsFixed[C]String` using this buffer as its backing storage. +//! +//! Usage of this macro is similar to the C++ type `nsAuto[C]String`, but could +//! not be implemented as a basic type due to the differences between rust and +//! C++'s move semantics. +//! +//! ## `ns[C]StringRepr` +//! +//! This type represents a C++ `ns[C]String`. This type is `#[repr(C)]` and is +//! safe to use in struct definitions which are shared across the language +//! boundary. It automatically dereferences to `&{mut,} nsA[C]String`, and thus +//! can be treated similarially to `ns[C]String`. +//! +//! If this type is dropped in rust, it will not free its backing storage. This +//! is because types implementing `Drop` have a drop flag added, which messes up +//! the layout of this type. When drop flags are removed, which should happen in +//! `rustc 1.13` (see rust-lang/rust#35764), this type will likely be removed, +//! and replaced with direct usage of `ns[C]String<'a>`, as its layout may be +//! identical. This module provides rust bindings to our xpcom ns[C]String +//! types. + +#![allow(non_camel_case_types)] + +use std::ops::{Deref, DerefMut}; +use std::marker::PhantomData; +use std::slice; +use std::ptr; +use std::mem; +use std::fmt; +use std::cmp; +use std::u32; + +////////////////////////////////// +// Internal Implemenation Flags // +////////////////////////////////// + +const F_NONE: u32 = 0; // no flags + +// data flags are in the lower 16-bits +const F_TERMINATED: u32 = 1 << 0; // IsTerminated returns true +const F_VOIDED: u32 = 1 << 1; // IsVoid returns true +const F_SHARED: u32 = 1 << 2; // mData points to a heap-allocated, shared buffer +const F_OWNED: u32 = 1 << 3; // mData points to a heap-allocated, raw buffer +const F_FIXED: u32 = 1 << 4; // mData points to a fixed-size writable, dependent buffer +const F_LITERAL: u32 = 1 << 5; // mData points to a string literal; F_TERMINATED will also be set + +// class flags are in the upper 16-bits +const F_CLASS_FIXED: u32 = 1 << 16; // indicates that |this| is of type nsTFixedString + +//////////////////////////////////// +// Generic String Bindings Macros // +//////////////////////////////////// + +macro_rules! define_string_types { + { + char_t = $char_t: ty; + AString = $AString: ident; + String = $String: ident; + FixedString = $FixedString: ident; + + StringRepr = $StringRepr: ident; + FixedStringRepr = $FixedStringRepr: ident; + AutoStringRepr = $AutoStringRepr: ident; + } => { + /// The representation of a ns[C]String type in C++. This type is + /// used internally by our definition of ns[C]String to ensure layout + /// compatibility with the C++ ns[C]String type. + /// + /// This type may also be used in place of a C++ ns[C]String inside of + /// struct definitions which are shared with C++, as it has identical + /// layout to our ns[C]String type. Due to drop flags, our ns[C]String + /// type does not have identical layout. When drop flags are removed, + /// this type will likely be made a private implementation detail, and + /// its uses will be replaced with `ns[C]String`. + /// + /// This struct will leak its data if dropped from rust. See the module + /// documentation for more information on this type. + #[repr(C)] + #[derive(Debug)] + pub struct $StringRepr { + data: *const $char_t, + length: u32, + flags: u32, + } + + impl Deref for $StringRepr { + type Target = $AString; + fn deref(&self) -> &$AString { + unsafe { + mem::transmute(self) + } + } + } + + impl DerefMut for $StringRepr { + fn deref_mut(&mut self) -> &mut $AString { + unsafe { + mem::transmute(self) + } + } + } + + /// The representation of a nsFixed[C]String type in C++. This type is + /// used internally by our definition of nsFixed[C]String to ensure layout + /// compatibility with the C++ nsFixed[C]String type. + #[repr(C)] + #[derive(Debug)] + struct $FixedStringRepr { + base: $StringRepr, + capacity: u32, + buffer: *mut $char_t, + } + + /// This type is the abstract type which is used for interacting with + /// strings in rust. Each string type can derefence to an instance of + /// this type, which provides the useful operations on strings. + /// + /// NOTE: Rust thinks this type has a size of 0, because the data + /// associated with it is not necessarially safe to move. It is not safe + /// to construct a nsAString yourself, unless it is received by + /// dereferencing one of these types. + /// + /// NOTE: The `[u8; 0]` member is zero sized, and only exists to prevent + /// the construction by code outside of this module. It is used instead + /// of a private `()` member because the `improper_ctypes` lint complains + /// about some ZST members in `extern "C"` function declarations. + #[repr(C)] + pub struct $AString { + _prohibit_constructor: [u8; 0], + } + + impl Deref for $AString { + type Target = [$char_t]; + fn deref(&self) -> &[$char_t] { + unsafe { + // This is legal, as all $AString values actually point to a + // $StringRepr + let this: &$StringRepr = mem::transmute(self); + if this.data.is_null() { + debug_assert!(this.length == 0); + // Use an arbitrary non-null value as the pointer + slice::from_raw_parts(0x1 as *const $char_t, 0) + } else { + slice::from_raw_parts(this.data, this.length as usize) + } + } + } + } + + impl cmp::PartialEq for $AString { + fn eq(&self, other: &$AString) -> bool { + &self[..] == &other[..] + } + } + + impl cmp::PartialEq<[$char_t]> for $AString { + fn eq(&self, other: &[$char_t]) -> bool { + &self[..] == other + } + } + + impl<'a> cmp::PartialEq<$String<'a>> for $AString { + fn eq(&self, other: &$String<'a>) -> bool { + self.eq(&**other) + } + } + + impl<'a> cmp::PartialEq<$FixedString<'a>> for $AString { + fn eq(&self, other: &$FixedString<'a>) -> bool { + self.eq(&**other) + } + } + + pub struct $String<'a> { + hdr: $StringRepr, + _marker: PhantomData<&'a [$char_t]>, + } + + impl $String<'static> { + pub fn new() -> $String<'static> { + $String { + hdr: $StringRepr { + data: ptr::null(), + length: 0, + flags: F_NONE, + }, + _marker: PhantomData, + } + } + } + + impl<'a> Deref for $String<'a> { + type Target = $AString; + fn deref(&self) -> &$AString { + &self.hdr + } + } + + impl<'a> DerefMut for $String<'a> { + fn deref_mut(&mut self) -> &mut $AString { + &mut self.hdr + } + } + + impl<'a> From<&'a [$char_t]> for $String<'a> { + fn from(s: &'a [$char_t]) -> $String<'a> { + assert!(s.len() < (u32::MAX as usize)); + $String { + hdr: $StringRepr { + data: s.as_ptr(), + length: s.len() as u32, + flags: F_NONE, + }, + _marker: PhantomData, + } + } + } + + impl From> for $String<'static> { + fn from(s: Box<[$char_t]>) -> $String<'static> { + assert!(s.len() < (u32::MAX as usize)); + // SAFETY NOTE: This method produces an F_OWNED ns[C]String from + // a Box<[$char_t]>. this is only safe because in the Gecko + // tree, we use the same allocator for Rust code as for C++ + // code, meaning that our box can be legally freed with + // libc::free(). + let length = s.len() as u32; + let ptr = s.as_ptr(); + mem::forget(s); + $String { + hdr: $StringRepr { + data: ptr, + length: length, + flags: F_OWNED, + }, + _marker: PhantomData, + } + } + } + + impl From> for $String<'static> { + fn from(s: Vec<$char_t>) -> $String<'static> { + s.into_boxed_slice().into() + } + } + + impl<'a> From<&'a $AString> for $String<'static> { + fn from(s: &'a $AString) -> $String<'static> { + let mut string = $String::new(); + string.assign(s); + string + } + } + + impl<'a> fmt::Write for $String<'a> { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + $AString::write_str(self, s) + } + } + + impl<'a> fmt::Display for $String<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Display>::fmt(self, f) + } + } + + impl<'a> fmt::Debug for $String<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Debug>::fmt(self, f) + } + } + + impl<'a> cmp::PartialEq for $String<'a> { + fn eq(&self, other: &$String<'a>) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<[$char_t]> for $String<'a> { + fn eq(&self, other: &[$char_t]) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b [$char_t]> for $String<'a> { + fn eq(&self, other: &&'b [$char_t]) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> cmp::PartialEq for $String<'a> { + fn eq(&self, other: &str) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b str> for $String<'a> { + fn eq(&self, other: &&'b str) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> Drop for $String<'a> { + fn drop(&mut self) { + unsafe { + self.finalize(); + } + } + } + + /// A nsFixed[C]String is a string which uses a fixed size mutable + /// backing buffer for storing strings which will fit within that + /// buffer, rather than using heap allocations. + pub struct $FixedString<'a> { + hdr: $FixedStringRepr, + _marker: PhantomData<&'a mut [$char_t]>, + } + + impl<'a> $FixedString<'a> { + pub fn new(buf: &'a mut [$char_t]) -> $FixedString<'a> { + let len = buf.len(); + assert!(len < (u32::MAX as usize)); + let buf_ptr = buf.as_mut_ptr(); + $FixedString { + hdr: $FixedStringRepr { + base: $StringRepr { + data: ptr::null(), + length: 0, + flags: F_CLASS_FIXED, + }, + capacity: len as u32, + buffer: buf_ptr, + }, + _marker: PhantomData, + } + } + } + + impl<'a> Deref for $FixedString<'a> { + type Target = $AString; + fn deref(&self) -> &$AString { + &self.hdr.base + } + } + + impl<'a> DerefMut for $FixedString<'a> { + fn deref_mut(&mut self) -> &mut $AString { + &mut self.hdr.base + } + } + + impl<'a> fmt::Write for $FixedString<'a> { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + $AString::write_str(self, s) + } + } + + impl<'a> fmt::Display for $FixedString<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Display>::fmt(self, f) + } + } + + impl<'a> fmt::Debug for $FixedString<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Debug>::fmt(self, f) + } + } + + impl<'a> cmp::PartialEq for $FixedString<'a> { + fn eq(&self, other: &$FixedString<'a>) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<[$char_t]> for $FixedString<'a> { + fn eq(&self, other: &[$char_t]) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b [$char_t]> for $FixedString<'a> { + fn eq(&self, other: &&'b [$char_t]) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> cmp::PartialEq for $FixedString<'a> { + fn eq(&self, other: &str) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b str> for $FixedString<'a> { + fn eq(&self, other: &&'b str) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> Drop for $FixedString<'a> { + fn drop(&mut self) { + unsafe { + self.finalize(); + } + } + } + } +} + +/////////////////////////////////////////// +// Bindings for nsCString (u8 char type) // +/////////////////////////////////////////// + +define_string_types! { + char_t = u8; + + AString = nsACString; + String = nsCString; + FixedString = nsFixedCString; + + StringRepr = nsCStringRepr; + FixedStringRepr = nsFixedCStringRepr; + AutoStringRepr = nsAutoCStringRepr; +} + +impl nsACString { + /// Leaves the nsACString in an unstable state with a dangling data pointer. + /// Should only be used in drop implementations of rust types which wrap + /// this type. + unsafe fn finalize(&mut self) { + Gecko_FinalizeCString(self); + } + + pub fn assign(&mut self, other: &nsACString) { + unsafe { + Gecko_AssignCString(self as *mut _, other as *const _); + } + } + + pub fn assign_utf16(&mut self, other: &nsAString) { + self.assign(&nsCString::new()); + self.append_utf16(other); + } + + pub fn append(&mut self, other: &nsACString) { + unsafe { + Gecko_AppendCString(self as *mut _, other as *const _); + } + } + + pub fn append_utf16(&mut self, other: &nsAString) { + unsafe { + Gecko_AppendUTF16toCString(self as *mut _, other as *const _); + } + } +} + +impl<'a> From<&'a str> for nsCString<'a> { + fn from(s: &'a str) -> nsCString<'a> { + s.as_bytes().into() + } +} + +impl From> for nsCString<'static> { + fn from(s: Box) -> nsCString<'static> { + s.into_string().into() + } +} + +impl From for nsCString<'static> { + fn from(s: String) -> nsCString<'static> { + s.into_bytes().into() + } +} + +// Support for the write!() macro for appending to nsACStrings +impl fmt::Write for nsACString { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + self.append(&nsCString::from(s)); + Ok(()) + } +} + +impl fmt::Display for nsACString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Display::fmt(&String::from_utf8_lossy(&self[..]), f) + } +} + +impl fmt::Debug for nsACString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&String::from_utf8_lossy(&self[..]), f) + } +} + +impl cmp::PartialEq for nsACString { + fn eq(&self, other: &str) -> bool { + &self[..] == other.as_bytes() + } +} + +#[macro_export] +macro_rules! ns_auto_cstring { + ($name:ident) => { + let mut buf: [u8; 64] = [0; 64]; + let mut $name = $crate::nsFixedCString::new(&mut buf); + } +} + +/////////////////////////////////////////// +// Bindings for nsString (u16 char type) // +/////////////////////////////////////////// + +define_string_types! { + char_t = u16; + + AString = nsAString; + String = nsString; + FixedString = nsFixedString; + + StringRepr = nsStringRepr; + FixedStringRepr = nsFixedStringRepr; + AutoStringRepr = nsAutoStringRepr; +} + +impl nsAString { + /// Leaves the nsAString in an unstable state with a dangling data pointer. + /// Should only be used in drop implementations of rust types which wrap + /// this type. + unsafe fn finalize(&mut self) { + Gecko_FinalizeString(self); + } + + pub fn assign(&mut self, other: &nsAString) { + unsafe { + Gecko_AssignString(self as *mut _, other as *const _); + } + } + + pub fn assign_utf8(&mut self, other: &nsACString) { + self.assign(&nsString::new()); + self.append_utf8(other); + } + + pub fn append(&mut self, other: &nsAString) { + unsafe { + Gecko_AppendString(self as *mut _, other as *const _); + } + } + + pub fn append_utf8(&mut self, other: &nsACString) { + unsafe { + Gecko_AppendUTF8toString(self as *mut _, other as *const _); + } + } +} + +// NOTE: The From impl for a string slice for nsString produces a <'static> +// lifetime, as it allocates. +impl<'a> From<&'a str> for nsString<'static> { + fn from(s: &'a str) -> nsString<'static> { + s.encode_utf16().collect::>().into() + } +} + +// Support for the write!() macro for writing to nsStrings +impl fmt::Write for nsAString { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + // Directly invoke gecko's routines for appending utf8 strings to + // nsAString values, to avoid as much overhead as possible + self.append_utf8(&nsCString::from(s)); + Ok(()) + } +} + +impl fmt::Display for nsAString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Display::fmt(&String::from_utf16_lossy(&self[..]), f) + } +} + +impl fmt::Debug for nsAString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&String::from_utf16_lossy(&self[..]), f) + } +} + +impl cmp::PartialEq for nsAString { + fn eq(&self, other: &str) -> bool { + other.encode_utf16().eq(self.iter().cloned()) + } +} + +#[macro_export] +macro_rules! ns_auto_string { + ($name:ident) => { + let mut buf: [u16; 64] = [0; 64]; + let mut $name = $crate::nsFixedString::new(&mut buf); + } +} + +// NOTE: These bindings currently only expose infallible operations. Perhaps +// consider allowing for fallible methods? +extern "C" { + // Gecko implementation in nsSubstring.cpp + fn Gecko_FinalizeCString(this: *mut nsACString); + fn Gecko_AssignCString(this: *mut nsACString, other: *const nsACString); + fn Gecko_AppendCString(this: *mut nsACString, other: *const nsACString); + + fn Gecko_FinalizeString(this: *mut nsAString); + fn Gecko_AssignString(this: *mut nsAString, other: *const nsAString); + fn Gecko_AppendString(this: *mut nsAString, other: *const nsAString); + + // Gecko implementation in nsReadableUtils.cpp + fn Gecko_AppendUTF16toCString(this: *mut nsACString, other: *const nsAString); + fn Gecko_AppendUTF8toString(this: *mut nsAString, other: *const nsACString); +} + +////////////////////////////////////// +// Repr Validation Helper Functions // +////////////////////////////////////// + +pub mod test_helpers { + //! This module only exists to help with ensuring that the layout of the + //! structs inside of rust and C++ are identical. + //! + //! It is public to ensure that these testing functions are avaliable to + //! gtest code. + + use super::{ + nsFixedCStringRepr, + nsFixedStringRepr, + nsCStringRepr, + nsStringRepr, + F_NONE, + F_TERMINATED, + F_VOIDED, + F_SHARED, + F_OWNED, + F_FIXED, + F_LITERAL, + F_CLASS_FIXED, + }; + use std::mem; + + /// Generates an #[no_mangle] extern "C" function which returns the size and + /// alignment of the given type with the given name. + macro_rules! size_align_check { + ($T:ty, $fname:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + pub extern fn $fname(size: *mut usize, align: *mut usize) { + unsafe { + *size = mem::size_of::<$T>(); + *align = mem::align_of::<$T>(); + } + } + } + } + + size_align_check!(nsStringRepr, Rust_Test_ReprSizeAlign_nsString); + size_align_check!(nsCStringRepr, Rust_Test_ReprSizeAlign_nsCString); + size_align_check!(nsFixedStringRepr, Rust_Test_ReprSizeAlign_nsFixedString); + size_align_check!(nsFixedCStringRepr, Rust_Test_ReprSizeAlign_nsFixedCString); + + /// Generates a $[no_mangle] extern "C" function which returns the size, + /// alignment and offset in the parent struct of a given member, with the + /// given name. + /// + /// This method can trigger Undefined Behavior if the accessing the member + /// $member on a given type would use that type's `Deref` implementation. + macro_rules! member_check { + ($T:ty, $member:ident, $method:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + pub extern fn $method(size: *mut usize, + align: *mut usize, + offset: *mut usize) { + unsafe { + // Create a temporary value of type T to get offsets, sizes + // and aligns off of + let tmp: $T = mem::zeroed(); + *size = mem::size_of_val(&tmp.$member); + *align = mem::align_of_val(&tmp.$member); + *offset = + (&tmp.$member as *const _ as usize) - + (&tmp as *const _ as usize); + mem::forget(tmp); + } + } + } + } + + member_check!(nsStringRepr, data, Rust_Test_Member_nsString_mData); + member_check!(nsStringRepr, length, Rust_Test_Member_nsString_mLength); + member_check!(nsStringRepr, flags, Rust_Test_Member_nsString_mFlags); + member_check!(nsCStringRepr, data, Rust_Test_Member_nsCString_mData); + member_check!(nsCStringRepr, length, Rust_Test_Member_nsCString_mLength); + member_check!(nsCStringRepr, flags, Rust_Test_Member_nsCString_mFlags); + member_check!(nsFixedStringRepr, capacity, Rust_Test_Member_nsFixedString_mFixedCapacity); + member_check!(nsFixedStringRepr, buffer, Rust_Test_Member_nsFixedString_mFixedBuf); + member_check!(nsFixedCStringRepr, capacity, Rust_Test_Member_nsFixedCString_mFixedCapacity); + member_check!(nsFixedCStringRepr, buffer, Rust_Test_Member_nsFixedCString_mFixedBuf); + + #[no_mangle] + #[allow(non_snake_case)] + pub extern fn Rust_Test_NsStringFlags(f_none: *mut u32, + f_terminated: *mut u32, + f_voided: *mut u32, + f_shared: *mut u32, + f_owned: *mut u32, + f_fixed: *mut u32, + f_literal: *mut u32, + f_class_fixed: *mut u32) { + unsafe { + *f_none = F_NONE; + *f_terminated = F_TERMINATED; + *f_voided = F_VOIDED; + *f_shared = F_SHARED; + *f_owned = F_OWNED; + *f_fixed = F_FIXED; + *f_literal = F_LITERAL; + *f_class_fixed = F_CLASS_FIXED; + } + } +} diff --git a/components/style/gecko_bindings/structs_debug.rs b/components/style/gecko_bindings/structs_debug.rs index 3e092c2cb0a5..635d4ea10a4b 100644 --- a/components/style/gecko_bindings/structs_debug.rs +++ b/components/style/gecko_bindings/structs_debug.rs @@ -1,6 +1,7 @@ /* automatically generated by rust-bindgen */ pub enum OpaqueStyleData {} +pub use nsstring::nsStringRepr as nsString; pub type ServoUnsafeCell = ::std::cell::UnsafeCell; pub type ServoCell = ::std::cell::Cell; pub type ServoNodeData = OpaqueStyleData; @@ -1780,31 +1781,6 @@ impl Clone for nsSubstringTuple { fn clone(&self) -> Self { *self } } #[repr(C)] -#[derive(Debug)] -pub struct nsString { - pub _base: nsAString_internal, -} -pub type nsString_self_type = nsString; -#[repr(C)] -#[derive(Debug, Copy)] -pub struct nsString_Segment { - pub mBegin: u32, - pub mLength: u32, -} -#[test] -fn bindgen_test_layout_nsString_Segment() { - assert_eq!(::std::mem::size_of::() , 8usize); - assert_eq!(::std::mem::align_of::() , 4usize); -} -impl Clone for nsString_Segment { - fn clone(&self) -> Self { *self } -} -#[test] -fn bindgen_test_layout_nsString() { - assert_eq!(::std::mem::size_of::() , 16usize); - assert_eq!(::std::mem::align_of::() , 8usize); -} -#[repr(C)] pub struct bindgen_vtable__bindgen_id_65148 { } #[repr(C)] diff --git a/components/style/gecko_bindings/structs_release.rs b/components/style/gecko_bindings/structs_release.rs index 664b4c3a85eb..a8ed19e5a170 100644 --- a/components/style/gecko_bindings/structs_release.rs +++ b/components/style/gecko_bindings/structs_release.rs @@ -1,6 +1,7 @@ /* automatically generated by rust-bindgen */ pub enum OpaqueStyleData {} +pub use nsstring::nsStringRepr as nsString; pub type ServoUnsafeCell = ::std::cell::UnsafeCell; pub type ServoCell = ::std::cell::Cell; pub type ServoNodeData = OpaqueStyleData; @@ -1780,31 +1781,6 @@ impl Clone for nsSubstringTuple { fn clone(&self) -> Self { *self } } #[repr(C)] -#[derive(Debug)] -pub struct nsString { - pub _base: nsAString_internal, -} -pub type nsString_self_type = nsString; -#[repr(C)] -#[derive(Debug, Copy)] -pub struct nsString_Segment { - pub mBegin: u32, - pub mLength: u32, -} -#[test] -fn bindgen_test_layout_nsString_Segment() { - assert_eq!(::std::mem::size_of::() , 8usize); - assert_eq!(::std::mem::align_of::() , 4usize); -} -impl Clone for nsString_Segment { - fn clone(&self) -> Self { *self } -} -#[test] -fn bindgen_test_layout_nsString() { - assert_eq!(::std::mem::size_of::() , 16usize); - assert_eq!(::std::mem::align_of::() , 8usize); -} -#[repr(C)] pub struct bindgen_vtable__bindgen_id_62951 { } #[repr(C)] diff --git a/components/style/lib.rs b/components/style/lib.rs index 6d576c842deb..c4adf08ef8fa 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -68,6 +68,7 @@ extern crate log; #[allow(unused_extern_crates)] #[macro_use] extern crate matches; +#[cfg(feature = "gecko")] extern crate nsstring_vendor as nsstring; extern crate num_integer; extern crate num_traits; #[cfg(feature = "gecko")] extern crate num_cpus; diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 0b643d12fc72..42390c3acf6d 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -451,7 +451,7 @@ impl Debug for ${style_struct.gecko_struct_name} { # These live in an nsFont member in Gecko. Should be straightforward to do manually. force_stub += ["font-kerning", "font-variant"] # These have unusual representations in gecko. - force_stub += ["list-style-type", "text-overflow"] + force_stub += ["list-style-type"] # In a nsTArray, have to be done manually, but probably not too much work # (the "filling them", not the "making them work") force_stub += ["animation-name", "animation-duration", @@ -1640,7 +1640,7 @@ fn static_assert() { <%self:impl_trait style_struct_name="Text" - skip_longhands="text-decoration-color text-decoration-line" + skip_longhands="text-decoration-color text-decoration-line text-overflow" skip_additionals="*"> ${impl_color("text_decoration_color", "mTextDecorationColor", need_clone=True)} @@ -1661,6 +1661,62 @@ fn static_assert() { ${impl_simple_copy('text_decoration_line', 'mTextDecorationLine')} + + fn clear_overflow_sides_if_string(&mut self) { + use gecko_bindings::structs::nsStyleTextOverflowSide; + use nsstring::nsString; + fn clear_if_string(side: &mut nsStyleTextOverflowSide) { + if side.mType == structs::NS_STYLE_TEXT_OVERFLOW_STRING as u8 { + side.mString.assign(&nsString::new()); + side.mType = structs::NS_STYLE_TEXT_OVERFLOW_CLIP as u8; + } + } + clear_if_string(&mut self.gecko.mTextOverflow.mLeft); + clear_if_string(&mut self.gecko.mTextOverflow.mRight); + } + pub fn set_text_overflow(&mut self, v: longhands::text_overflow::computed_value::T) { + use gecko_bindings::structs::nsStyleTextOverflowSide; + use properties::longhands::text_overflow::{SpecifiedValue, Side}; + + fn set(side: &mut nsStyleTextOverflowSide, value: &Side) { + use nsstring::nsCString; + let ty = match *value { + Side::Clip => structs::NS_STYLE_TEXT_OVERFLOW_CLIP, + Side::Ellipsis => structs::NS_STYLE_TEXT_OVERFLOW_ELLIPSIS, + Side::String(ref s) => { + side.mString.assign_utf8(&nsCString::from(&**s)); + structs::NS_STYLE_TEXT_OVERFLOW_STRING + } + }; + side.mType = ty as u8; + } + + self.clear_overflow_sides_if_string(); + if v.second.is_none() { + self.gecko.mTextOverflow.mLogicalDirections = true; + } + + let SpecifiedValue { ref first, ref second } = v; + let second = second.as_ref().unwrap_or(&first); + + set(&mut self.gecko.mTextOverflow.mLeft, first); + set(&mut self.gecko.mTextOverflow.mRight, second); + } + + pub fn copy_text_overflow_from(&mut self, other: &Self) { + use gecko_bindings::structs::nsStyleTextOverflowSide; + fn set(side: &mut nsStyleTextOverflowSide, other: &nsStyleTextOverflowSide) { + if other.mType == structs::NS_STYLE_TEXT_OVERFLOW_STRING as u8 { + side.mString.assign(&other.mString) + } + side.mType = other.mType + } + self.clear_overflow_sides_if_string(); + set(&mut self.gecko.mTextOverflow.mLeft, &other.gecko.mTextOverflow.mLeft); + set(&mut self.gecko.mTextOverflow.mRight, &other.gecko.mTextOverflow.mRight); + self.gecko.mTextOverflow.mLogicalDirections = other.gecko.mTextOverflow.mLogicalDirections; + } + #[inline] pub fn has_underline(&self) -> bool { (self.gecko.mTextDecorationLine & (structs::NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE as u8)) != 0 diff --git a/components/style/properties/longhand/text.mako.rs b/components/style/properties/longhand/text.mako.rs index bb65836915aa..94e1981b03c7 100644 --- a/components/style/properties/longhand/text.mako.rs +++ b/components/style/properties/longhand/text.mako.rs @@ -12,7 +12,88 @@ Method("has_overline", "bool"), Method("has_line_through", "bool")]) %> -${helpers.single_keyword("text-overflow", "clip ellipsis", animatable=False)} +% if product == "servo": + ${helpers.single_keyword("text-overflow", "clip ellipsis", animatable=False)} +% else: +<%helpers:longhand name="text-overflow" animatable="False"> + use cssparser::ToCss; + use std::fmt; + use values::computed::ComputedValueAsSpecified; + use values::NoViewportPercentage; + + impl ComputedValueAsSpecified for SpecifiedValue {} + impl NoViewportPercentage for SpecifiedValue {} + + #[derive(PartialEq, Eq, Clone, Debug)] + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub enum Side { + Clip, + Ellipsis, + String(String), + } + + #[derive(PartialEq, Eq, Clone, Debug)] + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub struct SpecifiedValue { + pub first: Side, + pub second: Option + } + + pub mod computed_value { + pub type T = super::SpecifiedValue; + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + SpecifiedValue { + first: Side::Clip, + second: None + } + } + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + let first = try!(Side::parse(input)); + let second = Side::parse(input).ok(); + Ok(SpecifiedValue { + first: first, + second: second, + }) + } + impl Parse for Side { + fn parse(input: &mut Parser) -> Result { + if let Ok(ident) = input.try(|input| input.expect_ident()) { + match_ignore_ascii_case! { ident, + "clip" => Ok(Side::Clip), + "ellipsis" => Ok(Side::Ellipsis), + _ => Err(()) + } + } else { + Ok(Side::String(try!(input.expect_string()).into_owned())) + } + } + } + + impl ToCss for Side { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + Side::Clip => dest.write_str("clip"), + Side::Ellipsis => dest.write_str("ellipsis"), + Side::String(ref s) => dest.write_str(s) + } + } + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.first.to_css(dest)); + if let Some(ref second) = self.second { + try!(dest.write_str(" ")); + try!(second.to_css(dest)); + } + Ok(()) + } + } + +% endif ${helpers.single_keyword("unicode-bidi", "normal embed isolate bidi-override isolate-override plaintext", diff --git a/ports/geckolib/Cargo.lock b/ports/geckolib/Cargo.lock index 17b8d28f8681..89f2bbfaa0c3 100644 --- a/ports/geckolib/Cargo.lock +++ b/ports/geckolib/Cargo.lock @@ -215,6 +215,10 @@ dependencies = [ "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nsstring_vendor" +version = "0.1.0" + [[package]] name = "num-integer" version = "0.1.32" @@ -376,6 +380,7 @@ dependencies = [ "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "nsstring_vendor 0.1.0", "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/servo-tidy.toml b/servo-tidy.toml index d2317d17b9ff..f96cadc62cfc 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -45,6 +45,7 @@ directories = [ # Generated and upstream code combined with our own. Could use cleanup "./target", "./ports/cef", + "./components/style/gecko_bindings/nsstring_vendor/", ] # Directories that are checked for correct file extension