diff --git a/Cargo.lock b/Cargo.lock index 313bd7d2e41..7fcfe69f26e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,9 +1204,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.10.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", @@ -1214,9 +1214,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.10.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", @@ -1227,9 +1227,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.10.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", @@ -1601,14 +1601,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -[[package]] -name = "fallible" -version = "0.0.1" -dependencies = [ - "hashglobe", - "smallvec", -] - [[package]] name = "fastrand" version = "1.7.0" @@ -2598,14 +2590,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -[[package]] -name = "hashglobe" -version = "0.1.0" -dependencies = [ - "libc", - "rand 0.7.3", -] - [[package]] name = "headers" version = "0.3.8" @@ -2775,9 +2759,9 @@ dependencies = [ [[package]] name = "ident_case" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9826188e666f2ed92071d2dadef6edc430b11b158b5b2b3f4babbcc891eaaa" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" @@ -3461,7 +3445,6 @@ dependencies = [ "crossbeam-channel 0.4.4", "cssparser", "euclid", - "hashglobe", "http", "hyper_serde", "keyboard-types", @@ -3472,7 +3455,6 @@ dependencies = [ "smallbitvec", "smallvec", "string_cache", - "thin-slice", "time 0.1.45", "tokio", "url", @@ -3749,6 +3731,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "mozbuild" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903970ae2f248d7275214cf8f387f8ba0c4ea7e3d87a320e85493db60ce28616" + [[package]] name = "mozjs" version = "0.14.1" @@ -5970,9 +5958,7 @@ dependencies = [ "derive_more", "encoding_rs", "euclid", - "fallible", "fxhash", - "hashglobe", "html5ever", "indexmap", "itertools", @@ -5982,6 +5968,7 @@ dependencies = [ "malloc_size_of", "malloc_size_of_derive", "mime", + "mozbuild", "new_debug_unreachable", "num-derive", "num-integer", @@ -6003,7 +5990,6 @@ dependencies = [ "string_cache", "style_derive", "style_traits", - "thin-slice", "time 0.1.45", "to_shmem", "to_shmem_derive", @@ -6216,12 +6202,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thiserror" version = "1.0.38" @@ -6371,7 +6351,6 @@ dependencies = [ "smallbitvec", "smallvec", "string_cache", - "thin-slice", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 190f502fe00..62d66812f3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ cookie = "0.12" content-security-policy = { version = "0.5", features = ["serde"]} crossbeam-channel = "0.4" cssparser = "0.29" -darling = { version = "0.10", default-features = false } +darling = { version = "0.13", default-features = false } data-url = "0.1.0" env_logger = "0.8" fnv = "1.0" diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 2d0ef951067..7e4f385169e 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -1,3 +1,7 @@ +-moz-gtk-csd-close-button-position +-moz-gtk-csd-maximize-button-position +-moz-gtk-csd-menu-radius +-moz-gtk-csd-minimize-button-position -moz-gtk-csd-titlebar-radius -moz-gtk-menu-radius DOMContentLoaded diff --git a/components/fallible/Cargo.toml b/components/fallible/Cargo.toml deleted file mode 100644 index 3d4f9f5549c..00000000000 --- a/components/fallible/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "fallible" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -edition = "2018" -publish = false - -[lib] -name = "fallible" -path = "lib.rs" - -[dependencies] -hashglobe = { path = "../hashglobe" } -smallvec = { workspace = true } - -# This crate effectively does nothing except if the `known_system_malloc` -# feature is specified. -# -# In that case, we actually call the system malloc functions to reserve space, -# otherwise we just let Rust do its thing (aborting on OOM). -# -# This is effectively a stop-gap measure until we can do this properly in -# stable Rust. -[features] -known_system_malloc = [] diff --git a/components/fallible/lib.rs b/components/fallible/lib.rs deleted file mode 100644 index f7506afd510..00000000000 --- a/components/fallible/lib.rs +++ /dev/null @@ -1,160 +0,0 @@ -/* 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/. */ - -#[cfg(feature = "known_system_malloc")] -use hashglobe::alloc; -use hashglobe::FailedAllocationError; -use smallvec::Array; -use smallvec::SmallVec; -use std::vec::Vec; - -pub trait FallibleVec { - /// Append |val| to the end of |vec|. Returns Ok(()) on success, - /// Err(reason) if it fails, with |reason| describing the failure. - fn try_push(&mut self, value: T) -> Result<(), FailedAllocationError>; -} - -///////////////////////////////////////////////////////////////// -// Vec - -impl FallibleVec for Vec { - #[inline(always)] - fn try_push(&mut self, val: T) -> Result<(), FailedAllocationError> { - #[cfg(feature = "known_system_malloc")] - { - if self.capacity() == self.len() { - try_double_vec(self)?; - debug_assert!(self.capacity() > self.len()); - } - } - self.push(val); - Ok(()) - } -} - -// Double the capacity of |vec|, or fail to do so due to lack of memory. -// Returns Ok(()) on success, Err(..) on failure. -#[cfg(feature = "known_system_malloc")] -#[inline(never)] -#[cold] -fn try_double_vec(vec: &mut Vec) -> Result<(), FailedAllocationError> { - use std::mem; - - let old_ptr = vec.as_mut_ptr(); - let old_len = vec.len(); - - let old_cap: usize = vec.capacity(); - let new_cap: usize = if old_cap == 0 { - 4 - } else { - old_cap - .checked_mul(2) - .ok_or(FailedAllocationError::new("capacity overflow for Vec"))? - }; - - let new_size_bytes = new_cap - .checked_mul(mem::size_of::()) - .ok_or(FailedAllocationError::new("capacity overflow for Vec"))?; - - let new_ptr = unsafe { - if old_cap == 0 { - alloc::alloc(new_size_bytes, 0) - } else { - alloc::realloc(old_ptr as *mut u8, new_size_bytes) - } - }; - - if new_ptr.is_null() { - return Err(FailedAllocationError::new( - "out of memory when allocating Vec", - )); - } - - let new_vec = unsafe { Vec::from_raw_parts(new_ptr as *mut T, old_len, new_cap) }; - - mem::forget(mem::replace(vec, new_vec)); - Ok(()) -} - -///////////////////////////////////////////////////////////////// -// SmallVec - -impl FallibleVec for SmallVec { - #[inline(always)] - fn try_push(&mut self, val: T::Item) -> Result<(), FailedAllocationError> { - #[cfg(feature = "known_system_malloc")] - { - if self.capacity() == self.len() { - try_double_small_vec(self)?; - debug_assert!(self.capacity() > self.len()); - } - } - self.push(val); - Ok(()) - } -} - -// Double the capacity of |svec|, or fail to do so due to lack of memory. -// Returns Ok(()) on success, Err(..) on failure. -#[cfg(feature = "known_system_malloc")] -#[inline(never)] -#[cold] -fn try_double_small_vec(svec: &mut SmallVec) -> Result<(), FailedAllocationError> -where - T: Array, -{ - use std::mem; - use std::ptr::copy_nonoverlapping; - - let old_ptr = svec.as_mut_ptr(); - let old_len = svec.len(); - - let old_cap: usize = svec.capacity(); - let new_cap: usize = if old_cap == 0 { - 4 - } else { - old_cap - .checked_mul(2) - .ok_or(FailedAllocationError::new("capacity overflow for SmallVec"))? - }; - - // This surely shouldn't fail, if |old_cap| was previously accepted as a - // valid value. But err on the side of caution. - let old_size_bytes = old_cap - .checked_mul(mem::size_of::()) - .ok_or(FailedAllocationError::new("capacity overflow for SmallVec"))?; - - let new_size_bytes = new_cap - .checked_mul(mem::size_of::()) - .ok_or(FailedAllocationError::new("capacity overflow for SmallVec"))?; - - let new_ptr; - if svec.spilled() { - // There's an old block to free, and, presumably, old contents to - // copy. realloc takes care of both aspects. - unsafe { - new_ptr = alloc::realloc(old_ptr as *mut u8, new_size_bytes); - } - } else { - // There's no old block to free. There may be old contents to copy. - unsafe { - new_ptr = alloc::alloc(new_size_bytes, 0); - if !new_ptr.is_null() && old_size_bytes > 0 { - copy_nonoverlapping(old_ptr as *const u8, new_ptr as *mut u8, old_size_bytes); - } - } - } - - if new_ptr.is_null() { - return Err(FailedAllocationError::new( - "out of memory when allocating SmallVec", - )); - } - - let new_vec = unsafe { Vec::from_raw_parts(new_ptr as *mut T::Item, old_len, new_cap) }; - - let new_svec = SmallVec::from_vec(new_vec); - mem::forget(mem::replace(svec, new_svec)); - Ok(()) -} diff --git a/components/gfx/tests/font_context.rs b/components/gfx/tests/font_context.rs index 0cda196f12d..796f693f418 100644 --- a/components/gfx/tests/font_context.rs +++ b/components/gfx/tests/font_context.rs @@ -124,6 +124,7 @@ fn font_family(names: Vec<&str>) -> FontFamily { list: names.into_boxed_slice(), }, is_system_font: false, + is_initial: false, } } diff --git a/components/hashglobe/Cargo.toml b/components/hashglobe/Cargo.toml deleted file mode 100644 index e453136c43f..00000000000 --- a/components/hashglobe/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "hashglobe" -version = "0.1.0" -authors = ["The Rust Project Developers", "Manish Goregaokar "] -license = "MIT OR Apache-2.0" -description = "Fork of std::HashMap with stable fallible allocation." -documentation = "https://docs.rs/hashglobe" -repository = "https://github.com/Manishearth/hashglobe" -readme = "README.md" -edition = "2018" - -[dependencies] -libc = { workspace = true } - -[dev-dependencies] -rand = { workspace = true } diff --git a/components/hashglobe/LICENSE-APACHE b/components/hashglobe/LICENSE-APACHE deleted file mode 100644 index 16fe87b06e8..00000000000 --- a/components/hashglobe/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/components/hashglobe/LICENSE-MIT b/components/hashglobe/LICENSE-MIT deleted file mode 100644 index 31aa79387f2..00000000000 --- a/components/hashglobe/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/components/hashglobe/README.md b/components/hashglobe/README.md deleted file mode 100644 index e2f1df4fa97..00000000000 --- a/components/hashglobe/README.md +++ /dev/null @@ -1,17 +0,0 @@ -hashglobe -======== - - -This is a fork of Rust's `std::HashMap`. It works on stable out of the stdlib and has fallible APIs. - -We intend to diverge as little as possible from the original hashmap. - - -Dual licensed Apache/MIT, the same as the stdlib. - - -## Should I use this? - -No. - -Wait for https://github.com/rust-lang/rfcs/pull/2116 instead. diff --git a/components/hashglobe/src/alloc.rs b/components/hashglobe/src/alloc.rs deleted file mode 100644 index 50e86ee4af6..00000000000 --- a/components/hashglobe/src/alloc.rs +++ /dev/null @@ -1,160 +0,0 @@ -// FORK NOTE: Copied from liballoc_system, removed unnecessary APIs, -// APIs take size/align directly instead of Layout - -// The minimum alignment guaranteed by the architecture. This value is used to -// add fast paths for low alignment values. In practice, the alignment is a -// constant at the call site and the branch will be optimized out. -#[cfg(all(any( - target_arch = "x86", - target_arch = "arm", - target_arch = "mips", - target_arch = "powerpc", - target_arch = "powerpc64", - target_arch = "asmjs", - target_arch = "wasm32" -)))] -const MIN_ALIGN: usize = 8; -#[cfg(all(any( - target_arch = "x86_64", - target_arch = "aarch64", - target_arch = "mips64", - target_arch = "s390x", - target_arch = "sparc64" -)))] -const MIN_ALIGN: usize = 16; - -pub use self::platform::{alloc, dealloc, realloc}; - -#[cfg(any(unix, target_os = "redox"))] -mod platform { - use libc; - - #[cfg(not(any(target_os = "android")))] - use std::ptr; - - use super::MIN_ALIGN; - - #[inline] - pub unsafe fn alloc(size: usize, align: usize) -> *mut u8 { - if align <= MIN_ALIGN { - libc::malloc(size) as *mut u8 - } else { - aligned_malloc(size, align) - } - } - - #[inline] - pub unsafe fn dealloc(ptr: *mut u8, _align: usize) { - libc::free(ptr as *mut libc::c_void) - } - - #[inline] - pub unsafe fn realloc(ptr: *mut u8, new_size: usize) -> *mut u8 { - libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 - } - - #[cfg(any(target_os = "android", target_os = "redox"))] - #[inline] - unsafe fn aligned_malloc(size: usize, align: usize) -> *mut u8 { - // On android we currently target API level 9 which unfortunately - // doesn't have the `posix_memalign` API used below. Instead we use - // `memalign`, but this unfortunately has the property on some systems - // where the memory returned cannot be deallocated by `free`! - // - // Upon closer inspection, however, this appears to work just fine with - // Android, so for this platform we should be fine to call `memalign` - // (which is present in API level 9). Some helpful references could - // possibly be chromium using memalign [1], attempts at documenting that - // memalign + free is ok [2] [3], or the current source of chromium - // which still uses memalign on android [4]. - // - // [1]: https://codereview.chromium.org/10796020/ - // [2]: https://code.google.com/p/android/issues/detail?id=35391 - // [3]: https://bugs.chromium.org/p/chromium/issues/detail?id=138579 - // [4]: https://chromium.googlesource.com/chromium/src/base/+/master/ - // /memory/aligned_memory.cc - libc::memalign(align, size) as *mut u8 - } - - #[cfg(not(any(target_os = "android", target_os = "redox")))] - #[inline] - unsafe fn aligned_malloc(size: usize, align: usize) -> *mut u8 { - let mut out = ptr::null_mut(); - let ret = libc::posix_memalign(&mut out, align, size); - if ret != 0 { - ptr::null_mut() - } else { - out as *mut u8 - } - } -} - -#[cfg(windows)] -#[allow(bad_style)] -mod platform { - - use super::MIN_ALIGN; - type LPVOID = *mut u8; - type HANDLE = LPVOID; - type SIZE_T = usize; - type DWORD = u32; - type BOOL = i32; - - extern "system" { - fn GetProcessHeap() -> HANDLE; - fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) -> LPVOID; - fn HeapReAlloc(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID, dwBytes: SIZE_T) -> LPVOID; - fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL; - fn GetLastError() -> DWORD; - } - - #[repr(C)] - struct Header(*mut u8); - - unsafe fn get_header<'a>(ptr: *mut u8) -> &'a mut Header { - &mut *(ptr as *mut Header).offset(-1) - } - - unsafe fn align_ptr(ptr: *mut u8, align: usize) -> *mut u8 { - let aligned = ptr.offset((align - (ptr as usize & (align - 1))) as isize); - *get_header(aligned) = Header(ptr); - aligned - } - - #[inline] - unsafe fn allocate_with_flags(size: usize, align: usize, flags: DWORD) -> *mut u8 { - if align <= MIN_ALIGN { - HeapAlloc(GetProcessHeap(), flags, size) - } else { - let size = size + align; - let ptr = HeapAlloc(GetProcessHeap(), flags, size); - if ptr.is_null() { - ptr - } else { - align_ptr(ptr, align) - } - } - } - - #[inline] - pub unsafe fn alloc(size: usize, align: usize) -> *mut u8 { - allocate_with_flags(size, align, 0) - } - - #[inline] - pub unsafe fn dealloc(ptr: *mut u8, align: usize) { - if align <= MIN_ALIGN { - let err = HeapFree(GetProcessHeap(), 0, ptr as LPVOID); - debug_assert!(err != 0, "Failed to free heap memory: {}", GetLastError()); - } else { - let header = get_header(ptr); - let err = HeapFree(GetProcessHeap(), 0, header.0 as LPVOID); - debug_assert!(err != 0, "Failed to free heap memory: {}", GetLastError()); - } - } - - #[inline] - pub unsafe fn realloc(ptr: *mut u8, new_size: usize) -> *mut u8 { - HeapReAlloc(GetProcessHeap(), 0, ptr as LPVOID, new_size) as *mut u8 - } -} diff --git a/components/hashglobe/src/fake.rs b/components/hashglobe/src/fake.rs deleted file mode 100644 index d544721a1a2..00000000000 --- a/components/hashglobe/src/fake.rs +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! This module contains shims around the stdlib HashMap -//! that add fallible methods -//! -//! These methods are a lie. They are not actually fallible. This is just to make -//! it smooth to switch between hashmap impls in a codebase. - -use std::collections::HashMap as StdMap; -use std::collections::HashSet as StdSet; -use std::fmt; -use std::hash::{BuildHasher, Hash}; -use std::ops::{Deref, DerefMut}; - -pub use std::collections::hash_map::{Entry, Iter as MapIter, IterMut as MapIterMut, RandomState}; -pub use std::collections::hash_set::{IntoIter as SetIntoIter, Iter as SetIter}; - -#[derive(Clone)] -pub struct HashMap(StdMap); - -use crate::FailedAllocationError; - -impl Deref for HashMap { - type Target = StdMap; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for HashMap { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - #[inline] - pub fn try_with_hasher(hash_builder: S) -> Result, FailedAllocationError> { - Ok(HashMap(StdMap::with_hasher(hash_builder))) - } - - #[inline] - pub fn try_with_capacity_and_hasher( - capacity: usize, - hash_builder: S, - ) -> Result, FailedAllocationError> { - Ok(HashMap(StdMap::with_capacity_and_hasher( - capacity, - hash_builder, - ))) - } - - pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> HashMap { - HashMap(StdMap::with_capacity_and_hasher(capacity, hash_builder)) - } - - #[inline] - pub fn try_reserve(&mut self, additional: usize) -> Result<(), FailedAllocationError> { - Ok(self.reserve(additional)) - } - - pub fn try_shrink_to_fit(&mut self) -> Result<(), FailedAllocationError> { - Ok(self.shrink_to_fit()) - } - - pub fn try_entry(&mut self, key: K) -> Result, FailedAllocationError> { - Ok(self.entry(key)) - } - - #[inline] - pub fn try_insert(&mut self, k: K, v: V) -> Result, FailedAllocationError> { - Ok(self.insert(k, v)) - } -} - -#[derive(Clone)] -pub struct HashSet(StdSet); - -impl Deref for HashSet { - type Target = StdSet; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for HashSet { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl HashSet { - #[inline] - pub fn new() -> HashSet { - HashSet(StdSet::new()) - } - - #[inline] - pub fn with_capacity(capacity: usize) -> HashSet { - HashSet(StdSet::with_capacity(capacity)) - } -} - -impl HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - #[inline] - pub fn with_hasher(hasher: S) -> HashSet { - HashSet(StdSet::with_hasher(hasher)) - } - - #[inline] - pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> HashSet { - HashSet(StdSet::with_capacity_and_hasher(capacity, hasher)) - } - - #[inline] - pub fn try_reserve(&mut self, additional: usize) -> Result<(), FailedAllocationError> { - Ok(self.reserve(additional)) - } - - #[inline] - pub fn try_shrink_to_fit(&mut self) -> Result<(), FailedAllocationError> { - Ok(self.shrink_to_fit()) - } - - #[inline] - pub fn try_insert(&mut self, value: T) -> Result { - Ok(self.insert(value)) - } -} - -// Pass through trait impls -// We can't derive these since the bounds are not obvious to the derive macro - -impl Default for HashMap { - fn default() -> Self { - HashMap(Default::default()) - } -} - -impl fmt::Debug for HashMap -where - K: Eq + Hash + fmt::Debug, - V: fmt::Debug, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl PartialEq for HashMap -where - K: Eq + Hash, - V: PartialEq, - S: BuildHasher, -{ - fn eq(&self, other: &HashMap) -> bool { - self.0.eq(&other.0) - } -} - -impl Eq for HashMap -where - K: Eq + Hash, - V: Eq, - S: BuildHasher, -{ -} - -impl<'a, K, V, S> IntoIterator for &'a HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (&'a K, &'a V); - type IntoIter = MapIter<'a, K, V>; - - fn into_iter(self) -> MapIter<'a, K, V> { - self.0.iter() - } -} - -impl<'a, K, V, S> IntoIterator for &'a mut HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (&'a K, &'a mut V); - type IntoIter = MapIterMut<'a, K, V>; - - fn into_iter(self) -> MapIterMut<'a, K, V> { - self.0.iter_mut() - } -} - -impl Default for HashSet { - fn default() -> Self { - HashSet(Default::default()) - } -} - -impl fmt::Debug for HashSet -where - T: Eq + Hash + fmt::Debug, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl PartialEq for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - fn eq(&self, other: &HashSet) -> bool { - self.0.eq(&other.0) - } -} - -impl Eq for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ -} - -impl<'a, T, S> IntoIterator for &'a HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - type IntoIter = SetIter<'a, T>; - - fn into_iter(self) -> SetIter<'a, T> { - self.0.iter() - } -} - -impl IntoIterator for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = T; - type IntoIter = SetIntoIter; - - fn into_iter(self) -> SetIntoIter { - self.0.into_iter() - } -} diff --git a/components/hashglobe/src/hash_map.rs b/components/hashglobe/src/hash_map.rs deleted file mode 100644 index e122a82aabb..00000000000 --- a/components/hashglobe/src/hash_map.rs +++ /dev/null @@ -1,3087 +0,0 @@ -// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use self::Entry::*; -use self::VacantEntryState::*; - -use std::borrow::Borrow; -use std::cmp::max; -use std::fmt::{self, Debug}; -#[allow(deprecated)] -use std::hash::{BuildHasher, Hash}; -use std::iter::FromIterator; -use std::mem::{self, replace}; -use std::ops::{Deref, Index}; - -use super::table::BucketState::{Empty, Full}; -use super::table::{self, Bucket, EmptyBucket, FullBucket, FullBucketMut, RawTable, SafeHash}; - -use crate::FailedAllocationError; - -const MIN_NONZERO_RAW_CAPACITY: usize = 32; // must be a power of two - -/// The default behavior of HashMap implements a maximum load factor of 90.9%. -#[derive(Clone)] -struct DefaultResizePolicy; - -impl DefaultResizePolicy { - fn new() -> DefaultResizePolicy { - DefaultResizePolicy - } - - /// A hash map's "capacity" is the number of elements it can hold without - /// being resized. Its "raw capacity" is the number of slots required to - /// provide that capacity, accounting for maximum loading. The raw capacity - /// is always zero or a power of two. - #[inline] - fn raw_capacity(&self, len: usize) -> usize { - if len == 0 { - 0 - } else { - // 1. Account for loading: `raw_capacity >= len * 1.1`. - // 2. Ensure it is a power of two. - // 3. Ensure it is at least the minimum size. - let mut raw_cap = len * 11 / 10; - assert!(raw_cap >= len, "raw_cap overflow"); - raw_cap = raw_cap - .checked_next_power_of_two() - .expect("raw_capacity overflow"); - raw_cap = max(MIN_NONZERO_RAW_CAPACITY, raw_cap); - raw_cap - } - } - - /// The capacity of the given raw capacity. - #[inline] - fn capacity(&self, raw_cap: usize) -> usize { - // This doesn't have to be checked for overflow since allocation size - // in bytes will overflow earlier than multiplication by 10. - // - // As per https://github.com/rust-lang/rust/pull/30991 this is updated - // to be: (raw_cap * den + den - 1) / num - (raw_cap * 10 + 10 - 1) / 11 - } -} - -// The main performance trick in this hashmap is called Robin Hood Hashing. -// It gains its excellent performance from one essential operation: -// -// If an insertion collides with an existing element, and that element's -// "probe distance" (how far away the element is from its ideal location) -// is higher than how far we've already probed, swap the elements. -// -// This massively lowers variance in probe distance, and allows us to get very -// high load factors with good performance. The 90% load factor I use is rather -// conservative. -// -// > Why a load factor of approximately 90%? -// -// In general, all the distances to initial buckets will converge on the mean. -// At a load factor of α, the odds of finding the target bucket after k -// probes is approximately 1-α^k. If we set this equal to 50% (since we converge -// on the mean) and set k=8 (64-byte cache line / 8-byte hash), α=0.92. I round -// this down to make the math easier on the CPU and avoid its FPU. -// Since on average we start the probing in the middle of a cache line, this -// strategy pulls in two cache lines of hashes on every lookup. I think that's -// pretty good, but if you want to trade off some space, it could go down to one -// cache line on average with an α of 0.84. -// -// > Wait, what? Where did you get 1-α^k from? -// -// On the first probe, your odds of a collision with an existing element is α. -// The odds of doing this twice in a row is approximately α^2. For three times, -// α^3, etc. Therefore, the odds of colliding k times is α^k. The odds of NOT -// colliding after k tries is 1-α^k. -// -// The paper from 1986 cited below mentions an implementation which keeps track -// of the distance-to-initial-bucket histogram. This approach is not suitable -// for modern architectures because it requires maintaining an internal data -// structure. This allows very good first guesses, but we are most concerned -// with guessing entire cache lines, not individual indexes. Furthermore, array -// accesses are no longer linear and in one direction, as we have now. There -// is also memory and cache pressure that this would entail that would be very -// difficult to properly see in a microbenchmark. -// -// ## Future Improvements (FIXME!) -// -// Allow the load factor to be changed dynamically and/or at initialization. -// -// Also, would it be possible for us to reuse storage when growing the -// underlying table? This is exactly the use case for 'realloc', and may -// be worth exploring. -// -// ## Future Optimizations (FIXME!) -// -// Another possible design choice that I made without any real reason is -// parameterizing the raw table over keys and values. Technically, all we need -// is the size and alignment of keys and values, and the code should be just as -// efficient (well, we might need one for power-of-two size and one for not...). -// This has the potential to reduce code bloat in rust executables, without -// really losing anything except 4 words (key size, key alignment, val size, -// val alignment) which can be passed in to every call of a `RawTable` function. -// This would definitely be an avenue worth exploring if people start complaining -// about the size of rust executables. -// -// Annotate exceedingly likely branches in `table::make_hash` -// and `search_hashed` to reduce instruction cache pressure -// and mispredictions once it becomes possible (blocked on issue #11092). -// -// Shrinking the table could simply reallocate in place after moving buckets -// to the first half. -// -// The growth algorithm (fragment of the Proof of Correctness) -// -------------------- -// -// The growth algorithm is basically a fast path of the naive reinsertion- -// during-resize algorithm. Other paths should never be taken. -// -// Consider growing a robin hood hashtable of capacity n. Normally, we do this -// by allocating a new table of capacity `2n`, and then individually reinsert -// each element in the old table into the new one. This guarantees that the -// new table is a valid robin hood hashtable with all the desired statistical -// properties. Remark that the order we reinsert the elements in should not -// matter. For simplicity and efficiency, we will consider only linear -// reinsertions, which consist of reinserting all elements in the old table -// into the new one by increasing order of index. However we will not be -// starting our reinsertions from index 0 in general. If we start from index -// i, for the purpose of reinsertion we will consider all elements with real -// index j < i to have virtual index n + j. -// -// Our hash generation scheme consists of generating a 64-bit hash and -// truncating the most significant bits. When moving to the new table, we -// simply introduce a new bit to the front of the hash. Therefore, if an -// elements has ideal index i in the old table, it can have one of two ideal -// locations in the new table. If the new bit is 0, then the new ideal index -// is i. If the new bit is 1, then the new ideal index is n + i. Intuitively, -// we are producing two independent tables of size n, and for each element we -// independently choose which table to insert it into with equal probability. -// However the rather than wrapping around themselves on overflowing their -// indexes, the first table overflows into the first, and the first into the -// second. Visually, our new table will look something like: -// -// [yy_xxx_xxxx_xxx|xx_yyy_yyyy_yyy] -// -// Where x's are elements inserted into the first table, y's are elements -// inserted into the second, and _'s are empty sections. We now define a few -// key concepts that we will use later. Note that this is a very abstract -// perspective of the table. A real resized table would be at least half -// empty. -// -// Theorem: A linear robin hood reinsertion from the first ideal element -// produces identical results to a linear naive reinsertion from the same -// element. -// -// FIXME(Gankro, pczarn): review the proof and put it all in a separate README.md -// -// Adaptive early resizing -// ---------------------- -// To protect against degenerate performance scenarios (including DOS attacks), -// the implementation includes an adaptive behavior that can resize the map -// early (before its capacity is exceeded) when suspiciously long probe sequences -// are encountered. -// -// With this algorithm in place it would be possible to turn a CPU attack into -// a memory attack due to the aggressive resizing. To prevent that the -// adaptive behavior only triggers when the map is at least half full. -// This reduces the effectiveness of the algorithm but also makes it completely safe. -// -// The previous safety measure also prevents degenerate interactions with -// really bad quality hash algorithms that can make normal inputs look like a -// DOS attack. -// -const DISPLACEMENT_THRESHOLD: usize = 128; -// -// The threshold of 128 is chosen to minimize the chance of exceeding it. -// In particular, we want that chance to be less than 10^-8 with a load of 90%. -// For displacement, the smallest constant that fits our needs is 90, -// so we round that up to 128. -// -// At a load factor of α, the odds of finding the target bucket after exactly n -// unsuccessful probes[1] are -// -// Pr_α{displacement = n} = -// (1 - α) / α * ∑_{k≥1} e^(-kα) * (kα)^(k+n) / (k + n)! * (1 - kα / (k + n + 1)) -// -// We use this formula to find the probability of triggering the adaptive behavior -// -// Pr_0.909{displacement > 128} = 1.601 * 10^-11 -// -// 1. Alfredo Viola (2005). Distributional analysis of Robin Hood linear probing -// hashing with buckets. - -/// A hash map implemented with linear probing and Robin Hood bucket stealing. -/// -/// By default, `HashMap` uses a hashing algorithm selected to provide -/// resistance against HashDoS attacks. The algorithm is randomly seeded, and a -/// reasonable best-effort is made to generate this seed from a high quality, -/// secure source of randomness provided by the host without blocking the -/// program. Because of this, the randomness of the seed depends on the output -/// quality of the system's random number generator when the seed is created. -/// In particular, seeds generated when the system's entropy pool is abnormally -/// low such as during system boot may be of a lower quality. -/// -/// The default hashing algorithm is currently SipHash 1-3, though this is -/// subject to change at any point in the future. While its performance is very -/// competitive for medium sized keys, other hashing algorithms will outperform -/// it for small keys such as integers as well as large keys such as long -/// strings, though those algorithms will typically *not* protect against -/// attacks such as HashDoS. -/// -/// The hashing algorithm can be replaced on a per-`HashMap` basis using the -/// [`default`], [`with_hasher`], and [`with_capacity_and_hasher`] methods. Many -/// alternative algorithms are available on crates.io, such as the [`fnv`] crate. -/// -/// It is required that the keys implement the [`Eq`] and [`Hash`] traits, although -/// this can frequently be achieved by using `#[derive(PartialEq, Eq, Hash)]`. -/// If you implement these yourself, it is important that the following -/// property holds: -/// -/// ```text -/// k1 == k2 -> hash(k1) == hash(k2) -/// ``` -/// -/// In other words, if two keys are equal, their hashes must be equal. -/// -/// It is a logic error for a key to be modified in such a way that the key's -/// hash, as determined by the [`Hash`] trait, or its equality, as determined by -/// the [`Eq`] trait, changes while it is in the map. This is normally only -/// possible through [`Cell`], [`RefCell`], global state, I/O, or unsafe code. -/// -/// Relevant papers/articles: -/// -/// 1. Pedro Celis. ["Robin Hood Hashing"](https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf) -/// 2. Emmanuel Goossaert. ["Robin Hood -/// hashing"](http://codecapsule.com/2013/11/11/robin-hood-hashing/) -/// 3. Emmanuel Goossaert. ["Robin Hood hashing: backward shift -/// deletion"](http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/) -/// -/// # Examples -/// -/// ``` -/// use std::collections::HashMap; -/// -/// // type inference lets us omit an explicit type signature (which -/// // would be `HashMap<&str, &str>` in this example). -/// let mut book_reviews = HashMap::new(); -/// -/// // review some books. -/// book_reviews.insert("Adventures of Huckleberry Finn", "My favorite book."); -/// book_reviews.insert("Grimms' Fairy Tales", "Masterpiece."); -/// book_reviews.insert("Pride and Prejudice", "Very enjoyable."); -/// book_reviews.insert("The Adventures of Sherlock Holmes", "Eye lyked it alot."); -/// -/// // check for a specific one. -/// if !book_reviews.contains_key("Les Misérables") { -/// println!("We've got {} reviews, but Les Misérables ain't one.", -/// book_reviews.len()); -/// } -/// -/// // oops, this review has a lot of spelling mistakes, let's delete it. -/// book_reviews.remove("The Adventures of Sherlock Holmes"); -/// -/// // look up the values associated with some keys. -/// let to_find = ["Pride and Prejudice", "Alice's Adventure in Wonderland"]; -/// for book in &to_find { -/// match book_reviews.get(book) { -/// Some(review) => println!("{}: {}", book, review), -/// None => println!("{} is unreviewed.", book) -/// } -/// } -/// -/// // iterate over everything. -/// for (book, review) in &book_reviews { -/// println!("{}: \"{}\"", book, review); -/// } -/// ``` -/// -/// `HashMap` also implements an [`Entry API`](#method.entry), which allows -/// for more complex methods of getting, setting, updating and removing keys and -/// their values: -/// -/// ``` -/// use std::collections::HashMap; -/// -/// // type inference lets us omit an explicit type signature (which -/// // would be `HashMap<&str, u8>` in this example). -/// let mut player_stats = HashMap::new(); -/// -/// fn random_stat_buff() -> u8 { -/// // could actually return some random value here - let's just return -/// // some fixed value for now -/// 42 -/// } -/// -/// // insert a key only if it doesn't already exist -/// player_stats.entry("health").or_insert(100); -/// -/// // insert a key using a function that provides a new value only if it -/// // doesn't already exist -/// player_stats.entry("defence").or_insert_with(random_stat_buff); -/// -/// // update a key, guarding against the key possibly not being set -/// let stat = player_stats.entry("attack").or_insert(100); -/// *stat += random_stat_buff(); -/// ``` -/// -/// The easiest way to use `HashMap` with a custom type as key is to derive [`Eq`] and [`Hash`]. -/// We must also derive [`PartialEq`]. -/// -/// [`Eq`]: ../../std/cmp/trait.Eq.html -/// [`Hash`]: ../../std/hash/trait.Hash.html -/// [`PartialEq`]: ../../std/cmp/trait.PartialEq.html -/// [`RefCell`]: ../../std/cell/struct.RefCell.html -/// [`Cell`]: ../../std/cell/struct.Cell.html -/// [`default`]: #method.default -/// [`with_hasher`]: #method.with_hasher -/// [`with_capacity_and_hasher`]: #method.with_capacity_and_hasher -/// [`fnv`]: https://crates.io/crates/fnv -/// -/// ``` -/// use std::collections::HashMap; -/// -/// #[derive(Hash, Eq, PartialEq, Debug)] -/// struct Viking { -/// name: String, -/// country: String, -/// } -/// -/// impl Viking { -/// /// Create a new Viking. -/// fn new(name: &str, country: &str) -> Viking { -/// Viking { name: name.to_string(), country: country.to_string() } -/// } -/// } -/// -/// // Use a HashMap to store the vikings' health points. -/// let mut vikings = HashMap::new(); -/// -/// vikings.insert(Viking::new("Einar", "Norway"), 25); -/// vikings.insert(Viking::new("Olaf", "Denmark"), 24); -/// vikings.insert(Viking::new("Harald", "Iceland"), 12); -/// -/// // Use derived implementation to print the status of the vikings. -/// for (viking, health) in &vikings { -/// println!("{:?} has {} hp", viking, health); -/// } -/// ``` -/// -/// A `HashMap` with fixed list of elements can be initialized from an array: -/// -/// ``` -/// use std::collections::HashMap; -/// -/// fn main() { -/// let timber_resources: HashMap<&str, i32> = -/// [("Norway", 100), -/// ("Denmark", 50), -/// ("Iceland", 10)] -/// .iter().cloned().collect(); -/// // use the values stored in map -/// } -/// ``` - -#[derive(Clone)] -pub struct HashMap { - // All hashes are keyed on these values, to prevent hash collision attacks. - hash_builder: S, - - table: RawTable, - - resize_policy: DefaultResizePolicy, -} - -/// Search for a pre-hashed key. -#[inline] -fn search_hashed(table: M, hash: SafeHash, mut is_match: F) -> InternalEntry -where - M: Deref>, - F: FnMut(&K) -> bool, -{ - // This is the only function where capacity can be zero. To avoid - // undefined behavior when Bucket::new gets the raw bucket in this - // case, immediately return the appropriate search result. - if table.capacity() == 0 { - return InternalEntry::TableIsEmpty; - } - - let size = table.size(); - let mut probe = Bucket::new(table, hash); - let mut displacement = 0; - - loop { - let full = match probe.peek() { - Empty(bucket) => { - // Found a hole! - return InternalEntry::Vacant { - hash, - elem: NoElem(bucket, displacement), - }; - }, - Full(bucket) => bucket, - }; - - let probe_displacement = full.displacement(); - - if probe_displacement < displacement { - // Found a luckier bucket than me. - // We can finish the search early if we hit any bucket - // with a lower distance to initial bucket than we've probed. - return InternalEntry::Vacant { - hash, - elem: NeqElem(full, probe_displacement), - }; - } - - // If the hash doesn't match, it can't be this one.. - if hash == full.hash() { - // If the key doesn't match, it can't be this one.. - if is_match(full.read().0) { - return InternalEntry::Occupied { elem: full }; - } - } - displacement += 1; - probe = full.next(); - debug_assert!(displacement <= size); - } -} - -fn pop_internal(starting_bucket: FullBucketMut<'_, K, V>) -> (K, V, &mut RawTable) { - let (empty, retkey, retval) = starting_bucket.take(); - let mut gap = match empty.gap_peek() { - Ok(b) => b, - Err(b) => return (retkey, retval, b.into_table()), - }; - - while gap.full().displacement() != 0 { - gap = match gap.shift() { - Ok(b) => b, - Err(b) => { - return (retkey, retval, b.into_table()); - }, - }; - } - - // Now we've done all our shifting. Return the value we grabbed earlier. - (retkey, retval, gap.into_table()) -} - -/// Perform robin hood bucket stealing at the given `bucket`. You must -/// also pass that bucket's displacement so we don't have to recalculate it. -/// -/// `hash`, `key`, and `val` are the elements to "robin hood" into the hashtable. -fn robin_hood<'a, K: 'a, V: 'a>( - bucket: FullBucketMut<'a, K, V>, - mut displacement: usize, - mut hash: SafeHash, - mut key: K, - mut val: V, -) -> FullBucketMut<'a, K, V> { - let size = bucket.table().size(); - let raw_capacity = bucket.table().capacity(); - // There can be at most `size - dib` buckets to displace, because - // in the worst case, there are `size` elements and we already are - // `displacement` buckets away from the initial one. - let idx_end = (bucket.index() + size - bucket.displacement()) % raw_capacity; - // Save the *starting point*. - let mut bucket = bucket.stash(); - - loop { - let (old_hash, old_key, old_val) = bucket.replace(hash, key, val); - hash = old_hash; - key = old_key; - val = old_val; - - loop { - displacement += 1; - let probe = bucket.next(); - debug_assert_ne!(probe.index(), idx_end); - - let full_bucket = match probe.peek() { - Empty(bucket) => { - // Found a hole! - let bucket = bucket.put(hash, key, val); - // Now that it's stolen, just read the value's pointer - // right out of the table! Go back to the *starting point*. - // - // This use of `into_table` is misleading. It turns the - // bucket, which is a FullBucket on top of a - // FullBucketMut, into just one FullBucketMut. The "table" - // refers to the inner FullBucketMut in this context. - return bucket.into_table(); - }, - Full(bucket) => bucket, - }; - - let probe_displacement = full_bucket.displacement(); - - bucket = full_bucket; - - // Robin hood! Steal the spot. - if probe_displacement < displacement { - displacement = probe_displacement; - break; - } - } - } -} - -impl HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - fn make_hash(&self, x: &X) -> SafeHash - where - X: Hash, - { - table::make_hash(&self.hash_builder, x) - } - - /// Search for a key, yielding the index if it's found in the hashtable. - /// If you already have the hash for the key lying around, use - /// search_hashed. - #[inline] - fn search<'a, Q: ?Sized>(&'a self, q: &Q) -> InternalEntry> - where - K: Borrow, - Q: Eq + Hash, - { - let hash = self.make_hash(q); - search_hashed(&self.table, hash, |k| q.eq(k.borrow())) - } - - #[inline] - fn search_mut<'a, Q: ?Sized>(&'a mut self, q: &Q) -> InternalEntry> - where - K: Borrow, - Q: Eq + Hash, - { - let hash = self.make_hash(q); - search_hashed(&mut self.table, hash, |k| q.eq(k.borrow())) - } - - // The caller should ensure that invariants by Robin Hood Hashing hold - // and that there's space in the underlying table. - fn insert_hashed_ordered(&mut self, hash: SafeHash, k: K, v: V) { - let mut buckets = Bucket::new(&mut self.table, hash); - let start_index = buckets.index(); - - loop { - // We don't need to compare hashes for value swap. - // Not even DIBs for Robin Hood. - buckets = match buckets.peek() { - Empty(empty) => { - empty.put(hash, k, v); - return; - }, - Full(b) => b.into_bucket(), - }; - buckets.next(); - debug_assert_ne!(buckets.index(), start_index); - } - } -} - -impl HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - /// Creates an empty `HashMap` which will use the given hash builder to hash - /// keys. - /// - /// The created map has the default initial capacity. - /// - /// Warning: `hash_builder` is normally randomly generated, and - /// is designed to allow HashMaps to be resistant to attacks that - /// cause many collisions and very poor performance. Setting it - /// manually using this function can expose a DoS attack vector. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::RandomState; - /// - /// let s = RandomState::new(); - /// let mut map = HashMap::with_hasher(s); - /// map.insert(1, 2); - /// ``` - #[inline] - pub fn try_with_hasher(hash_builder: S) -> Result, FailedAllocationError> { - Ok(HashMap { - hash_builder, - resize_policy: DefaultResizePolicy::new(), - table: RawTable::new(0)?, - }) - } - - #[inline] - pub fn with_hasher(hash_builder: S) -> HashMap { - Self::try_with_hasher(hash_builder).unwrap() - } - - /// Creates an empty `HashMap` with the specified capacity, using `hash_builder` - /// to hash the keys. - /// - /// The hash map will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash map will not allocate. - /// - /// Warning: `hash_builder` is normally randomly generated, and - /// is designed to allow HashMaps to be resistant to attacks that - /// cause many collisions and very poor performance. Setting it - /// manually using this function can expose a DoS attack vector. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::RandomState; - /// - /// let s = RandomState::new(); - /// let mut map = HashMap::with_capacity_and_hasher(10, s); - /// map.insert(1, 2); - /// ``` - #[inline] - pub fn try_with_capacity_and_hasher( - capacity: usize, - hash_builder: S, - ) -> Result, FailedAllocationError> { - let resize_policy = DefaultResizePolicy::new(); - let raw_cap = resize_policy.raw_capacity(capacity); - Ok(HashMap { - hash_builder, - resize_policy, - table: RawTable::new(raw_cap)?, - }) - } - - pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> HashMap { - Self::try_with_capacity_and_hasher(capacity, hash_builder).unwrap() - } - - /// Returns a reference to the map's [`BuildHasher`]. - /// - /// [`BuildHasher`]: ../../std/hash/trait.BuildHasher.html - pub fn hasher(&self) -> &S { - &self.hash_builder - } - - /// Returns the number of elements the map can hold without reallocating. - /// - /// This number is a lower bound; the `HashMap` might be able to hold - /// more, but is guaranteed to be able to hold at least this many. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// let map: HashMap = HashMap::with_capacity(100); - /// assert!(map.capacity() >= 100); - /// ``` - #[inline] - pub fn capacity(&self) -> usize { - self.resize_policy.capacity(self.raw_capacity()) - } - - /// Returns the hash map's raw capacity. - #[inline] - fn raw_capacity(&self) -> usize { - self.table.capacity() - } - - /// Reserves capacity for at least `additional` more elements to be inserted - /// in the `HashMap`. The collection may reserve more space to avoid - /// frequent reallocations. - /// - /// # Panics - /// - /// Panics if the new allocation size overflows [`usize`]. - /// - /// [`usize`]: ../../std/primitive.usize.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// let mut map: HashMap<&str, isize> = HashMap::new(); - /// map.reserve(10); - /// ``` - pub fn reserve(&mut self, additional: usize) { - self.try_reserve(additional).unwrap(); - } - - #[inline] - pub fn try_reserve(&mut self, additional: usize) -> Result<(), FailedAllocationError> { - let remaining = self.capacity() - self.len(); // this can't overflow - if remaining < additional { - let min_cap = self - .len() - .checked_add(additional) - .expect("reserve overflow"); - let raw_cap = self.resize_policy.raw_capacity(min_cap); - self.try_resize(raw_cap)?; - } else if self.table.tag() && remaining <= self.len() { - // Probe sequence is too long and table is half full, - // resize early to reduce probing length. - let new_capacity = self.table.capacity() * 2; - self.try_resize(new_capacity)?; - } - Ok(()) - } - - #[cold] - #[inline(never)] - fn try_resize(&mut self, new_raw_cap: usize) -> Result<(), FailedAllocationError> { - assert!(self.table.size() <= new_raw_cap); - assert!(new_raw_cap.is_power_of_two() || new_raw_cap == 0); - - let mut old_table = replace(&mut self.table, RawTable::new(new_raw_cap)?); - let old_size = old_table.size(); - - if old_table.size() == 0 { - return Ok(()); - } - - let mut bucket = Bucket::head_bucket(&mut old_table); - - // This is how the buckets might be laid out in memory: - // ($ marks an initialized bucket) - // ________________ - // |$$$_$$$$$$_$$$$$| - // - // But we've skipped the entire initial cluster of buckets - // and will continue iteration in this order: - // ________________ - // |$$$$$$_$$$$$ - // ^ wrap around once end is reached - // ________________ - // $$$_____________| - // ^ exit once table.size == 0 - loop { - bucket = match bucket.peek() { - Full(bucket) => { - let h = bucket.hash(); - let (b, k, v) = bucket.take(); - self.insert_hashed_ordered(h, k, v); - if b.table().size() == 0 { - break; - } - b.into_bucket() - }, - Empty(b) => b.into_bucket(), - }; - bucket.next(); - } - - assert_eq!(self.table.size(), old_size); - Ok(()) - } - - /// Shrinks the capacity of the map as much as possible. It will drop - /// down as much as possible while maintaining the internal rules - /// and possibly leaving some space in accordance with the resize policy. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap = HashMap::with_capacity(100); - /// map.insert(1, 2); - /// map.insert(3, 4); - /// assert!(map.capacity() >= 100); - /// map.shrink_to_fit(); - /// assert!(map.capacity() >= 2); - /// ``` - pub fn shrink_to_fit(&mut self) { - self.try_shrink_to_fit().unwrap(); - } - - pub fn try_shrink_to_fit(&mut self) -> Result<(), FailedAllocationError> { - let new_raw_cap = self.resize_policy.raw_capacity(self.len()); - if self.raw_capacity() != new_raw_cap { - let old_table = replace(&mut self.table, RawTable::new(new_raw_cap)?); - let old_size = old_table.size(); - - // Shrink the table. Naive algorithm for resizing: - for (h, k, v) in old_table.into_iter() { - self.insert_hashed_nocheck(h, k, v); - } - - debug_assert_eq!(self.table.size(), old_size); - } - Ok(()) - } - - /// Insert a pre-hashed key-value pair, without first checking - /// that there's enough room in the buckets. Returns a reference to the - /// newly insert value. - /// - /// If the key already exists, the hashtable will be returned untouched - /// and a reference to the existing element will be returned. - fn insert_hashed_nocheck(&mut self, hash: SafeHash, k: K, v: V) -> Option { - let entry = search_hashed(&mut self.table, hash, |key| *key == k).into_entry(k); - match entry { - Some(Occupied(mut elem)) => Some(elem.insert(v)), - Some(Vacant(elem)) => { - elem.insert(v); - None - }, - None => unreachable!(), - } - } - - /// An iterator visiting all keys in arbitrary order. - /// The iterator element type is `&'a K`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// for key in map.keys() { - /// println!("{}", key); - /// } - /// ``` - pub fn keys(&self) -> Keys<'_, K, V> { - Keys { inner: self.iter() } - } - - /// An iterator visiting all values in arbitrary order. - /// The iterator element type is `&'a V`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// for val in map.values() { - /// println!("{}", val); - /// } - /// ``` - pub fn values(&self) -> Values<'_, K, V> { - Values { inner: self.iter() } - } - - /// An iterator visiting all values mutably in arbitrary order. - /// The iterator element type is `&'a mut V`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// for val in map.values_mut() { - /// *val = *val + 10; - /// } - /// - /// for val in map.values() { - /// println!("{}", val); - /// } - /// ``` - pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { - ValuesMut { - inner: self.iter_mut(), - } - } - - /// An iterator visiting all key-value pairs in arbitrary order. - /// The iterator element type is `(&'a K, &'a V)`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// for (key, val) in map.iter() { - /// println!("key: {} val: {}", key, val); - /// } - /// ``` - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - inner: self.table.iter(), - } - } - - /// An iterator visiting all key-value pairs in arbitrary order, - /// with mutable references to the values. - /// The iterator element type is `(&'a K, &'a mut V)`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// // Update all values - /// for (_, val) in map.iter_mut() { - /// *val *= 2; - /// } - /// - /// for (key, val) in &map { - /// println!("key: {} val: {}", key, val); - /// } - /// ``` - pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { - IterMut { - inner: self.table.iter_mut(), - } - } - - /// Gets the given key's corresponding entry in the map for in-place manipulation. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut letters = HashMap::new(); - /// - /// for ch in "a short treatise on fungi".chars() { - /// let counter = letters.entry(ch).or_insert(0); - /// *counter += 1; - /// } - /// - /// assert_eq!(letters[&'s'], 2); - /// assert_eq!(letters[&'t'], 3); - /// assert_eq!(letters[&'u'], 1); - /// assert_eq!(letters.get(&'y'), None); - /// ``` - pub fn entry(&mut self, key: K) -> Entry<'_, K, V> { - self.try_entry(key).unwrap() - } - - #[inline(always)] - pub fn try_entry(&mut self, key: K) -> Result, FailedAllocationError> { - // Gotta resize now. - self.try_reserve(1)?; - let hash = self.make_hash(&key); - Ok(search_hashed(&mut self.table, hash, |q| q.eq(&key)) - .into_entry(key) - .expect("unreachable")) - } - - /// Returns the number of elements in the map. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut a = HashMap::new(); - /// assert_eq!(a.len(), 0); - /// a.insert(1, "a"); - /// assert_eq!(a.len(), 1); - /// ``` - pub fn len(&self) -> usize { - self.table.size() - } - - /// Returns true if the map contains no elements. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut a = HashMap::new(); - /// assert!(a.is_empty()); - /// a.insert(1, "a"); - /// assert!(!a.is_empty()); - /// ``` - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Clears the map, returning all key-value pairs as an iterator. Keeps the - /// allocated memory for reuse. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut a = HashMap::new(); - /// a.insert(1, "a"); - /// a.insert(2, "b"); - /// - /// for (k, v) in a.drain().take(1) { - /// assert!(k == 1 || k == 2); - /// assert!(v == "a" || v == "b"); - /// } - /// - /// assert!(a.is_empty()); - /// ``` - #[inline] - pub fn drain(&mut self) -> Drain<'_, K, V> - where - K: 'static, - V: 'static, - { - Drain { - inner: self.table.drain(), - } - } - - /// Clears the map, removing all key-value pairs. Keeps the allocated memory - /// for reuse. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut a = HashMap::new(); - /// a.insert(1, "a"); - /// a.clear(); - /// assert!(a.is_empty()); - /// ``` - #[inline] - pub fn clear(&mut self) - where - K: 'static, - V: 'static, - { - self.drain(); - } - - /// Returns a reference to the value corresponding to the key. - /// - /// The key may be any borrowed form of the map's key type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the key type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert(1, "a"); - /// assert_eq!(map.get(&1), Some(&"a")); - /// assert_eq!(map.get(&2), None); - /// ``` - pub fn get(&self, k: &Q) -> Option<&V> - where - K: Borrow, - Q: Hash + Eq, - { - self.search(k) - .into_occupied_bucket() - .map(|bucket| bucket.into_refs().1) - } - - /// Returns true if the map contains a value for the specified key. - /// - /// The key may be any borrowed form of the map's key type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the key type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert(1, "a"); - /// assert_eq!(map.contains_key(&1), true); - /// assert_eq!(map.contains_key(&2), false); - /// ``` - pub fn contains_key(&self, k: &Q) -> bool - where - K: Borrow, - Q: Hash + Eq, - { - self.search(k).into_occupied_bucket().is_some() - } - - /// Returns a mutable reference to the value corresponding to the key. - /// - /// The key may be any borrowed form of the map's key type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the key type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert(1, "a"); - /// if let Some(x) = map.get_mut(&1) { - /// *x = "b"; - /// } - /// assert_eq!(map[&1], "b"); - /// ``` - pub fn get_mut(&mut self, k: &Q) -> Option<&mut V> - where - K: Borrow, - Q: Hash + Eq, - { - self.search_mut(k) - .into_occupied_bucket() - .map(|bucket| bucket.into_mut_refs().1) - } - - /// Inserts a key-value pair into the map. - /// - /// If the map did not have this key present, [`None`] is returned. - /// - /// If the map did have this key present, the value is updated, and the old - /// value is returned. The key is not updated, though; this matters for - /// types that can be `==` without being identical. See the [module-level - /// documentation] for more. - /// - /// [`None`]: ../../std/option/enum.Option.html#variant.None - /// [module-level documentation]: index.html#insert-and-complex-keys - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// assert_eq!(map.insert(37, "a"), None); - /// assert_eq!(map.is_empty(), false); - /// - /// map.insert(37, "b"); - /// assert_eq!(map.insert(37, "c"), Some("b")); - /// assert_eq!(map[&37], "c"); - /// ``` - pub fn insert(&mut self, k: K, v: V) -> Option { - self.try_insert(k, v).unwrap() - } - - #[inline] - pub fn try_insert(&mut self, k: K, v: V) -> Result, FailedAllocationError> { - let hash = self.make_hash(&k); - self.try_reserve(1)?; - Ok(self.insert_hashed_nocheck(hash, k, v)) - } - - /// Removes a key from the map, returning the value at the key if the key - /// was previously in the map. - /// - /// The key may be any borrowed form of the map's key type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the key type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert(1, "a"); - /// assert_eq!(map.remove(&1), Some("a")); - /// assert_eq!(map.remove(&1), None); - /// ``` - pub fn remove(&mut self, k: &Q) -> Option - where - K: Borrow, - Q: Hash + Eq, - { - if self.table.size() == 0 { - return None; - } - - self.search_mut(k) - .into_occupied_bucket() - .map(|bucket| pop_internal(bucket).1) - } - - /// Retains only the elements specified by the predicate. - /// - /// In other words, remove all pairs `(k, v)` such that `f(&k,&mut v)` returns `false`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap = (0..8).map(|x|(x, x*10)).collect(); - /// map.retain(|&k, _| k % 2 == 0); - /// assert_eq!(map.len(), 4); - /// ``` - pub fn retain(&mut self, mut f: F) - where - F: FnMut(&K, &mut V) -> bool, - { - if self.table.size() == 0 { - return; - } - let mut elems_left = self.table.size(); - let mut bucket = Bucket::head_bucket(&mut self.table); - bucket.prev(); - let start_index = bucket.index(); - while elems_left != 0 { - bucket = match bucket.peek() { - Full(mut full) => { - elems_left -= 1; - let should_remove = { - let (k, v) = full.read_mut(); - !f(k, v) - }; - if should_remove { - let prev_raw = full.raw(); - let (_, _, t) = pop_internal(full); - Bucket::new_from(prev_raw, t) - } else { - full.into_bucket() - } - }, - Empty(b) => b.into_bucket(), - }; - bucket.prev(); // reverse iteration - debug_assert!(elems_left == 0 || bucket.index() != start_index); - } - } -} - -impl PartialEq for HashMap -where - K: Eq + Hash, - V: PartialEq, - S: BuildHasher, -{ - fn eq(&self, other: &HashMap) -> bool { - if self.len() != other.len() { - return false; - } - - self.iter() - .all(|(key, value)| other.get(key).map_or(false, |v| *value == *v)) - } -} - -impl Eq for HashMap -where - K: Eq + Hash, - V: Eq, - S: BuildHasher, -{ -} - -impl Debug for HashMap -where - K: Eq + Hash + Debug, - V: Debug, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map().entries(self.iter()).finish() - } -} - -impl Default for HashMap -where - K: Eq + Hash, - S: BuildHasher + Default, -{ - /// Creates an empty `HashMap`, with the `Default` value for the hasher. - fn default() -> HashMap { - HashMap::with_hasher(Default::default()) - } -} - -impl<'a, K, Q: ?Sized, V, S> Index<&'a Q> for HashMap -where - K: Eq + Hash + Borrow, - Q: Eq + Hash, - S: BuildHasher, -{ - type Output = V; - - #[inline] - fn index(&self, index: &Q) -> &V { - self.get(index).expect("no entry found for key") - } -} - -/// An iterator over the entries of a `HashMap`. -/// -/// This `struct` is created by the [`iter`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`iter`]: struct.HashMap.html#method.iter -/// [`HashMap`]: struct.HashMap.html -pub struct Iter<'a, K, V> { - inner: table::Iter<'a, K, V>, -} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for Iter<'a, K, V> { - fn clone(&self) -> Iter<'a, K, V> { - Iter { - inner: self.inner.clone(), - } - } -} - -impl<'a, K: Debug, V: Debug> fmt::Debug for Iter<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A mutable iterator over the entries of a `HashMap`. -/// -/// This `struct` is created by the [`iter_mut`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`iter_mut`]: struct.HashMap.html#method.iter_mut -/// [`HashMap`]: struct.HashMap.html -pub struct IterMut<'a, K, V> { - inner: table::IterMut<'a, K, V>, -} - -/// An owning iterator over the entries of a `HashMap`. -/// -/// This `struct` is created by the [`into_iter`] method on [`HashMap`][`HashMap`] -/// (provided by the `IntoIterator` trait). See its documentation for more. -/// -/// [`into_iter`]: struct.HashMap.html#method.into_iter -/// [`HashMap`]: struct.HashMap.html -pub struct IntoIter { - pub(super) inner: table::IntoIter, -} - -/// An iterator over the keys of a `HashMap`. -/// -/// This `struct` is created by the [`keys`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`keys`]: struct.HashMap.html#method.keys -/// [`HashMap`]: struct.HashMap.html -pub struct Keys<'a, K, V> { - inner: Iter<'a, K, V>, -} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for Keys<'a, K, V> { - fn clone(&self) -> Keys<'a, K, V> { - Keys { - inner: self.inner.clone(), - } - } -} - -impl<'a, K: Debug, V> fmt::Debug for Keys<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// An iterator over the values of a `HashMap`. -/// -/// This `struct` is created by the [`values`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`values`]: struct.HashMap.html#method.values -/// [`HashMap`]: struct.HashMap.html -pub struct Values<'a, K, V> { - inner: Iter<'a, K, V>, -} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for Values<'a, K, V> { - fn clone(&self) -> Values<'a, K, V> { - Values { - inner: self.inner.clone(), - } - } -} - -impl<'a, K, V: Debug> fmt::Debug for Values<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A draining iterator over the entries of a `HashMap`. -/// -/// This `struct` is created by the [`drain`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`drain`]: struct.HashMap.html#method.drain -/// [`HashMap`]: struct.HashMap.html -pub struct Drain<'a, K: 'static, V: 'static> { - pub(super) inner: table::Drain<'a, K, V>, -} - -/// A mutable iterator over the values of a `HashMap`. -/// -/// This `struct` is created by the [`values_mut`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`values_mut`]: struct.HashMap.html#method.values_mut -/// [`HashMap`]: struct.HashMap.html -pub struct ValuesMut<'a, K, V> { - inner: IterMut<'a, K, V>, -} - -enum InternalEntry { - Occupied { - elem: FullBucket, - }, - Vacant { - hash: SafeHash, - elem: VacantEntryState, - }, - TableIsEmpty, -} - -impl InternalEntry { - #[inline] - fn into_occupied_bucket(self) -> Option> { - match self { - InternalEntry::Occupied { elem } => Some(elem), - _ => None, - } - } -} - -impl<'a, K, V> InternalEntry> { - #[inline] - fn into_entry(self, key: K) -> Option> { - match self { - InternalEntry::Occupied { elem } => Some(Occupied(OccupiedEntry { - key: Some(key), - elem, - })), - InternalEntry::Vacant { hash, elem } => Some(Vacant(VacantEntry { hash, key, elem })), - InternalEntry::TableIsEmpty => None, - } - } -} - -/// A view into a single entry in a map, which may either be vacant or occupied. -/// -/// This `enum` is constructed from the [`entry`] method on [`HashMap`]. -/// -/// [`HashMap`]: struct.HashMap.html -/// [`entry`]: struct.HashMap.html#method.entry -pub enum Entry<'a, K, V> { - /// An occupied entry. - Occupied(OccupiedEntry<'a, K, V>), - - /// A vacant entry. - Vacant(VacantEntry<'a, K, V>), -} - -impl<'a, K: 'a + Debug, V: 'a + Debug> Debug for Entry<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Vacant(ref v) => f.debug_tuple("Entry").field(v).finish(), - Occupied(ref o) => f.debug_tuple("Entry").field(o).finish(), - } - } -} - -/// A view into an occupied entry in a `HashMap`. -/// It is part of the [`Entry`] enum. -/// -/// [`Entry`]: enum.Entry.html -pub struct OccupiedEntry<'a, K, V> { - key: Option, - elem: FullBucket>, -} - -impl<'a, K: 'a + Debug, V: 'a + Debug> Debug for OccupiedEntry<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("OccupiedEntry") - .field("key", self.key()) - .field("value", self.get()) - .finish() - } -} - -/// A view into a vacant entry in a `HashMap`. -/// It is part of the [`Entry`] enum. -/// -/// [`Entry`]: enum.Entry.html -pub struct VacantEntry<'a, K, V> { - hash: SafeHash, - key: K, - elem: VacantEntryState>, -} - -impl<'a, K: 'a + Debug, V: 'a> Debug for VacantEntry<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("VacantEntry").field(self.key()).finish() - } -} - -/// Possible states of a VacantEntry. -enum VacantEntryState { - /// The index is occupied, but the key to insert has precedence, - /// and will kick the current one out on insertion. - NeqElem(FullBucket, usize), - /// The index is genuinely vacant. - NoElem(EmptyBucket, usize), -} - -impl<'a, K, V, S> IntoIterator for &'a HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (&'a K, &'a V); - type IntoIter = Iter<'a, K, V>; - - fn into_iter(self) -> Iter<'a, K, V> { - self.iter() - } -} - -impl<'a, K, V, S> IntoIterator for &'a mut HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (&'a K, &'a mut V); - type IntoIter = IterMut<'a, K, V>; - - fn into_iter(self) -> IterMut<'a, K, V> { - self.iter_mut() - } -} - -impl IntoIterator for HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (K, V); - type IntoIter = IntoIter; - - /// Creates a consuming iterator, that is, one that moves each key-value - /// pair out of the map in arbitrary order. The map cannot be used after - /// calling this. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// // Not possible with .iter() - /// let vec: Vec<(&str, isize)> = map.into_iter().collect(); - /// ``` - fn into_iter(self) -> IntoIter { - IntoIter { - inner: self.table.into_iter(), - } - } -} - -impl<'a, K, V> Iterator for Iter<'a, K, V> { - type Item = (&'a K, &'a V); - - #[inline] - fn next(&mut self) -> Option<(&'a K, &'a V)> { - self.inner.next() - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for Iter<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> Iterator for IterMut<'a, K, V> { - type Item = (&'a K, &'a mut V); - - #[inline] - fn next(&mut self) -> Option<(&'a K, &'a mut V)> { - self.inner.next() - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> fmt::Debug for IterMut<'a, K, V> -where - K: fmt::Debug, - V: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.inner.iter()).finish() - } -} - -impl Iterator for IntoIter { - type Item = (K, V); - - #[inline] - fn next(&mut self) -> Option<(K, V)> { - self.inner.next().map(|(_, k, v)| (k, v)) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl ExactSizeIterator for IntoIter { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl fmt::Debug for IntoIter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.inner.iter()).finish() - } -} - -impl<'a, K, V> Iterator for Keys<'a, K, V> { - type Item = &'a K; - - #[inline] - fn next(&mut self) -> Option<&'a K> { - self.inner.next().map(|(k, _)| k) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for Keys<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> Iterator for Values<'a, K, V> { - type Item = &'a V; - - #[inline] - fn next(&mut self) -> Option<&'a V> { - self.inner.next().map(|(_, v)| v) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for Values<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} -impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { - type Item = &'a mut V; - - #[inline] - fn next(&mut self) -> Option<&'a mut V> { - self.inner.next().map(|(_, v)| v) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for ValuesMut<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> fmt::Debug for ValuesMut<'a, K, V> -where - K: fmt::Debug, - V: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.inner.inner.iter()).finish() - } -} - -impl<'a, K, V> Iterator for Drain<'a, K, V> { - type Item = (K, V); - - #[inline] - fn next(&mut self) -> Option<(K, V)> { - self.inner.next().map(|(_, k, v)| (k, v)) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for Drain<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> fmt::Debug for Drain<'a, K, V> -where - K: fmt::Debug, - V: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.inner.iter()).finish() - } -} - -// FORK NOTE: Removed Placer impl - -impl<'a, K, V> Entry<'a, K, V> { - /// Ensures a value is in the entry by inserting the default if empty, and returns - /// a mutable reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// assert_eq!(map["poneyland"], 12); - /// - /// *map.entry("poneyland").or_insert(12) += 10; - /// assert_eq!(map["poneyland"], 22); - /// ``` - pub fn or_insert(self, default: V) -> &'a mut V { - match self { - Occupied(entry) => entry.into_mut(), - Vacant(entry) => entry.insert(default), - } - } - - /// Ensures a value is in the entry by inserting the result of the default function if empty, - /// and returns a mutable reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, String> = HashMap::new(); - /// let s = "hoho".to_string(); - /// - /// map.entry("poneyland").or_insert_with(|| s); - /// - /// assert_eq!(map["poneyland"], "hoho".to_string()); - /// ``` - pub fn or_insert_with V>(self, default: F) -> &'a mut V { - match self { - Occupied(entry) => entry.into_mut(), - Vacant(entry) => entry.insert(default()), - } - } - - /// Returns a reference to this entry's key. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// assert_eq!(map.entry("poneyland").key(), &"poneyland"); - /// ``` - pub fn key(&self) -> &K { - match *self { - Occupied(ref entry) => entry.key(), - Vacant(ref entry) => entry.key(), - } - } -} - -impl<'a, K, V> OccupiedEntry<'a, K, V> { - /// Gets a reference to the key in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// assert_eq!(map.entry("poneyland").key(), &"poneyland"); - /// ``` - pub fn key(&self) -> &K { - self.elem.read().0 - } - - /// Take the ownership of the key and value from the map. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// if let Entry::Occupied(o) = map.entry("poneyland") { - /// // We delete the entry from the map. - /// o.remove_entry(); - /// } - /// - /// assert_eq!(map.contains_key("poneyland"), false); - /// ``` - pub fn remove_entry(self) -> (K, V) { - let (k, v, _) = pop_internal(self.elem); - (k, v) - } - - /// Gets a reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// if let Entry::Occupied(o) = map.entry("poneyland") { - /// assert_eq!(o.get(), &12); - /// } - /// ``` - pub fn get(&self) -> &V { - self.elem.read().1 - } - - /// Gets a mutable reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// assert_eq!(map["poneyland"], 12); - /// if let Entry::Occupied(mut o) = map.entry("poneyland") { - /// *o.get_mut() += 10; - /// } - /// - /// assert_eq!(map["poneyland"], 22); - /// ``` - pub fn get_mut(&mut self) -> &mut V { - self.elem.read_mut().1 - } - - /// Converts the OccupiedEntry into a mutable reference to the value in the entry - /// with a lifetime bound to the map itself. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// assert_eq!(map["poneyland"], 12); - /// if let Entry::Occupied(o) = map.entry("poneyland") { - /// *o.into_mut() += 10; - /// } - /// - /// assert_eq!(map["poneyland"], 22); - /// ``` - pub fn into_mut(self) -> &'a mut V { - self.elem.into_mut_refs().1 - } - - /// Sets the value of the entry, and returns the entry's old value. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// if let Entry::Occupied(mut o) = map.entry("poneyland") { - /// assert_eq!(o.insert(15), 12); - /// } - /// - /// assert_eq!(map["poneyland"], 15); - /// ``` - pub fn insert(&mut self, mut value: V) -> V { - let old_value = self.get_mut(); - mem::swap(&mut value, old_value); - value - } - - /// Takes the value out of the entry, and returns it. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// if let Entry::Occupied(o) = map.entry("poneyland") { - /// assert_eq!(o.remove(), 12); - /// } - /// - /// assert_eq!(map.contains_key("poneyland"), false); - /// ``` - pub fn remove(self) -> V { - pop_internal(self.elem).1 - } - - /// Returns a key that was used for search. - /// - /// The key was retained for further use. - fn take_key(&mut self) -> Option { - self.key.take() - } -} - -impl<'a, K: 'a, V: 'a> VacantEntry<'a, K, V> { - /// Gets a reference to the key that would be used when inserting a value - /// through the `VacantEntry`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// assert_eq!(map.entry("poneyland").key(), &"poneyland"); - /// ``` - pub fn key(&self) -> &K { - &self.key - } - - /// Take ownership of the key. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// - /// if let Entry::Vacant(v) = map.entry("poneyland") { - /// v.into_key(); - /// } - /// ``` - pub fn into_key(self) -> K { - self.key - } - - /// Sets the value of the entry with the VacantEntry's key, - /// and returns a mutable reference to it. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// - /// if let Entry::Vacant(o) = map.entry("poneyland") { - /// o.insert(37); - /// } - /// assert_eq!(map["poneyland"], 37); - /// ``` - pub fn insert(self, value: V) -> &'a mut V { - let b = match self.elem { - NeqElem(mut bucket, disp) => { - if disp >= DISPLACEMENT_THRESHOLD { - bucket.table_mut().set_tag(true); - } - robin_hood(bucket, disp, self.hash, self.key, value) - }, - NoElem(mut bucket, disp) => { - if disp >= DISPLACEMENT_THRESHOLD { - bucket.table_mut().set_tag(true); - } - bucket.put(self.hash, self.key, value) - }, - }; - b.into_mut_refs().1 - } -} - -impl FromIterator<(K, V)> for HashMap -where - K: Eq + Hash, - S: BuildHasher + Default, -{ - fn from_iter>(iter: T) -> HashMap { - let mut map = HashMap::with_hasher(Default::default()); - map.extend(iter); - map - } -} - -impl Extend<(K, V)> for HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - fn extend>(&mut self, iter: T) { - // Keys may be already present or show multiple times in the iterator. - // Reserve the entire hint lower bound if the map is empty. - // Otherwise reserve half the hint (rounded up), so the map - // will only resize twice in the worst case. - let iter = iter.into_iter(); - let reserve = if self.is_empty() { - iter.size_hint().0 - } else { - (iter.size_hint().0 + 1) / 2 - }; - self.reserve(reserve); - for (k, v) in iter { - self.insert(k, v); - } - } -} - -impl<'a, K, V, S> Extend<(&'a K, &'a V)> for HashMap -where - K: Eq + Hash + Copy, - V: Copy, - S: BuildHasher, -{ - fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(|(&key, &value)| (key, value))); - } -} - -// FORK NOTE: These can be reused -pub use std::collections::hash_map::{DefaultHasher, RandomState}; - -impl super::Recover for HashMap -where - K: Eq + Hash + Borrow, - S: BuildHasher, - Q: Eq + Hash, -{ - type Key = K; - - fn get(&self, key: &Q) -> Option<&K> { - self.search(key) - .into_occupied_bucket() - .map(|bucket| bucket.into_refs().0) - } - - fn take(&mut self, key: &Q) -> Option { - if self.table.size() == 0 { - return None; - } - - self.search_mut(key) - .into_occupied_bucket() - .map(|bucket| pop_internal(bucket).0) - } - - fn replace(&mut self, key: K) -> Option { - self.reserve(1); - - match self.entry(key) { - Occupied(mut occupied) => { - let key = occupied.take_key().unwrap(); - Some(mem::replace(occupied.elem.read_mut().0, key)) - }, - Vacant(vacant) => { - vacant.insert(()); - None - }, - } - } -} - -#[allow(dead_code)] -fn assert_covariance() { - fn map_key<'new>(v: HashMap<&'static str, u8>) -> HashMap<&'new str, u8> { - v - } - fn map_val<'new>(v: HashMap) -> HashMap { - v - } - fn iter_key<'a, 'new>(v: Iter<'a, &'static str, u8>) -> Iter<'a, &'new str, u8> { - v - } - fn iter_val<'a, 'new>(v: Iter<'a, u8, &'static str>) -> Iter<'a, u8, &'new str> { - v - } - fn into_iter_key<'new>(v: IntoIter<&'static str, u8>) -> IntoIter<&'new str, u8> { - v - } - fn into_iter_val<'new>(v: IntoIter) -> IntoIter { - v - } - fn keys_key<'a, 'new>(v: Keys<'a, &'static str, u8>) -> Keys<'a, &'new str, u8> { - v - } - fn keys_val<'a, 'new>(v: Keys<'a, u8, &'static str>) -> Keys<'a, u8, &'new str> { - v - } - fn values_key<'a, 'new>(v: Values<'a, &'static str, u8>) -> Values<'a, &'new str, u8> { - v - } - fn values_val<'a, 'new>(v: Values<'a, u8, &'static str>) -> Values<'a, u8, &'new str> { - v - } - fn drain<'new>( - d: Drain<'static, &'static str, &'static str>, - ) -> Drain<'new, &'new str, &'new str> { - d - } -} - -#[cfg(test)] -mod test_map { - extern crate rand; - use self::rand::{thread_rng, Rng}; - use super::Entry::{Occupied, Vacant}; - use super::HashMap; - use super::RandomState; - use std::cell::RefCell; - - #[test] - fn test_zero_capacities() { - type HM = HashMap; - - let m = HM::new(); - assert_eq!(m.capacity(), 0); - - let m = HM::default(); - assert_eq!(m.capacity(), 0); - - let m = HM::with_hasher(RandomState::new()); - assert_eq!(m.capacity(), 0); - - let m = HM::with_capacity(0); - assert_eq!(m.capacity(), 0); - - let m = HM::with_capacity_and_hasher(0, RandomState::new()); - assert_eq!(m.capacity(), 0); - - let mut m = HM::new(); - m.insert(1, 1); - m.insert(2, 2); - m.remove(&1); - m.remove(&2); - m.shrink_to_fit(); - assert_eq!(m.capacity(), 0); - - let mut m = HM::new(); - m.reserve(0); - assert_eq!(m.capacity(), 0); - } - - #[test] - fn test_create_capacity_zero() { - let mut m = HashMap::with_capacity(0); - - assert!(m.insert(1, 1).is_none()); - - assert!(m.contains_key(&1)); - assert!(!m.contains_key(&0)); - } - - #[test] - fn test_insert() { - let mut m = HashMap::new(); - assert_eq!(m.len(), 0); - assert!(m.insert(1, 2).is_none()); - assert_eq!(m.len(), 1); - assert!(m.insert(2, 4).is_none()); - assert_eq!(m.len(), 2); - assert_eq!(*m.get(&1).unwrap(), 2); - assert_eq!(*m.get(&2).unwrap(), 4); - } - - #[test] - fn test_clone() { - let mut m = HashMap::new(); - assert_eq!(m.len(), 0); - assert!(m.insert(1, 2).is_none()); - assert_eq!(m.len(), 1); - assert!(m.insert(2, 4).is_none()); - assert_eq!(m.len(), 2); - let m2 = m.clone(); - assert_eq!(*m2.get(&1).unwrap(), 2); - assert_eq!(*m2.get(&2).unwrap(), 4); - assert_eq!(m2.len(), 2); - } - - thread_local! { static DROP_VECTOR: RefCell> = RefCell::new(Vec::new()) } - - #[derive(Hash, PartialEq, Eq)] - struct Dropable { - k: usize, - } - - impl Dropable { - fn new(k: usize) -> Dropable { - DROP_VECTOR.with(|slot| { - slot.borrow_mut()[k] += 1; - }); - - Dropable { k: k } - } - } - - impl Drop for Dropable { - fn drop(&mut self) { - DROP_VECTOR.with(|slot| { - slot.borrow_mut()[self.k] -= 1; - }); - } - } - - impl Clone for Dropable { - fn clone(&self) -> Dropable { - Dropable::new(self.k) - } - } - - #[test] - fn test_drops() { - DROP_VECTOR.with(|slot| { - *slot.borrow_mut() = vec![0; 200]; - }); - - { - let mut m = HashMap::new(); - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 0); - } - }); - - for i in 0..100 { - let d1 = Dropable::new(i); - let d2 = Dropable::new(i + 100); - m.insert(d1, d2); - } - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 1); - } - }); - - for i in 0..50 { - let k = Dropable::new(i); - let v = m.remove(&k); - - assert!(v.is_some()); - - DROP_VECTOR.with(|v| { - assert_eq!(v.borrow()[i], 1); - assert_eq!(v.borrow()[i + 100], 1); - }); - } - - DROP_VECTOR.with(|v| { - for i in 0..50 { - assert_eq!(v.borrow()[i], 0); - assert_eq!(v.borrow()[i + 100], 0); - } - - for i in 50..100 { - assert_eq!(v.borrow()[i], 1); - assert_eq!(v.borrow()[i + 100], 1); - } - }); - } - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 0); - } - }); - } - - #[test] - fn test_into_iter_drops() { - DROP_VECTOR.with(|v| { - *v.borrow_mut() = vec![0; 200]; - }); - - let hm = { - let mut hm = HashMap::new(); - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 0); - } - }); - - for i in 0..100 { - let d1 = Dropable::new(i); - let d2 = Dropable::new(i + 100); - hm.insert(d1, d2); - } - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 1); - } - }); - - hm - }; - - // By the way, ensure that cloning doesn't screw up the dropping. - drop(hm.clone()); - - { - let mut half = hm.into_iter().take(50); - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 1); - } - }); - - for _ in half.by_ref() {} - - DROP_VECTOR.with(|v| { - let nk = (0..100).filter(|&i| v.borrow()[i] == 1).count(); - - let nv = (0..100).filter(|&i| v.borrow()[i + 100] == 1).count(); - - assert_eq!(nk, 50); - assert_eq!(nv, 50); - }); - }; - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 0); - } - }); - } - - #[test] - fn test_empty_remove() { - let mut m: HashMap = HashMap::new(); - assert_eq!(m.remove(&0), None); - } - - #[test] - fn test_empty_entry() { - let mut m: HashMap = HashMap::new(); - match m.entry(0) { - Occupied(_) => panic!(), - Vacant(_) => {}, - } - assert!(*m.entry(0).or_insert(true)); - assert_eq!(m.len(), 1); - } - - #[test] - fn test_empty_iter() { - let mut m: HashMap = HashMap::new(); - assert_eq!(m.drain().next(), None); - assert_eq!(m.keys().next(), None); - assert_eq!(m.values().next(), None); - assert_eq!(m.values_mut().next(), None); - assert_eq!(m.iter().next(), None); - assert_eq!(m.iter_mut().next(), None); - assert_eq!(m.len(), 0); - assert!(m.is_empty()); - assert_eq!(m.into_iter().next(), None); - } - - #[test] - fn test_lots_of_insertions() { - let mut m = HashMap::new(); - - // Try this a few times to make sure we never screw up the hashmap's - // internal state. - for _ in 0..10 { - assert!(m.is_empty()); - - for i in 1..1001 { - assert!(m.insert(i, i).is_none()); - - for j in 1..i + 1 { - let r = m.get(&j); - assert_eq!(r, Some(&j)); - } - - for j in i + 1..1001 { - let r = m.get(&j); - assert_eq!(r, None); - } - } - - for i in 1001..2001 { - assert!(!m.contains_key(&i)); - } - - // remove forwards - for i in 1..1001 { - assert!(m.remove(&i).is_some()); - - for j in 1..i + 1 { - assert!(!m.contains_key(&j)); - } - - for j in i + 1..1001 { - assert!(m.contains_key(&j)); - } - } - - for i in 1..1001 { - assert!(!m.contains_key(&i)); - } - - for i in 1..1001 { - assert!(m.insert(i, i).is_none()); - } - - // remove backwards - for i in (1..1001).rev() { - assert!(m.remove(&i).is_some()); - - for j in i..1001 { - assert!(!m.contains_key(&j)); - } - - for j in 1..i { - assert!(m.contains_key(&j)); - } - } - } - } - - #[test] - fn test_find_mut() { - let mut m = HashMap::new(); - assert!(m.insert(1, 12).is_none()); - assert!(m.insert(2, 8).is_none()); - assert!(m.insert(5, 14).is_none()); - let new = 100; - match m.get_mut(&5) { - None => panic!(), - Some(x) => *x = new, - } - assert_eq!(m.get(&5), Some(&new)); - } - - #[test] - fn test_insert_overwrite() { - let mut m = HashMap::new(); - assert!(m.insert(1, 2).is_none()); - assert_eq!(*m.get(&1).unwrap(), 2); - assert!(!m.insert(1, 3).is_none()); - assert_eq!(*m.get(&1).unwrap(), 3); - } - - #[test] - fn test_insert_conflicts() { - let mut m = HashMap::with_capacity(4); - assert!(m.insert(1, 2).is_none()); - assert!(m.insert(5, 3).is_none()); - assert!(m.insert(9, 4).is_none()); - assert_eq!(*m.get(&9).unwrap(), 4); - assert_eq!(*m.get(&5).unwrap(), 3); - assert_eq!(*m.get(&1).unwrap(), 2); - } - - #[test] - fn test_conflict_remove() { - let mut m = HashMap::with_capacity(4); - assert!(m.insert(1, 2).is_none()); - assert_eq!(*m.get(&1).unwrap(), 2); - assert!(m.insert(5, 3).is_none()); - assert_eq!(*m.get(&1).unwrap(), 2); - assert_eq!(*m.get(&5).unwrap(), 3); - assert!(m.insert(9, 4).is_none()); - assert_eq!(*m.get(&1).unwrap(), 2); - assert_eq!(*m.get(&5).unwrap(), 3); - assert_eq!(*m.get(&9).unwrap(), 4); - assert!(m.remove(&1).is_some()); - assert_eq!(*m.get(&9).unwrap(), 4); - assert_eq!(*m.get(&5).unwrap(), 3); - } - - #[test] - fn test_is_empty() { - let mut m = HashMap::with_capacity(4); - assert!(m.insert(1, 2).is_none()); - assert!(!m.is_empty()); - assert!(m.remove(&1).is_some()); - assert!(m.is_empty()); - } - - #[test] - fn test_pop() { - let mut m = HashMap::new(); - m.insert(1, 2); - assert_eq!(m.remove(&1), Some(2)); - assert_eq!(m.remove(&1), None); - } - - #[test] - fn test_iterate() { - let mut m = HashMap::with_capacity(4); - for i in 0..32 { - assert!(m.insert(i, i * 2).is_none()); - } - assert_eq!(m.len(), 32); - - let mut observed: u32 = 0; - - for (k, v) in &m { - assert_eq!(*v, *k * 2); - observed |= 1 << *k; - } - assert_eq!(observed, 0xFFFF_FFFF); - } - - #[test] - fn test_keys() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: HashMap<_, _> = vec.into_iter().collect(); - let keys: Vec<_> = map.keys().cloned().collect(); - assert_eq!(keys.len(), 3); - assert!(keys.contains(&1)); - assert!(keys.contains(&2)); - assert!(keys.contains(&3)); - } - - #[test] - fn test_values() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: HashMap<_, _> = vec.into_iter().collect(); - let values: Vec<_> = map.values().cloned().collect(); - assert_eq!(values.len(), 3); - assert!(values.contains(&'a')); - assert!(values.contains(&'b')); - assert!(values.contains(&'c')); - } - - #[test] - fn test_values_mut() { - let vec = vec![(1, 1), (2, 2), (3, 3)]; - let mut map: HashMap<_, _> = vec.into_iter().collect(); - for value in map.values_mut() { - *value = (*value) * 2 - } - let values: Vec<_> = map.values().cloned().collect(); - assert_eq!(values.len(), 3); - assert!(values.contains(&2)); - assert!(values.contains(&4)); - assert!(values.contains(&6)); - } - - #[test] - fn test_find() { - let mut m = HashMap::new(); - assert!(m.get(&1).is_none()); - m.insert(1, 2); - match m.get(&1) { - None => panic!(), - Some(v) => assert_eq!(*v, 2), - } - } - - #[test] - fn test_eq() { - let mut m1 = HashMap::new(); - m1.insert(1, 2); - m1.insert(2, 3); - m1.insert(3, 4); - - let mut m2 = HashMap::new(); - m2.insert(1, 2); - m2.insert(2, 3); - - assert_ne!(m1, m2); - - m2.insert(3, 4); - - assert_eq!(m1, m2); - } - - #[test] - fn test_show() { - let mut map = HashMap::new(); - let empty: HashMap = HashMap::new(); - - map.insert(1, 2); - map.insert(3, 4); - - let map_str = format!("{:?}", map); - - assert!(map_str == "{1: 2, 3: 4}" || map_str == "{3: 4, 1: 2}"); - assert_eq!(format!("{:?}", empty), "{}"); - } - - #[test] - fn test_expand() { - let mut m = HashMap::new(); - - assert_eq!(m.len(), 0); - assert!(m.is_empty()); - - let mut i = 0; - let old_raw_cap = m.raw_capacity(); - while old_raw_cap == m.raw_capacity() { - m.insert(i, i); - i += 1; - } - - assert_eq!(m.len(), i); - assert!(!m.is_empty()); - } - - #[test] - fn test_behavior_resize_policy() { - let mut m = HashMap::new(); - - assert_eq!(m.len(), 0); - assert_eq!(m.raw_capacity(), 0); - assert!(m.is_empty()); - - m.insert(0, 0); - m.remove(&0); - assert!(m.is_empty()); - let initial_raw_cap = m.raw_capacity(); - m.reserve(initial_raw_cap); - let raw_cap = m.raw_capacity(); - - assert_eq!(raw_cap, initial_raw_cap * 2); - - let mut i = 0; - for _ in 0..raw_cap * 3 / 4 { - m.insert(i, i); - i += 1; - } - // three quarters full - - assert_eq!(m.len(), i); - assert_eq!(m.raw_capacity(), raw_cap); - - for _ in 0..raw_cap / 4 { - m.insert(i, i); - i += 1; - } - // half full - - let new_raw_cap = m.raw_capacity(); - assert_eq!(new_raw_cap, raw_cap * 2); - - for _ in 0..raw_cap / 2 - 1 { - i -= 1; - m.remove(&i); - assert_eq!(m.raw_capacity(), new_raw_cap); - } - // A little more than one quarter full. - m.shrink_to_fit(); - assert_eq!(m.raw_capacity(), raw_cap); - // again, a little more than half full - for _ in 0..raw_cap / 2 - 1 { - i -= 1; - m.remove(&i); - } - m.shrink_to_fit(); - - assert_eq!(m.len(), i); - assert!(!m.is_empty()); - assert_eq!(m.raw_capacity(), initial_raw_cap); - } - - #[test] - fn test_reserve_shrink_to_fit() { - let mut m = HashMap::new(); - m.insert(0, 0); - m.remove(&0); - assert!(m.capacity() >= m.len()); - for i in 0..128 { - m.insert(i, i); - } - m.reserve(256); - - let usable_cap = m.capacity(); - for i in 128..(128 + 256) { - m.insert(i, i); - assert_eq!(m.capacity(), usable_cap); - } - - for i in 100..(128 + 256) { - assert_eq!(m.remove(&i), Some(i)); - } - m.shrink_to_fit(); - - assert_eq!(m.len(), 100); - assert!(!m.is_empty()); - assert!(m.capacity() >= m.len()); - - for i in 0..100 { - assert_eq!(m.remove(&i), Some(i)); - } - m.shrink_to_fit(); - m.insert(0, 0); - - assert_eq!(m.len(), 1); - assert!(m.capacity() >= m.len()); - assert_eq!(m.remove(&0), Some(0)); - } - - #[test] - fn test_from_iter() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let map: HashMap<_, _> = xs.iter().cloned().collect(); - - for &(k, v) in &xs { - assert_eq!(map.get(&k), Some(&v)); - } - } - - #[test] - fn test_size_hint() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let map: HashMap<_, _> = xs.iter().cloned().collect(); - - let mut iter = map.iter(); - - for _ in iter.by_ref().take(3) {} - - assert_eq!(iter.size_hint(), (3, Some(3))); - } - - #[test] - fn test_iter_len() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let map: HashMap<_, _> = xs.iter().cloned().collect(); - - let mut iter = map.iter(); - - for _ in iter.by_ref().take(3) {} - - assert_eq!(iter.len(), 3); - } - - #[test] - fn test_mut_size_hint() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let mut map: HashMap<_, _> = xs.iter().cloned().collect(); - - let mut iter = map.iter_mut(); - - for _ in iter.by_ref().take(3) {} - - assert_eq!(iter.size_hint(), (3, Some(3))); - } - - #[test] - fn test_iter_mut_len() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let mut map: HashMap<_, _> = xs.iter().cloned().collect(); - - let mut iter = map.iter_mut(); - - for _ in iter.by_ref().take(3) {} - - assert_eq!(iter.len(), 3); - } - - #[test] - fn test_index() { - let mut map = HashMap::new(); - - map.insert(1, 2); - map.insert(2, 1); - map.insert(3, 4); - - assert_eq!(map[&2], 1); - } - - #[test] - #[should_panic] - fn test_index_nonexistent() { - let mut map = HashMap::new(); - - map.insert(1, 2); - map.insert(2, 1); - map.insert(3, 4); - - map[&4]; - } - - #[test] - fn test_entry() { - let xs = [(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)]; - - let mut map: HashMap<_, _> = xs.iter().cloned().collect(); - - // Existing key (insert) - match map.entry(1) { - Vacant(_) => unreachable!(), - Occupied(mut view) => { - assert_eq!(view.get(), &10); - assert_eq!(view.insert(100), 10); - }, - } - assert_eq!(map.get(&1).unwrap(), &100); - assert_eq!(map.len(), 6); - - // Existing key (update) - match map.entry(2) { - Vacant(_) => unreachable!(), - Occupied(mut view) => { - let v = view.get_mut(); - let new_v = (*v) * 10; - *v = new_v; - }, - } - assert_eq!(map.get(&2).unwrap(), &200); - assert_eq!(map.len(), 6); - - // Existing key (take) - match map.entry(3) { - Vacant(_) => unreachable!(), - Occupied(view) => { - assert_eq!(view.remove(), 30); - }, - } - assert_eq!(map.get(&3), None); - assert_eq!(map.len(), 5); - - // Inexistent key (insert) - match map.entry(10) { - Occupied(_) => unreachable!(), - Vacant(view) => { - assert_eq!(*view.insert(1000), 1000); - }, - } - assert_eq!(map.get(&10).unwrap(), &1000); - assert_eq!(map.len(), 6); - } - - #[test] - fn test_entry_take_doesnt_corrupt() { - #![allow(deprecated)] //rand - // Test for #19292 - fn check(m: &HashMap) { - for k in m.keys() { - assert!(m.contains_key(k), "{} is in keys() but not in the map?", k); - } - } - - let mut m = HashMap::new(); - let mut rng = thread_rng(); - - // Populate the map with some items. - for _ in 0..50 { - let x = rng.gen_range(-10, 10); - m.insert(x, ()); - } - - for i in 0..1000 { - let x = rng.gen_range(-10, 10); - match m.entry(x) { - Vacant(_) => {}, - Occupied(e) => { - println!("{}: remove {}", i, x); - e.remove(); - }, - } - - check(&m); - } - } - - #[test] - fn test_extend_ref() { - let mut a = HashMap::new(); - a.insert(1, "one"); - let mut b = HashMap::new(); - b.insert(2, "two"); - b.insert(3, "three"); - - a.extend(&b); - - assert_eq!(a.len(), 3); - assert_eq!(a[&1], "one"); - assert_eq!(a[&2], "two"); - assert_eq!(a[&3], "three"); - } - - #[test] - fn test_capacity_not_less_than_len() { - let mut a = HashMap::new(); - let mut item = 0; - - for _ in 0..116 { - a.insert(item, 0); - item += 1; - } - - assert!(a.capacity() > a.len()); - - let free = a.capacity() - a.len(); - for _ in 0..free { - a.insert(item, 0); - item += 1; - } - - assert_eq!(a.len(), a.capacity()); - - // Insert at capacity should cause allocation. - a.insert(item, 0); - assert!(a.capacity() > a.len()); - } - - #[test] - fn test_occupied_entry_key() { - let mut a = HashMap::new(); - let key = "hello there"; - let value = "value goes here"; - assert!(a.is_empty()); - a.insert(key.clone(), value.clone()); - assert_eq!(a.len(), 1); - assert_eq!(a[key], value); - - match a.entry(key.clone()) { - Vacant(_) => panic!(), - Occupied(e) => assert_eq!(key, *e.key()), - } - assert_eq!(a.len(), 1); - assert_eq!(a[key], value); - } - - #[test] - fn test_vacant_entry_key() { - let mut a = HashMap::new(); - let key = "hello there"; - let value = "value goes here"; - - assert!(a.is_empty()); - match a.entry(key.clone()) { - Occupied(_) => panic!(), - Vacant(e) => { - assert_eq!(key, *e.key()); - e.insert(value.clone()); - }, - } - assert_eq!(a.len(), 1); - assert_eq!(a[key], value); - } - - #[test] - fn test_retain() { - let mut map: HashMap = (0..100).map(|x| (x, x * 10)).collect(); - - map.retain(|&k, _| k % 2 == 0); - assert_eq!(map.len(), 50); - assert_eq!(map[&2], 20); - assert_eq!(map[&4], 40); - assert_eq!(map[&6], 60); - } - - #[test] - fn test_adaptive() { - const TEST_LEN: usize = 5000; - // by cloning we get maps with the same hasher seed - let mut first = HashMap::new(); - let mut second = first.clone(); - first.extend((0..TEST_LEN).map(|i| (i, i))); - second.extend((TEST_LEN..TEST_LEN * 2).map(|i| (i, i))); - - for (&k, &v) in &second { - let prev_cap = first.capacity(); - let expect_grow = first.len() == prev_cap; - first.insert(k, v); - if !expect_grow && first.capacity() != prev_cap { - return; - } - } - panic!("Adaptive early resize failed"); - } -} diff --git a/components/hashglobe/src/hash_set.rs b/components/hashglobe/src/hash_set.rs deleted file mode 100644 index e5fca180c77..00000000000 --- a/components/hashglobe/src/hash_set.rs +++ /dev/null @@ -1,1648 +0,0 @@ -// Copyright 2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::borrow::Borrow; -use std::fmt; -use std::hash::{BuildHasher, Hash}; -use std::iter::{Chain, FromIterator}; -use std::ops::{BitAnd, BitOr, BitXor, Sub}; - -use super::hash_map::{self, HashMap, Keys, RandomState}; -use super::Recover; - -use crate::FailedAllocationError; - -// Future Optimization (FIXME!) -// ============================= -// -// Iteration over zero sized values is a noop. There is no need -// for `bucket.val` in the case of HashSet. I suppose we would need HKT -// to get rid of it properly. - -/// A hash set implemented as a `HashMap` where the value is `()`. -/// -/// As with the [`HashMap`] type, a `HashSet` requires that the elements -/// implement the [`Eq`] and [`Hash`] traits. This can frequently be achieved by -/// using `#[derive(PartialEq, Eq, Hash)]`. If you implement these yourself, -/// it is important that the following property holds: -/// -/// ```text -/// k1 == k2 -> hash(k1) == hash(k2) -/// ``` -/// -/// In other words, if two keys are equal, their hashes must be equal. -/// -/// -/// It is a logic error for an item to be modified in such a way that the -/// item's hash, as determined by the [`Hash`] trait, or its equality, as -/// determined by the [`Eq`] trait, changes while it is in the set. This is -/// normally only possible through [`Cell`], [`RefCell`], global state, I/O, or -/// unsafe code. -/// -/// # Examples -/// -/// ``` -/// use std::collections::HashSet; -/// // Type inference lets us omit an explicit type signature (which -/// // would be `HashSet<&str>` in this example). -/// let mut books = HashSet::new(); -/// -/// // Add some books. -/// books.insert("A Dance With Dragons"); -/// books.insert("To Kill a Mockingbird"); -/// books.insert("The Odyssey"); -/// books.insert("The Great Gatsby"); -/// -/// // Check for a specific one. -/// if !books.contains("The Winds of Winter") { -/// println!("We have {} books, but The Winds of Winter ain't one.", -/// books.len()); -/// } -/// -/// // Remove a book. -/// books.remove("The Odyssey"); -/// -/// // Iterate over everything. -/// for book in &books { -/// println!("{}", book); -/// } -/// ``` -/// -/// The easiest way to use `HashSet` with a custom type is to derive -/// [`Eq`] and [`Hash`]. We must also derive [`PartialEq`], this will in the -/// future be implied by [`Eq`]. -/// -/// ``` -/// use std::collections::HashSet; -/// #[derive(Hash, Eq, PartialEq, Debug)] -/// struct Viking<'a> { -/// name: &'a str, -/// power: usize, -/// } -/// -/// let mut vikings = HashSet::new(); -/// -/// vikings.insert(Viking { name: "Einar", power: 9 }); -/// vikings.insert(Viking { name: "Einar", power: 9 }); -/// vikings.insert(Viking { name: "Olaf", power: 4 }); -/// vikings.insert(Viking { name: "Harald", power: 8 }); -/// -/// // Use derived implementation to print the vikings. -/// for x in &vikings { -/// println!("{:?}", x); -/// } -/// ``` -/// -/// A `HashSet` with fixed list of elements can be initialized from an array: -/// -/// ``` -/// use std::collections::HashSet; -/// -/// fn main() { -/// let viking_names: HashSet<&str> = -/// [ "Einar", "Olaf", "Harald" ].iter().cloned().collect(); -/// // use the values stored in the set -/// } -/// ``` -/// -/// [`Cell`]: ../../std/cell/struct.Cell.html -/// [`Eq`]: ../../std/cmp/trait.Eq.html -/// [`Hash`]: ../../std/hash/trait.Hash.html -/// [`HashMap`]: struct.HashMap.html -/// [`PartialEq`]: ../../std/cmp/trait.PartialEq.html -/// [`RefCell`]: ../../std/cell/struct.RefCell.html -#[derive(Clone)] -pub struct HashSet { - map: HashMap, -} - -impl HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - /// Creates a new empty hash set which will use the given hasher to hash - /// keys. - /// - /// The hash set is also created with the default initial capacity. - /// - /// Warning: `hasher` is normally randomly generated, and - /// is designed to allow `HashSet`s to be resistant to attacks that - /// cause many collisions and very poor performance. Setting it - /// manually using this function can expose a DoS attack vector. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// use std::collections::hash_map::RandomState; - /// - /// let s = RandomState::new(); - /// let mut set = HashSet::with_hasher(s); - /// set.insert(2); - /// ``` - #[inline] - pub fn with_hasher(hasher: S) -> HashSet { - HashSet { - map: HashMap::with_hasher(hasher), - } - } - - /// Creates an empty `HashSet` with with the specified capacity, using - /// `hasher` to hash the keys. - /// - /// The hash set will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash set will not allocate. - /// - /// Warning: `hasher` is normally randomly generated, and - /// is designed to allow `HashSet`s to be resistant to attacks that - /// cause many collisions and very poor performance. Setting it - /// manually using this function can expose a DoS attack vector. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// use std::collections::hash_map::RandomState; - /// - /// let s = RandomState::new(); - /// let mut set = HashSet::with_capacity_and_hasher(10, s); - /// set.insert(1); - /// ``` - #[inline] - pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> HashSet { - HashSet { - map: HashMap::with_capacity_and_hasher(capacity, hasher), - } - } - - /// Returns a reference to the set's [`BuildHasher`]. - /// - /// [`BuildHasher`]: ../../std/hash/trait.BuildHasher.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// use std::collections::hash_map::RandomState; - /// - /// let hasher = RandomState::new(); - /// let set: HashSet = HashSet::with_hasher(hasher); - /// let hasher: &RandomState = set.hasher(); - /// ``` - pub fn hasher(&self) -> &S { - self.map.hasher() - } - - /// Returns the number of elements the set can hold without reallocating. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let set: HashSet = HashSet::with_capacity(100); - /// assert!(set.capacity() >= 100); - /// ``` - #[inline] - pub fn capacity(&self) -> usize { - self.map.capacity() - } - - /// Reserves capacity for at least `additional` more elements to be inserted - /// in the `HashSet`. The collection may reserve more space to avoid - /// frequent reallocations. - /// - /// # Panics - /// - /// Panics if the new allocation size overflows `usize`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let mut set: HashSet = HashSet::new(); - /// set.reserve(10); - /// assert!(set.capacity() >= 10); - /// ``` - pub fn reserve(&mut self, additional: usize) { - self.map.reserve(additional) - } - - /// Shrinks the capacity of the set as much as possible. It will drop - /// down as much as possible while maintaining the internal rules - /// and possibly leaving some space in accordance with the resize policy. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut set = HashSet::with_capacity(100); - /// set.insert(1); - /// set.insert(2); - /// assert!(set.capacity() >= 100); - /// set.shrink_to_fit(); - /// assert!(set.capacity() >= 2); - /// ``` - pub fn shrink_to_fit(&mut self) { - self.map.shrink_to_fit() - } - - /// An iterator visiting all elements in arbitrary order. - /// The iterator element type is `&'a T`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let mut set = HashSet::new(); - /// set.insert("a"); - /// set.insert("b"); - /// - /// // Will print in an arbitrary order. - /// for x in set.iter() { - /// println!("{}", x); - /// } - /// ``` - pub fn iter(&self) -> Iter<'_, T> { - Iter { - iter: self.map.keys(), - } - } - - /// Visits the values representing the difference, - /// i.e. the values that are in `self` but not in `other`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let b: HashSet<_> = [4, 2, 3, 4].iter().cloned().collect(); - /// - /// // Can be seen as `a - b`. - /// for x in a.difference(&b) { - /// println!("{}", x); // Print 1 - /// } - /// - /// let diff: HashSet<_> = a.difference(&b).collect(); - /// assert_eq!(diff, [1].iter().collect()); - /// - /// // Note that difference is not symmetric, - /// // and `b - a` means something else: - /// let diff: HashSet<_> = b.difference(&a).collect(); - /// assert_eq!(diff, [4].iter().collect()); - /// ``` - pub fn difference<'a>(&'a self, other: &'a HashSet) -> Difference<'a, T, S> { - Difference { - iter: self.iter(), - other, - } - } - - /// Visits the values representing the symmetric difference, - /// i.e. the values that are in `self` or in `other` but not in both. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let b: HashSet<_> = [4, 2, 3, 4].iter().cloned().collect(); - /// - /// // Print 1, 4 in arbitrary order. - /// for x in a.symmetric_difference(&b) { - /// println!("{}", x); - /// } - /// - /// let diff1: HashSet<_> = a.symmetric_difference(&b).collect(); - /// let diff2: HashSet<_> = b.symmetric_difference(&a).collect(); - /// - /// assert_eq!(diff1, diff2); - /// assert_eq!(diff1, [1, 4].iter().collect()); - /// ``` - pub fn symmetric_difference<'a>( - &'a self, - other: &'a HashSet, - ) -> SymmetricDifference<'a, T, S> { - SymmetricDifference { - iter: self.difference(other).chain(other.difference(self)), - } - } - - /// Visits the values representing the intersection, - /// i.e. the values that are both in `self` and `other`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let b: HashSet<_> = [4, 2, 3, 4].iter().cloned().collect(); - /// - /// // Print 2, 3 in arbitrary order. - /// for x in a.intersection(&b) { - /// println!("{}", x); - /// } - /// - /// let intersection: HashSet<_> = a.intersection(&b).collect(); - /// assert_eq!(intersection, [2, 3].iter().collect()); - /// ``` - pub fn intersection<'a>(&'a self, other: &'a HashSet) -> Intersection<'a, T, S> { - Intersection { - iter: self.iter(), - other, - } - } - - /// Visits the values representing the union, - /// i.e. all the values in `self` or `other`, without duplicates. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let b: HashSet<_> = [4, 2, 3, 4].iter().cloned().collect(); - /// - /// // Print 1, 2, 3, 4 in arbitrary order. - /// for x in a.union(&b) { - /// println!("{}", x); - /// } - /// - /// let union: HashSet<_> = a.union(&b).collect(); - /// assert_eq!(union, [1, 2, 3, 4].iter().collect()); - /// ``` - pub fn union<'a>(&'a self, other: &'a HashSet) -> Union<'a, T, S> { - Union { - iter: self.iter().chain(other.difference(self)), - } - } - - /// Returns the number of elements in the set. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut v = HashSet::new(); - /// assert_eq!(v.len(), 0); - /// v.insert(1); - /// assert_eq!(v.len(), 1); - /// ``` - pub fn len(&self) -> usize { - self.map.len() - } - - /// Returns true if the set contains no elements. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut v = HashSet::new(); - /// assert!(v.is_empty()); - /// v.insert(1); - /// assert!(!v.is_empty()); - /// ``` - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - /// Clears the set, returning all elements in an iterator. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut set: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// assert!(!set.is_empty()); - /// - /// // print 1, 2, 3 in an arbitrary order - /// for i in set.drain() { - /// println!("{}", i); - /// } - /// - /// assert!(set.is_empty()); - /// ``` - #[inline] - pub fn drain(&mut self) -> Drain<'_, T> { - Drain { - iter: self.map.drain(), - } - } - - /// Clears the set, removing all values. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut v = HashSet::new(); - /// v.insert(1); - /// v.clear(); - /// assert!(v.is_empty()); - /// ``` - pub fn clear(&mut self) - where - T: 'static, - { - self.map.clear() - } - - /// Returns `true` if the set contains a value. - /// - /// The value may be any borrowed form of the set's value type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the value type. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let set: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// assert_eq!(set.contains(&1), true); - /// assert_eq!(set.contains(&4), false); - /// ``` - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - pub fn contains(&self, value: &Q) -> bool - where - T: Borrow, - Q: Hash + Eq, - { - self.map.contains_key(value) - } - - /// Returns a reference to the value in the set, if any, that is equal to the given value. - /// - /// The value may be any borrowed form of the set's value type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the value type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - pub fn get(&self, value: &Q) -> Option<&T> - where - T: Borrow, - Q: Hash + Eq, - { - Recover::get(&self.map, value) - } - - /// Returns `true` if `self` has no elements in common with `other`. - /// This is equivalent to checking for an empty intersection. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let mut b = HashSet::new(); - /// - /// assert_eq!(a.is_disjoint(&b), true); - /// b.insert(4); - /// assert_eq!(a.is_disjoint(&b), true); - /// b.insert(1); - /// assert_eq!(a.is_disjoint(&b), false); - /// ``` - pub fn is_disjoint(&self, other: &HashSet) -> bool { - self.iter().all(|v| !other.contains(v)) - } - - /// Returns `true` if the set is a subset of another, - /// i.e. `other` contains at least all the values in `self`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let sup: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let mut set = HashSet::new(); - /// - /// assert_eq!(set.is_subset(&sup), true); - /// set.insert(2); - /// assert_eq!(set.is_subset(&sup), true); - /// set.insert(4); - /// assert_eq!(set.is_subset(&sup), false); - /// ``` - pub fn is_subset(&self, other: &HashSet) -> bool { - self.iter().all(|v| other.contains(v)) - } - - /// Returns `true` if the set is a superset of another, - /// i.e. `self` contains at least all the values in `other`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let sub: HashSet<_> = [1, 2].iter().cloned().collect(); - /// let mut set = HashSet::new(); - /// - /// assert_eq!(set.is_superset(&sub), false); - /// - /// set.insert(0); - /// set.insert(1); - /// assert_eq!(set.is_superset(&sub), false); - /// - /// set.insert(2); - /// assert_eq!(set.is_superset(&sub), true); - /// ``` - #[inline] - pub fn is_superset(&self, other: &HashSet) -> bool { - other.is_subset(self) - } - - /// Adds a value to the set. - /// - /// If the set did not have this value present, `true` is returned. - /// - /// If the set did have this value present, `false` is returned. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut set = HashSet::new(); - /// - /// assert_eq!(set.insert(2), true); - /// assert_eq!(set.insert(2), false); - /// assert_eq!(set.len(), 1); - /// ``` - pub fn insert(&mut self, value: T) -> bool { - self.map.insert(value, ()).is_none() - } - - /// Fallible version of `insert`. - #[inline] - pub fn try_insert(&mut self, value: T) -> Result { - Ok(self.map.try_insert(value, ())?.is_none()) - } - - /// Adds a value to the set, replacing the existing value, if any, that is equal to the given - /// one. Returns the replaced value. - pub fn replace(&mut self, value: T) -> Option { - Recover::replace(&mut self.map, value) - } - - /// Removes a value from the set. Returns `true` if the value was - /// present in the set. - /// - /// The value may be any borrowed form of the set's value type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the value type. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut set = HashSet::new(); - /// - /// set.insert(2); - /// assert_eq!(set.remove(&2), true); - /// assert_eq!(set.remove(&2), false); - /// ``` - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - pub fn remove(&mut self, value: &Q) -> bool - where - T: Borrow, - Q: Hash + Eq, - { - self.map.remove(value).is_some() - } - - /// Removes and returns the value in the set, if any, that is equal to the given one. - /// - /// The value may be any borrowed form of the set's value type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the value type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - pub fn take(&mut self, value: &Q) -> Option - where - T: Borrow, - Q: Hash + Eq, - { - Recover::take(&mut self.map, value) - } - - /// Retains only the elements specified by the predicate. - /// - /// In other words, remove all elements `e` such that `f(&e)` returns `false`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let xs = [1,2,3,4,5,6]; - /// let mut set: HashSet = xs.iter().cloned().collect(); - /// set.retain(|&k| k % 2 == 0); - /// assert_eq!(set.len(), 3); - /// ``` - pub fn retain(&mut self, mut f: F) - where - F: FnMut(&T) -> bool, - { - self.map.retain(|k, _| f(k)); - } -} - -impl PartialEq for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - fn eq(&self, other: &HashSet) -> bool { - if self.len() != other.len() { - return false; - } - - self.iter().all(|key| other.contains(key)) - } -} - -impl Eq for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ -} - -impl fmt::Debug for HashSet -where - T: Eq + Hash + fmt::Debug, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_set().entries(self.iter()).finish() - } -} - -impl FromIterator for HashSet -where - T: Eq + Hash, - S: BuildHasher + Default, -{ - fn from_iter>(iter: I) -> HashSet { - let mut set = HashSet::with_hasher(Default::default()); - set.extend(iter); - set - } -} - -impl Extend for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - fn extend>(&mut self, iter: I) { - self.map.extend(iter.into_iter().map(|k| (k, ()))); - } -} - -impl<'a, T, S> Extend<&'a T> for HashSet -where - T: 'a + Eq + Hash + Copy, - S: BuildHasher, -{ - fn extend>(&mut self, iter: I) { - self.extend(iter.into_iter().cloned()); - } -} - -impl Default for HashSet -where - T: Eq + Hash, - S: BuildHasher + Default, -{ - /// Creates an empty `HashSet` with the `Default` value for the hasher. - fn default() -> HashSet { - HashSet { - map: HashMap::default(), - } - } -} - -impl<'a, 'b, T, S> BitOr<&'b HashSet> for &'a HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher + Default, -{ - type Output = HashSet; - - /// Returns the union of `self` and `rhs` as a new `HashSet`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); - /// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect(); - /// - /// let set = &a | &b; - /// - /// let mut i = 0; - /// let expected = [1, 2, 3, 4, 5]; - /// for x in &set { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn bitor(self, rhs: &HashSet) -> HashSet { - self.union(rhs).cloned().collect() - } -} - -impl<'a, 'b, T, S> BitAnd<&'b HashSet> for &'a HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher + Default, -{ - type Output = HashSet; - - /// Returns the intersection of `self` and `rhs` as a new `HashSet`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); - /// let b: HashSet<_> = vec![2, 3, 4].into_iter().collect(); - /// - /// let set = &a & &b; - /// - /// let mut i = 0; - /// let expected = [2, 3]; - /// for x in &set { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn bitand(self, rhs: &HashSet) -> HashSet { - self.intersection(rhs).cloned().collect() - } -} - -impl<'a, 'b, T, S> BitXor<&'b HashSet> for &'a HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher + Default, -{ - type Output = HashSet; - - /// Returns the symmetric difference of `self` and `rhs` as a new `HashSet`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); - /// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect(); - /// - /// let set = &a ^ &b; - /// - /// let mut i = 0; - /// let expected = [1, 2, 4, 5]; - /// for x in &set { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn bitxor(self, rhs: &HashSet) -> HashSet { - self.symmetric_difference(rhs).cloned().collect() - } -} - -impl<'a, 'b, T, S> Sub<&'b HashSet> for &'a HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher + Default, -{ - type Output = HashSet; - - /// Returns the difference of `self` and `rhs` as a new `HashSet`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); - /// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect(); - /// - /// let set = &a - &b; - /// - /// let mut i = 0; - /// let expected = [1, 2]; - /// for x in &set { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn sub(self, rhs: &HashSet) -> HashSet { - self.difference(rhs).cloned().collect() - } -} - -/// An iterator over the items of a `HashSet`. -/// -/// This `struct` is created by the [`iter`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`iter`]: struct.HashSet.html#method.iter -pub struct Iter<'a, K> { - iter: Keys<'a, K, ()>, -} - -/// An owning iterator over the items of a `HashSet`. -/// -/// This `struct` is created by the [`into_iter`] method on [`HashSet`][`HashSet`] -/// (provided by the `IntoIterator` trait). See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`into_iter`]: struct.HashSet.html#method.into_iter -pub struct IntoIter { - iter: hash_map::IntoIter, -} - -/// A draining iterator over the items of a `HashSet`. -/// -/// This `struct` is created by the [`drain`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`drain`]: struct.HashSet.html#method.drain -pub struct Drain<'a, K: 'static> { - iter: hash_map::Drain<'a, K, ()>, -} - -/// A lazy iterator producing elements in the intersection of `HashSet`s. -/// -/// This `struct` is created by the [`intersection`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`intersection`]: struct.HashSet.html#method.intersection -pub struct Intersection<'a, T, S> { - // iterator of the first set - iter: Iter<'a, T>, - // the second set - other: &'a HashSet, -} - -/// A lazy iterator producing elements in the difference of `HashSet`s. -/// -/// This `struct` is created by the [`difference`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`difference`]: struct.HashSet.html#method.difference -pub struct Difference<'a, T, S> { - // iterator of the first set - iter: Iter<'a, T>, - // the second set - other: &'a HashSet, -} - -/// A lazy iterator producing elements in the symmetric difference of `HashSet`s. -/// -/// This `struct` is created by the [`symmetric_difference`] method on -/// [`HashSet`]. See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`symmetric_difference`]: struct.HashSet.html#method.symmetric_difference -pub struct SymmetricDifference<'a, T, S> { - iter: Chain, Difference<'a, T, S>>, -} - -/// A lazy iterator producing elements in the union of `HashSet`s. -/// -/// This `struct` is created by the [`union`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`union`]: struct.HashSet.html#method.union -pub struct Union<'a, T, S> { - iter: Chain, Difference<'a, T, S>>, -} - -impl<'a, T, S> IntoIterator for &'a HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - type IntoIter = Iter<'a, T>; - - fn into_iter(self) -> Iter<'a, T> { - self.iter() - } -} - -impl IntoIterator for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = T; - type IntoIter = IntoIter; - - /// Creates a consuming iterator, that is, one that moves each value out - /// of the set in arbitrary order. The set cannot be used after calling - /// this. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let mut set = HashSet::new(); - /// set.insert("a".to_string()); - /// set.insert("b".to_string()); - /// - /// // Not possible to collect to a Vec with a regular `.iter()`. - /// let v: Vec = set.into_iter().collect(); - /// - /// // Will print in an arbitrary order. - /// for x in &v { - /// println!("{}", x); - /// } - /// ``` - fn into_iter(self) -> IntoIter { - IntoIter { - iter: self.map.into_iter(), - } - } -} - -impl<'a, K> Clone for Iter<'a, K> { - fn clone(&self) -> Iter<'a, K> { - Iter { - iter: self.iter.clone(), - } - } -} -impl<'a, K> Iterator for Iter<'a, K> { - type Item = &'a K; - - fn next(&mut self) -> Option<&'a K> { - self.iter.next() - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} -impl<'a, K> ExactSizeIterator for Iter<'a, K> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl<'a, K: fmt::Debug> fmt::Debug for Iter<'a, K> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl Iterator for IntoIter { - type Item = K; - - fn next(&mut self) -> Option { - self.iter.next().map(|(k, _)| k) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} -impl ExactSizeIterator for IntoIter { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl fmt::Debug for IntoIter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let entries_iter = self.iter.inner.iter().map(|(k, _)| k); - f.debug_list().entries(entries_iter).finish() - } -} - -impl<'a, K> Iterator for Drain<'a, K> { - type Item = K; - - fn next(&mut self) -> Option { - self.iter.next().map(|(k, _)| k) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} -impl<'a, K> ExactSizeIterator for Drain<'a, K> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl<'a, K: fmt::Debug> fmt::Debug for Drain<'a, K> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let entries_iter = self.iter.inner.iter().map(|(k, _)| k); - f.debug_list().entries(entries_iter).finish() - } -} - -impl<'a, T, S> Clone for Intersection<'a, T, S> { - fn clone(&self) -> Intersection<'a, T, S> { - Intersection { - iter: self.iter.clone(), - ..*self - } - } -} - -impl<'a, T, S> Iterator for Intersection<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option<&'a T> { - loop { - let elt = self.iter.next()?; - if self.other.contains(elt) { - return Some(elt); - } - } - } - - fn size_hint(&self) -> (usize, Option) { - let (_, upper) = self.iter.size_hint(); - (0, upper) - } -} - -impl<'a, T, S> fmt::Debug for Intersection<'a, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl<'a, T, S> Clone for Difference<'a, T, S> { - fn clone(&self) -> Difference<'a, T, S> { - Difference { - iter: self.iter.clone(), - ..*self - } - } -} - -impl<'a, T, S> Iterator for Difference<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option<&'a T> { - loop { - let elt = self.iter.next()?; - if !self.other.contains(elt) { - return Some(elt); - } - } - } - - fn size_hint(&self) -> (usize, Option) { - let (_, upper) = self.iter.size_hint(); - (0, upper) - } -} - -impl<'a, T, S> fmt::Debug for Difference<'a, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl<'a, T, S> Clone for SymmetricDifference<'a, T, S> { - fn clone(&self) -> SymmetricDifference<'a, T, S> { - SymmetricDifference { - iter: self.iter.clone(), - } - } -} - -impl<'a, T, S> Iterator for SymmetricDifference<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option<&'a T> { - self.iter.next() - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, T, S> fmt::Debug for SymmetricDifference<'a, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl<'a, T, S> Clone for Union<'a, T, S> { - fn clone(&self) -> Union<'a, T, S> { - Union { - iter: self.iter.clone(), - } - } -} - -impl<'a, T, S> fmt::Debug for Union<'a, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl<'a, T, S> Iterator for Union<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option<&'a T> { - self.iter.next() - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -#[allow(dead_code)] -fn assert_covariance() { - fn set<'new>(v: HashSet<&'static str>) -> HashSet<&'new str> { - v - } - fn iter<'a, 'new>(v: Iter<'a, &'static str>) -> Iter<'a, &'new str> { - v - } - fn into_iter<'new>(v: IntoIter<&'static str>) -> IntoIter<&'new str> { - v - } - fn difference<'a, 'new>( - v: Difference<'a, &'static str, RandomState>, - ) -> Difference<'a, &'new str, RandomState> { - v - } - fn symmetric_difference<'a, 'new>( - v: SymmetricDifference<'a, &'static str, RandomState>, - ) -> SymmetricDifference<'a, &'new str, RandomState> { - v - } - fn intersection<'a, 'new>( - v: Intersection<'a, &'static str, RandomState>, - ) -> Intersection<'a, &'new str, RandomState> { - v - } - fn union<'a, 'new>( - v: Union<'a, &'static str, RandomState>, - ) -> Union<'a, &'new str, RandomState> { - v - } - fn drain<'new>(d: Drain<'static, &'static str>) -> Drain<'new, &'new str> { - d - } -} - -#[cfg(test)] -mod test_set { - use super::hash_map::RandomState; - use super::HashSet; - - #[test] - fn test_zero_capacities() { - type HS = HashSet; - - let s = HS::new(); - assert_eq!(s.capacity(), 0); - - let s = HS::default(); - assert_eq!(s.capacity(), 0); - - let s = HS::with_hasher(RandomState::new()); - assert_eq!(s.capacity(), 0); - - let s = HS::with_capacity(0); - assert_eq!(s.capacity(), 0); - - let s = HS::with_capacity_and_hasher(0, RandomState::new()); - assert_eq!(s.capacity(), 0); - - let mut s = HS::new(); - s.insert(1); - s.insert(2); - s.remove(&1); - s.remove(&2); - s.shrink_to_fit(); - assert_eq!(s.capacity(), 0); - - let mut s = HS::new(); - s.reserve(0); - assert_eq!(s.capacity(), 0); - } - - #[test] - fn test_disjoint() { - let mut xs = HashSet::new(); - let mut ys = HashSet::new(); - assert!(xs.is_disjoint(&ys)); - assert!(ys.is_disjoint(&xs)); - assert!(xs.insert(5)); - assert!(ys.insert(11)); - assert!(xs.is_disjoint(&ys)); - assert!(ys.is_disjoint(&xs)); - assert!(xs.insert(7)); - assert!(xs.insert(19)); - assert!(xs.insert(4)); - assert!(ys.insert(2)); - assert!(ys.insert(-11)); - assert!(xs.is_disjoint(&ys)); - assert!(ys.is_disjoint(&xs)); - assert!(ys.insert(7)); - assert!(!xs.is_disjoint(&ys)); - assert!(!ys.is_disjoint(&xs)); - } - - #[test] - fn test_subset_and_superset() { - let mut a = HashSet::new(); - assert!(a.insert(0)); - assert!(a.insert(5)); - assert!(a.insert(11)); - assert!(a.insert(7)); - - let mut b = HashSet::new(); - assert!(b.insert(0)); - assert!(b.insert(7)); - assert!(b.insert(19)); - assert!(b.insert(250)); - assert!(b.insert(11)); - assert!(b.insert(200)); - - assert!(!a.is_subset(&b)); - assert!(!a.is_superset(&b)); - assert!(!b.is_subset(&a)); - assert!(!b.is_superset(&a)); - - assert!(b.insert(5)); - - assert!(a.is_subset(&b)); - assert!(!a.is_superset(&b)); - assert!(!b.is_subset(&a)); - assert!(b.is_superset(&a)); - } - - #[test] - fn test_iterate() { - let mut a = HashSet::new(); - for i in 0..32 { - assert!(a.insert(i)); - } - let mut observed: u32 = 0; - for k in &a { - observed |= 1 << *k; - } - assert_eq!(observed, 0xFFFF_FFFF); - } - - #[test] - fn test_intersection() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - assert!(a.insert(11)); - assert!(a.insert(1)); - assert!(a.insert(3)); - assert!(a.insert(77)); - assert!(a.insert(103)); - assert!(a.insert(5)); - assert!(a.insert(-5)); - - assert!(b.insert(2)); - assert!(b.insert(11)); - assert!(b.insert(77)); - assert!(b.insert(-9)); - assert!(b.insert(-42)); - assert!(b.insert(5)); - assert!(b.insert(3)); - - let mut i = 0; - let expected = [3, 5, 11, 77]; - for x in a.intersection(&b) { - assert!(expected.contains(x)); - i += 1 - } - assert_eq!(i, expected.len()); - } - - #[test] - fn test_difference() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - assert!(a.insert(1)); - assert!(a.insert(3)); - assert!(a.insert(5)); - assert!(a.insert(9)); - assert!(a.insert(11)); - - assert!(b.insert(3)); - assert!(b.insert(9)); - - let mut i = 0; - let expected = [1, 5, 11]; - for x in a.difference(&b) { - assert!(expected.contains(x)); - i += 1 - } - assert_eq!(i, expected.len()); - } - - #[test] - fn test_symmetric_difference() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - assert!(a.insert(1)); - assert!(a.insert(3)); - assert!(a.insert(5)); - assert!(a.insert(9)); - assert!(a.insert(11)); - - assert!(b.insert(-2)); - assert!(b.insert(3)); - assert!(b.insert(9)); - assert!(b.insert(14)); - assert!(b.insert(22)); - - let mut i = 0; - let expected = [-2, 1, 5, 11, 14, 22]; - for x in a.symmetric_difference(&b) { - assert!(expected.contains(x)); - i += 1 - } - assert_eq!(i, expected.len()); - } - - #[test] - fn test_union() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - assert!(a.insert(1)); - assert!(a.insert(3)); - assert!(a.insert(5)); - assert!(a.insert(9)); - assert!(a.insert(11)); - assert!(a.insert(16)); - assert!(a.insert(19)); - assert!(a.insert(24)); - - assert!(b.insert(-2)); - assert!(b.insert(1)); - assert!(b.insert(5)); - assert!(b.insert(9)); - assert!(b.insert(13)); - assert!(b.insert(19)); - - let mut i = 0; - let expected = [-2, 1, 3, 5, 9, 11, 13, 16, 19, 24]; - for x in a.union(&b) { - assert!(expected.contains(x)); - i += 1 - } - assert_eq!(i, expected.len()); - } - - #[test] - fn test_from_iter() { - let xs = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - - let set: HashSet<_> = xs.iter().cloned().collect(); - - for x in &xs { - assert!(set.contains(x)); - } - } - - #[test] - fn test_move_iter() { - let hs = { - let mut hs = HashSet::new(); - - hs.insert('a'); - hs.insert('b'); - - hs - }; - - let v = hs.into_iter().collect::>(); - assert!(v == ['a', 'b'] || v == ['b', 'a']); - } - - #[test] - fn test_eq() { - // These constants once happened to expose a bug in insert(). - // I'm keeping them around to prevent a regression. - let mut s1 = HashSet::new(); - - s1.insert(1); - s1.insert(2); - s1.insert(3); - - let mut s2 = HashSet::new(); - - s2.insert(1); - s2.insert(2); - - assert_ne!(s1, s2); - - s2.insert(3); - - assert_eq!(s1, s2); - } - - #[test] - fn test_show() { - let mut set = HashSet::new(); - let empty = HashSet::::new(); - - set.insert(1); - set.insert(2); - - let set_str = format!("{:?}", set); - - assert!(set_str == "{1, 2}" || set_str == "{2, 1}"); - assert_eq!(format!("{:?}", empty), "{}"); - } - - #[test] - fn test_trivial_drain() { - let mut s = HashSet::::new(); - for _ in s.drain() {} - assert!(s.is_empty()); - drop(s); - - let mut s = HashSet::::new(); - drop(s.drain()); - assert!(s.is_empty()); - } - - #[test] - fn test_drain() { - let mut s: HashSet<_> = (1..100).collect(); - - // try this a bunch of times to make sure we don't screw up internal state. - for _ in 0..20 { - assert_eq!(s.len(), 99); - - { - let mut last_i = 0; - let mut d = s.drain(); - for (i, x) in d.by_ref().take(50).enumerate() { - last_i = i; - assert_ne!(x, 0); - } - assert_eq!(last_i, 49); - } - - for _ in &s { - panic!("s should be empty!"); - } - - // reset to try again. - s.extend(1..100); - } - } - - #[test] - fn test_replace() { - use std::hash; - - #[derive(Debug)] - struct Foo(&'static str, i32); - - impl PartialEq for Foo { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - - impl Eq for Foo {} - - impl hash::Hash for Foo { - fn hash(&self, h: &mut H) { - self.0.hash(h); - } - } - - let mut s = HashSet::new(); - assert_eq!(s.replace(Foo("a", 1)), None); - assert_eq!(s.len(), 1); - assert_eq!(s.replace(Foo("a", 2)), Some(Foo("a", 1))); - assert_eq!(s.len(), 1); - - let mut it = s.iter(); - assert_eq!(it.next(), Some(&Foo("a", 2))); - assert_eq!(it.next(), None); - } - - #[test] - fn test_extend_ref() { - let mut a = HashSet::new(); - a.insert(1); - - a.extend(&[2, 3, 4]); - - assert_eq!(a.len(), 4); - assert!(a.contains(&1)); - assert!(a.contains(&2)); - assert!(a.contains(&3)); - assert!(a.contains(&4)); - - let mut b = HashSet::new(); - b.insert(5); - b.insert(6); - - a.extend(&b); - - assert_eq!(a.len(), 6); - assert!(a.contains(&1)); - assert!(a.contains(&2)); - assert!(a.contains(&3)); - assert!(a.contains(&4)); - assert!(a.contains(&5)); - assert!(a.contains(&6)); - } - - #[test] - fn test_retain() { - let xs = [1, 2, 3, 4, 5, 6]; - let mut set: HashSet = xs.iter().cloned().collect(); - set.retain(|&k| k % 2 == 0); - assert_eq!(set.len(), 3); - assert!(set.contains(&2)); - assert!(set.contains(&4)); - assert!(set.contains(&6)); - } -} diff --git a/components/hashglobe/src/lib.rs b/components/hashglobe/src/lib.rs deleted file mode 100644 index 865a4dab113..00000000000 --- a/components/hashglobe/src/lib.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -pub mod alloc; -pub mod hash_map; -pub mod hash_set; -mod shim; -mod table; - -pub mod fake; - -use std::{error, fmt}; - -trait Recover { - type Key; - - fn get(&self, key: &Q) -> Option<&Self::Key>; - fn take(&mut self, key: &Q) -> Option; - fn replace(&mut self, key: Self::Key) -> Option; -} - -#[derive(Debug)] -pub struct AllocationInfo { - /// The size we are requesting. - size: usize, - /// The alignment we are requesting. - alignment: usize, -} - -#[derive(Debug)] -pub struct FailedAllocationError { - reason: &'static str, - /// The allocation info we are requesting, if needed. - allocation_info: Option, -} - -impl FailedAllocationError { - #[inline] - pub fn new(reason: &'static str) -> Self { - Self { - reason, - allocation_info: None, - } - } -} - -impl error::Error for FailedAllocationError { - fn description(&self) -> &str { - self.reason - } -} - -impl fmt::Display for FailedAllocationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.allocation_info { - Some(ref info) => write!( - f, - "{}, allocation: (size: {}, alignment: {})", - self.reason, info.size, info.alignment - ), - None => self.reason.fmt(f), - } - } -} diff --git a/components/hashglobe/src/shim.rs b/components/hashglobe/src/shim.rs deleted file mode 100644 index 8b08af871d6..00000000000 --- a/components/hashglobe/src/shim.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::marker::PhantomData; - -// FIXME: remove this and use std::ptr::NonNull when Firefox requires Rust 1.25+ -pub struct NonZeroPtr(&'static T); - -impl NonZeroPtr { - pub unsafe fn new_unchecked(ptr: *mut T) -> Self { - NonZeroPtr(&*ptr) - } - pub fn as_ptr(&self) -> *mut T { - self.0 as *const T as *mut T - } -} - -pub struct Unique { - ptr: NonZeroPtr, - _marker: PhantomData, -} - -impl Unique { - pub unsafe fn new_unchecked(ptr: *mut T) -> Self { - Unique { - ptr: NonZeroPtr::new_unchecked(ptr), - _marker: PhantomData, - } - } - pub fn as_ptr(&self) -> *mut T { - self.ptr.as_ptr() - } -} - -unsafe impl Send for Unique {} - -unsafe impl Sync for Unique {} - -pub struct Shared { - ptr: NonZeroPtr, - _marker: PhantomData, - // force it to be !Send/!Sync - _marker2: PhantomData<*const u8>, -} - -impl Shared { - pub unsafe fn new_unchecked(ptr: *mut T) -> Self { - Shared { - ptr: NonZeroPtr::new_unchecked(ptr), - _marker: PhantomData, - _marker2: PhantomData, - } - } - - #[allow(clippy::mut_from_ref)] - pub unsafe fn as_mut(&self) -> &mut T { - &mut *self.ptr.as_ptr() - } -} - -impl<'a, T> From<&'a mut T> for Shared { - fn from(reference: &'a mut T) -> Self { - unsafe { Shared::new_unchecked(reference) } - } -} diff --git a/components/hashglobe/src/table.rs b/components/hashglobe/src/table.rs deleted file mode 100644 index 06c87e863dc..00000000000 --- a/components/hashglobe/src/table.rs +++ /dev/null @@ -1,1231 +0,0 @@ -// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use crate::alloc::{alloc, dealloc}; -use crate::shim::{Shared, Unique}; -use std::cmp; -use std::hash::{BuildHasher, Hash, Hasher}; -use std::marker; -use std::mem::{self, align_of, size_of}; -use std::ops::{Deref, DerefMut}; -use std::ptr; - -use self::BucketState::*; -use crate::FailedAllocationError; - -/// Integer type used for stored hash values. -/// -/// No more than bit_width(usize) bits are needed to select a bucket. -/// -/// The most significant bit is ours to use for tagging `SafeHash`. -/// -/// (Even if we could have usize::MAX bytes allocated for buckets, -/// each bucket stores at least a `HashUint`, so there can be no more than -/// usize::MAX / size_of(usize) buckets.) -type HashUint = usize; - -const EMPTY_BUCKET: HashUint = 0; -const EMPTY: usize = 1; - -/// Special `Unique` that uses the lower bit of the pointer -/// to expose a boolean tag. -/// Note: when the pointer is initialized to EMPTY `.ptr()` will return -/// null and the tag functions shouldn't be used. -struct TaggedHashUintPtr(Unique); - -impl TaggedHashUintPtr { - #[inline] - unsafe fn new(ptr: *mut HashUint) -> Self { - debug_assert!(ptr as usize & 1 == 0 || ptr as usize == EMPTY as usize); - TaggedHashUintPtr(Unique::new_unchecked(ptr)) - } - - #[inline] - fn set_tag(&mut self, value: bool) { - let mut usize_ptr = self.0.as_ptr() as usize; - unsafe { - if value { - usize_ptr |= 1; - } else { - usize_ptr &= !1; - } - self.0 = Unique::new_unchecked(usize_ptr as *mut HashUint) - } - } - - #[inline] - fn tag(&self) -> bool { - (self.0.as_ptr() as usize) & 1 == 1 - } - - #[inline] - fn ptr(&self) -> *mut HashUint { - (self.0.as_ptr() as usize & !1) as *mut HashUint - } -} - -/// The raw hashtable, providing safe-ish access to the unzipped and highly -/// optimized arrays of hashes, and key-value pairs. -/// -/// This design is a lot faster than the naive -/// `Vec>`, because we don't pay for the overhead of an -/// option on every element, and we get a generally more cache-aware design. -/// -/// Essential invariants of this structure: -/// -/// - if t.hashes[i] == EMPTY_BUCKET, then `Bucket::at_index(&t, i).raw` -/// points to 'undefined' contents. Don't read from it. This invariant is -/// enforced outside this module with the `EmptyBucket`, `FullBucket`, -/// and `SafeHash` types. -/// -/// - An `EmptyBucket` is only constructed at an index with -/// a hash of EMPTY_BUCKET. -/// -/// - A `FullBucket` is only constructed at an index with a -/// non-EMPTY_BUCKET hash. -/// -/// - A `SafeHash` is only constructed for non-`EMPTY_BUCKET` hash. We get -/// around hashes of zero by changing them to 0x8000_0000_0000_0000, -/// which will likely map to the same bucket, while not being confused -/// with "empty". -/// -/// - Both "arrays represented by pointers" are the same length: -/// `capacity`. This is set at creation and never changes. The arrays -/// are unzipped and are more cache aware (scanning through 8 hashes -/// brings in at most 2 cache lines, since they're all right beside each -/// other). This layout may waste space in padding such as in a map from -/// u64 to u8, but is a more cache conscious layout as the key-value pairs -/// are only very shortly probed and the desired value will be in the same -/// or next cache line. -/// -/// You can kind of think of this module/data structure as a safe wrapper -/// around just the "table" part of the hashtable. It enforces some -/// invariants at the type level and employs some performance trickery, -/// but in general is just a tricked out `Vec>`. -/// -/// The hashtable also exposes a special boolean tag. The tag defaults to false -/// when the RawTable is created and is accessible with the `tag` and `set_tag` -/// functions. -pub struct RawTable { - capacity_mask: usize, - size: usize, - hashes: TaggedHashUintPtr, - - // Because K/V do not appear directly in any of the types in the struct, - // inform rustc that in fact instances of K and V are reachable from here. - marker: marker::PhantomData<(K, V)>, -} - -unsafe impl Send for RawTable {} -unsafe impl Sync for RawTable {} - -// An unsafe view of a RawTable bucket -// Valid indexes are within [0..table_capacity) -pub struct RawBucket { - hash_start: *mut HashUint, - // We use *const to ensure covariance with respect to K and V - pair_start: *const (K, V), - idx: usize, - _marker: marker::PhantomData<(K, V)>, -} - -impl Copy for RawBucket {} -impl Clone for RawBucket { - fn clone(&self) -> RawBucket { - *self - } -} - -pub struct Bucket { - raw: RawBucket, - table: M, -} - -impl Copy for Bucket {} -impl Clone for Bucket { - fn clone(&self) -> Bucket { - *self - } -} - -pub struct EmptyBucket { - raw: RawBucket, - table: M, -} - -pub struct FullBucket { - raw: RawBucket, - table: M, -} - -pub type FullBucketMut<'table, K, V> = FullBucket>; - -pub enum BucketState { - Empty(EmptyBucket), - Full(FullBucket), -} - -// A GapThenFull encapsulates the state of two consecutive buckets at once. -// The first bucket, called the gap, is known to be empty. -// The second bucket is full. -pub struct GapThenFull { - gap: EmptyBucket, - full: FullBucket, -} - -/// A hash that is not zero, since we use a hash of zero to represent empty -/// buckets. -#[derive(PartialEq, Copy, Clone)] -pub struct SafeHash { - hash: HashUint, -} - -impl SafeHash { - /// Peek at the hash value, which is guaranteed to be non-zero. - #[inline(always)] - pub fn inspect(&self) -> HashUint { - self.hash - } - - #[inline(always)] - pub fn new(hash: u64) -> Self { - // We need to avoid 0 in order to prevent collisions with - // EMPTY_HASH. We can maintain our precious uniform distribution - // of initial indexes by unconditionally setting the MSB, - // effectively reducing the hashes by one bit. - // - // Truncate hash to fit in `HashUint`. - let hash_bits = HashUint::BITS; - SafeHash { - hash: (1 << (hash_bits - 1)) | (hash as HashUint), - } - } -} - -/// We need to remove hashes of 0. That's reserved for empty buckets. -/// This function wraps up `hash_keyed` to be the only way outside this -/// module to generate a SafeHash. -pub fn make_hash(hash_state: &S, t: &T) -> SafeHash -where - T: Hash, - S: BuildHasher, -{ - let mut state = hash_state.build_hasher(); - t.hash(&mut state); - SafeHash::new(state.finish()) -} - -// `replace` casts a `*HashUint` to a `*SafeHash`. Since we statically -// ensure that a `FullBucket` points to an index with a non-zero hash, -// and a `SafeHash` is just a `HashUint` with a different name, this is -// safe. -// -// This test ensures that a `SafeHash` really IS the same size as a -// `HashUint`. If you need to change the size of `SafeHash` (and -// consequently made this test fail), `replace` needs to be -// modified to no longer assume this. -#[test] -fn can_alias_safehash_as_hash() { - assert_eq!(size_of::(), size_of::()) -} - -// RawBucket methods are unsafe as it's possible to -// make a RawBucket point to invalid memory using safe code. -impl RawBucket { - unsafe fn hash(&self) -> *mut HashUint { - self.hash_start.offset(self.idx as isize) - } - unsafe fn pair(&self) -> *mut (K, V) { - self.pair_start.add(self.idx) as *mut (K, V) - } - unsafe fn hash_pair(&self) -> (*mut HashUint, *mut (K, V)) { - (self.hash(), self.pair()) - } -} - -// Buckets hold references to the table. -impl FullBucket { - /// Borrow a reference to the table. - pub fn table(&self) -> &M { - &self.table - } - /// Borrow a mutable reference to the table. - pub fn table_mut(&mut self) -> &mut M { - &mut self.table - } - /// Move out the reference to the table. - pub fn into_table(self) -> M { - self.table - } - /// Get the raw index. - pub fn index(&self) -> usize { - self.raw.idx - } - /// Get the raw bucket. - pub fn raw(&self) -> RawBucket { - self.raw - } -} - -impl EmptyBucket { - /// Borrow a reference to the table. - pub fn table(&self) -> &M { - &self.table - } - /// Borrow a mutable reference to the table. - pub fn table_mut(&mut self) -> &mut M { - &mut self.table - } -} - -impl Bucket { - /// Get the raw index. - pub fn index(&self) -> usize { - self.raw.idx - } - /// get the table. - pub fn into_table(self) -> M { - self.table - } -} - -impl Deref for FullBucket -where - M: Deref>, -{ - type Target = RawTable; - fn deref(&self) -> &RawTable { - &self.table - } -} - -/// `Put` is implemented for types which provide access to a table and cannot be invalidated -/// by filling a bucket. A similar implementation for `Take` is possible. -pub trait Put { - unsafe fn borrow_table_mut(&mut self) -> &mut RawTable; -} - -impl<'t, K, V> Put for &'t mut RawTable { - unsafe fn borrow_table_mut(&mut self) -> &mut RawTable { - *self - } -} - -impl Put for Bucket -where - M: Put, -{ - unsafe fn borrow_table_mut(&mut self) -> &mut RawTable { - self.table.borrow_table_mut() - } -} - -impl Put for FullBucket -where - M: Put, -{ - unsafe fn borrow_table_mut(&mut self) -> &mut RawTable { - self.table.borrow_table_mut() - } -} - -impl>> Bucket { - pub fn new(table: M, hash: SafeHash) -> Bucket { - Bucket::at_index(table, hash.inspect() as usize) - } - - pub fn new_from(r: RawBucket, t: M) -> Bucket { - Bucket { raw: r, table: t } - } - - pub fn at_index(table: M, ib_index: usize) -> Bucket { - // if capacity is 0, then the RawBucket will be populated with bogus pointers. - // This is an uncommon case though, so avoid it in release builds. - debug_assert!( - table.capacity() > 0, - "Table should have capacity at this point" - ); - let ib_index = ib_index & table.capacity_mask; - Bucket { - raw: table.raw_bucket_at(ib_index), - table, - } - } - - pub fn first(table: M) -> Bucket { - Bucket { - raw: table.raw_bucket_at(0), - table, - } - } - - // "So a few of the first shall be last: for many be called, - // but few chosen." - // - // We'll most likely encounter a few buckets at the beginning that - // have their initial buckets near the end of the table. They were - // placed at the beginning as the probe wrapped around the table - // during insertion. We must skip forward to a bucket that won't - // get reinserted too early and won't unfairly steal others spot. - // This eliminates the need for robin hood. - pub fn head_bucket(table: M) -> Bucket { - let mut bucket = Bucket::first(table); - - loop { - bucket = match bucket.peek() { - Full(full) => { - if full.displacement() == 0 { - // This bucket occupies its ideal spot. - // It indicates the start of another "cluster". - bucket = full.into_bucket(); - break; - } - // Leaving this bucket in the last cluster for later. - full.into_bucket() - }, - Empty(b) => { - // Encountered a hole between clusters. - b.into_bucket() - }, - }; - bucket.next(); - } - bucket - } - - /// Reads a bucket at a given index, returning an enum indicating whether - /// it's initialized or not. You need to match on this enum to get - /// the appropriate types to call most of the other functions in - /// this module. - pub fn peek(self) -> BucketState { - match unsafe { *self.raw.hash() } { - EMPTY_BUCKET => Empty(EmptyBucket { - raw: self.raw, - table: self.table, - }), - _ => Full(FullBucket { - raw: self.raw, - table: self.table, - }), - } - } - - /// Modifies the bucket in place to make it point to the next slot. - pub fn next(&mut self) { - self.raw.idx = self.raw.idx.wrapping_add(1) & self.table.capacity_mask; - } - - /// Modifies the bucket in place to make it point to the previous slot. - pub fn prev(&mut self) { - self.raw.idx = self.raw.idx.wrapping_sub(1) & self.table.capacity_mask; - } -} - -impl>> EmptyBucket { - #[inline] - pub fn next(self) -> Bucket { - let mut bucket = self.into_bucket(); - bucket.next(); - bucket - } - - #[inline] - pub fn into_bucket(self) -> Bucket { - Bucket { - raw: self.raw, - table: self.table, - } - } - - pub fn gap_peek(self) -> Result, Bucket> { - let gap = EmptyBucket { - raw: self.raw, - table: (), - }; - - match self.next().peek() { - Full(bucket) => Ok(GapThenFull { gap, full: bucket }), - Empty(e) => Err(e.into_bucket()), - } - } -} - -impl EmptyBucket -where - M: Put, -{ - /// Puts given key and value pair, along with the key's hash, - /// into this bucket in the hashtable. Note how `self` is 'moved' into - /// this function, because this slot will no longer be empty when - /// we return! A `FullBucket` is returned for later use, pointing to - /// the newly-filled slot in the hashtable. - /// - /// Use `make_hash` to construct a `SafeHash` to pass to this function. - pub fn put(mut self, hash: SafeHash, key: K, value: V) -> FullBucket { - unsafe { - *self.raw.hash() = hash.inspect(); - ptr::write(self.raw.pair(), (key, value)); - - self.table.borrow_table_mut().size += 1; - } - - FullBucket { - raw: self.raw, - table: self.table, - } - } -} - -impl>> FullBucket { - #[inline] - pub fn next(self) -> Bucket { - let mut bucket = self.into_bucket(); - bucket.next(); - bucket - } - - #[inline] - pub fn into_bucket(self) -> Bucket { - Bucket { - raw: self.raw, - table: self.table, - } - } - - /// Duplicates the current position. This can be useful for operations - /// on two or more buckets. - pub fn stash(self) -> FullBucket { - FullBucket { - raw: self.raw, - table: self, - } - } - - /// Get the distance between this bucket and the 'ideal' location - /// as determined by the key's hash stored in it. - /// - /// In the cited blog posts above, this is called the "distance to - /// initial bucket", or DIB. Also known as "probe count". - pub fn displacement(&self) -> usize { - // Calculates the distance one has to travel when going from - // `hash mod capacity` onwards to `idx mod capacity`, wrapping around - // if the destination is not reached before the end of the table. - (self.raw.idx.wrapping_sub(self.hash().inspect() as usize)) & self.table.capacity_mask - } - - #[inline] - pub fn hash(&self) -> SafeHash { - unsafe { - SafeHash { - hash: *self.raw.hash(), - } - } - } - - /// Gets references to the key and value at a given index. - pub fn read(&self) -> (&K, &V) { - unsafe { - let pair_ptr = self.raw.pair(); - (&(*pair_ptr).0, &(*pair_ptr).1) - } - } -} - -// We take a mutable reference to the table instead of accepting anything that -// implements `DerefMut` to prevent fn `take` from being called on `stash`ed -// buckets. -impl<'t, K, V> FullBucket> { - /// Removes this bucket's key and value from the hashtable. - /// - /// This works similarly to `put`, building an `EmptyBucket` out of the - /// taken bucket. - pub fn take(self) -> (EmptyBucket>, K, V) { - self.table.size -= 1; - - unsafe { - *self.raw.hash() = EMPTY_BUCKET; - let (k, v) = ptr::read(self.raw.pair()); - ( - EmptyBucket { - raw: self.raw, - table: self.table, - }, - k, - v, - ) - } - } -} - -// This use of `Put` is misleading and restrictive, but safe and sufficient for our use cases -// where `M` is a full bucket or table reference type with mutable access to the table. -impl FullBucket -where - M: Put, -{ - pub fn replace(&mut self, h: SafeHash, k: K, v: V) -> (SafeHash, K, V) { - unsafe { - let old_hash = ptr::replace(self.raw.hash() as *mut SafeHash, h); - let (old_key, old_val) = ptr::replace(self.raw.pair(), (k, v)); - - (old_hash, old_key, old_val) - } - } -} - -impl FullBucket -where - M: Deref> + DerefMut, -{ - /// Gets mutable references to the key and value at a given index. - pub fn read_mut(&mut self) -> (&mut K, &mut V) { - unsafe { - let pair_ptr = self.raw.pair(); - (&mut (*pair_ptr).0, &mut (*pair_ptr).1) - } - } -} - -impl<'t, K, V, M> FullBucket -where - M: Deref> + 't, -{ - /// Exchange a bucket state for immutable references into the table. - /// Because the underlying reference to the table is also consumed, - /// no further changes to the structure of the table are possible; - /// in exchange for this, the returned references have a longer lifetime - /// than the references returned by `read()`. - pub fn into_refs(self) -> (&'t K, &'t V) { - unsafe { - let pair_ptr = self.raw.pair(); - (&(*pair_ptr).0, &(*pair_ptr).1) - } - } -} - -impl<'t, K, V, M> FullBucket -where - M: Deref> + DerefMut + 't, -{ - /// This works similarly to `into_refs`, exchanging a bucket state - /// for mutable references into the table. - pub fn into_mut_refs(self) -> (&'t mut K, &'t mut V) { - unsafe { - let pair_ptr = self.raw.pair(); - (&mut (*pair_ptr).0, &mut (*pair_ptr).1) - } - } -} - -impl GapThenFull -where - M: Deref>, -{ - #[inline] - pub fn full(&self) -> &FullBucket { - &self.full - } - - pub fn into_table(self) -> M { - self.full.into_table() - } - - pub fn shift(mut self) -> Result, Bucket> { - unsafe { - let (gap_hash, gap_pair) = self.gap.raw.hash_pair(); - let (full_hash, full_pair) = self.full.raw.hash_pair(); - *gap_hash = mem::replace(&mut *full_hash, EMPTY_BUCKET); - ptr::copy_nonoverlapping(full_pair, gap_pair, 1); - } - - let FullBucket { raw: prev_raw, .. } = self.full; - - match self.full.next().peek() { - Full(bucket) => { - self.gap.raw = prev_raw; - - self.full = bucket; - - Ok(self) - }, - Empty(b) => Err(b.into_bucket()), - } - } -} - -/// Rounds up to a multiple of a power of two. Returns the closest multiple -/// of `target_alignment` that is higher or equal to `unrounded`. -/// -/// # Panics -/// -/// Panics if `target_alignment` is not a power of two. -#[inline] -fn round_up_to_next(unrounded: usize, target_alignment: usize) -> usize { - assert!(target_alignment.is_power_of_two()); - (unrounded + target_alignment - 1) & !(target_alignment - 1) -} - -#[test] -fn test_rounding() { - assert_eq!(round_up_to_next(0, 4), 0); - assert_eq!(round_up_to_next(1, 4), 4); - assert_eq!(round_up_to_next(2, 4), 4); - assert_eq!(round_up_to_next(3, 4), 4); - assert_eq!(round_up_to_next(4, 4), 4); - assert_eq!(round_up_to_next(5, 4), 8); -} - -// Returns a tuple of (pairs_offset, end_of_pairs_offset), -// from the start of a mallocated array. -#[inline] -fn calculate_offsets( - hashes_size: usize, - pairs_size: usize, - pairs_align: usize, -) -> (usize, usize, bool) { - let pairs_offset = round_up_to_next(hashes_size, pairs_align); - let (end_of_pairs, oflo) = pairs_offset.overflowing_add(pairs_size); - - (pairs_offset, end_of_pairs, oflo) -} - -// Returns a tuple of (minimum required malloc alignment, hash_offset, -// array_size), from the start of a mallocated array. -fn calculate_allocation( - hash_size: usize, - hash_align: usize, - pairs_size: usize, - pairs_align: usize, -) -> (usize, usize, usize, bool) { - let hash_offset = 0; - let (_, end_of_pairs, oflo) = calculate_offsets(hash_size, pairs_size, pairs_align); - - let align = cmp::max(hash_align, pairs_align); - - (align, hash_offset, end_of_pairs, oflo) -} - -#[test] -fn test_offset_calculation() { - assert_eq!(calculate_allocation(128, 8, 16, 8), (8, 0, 144, false)); - assert_eq!(calculate_allocation(3, 1, 2, 1), (1, 0, 5, false)); - assert_eq!(calculate_allocation(6, 2, 12, 4), (4, 0, 20, false)); - assert_eq!(calculate_offsets(128, 15, 4), (128, 143, false)); - assert_eq!(calculate_offsets(3, 2, 4), (4, 6, false)); - assert_eq!(calculate_offsets(6, 12, 4), (8, 20, false)); -} - -impl RawTable { - unsafe fn new_uninitialized(capacity: usize) -> RawTable { - if let Ok(table) = Self::try_new_uninitialized(capacity) { - table - } else { - libc::abort(); - } - } - - /// Does not initialize the buckets. The caller should ensure they, - /// at the very least, set every hash to EMPTY_BUCKET. - unsafe fn try_new_uninitialized( - capacity: usize, - ) -> Result, FailedAllocationError> { - if capacity == 0 { - return Ok(RawTable { - size: 0, - capacity_mask: capacity.wrapping_sub(1), - hashes: TaggedHashUintPtr::new(EMPTY as *mut HashUint), - marker: marker::PhantomData, - }); - } - - // No need for `checked_mul` before a more restrictive check performed - // later in this method. - let hashes_size = capacity.wrapping_mul(size_of::()); - let pairs_size = capacity.wrapping_mul(size_of::<(K, V)>()); - - // Allocating hashmaps is a little tricky. We need to allocate two - // arrays, but since we know their sizes and alignments up front, - // we just allocate a single array, and then have the subarrays - // point into it. - // - // This is great in theory, but in practice getting the alignment - // right is a little subtle. Therefore, calculating offsets has been - // factored out into a different function. - let (alignment, hash_offset, size, oflo) = calculate_allocation( - hashes_size, - align_of::(), - pairs_size, - align_of::<(K, V)>(), - ); - - if oflo { - return Err(FailedAllocationError::new( - "capacity overflow when allocating RawTable", - )); - } - - // One check for overflow that covers calculation and rounding of size. - let size_of_bucket = size_of::() - .checked_add(size_of::<(K, V)>()) - .unwrap(); - - let cap_bytes = capacity.checked_mul(size_of_bucket); - - if let Some(cap_bytes) = cap_bytes { - if size < cap_bytes { - return Err(FailedAllocationError::new( - "capacity overflow when allocating RawTable", - )); - } - } else { - return Err(FailedAllocationError::new( - "capacity overflow when allocating RawTable", - )); - } - - // FORK NOTE: Uses alloc shim instead of Heap.alloc - let buffer = alloc(size, alignment); - - if buffer.is_null() { - use crate::AllocationInfo; - return Err(FailedAllocationError { - reason: "out of memory when allocating RawTable", - allocation_info: Some(AllocationInfo { size, alignment }), - }); - } - - let hashes = buffer.offset(hash_offset as isize) as *mut HashUint; - - Ok(RawTable { - capacity_mask: capacity.wrapping_sub(1), - size: 0, - hashes: TaggedHashUintPtr::new(hashes), - marker: marker::PhantomData, - }) - } - - fn raw_bucket_at(&self, index: usize) -> RawBucket { - let hashes_size = self.capacity() * size_of::(); - let pairs_size = self.capacity() * size_of::<(K, V)>(); - - let (pairs_offset, _, oflo) = - calculate_offsets(hashes_size, pairs_size, align_of::<(K, V)>()); - debug_assert!(!oflo, "capacity overflow"); - - let buffer = self.hashes.ptr() as *mut u8; - unsafe { - RawBucket { - hash_start: buffer as *mut HashUint, - pair_start: buffer.add(pairs_offset) as *const (K, V), - idx: index, - _marker: marker::PhantomData, - } - } - } - - /// Creates a new raw table from a given capacity. All buckets are - /// initially empty. - pub fn new(capacity: usize) -> Result, FailedAllocationError> { - unsafe { - let ret = RawTable::try_new_uninitialized(capacity)?; - if capacity > 0 { - ptr::write_bytes(ret.hashes.ptr(), 0, capacity); - } - Ok(ret) - } - } - - /// The hashtable's capacity, similar to a vector's. - pub fn capacity(&self) -> usize { - self.capacity_mask.wrapping_add(1) - } - - /// The number of elements ever `put` in the hashtable, minus the number - /// of elements ever `take`n. - pub fn size(&self) -> usize { - self.size - } - - fn raw_buckets(&self) -> RawBuckets<'_, K, V> { - RawBuckets { - raw: self.raw_bucket_at(0), - elems_left: self.size, - marker: marker::PhantomData, - } - } - - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.raw_buckets(), - } - } - - pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { - IterMut { - iter: self.raw_buckets(), - _marker: marker::PhantomData, - } - } - - pub fn into_iter(self) -> IntoIter { - let RawBuckets { - raw, elems_left, .. - } = self.raw_buckets(); - // Replace the marker regardless of lifetime bounds on parameters. - IntoIter { - iter: RawBuckets { - raw, - elems_left, - marker: marker::PhantomData, - }, - table: self, - } - } - - pub fn drain(&mut self) -> Drain<'_, K, V> { - let RawBuckets { - raw, elems_left, .. - } = self.raw_buckets(); - // Replace the marker regardless of lifetime bounds on parameters. - Drain { - iter: RawBuckets { - raw, - elems_left, - marker: marker::PhantomData, - }, - table: Shared::from(self), - marker: marker::PhantomData, - } - } - - /// Drops buckets in reverse order. It leaves the table in an inconsistent - /// state and should only be used for dropping the table's remaining - /// entries. It's used in the implementation of Drop. - unsafe fn rev_drop_buckets(&mut self) { - // initialize the raw bucket past the end of the table - let mut raw = self.raw_bucket_at(self.capacity()); - let mut elems_left = self.size; - - while elems_left != 0 { - raw.idx -= 1; - - if *raw.hash() != EMPTY_BUCKET { - elems_left -= 1; - ptr::drop_in_place(raw.pair()); - } - } - } - - /// Set the table tag - pub fn set_tag(&mut self, value: bool) { - self.hashes.set_tag(value) - } - - /// Get the table tag - pub fn tag(&self) -> bool { - self.hashes.tag() - } -} - -/// A raw iterator. The basis for some other iterators in this module. Although -/// this interface is safe, it's not used outside this module. -struct RawBuckets<'a, K, V> { - raw: RawBucket, - elems_left: usize, - - // Strictly speaking, this should be &'a (K,V), but that would - // require that K:'a, and we often use RawBuckets<'static...> for - // move iterations, so that messes up a lot of other things. So - // just use `&'a (K,V)` as this is not a publicly exposed type - // anyway. - marker: marker::PhantomData<&'a ()>, -} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for RawBuckets<'a, K, V> { - fn clone(&self) -> RawBuckets<'a, K, V> { - RawBuckets { - raw: self.raw, - elems_left: self.elems_left, - marker: marker::PhantomData, - } - } -} - -impl<'a, K, V> Iterator for RawBuckets<'a, K, V> { - type Item = RawBucket; - - fn next(&mut self) -> Option> { - if self.elems_left == 0 { - return None; - } - - loop { - unsafe { - let item = self.raw; - self.raw.idx += 1; - if *item.hash() != EMPTY_BUCKET { - self.elems_left -= 1; - return Some(item); - } - } - } - } - - fn size_hint(&self) -> (usize, Option) { - (self.elems_left, Some(self.elems_left)) - } -} - -impl<'a, K, V> ExactSizeIterator for RawBuckets<'a, K, V> { - fn len(&self) -> usize { - self.elems_left - } -} - -/// Iterator over shared references to entries in a table. -pub struct Iter<'a, K: 'a, V: 'a> { - iter: RawBuckets<'a, K, V>, -} - -unsafe impl<'a, K: Sync, V: Sync> Sync for Iter<'a, K, V> {} -unsafe impl<'a, K: Sync, V: Sync> Send for Iter<'a, K, V> {} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for Iter<'a, K, V> { - fn clone(&self) -> Iter<'a, K, V> { - Iter { - iter: self.iter.clone(), - } - } -} - -/// Iterator over mutable references to entries in a table. -pub struct IterMut<'a, K: 'a, V> { - iter: RawBuckets<'a, K, V>, - // To ensure invariance with respect to V - _marker: marker::PhantomData<&'a mut V>, -} - -unsafe impl<'a, K: Sync, V: Sync> Sync for IterMut<'a, K, V> {} -// Both K: Sync and K: Send are correct for IterMut's Send impl, -// but Send is the more useful bound -unsafe impl<'a, K: Send, V: Send> Send for IterMut<'a, K, V> {} - -impl<'a, K: 'a, V: 'a> IterMut<'a, K, V> { - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.iter.clone(), - } - } -} - -/// Iterator over the entries in a table, consuming the table. -pub struct IntoIter { - table: RawTable, - iter: RawBuckets<'static, K, V>, -} - -unsafe impl Sync for IntoIter {} -unsafe impl Send for IntoIter {} - -impl IntoIter { - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.iter.clone(), - } - } -} - -/// Iterator over the entries in a table, clearing the table. -pub struct Drain<'a, K: 'static, V: 'static> { - table: Shared>, - iter: RawBuckets<'static, K, V>, - marker: marker::PhantomData<&'a RawTable>, -} - -unsafe impl<'a, K: Sync, V: Sync> Sync for Drain<'a, K, V> {} -unsafe impl<'a, K: Send, V: Send> Send for Drain<'a, K, V> {} - -impl<'a, K, V> Drain<'a, K, V> { - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.iter.clone(), - } - } -} - -impl<'a, K, V> Iterator for Iter<'a, K, V> { - type Item = (&'a K, &'a V); - - fn next(&mut self) -> Option<(&'a K, &'a V)> { - self.iter.next().map(|raw| unsafe { - let pair_ptr = raw.pair(); - (&(*pair_ptr).0, &(*pair_ptr).1) - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, K, V> ExactSizeIterator for Iter<'a, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl<'a, K, V> Iterator for IterMut<'a, K, V> { - type Item = (&'a K, &'a mut V); - - fn next(&mut self) -> Option<(&'a K, &'a mut V)> { - self.iter.next().map(|raw| unsafe { - let pair_ptr = raw.pair(); - (&(*pair_ptr).0, &mut (*pair_ptr).1) - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl Iterator for IntoIter { - type Item = (SafeHash, K, V); - - fn next(&mut self) -> Option<(SafeHash, K, V)> { - self.iter.next().map(|raw| { - self.table.size -= 1; - unsafe { - let (k, v) = ptr::read(raw.pair()); - (SafeHash { hash: *raw.hash() }, k, v) - } - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl ExactSizeIterator for IntoIter { - fn len(&self) -> usize { - self.iter().len() - } -} - -impl<'a, K, V> Iterator for Drain<'a, K, V> { - type Item = (SafeHash, K, V); - - #[inline] - fn next(&mut self) -> Option<(SafeHash, K, V)> { - self.iter.next().map(|raw| unsafe { - self.table.as_mut().size -= 1; - let (k, v) = ptr::read(raw.pair()); - ( - SafeHash { - hash: ptr::replace(&mut *raw.hash(), EMPTY_BUCKET), - }, - k, - v, - ) - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, K, V> ExactSizeIterator for Drain<'a, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl<'a, K: 'static, V: 'static> Drop for Drain<'a, K, V> { - fn drop(&mut self) { - for _ in self {} - } -} - -impl Clone for RawTable { - fn clone(&self) -> RawTable { - unsafe { - let cap = self.capacity(); - let mut new_ht = RawTable::new_uninitialized(cap); - - let mut new_buckets = new_ht.raw_bucket_at(0); - let mut buckets = self.raw_bucket_at(0); - while buckets.idx < cap { - *new_buckets.hash() = *buckets.hash(); - if *new_buckets.hash() != EMPTY_BUCKET { - let pair_ptr = buckets.pair(); - let kv = ((*pair_ptr).0.clone(), (*pair_ptr).1.clone()); - ptr::write(new_buckets.pair(), kv); - } - buckets.idx += 1; - new_buckets.idx += 1; - } - - new_ht.size = self.size(); - - new_ht - } - } -} - -// FORK NOTE: There may be lifetime errors that do not occur on std::HashMap -// since we removed the may_dangle (which allows more things to compile but has stricter guarantees). -// Generally we should be fine as long as no borrowed data is stuck into the map. -impl Drop for RawTable { - fn drop(&mut self) { - if self.capacity() == 0 { - return; - } - - // This is done in reverse because we've likely partially taken - // some elements out with `.into_iter()` from the front. - // Check if the size is 0, so we don't do a useless scan when - // dropping empty tables such as on resize. - // Also avoid double drop of elements that have been already moved out. - unsafe { - // FORK NOTE: Can't needs_drop on stable - // if needs_drop::<(K, V)>() { - // avoid linear runtime for types that don't need drop - self.rev_drop_buckets(); - // } - } - - let hashes_size = self.capacity() * size_of::(); - let pairs_size = self.capacity() * size_of::<(K, V)>(); - let (align, _, _, oflo) = calculate_allocation( - hashes_size, - align_of::(), - pairs_size, - align_of::<(K, V)>(), - ); - - debug_assert!(!oflo, "should be impossible"); - - unsafe { - dealloc(self.hashes.ptr() as *mut u8, align); - // Remember how everything was allocated out of one buffer - // during initialization? We only need one call to free here. - } - } -} diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml index 86f475609fa..23d7a79d371 100644 --- a/components/malloc_size_of/Cargo.toml +++ b/components/malloc_size_of/Cargo.toml @@ -34,7 +34,6 @@ content-security-policy = { workspace = true, optional = true } crossbeam-channel = { workspace = true, optional = true } cssparser = { workspace = true } euclid = { workspace = true } -hashglobe = { path = "../hashglobe" } http = { workspace = true, optional = true } hyper_serde = { workspace = true, optional = true } keyboard-types = { workspace = true, optional = true } @@ -45,7 +44,6 @@ servo_arc = { path = "../servo_arc" } smallbitvec = { workspace = true } smallvec = { workspace = true } string_cache = { workspace = true, optional = true } -thin-slice = { workspace = true } time = { workspace = true, optional = true } tokio = { workspace = true } url = { workspace = true, optional = true } diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 6822a70702b..2a684ac73d1 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -213,24 +213,6 @@ impl MallocSizeOf for Box { } } -impl MallocShallowSizeOf for thin_slice::ThinBoxedSlice { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = 0; - unsafe { - n += thin_slice::ThinBoxedSlice::spilled_storage(self) - .map_or(0, |ptr| ops.malloc_size_of(ptr)); - n += ops.malloc_size_of(&**self); - } - n - } -} - -impl MallocSizeOf for thin_slice::ThinBoxedSlice { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.shallow_size_of(ops) + (**self).size_of(ops) - } -} - impl MallocSizeOf for () { fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { 0 @@ -448,8 +430,6 @@ macro_rules! malloc_size_of_hash_set { } malloc_size_of_hash_set!(std::collections::HashSet); -malloc_size_of_hash_set!(hashglobe::hash_set::HashSet); -malloc_size_of_hash_set!(hashglobe::fake::HashSet); macro_rules! malloc_size_of_hash_map { ($ty:ty) => { @@ -489,8 +469,6 @@ macro_rules! malloc_size_of_hash_map { } malloc_size_of_hash_map!(std::collections::HashMap); -malloc_size_of_hash_map!(hashglobe::hash_map::HashMap); -malloc_size_of_hash_map!(hashglobe::fake::HashMap); impl MallocShallowSizeOf for std::collections::BTreeMap where diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index 853a9c9b643..e23e6721301 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -105,7 +105,8 @@ impl CSSRule { }, StyleCssRule::Page(_) => unreachable!(), StyleCssRule::Document(_) => unimplemented!(), // TODO - StyleCssRule::Layer(_) => unimplemented!(), // TODO + StyleCssRule::LayerBlock(_) => unimplemented!(), // TODO + StyleCssRule::LayerStatement(_) => unimplemented!(), // TODO StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO } } diff --git a/components/script/dom/cssstylerule.rs b/components/script/dom/cssstylerule.rs index a84c5c598b7..f81f30ab258 100644 --- a/components/script/dom/cssstylerule.rs +++ b/components/script/dom/cssstylerule.rs @@ -96,19 +96,15 @@ impl CSSStyleRuleMethods for CSSStyleRule { // https://drafts.csswg.org/cssom/#dom-cssstylerule-selectortext fn SetSelectorText(&self, value: DOMString) { + let contents = &self.cssrule.parent_stylesheet().style_stylesheet().contents; // It's not clear from the spec if we should use the stylesheet's namespaces. // https://github.com/w3c/csswg-drafts/issues/1511 - let namespaces = self - .cssrule - .parent_stylesheet() - .style_stylesheet() - .contents - .namespaces - .read(); + let namespaces = contents.namespaces.read(); + let url_data = contents.url_data.read(); let parser = SelectorParser { stylesheet_origin: Origin::Author, namespaces: &namespaces, - url_data: None, + url_data: &url_data, }; let mut css_parser = CssParserInput::new(&*value); let mut css_parser = CssParser::new(&mut css_parser); diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index f2de9aaa1a1..2e94e635b26 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -130,6 +130,7 @@ use style::selector_parser::{ NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser, }; use style::shared_lock::{Locked, SharedRwLock}; +use style::stylesheets::layer_rule::LayerOrder; use style::stylesheets::CssRuleType; use style::thread_state; use style::values::generics::NonNegative; @@ -665,6 +666,7 @@ impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> { Importance::Normal, ))), CascadeLevel::PresHints, + LayerOrder::root(), ) } @@ -2700,12 +2702,14 @@ impl ElementMethods for Element { // https://dom.spec.whatwg.org/#dom-element-matches fn Matches(&self, selectors: DOMString) -> Fallible { - let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors) { + let doc = document_from_node(self); + let url = doc.url(); + let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors, &url) { Err(_) => return Err(Error::Syntax), Ok(selectors) => selectors, }; - let quirks_mode = document_from_node(self).quirks_mode(); + let quirks_mode = doc.quirks_mode(); let element = DomRoot::from_ref(self); Ok(dom_apis::element_matches(&element, &selectors, quirks_mode)) @@ -2718,12 +2722,14 @@ impl ElementMethods for Element { // https://dom.spec.whatwg.org/#dom-element-closest fn Closest(&self, selectors: DOMString) -> Fallible>> { - let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors) { + let doc = document_from_node(self); + let url = doc.url(); + let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors, &url) { Err(_) => return Err(Error::Syntax), Ok(selectors) => selectors, }; - let quirks_mode = document_from_node(self).quirks_mode(); + let quirks_mode = doc.quirks_mode(); Ok(dom_apis::element_closest( DomRoot::from_ref(self), &selectors, diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index c98e9b93bfe..f419dcab485 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -949,18 +949,15 @@ impl Node { // https://dom.spec.whatwg.org/#dom-parentnode-queryselector pub fn query_selector(&self, selectors: DOMString) -> Fallible>> { // Step 1. - match SelectorParser::parse_author_origin_no_namespace(&selectors) { + let doc = self.owner_doc(); + match SelectorParser::parse_author_origin_no_namespace(&selectors, &doc.url()) { // Step 2. Err(_) => Err(Error::Syntax), // Step 3. Ok(selectors) => { // FIXME(bholley): Consider an nth-index cache here. - let mut ctx = MatchingContext::new( - MatchingMode::Normal, - None, - None, - self.owner_doc().quirks_mode(), - ); + let mut ctx = + MatchingContext::new(MatchingMode::Normal, None, None, doc.quirks_mode()); Ok(self .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast) @@ -975,7 +972,8 @@ impl Node { /// whilst iterating, otherwise the iterator may be invalidated. pub fn query_selector_iter(&self, selectors: DOMString) -> Fallible { // Step 1. - match SelectorParser::parse_author_origin_no_namespace(&selectors) { + let url = self.owner_doc().url(); + match SelectorParser::parse_author_origin_no_namespace(&selectors, &url) { // Step 2. Err(_) => Err(Error::Syntax), // Step 3. diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index b4eb6dc55c5..bbb75880b53 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -17,8 +17,7 @@ path = "lib.rs" doctest = false [features] -gecko = ["style_traits/gecko", "fallible/known_system_malloc", "bindgen", "regex", "toml", - "num_cpus", "thin-slice"] +gecko = ["style_traits/gecko", "bindgen", "regex", "toml", "num_cpus", "mozbuild"] servo = ["serde", "style_traits/servo", "servo_atoms", "servo_config", "html5ever", "cssparser/serde", "encoding_rs", "malloc_size_of/servo", "servo_url", "string_cache", "to_shmem/servo", @@ -38,9 +37,7 @@ cssparser = "0.29" derive_more = "0.99" encoding_rs = { version = "0.8", optional = true } euclid = "0.22" -fallible = { path = "../fallible" } fxhash = "0.2" -hashglobe = { path = "../hashglobe" } html5ever = { version = "0.26", optional = true } indexmap = "1.0" itertools = "0.8" @@ -70,7 +67,6 @@ smallvec = "1.0" string_cache = { version = "0.8", optional = true } style_derive = { path = "../style_derive" } style_traits = { path = "../style_traits" } -thin-slice = { version = "0.1.0", optional = true } time = "0.1" to_shmem = { path = "../to_shmem" } to_shmem_derive = { path = "../to_shmem_derive" } @@ -83,6 +79,7 @@ void = "1.0.2" bindgen = { version = "0.62", optional = true, default-features = false } lazy_static = "1" log = "0.4" +mozbuild = {version = "0.1", optional = true} regex = { version = "1.1", optional = true } toml = { version = "0.5", optional = true, default-features = false } walkdir = "2.1.4" diff --git a/components/style/animation.rs b/components/style/animation.rs index 57ce98141a7..7f0c7e6a588 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -24,6 +24,7 @@ use crate::selector_parser::PseudoElement; use crate::shared_lock::{Locked, SharedRwLock}; use crate::style_resolver::StyleResolverForElement; use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue}; +use crate::stylesheets::layer_rule::LayerOrder; use crate::values::animated::{Animate, Procedure}; use crate::values::computed::{Time, TimingFunction}; use crate::values::generics::box_::AnimationIterationCount; @@ -290,6 +291,7 @@ impl IntermediateComputedKeyframe { let rule_node = base_style.rules().clone(); let new_node = context.stylist.rule_tree().update_rule_at_level( CascadeLevel::Animations, + LayerOrder::root(), Some(locked_block.borrow_arc()), &rule_node, &context.guards, diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index db0f7fce99f..5849e7c2250 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -6,8 +6,8 @@ use crate::properties::PropertyDeclarationBlock; use crate::rule_tree::{CascadeLevel, StyleSource}; -use crate::stylesheets::layer_rule::LayerOrder; use crate::shared_lock::Locked; +use crate::stylesheets::layer_rule::LayerOrder; use servo_arc::Arc; use smallvec::SmallVec; @@ -24,36 +24,109 @@ pub type ApplicableDeclarationList = SmallVec<[ApplicableDeclarationBlock; 16]>; /// That's a limit that could be reached in realistic webpages, so we use /// 24 bits and enforce defined behavior in the overflow case. /// +/// Note that right now this restriction could be lifted if wanted (because we +/// no longer stash the cascade level in the remaining bits), but we keep it in +/// place in case we come up with a use-case for them, lacking reports of the +/// current limit being too small. +/// /// [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/css/ /// RuleSet.h?l=128&rcl=90140ab80b84d0f889abc253410f44ed54ae04f3 -const SOURCE_ORDER_SHIFT: usize = 0; const SOURCE_ORDER_BITS: usize = 24; const SOURCE_ORDER_MAX: u32 = (1 << SOURCE_ORDER_BITS) - 1; -const SOURCE_ORDER_MASK: u32 = SOURCE_ORDER_MAX << SOURCE_ORDER_SHIFT; +const SOURCE_ORDER_MASK: u32 = SOURCE_ORDER_MAX; -/// We pack the cascade level in a single byte, see CascadeLevel::to_byte_lossy -/// for the different trade-offs there. -const CASCADE_LEVEL_SHIFT: usize = SOURCE_ORDER_BITS; +/// The cascade-level+layer order of this declaration. +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] +pub struct CascadePriority { + cascade_level: CascadeLevel, + layer_order: LayerOrder, +} -/// Stores the source order of a block, the cascade level it belongs to, and the -/// counter needed to handle Shadow DOM cascade order properly. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -struct ApplicableDeclarationBits(u32); +#[allow(dead_code)] +fn size_assert() { + #[allow(unsafe_code)] + unsafe { + std::mem::transmute::(0u32) + }; +} -impl ApplicableDeclarationBits { - fn new(source_order: u32, cascade_level: CascadeLevel) -> Self { - Self( - (source_order & SOURCE_ORDER_MASK) | - ((cascade_level.to_byte_lossy() as u32) << CASCADE_LEVEL_SHIFT), - ) +impl PartialOrd for CascadePriority { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CascadePriority { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.cascade_level.cmp(&other.cascade_level).then_with(|| { + let ordering = self.layer_order.cmp(&other.layer_order); + if ordering == std::cmp::Ordering::Equal { + return ordering; + } + // https://drafts.csswg.org/css-cascade-5/#cascade-layering + // + // Cascade layers (like declarations) are ordered by order + // of appearance. When comparing declarations that belong to + // different layers, then for normal rules the declaration + // whose cascade layer is last wins, and for important rules + // the declaration whose cascade layer is first wins. + // + // But the style attribute layer for some reason is special. + if self.cascade_level.is_important() && + !self.layer_order.is_style_attribute_layer() && + !other.layer_order.is_style_attribute_layer() + { + ordering.reverse() + } else { + ordering + } + }) + } +} + +impl CascadePriority { + /// Construct a new CascadePriority for a given (level, order) pair. + pub fn new(cascade_level: CascadeLevel, layer_order: LayerOrder) -> Self { + Self { + cascade_level, + layer_order, + } } - fn source_order(&self) -> u32 { - self.0 & SOURCE_ORDER_MASK + /// Returns the layer order. + #[inline] + pub fn layer_order(&self) -> LayerOrder { + self.layer_order } - fn level(&self) -> CascadeLevel { - CascadeLevel::from_byte((self.0 >> CASCADE_LEVEL_SHIFT) as u8) + /// Returns the cascade level. + #[inline] + pub fn cascade_level(&self) -> CascadeLevel { + self.cascade_level + } + + /// Whether this declaration should be allowed if `revert` or `revert-layer` + /// have been specified on a given origin. + /// + /// `self` is the priority at which the `revert` or `revert-layer` keyword + /// have been specified. + pub fn allows_when_reverted(&self, other: &Self, origin_revert: bool) -> bool { + if origin_revert { + other.cascade_level.origin() < self.cascade_level.origin() + } else { + other.unimportant() < self.unimportant() + } + } + + /// Convert this priority from "important" to "non-important", if needed. + pub fn unimportant(&self) -> Self { + Self::new(self.cascade_level().unimportant(), self.layer_order()) + } + + /// Convert this priority from "non-important" to "important", if needed. + pub fn important(&self) -> Self { + Self::new(self.cascade_level().important(), self.layer_order()) } } @@ -69,11 +142,11 @@ pub struct ApplicableDeclarationBlock { pub source: StyleSource, /// The bits containing the source order, cascade level, and shadow cascade /// order. - bits: ApplicableDeclarationBits, + source_order: u32, /// The specificity of the selector. pub specificity: u32, - /// The layer order of the selector. - pub layer_order: LayerOrder, + /// The cascade priority of the rule. + pub cascade_priority: CascadePriority, } impl ApplicableDeclarationBlock { @@ -83,12 +156,13 @@ impl ApplicableDeclarationBlock { pub fn from_declarations( declarations: Arc>, level: CascadeLevel, + layer_order: LayerOrder, ) -> Self { ApplicableDeclarationBlock { source: StyleSource::from_declarations(declarations), - bits: ApplicableDeclarationBits::new(0, level), + source_order: 0, specificity: 0, - layer_order: LayerOrder::root(), + cascade_priority: CascadePriority::new(level, layer_order), } } @@ -103,29 +177,34 @@ impl ApplicableDeclarationBlock { ) -> Self { ApplicableDeclarationBlock { source, - bits: ApplicableDeclarationBits::new(source_order, level), + source_order: source_order & SOURCE_ORDER_MASK, specificity, - layer_order, + cascade_priority: CascadePriority::new(level, layer_order), } } /// Returns the source order of the block. #[inline] pub fn source_order(&self) -> u32 { - self.bits.source_order() + self.source_order } /// Returns the cascade level of the block. #[inline] pub fn level(&self) -> CascadeLevel { - self.bits.level() + self.cascade_priority.cascade_level() + } + + /// Returns the cascade level of the block. + #[inline] + pub fn layer_order(&self) -> LayerOrder { + self.cascade_priority.layer_order() } /// Convenience method to consume self and return the right thing for the /// rule tree to iterate over. #[inline] - pub fn for_rule_tree(self) -> (StyleSource, CascadeLevel) { - let level = self.level(); - (self.source, level) + pub fn for_rule_tree(self) -> (StyleSource, CascadePriority) { + (self.source, self.cascade_priority) } } diff --git a/components/style/author_styles.rs b/components/style/author_styles.rs index dfd33711ed2..83bd0ad2854 100644 --- a/components/style/author_styles.rs +++ b/components/style/author_styles.rs @@ -10,10 +10,10 @@ use crate::dom::TElement; use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; use crate::invalidation::media_queries::ToMediaListKey; use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylist::Stylist; use crate::stylesheet_set::AuthorStylesheetSet; use crate::stylesheets::StylesheetInDocument; use crate::stylist::CascadeData; +use crate::stylist::Stylist; use servo_arc::Arc; /// A set of author stylesheets and their computed representation, such as the @@ -32,9 +32,7 @@ where } lazy_static! { - static ref EMPTY_CASCADE_DATA: Arc = { - Arc::new_leaked(CascadeData::new()) - }; + static ref EMPTY_CASCADE_DATA: Arc = Arc::new_leaked(CascadeData::new()); } impl AuthorStyles @@ -55,11 +53,8 @@ where /// TODO(emilio): Need a host element and a snapshot map to do invalidation /// properly. #[inline] - pub fn flush( - &mut self, - stylist: &mut Stylist, - guard: &SharedRwLockReadGuard, - ) where + pub fn flush(&mut self, stylist: &mut Stylist, guard: &SharedRwLockReadGuard) + where E: TElement, S: ToMediaListKey, { diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index ddf4a8b71ef..9c411b3ddb2 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -42,14 +42,12 @@ fn read_config(path: &PathBuf) -> Table { lazy_static! { static ref CONFIG: Table = { // Load Gecko's binding generator config from the source tree. - let path = PathBuf::from(env::var_os("MOZ_SRC").unwrap()) - .join("layout/style/ServoBindings.toml"); + let path = mozbuild::TOPSRCDIR.join("layout/style/ServoBindings.toml"); read_config(&path) }; static ref BINDGEN_FLAGS: Vec = { // Load build-specific config overrides. - let path = PathBuf::from(env::var_os("MOZ_TOPOBJDIR").unwrap()) - .join("layout/style/extra-bindgen-flags"); + let path = mozbuild::TOPOBJDIR.join("layout/style/extra-bindgen-flags"); println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); fs::read_to_string(path).expect("Failed to read extra-bindgen-flags file") .split_whitespace() @@ -57,13 +55,7 @@ lazy_static! { .collect() }; static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap(); - static ref DISTDIR_PATH: PathBuf = { - let path = PathBuf::from(env::var_os("MOZ_DIST").unwrap()); - if !path.is_absolute() || !path.is_dir() { - panic!("MOZ_DIST must be an absolute directory, was: {}", path.display()); - } - path - }; + static ref DISTDIR_PATH: PathBuf = mozbuild::TOPOBJDIR.join("dist"); static ref SEARCH_PATHS: Vec = vec![ DISTDIR_PATH.join("include"), DISTDIR_PATH.join("include/nspr"), diff --git a/components/style/counter_style/mod.rs b/components/style/counter_style/mod.rs index e470e53ae1f..daad5145e1c 100644 --- a/components/style/counter_style/mod.rs +++ b/components/style/counter_style/mod.rs @@ -55,12 +55,12 @@ pub fn parse_counter_style_name<'i, 't>( } fn is_valid_name_definition(ident: &CustomIdent) -> bool { - ident.0 != atom!("decimal") - && ident.0 != atom!("disc") - && ident.0 != atom!("circle") - && ident.0 != atom!("square") - && ident.0 != atom!("disclosure-closed") - && ident.0 != atom!("disclosure-open") + ident.0 != atom!("decimal") && + ident.0 != atom!("disc") && + ident.0 != atom!("circle") && + ident.0 != atom!("square") && + ident.0 != atom!("disclosure-closed") && + ident.0 != atom!("disclosure-open") } /// Parse the prelude of an @counter-style rule diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 96328d9a51a..7be700ec350 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -6,11 +6,10 @@ //! //! [custom]: https://drafts.csswg.org/css-variables/ -use crate::hash::map::Entry; +use crate::applicable_declarations::CascadePriority; use crate::media_queries::Device; use crate::properties::{CSSWideKeyword, CustomDeclaration, CustomDeclarationValue}; use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, PrecomputedHasher}; -use crate::stylesheets::{Origin, PerOrigin}; use crate::Atom; use cssparser::{ CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType, @@ -21,6 +20,7 @@ use servo_arc::Arc; use smallvec::SmallVec; use std::borrow::Cow; use std::cmp; +use std::collections::hash_map::Entry; use std::fmt::{self, Write}; use std::hash::BuildHasherDefault; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; @@ -49,19 +49,19 @@ macro_rules! make_variable { } fn get_safearea_inset_top(device: &Device) -> VariableValue { - VariableValue::pixel(device.safe_area_insets().top) + VariableValue::pixels(device.safe_area_insets().top) } fn get_safearea_inset_bottom(device: &Device) -> VariableValue { - VariableValue::pixel(device.safe_area_insets().bottom) + VariableValue::pixels(device.safe_area_insets().bottom) } fn get_safearea_inset_left(device: &Device) -> VariableValue { - VariableValue::pixel(device.safe_area_insets().left) + VariableValue::pixels(device.safe_area_insets().left) } fn get_safearea_inset_right(device: &Device) -> VariableValue { - VariableValue::pixel(device.safe_area_insets().right) + VariableValue::pixels(device.safe_area_insets().right) } static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ @@ -71,17 +71,56 @@ static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), ]; -fn get_titlebar_radius(device: &Device) -> VariableValue { - VariableValue::pixel(device.titlebar_radius()) +#[cfg(feature = "gecko")] +macro_rules! lnf_int { + ($id:ident) => { + unsafe { + crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt( + crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32, + ) + } + }; } -fn get_menu_radius(device: &Device) -> VariableValue { - VariableValue::pixel(device.menu_radius()) +#[cfg(feature = "servo")] +macro_rules! lnf_int { + ($id:ident) => { + // TODO: implement this. + 0 + }; } -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 2] = [ - make_variable!(atom!("-moz-gtk-csd-titlebar-radius"), get_titlebar_radius), - make_variable!(atom!("-moz-gtk-menu-radius"), get_menu_radius), +macro_rules! lnf_int_variable { + ($atom:expr, $id:ident, $ctor:ident) => {{ + fn __eval(_: &Device) -> VariableValue { + VariableValue::$ctor(lnf_int!($id)) + } + make_variable!($atom, __eval) + }}; +} + +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [ + lnf_int_variable!( + atom!("-moz-gtk-csd-titlebar-radius"), + TitlebarRadius, + int_pixels + ), + lnf_int_variable!(atom!("-moz-gtk-csd-menu-radius"), GtkMenuRadius, int_pixels), + lnf_int_variable!( + atom!("-moz-gtk-csd-close-button-position"), + GTKCSDCloseButtonPosition, + integer + ), + lnf_int_variable!( + atom!("-moz-gtk-csd-minimize-button-position"), + GTKCSDMinimizeButtonPosition, + integer + ), + lnf_int_variable!( + atom!("-moz-gtk-csd-maximize-button-position"), + GTKCSDMaximizeButtonPosition, + integer + ), ]; impl CssEnvironment { @@ -93,7 +132,9 @@ impl CssEnvironment { if !device.is_chrome_document() { return None; } - let var = CHROME_ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?; + let var = CHROME_ENVIRONMENT_VARIABLES + .iter() + .find(|var| var.name == *name)?; Some((var.evaluator)(device)) } } @@ -278,17 +319,39 @@ impl VariableValue { })) } - /// Create VariableValue from css pixel value - pub fn pixel(number: f32) -> Self { + /// Create VariableValue from an int. + fn integer(number: i32) -> Self { + Self::from_token(Token::Number { + has_sign: false, + value: number as f32, + int_value: Some(number), + }) + } + + /// Create VariableValue from a float amount of CSS pixels. + fn pixels(number: f32) -> Self { // FIXME (https://github.com/servo/rust-cssparser/issues/266): // No way to get TokenSerializationType::Dimension without creating // Token object. - let token = Token::Dimension { + Self::from_token(Token::Dimension { has_sign: false, value: number, int_value: None, unit: CowRcStr::from("px"), - }; + }) + } + + /// Create VariableValue from an integer amount of CSS pixels. + fn int_pixels(number: i32) -> Self { + Self::from_token(Token::Dimension { + has_sign: false, + value: number as f32, + int_value: Some(number), + unit: CowRcStr::from("px"), + }) + } + + fn from_token(token: Token) -> Self { let token_type = token.serialization_type(); let mut css = token.to_css_string(); css.shrink_to_fit(); @@ -536,10 +599,10 @@ fn parse_env_function<'i, 't>( /// properties. pub struct CustomPropertiesBuilder<'a> { seen: PrecomputedHashSet<&'a Name>, - reverted: PerOrigin>, may_have_cycles: bool, custom_properties: Option, inherited: Option<&'a Arc>, + reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>, device: &'a Device, } @@ -557,14 +620,16 @@ impl<'a> CustomPropertiesBuilder<'a> { } /// Cascade a given custom property declaration. - pub fn cascade(&mut self, declaration: &'a CustomDeclaration, origin: Origin) { + pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) { let CustomDeclaration { ref name, ref value, } = *declaration; - if self.reverted.borrow_for_origin(&origin).contains(&name) { - return; + if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) { + if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { + return; + } } let was_already_present = !self.seen.insert(name); @@ -597,8 +662,7 @@ impl<'a> CustomPropertiesBuilder<'a> { match result { Ok(new_value) => new_value, Err(..) => { - // Don't touch the map, this has the same effect as - // making it compute to the inherited one. + map.remove(name); return; }, } @@ -608,11 +672,10 @@ impl<'a> CustomPropertiesBuilder<'a> { map.insert(name.clone(), value); }, CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword { - CSSWideKeyword::Revert => { + CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { + let origin_revert = keyword == CSSWideKeyword::Revert; self.seen.remove(name); - for origin in origin.following_including() { - self.reverted.borrow_mut_for_origin(&origin).insert(name); - } + self.reverted.insert(name, (priority, origin_revert)); }, CSSWideKeyword::Initial => { map.remove(name); @@ -660,6 +723,22 @@ impl<'a> CustomPropertiesBuilder<'a> { true } + fn inherited_properties_match(&self, map: &CustomPropertiesMap) -> bool { + let inherited = match self.inherited { + Some(inherited) => inherited, + None => return false, + }; + if inherited.len() != map.len() { + return false; + } + for name in self.seen.iter() { + if inherited.get(*name) != map.get(*name) { + return false; + } + } + true + } + /// Returns the final map of applicable custom properties. /// /// If there was any specified property, we've created a new map and now we @@ -671,10 +750,19 @@ impl<'a> CustomPropertiesBuilder<'a> { Some(m) => m, None => return self.inherited.cloned(), }; + if self.may_have_cycles { - let inherited = self.inherited.as_ref().map(|m| &***m); - substitute_all(&mut map, inherited, self.device); + substitute_all(&mut map, &self.seen, self.device); } + + // Some pages apply a lot of redundant custom properties, see e.g. + // bug 1758974 comment 5. Try to detect the case where the values + // haven't really changed, and save some memory by reusing the inherited + // map in that case. + if self.inherited_properties_match(&map) { + return self.inherited.cloned(); + } + map.shrink_to_fit(); Some(Arc::new(map)) } @@ -684,11 +772,7 @@ impl<'a> CustomPropertiesBuilder<'a> { /// (meaning we should use the inherited value). /// /// It does cycle dependencies removal at the same time as substitution. -fn substitute_all( - custom_properties_map: &mut CustomPropertiesMap, - inherited: Option<&CustomPropertiesMap>, - device: &Device, -) { +fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, seen: &PrecomputedHashSet<&Name>, device: &Device) { // The cycle dependencies removal in this function is a variant // of Tarjan's algorithm. It is mostly based on the pseudo-code // listed in @@ -724,10 +808,7 @@ fn substitute_all( /// all unfinished strong connected components. stack: SmallVec<[usize; 5]>, map: &'a mut CustomPropertiesMap, - /// The inherited variables. We may need to restore some if we fail - /// substitution. - inherited: Option<&'a CustomPropertiesMap>, - /// to resolve the environment to substitute `env()` variables. + /// To resolve the environment to substitute `env()` variables. device: &'a Device, } @@ -749,10 +830,10 @@ fn substitute_all( /// doesn't have reference at all in specified value, or it has /// been completely resolved. /// * There is no such variable at all. - fn traverse<'a>(name: Name, context: &mut Context<'a>) -> Option { + fn traverse<'a>(name: &Name, context: &mut Context<'a>) -> Option { // Some shortcut checks. let (name, value) = { - let value = context.map.get(&name)?; + let value = context.map.get(name)?; // Nothing to resolve. if value.references.is_empty() { @@ -765,7 +846,7 @@ fn substitute_all( // Whether this variable has been visited in this traversal. let key; - match context.index_map.entry(name) { + match context.index_map.entry(name.clone()) { Entry::Occupied(entry) => { return Some(*entry.get()); }, @@ -793,7 +874,7 @@ fn substitute_all( let mut self_ref = false; let mut lowlink = index; for next in value.references.iter() { - let next_index = match traverse(next.clone(), context) { + let next_index = match traverse(next, context) { Some(index) => index, // There is nothing to do if the next variable has been // fully resolved at this point. @@ -869,16 +950,8 @@ fn substitute_all( context.map.insert(name, computed_value); }, Err(..) => { - // This is invalid, reset it to the unset (inherited) value. - let inherited = context.inherited.and_then(|m| m.get(&name)).cloned(); - match inherited { - Some(computed_value) => { - context.map.insert(name, computed_value); - }, - None => { - context.map.remove(&name); - }, - }; + // This is invalid, reset it to the guaranteed-invalid value. + context.map.remove(&name); }, } @@ -886,17 +959,16 @@ fn substitute_all( None } - // We have to clone the names so that we can mutably borrow the map - // in the context we create for traversal. - let names: Vec<_> = custom_properties_map.keys().cloned().collect(); - for name in names.into_iter() { + // Note that `seen` doesn't contain names inherited from our parent, but + // those can't have variable references (since we inherit the computed + // variables) so we don't want to spend cycles traversing them anyway. + for name in seen { let mut context = Context { count: 0, index_map: PrecomputedHashMap::default(), stack: SmallVec::new(), var_info: SmallVec::new(), map: custom_properties_map, - inherited, device, }; traverse(name, &mut context); @@ -1014,7 +1086,9 @@ fn substitute_block<'i>( let first_token_type = input .next_including_whitespace_and_comments() .ok() - .map_or_else(TokenSerializationType::nothing, |t| t.serialization_type()); + .map_or_else(TokenSerializationType::nothing, |t| { + t.serialization_type() + }); input.reset(&after_comma); let mut position = (after_comma.position(), first_token_type); last_token_type = substitute_block( diff --git a/components/style/data.rs b/components/style/data.rs index f486b24dc58..758adf57a68 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -179,13 +179,20 @@ impl ElementStyles { pub fn uses_viewport_units(&self) -> bool { use crate::computed_value_flags::ComputedValueFlags; - if self.primary().flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) { + if self + .primary() + .flags + .intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) + { return true; } for pseudo_style in self.pseudos.as_array() { if let Some(ref pseudo_style) = pseudo_style { - if pseudo_style.flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) { + if pseudo_style + .flags + .intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) + { return true; } } diff --git a/components/style/driver.rs b/components/style/driver.rs index 82ed2a66386..b7e33454f41 100644 --- a/components/style/driver.rs +++ b/components/style/driver.rs @@ -133,29 +133,27 @@ where let tls = ScopedTLS::>::new(pool); let root_opaque = root.as_node().opaque(); let drain = discovered.drain(..); - pool.install(|| { + pool.scope_fifo(|scope| { // Enable a breadth-first rayon traversal. This causes the work // queue to be always FIFO, rather than FIFO for stealers and // FILO for the owner (which is what rayon does by default). This // ensures that we process all the elements at a given depth before // proceeding to the next depth, which is important for style sharing. - rayon::scope_fifo(|scope| { - #[cfg(feature = "gecko")] - gecko_profiler_label!(Layout, StyleComputation); - parallel::traverse_nodes( - drain, - DispatchMode::TailCall, - /* recursion_ok = */ true, - root_opaque, - PerLevelTraversalData { - current_dom_depth: depth, - }, - scope, - pool, - traversal, - &tls, - ); - }); + #[cfg(feature = "gecko")] + gecko_profiler_label!(Layout, StyleComputation); + parallel::traverse_nodes( + drain, + DispatchMode::TailCall, + /* recursion_ok = */ true, + root_opaque, + PerLevelTraversalData { + current_dom_depth: depth, + }, + scope, + pool, + traversal, + &tls, + ); }); tls_slots = Some(tls.into_slots()); diff --git a/components/style/element_state.rs b/components/style/element_state.rs index 8a9f4065b8b..30c65dc9feb 100644 --- a/components/style/element_state.rs +++ b/components/style/element_state.rs @@ -80,8 +80,6 @@ bitflags! { const IN_READWRITE_STATE = 1 << 25; /// const IN_DEFAULT_STATE = 1 << 26; - /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-submit-invalid - const IN_MOZ_SUBMITINVALID_STATE = 1 << 27; /// Non-standard & undocumented. const IN_OPTIMUM_STATE = 1 << 28; /// Non-standard & undocumented. @@ -127,6 +125,11 @@ bitflags! { const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45; /// Used for the devtools style editor. Probably should go away. const IN_STYLEEDITOR_TRANSITIONING_STATE = 1 << 46; + /// For :-moz-value-empty (to show widgets like the reveal password + /// button or the clear button). + const IN_VALUE_EMPTY_STATE = 1 << 47; + /// For :-moz-revealed. + const IN_REVEALED_STATE = 1 << 48; } } @@ -137,9 +140,17 @@ bitflags! { /// dom/base/Document.h. #[derive(MallocSizeOf)] pub struct DocumentState: u64 { - /// RTL locale: specific to the XUL localedir attribute - const NS_DOCUMENT_STATE_RTL_LOCALE = 1 << 0; /// Window activation status - const NS_DOCUMENT_STATE_WINDOW_INACTIVE = 1 << 1; + const WINDOW_INACTIVE = 1 << 0; + /// RTL locale: specific to the XUL localedir attribute + const RTL_LOCALE = 1 << 1; + /// LTR locale: specific to the XUL localedir attribute + const LTR_LOCALE = 1 << 2; + /// LWTheme status + const LWTHEME = 1 << 3; + /// LWTheme status + const LWTHEME_BRIGHTTEXT = 1 << 4; + /// LWTheme status + const LWTHEME_DARKTEXT = 1 << 5; } } diff --git a/components/style/error_reporting.rs b/components/style/error_reporting.rs index eebb0901309..752f68b5bdc 100644 --- a/components/style/error_reporting.rs +++ b/components/style/error_reporting.rs @@ -214,7 +214,7 @@ impl<'a> fmt::Display for ContextualParseError<'a> { ContextualParseError::UnsupportedValue(_value, ref err) => parse_error_to_str(err, f), ContextualParseError::NeverMatchingHostSelector(ref selector) => { write!(f, ":host selector is not featureless: {}", selector) - } + }, } } } diff --git a/components/style/font_metrics.rs b/components/style/font_metrics.rs index 13a4e9e182b..259e5693942 100644 --- a/components/style/font_metrics.rs +++ b/components/style/font_metrics.rs @@ -18,6 +18,8 @@ pub struct FontMetrics { pub zero_advance_measure: Option, /// The cap-height of the font. pub cap_height: Option, + /// The ideographic-width of the font. + pub ic_width: Option, /// The ascent of the font (a value is always available for this). pub ascent: Length, } @@ -28,6 +30,7 @@ impl Default for FontMetrics { x_height: None, zero_advance_measure: None, cap_height: None, + ic_width: None, ascent: Length::new(0.0), } } diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 05fcdb5b12d..4fb08c3a309 100644 --- a/components/style/gecko/arc_types.rs +++ b/components/style/gecko/arc_types.rs @@ -12,9 +12,10 @@ use crate::gecko::url::CssUrlData; use crate::gecko_bindings::structs::{ RawServoAnimationValue, RawServoCounterStyleRule, RawServoCssUrlData, RawServoDeclarationBlock, RawServoFontFaceRule, RawServoFontFeatureValuesRule, RawServoImportRule, RawServoKeyframe, - RawServoKeyframesRule, RawServoLayerRule, RawServoMediaList, RawServoMediaRule, - RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule, RawServoScrollTimelineRule, - RawServoStyleRule, RawServoStyleSheetContents, RawServoSupportsRule, ServoCssRules, + RawServoKeyframesRule, RawServoLayerBlockRule, RawServoLayerStatementRule, RawServoMediaList, + RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule, + RawServoScrollTimelineRule, RawServoStyleRule, RawServoStyleSheetContents, + RawServoSupportsRule, ServoCssRules, }; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong}; use crate::media_queries::MediaList; @@ -24,8 +25,8 @@ use crate::shared_lock::Locked; use crate::stylesheets::keyframes_rule::Keyframe; use crate::stylesheets::{ CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule, - KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, ScrollTimelineRule, StyleRule, - StylesheetContents, SupportsRule, + KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, PageRule, + ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, }; use servo_arc::{Arc, ArcBorrow}; use std::{mem, ptr}; @@ -73,8 +74,11 @@ impl_arc_ffi!(Locked => RawServoKeyframe impl_arc_ffi!(Locked => RawServoKeyframesRule [Servo_KeyframesRule_AddRef, Servo_KeyframesRule_Release]); -impl_arc_ffi!(Locked => RawServoLayerRule - [Servo_LayerRule_AddRef, Servo_LayerRule_Release]); +impl_arc_ffi!(Locked => RawServoLayerBlockRule + [Servo_LayerBlockRule_AddRef, Servo_LayerBlockRule_Release]); + +impl_arc_ffi!(Locked => RawServoLayerStatementRule + [Servo_LayerStatementRule_AddRef, Servo_LayerStatementRule_Release]); impl_arc_ffi!(Locked => RawServoMediaList [Servo_MediaList_AddRef, Servo_MediaList_Release]); diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 40a34095d6f..af57be431f9 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -13,7 +13,6 @@ use crate::media_queries::{Device, MediaType}; use crate::values::computed::CSSPixelLength; use crate::values::computed::Ratio; use crate::values::computed::Resolution; -use crate::Atom; use app_units::Au; use euclid::default::Size2D; @@ -396,16 +395,31 @@ fn eval_overflow_inline(device: &Device, query_value: Option) -> } } -/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme -fn eval_prefers_color_scheme(device: &Device, query_value: Option) -> bool { +fn do_eval_prefers_color_scheme( + device: &Device, + use_content: bool, + query_value: Option, +) -> bool { let prefers_color_scheme = - unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document()) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document(), use_content) }; match query_value { Some(v) => prefers_color_scheme == v, None => true, } } +/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme +fn eval_prefers_color_scheme(device: &Device, query_value: Option) -> bool { + do_eval_prefers_color_scheme(device, /* use_content = */ false, query_value) +} + +fn eval_content_prefers_color_scheme( + device: &Device, + query_value: Option, +) -> bool { + do_eval_prefers_color_scheme(device, /* use_content = */ true, query_value) +} + bitflags! { /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction struct PointerCapabilities: u8 { @@ -535,20 +549,36 @@ fn eval_moz_is_resource_document( query_value.map_or(is_resource_doc, |v| v == is_resource_doc) } -fn eval_moz_os_version( - device: &Device, - query_value: Option, - _: Option, -) -> bool { +/// Allows front-end CSS to discern platform via media queries. +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +pub enum Platform { + /// Matches any Android version. + Android, + /// For our purposes here, "linux" is just "gtk" (so unix-but-not-mac). + /// There's no need for our front-end code to differentiate between those + /// platforms and they already use the "linux" string elsewhere (e.g., + /// toolkit/themes/linux). + Linux, + /// Matches any macOS version. + Macos, + /// Matches any Windows version. + Windows, + /// Matches only Windows 7. + WindowsWin7, + /// Matches only Windows 8. + WindowsWin8, + /// Matches windows 10 and actually matches windows 11 too, as of right now. + WindowsWin10, +} + +fn eval_moz_platform(_: &Device, query_value: Option) -> bool { let query_value = match query_value { Some(v) => v, None => return false, }; - let os_version = - unsafe { bindings::Gecko_MediaFeatures_GetOperatingSystemVersion(device.document()) }; - - query_value.as_ptr() == os_version + unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) } } fn eval_moz_windows_non_native_menus( @@ -556,11 +586,12 @@ fn eval_moz_windows_non_native_menus( query_value: Option, _: Option, ) -> bool { - let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus") { + let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus") + { 0 => false, 1 => true, _ => { - eval_moz_os_version(device, Some(atom!("windows-win10")), None) && + eval_moz_platform(device, Some(Platform::WindowsWin10)) && get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32) }, }; @@ -804,6 +835,15 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme), ParsingRequirements::empty(), ), + // Evaluates to the preferred color scheme for content. Only useful in + // chrome context, where the chrome color-scheme and the content + // color-scheme might differ. + feature!( + atom!("-moz-content-prefers-color-scheme"), + AllowsRanges::No, + keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), feature!( atom!("pointer"), AllowsRanges::No, @@ -844,9 +884,9 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( - atom!("-moz-os-version"), + atom!("-moz-platform"), AllowsRanges::No, - Evaluator::Ident(eval_moz_os_version), + keyword_evaluator!(eval_moz_platform, Platform), ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( @@ -873,18 +913,39 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ Evaluator::BoolInteger(eval_moz_overlay_scrollbars), ParsingRequirements::CHROME_AND_UA_ONLY, ), - - lnf_int_feature!(atom!("-moz-scrollbar-start-backward"), ScrollArrowStyle, get_scrollbar_start_backward), - lnf_int_feature!(atom!("-moz-scrollbar-start-forward"), ScrollArrowStyle, get_scrollbar_start_forward), - lnf_int_feature!(atom!("-moz-scrollbar-end-backward"), ScrollArrowStyle, get_scrollbar_end_backward), - lnf_int_feature!(atom!("-moz-scrollbar-end-forward"), ScrollArrowStyle, get_scrollbar_end_forward), - lnf_int_feature!(atom!("-moz-scrollbar-thumb-proportional"), ScrollSliderStyle), + lnf_int_feature!( + atom!("-moz-scrollbar-start-backward"), + ScrollArrowStyle, + get_scrollbar_start_backward + ), + lnf_int_feature!( + atom!("-moz-scrollbar-start-forward"), + ScrollArrowStyle, + get_scrollbar_start_forward + ), + lnf_int_feature!( + atom!("-moz-scrollbar-end-backward"), + ScrollArrowStyle, + get_scrollbar_end_backward + ), + lnf_int_feature!( + atom!("-moz-scrollbar-end-forward"), + ScrollArrowStyle, + get_scrollbar_end_forward + ), + lnf_int_feature!( + atom!("-moz-scrollbar-thumb-proportional"), + ScrollSliderStyle + ), lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag), lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), lnf_int_feature!(atom!("-moz-mac-big-sur-theme"), MacBigSurTheme), lnf_int_feature!(atom!("-moz-mac-rtl"), MacRTL), - lnf_int_feature!(atom!("-moz-windows-accent-color-in-titlebar"), WindowsAccentColorInTitlebar), + lnf_int_feature!( + atom!("-moz-windows-accent-color-in-titlebar"), + WindowsAccentColorInTitlebar + ), lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor), lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic), lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass), @@ -893,8 +954,13 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ lnf_int_feature!(atom!("-moz-gtk-csd-minimize-button"), GTKCSDMinimizeButton), lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton), lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton), - lnf_int_feature!(atom!("-moz-gtk-csd-reversed-placement"), GTKCSDReversedPlacement), + lnf_int_feature!( + atom!("-moz-gtk-csd-reversed-placement"), + GTKCSDReversedPlacement + ), lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), - bool_pref_feature!(atom!("-moz-proton"), "browser.proton.enabled"), - bool_pref_feature!(atom!("-moz-proton-places-tooltip"), "browser.proton.places-tooltip.enabled"), + bool_pref_feature!( + atom!("-moz-proton-places-tooltip"), + "browser.proton.places-tooltip.enabled" + ), ]; diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 7bab0b002d1..e4634d7eab9 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -14,7 +14,8 @@ use crate::media_queries::MediaType; use crate::properties::ComputedValues; use crate::string_cache::Atom; use crate::values::computed::font::GenericFontFamily; -use crate::values::computed::Length; +use crate::values::computed::{ColorScheme, Length}; +use crate::values::specified::color::SystemColor; use crate::values::specified::font::FONT_MEDIUM_PX; use crate::values::{CustomIdent, KeyframesName}; use app_units::{Au, AU_PER_PX}; @@ -387,19 +388,30 @@ impl Device { self.pref_sheet_prefs().mUseDocumentColors } + /// Computes a system color and returns it as an nscolor. + pub(crate) fn system_nscolor( + &self, + system_color: SystemColor, + color_scheme: &ColorScheme, + ) -> u32 { + unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) } + } + /// Returns the default background color. /// /// This is only for forced-colors/high-contrast, so looking at light colors /// is ok. - pub fn default_background_color_for_forced_colors(&self) -> RGBA { - convert_nscolor_to_rgba(self.pref_sheet_prefs().mLightColors.mDefaultBackground) + pub fn default_background_color(&self) -> RGBA { + let normal = ColorScheme::normal(); + convert_nscolor_to_rgba(self.system_nscolor(SystemColor::Canvas, &normal)) } /// Returns the default foreground color. /// /// See above for looking at light colors only. - pub fn default_color_for_forced_colors(&self) -> RGBA { - convert_nscolor_to_rgba(self.pref_sheet_prefs().mLightColors.mDefault) + pub fn default_color(&self) -> RGBA { + let normal = ColorScheme::normal(); + convert_nscolor_to_rgba(self.system_nscolor(SystemColor::Canvastext, &normal)) } /// Returns the current effective text zoom. @@ -447,20 +459,6 @@ impl Device { } } - /// Returns the gtk titlebar radius in CSS pixels. - pub fn titlebar_radius(&self) -> f32 { - unsafe { - bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::TitlebarRadius as i32) as f32 - } - } - - /// Returns the gtk menu radius in CSS pixels. - pub fn menu_radius(&self) -> f32 { - unsafe { - bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::GtkMenuRadius as i32) as f32 - } - } - /// Return whether the document is a chrome document. #[inline] pub fn is_chrome_document(&self) -> bool { diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs index 317cbfb83fe..feb28feb0df 100644 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ b/components/style/gecko/non_ts_pseudo_class_list.rs @@ -37,7 +37,7 @@ macro_rules! apply_non_ts_list { ("any-link", AnyLink, IN_VISITED_OR_UNVISITED_STATE, _), ("visited", Visited, IN_VISITED_STATE, _), ("active", Active, IN_ACTIVE_STATE, _), - ("autofill", Autofill, IN_AUTOFILL_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("autofill", Autofill, IN_AUTOFILL_STATE, _), ("checked", Checked, IN_CHECKED_STATE, _), ("defined", Defined, IN_DEFINED_STATE, _), ("disabled", Disabled, IN_DISABLED_STATE, _), @@ -63,6 +63,8 @@ macro_rules! apply_non_ts_list { ("-moz-dir-attr-like-auto", MozDirAttrLikeAuto, IN_HAS_DIR_ATTR_LIKE_AUTO_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-autofill-preview", MozAutofillPreview, IN_AUTOFILL_PREVIEW_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("-moz-value-empty", MozValueEmpty, IN_VALUE_EMPTY_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), + ("-moz-revealed", MozRevealed, IN_REVEALED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-math-increment-script-level", MozMathIncrementScriptLevel, IN_INCREMENT_SCRIPT_LEVEL_STATE, _), @@ -76,7 +78,6 @@ macro_rules! apply_non_ts_list { ("placeholder-shown", PlaceholderShown, IN_PLACEHOLDER_SHOWN_STATE, _), ("read-only", ReadOnly, IN_READONLY_STATE, _), ("read-write", ReadWrite, IN_READWRITE_STATE, _), - ("-moz-submit-invalid", MozSubmitInvalid, IN_MOZ_SUBMITINVALID_STATE, _), ("user-valid", UserValid, IN_MOZ_UI_VALID_STATE, _), ("user-invalid", UserInvalid, IN_MOZ_UI_INVALID_STATE, _), ("-moz-meter-optimum", MozMeterOptimum, IN_OPTIMUM_STATE, _), @@ -90,9 +91,9 @@ macro_rules! apply_non_ts_list { ("-moz-use-shadow-tree-root", MozUseShadowTreeRoot, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-is-html", MozIsHTML, _, _), ("-moz-placeholder", MozPlaceholder, _, _), - ("-moz-lwtheme", MozLWTheme, _, _), - ("-moz-lwtheme-brighttext", MozLWThemeBrightText, _, _), - ("-moz-lwtheme-darktext", MozLWThemeDarkText, _, _), + ("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("-moz-lwtheme-brighttext", MozLWThemeBrightText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("-moz-lwtheme-darktext", MozLWThemeDarkText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), ("-moz-window-inactive", MozWindowInactive, _, _), ] } diff --git a/components/style/gecko/pseudo_element.rs b/components/style/gecko/pseudo_element.rs index 5a9b955b1c8..855fed9a0d8 100644 --- a/components/style/gecko/pseudo_element.rs +++ b/components/style/gecko/pseudo_element.rs @@ -17,7 +17,6 @@ use crate::string_cache::Atom; use crate::values::serialize_atom_identifier; use cssparser::ToCss; use std::fmt; -use thin_slice::ThinBoxedSlice; include!(concat!( env!("OUT_DIR"), @@ -93,9 +92,10 @@ impl PseudoElement { EAGER_PSEUDOS[i].clone() } - /// Whether the current pseudo element is animatable. + /// Whether animations for the current pseudo element are stored in the + /// parent element. #[inline] - pub fn is_animatable(&self) -> bool { + pub fn animations_stored_in_parent(&self) -> bool { matches!(*self, Self::Before | Self::After | Self::Marker) } diff --git a/components/style/gecko/pseudo_element_definition.mako.rs b/components/style/gecko/pseudo_element_definition.mako.rs index 41dff1a9468..dd6d1aad227 100644 --- a/components/style/gecko/pseudo_element_definition.mako.rs +++ b/components/style/gecko/pseudo_element_definition.mako.rs @@ -3,12 +3,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /// Gecko's pseudo-element definition. +/// +/// We intentionally double-box legacy ::-moz-tree pseudo-elements to keep the +/// size of PseudoElement (and thus selector components) small. #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] pub enum PseudoElement { % for pseudo in PSEUDOS: /// ${pseudo.value} % if pseudo.is_tree_pseudo_element(): - ${pseudo.capitalized_pseudo()}(ThinBoxedSlice), + ${pseudo.capitalized_pseudo()}(Box>), % else: ${pseudo.capitalized_pseudo()}, % endif diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 7028d554aa2..8fc0900aece 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -8,7 +8,7 @@ use crate::element_state::{DocumentState, ElementState}; use crate::gecko_bindings::structs::RawServoSelectorList; use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; use crate::invalidation::element::document_state::InvalidationMatchingData; -use crate::selector_parser::{Direction, SelectorParser}; +use crate::selector_parser::{Direction, HorizontalDirection, SelectorParser}; use crate::str::starts_with_ignore_ascii_case; use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; use crate::values::{AtomIdent, AtomString}; @@ -69,7 +69,7 @@ impl ToCss for NonTSPseudoClass { $(NonTSPseudoClass::$name => concat!(":", $css),)* NonTSPseudoClass::Lang(ref s) => { dest.write_str(":lang(")?; - s.to_css(dest)?; + cssparser::ToCss::to_css(s, dest)?; return dest.write_char(')'); }, NonTSPseudoClass::MozLocaleDir(ref dir) => { @@ -127,7 +127,7 @@ impl NonTSPseudoClass { ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { match *self { $(NonTSPseudoClass::$name => check_flag!($flags),)* - NonTSPseudoClass::MozLocaleDir(_) | + NonTSPseudoClass::MozLocaleDir(_) => check_flag!(PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), NonTSPseudoClass::Lang(_) | NonTSPseudoClass::Dir(_) => false, } @@ -139,11 +139,14 @@ impl NonTSPseudoClass { /// Returns whether the pseudo-class is enabled in content sheets. #[inline] fn is_enabled_in_content(&self) -> bool { - if let NonTSPseudoClass::Autofill = *self { - return static_prefs::pref!("layout.css.autofill.enabled"); + if matches!( + *self, + Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText + ) { + return static_prefs::pref!("layout.css.moz-lwtheme.content.enabled"); } - if let NonTSPseudoClass::MozSubmitInvalid = *self { - return static_prefs::pref!("layout.css.moz-submit-invalid.enabled"); + if let NonTSPseudoClass::MozLocaleDir(..) = *self { + return static_prefs::pref!("layout.css.moz-locale-dir.content.enabled"); } !self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME) } @@ -162,7 +165,7 @@ impl NonTSPseudoClass { ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { match *self { $(NonTSPseudoClass::$name => flag!($state),)* - NonTSPseudoClass::Dir(..) | + NonTSPseudoClass::Dir(ref dir) => dir.element_state(), NonTSPseudoClass::MozLocaleDir(..) | NonTSPseudoClass::Lang(..) => ElementState::empty(), } @@ -174,8 +177,15 @@ impl NonTSPseudoClass { /// Get the document state flag associated with a pseudo-class, if any. pub fn document_state_flag(&self) -> DocumentState { match *self { - NonTSPseudoClass::MozLocaleDir(..) => DocumentState::NS_DOCUMENT_STATE_RTL_LOCALE, - NonTSPseudoClass::MozWindowInactive => DocumentState::NS_DOCUMENT_STATE_WINDOW_INACTIVE, + NonTSPseudoClass::MozLocaleDir(ref dir) => match dir.as_horizontal_direction() { + Some(HorizontalDirection::Ltr) => DocumentState::LTR_LOCALE, + Some(HorizontalDirection::Rtl) => DocumentState::RTL_LOCALE, + None => DocumentState::empty(), + }, + NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, + NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME, + NonTSPseudoClass::MozLWThemeBrightText => DocumentState::LWTHEME_BRIGHTTEXT, + NonTSPseudoClass::MozLWThemeDarkText => DocumentState::LWTHEME_DARKTEXT, _ => DocumentState::empty(), } } @@ -186,10 +196,8 @@ impl NonTSPseudoClass { self.state_flag().is_empty() && !matches!( *self, - // :dir() depends on state only, but doesn't use state_flag - // because its semantics don't quite match. Nevertheless, it - // doesn't need cache revalidation, because we already compare - // states for elements and candidates. + // :dir() depends on state only, but may have an empty + // state_flag for invalid arguments. NonTSPseudoClass::Dir(_) | // :-moz-is-html only depends on the state of the document and // the namespace of the element; the former is invariant diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 95da8b986f6..ca2c3816ec9 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -27,6 +27,7 @@ use crate::gecko_bindings::bindings; use crate::gecko_bindings::bindings::Gecko_ElementHasAnimations; use crate::gecko_bindings::bindings::Gecko_ElementHasCSSAnimations; use crate::gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; +use crate::gecko_bindings::bindings::Gecko_ElementState; use crate::gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock; use crate::gecko_bindings::bindings::Gecko_GetAnimationEffectCount; use crate::gecko_bindings::bindings::Gecko_GetAnimationRule; @@ -39,11 +40,8 @@ use crate::gecko_bindings::bindings::Gecko_IsSignificantChild; use crate::gecko_bindings::bindings::Gecko_MatchLang; use crate::gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr; use crate::gecko_bindings::bindings::Gecko_UpdateAnimations; -use crate::gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentLWTheme}; -use crate::gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags}; use crate::gecko_bindings::structs; use crate::gecko_bindings::structs::nsChangeHint; -use crate::gecko_bindings::structs::Document_DocumentTheme as DocumentTheme; use crate::gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel; use crate::gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT; use crate::gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO; @@ -55,14 +53,13 @@ use crate::gecko_bindings::structs::{nsAtom, nsIContent, nsINode_BooleanFlag}; use crate::gecko_bindings::structs::{nsINode as RawGeckoNode, Element as RawGeckoElement}; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasSimpleFFI}; use crate::global_style_data::GLOBAL_STYLE_DATA; -use crate::hash::FxHashMap; use crate::invalidation::element::restyle_hints::RestyleHint; use crate::media_queries::Device; use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; use crate::properties::{ComputedValues, LonghandId}; use crate::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock}; use crate::rule_tree::CascadeLevel as ServoCascadeLevel; -use crate::selector_parser::{AttrValue, HorizontalDirection, Lang}; +use crate::selector_parser::{AttrValue, Lang}; use crate::shared_lock::{Locked, SharedRwLock}; use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; use crate::stylist::CascadeData; @@ -70,6 +67,7 @@ use crate::values::{AtomIdent, AtomString}; use crate::CaseSensitivityExt; use crate::LocalName; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; +use fxhash::FxHashMap; use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator}; use selectors::attr::{CaseSensitivity, NamespaceConstraint}; use selectors::matching::VisitedHandlingMode; @@ -77,6 +75,7 @@ use selectors::matching::{ElementSelectorFlags, MatchingContext}; use selectors::sink::Push; use selectors::{Element, OpaqueElement}; use servo_arc::{Arc, ArcBorrow, RawOffsetArc}; +use std::sync::atomic::{AtomicU32, Ordering}; use std::fmt; use std::hash::{Hash, Hasher}; use std::mem; @@ -266,9 +265,29 @@ impl<'ln> GeckoNode<'ln> { GeckoNode(&content._base) } + #[inline] + fn flags_atomic(&self) -> &AtomicU32 { + use std::cell::Cell; + let flags: &Cell = &(self.0)._base._base_1.mFlags; + + #[allow(dead_code)] + fn static_assert() { + let _: [u8; std::mem::size_of::>()] = [0u8; std::mem::size_of::()]; + let _: [u8; std::mem::align_of::>()] = [0u8; std::mem::align_of::()]; + } + + // Rust doesn't provide standalone atomic functions like GCC/clang do + // (via the atomic intrinsics) or via std::atomic_ref, but it guarantees + // that the memory representation of u32 and AtomicU32 matches: + // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU32.html + unsafe { + std::mem::transmute::<&Cell, &AtomicU32>(flags) + } + } + #[inline] fn flags(&self) -> u32 { - (self.0)._base._base_1.mFlags + self.flags_atomic().load(Ordering::Relaxed) } #[inline] @@ -649,18 +668,14 @@ impl<'le> GeckoElement<'le> { self.as_node().flags() } - // FIXME: We can implement this without OOL calls, but we can't easily given - // GeckoNode is a raw reference. - // - // We can use a Cell, but that's a bit of a pain. #[inline] fn set_flags(&self, flags: u32) { - unsafe { Gecko_SetNodeFlags(self.as_node().0, flags) } + self.as_node().flags_atomic().fetch_or(flags, Ordering::Relaxed); } #[inline] unsafe fn unset_flags(&self, flags: u32) { - Gecko_UnsetNodeFlags(self.as_node().0, flags) + self.as_node().flags_atomic().fetch_and(!flags, Ordering::Relaxed); } /// Returns true if this element has descendants for lazy frame construction. @@ -752,12 +767,6 @@ impl<'le> GeckoElement<'le> { .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveStyle) } - #[inline] - fn document_theme(&self) -> DocumentTheme { - let node = self.as_node(); - unsafe { Gecko_GetDocumentLWTheme(node.owner_doc().0) } - } - /// Only safe to call on the main thread, with exclusive access to the /// element and its ancestors. /// @@ -1203,7 +1212,11 @@ impl<'le> TElement for GeckoElement<'le> { where F: FnMut(&AtomIdent), { - for attr in self.non_mapped_attrs().iter().chain(self.mapped_attrs().iter()) { + for attr in self + .non_mapped_attrs() + .iter() + .chain(self.mapped_attrs().iter()) + { let is_nodeinfo = attr.mName.mBits & 1 != 0; unsafe { let atom = if is_nodeinfo { @@ -1393,15 +1406,14 @@ impl<'le> TElement for GeckoElement<'le> { #[inline] fn may_have_animations(&self) -> bool { if let Some(pseudo) = self.implemented_pseudo_element() { - if !pseudo.is_animatable() { - return false; + if pseudo.animations_stored_in_parent() { + // FIXME(emilio): When would the parent of a ::before / ::after + // pseudo-element be null? + return self.parent_element().map_or(false, |p| { + p.as_node() + .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) + }); } - // FIXME(emilio): When would the parent of a ::before / ::after - // pseudo-element be null? - return self.parent_element().map_or(false, |p| { - p.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) - }); } self.as_node() .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) @@ -1582,6 +1594,7 @@ impl<'le> TElement for GeckoElement<'le> { use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom; use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor; use crate::properties::longhands::text_align::SpecifiedValue as SpecifiedTextAlign; + use crate::stylesheets::layer_rule::LayerOrder; use crate::values::specified::color::Color; lazy_static! { static ref TH_RULE: ApplicableDeclarationBlock = { @@ -1591,7 +1604,11 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints) + ApplicableDeclarationBlock::from_declarations( + arc, + ServoCascadeLevel::PresHints, + LayerOrder::root(), + ) }; static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1600,7 +1617,11 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints) + ApplicableDeclarationBlock::from_declarations( + arc, + ServoCascadeLevel::PresHints, + LayerOrder::root(), + ) }; static ref MATHML_LANG_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1609,7 +1630,11 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints) + ApplicableDeclarationBlock::from_declarations( + arc, + ServoCascadeLevel::PresHints, + LayerOrder::root(), + ) }; static ref SVG_TEXT_DISABLE_ZOOM_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1618,7 +1643,11 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints) + ApplicableDeclarationBlock::from_declarations( + arc, + ServoCascadeLevel::PresHints, + LayerOrder::root(), + ) }; }; @@ -1646,6 +1675,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( decl.clone_arc(), ServoCascadeLevel::PresHints, + LayerOrder::root(), )); } let declarations = unsafe { Gecko_GetExtraContentStyleDeclarations(self.0).as_ref() }; @@ -1655,6 +1685,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( decl.clone_arc(), ServoCascadeLevel::PresHints, + LayerOrder::root(), )); } @@ -1682,6 +1713,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( decl.clone_arc(), ServoCascadeLevel::PresHints, + LayerOrder::root(), )); } @@ -1697,6 +1729,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( decl.clone_arc(), ServoCascadeLevel::PresHints, + LayerOrder::root(), )); } } @@ -1718,6 +1751,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( arc, ServoCascadeLevel::PresHints, + LayerOrder::root(), )) } // MathML's default lang has precedence over both `lang` and `xml:lang` @@ -1965,7 +1999,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::InRange | NonTSPseudoClass::OutOfRange | NonTSPseudoClass::Default | - NonTSPseudoClass::MozSubmitInvalid | NonTSPseudoClass::UserValid | NonTSPseudoClass::UserInvalid | NonTSPseudoClass::MozMeterOptimum | @@ -1979,9 +2012,10 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozTopmostModalDialog | NonTSPseudoClass::Active | NonTSPseudoClass::Hover | - NonTSPseudoClass::MozAutofillPreview => { - self.state().intersects(pseudo_class.state_flag()) - }, + NonTSPseudoClass::MozAutofillPreview | + NonTSPseudoClass::MozRevealed | + NonTSPseudoClass::MozValueEmpty | + NonTSPseudoClass::Dir(..) => self.state().intersects(pseudo_class.state_flag()), NonTSPseudoClass::AnyLink => self.is_link(), NonTSPseudoClass::Link => { self.is_link() && context.visited_handling().matches_unvisited() @@ -2032,40 +2066,27 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { bindings::Gecko_IsSelectListBox(self.0) }, NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(), - NonTSPseudoClass::MozLWTheme => self.document_theme() != DocumentTheme::Doc_Theme_None, - NonTSPseudoClass::MozLWThemeBrightText => { - self.document_theme() == DocumentTheme::Doc_Theme_Bright - }, - NonTSPseudoClass::MozLWThemeDarkText => { - self.document_theme() == DocumentTheme::Doc_Theme_Dark - }, + + NonTSPseudoClass::MozLWTheme | + NonTSPseudoClass::MozLWThemeBrightText | + NonTSPseudoClass::MozLWThemeDarkText | + NonTSPseudoClass::MozLocaleDir(..) | NonTSPseudoClass::MozWindowInactive => { - let state_bit = DocumentState::NS_DOCUMENT_STATE_WINDOW_INACTIVE; + let state_bit = pseudo_class.document_state_flag(); + if state_bit.is_empty() { + debug_assert!( + matches!(pseudo_class, NonTSPseudoClass::MozLocaleDir(..)), + "Only moz-locale-dir should ever return an empty state" + ); + return false; + } if context.extra_data.document_state.intersects(state_bit) { return !context.in_negation(); } - self.document_state().contains(state_bit) }, NonTSPseudoClass::MozPlaceholder => false, NonTSPseudoClass::Lang(ref lang_arg) => self.match_element_lang(None, lang_arg), - NonTSPseudoClass::MozLocaleDir(ref dir) => { - let state_bit = DocumentState::NS_DOCUMENT_STATE_RTL_LOCALE; - if context.extra_data.document_state.intersects(state_bit) { - // NOTE(emilio): We could still return false for values - // other than "ltr" and "rtl", but we don't bother. - return !context.in_negation(); - } - - let doc_is_rtl = self.document_state().contains(state_bit); - - match dir.as_horizontal_direction() { - Some(HorizontalDirection::Ltr) => !doc_is_rtl, - Some(HorizontalDirection::Rtl) => doc_is_rtl, - None => false, - } - }, - NonTSPseudoClass::Dir(ref dir) => self.state().intersects(dir.element_state()), } } diff --git a/components/style/hash.rs b/components/style/hash.rs deleted file mode 100644 index 197c5c12832..00000000000 --- a/components/style/hash.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* 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/. */ - -//! Reexports of hashglobe types in Gecko mode, and stdlib hashmap shims in Servo mode -//! -//! Can go away when the stdlib gets fallible collections -//! https://github.com/rust-lang/rfcs/pull/2116 - -use fxhash; - -#[cfg(feature = "gecko")] -pub use hashglobe::hash_map::HashMap; -#[cfg(feature = "gecko")] -pub use hashglobe::hash_set::HashSet; - -#[cfg(feature = "servo")] -pub use hashglobe::fake::{HashMap, HashSet}; - -/// Appropriate reexports of hash_map types -pub mod map { - #[cfg(feature = "gecko")] - pub use hashglobe::hash_map::{Entry, Iter}; - #[cfg(feature = "servo")] - pub use std::collections::hash_map::{Entry, Iter}; -} - -/// Hash map that uses the Fx hasher -pub type FxHashMap = HashMap; -/// Hash set that uses the Fx hasher -pub type FxHashSet = HashSet; diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs index d79e1402228..2aa5749fdee 100644 --- a/components/style/invalidation/element/element_wrapper.rs +++ b/components/style/invalidation/element/element_wrapper.rs @@ -178,28 +178,6 @@ where // Some pseudo-classes need special handling to evaluate them against // the snapshot. match *pseudo_class { - // :dir is implemented in terms of state flags, but which state flag - // it maps to depends on the argument to :dir. That means we can't - // just add its state flags to the NonTSPseudoClass, because if we - // added all of them there, and tested via intersects() here, we'd - // get incorrect behavior for :not(:dir()) cases. - // - // FIXME(bz): How can I set this up so once Servo adds :dir() - // support we don't forget to update this code? - #[cfg(feature = "gecko")] - NonTSPseudoClass::Dir(ref dir) => { - let selector_flag = dir.element_state(); - if selector_flag.is_empty() { - // :dir() with some random argument; does not match. - return false; - } - let state = match self.snapshot().and_then(|s| s.state()) { - Some(snapshot_state) => snapshot_state, - None => self.element.state(), - }; - return state.contains(selector_flag); - }, - // For :link and :visited, we don't actually want to test the // element state directly. // diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index a7c2b04df13..0bba423a17f 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -10,9 +10,8 @@ use crate::selector_map::{ MaybeCaseInsensitiveHashMap, PrecomputedHashMap, SelectorMap, SelectorMapEntry, }; use crate::selector_parser::SelectorImpl; -use crate::{Atom, LocalName, Namespace}; -use fallible::FallibleVec; -use hashglobe::FailedAllocationError; +use crate::AllocErr; +use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded}; use selectors::attr::NamespaceConstraint; use selectors::parser::{Combinator, Component}; use selectors::parser::{Selector, SelectorIter}; @@ -238,13 +237,21 @@ impl InvalidationMap { self.other_attribute_affecting_selectors.clear(); } + /// Shrink the capacity of hash maps if needed. + pub fn shrink_if_needed(&mut self) { + self.class_to_selector.shrink_if_needed(); + self.id_to_selector.shrink_if_needed(); + self.state_affecting_selectors.shrink_if_needed(); + self.other_attribute_affecting_selectors.shrink_if_needed(); + } + /// Adds a selector to this `InvalidationMap`. Returns Err(..) to /// signify OOM. pub fn note_selector( &mut self, selector: &Selector, quirks_mode: QuirksMode, - ) -> Result<(), FailedAllocationError> { + ) -> Result<(), AllocErr> { debug!("InvalidationMap::note_selector({:?})", selector); let mut document_state = DocumentState::empty(); @@ -274,7 +281,8 @@ impl InvalidationMap { state: document_state, dependency: Dependency::for_full_selector_invalidation(selector.clone()), }; - self.document_state_selectors.try_push(dep)?; + self.document_state_selectors.try_reserve(1)?; + self.document_state_selectors.push(dep); } Ok(()) @@ -325,7 +333,7 @@ struct SelectorDependencyCollector<'a> { compound_state: PerCompoundState, /// The allocation error, if we OOM. - alloc_error: &'a mut Option, + alloc_error: &'a mut Option, } impl<'a> SelectorDependencyCollector<'a> { @@ -361,7 +369,7 @@ impl<'a> SelectorDependencyCollector<'a> { self.quirks_mode, ); if let Err(alloc_error) = result { - *self.alloc_error = Some(alloc_error); + *self.alloc_error = Some(alloc_error.into()); return false; } } @@ -378,21 +386,17 @@ impl<'a> SelectorDependencyCollector<'a> { let dependency = self.dependency(); let map = &mut self.map.other_attribute_affecting_selectors; - let entry = match map.try_entry(name) { - Ok(entry) => entry, - Err(err) => { - *self.alloc_error = Some(err); - return false; - }, - }; - - match entry.or_insert_with(SmallVec::new).try_push(dependency) { - Ok(..) => true, - Err(err) => { - *self.alloc_error = Some(err); - return false; - }, + if let Err(err) = map.try_reserve(1) { + *self.alloc_error = Some(err.into()); + return false; } + let vec = map.entry(name).or_default(); + if let Err(err) = vec.try_reserve(1) { + *self.alloc_error = Some(err.into()); + return false; + } + vec.push(dependency); + true } fn dependency(&self) -> Dependency { @@ -481,24 +485,20 @@ impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> { let entry = match map.try_entry(atom.0.clone(), self.quirks_mode) { Ok(entry) => entry, Err(err) => { - *self.alloc_error = Some(err); + *self.alloc_error = Some(err.into()); return false; }, }; - match entry.or_insert_with(SmallVec::new).try_push(dependency) { - Ok(..) => true, - Err(err) => { - *self.alloc_error = Some(err); - return false; - }, + let vec = entry.or_insert_with(SmallVec::new); + if let Err(err) = vec.try_reserve(1) { + *self.alloc_error = Some(err.into()); + return false; } + vec.push(dependency); + true }, Component::NonTSPseudoClass(ref pc) => { - self.compound_state.element_state |= match *pc { - #[cfg(feature = "gecko")] - NonTSPseudoClass::Dir(ref dir) => dir.element_state(), - _ => pc.state_flag(), - }; + self.compound_state.element_state |= pc.state_flag(); *self.document_state |= pc.document_state_flag(); let attr_name = match *pc { diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index fc39b8cca76..53130de7a2e 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -18,7 +18,7 @@ use crate::shared_lock::SharedRwLockReadGuard; use crate::stylesheets::{CssRule, StylesheetInDocument}; use crate::stylesheets::{EffectiveRules, EffectiveRulesIterator}; use crate::values::AtomIdent; -use crate::Atom; +use crate::{Atom, ShrinkIfNeeded}; use crate::LocalName as SelectorLocalName; use selectors::parser::{Component, LocalName, Selector}; @@ -119,6 +119,15 @@ impl StylesheetInvalidationSet { self.fully_invalid = true; } + fn shrink_if_needed(&mut self) { + if self.fully_invalid { + return; + } + self.classes.shrink_if_needed(); + self.ids.shrink_if_needed(); + self.local_names.shrink_if_needed(); + } + /// Analyze the given stylesheet, and collect invalidations from their /// rules, in order to avoid doing a full restyle when we style the document /// next time. @@ -149,6 +158,8 @@ impl StylesheetInvalidationSet { } } + self.shrink_if_needed(); + debug!(" > resulting class invalidations: {:?}", self.classes); debug!(" > resulting id invalidations: {:?}", self.ids); debug!( @@ -484,16 +495,16 @@ impl StylesheetInvalidationSet { }, Invalidation::LocalName { name, lower_name } => { let insert_lower = name != lower_name; - let entry = match self.local_names.try_entry(name) { - Ok(e) => e, - Err(..) => return false, - }; + if self.local_names.try_reserve(1).is_err() { + return false; + } + let entry = self.local_names.entry(name); *entry.or_insert(InvalidationKind::None) |= kind; if insert_lower { - let entry = match self.local_names.try_entry(lower_name) { - Ok(e) => e, - Err(..) => return false, - }; + if self.local_names.try_reserve(1).is_err() { + return false; + } + let entry = self.local_names.entry(lower_name); *entry.or_insert(InvalidationKind::None) |= kind; } }, @@ -541,6 +552,7 @@ impl StylesheetInvalidationSet { Page(..) | Viewport(..) | FontFeatureValues(..) | + LayerStatement(..) | FontFace(..) | Keyframes(..) | ScrollTimeline(..) | @@ -556,7 +568,7 @@ impl StylesheetInvalidationSet { self.collect_invalidations_for_rule(rule, guard, device, quirks_mode) }, - Document(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { + Document(..) | Import(..) | Media(..) | Supports(..) | LayerBlock(..) => { if !is_generic_change && !EffectiveRules::is_effective(guard, device, quirks_mode, rule) { @@ -597,7 +609,8 @@ impl StylesheetInvalidationSet { } } }, - Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { + Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | + LayerStatement(..) | LayerBlock(..) => { // Do nothing, relevant nested rules are visited as part of the // iteration. }, @@ -619,11 +632,12 @@ impl StylesheetInvalidationSet { // existing elements. } }, - ScrollTimeline(..) => { - // TODO: Bug 1676784: check if animation-timeline name is referenced. - // Now we do nothing. - }, - CounterStyle(..) | Page(..) | Viewport(..) | FontFeatureValues(..) => { + // TODO: Check if timeline name is referenced, though this might go away in bug 1737918. + ScrollTimeline(..) | + CounterStyle(..) | + Page(..) | + Viewport(..) | + FontFeatureValues(..) => { debug!( " > Found unsupported rule, marking the whole subtree \ invalid." diff --git a/components/style/lib.rs b/components/style/lib.rs index b721ea0d952..2967fbcf7ce 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -97,7 +97,6 @@ pub mod font_metrics; #[allow(unsafe_code)] pub mod gecko_bindings; pub mod global_style_data; -pub mod hash; pub mod invalidation; #[allow(missing_docs)] // TODO. pub mod logical_geometry; @@ -158,6 +157,8 @@ pub use style_traits::arc_slice::ArcSlice; pub use style_traits::owned_slice::OwnedSlice; pub use style_traits::owned_str::OwnedStr; +use std::hash::{Hash, BuildHasher}; + /// The CSS properties supported by the style system. /// Generated from the properties.mako.rs template by build.rs #[macro_use] @@ -263,3 +264,68 @@ where *self == One::one() } } + +/// An allocation error. +/// +/// TODO(emilio): Would be nice to have more information here, or for SmallVec +/// to return the standard error type (and then we can just return that). +/// +/// But given we use these mostly to bail out and ignore them, it's not a big +/// deal. +#[derive(Debug)] +pub struct AllocErr; + +impl From for AllocErr { + #[inline] + fn from(_: smallvec::CollectionAllocErr) -> Self { + Self + } +} + +impl From for AllocErr { + #[inline] + fn from(_: std::collections::TryReserveError) -> Self { + Self + } +} + +/// Shrink the capacity of the collection if needed. +pub (crate) trait ShrinkIfNeeded { + fn shrink_if_needed(&mut self); +} + +/// We shrink the capacity of a collection if we're wasting more than a 25% of +/// its capacity, and if the collection is arbitrarily big enough +/// (>= CAPACITY_THRESHOLD entries). +#[inline] +fn should_shrink(len: usize, capacity: usize) -> bool { + const CAPACITY_THRESHOLD: usize = 64; + capacity >= CAPACITY_THRESHOLD && len + capacity / 4 < capacity +} + +impl ShrinkIfNeeded for std::collections::HashMap +where + K: Eq + Hash, + H: BuildHasher, +{ + fn shrink_if_needed(&mut self) { + if should_shrink(self.len(), self.capacity()) { + self.shrink_to_fit(); + } + } +} + +impl ShrinkIfNeeded for std::collections::HashSet +where + T: Eq + Hash, + H: BuildHasher, +{ + fn shrink_if_needed(&mut self) { + if should_shrink(self.len(), self.capacity()) { + self.shrink_to_fit(); + } + } +} + +// TODO(emilio): Measure and see if we're wasting a lot of memory on Vec / +// SmallVec, and if so consider shrinking those as well. diff --git a/components/style/matching.rs b/components/style/matching.rs index f81f9c3a233..f2d300df4b2 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -23,6 +23,7 @@ use crate::selector_parser::{PseudoElement, RestyleDamage}; use crate::shared_lock::Locked; use crate::style_resolver::ResolvedElementStyles; use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; +use crate::stylesheets::layer_rule::LayerOrder; use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; use selectors::matching::ElementSelectorFlags; @@ -92,6 +93,7 @@ trait PrivateMatchMethods: TElement { fn replace_single_rule_node( context: &SharedStyleContext, level: CascadeLevel, + layer_order: LayerOrder, pdb: Option>>, path: &mut StrongRuleNode, ) -> bool { @@ -101,6 +103,7 @@ trait PrivateMatchMethods: TElement { let mut important_rules_changed = false; let new_node = stylist.rule_tree().update_rule_at_level( level, + layer_order, pdb, path, guards, @@ -145,12 +148,14 @@ trait PrivateMatchMethods: TElement { result |= Self::replace_single_rule_node( context.shared, CascadeLevel::same_tree_author_normal(), + LayerOrder::root(), style_attribute, primary_rules, ); result |= Self::replace_single_rule_node( context.shared, CascadeLevel::same_tree_author_important(), + LayerOrder::root(), style_attribute, primary_rules, ); @@ -172,6 +177,7 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( context.shared, CascadeLevel::SMILOverride, + LayerOrder::root(), self.smil_override(), primary_rules, ); @@ -181,6 +187,7 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( context.shared, CascadeLevel::Transitions, + LayerOrder::root(), self.transition_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), @@ -192,6 +199,7 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( context.shared, CascadeLevel::Animations, + LayerOrder::root(), self.animation_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), @@ -277,7 +285,7 @@ trait PrivateMatchMethods: TElement { let old_box_style = old_style.get_box(); - let keyframes_could_have_changed = context + let keyframes_or_timeline_could_have_changed = context .shared .traversal_flags .contains(TraversalFlags::ForCSSRuleChanges); @@ -287,9 +295,9 @@ trait PrivateMatchMethods: TElement { // element has or will have CSS animation style regardless of whether // the animation is running or not. // - // TODO: We should check which @keyframes were added/changed/deleted and - // update only animations corresponding to those @keyframes. - if keyframes_could_have_changed { + // TODO: We should check which @keyframes/@scroll-timeline were added/changed/deleted and + // update only animations corresponding to those @keyframes/@scroll-timeline. + if keyframes_or_timeline_could_have_changed { return true; } @@ -505,12 +513,14 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( &context.shared, CascadeLevel::Transitions, + LayerOrder::root(), declarations.transitions.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); Self::replace_single_rule_node( &context.shared, CascadeLevel::Animations, + LayerOrder::root(), declarations.animations.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); @@ -589,12 +599,14 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( &context.shared, CascadeLevel::Transitions, + LayerOrder::root(), declarations.transitions.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); Self::replace_single_rule_node( &context.shared, CascadeLevel::Animations, + LayerOrder::root(), declarations.animations.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs index 1658439da21..80827af401c 100644 --- a/components/style/media_queries/media_feature_expression.rs +++ b/components/style/media_queries/media_feature_expression.rs @@ -211,13 +211,15 @@ fn consume_operation_or_colon(input: &mut Parser) -> Result, () // // TODO(emilio): Maybe we should ignore comments as well? // https://github.com/w3c/csswg-drafts/issues/6248 - let parsed_equal = input.try_parse(|i| { - let t = i.next_including_whitespace().map_err(|_| ())?; - if !matches!(t, Token::Delim('=')) { - return Err(()) - } - Ok(()) - }).is_ok(); + let parsed_equal = input + .try_parse(|i| { + let t = i.next_including_whitespace().map_err(|_| ())?; + if !matches!(t, Token::Delim('=')) { + return Err(()); + } + Ok(()) + }) + .is_ok(); if !parsed_equal { return Ok(Some(operator)); diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index fe5bae33fe8..37d47e1ce61 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -4,6 +4,7 @@ //! The main cascading algorithm of the style system. +use crate::applicable_declarations::CascadePriority; use crate::context::QuirksMode; use crate::custom_properties::CustomPropertiesBuilder; use crate::dom::TElement; @@ -15,12 +16,13 @@ use crate::properties::{ ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY, }; use crate::rule_cache::{RuleCache, RuleCacheConditions}; -use crate::rule_tree::StrongRuleNode; +use crate::rule_tree::{StrongRuleNode, CascadeLevel}; use crate::selector_parser::PseudoElement; use crate::shared_lock::StylesheetGuards; use crate::style_adjuster::StyleAdjuster; -use crate::stylesheets::{Origin, PerOrigin}; +use crate::stylesheets::{Origin, layer_rule::LayerOrder}; use crate::values::{computed, specified}; +use fxhash::FxHashMap; use servo_arc::Arc; use smallvec::SmallVec; use std::borrow::Cow; @@ -115,6 +117,7 @@ struct DeclarationIterator<'a> { declarations: DeclarationImportanceIterator<'a>, origin: Origin, importance: Importance, + priority: CascadePriority, } impl<'a> DeclarationIterator<'a> { @@ -128,8 +131,9 @@ impl<'a> DeclarationIterator<'a> { let mut iter = Self { guards, current_rule_node: Some(rule_node), - origin: Origin::Author, + origin: Origin::UserAgent, importance: Importance::Normal, + priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), declarations: DeclarationImportanceIterator::default(), restriction, }; @@ -138,10 +142,11 @@ impl<'a> DeclarationIterator<'a> { } fn update_for_node(&mut self, node: &'a StrongRuleNode) { - let origin = node.cascade_level().origin(); - self.origin = origin; - self.importance = node.importance(); - let guard = match origin { + self.priority = node.cascade_priority(); + let level = self.priority.cascade_level(); + self.origin = level.origin(); + self.importance = level.importance(); + let guard = match self.origin { Origin::Author => self.guards.author, Origin::User | Origin::UserAgent => self.guards.ua_or_user, }; @@ -153,7 +158,7 @@ impl<'a> DeclarationIterator<'a> { } impl<'a> Iterator for DeclarationIterator<'a> { - type Item = (&'a PropertyDeclaration, Origin); + type Item = (&'a PropertyDeclaration, CascadePriority); #[inline] fn next(&mut self) -> Option { @@ -163,20 +168,19 @@ impl<'a> Iterator for DeclarationIterator<'a> { continue; } - let origin = self.origin; if let Some(restriction) = self.restriction { // decl.id() is either a longhand or a custom // property. Custom properties are always allowed, but // longhands are only allowed if they have our // restriction flag set. if let PropertyDeclarationId::Longhand(id) = decl.id() { - if !id.flags().contains(restriction) && origin != Origin::UserAgent { + if !id.flags().contains(restriction) && self.origin != Origin::UserAgent { continue; } } } - return Some((decl, origin)); + return Some((decl, self.priority)); } let next_node = self.current_rule_node.take()?.parent()?; @@ -259,7 +263,7 @@ pub fn apply_declarations<'a, E, I>( ) -> Arc where E: TElement, - I: Iterator, + I: Iterator, { debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); debug_assert_eq!( @@ -278,14 +282,14 @@ where let inherited_style = parent_style.unwrap_or(device.default_computed_values()); - let mut declarations = SmallVec::<[(&_, Origin); 32]>::new(); + let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new(); let custom_properties = { let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device); - for (declaration, origin) in iter { - declarations.push((declaration, origin)); + for (declaration, priority) in iter { + declarations.push((declaration, priority)); if let PropertyDeclaration::Custom(ref declaration) = *declaration { - builder.cascade(declaration, origin); + builder.cascade(declaration, priority); } } @@ -424,17 +428,18 @@ fn tweak_when_ignoring_colors( // otherwise, this is needed to preserve semi-transparent // backgrounds. // - // FIXME(emilio, bug 1666059): We revert for alpha == 0, but maybe - // should consider not doing that even if it causes some issues like - // bug 1625036, or finding a performant way to preserve the original - // widget background color's rgb channels but not alpha... + // NOTE(emilio): We revert even for alpha == 0. Not doing so would + // be a bit special casey, even though it causes issues like + // bug 1625036. The reasoning is that the conditions that trigger + // that (having mismatched widget and default backgrounds) are both + // uncommon, and broken in other applications as well, and not + // honoring transparent makes stuff uglier or break unconditionally + // (bug 1666059, bug 1755713). let alpha = alpha_channel(color, context); - if alpha != 0 { - let mut color = context.builder.device.default_background_color_for_forced_colors(); - color.alpha = alpha; - declarations_to_apply_unless_overriden - .push(PropertyDeclaration::BackgroundColor(color.into())) - } + let mut color = context.builder.device.default_background_color(); + color.alpha = alpha; + declarations_to_apply_unless_overriden + .push(PropertyDeclaration::BackgroundColor(color.into())) }, PropertyDeclaration::Color(ref color) => { // We honor color: transparent and system colors. @@ -448,7 +453,7 @@ fn tweak_when_ignoring_colors( // override this with a non-transparent color, then override it with // the default color. Otherwise just let it inherit through. if context.builder.get_parent_inherited_text().clone_color().alpha == 0 { - let color = context.builder.device.default_color_for_forced_colors(); + let color = context.builder.device.default_color(); declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color( specified::ColorPropertyValue(color.into()), )) @@ -465,17 +470,20 @@ fn tweak_when_ignoring_colors( } }, _ => { - // We honor transparent and system colors more generally for all - // colors. + // We honor system colors more generally for all colors. // - // NOTE(emilio): This doesn't handle caret-color and - // accent-color because those use a slightly different syntax - // ( | auto for example). That's probably fine though, as - // using a system color for caret-color doesn't make sense (using - // currentColor is fine), and we ignore accent-color in - // high-contrast-mode anyways. + // We used to honor transparent but that causes accessibility + // regressions like bug 1740924. + // + // NOTE(emilio): This doesn't handle caret-color and accent-color + // because those use a slightly different syntax ( | auto for + // example). + // + // That's probably fine though, as using a system color for + // caret-color doesn't make sense (using currentColor is fine), and + // we ignore accent-color in high-contrast-mode anyways. if let Some(color) = declaration.color_value() { - if color.is_system() || alpha_channel(color, context) == 0 { + if color.is_system() { return; } } @@ -491,7 +499,8 @@ struct Cascade<'a, 'b: 'a> { cascade_mode: CascadeMode<'a>, seen: LonghandIdSet, author_specified: LonghandIdSet, - reverted: PerOrigin, + reverted_set: LonghandIdSet, + reverted: FxHashMap, } impl<'a, 'b: 'a> Cascade<'a, 'b> { @@ -501,6 +510,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { cascade_mode, seen: LonghandIdSet::default(), author_specified: LonghandIdSet::default(), + reverted_set: Default::default(), reverted: Default::default(), } } @@ -572,7 +582,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, ) where Phase: CascadePhase, - I: Iterator, + I: Iterator, { let apply_reset = apply_reset == ApplyResetProperties::Yes; @@ -586,7 +596,9 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { let ignore_colors = !self.context.builder.device.use_document_colors(); let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new(); - for (declaration, origin) in declarations { + for (declaration, priority) in declarations { + let origin = priority.cascade_level().origin(); + let declaration_id = declaration.id(); let longhand_id = match declaration_id { PropertyDeclarationId::Longhand(id) => id, @@ -613,12 +625,12 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { continue; } - if self - .reverted - .borrow_for_origin(&origin) - .contains(physical_longhand_id) - { - continue; + if self.reverted_set.contains(physical_longhand_id) { + if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&physical_longhand_id) { + if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { + continue; + } + } } // Only a few properties are allowed to depend on the visited state @@ -650,32 +662,31 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { ); } - let css_wide_keyword = declaration.get_css_wide_keyword(); - if let Some(CSSWideKeyword::Revert) = css_wide_keyword { - // We intentionally don't want to insert it into `self.seen`, - // `reverted` takes care of rejecting other declarations as - // needed. - for origin in origin.following_including() { - self.reverted - .borrow_mut_for_origin(&origin) - .insert(physical_longhand_id); - } - continue; - } + let is_unset = match declaration.get_css_wide_keyword() { + Some(keyword) => match keyword { + CSSWideKeyword::RevertLayer | + CSSWideKeyword::Revert => { + let origin_revert = keyword == CSSWideKeyword::Revert; + // We intentionally don't want to insert it into + // `self.seen`, `reverted` takes care of rejecting other + // declarations as needed. + self.reverted_set.insert(physical_longhand_id); + self.reverted.insert(physical_longhand_id, (priority, origin_revert)); + continue; + }, + CSSWideKeyword::Unset => true, + CSSWideKeyword::Inherit => inherited, + CSSWideKeyword::Initial => !inherited, + }, + None => false, + }; self.seen.insert(physical_longhand_id); if origin == Origin::Author { self.author_specified.insert(physical_longhand_id); } - let unset = css_wide_keyword.map_or(false, |css_wide_keyword| match css_wide_keyword { - CSSWideKeyword::Unset => true, - CSSWideKeyword::Inherit => inherited, - CSSWideKeyword::Initial => !inherited, - CSSWideKeyword::Revert => unreachable!(), - }); - - if unset { + if is_unset { continue; } @@ -871,66 +882,83 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { true } - /// The default font type (which is stored in FontFamilyList's - /// `mDefaultFontType`) depends on the current lang group and generic font - /// family, so we may need to recompute it if or the family changed. - /// - /// Also, we prioritize non-document fonts here if we need to (see the pref - /// `browser.display.use_document_fonts`). + /// The initial font depends on the current lang group so we may need to + /// recompute it if the language changed. #[inline] #[cfg(feature = "gecko")] - fn recompute_default_font_family_type_if_needed(&mut self) { + fn recompute_initial_font_family_if_needed(&mut self) { use crate::gecko_bindings::bindings; - use crate::values::computed::font::GenericFontFamily; + use crate::values::computed::font::FontFamily; - if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) { + if !self.seen.contains(LonghandId::XLang) { return; } - let use_document_fonts = static_prefs::pref!("browser.display.use_document_fonts") != 0; let builder = &mut self.context.builder; - let (default_font_type, prioritize_user_fonts) = { + let default_font_type = { let font = builder.get_font().gecko(); - // System fonts are all right, and should have the default font type - // set to none already, so bail out early. - if font.mFont.family.is_system_font { - debug_assert_eq!( - font.mFont.family.families.fallback, - GenericFontFamily::None - ); + if !font.mFont.family.is_initial { return; } - let generic = font.mFont.family.families.single_generic().unwrap_or(GenericFontFamily::None); let default_font_type = unsafe { - bindings::Gecko_nsStyleFont_ComputeDefaultFontType( + bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( builder.device.document(), - generic, font.mLanguage.mRawPtr, ) }; - // We prioritize user fonts over document fonts if the pref is set, - // and we don't have a generic family already (or we're using - // cursive or fantasy, since they're ignored, see bug 789788), and - // we have a generic family to actually replace it with. - let prioritize_user_fonts = !use_document_fonts && - default_font_type != GenericFontFamily::None && - !generic.valid_for_user_font_prioritization(); - - if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback { - // Nothing to do. + let initial_generic = font.mFont.family.families.single_generic(); + debug_assert!(initial_generic.is_some(), "Initial font should be just one generic font"); + if initial_generic == Some(default_font_type) { return; } - (default_font_type, prioritize_user_fonts) + + default_font_type }; let font = builder.mutate_font().gecko_mut(); - font.mFont.family.families.fallback = default_font_type; - if prioritize_user_fonts { - font.mFont.family.families.prioritize_first_generic_or_prepend(default_font_type); + // NOTE: Leaves is_initial untouched. + font.mFont.family.families = FontFamily::generic(default_font_type).families.clone(); + } + + /// Prioritize user fonts if needed by pref. + #[inline] + #[cfg(feature = "gecko")] + fn prioritize_user_fonts_if_needed(&mut self) { + use crate::gecko_bindings::bindings; + + if !self.seen.contains(LonghandId::FontFamily) { + return; } + + if static_prefs::pref!("browser.display.use_document_fonts") != 0 { + return; + } + + let builder = &mut self.context.builder; + let default_font_type = { + let font = builder.get_font().gecko(); + + if font.mFont.family.is_system_font { + return; + } + + if !font.mFont.family.families.needs_user_font_prioritization() { + return; + } + + unsafe { + bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( + builder.device.document(), + font.mLanguage.mRawPtr, + ) + } + }; + + let font = builder.mutate_font().gecko_mut(); + font.mFont.family.families.prioritize_first_generic_or_prepend(default_font_type); } /// Some keyword sizes depend on the font family and language. @@ -1104,7 +1132,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { #[cfg(feature = "gecko")] { self.unzoom_fonts_if_needed(); - self.recompute_default_font_family_type_if_needed(); + self.recompute_initial_font_family_if_needed(); + self.prioritize_user_fonts_if_needed(); self.recompute_keyword_font_size_if_needed(); self.handle_mathml_scriptlevel_if_needed(); self.constrain_font_size_if_needed() diff --git a/components/style/properties/counted_unknown_properties.py b/components/style/properties/counted_unknown_properties.py index 6a562a9abab..473f2f599d2 100644 --- a/components/style/properties/counted_unknown_properties.py +++ b/components/style/properties/counted_unknown_properties.py @@ -38,7 +38,6 @@ COUNTED_UNKNOWN_PROPERTIES = [ "-webkit-writing-mode", "baseline-shift", "-webkit-hyphenate-character", - "page", "-webkit-highlight", "background-repeat-x", "-webkit-padding-end", @@ -121,5 +120,4 @@ COUNTED_UNKNOWN_PROPERTIES = [ "-webkit-columns", "-webkit-column-rule-color", "-webkit-shape-margin", - "content-visibility", ] diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 49157860fbb..eb61992c44b 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -462,6 +462,7 @@ class Longhand(Property): "Clear", "ColumnCount", "Contain", + "ContentVisibility", "Display", "FillRule", "Float", @@ -486,7 +487,6 @@ class Longhand(Property): "MasonryAutoFlow", "MozForceBrokenImageIcon", "text::MozControlCharacterVisibility", - "MozListReversed", "MathDepth", "MozScriptMinSize", "MozScriptSizeMultiplier", @@ -502,10 +502,12 @@ class Longhand(Property): "OverscrollBehavior", "Percentage", "PositiveIntegerOrNone", + "PrintColorAdjust", "Resize", "RubyPosition", "SVGOpacity", "SVGPaintOrder", + "ScrollbarGutter", "ScrollSnapAlign", "ScrollSnapAxis", "ScrollSnapStrictness", @@ -888,6 +890,7 @@ class PropertyRestrictions: "unicode-bidi", "direction", "content", + "line-height", "-moz-osx-font-smoothing", ] + PropertyRestrictions.spec(data, "css-fonts") diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs index 980d3de11ff..01b390f0fca 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -7,15 +7,17 @@ #![deny(missing_docs)] use super::*; +use crate::applicable_declarations::CascadePriority; use crate::context::QuirksMode; use crate::custom_properties::CustomPropertiesBuilder; use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; use crate::parser::ParserContext; use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; +use crate::rule_tree::CascadeLevel; use crate::selector_parser::SelectorImpl; use crate::shared_lock::Locked; use crate::str::{CssString, CssStringWriter}; -use crate::stylesheets::{CssRuleType, Origin, UrlExtraData}; +use crate::stylesheets::{CssRuleType, Origin, UrlExtraData, layer_rule::LayerOrder}; use crate::values::computed::Context; use cssparser::{parse_important, CowRcStr, DeclarationListParser, ParserInput}; use cssparser::{AtRuleParser, DeclarationParser, Delimiter, ParseErrorKind, Parser}; @@ -898,7 +900,7 @@ impl PropertyDeclarationBlock { for declaration in self.normal_declaration_iter() { if let PropertyDeclaration::Custom(ref declaration) = *declaration { - builder.cascade(declaration, Origin::Author); + builder.cascade(declaration, CascadePriority::new(CascadeLevel::same_tree_author_normal(), LayerOrder::root())); } } diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index e46024feba1..91be2d6011b 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -476,6 +476,7 @@ context.builder.inherit_${property.ident}(); % endif } + CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => unreachable!("Should never get here"), } return; diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 2f9e692f740..5ffea97ba50 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -19,7 +19,7 @@ use servo_arc::Arc; use smallvec::SmallVec; use std::ptr; use std::mem; -use crate::hash::FxHashMap; +use fxhash::FxHashMap; use super::ComputedValues; use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero}; use crate::values::animated::effects::AnimatedFilter; @@ -311,9 +311,9 @@ impl AnimationValue { % for prop in data.longhands: % if prop.animatable: LonghandId::${prop.camel_case} => { - // FIXME(emilio, bug 1533327): I think - // CSSWideKeyword::Revert handling is not fine here, but - // what to do instead? + // FIXME(emilio, bug 1533327): I think revert (and + // revert-layer) handling is not fine here, but what to + // do instead? // // Seems we'd need the computed value as if it was // revert, somehow. Treating it as `unset` seems fine @@ -321,6 +321,7 @@ impl AnimationValue { let style_struct = match declaration.keyword { % if not prop.style_struct.inherited: CSSWideKeyword::Revert | + CSSWideKeyword::RevertLayer | CSSWideKeyword::Unset | % endif CSSWideKeyword::Initial => { @@ -328,6 +329,7 @@ impl AnimationValue { }, % if prop.style_struct.inherited: CSSWideKeyword::Revert | + CSSWideKeyword::RevertLayer | CSSWideKeyword::Unset | % endif CSSWideKeyword::Inherit => { diff --git a/components/style/properties/longhands/background.mako.rs b/components/style/properties/longhands/background.mako.rs index 1cbf601ad5d..76b71b12cd0 100644 --- a/components/style/properties/longhands/background.mako.rs +++ b/components/style/properties/longhands/background.mako.rs @@ -111,5 +111,6 @@ ${helpers.single_keyword( vector=True, engines="gecko", animation_value_type="discrete", + gecko_inexhaustive=True, spec="https://drafts.fxtf.org/compositing/#background-blend-mode", )} diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 3c3532bea7a..33b28375847 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -613,6 +613,16 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-contain/#contain-property", )} +${helpers.predefined_type( + "content-visibility", + "ContentVisibility", + "computed::ContentVisibility::Visible", + engines="gecko", + spec="https://drafts.csswg.org/css-contain/#content-visibility", + gecko_pref="layout.css.content-visibility.enabled", + animation_value_type="none", +)} + ${helpers.predefined_type( "appearance", "Appearance", @@ -689,7 +699,6 @@ ${helpers.predefined_type( "TouchAction", "computed::TouchAction::auto()", engines="gecko", - gecko_pref="layout.css.touch_action.enabled", animation_value_type="discrete", spec="https://compat.spec.whatwg.org/#touch-action", )} @@ -706,3 +715,13 @@ ${helpers.predefined_type( animation_value_type="Integer", spec="https://drafts.csswg.org/css-overflow-3/#line-clamp", )} + +${helpers.predefined_type( + "scrollbar-gutter", + "ScrollbarGutter", + "computed::ScrollbarGutter::AUTO", + engines="gecko", + gecko_pref="layout.css.scrollbar-gutter.enabled", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property", +)} diff --git a/components/style/properties/longhands/counters.mako.rs b/components/style/properties/longhands/counters.mako.rs index db035568c71..2ad0f72cd4a 100644 --- a/components/style/properties/longhands/counters.mako.rs +++ b/components/style/properties/longhands/counters.mako.rs @@ -29,7 +29,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "counter-reset", - "CounterSetOrReset", + "CounterReset", engines="gecko servo-2013", initial_value="Default::default()", animation_value_type="discrete", @@ -39,7 +39,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "counter-set", - "CounterSetOrReset", + "CounterSet", engines="gecko", initial_value="Default::default()", animation_value_type="discrete", diff --git a/components/style/properties/longhands/effects.mako.rs b/components/style/properties/longhands/effects.mako.rs index 5470c74d47c..7a33a36babc 100644 --- a/components/style/properties/longhands/effects.mako.rs +++ b/components/style/properties/longhands/effects.mako.rs @@ -78,7 +78,7 @@ ${helpers.single_keyword( "mix-blend-mode", """normal multiply screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion hue - saturation color luminosity""", + saturation color luminosity""" + ("plus-lighter" if engine == 'gecko' else ""), engines="gecko servo-2013 servo-2020", gecko_enum_prefix="StyleBlend", animation_value_type="discrete", diff --git a/components/style/properties/longhands/font.mako.rs b/components/style/properties/longhands/font.mako.rs index 12191509622..65ee3cbb6ce 100644 --- a/components/style/properties/longhands/font.mako.rs +++ b/components/style/properties/longhands/font.mako.rs @@ -260,7 +260,7 @@ ${helpers.single_keyword( bold-sans-serif sans-serif-italic sans-serif-bold-italic monospace initial tailed looped stretched""", engines="gecko", - gecko_constant_prefix="NS_MATHML_MATHVARIANT", + gecko_enum_prefix="StyleMathVariant", gecko_ffi_name="mMathVariant", spec="Internal (not web-exposed)", animation_value_type="none", diff --git a/components/style/properties/longhands/inherited_box.mako.rs b/components/style/properties/longhands/inherited_box.mako.rs index 294f36bd96f..50f385ee343 100644 --- a/components/style/properties/longhands/inherited_box.mako.rs +++ b/components/style/properties/longhands/inherited_box.mako.rs @@ -56,15 +56,14 @@ ${helpers.single_keyword( spec="https://drafts.csswg.org/css-writing-modes/#propdef-text-orientation", )} -// CSS Color Module Level 4 -// https://drafts.csswg.org/css-color/ -${helpers.single_keyword( - "color-adjust", - "economy exact", +${helpers.predefined_type( + "print-color-adjust", + "PrintColorAdjust", + "computed::PrintColorAdjust::Economy", engines="gecko", - gecko_enum_prefix="StyleColorAdjust", + aliases="color-adjust", + spec="https://drafts.csswg.org/css-color-adjust/#print-color-adjust", animation_value_type="discrete", - spec="https://drafts.csswg.org/css-color/#propdef-color-adjust", )} // According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto` diff --git a/components/style/properties/longhands/inherited_text.mako.rs b/components/style/properties/longhands/inherited_text.mako.rs index 3a66eee9a3f..9065de3145c 100644 --- a/components/style/properties/longhands/inherited_text.mako.rs +++ b/components/style/properties/longhands/inherited_text.mako.rs @@ -373,3 +373,15 @@ ${helpers.predefined_type( animation_value_type="discrete", spec="https://drafts.csswg.org/css-text-decor-4/#text-decoration-skip-ink-property", )} + +// hyphenation character +${helpers.predefined_type( + "hyphenate-character", + "HyphenateCharacter", + "computed::HyphenateCharacter::Auto", + engines="gecko", + gecko_pref="layout.css.hyphenate-character.enabled", + has_effect_on_gecko_scrollbars=False, + animation_value_type="discrete", + spec="https://www.w3.org/TR/css-text-4/#hyphenate-character", +)} diff --git a/components/style/properties/longhands/list.mako.rs b/components/style/properties/longhands/list.mako.rs index 99d848d9db6..dcaa1bcac29 100644 --- a/components/style/properties/longhands/list.mako.rs +++ b/components/style/properties/longhands/list.mako.rs @@ -84,14 +84,3 @@ ${helpers.predefined_type( boxed=True, spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-image-region)", )} - -${helpers.predefined_type( - "-moz-list-reversed", - "MozListReversed", - "computed::MozListReversed::False", - engines="gecko", - animation_value_type="discrete", - enabled_in="ua", - spec="Internal implementation detail for
    ", - servo_restyle_damage="rebuild_and_reflow", -)} diff --git a/components/style/properties/longhands/page.mako.rs b/components/style/properties/longhands/page.mako.rs index 298456cb753..ec41989a137 100644 --- a/components/style/properties/longhands/page.mako.rs +++ b/components/style/properties/longhands/page.mako.rs @@ -19,3 +19,13 @@ ${helpers.predefined_type( animation_value_type="none", rule_types_allowed=PAGE_RULE, )} + +${helpers.predefined_type( + "page", + "PageName", + "computed::PageName::auto()", + engines="gecko", + gecko_pref="layout.css.named-pages.enabled", + spec="https://drafts.csswg.org/css-page-3/#using-named-pages", + animation_value_type="discrete", +)} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 4ee388dfe6c..dd4f72f831f 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -29,7 +29,7 @@ use crate::context::QuirksMode; use crate::logical_geometry::WritingMode; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use crate::computed_value_flags::*; -use crate::hash::FxHashMap; +use fxhash::FxHashMap; use crate::media_queries::Device; use crate::parser::ParserContext; use crate::selector_parser::PseudoElement; @@ -1051,6 +1051,8 @@ pub enum CSSWideKeyword { Unset, /// The `revert` keyword. Revert, + /// The `revert-layer` keyword. + RevertLayer, } impl CSSWideKeyword { @@ -1060,22 +1062,36 @@ impl CSSWideKeyword { CSSWideKeyword::Inherit => "inherit", CSSWideKeyword::Unset => "unset", CSSWideKeyword::Revert => "revert", + CSSWideKeyword::RevertLayer => "revert-layer", } } } +#[inline] +fn cascade_layes_enabled() -> bool { + #[cfg(feature = "gecko")] + return static_prefs::pref!("layout.css.cascade-layers.enabled"); + #[cfg(feature = "servo")] + return false; +} + impl CSSWideKeyword { + /// Parses a CSS wide keyword from a CSS identifier. + pub fn from_ident(ident: &str) -> Result { + Ok(match_ignore_ascii_case! { ident, + "initial" => CSSWideKeyword::Initial, + "inherit" => CSSWideKeyword::Inherit, + "unset" => CSSWideKeyword::Unset, + "revert" => CSSWideKeyword::Revert, + "revert-layer" if cascade_layes_enabled() => CSSWideKeyword::RevertLayer, + _ => return Err(()), + }) + } + fn parse(input: &mut Parser) -> Result { let keyword = { let ident = input.expect_ident().map_err(|_| ())?; - match_ignore_ascii_case! { ident, - // If modifying this set of keyword, also update values::CustomIdent::from_ident - "initial" => CSSWideKeyword::Initial, - "inherit" => CSSWideKeyword::Inherit, - "unset" => CSSWideKeyword::Unset, - "revert" => CSSWideKeyword::Revert, - _ => return Err(()), - } + Self::from_ident(ident)? }; input.expect_exhausted().map_err(|_| ())?; Ok(keyword) diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index 65f55ea887a..dfd4d2bd316 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -11,7 +11,7 @@ use crate::rule_tree::{CascadeLevel, ShadowCascadeOrder}; use crate::selector_map::SelectorMap; use crate::selector_parser::PseudoElement; use crate::shared_lock::Locked; -use crate::stylesheets::Origin; +use crate::stylesheets::{layer_rule::LayerOrder, Origin}; use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use servo_arc::ArcBorrow; @@ -147,8 +147,9 @@ where self.context.current_host = host.map(|e| e.opaque()); f(self); if start != self.rules.len() { - self.rules[start..] - .sort_unstable_by_key(|block| (block.layer_order, block.specificity, block.source_order())); + self.rules[start..].sort_unstable_by_key(|block| { + (block.layer_order(), block.specificity, block.source_order()) + }); } self.context.current_host = old_host; self.in_sort_scope = false; @@ -214,7 +215,12 @@ where } #[inline] - fn collect_rules_in_list(&mut self, part_rules: &[Rule], cascade_level: CascadeLevel, cascade_data: &CascadeData) { + fn collect_rules_in_list( + &mut self, + part_rules: &[Rule], + cascade_level: CascadeLevel, + cascade_data: &CascadeData, + ) { debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); SelectorMap::get_matching_rules( self.element, @@ -228,7 +234,12 @@ where } #[inline] - fn collect_rules_in_map(&mut self, map: &SelectorMap, cascade_level: CascadeLevel, cascade_data: &CascadeData) { + fn collect_rules_in_map( + &mut self, + map: &SelectorMap, + cascade_level: CascadeLevel, + cascade_data: &CascadeData, + ) { debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); map.get_all_matching_rules( self.element, @@ -390,10 +401,10 @@ where let outer_shadow = inner_shadow_host.containing_shadow(); let cascade_data = match outer_shadow { Some(shadow) => shadow.style_data(), - None => Some(self - .stylist - .cascade_data() - .borrow_for_origin(Origin::Author) + None => Some( + self.stylist + .cascade_data() + .borrow_for_origin(Origin::Author), ), }; @@ -406,7 +417,11 @@ where self.in_tree(containing_host, |collector| { for p in &parts { if let Some(part_rules) = part_rules.get(&p.0) { - collector.collect_rules_in_list(part_rules, cascade_level, cascade_data); + collector.collect_rules_in_list( + part_rules, + cascade_level, + cascade_data, + ); } } }); @@ -435,6 +450,7 @@ where .push(ApplicableDeclarationBlock::from_declarations( sa.clone_arc(), CascadeLevel::same_tree_author_normal(), + LayerOrder::style_attribute(), )); } } @@ -445,6 +461,7 @@ where .push(ApplicableDeclarationBlock::from_declarations( so.clone_arc(), CascadeLevel::SMILOverride, + LayerOrder::root(), )); } @@ -456,6 +473,7 @@ where .push(ApplicableDeclarationBlock::from_declarations( anim, CascadeLevel::Animations, + LayerOrder::root(), )); } @@ -466,6 +484,7 @@ where .push(ApplicableDeclarationBlock::from_declarations( anim, CascadeLevel::Transitions, + LayerOrder::root(), )); } } diff --git a/components/style/rule_tree/core.rs b/components/style/rule_tree/core.rs index ae1ba7bed94..e4632ffa711 100644 --- a/components/style/rule_tree/core.rs +++ b/components/style/rule_tree/core.rs @@ -4,8 +4,9 @@ #![allow(unsafe_code)] -use crate::properties::Importance; +use crate::applicable_declarations::CascadePriority; use crate::shared_lock::StylesheetGuards; +use crate::stylesheets::layer_rule::LayerOrder; use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps}; use parking_lot::RwLock; use smallvec::SmallVec; @@ -66,7 +67,7 @@ impl MallocSizeOf for RuleTree { } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -struct ChildKey(CascadeLevel, ptr::NonNull<()>); +struct ChildKey(CascadePriority, ptr::NonNull<()>); unsafe impl Send for ChildKey {} unsafe impl Sync for ChildKey {} @@ -127,7 +128,7 @@ impl RuleTree { return; } - let mut children_count = crate::hash::FxHashMap::default(); + let mut children_count = fxhash::FxHashMap::default(); let mut stack = SmallVec::<[_; 32]>::new(); stack.push(self.root.clone()); @@ -219,8 +220,8 @@ struct RuleNode { /// None for the root node. source: Option, - /// The cascade level this rule is positioned at. - level: CascadeLevel, + /// The cascade level + layer order this rule is positioned at. + cascade_priority: CascadePriority, /// The refcount of this node. /// @@ -316,14 +317,14 @@ impl RuleNode { root: WeakRuleNode, parent: StrongRuleNode, source: StyleSource, - level: CascadeLevel, + cascade_priority: CascadePriority, ) -> Self { debug_assert!(root.p.parent.is_none()); RuleNode { root: Some(root), parent: Some(parent), source: Some(source), - level: level, + cascade_priority, refcount: AtomicUsize::new(1), children: Default::default(), approximate_free_count: AtomicUsize::new(0), @@ -336,7 +337,7 @@ impl RuleNode { root: None, parent: None, source: None, - level: CascadeLevel::UANormal, + cascade_priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), refcount: AtomicUsize::new(1), approximate_free_count: AtomicUsize::new(0), children: Default::default(), @@ -346,7 +347,7 @@ impl RuleNode { fn key(&self) -> ChildKey { ChildKey( - self.level, + self.cascade_priority, self.source .as_ref() .expect("Called key() on the root node") @@ -554,20 +555,20 @@ impl StrongRuleNode { &self, root: &StrongRuleNode, source: StyleSource, - level: CascadeLevel, + cascade_priority: CascadePriority, ) -> StrongRuleNode { use parking_lot::RwLockUpgradableReadGuard; debug_assert!( - self.p.level <= level, + self.p.cascade_priority <= cascade_priority, "Should be ordered (instead {:?} > {:?}), from {:?} and {:?}", - self.p.level, - level, + self.p.cascade_priority, + cascade_priority, self.p.source, source, ); - let key = ChildKey(level, source.key()); + let key = ChildKey(cascade_priority, source.key()); let children = self.p.children.upgradable_read(); if let Some(child) = children.get(&key, |node| node.p.key()) { // Sound to call because we read-locked the parent's children. @@ -584,7 +585,7 @@ impl StrongRuleNode { root.downgrade(), self.clone(), source, - level, + cascade_priority, ))); // Sound to call because we still own a strong reference to // this node, through the `node` variable itself that we are @@ -602,14 +603,22 @@ impl StrongRuleNode { self.p.source.as_ref() } - /// The cascade level for this node - pub fn cascade_level(&self) -> CascadeLevel { - self.p.level + /// The cascade priority. + #[inline] + pub fn cascade_priority(&self) -> CascadePriority { + self.p.cascade_priority } - /// Get the importance that this rule node represents. - pub fn importance(&self) -> Importance { - self.p.level.importance() + /// The cascade level. + #[inline] + pub fn cascade_level(&self) -> CascadeLevel { + self.cascade_priority().cascade_level() + } + + /// The importance. + #[inline] + pub fn importance(&self) -> crate::properties::Importance { + self.cascade_level().importance() } /// Returns whether this node has any child, only intended for testing diff --git a/components/style/rule_tree/level.rs b/components/style/rule_tree/level.rs index c46e63796ad..b8cbe55ed9c 100644 --- a/components/style/rule_tree/level.rs +++ b/components/style/rule_tree/level.rs @@ -29,7 +29,7 @@ use crate::stylesheets::Origin; /// [3]: https://html.spec.whatwg.org/multipage/#presentational-hints /// [4]: https://drafts.csswg.org/css-scoping/#shadow-cascading #[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)] pub enum CascadeLevel { /// Normal User-Agent rules. UANormal, @@ -69,82 +69,44 @@ pub enum CascadeLevel { } impl CascadeLevel { - /// Pack this cascade level in a single byte. - /// - /// We have 10 levels, which we can represent with 4 bits, and then a - /// cascade order optionally, which we can clamp to three bits max, and - /// represent with a fourth bit for the sign. - /// - /// So this creates: SOOODDDD - /// - /// Where `S` is the sign of the order (one if negative, 0 otherwise), `O` - /// is the absolute value of the order, and `D`s are the discriminant. - #[inline] - pub fn to_byte_lossy(&self) -> u8 { - let (discriminant, order) = match *self { - Self::UANormal => (0, 0), - Self::UserNormal => (1, 0), - Self::PresHints => (2, 0), + /// Convert this level from "unimportant" to "important". + pub fn important(&self) -> Self { + match *self { + Self::UANormal => Self::UAImportant, + Self::UserNormal => Self::UserImportant, Self::AuthorNormal { shadow_cascade_order, - } => (3, shadow_cascade_order.0), - Self::SMILOverride => (4, 0), - Self::Animations => (5, 0), - Self::AuthorImportant { - shadow_cascade_order, - } => (6, shadow_cascade_order.0), - Self::UserImportant => (7, 0), - Self::UAImportant => (8, 0), - Self::Transitions => (9, 0), - }; - - debug_assert_eq!(discriminant & 0xf, discriminant); - if order == 0 { - return discriminant; + } => Self::AuthorImportant { + shadow_cascade_order: -shadow_cascade_order, + }, + Self::PresHints | + Self::SMILOverride | + Self::Animations | + Self::AuthorImportant { .. } | + Self::UserImportant | + Self::UAImportant | + Self::Transitions => *self, } - - let negative = order < 0; - let value = std::cmp::min(order.abs() as u8, 0b111); - (negative as u8) << 7 | value << 4 | discriminant } - /// Convert back from the single-byte representation of the cascade level - /// explained above. - #[inline] - pub fn from_byte(b: u8) -> Self { - let order = { - let abs = ((b & 0b01110000) >> 4) as i8; - let negative = b & 0b10000000 != 0; - if negative { - -abs - } else { - abs - } - }; - let discriminant = b & 0xf; - let level = match discriminant { - 0 => Self::UANormal, - 1 => Self::UserNormal, - 2 => Self::PresHints, - 3 => { - return Self::AuthorNormal { - shadow_cascade_order: ShadowCascadeOrder(order), - } + /// Convert this level from "important" to "non-important". + pub fn unimportant(&self) -> Self { + match *self { + Self::UAImportant => Self::UANormal, + Self::UserImportant => Self::UserNormal, + Self::AuthorImportant { + shadow_cascade_order, + } => Self::AuthorNormal { + shadow_cascade_order: -shadow_cascade_order, }, - 4 => Self::SMILOverride, - 5 => Self::Animations, - 6 => { - return Self::AuthorImportant { - shadow_cascade_order: ShadowCascadeOrder(order), - } - }, - 7 => Self::UserImportant, - 8 => Self::UAImportant, - 9 => Self::Transitions, - _ => unreachable!("Didn't expect {} as a discriminant", discriminant), - }; - debug_assert_eq!(order, 0, "Didn't expect an order value for {:?}", level); - level + Self::PresHints | + Self::SMILOverride | + Self::Animations | + Self::AuthorNormal { .. } | + Self::UserNormal | + Self::UANormal | + Self::Transitions => *self, + } } /// Select a lock guard for this level @@ -231,6 +193,12 @@ impl CascadeLevel { pub struct ShadowCascadeOrder(i8); impl ShadowCascadeOrder { + /// We keep a maximum of 3 bits of order as a limit so that we can pack + /// CascadeLevel in one byte by using half of it for the order, if that ends + /// up being necessary. + const MAX: i8 = 0b111; + const MIN: i8 = -Self::MAX; + /// A level for the outermost shadow tree (the shadow tree we own, and the /// ones from the slots we're slotted in). #[inline] @@ -256,7 +224,9 @@ impl ShadowCascadeOrder { #[inline] pub fn dec(&mut self) { debug_assert!(self.0 < 0); - self.0 = self.0.saturating_sub(1); + if self.0 != Self::MIN { + self.0 -= 1; + } } /// The level, moving inwards. We should only move inwards if we're @@ -264,7 +234,9 @@ impl ShadowCascadeOrder { #[inline] pub fn inc(&mut self) { debug_assert_ne!(self.0, -1); - self.0 = self.0.saturating_add(1); + if self.0 != Self::MAX { + self.0 += 1; + } } } diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index e50382255ca..c2339ee9907 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -6,9 +6,10 @@ //! The rule tree. -use crate::applicable_declarations::ApplicableDeclarationList; +use crate::applicable_declarations::{ApplicableDeclarationList, CascadePriority}; use crate::properties::{LonghandIdSet, PropertyDeclarationBlock}; use crate::shared_lock::{Locked, StylesheetGuards}; +use crate::stylesheets::layer_rule::LayerOrder; use servo_arc::{Arc, ArcBorrow}; use smallvec::SmallVec; use std::io::{self, Write}; @@ -47,21 +48,22 @@ impl RuleTree { guards: &StylesheetGuards, ) -> StrongRuleNode where - I: Iterator, + I: Iterator, { use self::CascadeLevel::*; let mut current = self.root().clone(); let mut found_important = false; - let mut important_author = SmallVec::<[(StyleSource, ShadowCascadeOrder); 4]>::new(); - - let mut important_user = SmallVec::<[StyleSource; 4]>::new(); - let mut important_ua = SmallVec::<[StyleSource; 4]>::new(); + let mut important_author = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); + let mut important_user = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); + let mut important_ua = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); let mut transition = None; - for (source, level) in iter { + for (source, priority) in iter { + let level = priority.cascade_level(); debug_assert!(!level.is_important(), "Important levels handled internally"); + let any_important = { let pdb = source.read(level.guard(guards)); pdb.any_important() @@ -70,13 +72,11 @@ impl RuleTree { if any_important { found_important = true; match level { - AuthorNormal { - shadow_cascade_order, - } => { - important_author.push((source.clone(), shadow_cascade_order)); + AuthorNormal { .. } => { + important_author.push((source.clone(), priority.important())) }, - UANormal => important_ua.push(source.clone()), - UserNormal => important_user.push(source.clone()), + UANormal => important_ua.push((source.clone(), priority.important())), + UserNormal => important_user.push((source.clone(), priority.important())), _ => {}, }; } @@ -98,7 +98,7 @@ impl RuleTree { debug_assert!(transition.is_none()); transition = Some(source); } else { - current = current.ensure_child(self.root(), source, level); + current = current.ensure_child(self.root(), source, priority); } } @@ -110,10 +110,8 @@ impl RuleTree { // Insert important declarations, in order of increasing importance, // followed by any transition rule. // - // Inner shadow wins over same-tree, which wins over outer-shadow. - // - // We negate the shadow cascade order to preserve the right PartialOrd - // behavior. + // Important rules are sorted differently from unimportant ones by + // shadow order and cascade order. if !important_author.is_empty() && important_author.first().unwrap().1 != important_author.last().unwrap().1 { @@ -129,29 +127,27 @@ impl RuleTree { // inside the same chunk already sorted. Seems like we could try to // keep a SmallVec-of-SmallVecs with the chunks and just iterate the // outer in reverse. - important_author.sort_by_key(|&(_, order)| -order); + important_author.sort_by_key(|&(_, priority)| priority); } - for (source, shadow_cascade_order) in important_author.drain(..) { - current = current.ensure_child( - self.root(), - source, - AuthorImportant { - shadow_cascade_order: -shadow_cascade_order, - }, - ); + for (source, priority) in important_author.drain(..) { + current = current.ensure_child(self.root(), source, priority); } - for source in important_user.drain(..) { - current = current.ensure_child(self.root(), source, UserImportant); + for (source, priority) in important_user.drain(..) { + current = current.ensure_child(self.root(), source, priority); } - for source in important_ua.drain(..) { - current = current.ensure_child(self.root(), source, UAImportant); + for (source, priority) in important_ua.drain(..) { + current = current.ensure_child(self.root(), source, priority); } if let Some(source) = transition { - current = current.ensure_child(self.root(), source, Transitions); + current = current.ensure_child( + self.root(), + source, + CascadePriority::new(Transitions, LayerOrder::root()), + ); } current @@ -174,18 +170,18 @@ impl RuleTree { /// return the corresponding rule node representing the last inserted one. pub fn insert_ordered_rules<'a, I>(&self, iter: I) -> StrongRuleNode where - I: Iterator, + I: Iterator, { self.insert_ordered_rules_from(self.root().clone(), iter) } fn insert_ordered_rules_from<'a, I>(&self, from: StrongRuleNode, iter: I) -> StrongRuleNode where - I: Iterator, + I: Iterator, { let mut current = from; - for (source, level) in iter { - current = current.ensure_child(self.root(), source, level); + for (source, priority) in iter { + current = current.ensure_child(self.root(), source, priority); } current } @@ -197,6 +193,7 @@ impl RuleTree { pub fn update_rule_at_level( &self, level: CascadeLevel, + layer_order: LayerOrder, pdb: Option>>, path: &StrongRuleNode, guards: &StylesheetGuards, @@ -209,10 +206,10 @@ impl RuleTree { // First walk up until the first less-or-equally specific rule. let mut children = SmallVec::<[_; 10]>::new(); - while current.cascade_level() > level { + while current.cascade_priority().cascade_level() > level { children.push(( current.style_source().unwrap().clone(), - current.cascade_level(), + current.cascade_priority(), )); current = current.parent().unwrap().clone(); } @@ -227,7 +224,7 @@ impl RuleTree { // to special-case (isn't hard, it's just about removing the `if` and // special cases, and replacing them for a `while` loop, avoiding the // optimizations). - if current.cascade_level() == level { + if current.cascade_priority().cascade_level() == level { *important_rules_changed |= level.is_important(); let current_decls = current.style_source().unwrap().as_declarations(); @@ -267,7 +264,7 @@ impl RuleTree { current = current.ensure_child( self.root(), StyleSource::from_declarations(pdb.clone_arc()), - level, + CascadePriority::new(level, layer_order), ); *important_rules_changed = true; } @@ -276,7 +273,7 @@ impl RuleTree { current = current.ensure_child( self.root(), StyleSource::from_declarations(pdb.clone_arc()), - level, + CascadePriority::new(level, layer_order), ); } } @@ -312,7 +309,10 @@ impl RuleTree { let mut children = SmallVec::<[_; 10]>::new(); for node in iter { if !node.cascade_level().is_animation() { - children.push((node.style_source().unwrap().clone(), node.cascade_level())); + children.push(( + node.style_source().unwrap().clone(), + node.cascade_priority(), + )); } last = node; } @@ -336,6 +336,7 @@ impl RuleTree { let mut dummy = false; self.update_rule_at_level( CascadeLevel::Transitions, + LayerOrder::root(), Some(pdb.borrow_arc()), path, guards, diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index a2b5d6bb6b6..8a575ca8386 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -8,18 +8,17 @@ use crate::applicable_declarations::ApplicableDeclarationList; use crate::context::QuirksMode; use crate::dom::TElement; -use crate::hash::map as hash_map; -use crate::hash::{HashMap, HashSet}; use crate::rule_tree::CascadeLevel; use crate::selector_parser::SelectorImpl; -use crate::stylist::{Rule, CascadeData}; -use crate::{Atom, LocalName, Namespace, WeakAtom}; -use fallible::FallibleVec; -use hashglobe::FailedAllocationError; +use crate::stylist::{CascadeData, Rule}; +use crate::AllocErr; +use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use precomputed_hash::PrecomputedHash; use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext}; use selectors::parser::{Combinator, Component, SelectorIter}; use smallvec::SmallVec; +use std::collections::hash_map; +use std::collections::{HashMap, HashSet}; use std::hash::{BuildHasherDefault, Hash, Hasher}; /// A hasher implementation that doesn't hash anything, because it expects its @@ -123,10 +122,7 @@ impl Default for SelectorMap { } } -// FIXME(Manishearth) the 'static bound can be removed when -// our HashMap fork (hashglobe) is able to use NonZero, -// or when stdlib gets fallible collections -impl SelectorMap { +impl SelectorMap { /// Trivially constructs an empty `SelectorMap`. pub fn new() -> Self { SelectorMap { @@ -153,6 +149,15 @@ impl SelectorMap { ret } + /// Shrink the capacity of the map if needed. + pub fn shrink_if_needed(&mut self) { + self.id_hash.shrink_if_needed(); + self.class_hash.shrink_if_needed(); + self.attribute_hash.shrink_if_needed(); + self.local_name_hash.shrink_if_needed(); + self.namespace_hash.shrink_if_needed(); + } + /// Clears the hashmap retaining storage. pub fn clear(&mut self) { self.root.clear(); @@ -313,7 +318,8 @@ impl SelectorMap { context, flags_setter, ) { - matching_rules.push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); + matching_rules + .push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); } } } @@ -321,11 +327,7 @@ impl SelectorMap { impl SelectorMap { /// Inserts an entry into the correct bucket(s). - pub fn insert( - &mut self, - entry: T, - quirks_mode: QuirksMode, - ) -> Result<(), FailedAllocationError> { + pub fn insert(&mut self, entry: T, quirks_mode: QuirksMode) -> Result<(), AllocErr> { self.count += 1; // NOTE(emilio): It'd be nice for this to be a separate function, but @@ -334,16 +336,16 @@ impl SelectorMap { // common path. macro_rules! insert_into_bucket { ($entry:ident, $bucket:expr) => {{ - match $bucket { + let vec = match $bucket { Bucket::Root => &mut self.root, Bucket::ID(id) => self .id_hash .try_entry(id.clone(), quirks_mode)? - .or_insert_with(SmallVec::new), + .or_default(), Bucket::Class(class) => self .class_hash .try_entry(class.clone(), quirks_mode)? - .or_insert_with(SmallVec::new), + .or_default(), Bucket::Attribute { name, lower_name } | Bucket::LocalName { name, lower_name } => { // If the local name in the selector isn't lowercase, @@ -366,28 +368,32 @@ impl SelectorMap { &mut self.local_name_hash }; if name != lower_name { - hash - .try_entry(lower_name.clone())? - .or_insert_with(SmallVec::new) - .try_push($entry.clone())?; + hash.try_reserve(1)?; + let vec = hash.entry(lower_name.clone()).or_default(); + vec.try_reserve(1)?; + vec.push($entry.clone()); } - hash - .try_entry(name.clone())? - .or_insert_with(SmallVec::new) + hash.try_reserve(1)?; + hash.entry(name.clone()).or_default() + }, + Bucket::Namespace(url) => { + self.namespace_hash.try_reserve(1)?; + self.namespace_hash.entry(url.clone()).or_default() }, - Bucket::Namespace(url) => self - .namespace_hash - .try_entry(url.clone())? - .or_insert_with(SmallVec::new), Bucket::Universal => &mut self.other, - } - .try_push($entry)?; + }; + vec.try_reserve(1)?; + vec.push($entry); }}; } let bucket = { let mut disjoint_buckets = SmallVec::new(); - let bucket = find_bucket(entry.selector(), &mut disjoint_buckets, self.bucket_attributes); + let bucket = find_bucket( + entry.selector(), + &mut disjoint_buckets, + self.bucket_attributes, + ); // See if inserting this selector in multiple entries in the // selector map would be worth it. Consider a case like: @@ -619,11 +625,16 @@ fn specific_bucket_for<'a>( Component::Root => Bucket::Root, Component::ID(ref id) => Bucket::ID(id), Component::Class(ref class) => Bucket::Class(class), - Component::AttributeInNoNamespace { ref local_name, .. } if bucket_attributes => Bucket::Attribute { - name: local_name, - lower_name: local_name, + Component::AttributeInNoNamespace { ref local_name, .. } if bucket_attributes => { + Bucket::Attribute { + name: local_name, + lower_name: local_name, + } }, - Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } if bucket_attributes => Bucket::Attribute { + Component::AttributeInNoNamespaceExists { + ref local_name, + ref local_name_lower, + } if bucket_attributes => Bucket::Attribute { name: local_name, lower_name: local_name_lower, }, @@ -656,8 +667,12 @@ fn specific_bucket_for<'a>( // // So inserting `span` in the rule hash makes sense since we want to // match the slotted . - Component::Slotted(ref selector) => find_bucket(selector.iter(), disjoint_buckets, bucket_attributes), - Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets, bucket_attributes), + Component::Slotted(ref selector) => { + find_bucket(selector.iter(), disjoint_buckets, bucket_attributes) + }, + Component::Host(Some(ref selector)) => { + find_bucket(selector.iter(), disjoint_buckets, bucket_attributes) + }, Component::Is(ref list) | Component::Where(ref list) => { if list.len() == 1 { find_bucket(list[0].iter(), disjoint_buckets, bucket_attributes) @@ -706,36 +721,39 @@ fn find_bucket<'a>( /// Wrapper for PrecomputedHashMap that does ASCII-case-insensitive lookup in quirks mode. #[derive(Clone, Debug, MallocSizeOf)] -pub struct MaybeCaseInsensitiveHashMap( +pub struct MaybeCaseInsensitiveHashMap( PrecomputedHashMap, ); -impl Default for MaybeCaseInsensitiveHashMap { +impl Default for MaybeCaseInsensitiveHashMap { #[inline] fn default() -> Self { MaybeCaseInsensitiveHashMap(PrecomputedHashMap::default()) } } -// FIXME(Manishearth) the 'static bound can be removed when -// our HashMap fork (hashglobe) is able to use NonZero, -// or when stdlib gets fallible collections -impl MaybeCaseInsensitiveHashMap { +impl MaybeCaseInsensitiveHashMap { /// Empty map pub fn new() -> Self { Self::default() } + /// Shrink the capacity of the map if needed. + pub fn shrink_if_needed(&mut self) { + self.0.shrink_if_needed() + } + /// HashMap::try_entry pub fn try_entry( &mut self, mut key: Atom, quirks_mode: QuirksMode, - ) -> Result, FailedAllocationError> { + ) -> Result, AllocErr> { if quirks_mode == QuirksMode::Quirks { key = key.to_ascii_lowercase() } - self.0.try_entry(key) + self.0.try_reserve(1)?; + Ok(self.0.entry(key)) } /// HashMap::is_empty diff --git a/components/style/selector_parser.rs b/components/style/selector_parser.rs index 67d4d4f6981..f29cab9735d 100644 --- a/components/style/selector_parser.rs +++ b/components/style/selector_parser.rs @@ -46,7 +46,7 @@ pub struct SelectorParser<'a> { pub namespaces: &'a Namespaces, /// The extra URL data of the stylesheet, which is used to look up /// whether we are parsing a chrome:// URL style sheet. - pub url_data: Option<&'a UrlExtraData>, + pub url_data: &'a UrlExtraData, } impl<'a> SelectorParser<'a> { @@ -54,14 +54,15 @@ impl<'a> SelectorParser<'a> { /// account namespaces. /// /// This is used for some DOM APIs like `querySelector`. - pub fn parse_author_origin_no_namespace( - input: &str, - ) -> Result, ParseError> { + pub fn parse_author_origin_no_namespace<'i>( + input: &'i str, + url_data: &UrlExtraData, + ) -> Result, ParseError<'i>> { let namespaces = Namespaces::default(); let parser = SelectorParser { stylesheet_origin: Origin::Author, namespaces: &namespaces, - url_data: None, + url_data, }; let mut input = ParserInput::new(input); SelectorList::parse(&parser, &mut CssParser::new(&mut input)) @@ -75,8 +76,7 @@ impl<'a> SelectorParser<'a> { /// Whether we're parsing selectors in a stylesheet that has chrome /// privilege. pub fn chrome_rules_enabled(&self) -> bool { - self.url_data.map_or(false, |d| d.chrome_rules_enabled()) || - self.stylesheet_origin == Origin::User + self.url_data.chrome_rules_enabled() || self.stylesheet_origin == Origin::User } } @@ -170,9 +170,14 @@ impl PerPseudoElementMap { } /// Get an iterator for the entries. - pub fn iter(&self) -> ::std::slice::Iter> { + pub fn iter(&self) -> std::slice::Iter> { self.entries.iter() } + + /// Get a mutable iterator for the entries. + pub fn iter_mut(&mut self) -> std::slice::IterMut> { + self.entries.iter_mut() + } } /// Values for the :dir() pseudo class diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index d575b7c2df7..e1822332ff1 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -190,12 +190,12 @@ impl Device { } /// Returns the default background color. - pub fn default_background_color_for_forced_colors(&self) -> RGBA { + pub fn default_background_color(&self) -> RGBA { RGBA::new(255, 255, 255, 255) } /// Returns the default foreground color. - pub fn default_color_for_forced_colors(&self) -> RGBA { + pub fn default_color(&self) -> RGBA { RGBA::new(0, 0, 0, 255) } @@ -221,18 +221,6 @@ impl Device { } } - /// Returns the gtk titlebar radius in CSS pixels. - /// TODO: implement this method. - pub fn titlebar_radius(&self) -> f32 { - 0.0 - } - - /// Returns the gtk menu radius in CSS pixels. - /// TODO: implement this method. - pub fn menu_radius(&self) -> f32 { - 0.0 - } - /// Return whether the document is a chrome document. #[inline] pub fn is_chrome_document(&self) -> bool { diff --git a/components/style/shared_lock.rs b/components/style/shared_lock.rs index d524f0c6cdf..55708a9f7bb 100644 --- a/components/style/shared_lock.rs +++ b/components/style/shared_lock.rs @@ -97,7 +97,10 @@ impl SharedRwLock { #[cfg(feature = "gecko")] #[inline] fn ptr(&self) -> *const SomethingZeroSizedButTyped { - self.cell.as_ref().map(|cell| cell.as_ptr() as *const _).unwrap_or(ptr::null()) + self.cell + .as_ref() + .map(|cell| cell.as_ptr() as *const _) + .unwrap_or(ptr::null()) } /// Wrap the given data to make its access protected by this lock. @@ -154,7 +157,10 @@ impl<'a> SharedRwLockReadGuard<'a> { #[inline] #[cfg(feature = "gecko")] fn ptr(&self) -> *const SomethingZeroSizedButTyped { - self.0.as_ref().map(|r| &**r as *const _).unwrap_or(ptr::null()) + self.0 + .as_ref() + .map(|r| &**r as *const _) + .unwrap_or(ptr::null()) } } diff --git a/components/style/style_adjuster.rs b/components/style/style_adjuster.rs index 31028f98571..ca5bbe9bb00 100644 --- a/components/style/style_adjuster.rs +++ b/components/style/style_adjuster.rs @@ -136,9 +136,15 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { /// computed to 'absolute' if the element is in a top layer. /// fn adjust_for_top_layer(&mut self) { - if !self.style.is_absolutely_positioned() && self.style.in_top_layer() { + if !self.style.in_top_layer() { + return; + } + if !self.style.is_absolutely_positioned() { self.style.mutate_box().set_position(Position::Absolute); } + if self.style.get_box().clone_display().is_contents() { + self.style.mutate_box().set_display(Display::Block); + } } /// CSS 2.1 section 9.7: @@ -760,9 +766,9 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { /// the same font as its fallback ('list-style-type') in case it fails to load. #[cfg(feature = "gecko")] fn adjust_for_marker_pseudo(&mut self) { + use crate::values::computed::counters::Content; use crate::values::computed::font::{FontFamily, FontSynthesis}; use crate::values::computed::text::{LetterSpacing, WordSpacing}; - use crate::values::computed::counters::{Content}; let is_legacy_marker = self.style.pseudo.map_or(false, |p| p.is_marker()) && self.style.get_list().clone_list_style_type().is_bullet() && @@ -770,21 +776,49 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { if !is_legacy_marker { return; } - if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY) { - self.style.mutate_font().set_font_family(FontFamily::moz_bullet().clone()); + if !self + .style + .flags + .get() + .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY) + { + self.style + .mutate_font() + .set_font_family(FontFamily::moz_bullet().clone()); // FIXME(mats): We can remove this if support for font-synthesis is added to @font-face rules. // Then we can add it to the @font-face rule in html.css instead. // https://github.com/w3c/csswg-drafts/issues/6081 - if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS) { - self.style.mutate_font().set_font_synthesis(FontSynthesis::none()); + if !self + .style + .flags + .get() + .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS) + { + self.style + .mutate_font() + .set_font_synthesis(FontSynthesis::none()); } } - if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING) { - self.style.mutate_inherited_text().set_letter_spacing(LetterSpacing::normal()); + if !self + .style + .flags + .get() + .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING) + { + self.style + .mutate_inherited_text() + .set_letter_spacing(LetterSpacing::normal()); } - if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING) { - self.style.mutate_inherited_text().set_word_spacing(WordSpacing::normal()); + if !self + .style + .flags + .get() + .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING) + { + self.style + .mutate_inherited_text() + .set_word_spacing(WordSpacing::normal()); } } diff --git a/components/style/stylesheets/import_rule.rs b/components/style/stylesheets/import_rule.rs index 7352dea0b9e..ccf0bb11e51 100644 --- a/components/style/stylesheets/import_rule.rs +++ b/components/style/stylesheets/import_rule.rs @@ -10,8 +10,8 @@ use crate::media_queries::MediaList; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; -use crate::stylesheets::{CssRule, StylesheetInDocument}; use crate::stylesheets::layer_rule::LayerName; +use crate::stylesheets::{CssRule, StylesheetInDocument}; use crate::values::CssUrl; use cssparser::SourceLocation; use std::fmt::{self, Write}; @@ -131,7 +131,6 @@ pub struct ImportLayer { pub name: Option, } - impl ToCss for ImportLayer { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where diff --git a/components/style/stylesheets/keyframes_rule.rs b/components/style/stylesheets/keyframes_rule.rs index 8b721b7a52c..e84339a3371 100644 --- a/components/style/stylesheets/keyframes_rule.rs +++ b/components/style/stylesheets/keyframes_rule.rs @@ -14,7 +14,6 @@ use crate::properties::{PropertyDeclarationId, SourcePropertyDeclaration}; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard}; use crate::shared_lock::{Locked, ToCssWithGuard}; use crate::str::CssStringWriter; -use crate::stylesheets::layer_rule::LayerId; use crate::stylesheets::rule_parser::VendorPrefix; use crate::stylesheets::{CssRuleType, StylesheetContents}; use crate::values::{serialize_percentage, KeyframesName}; @@ -358,8 +357,6 @@ pub struct KeyframesAnimation { pub properties_changed: LonghandIdSet, /// Vendor prefix type the @keyframes has. pub vendor_prefix: Option, - /// The id of the cascade layer the keyframe rule was in. - pub layer_id: LayerId, } /// Get all the animated properties in a keyframes animation. @@ -412,14 +409,12 @@ impl KeyframesAnimation { pub fn from_keyframes( keyframes: &[Arc>], vendor_prefix: Option, - layer_id: LayerId, guard: &SharedRwLockReadGuard, ) -> Self { let mut result = KeyframesAnimation { steps: vec![], properties_changed: LonghandIdSet::new(), vendor_prefix, - layer_id, }; if keyframes.is_empty() { @@ -500,8 +495,8 @@ pub fn parse_keyframe_list( RuleListParser::new_for_nested_rule( input, KeyframeListParser { - context: context, - shared_lock: shared_lock, + context, + shared_lock, declarations: &mut declarations, }, ) diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index ee066d813e2..ebff5bb9add 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -13,20 +13,38 @@ use crate::values::AtomIdent; use super::CssRules; -use cssparser::{Parser, SourceLocation, ToCss as CssParserToCss, Token}; +use cssparser::{Parser, SourceLocation, Token}; use servo_arc::Arc; use smallvec::SmallVec; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; -/// The order of a given layer. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] -pub struct LayerOrder(u32); +/// The order of a given layer. We use 16 bits so that we can pack LayerOrder +/// and CascadeLevel in a single 32-bit struct. If we need more bits we can go +/// back to packing CascadeLevel in a single byte as we did before. +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct LayerOrder(u16); impl LayerOrder { /// The order of the root layer. pub const fn root() -> Self { - Self(std::u32::MAX) + Self(std::u16::MAX - 1) + } + + /// The order of the style attribute layer. + pub const fn style_attribute() -> Self { + Self(std::u16::MAX) + } + + /// Returns whether this layer is for the style attribute, which behaves + /// differently in terms of !important, see + /// https://github.com/w3c/csswg-drafts/issues/6872 + /// + /// (This is a bit silly, mind-you, but it's needed so that revert-layer + /// behaves correctly). + #[inline] + pub fn is_style_attribute_layer(&self) -> bool { + *self == Self::style_attribute() } /// The first cascade layer order. @@ -37,7 +55,9 @@ impl LayerOrder { /// Increment the cascade layer order. #[inline] pub fn inc(&mut self) { - self.0 += 1; + if self.0 != std::u16::MAX - 1 { + self.0 += 1; + } } } @@ -139,71 +159,34 @@ impl ToCss for LayerName { } } -/// The kind of layer rule this is. #[derive(Debug, ToShmem)] -pub enum LayerRuleKind { - /// A block `@layer ? { ... }` - Block { - /// The layer name, or `None` if anonymous. - name: Option, - /// The nested rules. - rules: Arc>, - }, - /// A statement `@layer , , ;` - Statement { - /// The list of layers to sort. - names: Vec, - }, -} - -/// A [`@layer`][layer] rule. -/// -/// [layer]: https://drafts.csswg.org/css-cascade-5/#layering -#[derive(Debug, ToShmem)] -pub struct LayerRule { - /// The kind of layer rule we are. - pub kind: LayerRuleKind, - /// The source position where this media rule was found. +/// A block `@layer ? { ... }` +/// https://drafts.csswg.org/css-cascade-5/#layer-block +pub struct LayerBlockRule { + /// The layer name, or `None` if anonymous. + pub name: Option, + /// The nested rules. + pub rules: Arc>, + /// The source position where this rule was found. pub source_location: SourceLocation, } -impl ToCssWithGuard for LayerRule { +impl ToCssWithGuard for LayerBlockRule { fn to_css( &self, guard: &SharedRwLockReadGuard, dest: &mut crate::str::CssStringWriter, ) -> fmt::Result { dest.write_str("@layer")?; - match self.kind { - LayerRuleKind::Block { - ref name, - ref rules, - } => { - if let Some(ref name) = *name { - dest.write_char(' ')?; - name.to_css(&mut CssWriter::new(dest))?; - } - rules.read_with(guard).to_css_block(guard, dest) - }, - LayerRuleKind::Statement { ref names } => { - let mut writer = CssWriter::new(dest); - let mut first = true; - for name in &**names { - if first { - writer.write_char(' ')?; - } else { - writer.write_str(", ")?; - } - first = false; - name.to_css(&mut writer)?; - } - dest.write_char(';') - }, + if let Some(ref name) = self.name { + dest.write_char(' ')?; + name.to_css(&mut CssWriter::new(dest))?; } + self.rules.read_with(guard).to_css_block(guard, dest) } } -impl DeepCloneWithLock for LayerRule { +impl DeepCloneWithLock for LayerBlockRule { fn deep_clone_with_lock( &self, lock: &SharedRwLock, @@ -211,25 +194,57 @@ impl DeepCloneWithLock for LayerRule { params: &DeepCloneParams, ) -> Self { Self { - kind: match self.kind { - LayerRuleKind::Block { - ref name, - ref rules, - } => LayerRuleKind::Block { - name: name.clone(), - rules: Arc::new( - lock.wrap( - rules - .read_with(guard) - .deep_clone_with_lock(lock, guard, params), - ), - ), - }, - LayerRuleKind::Statement { ref names } => LayerRuleKind::Statement { - names: names.clone(), - }, - }, + name: self.name.clone(), + rules: Arc::new( + lock.wrap( + self.rules + .read_with(guard) + .deep_clone_with_lock(lock, guard, params), + ), + ), source_location: self.source_location.clone(), } } } + +/// A statement `@layer , , ;` +/// +/// https://drafts.csswg.org/css-cascade-5/#layer-empty +#[derive(Clone, Debug, ToShmem)] +pub struct LayerStatementRule { + /// The list of layers to sort. + pub names: Vec, + /// The source position where this rule was found. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for LayerStatementRule { + fn to_css( + &self, + _: &SharedRwLockReadGuard, + dest: &mut crate::str::CssStringWriter, + ) -> fmt::Result { + let mut writer = CssWriter::new(dest); + writer.write_str("@layer ")?; + let mut first = true; + for name in &*self.names { + if !first { + writer.write_str(", ")?; + } + first = false; + name.to_css(&mut writer)?; + } + writer.write_char(';') + } +} + +impl DeepCloneWithLock for LayerStatementRule { + fn deep_clone_with_lock( + &self, + _: &SharedRwLock, + _: &SharedRwLockReadGuard, + _: &DeepCloneParams, + ) -> Self { + self.clone() + } +} diff --git a/components/style/stylesheets/loader.rs b/components/style/stylesheets/loader.rs index c145bafdc04..45f7d22a55c 100644 --- a/components/style/stylesheets/loader.rs +++ b/components/style/stylesheets/loader.rs @@ -8,7 +8,7 @@ use crate::media_queries::MediaList; use crate::parser::ParserContext; use crate::shared_lock::{Locked, SharedRwLock}; -use crate::stylesheets::import_rule::{ImportRule, ImportLayer}; +use crate::stylesheets::import_rule::{ImportLayer, ImportRule}; use crate::values::CssUrl; use cssparser::SourceLocation; use servo_arc::Arc; diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index 762bca6033f..d48b7504797 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -51,12 +51,12 @@ pub use self::font_face_rule::FontFaceRule; pub use self::font_feature_values_rule::FontFeatureValuesRule; pub use self::import_rule::ImportRule; pub use self::keyframes_rule::KeyframesRule; -pub use self::layer_rule::LayerRule; +pub use self::layer_rule::{LayerBlockRule, LayerStatementRule}; pub use self::loader::StylesheetLoader; pub use self::media_rule::MediaRule; pub use self::namespace_rule::NamespaceRule; pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter}; -pub use self::page_rule::PageRule; +pub use self::page_rule::{PageRule, PageSelector, PageSelectors}; pub use self::rule_list::{CssRules, CssRulesHelpers}; pub use self::rule_parser::{InsertRuleContext, State, TopLevelRuleParser}; pub use self::rules_iterator::{AllRules, EffectiveRules}; @@ -261,7 +261,8 @@ pub enum CssRule { Supports(Arc>), Page(Arc>), Document(Arc>), - Layer(Arc>), + LayerBlock(Arc>), + LayerStatement(Arc>), ScrollTimeline(Arc>), } @@ -304,9 +305,8 @@ impl CssRule { lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) }, - // TODO(emilio): Add memory reporting for @layer rules. - CssRule::Layer(_) => 0, - CssRule::ScrollTimeline(_) => 0, + // TODO(emilio): Add memory reporting for these rules. + CssRule::LayerBlock(_) | CssRule::LayerStatement(_) | CssRule::ScrollTimeline(_) => 0, } } } @@ -341,8 +341,9 @@ pub enum CssRuleType { Viewport = 15, // After viewport, all rules should return 0 from the API, but we still need // a constant somewhere. - Layer = 16, - ScrollTimeline = 17, + LayerBlock = 16, + LayerStatement = 17, + ScrollTimeline = 18, } #[allow(missing_docs)] @@ -369,7 +370,8 @@ impl CssRule { CssRule::Supports(_) => CssRuleType::Supports, CssRule::Page(_) => CssRuleType::Page, CssRule::Document(_) => CssRuleType::Document, - CssRule::Layer(_) => CssRuleType::Layer, + CssRule::LayerBlock(_) => CssRuleType::LayerBlock, + CssRule::LayerStatement(_) => CssRuleType::LayerStatement, CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline, } } @@ -504,16 +506,22 @@ impl DeepCloneWithLock for CssRule { lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) }, - CssRule::Layer(ref arc) => { + CssRule::LayerStatement(ref arc) => { let rule = arc.read_with(guard); - CssRule::Layer(Arc::new( + CssRule::LayerStatement(Arc::new( lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) - } + }, + CssRule::LayerBlock(ref arc) => { + let rule = arc.read_with(guard); + CssRule::LayerBlock(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), + )) + }, CssRule::ScrollTimeline(ref arc) => { let rule = arc.read_with(guard); CssRule::ScrollTimeline(Arc::new(lock.wrap(rule.clone()))) - } + }, } } } @@ -534,7 +542,8 @@ impl ToCssWithGuard for CssRule { CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Layer(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::LayerBlock(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::LayerStatement(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::ScrollTimeline(ref lock) => lock.read_with(guard).to_css(guard, dest), } } diff --git a/components/style/stylesheets/namespace_rule.rs b/components/style/stylesheets/namespace_rule.rs index d76703e4f81..c0d017ab78f 100644 --- a/components/style/stylesheets/namespace_rule.rs +++ b/components/style/stylesheets/namespace_rule.rs @@ -7,7 +7,7 @@ use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; use crate::{Namespace, Prefix}; -use cssparser::{self, SourceLocation}; +use cssparser::SourceLocation; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; @@ -25,15 +25,15 @@ pub struct NamespaceRule { impl ToCssWithGuard for NamespaceRule { // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSNamespaceRule - fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + fn to_css(&self, _guard: &SharedRwLockReadGuard, dest_str: &mut CssStringWriter) -> fmt::Result { + let mut dest = CssWriter::new(dest_str); dest.write_str("@namespace ")?; if let Some(ref prefix) = self.prefix { - let prefix = prefix.to_string(); - cssparser::serialize_identifier(&prefix, dest)?; - dest.write_str(" ")?; + prefix.to_css(&mut dest)?; + dest.write_char(' ')?; } dest.write_str("url(")?; - self.url.to_string().to_css(&mut CssWriter::new(dest))?; + self.url.to_string().to_css(&mut dest)?; dest.write_str(");") } } diff --git a/components/style/stylesheets/origin.rs b/components/style/stylesheets/origin.rs index a65b61fca13..27ad3fa184a 100644 --- a/components/style/stylesheets/origin.rs +++ b/components/style/stylesheets/origin.rs @@ -10,7 +10,7 @@ use std::ops::BitOrAssign; /// Each style rule has an origin, which determines where it enters the cascade. /// /// -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem, PartialOrd, Ord)] #[repr(u8)] pub enum Origin { /// diff --git a/components/style/stylesheets/page_rule.rs b/components/style/stylesheets/page_rule.rs index 3edf562258f..ee156cf989e 100644 --- a/components/style/stylesheets/page_rule.rs +++ b/components/style/stylesheets/page_rule.rs @@ -6,27 +6,91 @@ //! //! [page]: https://drafts.csswg.org/css2/page.html#page-box +use crate::parser::{Parse, ParserContext}; use crate::properties::PropertyDeclarationBlock; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; -use cssparser::SourceLocation; +use crate::values::{AtomIdent, CustomIdent}; +use style_traits::{CssWriter, ParseError, ToCss}; +use cssparser::{Parser, SourceLocation}; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; use servo_arc::Arc; use std::fmt::{self, Write}; +/// Type of a single [`@page`][page selector] +/// +/// We do not support pseudo selectors yet. +/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors +#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +pub struct PageSelector(pub AtomIdent); + +impl PageSelector { + /// Checks if the ident matches a page-name's ident. + /// + /// This does not currently take pseudo selectors into account. + #[inline] + pub fn ident_matches(&self, other: &CustomIdent) -> bool { + self.0.0 == other.0 + } +} + +impl Parse for PageSelector { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let s = input.expect_ident()?; + Ok(PageSelector(AtomIdent::from(&**s))) + } +} + +/// A list of [`@page`][page selectors] +/// +/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors +#[derive(Clone, Debug, Default, MallocSizeOf, ToCss, ToShmem)] +#[css(comma)] +pub struct PageSelectors(#[css(iterable)] pub Box<[PageSelector]>); + +impl PageSelectors { + /// Creates a new PageSelectors from a Vec, as from parse_comma_separated + #[inline] + pub fn new(s: Vec) -> Self { + PageSelectors(s.into()) + } + /// Returns true iff there are any page selectors + #[inline] + pub fn is_empty(&self) -> bool { + self.as_slice().is_empty() + } + /// Get the underlying PageSelector data as a slice + #[inline] + pub fn as_slice(&self) -> &[PageSelector] { + &*self.0 + } +} + +impl Parse for PageSelectors { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Ok(PageSelectors::new(input.parse_comma_separated(|i| PageSelector::parse(context, i))?)) + } +} + /// A [`@page`][page] rule. /// /// This implements only a limited subset of the CSS /// 2.2 syntax. /// -/// In this subset, [page selectors][page-selectors] are not implemented. -/// /// [page]: https://drafts.csswg.org/css2/page.html#page-box /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors -#[derive(Debug, ToShmem)] +#[derive(Clone, Debug, ToShmem)] pub struct PageRule { + /// Selectors of the page-rule + pub selectors: PageSelectors, /// The declaration block this page rule contains. pub block: Arc>, /// The source position this rule was found at. @@ -38,7 +102,7 @@ impl PageRule { #[cfg(feature = "gecko")] pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { // Measurement of other fields may be added later. - self.block.unconditional_shallow_size_of(ops) + self.block.read_with(guard).size_of(ops) + self.block.unconditional_shallow_size_of(ops) + self.block.read_with(guard).size_of(ops) + self.selectors.size_of(ops) } } @@ -46,13 +110,18 @@ impl ToCssWithGuard for PageRule { /// Serialization of PageRule is not specced, adapted from steps for /// StyleRule. fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@page { ")?; + dest.write_str("@page ")?; + if !self.selectors.is_empty() { + self.selectors.to_css(&mut CssWriter::new(dest))?; + dest.write_char(' ')?; + } + dest.write_str("{ ")?; let declaration_block = self.block.read_with(guard); declaration_block.to_css(dest)?; if !declaration_block.declarations().is_empty() { - dest.write_str(" ")?; + dest.write_char(' ')?; } - dest.write_str("}") + dest.write_char('}') } } @@ -64,6 +133,7 @@ impl DeepCloneWithLock for PageRule { _params: &DeepCloneParams, ) -> Self { PageRule { + selectors: self.selectors.clone(), block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())), source_location: self.source_location.clone(), } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 7e3e4f9f86c..9c0095bc32d 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -17,13 +17,13 @@ use crate::stylesheets::document_rule::DocumentCondition; use crate::stylesheets::font_feature_values_rule::parse_family_name_list; use crate::stylesheets::import_rule::ImportLayer; use crate::stylesheets::keyframes_rule::parse_keyframe_list; -use crate::stylesheets::layer_rule::{LayerName, LayerRuleKind}; +use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule}; use crate::stylesheets::scroll_timeline_rule::ScrollTimelineDescriptors; use crate::stylesheets::stylesheet::Namespaces; use crate::stylesheets::supports_rule::SupportsCondition; use crate::stylesheets::{ viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule, - FontFeatureValuesRule, KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, + FontFeatureValuesRule, KeyframesRule, MediaRule, NamespaceRule, PageRule, PageSelectors, RulesMutateError, ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule, }; use crate::values::computed::font::FamilyName; @@ -168,8 +168,8 @@ pub enum AtRulePrelude { Viewport, /// A @keyframes rule, with its animation name and vendor prefix if exists. Keyframes(KeyframesName, Option), - /// A @page rule prelude. - Page, + /// A @page rule prelude, with its page name if it exists. + Page(PageSelectors), /// A @document rule, with its conditional. Document(DocumentCondition), /// A @import rule prelude. @@ -289,7 +289,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { &mut self, prelude: AtRulePrelude, start: &ParserState, - ) -> Result { + ) -> Result { let rule = match prelude { AtRulePrelude::Import(url, media, layer) => { let loader = self @@ -468,8 +468,13 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { let name = KeyframesName::parse(self.context, input)?; AtRulePrelude::Keyframes(name, prefix) }, - "page" if cfg!(feature = "gecko") => { - AtRulePrelude::Page + #[cfg(feature = "gecko")] + "page" => { + AtRulePrelude::Page(if static_prefs::pref!("layout.css.named-pages.enabled") { + input.try_parse(|i| PageSelectors::parse(self.context, i)).unwrap_or_default() + } else { + PageSelectors::default() + }) }, "-moz-document" if cfg!(feature = "gecko") => { let cond = DocumentCondition::parse(self.context, input)?; @@ -583,7 +588,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { }, )))) }, - AtRulePrelude::Page => { + AtRulePrelude::Page(selectors) => { let context = ParserContext::new_with_rule_type( self.context, CssRuleType::Page, @@ -592,6 +597,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { let declarations = parse_property_declaration_list(&context, input, None); Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule { + selectors, block: Arc::new(self.shared_lock.wrap(declarations)), source_location: start.source_location(), })))) @@ -613,12 +619,10 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { 0 | 1 => names.into_iter().next(), _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), }; - Ok(CssRule::Layer(Arc::new(self.shared_lock.wrap( - LayerRule { - kind: LayerRuleKind::Block { - name, - rules: self.parse_nested_rules(input, CssRuleType::Layer), - }, + Ok(CssRule::LayerBlock(Arc::new(self.shared_lock.wrap( + LayerBlockRule { + name, + rules: self.parse_nested_rules(input, CssRuleType::LayerBlock), source_location: start.source_location(), }, )))) @@ -650,14 +654,14 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { &mut self, prelude: AtRulePrelude, start: &ParserState, - ) -> Result { + ) -> Result { Ok(match prelude { AtRulePrelude::Layer(names) => { if names.is_empty() { return Err(()); } - CssRule::Layer(Arc::new(self.shared_lock.wrap(LayerRule { - kind: LayerRuleKind::Statement { names }, + CssRule::LayerStatement(Arc::new(self.shared_lock.wrap(LayerStatementRule { + names, source_location: start.source_location(), }))) }, @@ -687,7 +691,10 @@ fn check_for_useless_selector( } if found_host && found_non_host { let location = input.current_source_location(); - context.log_css_error(location, ContextualParseError::NeverMatchingHostSelector(selector.to_css_string())); + context.log_css_error( + location, + ContextualParseError::NeverMatchingHostSelector(selector.to_css_string()), + ); continue 'selector_loop; } } @@ -710,7 +717,7 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b> { let selector_parser = SelectorParser { stylesheet_origin: self.context.stylesheet_origin, namespaces: self.namespaces, - url_data: Some(self.context.url_data), + url_data: self.context.url_data, }; let selectors = SelectorList::parse(&selector_parser, input)?; if self.context.error_reporting_enabled() { diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index 32851dd2cca..417185953a0 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -70,6 +70,7 @@ where CssRule::Keyframes(_) | CssRule::ScrollTimeline(_) | CssRule::Page(_) | + CssRule::LayerStatement(_) | CssRule::FontFeatureValues(_) => None, CssRule::Import(ref import_rule) => { let import_rule = import_rule.read_with(guard); @@ -103,15 +104,10 @@ where } Some(supports_rule.rules.read_with(guard).0.iter()) }, - CssRule::Layer(ref lock) => { - use crate::stylesheets::layer_rule::LayerRuleKind; - + CssRule::LayerBlock(ref lock) => { let layer_rule = lock.read_with(guard); - match layer_rule.kind { - LayerRuleKind::Block { ref rules, .. } => Some(rules.read_with(guard).0.iter()), - LayerRuleKind::Statement { .. } => None, - } - } + Some(layer_rule.rules.read_with(guard).0.iter()) + }, } } } @@ -323,7 +319,8 @@ impl<'a, 'b> EffectiveRulesIterator<'a, 'b> { guard: &'a SharedRwLockReadGuard<'b>, rule: &'a CssRule, ) -> Self { - let children = RulesIterator::::children(rule, device, quirks_mode, guard, &mut false); + let children = + RulesIterator::::children(rule, device, quirks_mode, guard, &mut false); EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter())) } } diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs index bbc5d9caf8f..12b0d2013a6 100644 --- a/components/style/stylesheets/scroll_timeline_rule.rs +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -157,15 +157,19 @@ impl<'a, 'b, 'i> DeclarationParser<'i> for ScrollTimelineDescriptorsParser<'a, ' /// The scroll-timeline source. /// /// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-source +// FIXME: Bug 1733260 may drop the entire @scroll-timeline, and now we don't support source other +// than the default value (so use #[css(skip)]). #[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)] pub enum Source { /// The scroll container. + #[css(skip)] Selector(ScrollTimelineSelector), /// The initial value. The scrollingElement of the Document associated with the Window that is /// the current global object. Auto, /// Null. However, it's not clear what is the expected behavior of this. See the spec issue: /// https://drafts.csswg.org/scroll-animations/#issue-0d1e73bd + #[css(skip)] None, } @@ -182,7 +186,8 @@ impl Default for Source { /// definition of ScrollTimelineOptions (WebIDL API). /// https://drafts.csswg.org/scroll-animations/#dom-scrolltimelineoptions-orientation #[derive(Clone, Copy, Debug, MallocSizeOf, Eq, Parse, PartialEq, PartialOrd, ToCss, ToShmem)] -pub enum Orientation { +#[repr(u8)] +pub enum ScrollDirection { /// The initial value. Auto, /// The direction along the block axis. This is the default value. @@ -195,12 +200,15 @@ pub enum Orientation { Vertical, } -impl Default for Orientation { +impl Default for ScrollDirection { fn default() -> Self { - Orientation::Auto + ScrollDirection::Auto } } +// Avoid name collision in cbindgen with StyleOrientation. +pub use self::ScrollDirection as Orientation; + /// Scroll-timeline offsets. We treat None as an empty vector. /// value: none | # /// @@ -211,7 +219,7 @@ pub struct ScrollOffsets(#[css(if_empty = "none", iterable)] Box<[ScrollTimeline impl Parse for ScrollOffsets { fn parse<'i, 't>( - context: &ParserContext, + _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { @@ -220,7 +228,7 @@ impl Parse for ScrollOffsets { Ok(ScrollOffsets( input - .parse_comma_separated(|i| ScrollTimelineOffset::parse(context, i))? + .parse_comma_separated(|i| ScrollTimelineOffset::parse(i))? .into_boxed_slice(), )) } @@ -230,14 +238,18 @@ impl Parse for ScrollOffsets { /// value: auto | | /// /// https://drafts.csswg.org/scroll-animations/#typedef-scroll-timeline-offset +// FIXME: Bug 1733260 may drop the entire @scroll-timeline, and now we don't support +// other than the default value (so use #[css(skip)]). #[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)] pub enum ScrollTimelineOffset { /// The initial value. A container-based offset. Auto, /// A container-based offset with the distance indicated by the value along source's scroll /// range in orientation. + #[css(skip)] LengthPercentage(LengthPercentage), /// An element-based offset. + #[css(skip)] ElementOffset(ElementOffset), } @@ -312,7 +324,6 @@ impl ToCss for ScrollTimelineSelector { where W: Write, { - use crate::cssparser::ToCss as CssparserToCss; dest.write_str("selector(")?; dest.write_char('#')?; self.0.to_css(dest)?; diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index 1b6b59bac78..9d3aaedeff5 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -16,7 +16,6 @@ use crate::stylesheets::{CssRule, CssRules, Origin, UrlExtraData}; use crate::use_counters::UseCounters; use crate::{Namespace, Prefix}; use cssparser::{Parser, ParserInput, RuleListParser}; -use fallible::FallibleVec; use fxhash::FxHashMap; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; @@ -363,7 +362,8 @@ impl SanitizationKind { CssRule::Import(..) | // TODO(emilio): Perhaps Layer should not be always sanitized? But // we sanitize @media and co, so this seems safer for now. - CssRule::Layer(..) => false, + CssRule::LayerStatement(..) | + CssRule::LayerBlock(..) => false, CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true, @@ -505,9 +505,10 @@ impl Stylesheet { // Use a fallible push here, and if it fails, just fall // out of the loop. This will cause the page to be // shown incorrectly, but it's better than OOMing. - if rules.try_push(rule).is_err() { + if rules.try_reserve(1).is_err() { break; } + rules.push(rule); }, Err((error, slice)) => { let location = error.location; @@ -591,9 +592,11 @@ impl Clone for Stylesheet { // Make a deep clone of the media, using the new lock. let media = self.media.read_with(&guard).clone(); let media = Arc::new(lock.wrap(media)); - let contents = Arc::new(self - .contents - .deep_clone_with_lock(&lock, &guard, &DeepCloneParams)); + let contents = Arc::new(self.contents.deep_clone_with_lock( + &lock, + &guard, + &DeepCloneParams, + )); Stylesheet { contents, diff --git a/components/style/stylesheets/supports_rule.rs b/components/style/stylesheets/supports_rule.rs index d0aaa0a03d8..23d79fd1cc2 100644 --- a/components/style/stylesheets/supports_rule.rs +++ b/components/style/stylesheets/supports_rule.rs @@ -335,7 +335,7 @@ impl RawSelector { let parser = SelectorParser { namespaces, stylesheet_origin: context.stylesheet_origin, - url_data: Some(context.url_data), + url_data: context.url_data, }; #[allow(unused_variables)] diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 0663ea440d9..a1ad4aab659 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -4,14 +4,18 @@ //! Selector matching. -use crate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList}; +use crate::applicable_declarations::{ + ApplicableDeclarationBlock, ApplicableDeclarationList, CascadePriority, +}; use crate::context::{CascadeInputs, QuirksMode}; use crate::dom::{TElement, TShadowRoot}; use crate::element_state::{DocumentState, ElementState}; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion}; use crate::invalidation::element::invalidation_map::InvalidationMap; -use crate::invalidation::media_queries::{EffectiveMediaQueryResults, MediaListKey, ToMediaListKey}; +use crate::invalidation::media_queries::{ + EffectiveMediaQueryResults, MediaListKey, ToMediaListKey, +}; use crate::invalidation::stylesheets::RuleChangeKind; use crate::media_queries::Device; use crate::properties::{self, CascadeMode, ComputedValues}; @@ -25,19 +29,23 @@ use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; use crate::stylesheets::keyframes_rule::KeyframesAnimation; -use crate::stylesheets::layer_rule::{LayerName, LayerId, LayerOrder}; +use crate::stylesheets::layer_rule::{LayerId, LayerName, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; -use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents}; #[cfg(feature = "gecko")] -use crate::stylesheets::{CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule}; -use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, PerOriginIter, EffectiveRulesIterator}; +use crate::stylesheets::{ + CounterStyleRule, FontFaceRule, FontFeatureValuesRule, ScrollTimelineRule, +}; +use crate::stylesheets::{ + CssRule, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, PerOriginIter, +}; +use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument}; use crate::thread_state::{self, ThreadState}; -use crate::{Atom, LocalName, Namespace, WeakAtom}; -use fallible::FallibleVec; -use hashglobe::FailedAllocationError; -use malloc_size_of::MallocSizeOf; +use crate::AllocErr; +use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; +use fxhash::FxHashMap; +use malloc_size_of::{MallocSizeOf, MallocShallowSizeOf, MallocSizeOfOps}; #[cfg(feature = "gecko")] -use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; +use malloc_size_of::MallocUnconditionalShallowSizeOf; use selectors::attr::{CaseSensitivity, NamespaceConstraint}; use selectors::bloom::BloomFilter; use selectors::matching::VisitedHandlingMode; @@ -48,11 +56,11 @@ use selectors::NthIndexCache; use servo_arc::{Arc, ArcBorrow}; use smallbitvec::SmallBitVec; use smallvec::SmallVec; +use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; use std::sync::Mutex; use std::{mem, ops}; -use std::hash::{Hash, Hasher}; use style_traits::viewport::ViewportConstraints; -use fxhash::FxHashMap; /// The type of the stylesheets that the stylist contains. #[cfg(feature = "servo")] @@ -93,7 +101,7 @@ struct CascadeDataCacheKey { unsafe impl Send for CascadeDataCacheKey {} unsafe impl Sync for CascadeDataCacheKey {} -trait CascadeDataCacheEntry : Sized { +trait CascadeDataCacheEntry: Sized { /// Returns a reference to the cascade data. fn cascade_data(&self) -> &CascadeData; /// Rebuilds the cascade data for the new stylesheet collection. The @@ -104,7 +112,7 @@ trait CascadeDataCacheEntry : Sized { collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, old_entry: &Self, - ) -> Result, FailedAllocationError> + ) -> Result, AllocErr> where S: StylesheetInDocument + PartialEq + 'static; /// Measures heap memory usage. @@ -121,7 +129,9 @@ where Entry: CascadeDataCacheEntry, { fn new() -> Self { - Self { entries: Default::default() } + Self { + entries: Default::default(), + } } fn len(&self) -> usize { @@ -139,7 +149,7 @@ where collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, old_entry: &Entry, - ) -> Result>, FailedAllocationError> + ) -> Result>, AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { @@ -165,15 +175,9 @@ where match self.entries.entry(key) { HashMapEntry::Vacant(e) => { debug!("> Picking the slow path (not in the cache)"); - new_entry = Entry::rebuild( - device, - quirks_mode, - collection, - guard, - old_entry, - )?; + new_entry = Entry::rebuild(device, quirks_mode, collection, guard, old_entry)?; e.insert(new_entry.clone()); - } + }, HashMapEntry::Occupied(mut e) => { // Avoid reusing our old entry (this can happen if we get // invalidated due to CSSOM mutations and our old stylesheet @@ -192,15 +196,9 @@ where } debug!("> Picking the slow path due to same entry as old"); - new_entry = Entry::rebuild( - device, - quirks_mode, - collection, - guard, - old_entry, - )?; + new_entry = Entry::rebuild(device, quirks_mode, collection, guard, old_entry)?; e.insert(new_entry.clone()); - } + }, } Ok(Some(new_entry)) @@ -270,9 +268,9 @@ impl CascadeDataCacheEntry for UserAgentCascadeData { collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, _old: &Self, - ) -> Result, FailedAllocationError> + ) -> Result, AllocErr> where - S: StylesheetInDocument + PartialEq + 'static + S: StylesheetInDocument + PartialEq + 'static, { // TODO: Maybe we should support incremental rebuilds, though they seem // uncommon and rebuild() doesn't deal with @@ -293,7 +291,7 @@ impl CascadeDataCacheEntry for UserAgentCascadeData { )?; } - new_data.cascade_data.compute_layer_order(); + new_data.cascade_data.did_finish_rebuild(); Ok(Arc::new(new_data)) } @@ -386,7 +384,7 @@ impl DocumentCascadeData { quirks_mode: QuirksMode, mut flusher: DocumentStylesheetFlusher<'a, S>, guards: &StylesheetGuards, - ) -> Result<(), FailedAllocationError> + ) -> Result<(), AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { @@ -600,17 +598,12 @@ impl Stylist { old_data: &CascadeData, collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, - ) -> Result>, FailedAllocationError> + ) -> Result>, AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { - self.author_data_cache.lookup( - &self.device, - self.quirks_mode, - collection, - guard, - old_data, - ) + self.author_data_cache + .lookup(&self.device, self.quirks_mode, collection, guard, old_data) } /// Iterate over the extra data in origin order. @@ -1483,9 +1476,15 @@ impl Stylist { /* pseudo = */ None, self.rule_tree.root(), guards, - block - .declaration_importance_iter() - .map(|(declaration, _)| (declaration, Origin::Author)), + block.declaration_importance_iter().map(|(declaration, _)| { + ( + declaration, + CascadePriority::new( + CascadeLevel::same_tree_author_normal(), + LayerOrder::root(), + ), + ) + }), Some(parent_style), Some(parent_style), Some(parent_style), @@ -1533,6 +1532,133 @@ impl Stylist { } } +/// A vector that is sorted in layer order. +#[derive(Clone, Debug, Deref, MallocSizeOf)] +pub struct LayerOrderedVec(Vec<(T, LayerId)>); +impl Default for LayerOrderedVec { + fn default() -> Self { + Self(Default::default()) + } +} + +/// A map that is sorted in layer order. +#[derive(Clone, Debug, Deref, MallocSizeOf)] +pub struct LayerOrderedMap(PrecomputedHashMap>); +impl Default for LayerOrderedMap { + fn default() -> Self { + Self(Default::default()) + } +} + +#[cfg(feature = "gecko")] +impl LayerOrderedVec { + fn clear(&mut self) { + self.0.clear(); + } + fn push(&mut self, v: T, id: LayerId) { + self.0.push((v, id)); + } + fn sort(&mut self, layers: &[CascadeLayer]) { + self.0 + .sort_by_key(|&(_, ref id)| layers[id.0 as usize].order) + } +} + +impl LayerOrderedMap { + fn clear(&mut self) { + self.0.clear(); + } + #[cfg(feature = "gecko")] + fn try_insert(&mut self, name: Atom, v: T, id: LayerId) -> Result<(), AllocErr> { + self.try_insert_with(name, v, id, |_, _| Ordering::Equal) + } + fn try_insert_with( + &mut self, + name: Atom, + v: T, + id: LayerId, + cmp: impl Fn(&T, &T) -> Ordering, + ) -> Result<(), AllocErr> { + self.0.try_reserve(1)?; + let vec = self.0.entry(name).or_default(); + if let Some(&mut (ref mut val, ref last_id)) = vec.last_mut() { + if *last_id == id { + if cmp(&val, &v) != Ordering::Greater { + *val = v; + } + return Ok(()); + } + } + vec.push((v, id)); + Ok(()) + } + #[cfg(feature = "gecko")] + fn sort(&mut self, layers: &[CascadeLayer]) { + self.sort_with(layers, |_, _| Ordering::Equal) + } + fn sort_with(&mut self, layers: &[CascadeLayer], cmp: impl Fn(&T, &T) -> Ordering) { + for (_, v) in self.0.iter_mut() { + v.sort_by(|&(ref v1, ref id1), &(ref v2, ref id2)| { + let order1 = layers[id1.0 as usize].order; + let order2 = layers[id2.0 as usize].order; + order1.cmp(&order2).then_with(|| cmp(v1, v2)) + }) + } + } + /// Get an entry on the LayerOrderedMap by name. + pub fn get(&self, name: &Atom) -> Option<&T> { + let vec = self.0.get(name)?; + Some(&vec.last()?.0) + } +} + +/// Wrapper to allow better tracking of memory usage by page rule lists. +/// +/// This includes the layer ID for use with the named page table. +#[derive(Clone, Debug, MallocSizeOf)] +pub struct PageRuleData { + /// Layer ID for sorting page rules after matching. + pub layer: LayerId, + /// Page rule + #[ignore_malloc_size_of = "Arc, stylesheet measures as primary ref"] + pub rule: Arc>, +} + +/// Wrapper to allow better tracking of memory usage by page rule lists. +/// +/// This is meant to be used by the global page rule list which are already +/// sorted by layer ID, since all global page rules are less specific than all +/// named page rules that match a certain page. +#[derive(Clone, Debug, Deref, MallocSizeOf)] +pub struct PageRuleDataNoLayer( + #[ignore_malloc_size_of = "Arc, stylesheet measures as primary ref"] + pub Arc>, +); + +/// Stores page rules indexed by page names. +#[derive(Clone, Debug, Default, MallocSizeOf)] +pub struct PageRuleMap { + /// Global, unnamed page rules. + pub global: LayerOrderedVec, + /// Named page rules + pub named: PrecomputedHashMap>, +} + +#[cfg(feature = "gecko")] +impl PageRuleMap { + #[inline] + fn clear(&mut self) { + self.global.clear(); + self.named.clear(); + } +} + +impl MallocShallowSizeOf for PageRuleMap { + fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.global.size_of(ops) + self.named.shallow_size_of(ops) + } +} + /// This struct holds data which users of Stylist may want to extract /// from stylesheets which can be done at the same time as updating. #[derive(Clone, Debug, Default)] @@ -1540,31 +1666,39 @@ impl Stylist { pub struct ExtraStyleData { /// A list of effective font-face rules and their origin. #[cfg(feature = "gecko")] - pub font_faces: Vec>>, + pub font_faces: LayerOrderedVec>>, /// A list of effective font-feature-values rules. #[cfg(feature = "gecko")] - pub font_feature_values: Vec>>, + pub font_feature_values: LayerOrderedVec>>, /// A map of effective counter-style rules. #[cfg(feature = "gecko")] - pub counter_styles: PrecomputedHashMap>>, + pub counter_styles: LayerOrderedMap>>, /// A map of effective page rules. #[cfg(feature = "gecko")] - pub pages: Vec>>, + pub pages: PageRuleMap, + + /// A map of effective scroll-timeline rules. + #[cfg(feature = "gecko")] + pub scroll_timelines: LayerOrderedMap>>, } #[cfg(feature = "gecko")] impl ExtraStyleData { /// Add the given @font-face rule. - fn add_font_face(&mut self, rule: &Arc>) { - self.font_faces.push(rule.clone()); + fn add_font_face(&mut self, rule: &Arc>, layer: LayerId) { + self.font_faces.push(rule.clone(), layer); } /// Add the given @font-feature-values rule. - fn add_font_feature_values(&mut self, rule: &Arc>) { - self.font_feature_values.push(rule.clone()); + fn add_font_feature_values( + &mut self, + rule: &Arc>, + layer: LayerId, + ) { + self.font_feature_values.push(rule.clone(), layer); } /// Add the given @counter-style rule. @@ -1572,18 +1706,53 @@ impl ExtraStyleData { &mut self, guard: &SharedRwLockReadGuard, rule: &Arc>, - ) { + layer: LayerId, + ) -> Result<(), AllocErr> { let name = rule.read_with(guard).name().0.clone(); - self.counter_styles.insert(name, rule.clone()); + self.counter_styles.try_insert(name, rule.clone(), layer) } /// Add the given @page rule. - fn add_page(&mut self, rule: &Arc>) { - self.pages.push(rule.clone()); + fn add_page( + &mut self, + guard: &SharedRwLockReadGuard, + rule: &Arc>, + layer: LayerId, + ) -> Result<(), AllocErr> { + let page_rule = rule.read_with(guard); + if page_rule.selectors.0.is_empty() { + self.pages.global.push(PageRuleDataNoLayer(rule.clone()), layer); + } else { + // TODO: Handle pseudo-classes + self.pages.named.try_reserve(page_rule.selectors.0.len())?; + for name in page_rule.selectors.as_slice() { + let vec = self.pages.named.entry(name.0.0.clone()).or_default(); + vec.try_reserve(1)?; + vec.push(PageRuleData{layer, rule: rule.clone()}); + } + } + Ok(()) + } + + /// Add the given @scroll-timeline rule. + fn add_scroll_timeline( + &mut self, + guard: &SharedRwLockReadGuard, + rule: &Arc>, + layer: LayerId, + ) -> Result<(), AllocErr> { + let name = rule.read_with(guard).name.as_atom().clone(); + self.scroll_timelines.try_insert(name, rule.clone(), layer) + } + + fn sort_by_layer(&mut self, layers: &[CascadeLayer]) { + self.font_faces.sort(layers); + self.font_feature_values.sort(layers); + self.counter_styles.sort(layers); + self.pages.global.sort(layers); + self.scroll_timelines.sort(layers); } -} -impl ExtraStyleData { fn clear(&mut self) { #[cfg(feature = "gecko")] { @@ -1591,10 +1760,23 @@ impl ExtraStyleData { self.font_feature_values.clear(); self.counter_styles.clear(); self.pages.clear(); + self.scroll_timelines.clear(); } } } +// Don't let a prefixed keyframes animation override +// a non-prefixed one. +fn compare_keyframes_in_same_layer(v1: &KeyframesAnimation, v2: &KeyframesAnimation) -> Ordering { + if v1.vendor_prefix.is_some() == v2.vendor_prefix.is_some() { + Ordering::Equal + } else if v2.vendor_prefix.is_some() { + Ordering::Greater + } else { + Ordering::Less + } +} + /// An iterator over the different ExtraStyleData. pub struct ExtraStyleDataIterator<'a>(DocumentCascadeDataIter<'a>); @@ -1615,6 +1797,7 @@ impl MallocSizeOf for ExtraStyleData { n += self.font_feature_values.shallow_size_of(ops); n += self.counter_styles.shallow_size_of(ops); n += self.pages.shallow_size_of(ops); + n += self.scroll_timelines.shallow_size_of(ops); n } } @@ -1859,6 +2042,15 @@ impl ElementAndPseudoRules { self.element_map.clear(); self.pseudos_map.clear(); } + + fn shrink_if_needed(&mut self) { + self.element_map.shrink_if_needed(); + for pseudo in self.pseudos_map.iter_mut() { + if let Some(ref mut pseudo) = pseudo { + pseudo.shrink_if_needed(); + } + } + } } impl PartElementAndPseudoRules { @@ -1949,7 +2141,7 @@ pub struct CascadeData { /// A map with all the animations at this `CascadeData`'s origin, indexed /// by name. - animations: PrecomputedHashMap, + animations: LayerOrderedMap, /// A map from cascade layer name to layer order. layer_id: FxHashMap, @@ -2015,7 +2207,7 @@ impl CascadeData { quirks_mode: QuirksMode, collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, - ) -> Result<(), FailedAllocationError> + ) -> Result<(), AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { @@ -2045,7 +2237,7 @@ impl CascadeData { result.is_ok() }); - self.compute_layer_order(); + self.did_finish_rebuild(); result } @@ -2113,8 +2305,33 @@ impl CascadeData { self.layers[id.0 as usize].order } + fn did_finish_rebuild(&mut self) { + self.shrink_maps_if_needed(); + self.compute_layer_order(); + } + + fn shrink_maps_if_needed(&mut self) { + self.normal_rules.shrink_if_needed(); + if let Some(ref mut host_rules) = self.host_rules { + host_rules.shrink_if_needed(); + } + if let Some(ref mut slotted_rules) = self.slotted_rules { + slotted_rules.shrink_if_needed(); + } + self.invalidation_map.shrink_if_needed(); + self.attribute_dependencies.shrink_if_needed(); + self.mapped_ids.shrink_if_needed(); + self.layer_id.shrink_if_needed(); + self.selectors_for_cache_revalidation.shrink_if_needed(); + + } + fn compute_layer_order(&mut self) { - debug_assert_ne!(self.layers.len(), 0, "There should be at least the root layer!"); + debug_assert_ne!( + self.layers.len(), + 0, + "There should be at least the root layer!" + ); if self.layers.len() == 1 { return; // Nothing to do } @@ -2131,7 +2348,10 @@ impl CascadeData { order: &mut LayerOrder, ) { for child in parent.children.iter() { - debug_assert!(parent.id < *child, "Children are always registered after parents"); + debug_assert!( + parent.id < *child, + "Children are always registered after parents" + ); let child_index = (child.0 - parent.id.0 - 1) as usize; let (first, remaining) = remaining_layers.split_at_mut(child_index + 1); let child = &mut first[child_index]; @@ -2143,6 +2363,12 @@ impl CascadeData { order.inc(); } } + #[cfg(feature = "gecko")] + { + self.extra_data.sort_by_layer(&self.layers); + } + self.animations + .sort_with(&self.layers, compare_keyframes_in_same_layer); } /// Collects all the applicable media query results into `results`. @@ -2203,7 +2429,7 @@ impl CascadeData { mut current_layer: &mut LayerName, current_layer_id: LayerId, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, - ) -> Result<(), FailedAllocationError> + ) -> Result<(), AllocErr> where S: StylesheetInDocument + 'static, { @@ -2289,12 +2515,14 @@ impl CascadeData { // We choose the last one quite arbitrarily, // expecting it's slightly more likely to be more // specific. - self.part_rules + let map = self + .part_rules .get_or_insert_with(|| Box::new(Default::default())) - .for_insertion(pseudo_element) - .try_entry(parts.last().unwrap().clone().0)? - .or_insert_with(SmallVec::new) - .try_push(rule)?; + .for_insertion(pseudo_element); + map.try_reserve(1)?; + let vec = map.entry(parts.last().unwrap().clone().0).or_default(); + vec.try_reserve(1)?; + vec.push(rule); } else { // NOTE(emilio): It's fine to look at :host and then at // ::slotted(..), since :host::slotted(..) could never @@ -2316,65 +2544,45 @@ impl CascadeData { self.rules_source_order += 1; }, CssRule::Keyframes(ref keyframes_rule) => { - #[cfg(feature = "gecko")] - use hashglobe::hash_map::Entry; - #[cfg(feature = "servo")] - use hashglobe::fake::Entry; - - let keyframes_rule = keyframes_rule.read_with(guard); debug!("Found valid keyframes rule: {:?}", *keyframes_rule); - match self.animations.try_entry(keyframes_rule.name.as_atom().clone())? { - Entry::Vacant(e) => { - e.insert(KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, - keyframes_rule.vendor_prefix.clone(), - current_layer_id, - guard, - )); - }, - Entry::Occupied(mut e) => { - // Don't let a prefixed keyframes animation override - // a non-prefixed one. - // - // TODO(emilio): This will need to be harder for - // layers. - let needs_insert = - keyframes_rule.vendor_prefix.is_none() || - e.get().vendor_prefix.is_some(); - if needs_insert { - e.insert(KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, - keyframes_rule.vendor_prefix.clone(), - current_layer_id, - guard, - )); - } - }, - } + let keyframes_rule = keyframes_rule.read_with(guard); + let name = keyframes_rule.name.as_atom().clone(); + let animation = KeyframesAnimation::from_keyframes( + &keyframes_rule.keyframes, + keyframes_rule.vendor_prefix.clone(), + guard, + ); + self.animations.try_insert_with( + name, + animation, + current_layer_id, + compare_keyframes_in_same_layer, + )?; }, #[cfg(feature = "gecko")] - CssRule::ScrollTimeline(..) => { - // TODO: Bug 1676791: set the timeline into animation. - // https://phabricator.services.mozilla.com/D126452 - // + CssRule::ScrollTimeline(ref rule) => { // Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue // https://github.com/w3c/csswg-drafts/issues/6674 gets landed. + self.extra_data + .add_scroll_timeline(guard, rule, current_layer_id)?; }, #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { - self.extra_data.add_font_face(rule); + self.extra_data.add_font_face(rule, current_layer_id); }, #[cfg(feature = "gecko")] CssRule::FontFeatureValues(ref rule) => { - self.extra_data.add_font_feature_values(rule); + self.extra_data + .add_font_feature_values(rule, current_layer_id); }, #[cfg(feature = "gecko")] CssRule::CounterStyle(ref rule) => { - self.extra_data.add_counter_style(guard, rule); + self.extra_data + .add_counter_style(guard, rule, current_layer_id)?; }, #[cfg(feature = "gecko")] CssRule::Page(ref rule) => { - self.extra_data.add_page(rule); + self.extra_data.add_page(guard, rule, current_layer_id)?; }, CssRule::Viewport(..) => {}, _ => { @@ -2401,13 +2609,8 @@ impl CascadeData { } let mut effective = false; - let children = EffectiveRulesIterator::children( - rule, - device, - quirks_mode, - guard, - &mut effective, - ); + let children = + EffectiveRulesIterator::children(rule, device, quirks_mode, guard, &mut effective); if !effective { continue; @@ -2426,7 +2629,8 @@ impl CascadeData { let mut parent = layer.clone(); parent.0.pop(); - *data.layer_id + *data + .layer_id .get_mut(&parent) .expect("Parent layers should be registered before child layers") } else { @@ -2489,7 +2693,6 @@ impl CascadeData { &mut layer_names_to_pop, ); } - }, CssRule::Media(ref lock) => { if rebuild_kind.should_rebuild_invalidation() { @@ -2497,34 +2700,24 @@ impl CascadeData { self.effective_media_query_results.saw_effective(media_rule); } }, - CssRule::Layer(ref lock) => { - use crate::stylesheets::layer_rule::LayerRuleKind; - + CssRule::LayerBlock(ref lock) => { let layer_rule = lock.read_with(guard); - match layer_rule.kind { - LayerRuleKind::Block { ref name, .. } => { - children_layer_id = maybe_register_layers( - self, - name.as_ref(), - &mut current_layer, - &mut layer_names_to_pop, - ); - } - LayerRuleKind::Statement { ref names } => { - for name in &**names { - let mut pushed = 0; - // There are no children, so we can ignore the - // return value. - maybe_register_layers( - self, - Some(name), - &mut current_layer, - &mut pushed, - ); - for _ in 0..pushed { - current_layer.0.pop(); - } - } + children_layer_id = maybe_register_layers( + self, + layer_rule.name.as_ref(), + &mut current_layer, + &mut layer_names_to_pop, + ); + }, + CssRule::LayerStatement(ref lock) => { + let layer_rule = lock.read_with(guard); + for name in &*layer_rule.names { + let mut pushed = 0; + // There are no children, so we can ignore the + // return value. + maybe_register_layers(self, Some(name), &mut current_layer, &mut pushed); + for _ in 0..pushed { + current_layer.0.pop(); } } }, @@ -2563,7 +2756,7 @@ impl CascadeData { guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, - ) -> Result<(), FailedAllocationError> + ) -> Result<(), AllocErr> where S: StylesheetInDocument + 'static, { @@ -2609,7 +2802,9 @@ impl CascadeData { let effective_now = stylesheet.is_effective_for_device(device, guard); - let effective_then = self.effective_media_query_results.was_effective(stylesheet.contents()); + let effective_then = self + .effective_media_query_results + .was_effective(stylesheet.contents()); if effective_now != effective_then { debug!( @@ -2639,7 +2834,8 @@ impl CascadeData { CssRule::Page(..) | CssRule::Viewport(..) | CssRule::Document(..) | - CssRule::Layer(..) | + CssRule::LayerBlock(..) | + CssRule::LayerStatement(..) | CssRule::FontFeatureValues(..) => { // Not affected by device changes. continue; @@ -2708,7 +2904,10 @@ impl CascadeData { self.layer_id.clear(); self.layers.clear(); self.layers.push(CascadeLayer::root()); - self.extra_data.clear(); + #[cfg(feature = "gecko")] + { + self.extra_data.clear(); + } self.rules_source_order = 0; self.num_selectors = 0; self.num_declarations = 0; @@ -2737,9 +2936,9 @@ impl CascadeDataCacheEntry for CascadeData { collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, old: &Self, - ) -> Result, FailedAllocationError> + ) -> Result, AllocErr> where - S: StylesheetInDocument + PartialEq + 'static + S: StylesheetInDocument + PartialEq + 'static, { debug_assert!(collection.dirty(), "We surely need to do something?"); // If we're doing a full rebuild anyways, don't bother cloning the data. diff --git a/components/style/values/animated/transform.rs b/components/style/values/animated/transform.rs index 598bf9b59a7..8600281ce5b 100644 --- a/components/style/values/animated/transform.rs +++ b/components/style/values/animated/transform.rs @@ -891,25 +891,8 @@ impl Animate for ComputedTransform { match (this_remainder, other_remainder) { // If there is a remainder from *both* lists we must have had mismatched functions. // => Add the remainders to a suitable ___Matrix function. - (Some(this_remainder), Some(other_remainder)) => match procedure { - Procedure::Add => { - debug_assert!(false, "Should have already dealt with add by the point"); - return Err(()); - }, - Procedure::Interpolate { progress } => { - result.push(TransformOperation::InterpolateMatrix { - from_list: Transform(this_remainder.to_vec().into()), - to_list: Transform(other_remainder.to_vec().into()), - progress: Percentage(progress as f32), - }); - }, - Procedure::Accumulate { count } => { - result.push(TransformOperation::AccumulateMatrix { - from_list: Transform(this_remainder.to_vec().into()), - to_list: Transform(other_remainder.to_vec().into()), - count: cmp::min(count, i32::max_value() as u64) as i32, - }); - }, + (Some(this_remainder), Some(other_remainder)) => { + result.push(TransformOperation::animate_mismatched_transforms(this_remainder, other_remainder, procedure)?); }, // If there is a remainder from just one list, then one list must be shorter but // completely match the type of the corresponding functions in the longer list. @@ -923,36 +906,19 @@ impl Animate for ComputedTransform { let identity = transform.to_animated_zero().unwrap(); match transform { - // We can't interpolate/accumulate ___Matrix types directly with a - // matrix. Instead we need to wrap it in another ___Matrix type. TransformOperation::AccumulateMatrix { .. } | TransformOperation::InterpolateMatrix { .. } => { - let transform_list = Transform(vec![transform.clone()].into()); - let identity_list = Transform(vec![identity].into()); - let (from_list, to_list) = if fill_right { - (transform_list, identity_list) + let (from, to) = if fill_right { + (transform, &identity) } else { - (identity_list, transform_list) + (&identity, transform) }; - match procedure { - Procedure::Add => Err(()), - Procedure::Interpolate { progress } => { - Ok(TransformOperation::InterpolateMatrix { - from_list, - to_list, - progress: Percentage(progress as f32), - }) - }, - Procedure::Accumulate { count } => { - Ok(TransformOperation::AccumulateMatrix { - from_list, - to_list, - count: cmp::min(count, i32::max_value() as u64) - as i32, - }) - }, - } + TransformOperation::animate_mismatched_transforms( + &[from.clone()], + &[to.clone()], + procedure, + ) }, _ => { let (lhs, rhs) = if fill_right { @@ -981,9 +947,13 @@ impl ComputeSquaredDistance for ComputedTransform { // Roll back to matrix interpolation if there is any Err(()) in the // transform lists, such as mismatched transform functions. + // + // FIXME: Using a zero size here seems a bit sketchy but matches the + // previous behavior. if squared_dist.is_err() { - let matrix1: Matrix3D = self.to_transform_3d_matrix(None)?.0.into(); - let matrix2: Matrix3D = other.to_transform_3d_matrix(None)?.0.into(); + let rect = euclid::Rect::zero(); + let matrix1: Matrix3D = self.to_transform_3d_matrix(Some(&rect))?.0.into(); + let matrix2: Matrix3D = other.to_transform_3d_matrix(Some(&rect))?.0.into(); return matrix1.compute_squared_distance(&matrix2); } @@ -1122,7 +1092,7 @@ impl Animate for ComputedTransformOperation { 1. } else { -1. / perspective_z - } + }, )) }; Ok(TransformOperation::Perspective(used_value)) @@ -1141,6 +1111,52 @@ impl Animate for ComputedTransformOperation { } } +impl ComputedTransformOperation { + /// If there are no size dependencies, we try to animate in-place, to avoid + /// creating deeply nested Interpolate* operations. + fn try_animate_mismatched_transforms_in_place( + left: &[Self], + right: &[Self], + procedure: Procedure, + ) -> Result { + let (left, _left_3d) = Transform::components_to_transform_3d_matrix(left, None)?; + let (right, _right_3d) = Transform::components_to_transform_3d_matrix(right, None)?; + ComputedTransformOperation::Matrix3D(left.into()).animate(&ComputedTransformOperation::Matrix3D(right.into()), procedure) + } + + fn animate_mismatched_transforms( + left: &[Self], + right: &[Self], + procedure: Procedure, + ) -> Result { + if let Ok(op) = Self::try_animate_mismatched_transforms_in_place(left, right, procedure) { + return Ok(op); + } + let from_list = Transform(left.to_vec().into()); + let to_list = Transform(right.to_vec().into()); + Ok(match procedure { + Procedure::Add => { + debug_assert!(false, "Addition should've been handled earlier"); + return Err(()) + }, + Procedure::Interpolate { progress } => { + Self::InterpolateMatrix { + from_list, + to_list, + progress: Percentage(progress as f32), + } + } + Procedure::Accumulate { count } => { + Self::AccumulateMatrix { + from_list, + to_list, + count: cmp::min(count, i32::max_value() as u64) as i32, + } + } + }) + } +} + // This might not be the most useful definition of distance. It might be better, for example, // to trace the distance travelled by a point as its transform is interpolated between the two // lists. That, however, proves to be quite complicated so we take a simple approach for now. @@ -1204,10 +1220,9 @@ impl ComputeSquaredDistance for ComputedTransformOperation { ( &TransformOperation::Perspective(ref fd), &TransformOperation::Perspective(ref td), - ) => { - fd.infinity_or(|l| l.px()) - .compute_squared_distance(&td.infinity_or(|l| l.px())) - }, + ) => fd + .infinity_or(|l| l.px()) + .compute_squared_distance(&td.infinity_or(|l| l.px())), (&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m)) | (&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => { // FIXME(emilio): Is this right? Why interpolating this with diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index ce467343005..dd1e4900672 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -13,9 +13,9 @@ use crate::values::specified::box_ as specified; pub use crate::values::specified::box_::{ AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin, - Clear as SpecifiedClear, Contain, Display, Float as SpecifiedFloat, Overflow, OverflowAnchor, - OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, - ScrollSnapType, TouchAction, TransitionProperty, WillChange, + Clear as SpecifiedClear, Contain, ContentVisibility, Display, Float as SpecifiedFloat, Overflow, + OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, + ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, TransitionProperty, WillChange, }; /// A computed value for the `vertical-align` property. diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index 36cd1db4a42..7610bfbba3b 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -6,12 +6,12 @@ use crate::values::animated::color::RGBA as AnimatedRGBA; use crate::values::animated::ToAnimatedValue; -use crate::values::generics::color::{GenericColor, GenericColorOrAuto, GenericCaretColor}; +use crate::values::generics::color::{GenericCaretColor, GenericColor, GenericColorOrAuto}; use cssparser::{Color as CSSParserColor, RGBA}; use std::fmt; use style_traits::{CssWriter, ToCss}; -pub use crate::values::specified::color::ColorScheme; +pub use crate::values::specified::color::{ColorScheme, PrintColorAdjust}; /// The computed value of the `color` property. pub type ColorPropertyValue = RGBA; diff --git a/components/style/values/computed/counters.rs b/components/style/values/computed/counters.rs index 1ae46c772ab..fd5e915c4a8 100644 --- a/components/style/values/computed/counters.rs +++ b/components/style/values/computed/counters.rs @@ -7,13 +7,17 @@ use crate::values::computed::image::Image; use crate::values::generics::counters as generics; use crate::values::generics::counters::CounterIncrement as GenericCounterIncrement; -use crate::values::generics::counters::CounterSetOrReset as GenericCounterSetOrReset; +use crate::values::generics::counters::CounterReset as GenericCounterReset; +use crate::values::generics::counters::CounterSet as GenericCounterSet; /// A computed value for the `counter-increment` property. pub type CounterIncrement = GenericCounterIncrement; -/// A computed value for the `counter-set` and `counter-reset` properties. -pub type CounterSetOrReset = GenericCounterSetOrReset; +/// A computed value for the `counter-reset` property. +pub type CounterReset = GenericCounterReset; + +/// A computed value for the `counter-set` property. +pub type CounterSet = GenericCounterSet; /// A computed value for the `content` property. pub type Content = generics::GenericContent; diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs index 18fbe8c6fd8..275bbbc0eba 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -182,6 +182,9 @@ pub struct FontFamily { pub families: FontFamilyList, /// Whether this font-family came from a specified system-font. pub is_system_font: bool, + /// Whether this is the initial font-family that might react to language + /// changes. + pub is_initial: bool, } macro_rules! static_font_family { @@ -193,16 +196,14 @@ macro_rules! static_font_family { list: crate::ArcSlice::from_iter_leaked(std::iter::once($family)), #[cfg(feature = "servo")] list: Box::new([$family]), - #[cfg(feature = "gecko")] - fallback: GenericFontFamily::None, }, is_system_font: false, + is_initial: false, }; } }; } - impl FontFamily { #[inline] /// Get default font family as `serif` which is a generic font-family @@ -213,10 +214,13 @@ impl FontFamily { /// Returns the font family for `-moz-bullet-font`. #[cfg(feature = "gecko")] pub(crate) fn moz_bullet() -> &'static Self { - static_font_family!(MOZ_BULLET, SingleFontFamily::FamilyName(FamilyName { - name: atom!("-moz-bullet-font"), - syntax: FontFamilyNameSyntax::Identifiers, - })); + static_font_family!( + MOZ_BULLET, + SingleFontFamily::FamilyName(FamilyName { + name: atom!("-moz-bullet-font"), + syntax: FontFamilyNameSyntax::Identifiers, + }) + ); &*MOZ_BULLET } @@ -226,13 +230,15 @@ impl FontFamily { pub fn for_system_font(name: &str) -> Self { Self { families: FontFamilyList { - list: crate::ArcSlice::from_iter(std::iter::once(SingleFontFamily::FamilyName(FamilyName { - name: Atom::from(name), - syntax: FontFamilyNameSyntax::Identifiers, - }))), - fallback: GenericFontFamily::None, + list: crate::ArcSlice::from_iter(std::iter::once(SingleFontFamily::FamilyName( + FamilyName { + name: Atom::from(name), + syntax: FontFamilyNameSyntax::Identifiers, + }, + ))), }, is_system_font: true, + is_initial: false, } } @@ -240,8 +246,11 @@ impl FontFamily { pub fn generic(generic: GenericFontFamily) -> &'static Self { macro_rules! generic_font_family { ($ident:ident, $family:ident) => { - static_font_family!($ident, SingleFontFamily::Generic(GenericFontFamily::$family)) - } + static_font_family!( + $ident, + SingleFontFamily::Generic(GenericFontFamily::$family) + ) + }; } generic_font_family!(SERIF, Serif); @@ -257,7 +266,7 @@ impl FontFamily { GenericFontFamily::None => { debug_assert!(false, "Bogus caller!"); &*SERIF - } + }, GenericFontFamily::Serif => &*SERIF, GenericFontFamily::SansSerif => &*SANS_SERIF, GenericFontFamily::Monospace => &*MONOSPACE, @@ -296,7 +305,7 @@ impl ToCss for FontFamily { Some(f) => f.to_css(dest)?, None => { #[cfg(feature = "gecko")] - return self.families.fallback.to_css(dest); + return return Ok(()); #[cfg(feature = "servo")] unreachable!(); }, @@ -444,24 +453,21 @@ impl GenericFontFamily { /// families that the website might specify, since they're not configured by /// the user. See bug 789788 and bug 1730098. #[cfg(feature = "gecko")] - pub (crate) fn valid_for_user_font_prioritization(self) -> bool { + pub(crate) fn valid_for_user_font_prioritization(self) -> bool { match self { - Self::None | - Self::Fantasy | - Self::Cursive | - Self::SystemUi | - Self::MozEmoji => false, + Self::None | Self::Fantasy | Self::Cursive | Self::SystemUi | Self::MozEmoji => false, - Self::Serif | - Self::SansSerif | - Self::Monospace => true, + Self::Serif | Self::SansSerif | Self::Monospace => true, } } } impl Parse for SingleFontFamily { /// Parse a font-family value. - fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) { return Ok(SingleFontFamily::FamilyName(FamilyName { name: Atom::from(&*value), @@ -558,8 +564,6 @@ impl SingleFontFamily { pub struct FontFamilyList { /// The actual list of font families specified. pub list: crate::ArcSlice, - /// A fallback font type (none, serif, or sans-serif, generally). - pub fallback: GenericFontFamily, } /// A list of font families. @@ -588,28 +592,15 @@ impl FontFamilyList { self.list.iter() } - /// Puts the fallback in the list if needed. - #[cfg(feature = "gecko")] - pub fn normalize(&mut self) { - if self.fallback == GenericFontFamily::None { - return; - } - let mut new_list = self.list.iter().cloned().collect::>(); - new_list.push(SingleFontFamily::Generic(self.fallback)); - self.list = crate::ArcSlice::from_iter(new_list.into_iter()); - } - /// If there's a generic font family on the list which is suitable for user /// font prioritization, then move it to the front of the list. Otherwise, /// prepend the default generic. #[cfg(feature = "gecko")] - pub (crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) { - let index_of_first_generic = self.iter().position(|f| { - match *f { - SingleFontFamily::Generic(f) => f.valid_for_user_font_prioritization(), - _ => false, - } - }); + pub(crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) { + let index_of_first_generic = self.iter().position(|f| match *f { + SingleFontFamily::Generic(f) => f.valid_for_user_font_prioritization(), + _ => false, + }); if let Some(0) = index_of_first_generic { return; // Already first @@ -625,12 +616,21 @@ impl FontFamilyList { self.list = crate::ArcSlice::from_iter(new_list.into_iter()); } + /// Returns whether we need to prioritize user fonts. + #[cfg(feature = "gecko")] + pub(crate) fn needs_user_font_prioritization(&self) -> bool { + self.iter().next().map_or(true, |f| match f { + SingleFontFamily::Generic(f) => !f.valid_for_user_font_prioritization(), + _ => true, + }) + } + /// Return the generic ID if it is a single generic font pub fn single_generic(&self) -> Option { let mut iter = self.iter(); if let Some(SingleFontFamily::Generic(f)) = iter.next() { if iter.next().is_none() { - return Some(f.clone()); + return Some(*f); } } None diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index a0cb8f3fe45..980017b2822 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -84,7 +84,6 @@ impl ToComputedValue for specified::ImageSet { let mut selected_resolution = items[0].resolution.dppx(); for (i, item) in items.iter().enumerate() { - // If the MIME type is not supported, we discard the ImageSetItem if item.has_mime_type && !context.device().is_supported_mime_type(&item.mime_type) { continue; diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index f455da5f676..b8ff80587e6 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -37,7 +37,9 @@ impl ToComputedValue for specified::NoCalcLength { length.to_computed_value(context, FontBaseSize::CurrentStyle) }, specified::NoCalcLength::ViewportPercentage(length) => { - context.builder.add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS); + context + .builder + .add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS); length.to_computed_value(context.viewport_size_for_viewport_unit_resolution()) }, specified::NoCalcLength::ServoCharacterWidth(length) => { @@ -191,7 +193,7 @@ impl Size { GenericSize::MaxContent | GenericSize::FitContent | GenericSize::MozAvailable | - GenericSize::FitContentFunction(_) => false + GenericSize::FitContentFunction(_) => false, } } } diff --git a/components/style/values/computed/list.rs b/components/style/values/computed/list.rs index 2ae3776041b..3e5d1eb220d 100644 --- a/components/style/values/computed/list.rs +++ b/components/style/values/computed/list.rs @@ -6,7 +6,6 @@ #[cfg(feature = "gecko")] pub use crate::values::specified::list::ListStyleType; -pub use crate::values::specified::list::MozListReversed; pub use crate::values::specified::list::Quotes; impl Quotes { diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 955a1b93877..68194eb733a 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -45,14 +45,14 @@ pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderImageSlice, BorderImageWidth}; pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain}; -pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, Float}; +pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, ContentVisibility, Float}; pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty}; -pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; +pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, VerticalAlign, WillChange}; -pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; +pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; -pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset}; +pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; pub use self::easing::TimingFunction; pub use self::effects::{BoxShadow, Filter, SimpleShadow}; pub use self::flex::FlexBasis; @@ -62,18 +62,17 @@ pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis}; pub use self::font::{FontVariantAlternates, FontWeight}; pub use self::font::{FontVariantEastAsian, FontVariationSettings}; pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; -pub use self::image::{Gradient, Image, LineDirection, MozImageRect, ImageRendering}; +pub use self::image::{Gradient, Image, ImageRendering, LineDirection, MozImageRect}; pub use self::length::{CSSPixelLength, NonNegativeLength}; pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size}; pub use self::length::{NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto}; #[cfg(feature = "gecko")] pub use self::list::ListStyleType; -pub use self::list::MozListReversed; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; -pub use self::page::{Orientation, PageSize, PaperSize}; +pub use self::page::{Orientation, PageName, PageSize, PaperSize}; pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ @@ -85,6 +84,7 @@ pub use self::resolution::Resolution; pub use self::svg::{DProperty, MozContextProperties}; pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind}; pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; +pub use self::text::HyphenateCharacter; pub use self::text::TextUnderlinePosition; pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight}; pub use self::text::{OverflowWrap, RubyPosition, TextOverflow, WordBreak, WordSpacing}; diff --git a/components/style/values/computed/page.rs b/components/style/values/computed/page.rs index 27b16d0af15..080681e008f 100644 --- a/components/style/values/computed/page.rs +++ b/components/style/values/computed/page.rs @@ -2,7 +2,7 @@ * 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/. */ -//! Computed @page at-rule properties +//! Computed @page at-rule properties and named-page style properties use crate::values::computed::length::NonNegativeLength; use crate::values::computed::{Context, ToComputedValue}; @@ -13,6 +13,7 @@ use crate::values::specified::page as specified; pub use generics::page::GenericPageSize; pub use generics::page::Orientation; pub use generics::page::PaperSize; +pub use specified::PageName; /// Computed value of the @page size descriptor /// diff --git a/components/style/values/computed/text.rs b/components/style/values/computed/text.rs index 629626b06b3..c2b017f3973 100644 --- a/components/style/values/computed/text.rs +++ b/components/style/values/computed/text.rs @@ -18,7 +18,10 @@ use crate::Zero; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; -pub use crate::values::specified::text::{TextAlignLast, TextUnderlinePosition, MozControlCharacterVisibility}; +pub use crate::values::specified::text::{ + MozControlCharacterVisibility, TextAlignLast, TextUnderlinePosition, +}; +pub use crate::values::specified::HyphenateCharacter; pub use crate::values::specified::{LineBreak, OverflowWrap, RubyPosition, WordBreak}; pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition}; pub use crate::values::specified::{TextDecorationSkipInk, TextJustify, TextTransform}; diff --git a/components/style/values/computed/transform.rs b/components/style/values/computed/transform.rs index 5eafa0cd627..d70349ee0fe 100644 --- a/components/style/values/computed/transform.rs +++ b/components/style/values/computed/transform.rs @@ -520,7 +520,7 @@ impl ToAnimatedZero for TransformOperation { Ok(generic::TransformOperation::Rotate(Angle::zero())) }, generic::TransformOperation::Perspective(_) => Ok( - generic::TransformOperation::Perspective(generic::PerspectiveFunction::None) + generic::TransformOperation::Perspective(generic::PerspectiveFunction::None), ), generic::TransformOperation::AccumulateMatrix { .. } | generic::TransformOperation::InterpolateMatrix { .. } => { diff --git a/components/style/values/generics/calc.rs b/components/style/values/generics/calc.rs index d2bc2a85523..d9044bbb818 100644 --- a/components/style/values/generics/calc.rs +++ b/components/style/values/generics/calc.rs @@ -47,6 +47,7 @@ pub enum SortKey { Deg, Em, Ex, + Ic, Px, Rem, Sec, diff --git a/components/style/values/generics/counters.rs b/components/style/values/generics/counters.rs index 2a8f70c8f44..2d9948cef4a 100644 --- a/components/style/values/generics/counters.rs +++ b/components/style/values/generics/counters.rs @@ -11,7 +11,9 @@ use crate::values::generics::CounterStyle; #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] use crate::values::specified::Attr; use crate::values::CustomIdent; +use std::fmt::{self, Write}; use std::ops::Deref; +use style_traits::{CssWriter, ToCss}; /// A name / value pair for counters. #[derive( @@ -21,7 +23,6 @@ use std::ops::Deref; PartialEq, SpecifiedValueInfo, ToComputedValue, - ToCss, ToResolvedValue, ToShmem, )] @@ -31,9 +32,35 @@ pub struct GenericCounterPair { pub name: CustomIdent, /// The value of the counter / increment / etc. pub value: Integer, + /// If true, then this represents `reversed(name)`. + /// NOTE: It can only be true on `counter-reset` values. + pub is_reversed: bool, } pub use self::GenericCounterPair as CounterPair; +impl ToCss for CounterPair +where + Integer: ToCss + PartialEq, +{ + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if self.is_reversed { + dest.write_str("reversed(")?; + } + self.name.to_css(dest)?; + if self.is_reversed { + dest.write_str(")")?; + if self.value == i32::min_value() { + return Ok(()); + } + } + dest.write_str(" ")?; + self.value.to_css(dest) + } +} + /// A generic value for the `counter-increment` property. #[derive( Clone, @@ -48,7 +75,7 @@ pub use self::GenericCounterPair as CounterPair; ToShmem, )] #[repr(transparent)] -pub struct GenericCounterIncrement(pub GenericCounters); +pub struct GenericCounterIncrement(#[css(field_bound)] pub GenericCounters); pub use self::GenericCounterIncrement as CounterIncrement; impl CounterIncrement { @@ -68,7 +95,7 @@ impl Deref for CounterIncrement { } } -/// A generic value for the `counter-set` and `counter-reset` properties. +/// A generic value for the `counter-set` property. #[derive( Clone, Debug, @@ -82,18 +109,52 @@ impl Deref for CounterIncrement { ToShmem, )] #[repr(transparent)] -pub struct GenericCounterSetOrReset(pub GenericCounters); -pub use self::GenericCounterSetOrReset as CounterSetOrReset; +pub struct GenericCounterSet(#[css(field_bound)] pub GenericCounters); +pub use self::GenericCounterSet as CounterSet; -impl CounterSetOrReset { - /// Returns a new value for `counter-set` / `counter-reset`. +impl CounterSet { + /// Returns a new value for `counter-set`. #[inline] pub fn new(counters: Vec>) -> Self { - CounterSetOrReset(Counters(counters.into())) + CounterSet(Counters(counters.into())) } } -impl Deref for CounterSetOrReset { +impl Deref for CounterSet { + type Target = [CounterPair]; + + #[inline] + fn deref(&self) -> &Self::Target { + &(self.0).0 + } +} + +/// A generic value for the `counter-reset` property. +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct GenericCounterReset(#[css(field_bound)] pub GenericCounters); +pub use self::GenericCounterReset as CounterReset; + +impl CounterReset { + /// Returns a new value for `counter-reset`. + #[inline] + pub fn new(counters: Vec>) -> Self { + CounterReset(Counters(counters.into())) + } +} + +impl Deref for CounterReset { type Target = [CounterPair]; #[inline] @@ -119,7 +180,9 @@ impl Deref for CounterSetOrReset { )] #[repr(transparent)] pub struct GenericCounters( - #[css(iterable, if_empty = "none")] crate::OwnedSlice>, + #[css(field_bound)] + #[css(iterable, if_empty = "none")] + crate::OwnedSlice>, ); pub use self::GenericCounters as Counters; @@ -175,15 +238,7 @@ impl Content { /// Items for the `content` property. #[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, + Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum GenericContentItem { diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index 014ae46db02..fa53dd6ac8b 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -131,9 +131,7 @@ pub struct GenericImageSet { } /// An optional percent and a cross fade image. -#[derive( - Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, -)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(C)] pub struct GenericImageSetItem { /// ``. `` is converted to `Image::Url` at parse time. @@ -151,8 +149,7 @@ pub struct GenericImageSetItem { pub has_mime_type: bool, } -impl ToCss for GenericImageSetItem -{ +impl ToCss for GenericImageSetItem { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs index c0cdf04b71d..b15d4f01887 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -106,7 +106,7 @@ impl CounterStyle { name == &atom!("square") || name == &atom!("disclosure-closed") || name == &atom!("disclosure-open") - } + }, _ => false, } } diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 604872ba5dc..21c4b8dd95f 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -404,15 +404,7 @@ impl ToAbsoluteLength for ComputedLength { impl ToAbsoluteLength for ComputedLengthPercentage { #[inline] fn to_pixel_length(&self, containing_len: Option) -> Result { - match containing_len { - Some(relative_len) => Ok(self.resolve(relative_len).px()), - // If we don't have reference box, we cannot resolve the used value, - // so only retrieve the length part. This will be used for computing - // distance without any layout info. - // - // FIXME(emilio): This looks wrong. - None => Ok(self.resolve(Zero::zero()).px()), - } + Ok(self.maybe_percentage_relative_to(containing_len).ok_or(())?.px()) } } @@ -572,12 +564,21 @@ impl Transform { impl Transform { /// Return the equivalent 3d matrix of this transform list. + /// /// We return a pair: the first one is the transform matrix, and the second one /// indicates if there is any 3d transform function in this transform list. #[cfg_attr(rustfmt, rustfmt_skip)] pub fn to_transform_3d_matrix( &self, reference_box: Option<&Rect> + ) -> Result<(Transform3D, bool), ()> { + Self::components_to_transform_3d_matrix(&self.0, reference_box) + } + + /// Converts a series of components to a 3d matrix. + pub fn components_to_transform_3d_matrix( + ops: &[T], + reference_box: Option<&Rect> ) -> Result<(Transform3D, bool), ()> { let cast_3d_transform = |m: Transform3D| -> Transform3D { use std::{f32, f64}; @@ -590,26 +591,34 @@ impl Transform { ) }; - let (m, is_3d) = self.to_transform_3d_matrix_f64(reference_box)?; + let (m, is_3d) = Self::components_to_transform_3d_matrix_f64(ops, reference_box)?; Ok((cast_3d_transform(m), is_3d)) } /// Same as Transform::to_transform_3d_matrix but a f64 version. pub fn to_transform_3d_matrix_f64( &self, + reference_box: Option<&Rect> + ) -> Result<(Transform3D, bool), ()> { + Self::components_to_transform_3d_matrix_f64(&self.0, reference_box) + } + + fn components_to_transform_3d_matrix_f64( + ops: &[T], reference_box: Option<&Rect>, ) -> Result<(Transform3D, bool), ()> { - // We intentionally use Transform3D during computation to avoid error propagation - // because using f32 to compute triangle functions (e.g. in rotation()) is not - // accurate enough. In Gecko, we also use "double" to compute the triangle functions. - // Therefore, let's use Transform3D during matrix computation and cast it into f32 - // in the end. + // We intentionally use Transform3D during computation to avoid + // error propagation because using f32 to compute triangle functions + // (e.g. in rotation()) is not accurate enough. In Gecko, we also use + // "double" to compute the triangle functions. Therefore, let's use + // Transform3D during matrix computation and cast it into f32 in + // the end. let mut transform = Transform3D::::identity(); let mut contain_3d = false; - for operation in &*self.0 { + for operation in ops { let matrix = operation.to_3d_matrix(reference_box)?; - contain_3d |= operation.is_3d(); + contain_3d = contain_3d || operation.is_3d(); transform = matrix.then(&transform); } @@ -733,7 +742,7 @@ where dest.write_char(' ')?; z.to_css(dest)?; dest.write_char(' ')?; - } + }, } angle.to_css(dest) }, diff --git a/components/style/values/generics/ui.rs b/components/style/values/generics/ui.rs index ff6aefdc63e..4d9515199ad 100644 --- a/components/style/values/generics/ui.rs +++ b/components/style/values/generics/ui.rs @@ -56,15 +56,7 @@ impl ToCss for Cursor { } /// A generic value for item of `image cursors`. -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(C)] pub struct GenericCursorImage { /// The url to parse images from. diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index b8e7f1f3b8b..9d7b7d74acc 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -232,6 +232,16 @@ impl cssparser::ToCss for GenericAtomIdent style_traits::ToCss for GenericAtomIdent { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + serialize_atom_identifier(&self.0, dest) + } +} + #[cfg(feature = "servo")] impl PrecomputedHash for GenericAtomIdent { #[inline] @@ -293,6 +303,16 @@ impl cssparser::ToCss for AtomIdent { } } +#[cfg(feature = "gecko")] +impl style_traits::ToCss for AtomIdent { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + cssparser::ToCss::to_css(self, dest) + } +} + #[cfg(feature = "gecko")] impl PrecomputedHash for AtomIdent { #[inline] @@ -438,15 +458,22 @@ impl CustomIdent { ident: &CowRcStr<'i>, excluding: &[&str], ) -> Result> { - let valid = match_ignore_ascii_case! { ident, - "initial" | "inherit" | "unset" | "default" | "revert" => false, - _ => true - }; - if !valid { + use crate::properties::CSSWideKeyword; + // https://drafts.csswg.org/css-values-4/#custom-idents: + // + // The CSS-wide keywords are not valid s. The default + // keyword is reserved and is also not a valid . + // + if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") { return Err( location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) ); } + + // https://drafts.csswg.org/css-values-4/#custom-idents: + // + // Excluded keywords are excluded in all ASCII case permutations. + // if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) { Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } else { @@ -547,9 +574,7 @@ impl Parse for TimelineOrKeyframesName { s, &["none"], )?)), - Token::QuotedString(ref s) => { - Ok(Self::QuotedString(Atom::from(s.as_ref()))) - }, + Token::QuotedString(ref s) => Ok(Self::QuotedString(Atom::from(s.as_ref()))), ref t => Err(location.new_unexpected_token_error(t.clone())), } } diff --git a/components/style/values/resolved/color.rs b/components/style/values/resolved/color.rs index dbf6375e5bf..ea912945c5a 100644 --- a/components/style/values/resolved/color.rs +++ b/components/style/values/resolved/color.rs @@ -40,6 +40,8 @@ impl ToResolvedValue for computed::CaretColor { #[inline] fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - generics::CaretColor(generics::ColorOrAuto::Color(computed::Color::from_resolved_value(resolved))) + generics::CaretColor(generics::ColorOrAuto::Color( + computed::Color::from_resolved_value(resolved), + )) } } diff --git a/components/style/values/resolved/counters.rs b/components/style/values/resolved/counters.rs index cdaebcdea3e..e246dd567f9 100644 --- a/components/style/values/resolved/counters.rs +++ b/components/style/values/resolved/counters.rs @@ -31,19 +31,22 @@ impl ToResolvedValue for computed::Content { #[inline] fn to_resolved_value(self, context: &Context) -> Self { - let (is_pseudo, is_before_or_after, is_marker) = - match context.style.pseudo() { - Some(ref pseudo) => (true, pseudo.is_before_or_after(), pseudo.is_marker()), - None => (false, false, false) - }; + let (is_pseudo, is_before_or_after, is_marker) = match context.style.pseudo() { + Some(ref pseudo) => (true, pseudo.is_before_or_after(), pseudo.is_marker()), + None => (false, false, false), + }; match self { Self::Normal if is_before_or_after => Self::None, // For now, make `content: none` compute to `normal` for pseudos // other than ::before, ::after and ::marker, as we don't respect it. // https://github.com/w3c/csswg-drafts/issues/6124 // Ditto for non-pseudo elements if the pref is disabled. - Self::None if (is_pseudo && !is_before_or_after && !is_marker) || - (!is_pseudo && !allow_element_content_none()) => Self::Normal, + Self::None + if (is_pseudo && !is_before_or_after && !is_marker) || + (!is_pseudo && !allow_element_content_none()) => + { + Self::Normal + }, other => other, } } diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 7d5543cf7fc..e503e8a2f16 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -1450,6 +1450,34 @@ impl Parse for Contain { } } +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ContentVisibility { + /// `auto` variant, the element turns on layout containment, style containment, and paint + /// containment. In addition, if the element is not relevant to the user (such as by being + /// offscreen) it also skips its content + Auto, + /// `hidden` variant, the element skips its content + Hidden, + /// 'visible' variant, no effect + Visible, +} + /// A specified value for the `perspective` property. pub type Perspective = GenericPerspective; @@ -1658,9 +1686,6 @@ pub enum Appearance { ButtonArrowPrevious, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] ButtonArrowUp, - /// The focus outline box inside of a button. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ButtonFocus, /// A dual toolbar button (e.g., a Back button with a dropdown) #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] Dualbutton, @@ -1954,7 +1979,10 @@ impl BreakBetween { /// See https://drafts.csswg.org/css-break/#page-break-properties. #[cfg(feature = "gecko")] #[inline] - pub(crate) fn parse_legacy<'i>(_: &ParserContext, input: &mut Parser<'i, '_>) -> Result> { + pub(crate) fn parse_legacy<'i>( + _: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result> { let break_value = BreakBetween::parse(input)?; match break_value { BreakBetween::Always => Ok(BreakBetween::Page), @@ -2018,7 +2046,10 @@ impl BreakWithin { /// See https://drafts.csswg.org/css-break/#page-break-properties. #[cfg(feature = "gecko")] #[inline] - pub(crate) fn parse_legacy<'i>(_: &ParserContext, input: &mut Parser<'i, '_>) -> Result> { + pub(crate) fn parse_legacy<'i>( + _: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result> { let break_value = BreakWithin::parse(input)?; match break_value { BreakWithin::Auto | BreakWithin::Avoid => Ok(break_value), @@ -2052,7 +2083,6 @@ impl BreakWithin { Eq, Hash, MallocSizeOf, - Parse, PartialEq, SpecifiedValueInfo, ToCss, @@ -2067,10 +2097,28 @@ pub enum Overflow { Scroll, Auto, #[cfg(feature = "gecko")] - #[parse(aliases = "-moz-hidden-unscrollable")] Clip, } +// This can be derived once we remove or keep `-moz-hidden-unscrollable` +// indefinitely. +impl Parse for Overflow { + fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + Ok(try_match_ident_ignore_ascii_case! { input, + "visible" => Self::Visible, + "hidden" => Self::Hidden, + "scroll" => Self::Scroll, + "auto" => Self::Auto, + #[cfg(feature = "gecko")] + "clip" => Self::Clip, + #[cfg(feature = "gecko")] + "-moz-hidden-unscrollable" if static_prefs::pref!("layout.css.overflow-moz-hidden-unscrollable.enabled") => { + Overflow::Clip + }, + }) + } +} + impl Overflow { /// Return true if the value will create a scrollable box. #[inline] @@ -2089,3 +2137,73 @@ impl Overflow { } } } + +bitflags! { + #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] + #[value_info(other_values = "auto,stable,both-edges")] + #[repr(C)] + /// Values for scrollbar-gutter: + /// + pub struct ScrollbarGutter: u8 { + /// `auto` variant. Just for convenience if there is no flag set. + const AUTO = 0; + /// `stable` variant. + const STABLE = 1 << 0; + /// `both-edges` variant. + const BOTH_EDGES = 1 << 1; + } +} + +impl ToCss for ScrollbarGutter { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if self.is_empty() { + return dest.write_str("auto"); + } + + debug_assert!( + self.contains(ScrollbarGutter::STABLE), + "We failed to parse the syntax!" + ); + dest.write_str("stable")?; + if self.contains(ScrollbarGutter::BOTH_EDGES) { + dest.write_str(" both-edges")?; + } + + Ok(()) + } +} + +impl Parse for ScrollbarGutter { + /// auto | stable && both-edges? + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(ScrollbarGutter::AUTO); + } + + let mut result = ScrollbarGutter::empty(); + while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { + let flag = match_ignore_ascii_case! { &ident, + "stable" => Some(ScrollbarGutter::STABLE), + "both-edges" => Some(ScrollbarGutter::BOTH_EDGES), + _ => None + }; + + match flag { + Some(flag) if !result.contains(flag) => result.insert(flag), + _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + } + } + + if result.contains(ScrollbarGutter::STABLE) { + Ok(result) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } +} diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index a043e4ea96e..f5448591f21 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -185,6 +185,7 @@ impl generic::CalcNodeLeaf for Leaf { FontRelativeLength::Em(..) => SortKey::Em, FontRelativeLength::Ex(..) => SortKey::Ex, FontRelativeLength::Cap(..) => SortKey::Cap, + FontRelativeLength::Ic(..) => SortKey::Ic, FontRelativeLength::Rem(..) => SortKey::Rem, }, NoCalcLength::ViewportPercentage(ref vp) => match *vp { @@ -380,43 +381,47 @@ impl CalcNode { Ok(Self::MinMax(arguments.into(), op)) }, - MathFunction::Sin | - MathFunction::Cos | - MathFunction::Tan => { + MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => { let argument = Self::parse_argument(context, input, CalcUnit::Angle)?; let radians = match argument.to_number() { Ok(v) => v, Err(()) => match argument.to_angle() { Ok(angle) => angle.radians(), - Err(()) => return Err( - input.new_custom_error(StyleParseErrorKind::UnspecifiedError) - ), + Err(()) => { + return Err( + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + ) + }, }, }; let number = match function { MathFunction::Sin => radians.sin(), MathFunction::Cos => radians.cos(), MathFunction::Tan => radians.tan(), - _ => unsafe { debug_unreachable!("We just checked!"); }, + _ => unsafe { + debug_unreachable!("We just checked!"); + }, }; Ok(Self::Leaf(Leaf::Number(number))) }, - MathFunction::Asin | - MathFunction::Acos | - MathFunction::Atan => { + MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => { let argument = Self::parse_argument(context, input, CalcUnit::Number)?; let number = match argument.to_number() { Ok(v) => v, - Err(()) => return Err( - input.new_custom_error(StyleParseErrorKind::UnspecifiedError) - ), + Err(()) => { + return Err( + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + ) + }, }; let radians = match function { MathFunction::Asin => number.asin(), MathFunction::Acos => number.acos(), MathFunction::Atan => number.atan(), - _ => unsafe { debug_unreachable!("We just checked!"); }, + _ => unsafe { + debug_unreachable!("We just checked!"); + }, }; Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) @@ -597,7 +602,9 @@ impl CalcNode { let function = match MathFunction::from_ident(&*name) { Ok(f) => f, - Err(()) => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))), + Err(()) => { + return Err(location.new_unexpected_token_error(Token::Function(name.clone()))) + }, }; if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan) && !trig_enabled() { diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index fe22266ab23..4c0683dbce0 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -5,8 +5,6 @@ //! Specified color values. use super::AllowQuirks; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::structs::nscolor; use crate::parser::{Parse, ParserContext}; use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto}; @@ -232,9 +230,11 @@ pub enum Color { #[repr(u8)] pub enum SystemColor { #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TextSelectBackgroundDisabled, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TextSelectBackgroundAttention, + TextSelectDisabledBackground, + #[css(skip)] + TextSelectAttentionBackground, + #[css(skip)] + TextSelectAttentionForeground, #[css(skip)] TextHighlightBackground, #[css(skip)] @@ -453,19 +453,17 @@ pub enum SystemColor { impl SystemColor { #[inline] fn compute(&self, cx: &Context) -> ComputedColor { + use crate::gecko::values::convert_nscolor_to_rgba; use crate::gecko_bindings::bindings; // TODO: We should avoid cloning here most likely, though it's // cheap-ish. - let style_color_scheme = - cx.style().get_inherited_ui().clone_color_scheme(); - let color = unsafe { - bindings::Gecko_ComputeSystemColor(*self, cx.device().document(), &style_color_scheme) - }; + let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme(); + let color = cx.device().system_nscolor(*self, &style_color_scheme); if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { return ComputedColor::currentcolor(); } - convert_nscolor_to_computedcolor(color) + ComputedColor::rgba(convert_nscolor_to_rgba(color)) } } @@ -739,12 +737,6 @@ impl Color { } } -#[cfg(feature = "gecko")] -fn convert_nscolor_to_computedcolor(color: nscolor) -> ComputedColor { - use crate::gecko::values::convert_nscolor_to_rgba; - ComputedColor::rgba(convert_nscolor_to_rgba(color)) -} - impl Color { /// Converts this Color into a ComputedColor. /// @@ -837,7 +829,7 @@ impl SpecifiedValueInfo for Color { // should probably be handled that way as well. // XXX `currentColor` should really be `currentcolor`. But let's // keep it consistent with the old system for now. - f(&["rgb", "rgba", "hsl", "hsla", "currentColor", "transparent"]); + f(&["rgb", "rgba", "hsl", "hsla", "hwb", "currentColor", "transparent"]); } } @@ -939,7 +931,10 @@ impl ColorScheme { } impl Parse for ColorScheme { - fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + fn parse<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { let mut idents = vec![]; let mut bits = ColorSchemeFlags::empty(); @@ -1011,3 +1006,13 @@ impl ToCss for ColorScheme { Ok(()) } } + +/// https://drafts.csswg.org/css-color-adjust/#print-color-adjust +#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(u8)] +pub enum PrintColorAdjust { + /// Ignore backgrounds and darken text. + Economy, + /// Respect specified colors. + Exact, +} diff --git a/components/style/values/specified/counters.rs b/components/style/values/specified/counters.rs index aa442549fa5..91fd7e77078 100644 --- a/components/style/values/specified/counters.rs +++ b/components/style/values/specified/counters.rs @@ -21,6 +21,22 @@ use cssparser::{Parser, Token}; use selectors::parser::SelectorParseErrorKind; use style_traits::{KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind}; +#[derive(PartialEq)] +enum CounterType { + Increment, + Set, + Reset, +} + +impl CounterType { + fn default_value(&self) -> i32 { + match *self { + Self::Increment => 1, + Self::Reset | Self::Set => 0, + } + } +} + /// A specified value for the `counter-increment` property. pub type CounterIncrement = generics::GenericCounterIncrement; @@ -29,26 +45,46 @@ impl Parse for CounterIncrement { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - Ok(Self::new(parse_counters(context, input, 1)?)) + Ok(Self::new(parse_counters( + context, + input, + CounterType::Increment, + )?)) } } -/// A specified value for the `counter-set` and `counter-reset` properties. -pub type CounterSetOrReset = generics::GenericCounterSetOrReset; +/// A specified value for the `counter-set` property. +pub type CounterSet = generics::GenericCounterSet; -impl Parse for CounterSetOrReset { +impl Parse for CounterSet { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - Ok(Self::new(parse_counters(context, input, 0)?)) + Ok(Self::new(parse_counters(context, input, CounterType::Set)?)) + } +} + +/// A specified value for the `counter-reset` property. +pub type CounterReset = generics::GenericCounterReset; + +impl Parse for CounterReset { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Ok(Self::new(parse_counters( + context, + input, + CounterType::Reset, + )?)) } } fn parse_counters<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - default_value: i32, + counter_type: CounterType, ) -> Result>, ParseError<'i>> { if input .try_parse(|input| input.expect_ident_matching("none")) @@ -60,8 +96,21 @@ fn parse_counters<'i, 't>( let mut counters = Vec::new(); loop { let location = input.current_source_location(); - let name = match input.next() { - Ok(&Token::Ident(ref ident)) => CustomIdent::from_ident(location, ident, &["none"])?, + let (name, is_reversed) = match input.next() { + Ok(&Token::Ident(ref ident)) => { + (CustomIdent::from_ident(location, ident, &["none"])?, false) + }, + Ok(&Token::Function(ref name)) + if counter_type == CounterType::Reset && name.eq_ignore_ascii_case("reversed") => + { + input.parse_nested_block(|input| { + let location = input.current_source_location(); + Ok(( + CustomIdent::from_ident(location, input.expect_ident()?, &["none"])?, + true, + )) + })? + }, Ok(t) => { let t = t.clone(); return Err(location.new_unexpected_token_error(t)); @@ -69,10 +118,28 @@ fn parse_counters<'i, 't>( Err(_) => break, }; - let value = input - .try_parse(|input| Integer::parse(context, input)) - .unwrap_or(Integer::new(default_value)); - counters.push(CounterPair { name, value }); + let value = match input.try_parse(|input| Integer::parse(context, input)) { + Ok(start) => { + if start.value == i32::min_value() { + // The spec says that values must be clamped to the valid range, + // and we reserve i32::min_value() as an internal magic value. + // https://drafts.csswg.org/css-lists/#auto-numbering + Integer::new(i32::min_value() + 1) + } else { + start + } + }, + _ => Integer::new(if is_reversed { + i32::min_value() + } else { + counter_type.default_value() + }), + }; + counters.push(CounterPair { + name, + value, + is_reversed, + }); } if !counters.is_empty() { diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs index e78fec45d02..40a559a20be 100644 --- a/components/style/values/specified/font.rs +++ b/components/style/values/specified/font.rs @@ -8,16 +8,18 @@ use crate::context::QuirksMode; use crate::parser::{Parse, ParserContext}; use crate::values::computed::font::{FamilyName, FontFamilyList, FontStyleAngle, SingleFontFamily}; +use crate::values::computed::FontSizeAdjust as ComputedFontSizeAdjust; use crate::values::computed::{font as computed, Length, NonNegativeLength}; use crate::values::computed::{Angle as ComputedAngle, Percentage as ComputedPercentage}; use crate::values::computed::{CSSPixelLength, Context, ToComputedValue}; -use crate::values::computed::FontSizeAdjust as ComputedFontSizeAdjust; use crate::values::generics::font::VariationValue; -use crate::values::generics::font::{self as generics, FeatureTagValue, FontSettings, FontTag, GenericFontSizeAdjust}; +use crate::values::generics::font::{ + self as generics, FeatureTagValue, FontSettings, FontTag, GenericFontSizeAdjust, +}; use crate::values::generics::NonNegative; use crate::values::specified::length::{FontBaseSize, PX_PER_PT}; use crate::values::specified::{AllowQuirks, Angle, Integer, LengthPercentage}; -use crate::values::specified::{NoCalcLength, NonNegativeNumber, Number, NonNegativePercentage}; +use crate::values::specified::{NoCalcLength, NonNegativeNumber, NonNegativePercentage, Number}; use crate::values::CustomIdent; use crate::Atom; use cssparser::{Parser, Token}; @@ -406,7 +408,9 @@ impl ToComputedValue for FontStyle { /// /// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop #[allow(missing_docs)] -#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +#[derive( + Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, +)] #[repr(u8)] pub enum FontStretch { Stretch(NonNegativePercentage), @@ -510,7 +514,9 @@ impl ToComputedValue for FontStretch { } fn from_computed_value(computed: &Self::ComputedValue) -> Self { - FontStretch::Stretch(NonNegativePercentage::from_computed_value(&NonNegative((computed.0).0))) + FontStretch::Stretch(NonNegativePercentage::from_computed_value(&NonNegative( + (computed.0).0, + ))) } } @@ -691,6 +697,7 @@ impl ToComputedValue for FontFamily { FontFamily::Values(ref list) => computed::FontFamily { families: list.clone(), is_system_font: false, + is_initial: false, }, FontFamily::System(_) => self.compute_system(context), } @@ -723,14 +730,13 @@ impl Parse for FontFamily { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - let values = input.parse_comma_separated(|input| SingleFontFamily::parse(context, input))?; + let values = + input.parse_comma_separated(|input| SingleFontFamily::parse(context, input))?; Ok(FontFamily::Values(FontFamilyList { #[cfg(feature = "gecko")] list: crate::ArcSlice::from_iter(values.into_iter()), #[cfg(feature = "servo")] list: values.into_boxed_slice(), - #[cfg(feature = "gecko")] - fallback: computed::GenericFontFamily::None, })) } } @@ -755,9 +761,7 @@ impl Parse for FamilyName { } /// Preserve the readability of text when font fallback occurs -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] #[allow(missing_docs)] pub enum FontSizeAdjust { Value(GenericFontSizeAdjust), @@ -804,7 +808,9 @@ impl Parse for FontSizeAdjust { } // Without a basis keyword, the number refers to the 'ex-height' metric. let value = NonNegativeNumber::parse(context, input)?; - Ok(FontSizeAdjust::Value(GenericFontSizeAdjust::ExHeight(value))) + Ok(FontSizeAdjust::Value(GenericFontSizeAdjust::ExHeight( + value, + ))) } } @@ -1985,14 +1991,7 @@ impl Parse for FontFeatureSettings { } #[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToResolvedValue, - ToShmem, + Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] /// Whether user agents are allowed to synthesize bold or oblique font faces /// when a font family lacks those faces, or a small-caps variant when this is @@ -2102,11 +2101,7 @@ impl ToCss for FontSynthesis { impl SpecifiedValueInfo for FontSynthesis { fn collect_completion_keywords(f: KeywordsCollectFn) { - f(&[ - "none", - "weight", - "style", - ]); + f(&["none", "weight", "style"]); if allow_font_synthesis_small_caps() { f(&["small-caps"]); } @@ -2319,7 +2314,9 @@ impl Parse for VariationValue { /// A metrics override value for a @font-face descriptor /// /// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc -#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +#[derive( + Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, +)] pub enum MetricsOverride { /// A non-negative `` of the computed font size Override(NonNegativePercentage), diff --git a/components/style/values/specified/gecko.rs b/components/style/values/specified/gecko.rs index 9b01cc2a6c4..e721add59cf 100644 --- a/components/style/values/specified/gecko.rs +++ b/components/style/values/specified/gecko.rs @@ -52,7 +52,9 @@ impl Parse for IntersectionObserverRootMargin { use crate::Zero; if input.is_exhausted() { // If there are zero elements in tokens, set tokens to ["0px"]. - return Ok(IntersectionObserverRootMargin(Rect::all(LengthPercentage::zero()))); + return Ok(IntersectionObserverRootMargin(Rect::all( + LengthPercentage::zero(), + ))); } let rect = Rect::parse_with(context, input, parse_pixel_or_percent)?; Ok(IntersectionObserverRootMargin(rect)) diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 095ce20f4c7..f027c69f03a 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -181,7 +181,13 @@ impl Parse for Image { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - Image::parse_with_cors_mode(context, input, CorsMode::None, /* allow_none = */ true, /* only_url = */ false) + Image::parse_with_cors_mode( + context, + input, + CorsMode::None, + /* allow_none = */ true, + /* only_url = */ false, + ) } } @@ -334,7 +340,10 @@ impl CrossFadeImage { cors_mode: CorsMode, ) -> Result> { if let Ok(image) = input.try_parse(|input| { - Image::parse_with_cors_mode(context, input, cors_mode, /* allow_none = */ false, /* only_url = */ false) + Image::parse_with_cors_mode( + context, input, cors_mode, /* allow_none = */ false, + /* only_url = */ false, + ) }) { return Ok(Self::Image(image)); } @@ -374,7 +383,9 @@ impl ImageSet { } } let items = input.parse_nested_block(|input| { - input.parse_comma_separated(|input| ImageSetItem::parse(context, input, cors_mode, only_url)) + input.parse_comma_separated(|input| { + ImageSetItem::parse(context, input, cors_mode, only_url) + }) })?; Ok(Self { selected_index: 0, @@ -386,9 +397,7 @@ impl ImageSet { impl ImageSetItem { fn parse_type<'i>(p: &mut Parser<'i, '_>) -> Result> { p.expect_function_matching("type")?; - p.parse_nested_block(|input| { - Ok(input.expect_string()?.as_ref().to_owned().into()) - }) + p.parse_nested_block(|input| Ok(input.expect_string()?.as_ref().to_owned().into())) } fn parse<'i, 't>( @@ -404,23 +413,33 @@ impl ImageSetItem { cors_mode, )), Err(..) => Image::parse_with_cors_mode( - context, input, cors_mode, /* allow_none = */ false, /* only_url = */ only_url + context, input, cors_mode, /* allow_none = */ false, + /* only_url = */ only_url, )?, }; - let mut resolution = input.try_parse(|input| Resolution::parse(context, input)).ok(); + let mut resolution = input + .try_parse(|input| Resolution::parse(context, input)) + .ok(); let mime_type = input.try_parse(Self::parse_type).ok(); // Try to parse resolution after type(). if mime_type.is_some() && resolution.is_none() { - resolution = input.try_parse(|input| Resolution::parse(context, input)).ok(); + resolution = input + .try_parse(|input| Resolution::parse(context, input)) + .ok(); } let resolution = resolution.unwrap_or(Resolution::X(1.0)); let has_mime_type = mime_type.is_some(); let mime_type = mime_type.unwrap_or_default(); - Ok(Self { image, resolution, has_mime_type, mime_type }) + Ok(Self { + image, + resolution, + has_mime_type, + mime_type, + }) } } diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 6105155d81d..e092f254bd4 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -59,6 +59,9 @@ pub enum FontRelativeLength { /// A "cap" value: https://drafts.csswg.org/css-values/#cap #[css(dimension)] Cap(CSSFloat), + /// An "ic" value: https://drafts.csswg.org/css-values/#ic + #[css(dimension)] + Ic(CSSFloat), /// A "rem" value: https://drafts.csswg.org/css-values/#rem #[css(dimension)] Rem(CSSFloat), @@ -93,6 +96,7 @@ impl FontRelativeLength { FontRelativeLength::Ex(v) | FontRelativeLength::Ch(v) | FontRelativeLength::Cap(v) | + FontRelativeLength::Ic(v) | FontRelativeLength::Rem(v) => v == 0., } } @@ -103,6 +107,7 @@ impl FontRelativeLength { FontRelativeLength::Ex(v) | FontRelativeLength::Ch(v) | FontRelativeLength::Cap(v) | + FontRelativeLength::Ic(v) | FontRelativeLength::Rem(v) => v < 0., } } @@ -119,12 +124,13 @@ impl FontRelativeLength { (&Ex(one), &Ex(other)) => Ex(one + other), (&Ch(one), &Ch(other)) => Ch(one + other), (&Cap(one), &Cap(other)) => Cap(one + other), + (&Ic(one), &Ic(other)) => Ic(one + other), (&Rem(one), &Rem(other)) => Rem(one + other), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { match *self { - Em(..) | Ex(..) | Ch(..) | Cap(..) | Rem(..) => {}, + Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {}, } debug_unreachable!("Forgot to handle unit in try_sum()") }, @@ -237,6 +243,23 @@ impl FontRelativeLength { }); (reference_size, length) }, + FontRelativeLength::Ic(length) => { + let metrics = query_font_metrics( + context, + base_size, + FontMetricsOrientation::MatchContextPreferVertical, + ); + let reference_size = metrics.ic_width.unwrap_or_else(|| { + // https://drafts.csswg.org/css-values/#ic + // + // In the cases where it is impossible or impractical to + // determine the ideographic advance measure, it must be + // assumed to be 1em. + // + reference_font_size + }); + (reference_size, length) + }, FontRelativeLength::Rem(length) => { // https://drafts.csswg.org/css-values/#rem: // @@ -549,6 +572,7 @@ impl NoCalcLength { "ex" => NoCalcLength::FontRelative(FontRelativeLength::Ex(value)), "ch" => NoCalcLength::FontRelative(FontRelativeLength::Ch(value)), "cap" => NoCalcLength::FontRelative(FontRelativeLength::Cap(value)), + "ic" => NoCalcLength::FontRelative(FontRelativeLength::Ic(value)), "rem" => NoCalcLength::FontRelative(FontRelativeLength::Rem(value)), // viewport percentages "vw" if !context.in_page_rule() => { @@ -709,12 +733,13 @@ impl PartialOrd for FontRelativeLength { (&Ex(ref one), &Ex(ref other)) => one.partial_cmp(other), (&Ch(ref one), &Ch(ref other)) => one.partial_cmp(other), (&Cap(ref one), &Cap(ref other)) => one.partial_cmp(other), + (&Ic(ref one), &Ic(ref other)) => one.partial_cmp(other), (&Rem(ref one), &Rem(ref other)) => one.partial_cmp(other), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { match *self { - Em(..) | Ex(..) | Ch(..) | Cap(..) | Rem(..) => {}, + Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {}, } debug_unreachable!("Forgot an arm in partial_cmp?") }, @@ -732,6 +757,7 @@ impl Mul for FontRelativeLength { FontRelativeLength::Ex(v) => FontRelativeLength::Ex(v * scalar), FontRelativeLength::Ch(v) => FontRelativeLength::Ch(v * scalar), FontRelativeLength::Cap(v) => FontRelativeLength::Cap(v * scalar), + FontRelativeLength::Ic(v) => FontRelativeLength::Ic(v * scalar), FontRelativeLength::Rem(v) => FontRelativeLength::Rem(v * scalar), } } diff --git a/components/style/values/specified/list.rs b/components/style/values/specified/list.rs index 14004e65cc8..ff21eb5115e 100644 --- a/components/style/values/specified/list.rs +++ b/components/style/values/specified/list.rs @@ -199,27 +199,3 @@ impl Parse for Quotes { } } } - -/// Specified and computed `-moz-list-reversed` property (for UA sheets only). -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum MozListReversed { - /// the initial value - False, - /// exclusively used for
      in our html.css UA sheet - True, -} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 64bc063960e..ac622961642 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -38,13 +38,13 @@ pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle}; pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, Display}; pub use self::box_::{Appearance, BreakBetween, BreakWithin}; -pub use self::box_::{Clear, Float, Overflow, OverflowAnchor}; -pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; +pub use self::box_::{Clear, ContentVisibility, Float, Overflow, OverflowAnchor}; +pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange}; -pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; +pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; -pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset}; +pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; pub use self::easing::TimingFunction; pub use self::effects::{BoxShadow, Filter, SimpleShadow}; pub use self::flex::FlexBasis; @@ -55,7 +55,7 @@ pub use self::font::{FontVariantAlternates, FontWeight}; pub use self::font::{FontVariantEastAsian, FontVariationSettings}; pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; pub use self::image::{EndingShape as GradientEndingShape, Gradient}; -pub use self::image::{Image, MozImageRect, ImageRendering}; +pub use self::image::{Image, ImageRendering, MozImageRect}; pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth}; pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; @@ -66,12 +66,11 @@ pub use self::length::{ }; #[cfg(feature = "gecko")] pub use self::list::ListStyleType; -pub use self::list::MozListReversed; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; -pub use self::page::{Orientation, PageSize, PaperSize}; -pub use self::percentage::{Percentage, NonNegativePercentage}; +pub use self::page::{Orientation, PageName, PageSize, PaperSize}; +pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto, @@ -84,9 +83,10 @@ pub use self::svg::{DProperty, MozContextProperties}; pub use self::svg::{SVGLength, SVGOpacity, SVGPaint}; pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; pub use self::svg_path::SVGPathData; +pub use self::text::HyphenateCharacter; +pub use self::text::RubyPosition; pub use self::text::TextAlignLast; pub use self::text::TextUnderlinePosition; -pub use self::text::RubyPosition; pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight, TextAlign}; pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak}; pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing}; @@ -577,6 +577,12 @@ impl One for Integer { } } +impl PartialEq for Integer { + fn eq(&self, value: &i32) -> bool { + self.value() == *value + } +} + impl Integer { /// Trivially constructs a new `Integer` value. pub fn new(val: CSSInteger) -> Self { diff --git a/components/style/values/specified/page.rs b/components/style/values/specified/page.rs index 4d96b532689..883f529d867 100644 --- a/components/style/values/specified/page.rs +++ b/components/style/values/specified/page.rs @@ -2,12 +2,12 @@ * 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/. */ -//! Specified @page at-rule properties +//! Specified @page at-rule properties and named-page style properties use crate::parser::{Parse, ParserContext}; -use crate::values::generics; use crate::values::generics::size::Size2D; use crate::values::specified::length::NonNegativeLength; +use crate::values::{generics, CustomIdent}; use cssparser::Parser; use style_traits::ParseError; @@ -46,3 +46,53 @@ impl Parse for PageSize { Ok(PageSize::Auto) } } + +/// Page name value. +/// +/// https://drafts.csswg.org/css-page-3/#using-named-pages +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum PageName { + /// `auto` value. + Auto, + /// Page name value + PageName(CustomIdent), +} + +impl Parse for PageName { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let location = input.current_source_location(); + let ident = input.expect_ident()?; + Ok(match_ignore_ascii_case! { ident, + "auto" => PageName::auto(), + _ => PageName::PageName(CustomIdent::from_ident(location, ident, &[])?), + }) + } +} + +impl PageName { + /// `auto` value. + #[inline] + pub fn auto() -> Self { + PageName::Auto + } + + /// Whether this is the `auto` value. + #[inline] + pub fn is_auto(&self) -> bool { + matches!(*self, PageName::Auto) + } +} diff --git a/components/style/values/specified/svg_path.rs b/components/style/values/specified/svg_path.rs index a443a443326..b251ce900ce 100644 --- a/components/style/values/specified/svg_path.rs +++ b/components/style/values/specified/svg_path.rs @@ -85,7 +85,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_MOVETO_ABS), }); i = i + 2; - } + }, PATHSEG_LINETO_ABS | PATHSEG_LINETO_REL => { debug_assert!(i + 1 < path.len()); result.push(PathCommand::LineTo { @@ -93,7 +93,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_ABS), }); i = i + 2; - } + }, PATHSEG_CURVETO_CUBIC_ABS | PATHSEG_CURVETO_CUBIC_REL => { debug_assert!(i + 5 < path.len()); result.push(PathCommand::CurveTo { @@ -103,7 +103,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_ABS), }); i = i + 6; - } + }, PATHSEG_CURVETO_QUADRATIC_ABS | PATHSEG_CURVETO_QUADRATIC_REL => { debug_assert!(i + 3 < path.len()); result.push(PathCommand::QuadBezierCurveTo { @@ -112,7 +112,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_ABS), }); i = i + 4; - } + }, PATHSEG_ARC_ABS | PATHSEG_ARC_REL => { debug_assert!(i + 6 < path.len()); result.push(PathCommand::EllipticalArc { @@ -125,7 +125,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_ARC_ABS), }); i = i + 7; - } + }, PATHSEG_LINETO_HORIZONTAL_ABS | PATHSEG_LINETO_HORIZONTAL_REL => { debug_assert!(i < path.len()); result.push(PathCommand::HorizontalLineTo { @@ -133,7 +133,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_HORIZONTAL_ABS), }); i = i + 1; - } + }, PATHSEG_LINETO_VERTICAL_ABS | PATHSEG_LINETO_VERTICAL_REL => { debug_assert!(i < path.len()); result.push(PathCommand::VerticalLineTo { @@ -141,7 +141,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_VERTICAL_ABS), }); i = i + 1; - } + }, PATHSEG_CURVETO_CUBIC_SMOOTH_ABS | PATHSEG_CURVETO_CUBIC_SMOOTH_REL => { debug_assert!(i + 3 < path.len()); result.push(PathCommand::SmoothCurveTo { @@ -150,7 +150,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS), }); i = i + 4; - } + }, PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS | PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL => { debug_assert!(i + 1 < path.len()); result.push(PathCommand::SmoothQuadBezierCurveTo { @@ -158,7 +158,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS), }); i = i + 2; - } + }, PATHSEG_UNKNOWN | _ => return Err(()), } } diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index ce652734060..299f4eb4774 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -37,6 +37,27 @@ pub type WordSpacing = Spacing; /// A specified value for the `line-height` property. pub type LineHeight = GenericLineHeight; +/// A value for the `hyphenate-character` property. +#[derive( + Clone, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum HyphenateCharacter { + /// `auto` + Auto, + /// `` + String(crate::OwnedStr), +} + impl Parse for InitialLetter { fn parse<'i, 't>( context: &ParserContext, @@ -1061,7 +1082,6 @@ impl Default for MozControlCharacterVisibility { } } - /// Values for the `line-break` property. #[repr(u8)] #[derive( @@ -1259,15 +1279,7 @@ impl ToCss for TextUnderlinePosition { /// Values for `ruby-position` property #[repr(u8)] #[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToResolvedValue, - ToShmem, + Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[allow(missing_docs)] pub enum RubyPosition { @@ -1283,7 +1295,9 @@ impl Parse for RubyPosition { input: &mut Parser<'i, 't>, ) -> Result> { // Parse alternate before - let alternate = input.try_parse(|i| i.expect_ident_matching("alternate")).is_ok(); + let alternate = input + .try_parse(|i| i.expect_ident_matching("alternate")) + .is_ok(); if alternate && input.is_exhausted() { return Ok(RubyPosition::AlternateOver); } @@ -1294,7 +1308,9 @@ impl Parse for RubyPosition { }; // Parse alternate after let alternate = alternate || - input.try_parse(|i| i.expect_ident_matching("alternate")).is_ok(); + input + .try_parse(|i| i.expect_ident_matching("alternate")) + .is_ok(); Ok(match (over, alternate) { (true, true) => RubyPosition::AlternateOver, diff --git a/components/style/values/specified/ui.rs b/components/style/values/specified/ui.rs index 4594cbfcc77..07463efe66a 100644 --- a/components/style/values/specified/ui.rs +++ b/components/style/values/specified/ui.rs @@ -11,7 +11,9 @@ use crate::values::specified::image::Image; use crate::values::specified::Number; use cssparser::Parser; use std::fmt::{self, Write}; -use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; +use style_traits::{ + CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss, +}; /// A specified value for the `cursor` property. pub type Cursor = generics::GenericCursor; diff --git a/components/style_derive/Cargo.toml b/components/style_derive/Cargo.toml index d5b71ace70f..8f8a9ab5add 100644 --- a/components/style_derive/Cargo.toml +++ b/components/style_derive/Cargo.toml @@ -11,7 +11,7 @@ path = "lib.rs" proc-macro = true [dependencies] -darling = { version = "0.10", default-features = false } +darling = { workspace = true, default-features = false } derive_common = { path = "../derive_common" } proc-macro2 = "1" quote = "1" diff --git a/components/style_derive/to_css.rs b/components/style_derive/to_css.rs index 1f94751f889..40e219c374b 100644 --- a/components/style_derive/to_css.rs +++ b/components/style_derive/to_css.rs @@ -140,11 +140,19 @@ fn derive_variant_fields_expr( Some(pair) => pair, None => return quote! { Ok(()) }, }; + if attrs.field_bound { + let ty = &first.ast().ty; + // TODO(emilio): IntoIterator might not be enough for every type of + // iterable thing (like ArcSlice<> or what not). We might want to expose + // an `item = "T"` attribute to handle that in the future. + let predicate = if attrs.iterable { + parse_quote!(<#ty as IntoIterator>::Item: style_traits::ToCss) + } else { + parse_quote!(#ty: style_traits::ToCss) + }; + cg::add_predicate(where_clause, predicate); + } if !attrs.iterable && iter.peek().is_none() { - if attrs.field_bound { - let ty = &first.ast().ty; - cg::add_predicate(where_clause, parse_quote!(#ty: style_traits::ToCss)); - } let mut expr = quote! { style_traits::ToCss::to_css(#first, dest) }; if let Some(condition) = attrs.skip_if { expr = quote! { @@ -208,7 +216,7 @@ fn derive_single_field_expr( .ident .as_ref() .expect("Unnamed field with represents_keyword?"); - let ident = cg::to_css_identifier(&ident.to_string()); + let ident = cg::to_css_identifier(&ident.to_string()).replace("_", "-"); quote! { if *#field { writer.raw_item(#ident)?; diff --git a/components/style_traits/owned_slice.rs b/components/style_traits/owned_slice.rs index f6068365360..36ba3162e59 100644 --- a/components/style_traits/owned_slice.rs +++ b/components/style_traits/owned_slice.rs @@ -93,12 +93,6 @@ impl OwnedSlice { ret } - /// Iterate over all the elements in the slice taking ownership of them. - #[inline] - pub fn into_iter(self) -> impl Iterator + ExactSizeIterator { - self.into_vec().into_iter() - } - /// Convert the regular slice into an owned slice. #[inline] pub fn from_slice(s: &[T]) -> Self @@ -109,6 +103,16 @@ impl OwnedSlice { } } +impl IntoIterator for OwnedSlice { + type Item = T; + type IntoIter = as IntoIterator>::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.into_vec().into_iter() + } +} + impl Deref for OwnedSlice { type Target = [T]; diff --git a/components/to_shmem/Cargo.toml b/components/to_shmem/Cargo.toml index a87b441fa10..9ef2cefe112 100644 --- a/components/to_shmem/Cargo.toml +++ b/components/to_shmem/Cargo.toml @@ -20,4 +20,3 @@ servo_arc = { path = "../servo_arc" } smallbitvec = { workspace = true } smallvec = { workspace = true } string_cache = { workspace = true, optional = true } -thin-slice = { workspace = true } diff --git a/components/to_shmem/lib.rs b/components/to_shmem/lib.rs index 9188346eb59..6de65ff440e 100644 --- a/components/to_shmem/lib.rs +++ b/components/to_shmem/lib.rs @@ -32,7 +32,6 @@ use std::os::raw::c_void; use std::ptr::{self, NonNull}; use std::slice; use std::str; -use thin_slice::ThinBoxedSlice; /// Result type for ToShmem::to_shmem. /// @@ -334,23 +333,6 @@ impl ToShmem for Box<[T]> { } } -impl ToShmem for ThinBoxedSlice { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { - // We could support this if we needed but in practice we will never - // need to handle such big ThinBoxedSlices. - assert!( - self.spilled_storage().is_none(), - "ToShmem failed for ThinBoxedSlice: too many entries ({})", - self.len(), - ); - - unsafe { - let dest = to_shmem_slice(self.iter(), builder)?; - Ok(ManuallyDrop::new(ThinBoxedSlice::from_raw(dest))) - } - } -} - impl ToShmem for Box { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { // Reserve space for the string bytes. diff --git a/servo-tidy.toml b/servo-tidy.toml index 8abdd161bd1..7e198738942 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -11,7 +11,6 @@ lint-scripts = ["./python/servo/lints/wpt_lint.py"] [blocked-packages] num = [] rand = [ - "hashglobe", # Only used in tests "ipc-channel", "phf_generator", "quickcheck", # Only used in tests @@ -80,16 +79,21 @@ files = [ "./components/net/tests/parsable_mime/text", # These are ignored to avoid diverging from Gecko "./components/style/counter_style/mod.rs", + "./components/style/properties/declaration_block.rs", "./components/style/properties/helpers.mako.rs", "./components/style/rule_collector.rs", "./components/style/selector_map.rs", "./components/style/stylesheets/import_rule.rs", "./components/style/stylesheets/layer_rule.rs", + "./components/style/stylesheets/origin.rs", + "./components/style/stylesheets/page_rule.rs", "./components/style/stylesheets/rule_parser.rs", "./components/style/stylesheets/scroll_timeline_rule.rs", "./components/style/stylist.rs", + "./components/style/values/animated/transform.rs", "./components/style/values/computed/font.rs", "./components/style/values/computed/image.rs", + "./components/style/values/specified/box.rs", "./components/style/values/specified/color.rs", "./components/style/values/specified/transform.rs", # Mako does not lend itself easily to splitting long lines @@ -134,7 +138,6 @@ directories = [ "./components/script/dom/bindings/codegen/ply", "./python/_virtualenv*", "./python/mach", - "./components/hashglobe/src", # Generated and upstream code combined with our own. Could use cleanup "./target", ] diff --git a/tests/unit/style/custom_properties.rs b/tests/unit/style/custom_properties.rs index 1e4ab0a7282..21ce2ba3f86 100644 --- a/tests/unit/style/custom_properties.rs +++ b/tests/unit/style/custom_properties.rs @@ -5,13 +5,15 @@ use cssparser::{Parser, ParserInput}; use euclid::{Scale, Size2D}; use servo_arc::Arc; +use style::applicable_declarations::CascadePriority; use style::context::QuirksMode; use style::custom_properties::{ CustomPropertiesBuilder, CustomPropertiesMap, Name, SpecifiedValue, }; use style::media_queries::{Device, MediaType}; use style::properties::{CustomDeclaration, CustomDeclarationValue}; -use style::stylesheets::Origin; +use style::rule_tree::CascadeLevel; +use style::stylesheets::layer_rule::LayerOrder; use test::{self, Bencher}; fn cascade( @@ -38,7 +40,10 @@ fn cascade( let mut builder = CustomPropertiesBuilder::new(inherited, &device); for declaration in &declarations { - builder.cascade(declaration, Origin::Author); + builder.cascade( + declaration, + CascadePriority::new(CascadeLevel::same_tree_author_normal(), LayerOrder::root()), + ); } builder.build() diff --git a/tests/unit/style/parsing/selectors.rs b/tests/unit/style/parsing/selectors.rs index 7aa77200732..cefcf6e69e2 100644 --- a/tests/unit/style/parsing/selectors.rs +++ b/tests/unit/style/parsing/selectors.rs @@ -4,6 +4,7 @@ use cssparser::{Parser, ParserInput, ToCss}; use selectors::parser::SelectorList; +use servo_url::ServoUrl; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::stylesheets::{Namespaces, Origin}; use style_traits::ParseError; @@ -14,10 +15,11 @@ fn parse_selector<'i, 't>( let mut ns = Namespaces::default(); ns.prefixes .insert("svg".into(), style::Namespace::new(ns!(svg))); + let dummy_url = ServoUrl::parse("about:blank").unwrap(); let parser = SelectorParser { stylesheet_origin: Origin::UserAgent, namespaces: &ns, - url_data: None, + url_data: &dummy_url, }; SelectorList::parse(&parser, input) } diff --git a/tests/unit/style/rule_tree/bench.rs b/tests/unit/style/rule_tree/bench.rs index 9da1eb698e3..d7de685fbd8 100644 --- a/tests/unit/style/rule_tree/bench.rs +++ b/tests/unit/style/rule_tree/bench.rs @@ -6,12 +6,14 @@ use cssparser::SourceLocation; use rayon; use servo_arc::Arc; use servo_url::ServoUrl; +use style::applicable_declarations::CascadePriority; use style::context::QuirksMode; use style::error_reporting::{ContextualParseError, ParseErrorReporter}; use style::media_queries::MediaList; use style::properties::{longhands, Importance, PropertyDeclaration, PropertyDeclarationBlock}; use style::rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource}; use style::shared_lock::{SharedRwLock, StylesheetGuards}; +use style::stylesheets::layer_rule::LayerOrder; use style::stylesheets::{AllowImportRules, CssRule, Origin, Stylesheet}; use style::thread_state::{self, ThreadState}; use test::{self, Bencher}; @@ -85,7 +87,12 @@ fn parse_rules(lock: &SharedRwLock, css: &str) -> Vec<(StyleSource, CascadeLevel } fn test_insertion(rule_tree: &RuleTree, rules: Vec<(StyleSource, CascadeLevel)>) -> StrongRuleNode { - rule_tree.insert_ordered_rules(rules.into_iter()) + rule_tree.insert_ordered_rules(rules.into_iter().map(|(style_source, cascade_level)| { + ( + style_source, + CascadePriority::new(cascade_level, LayerOrder::root()), + ) + })) } fn test_insertion_style_attribute( diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index c89a401bbc0..46d83fca317 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -8,6 +8,7 @@ use euclid::Size2D; use selectors::parser::{AncestorHashes, Selector}; use servo_arc::Arc; use servo_atoms::Atom; +use servo_url::ServoUrl; use style::context::QuirksMode; use style::media_queries::{Device, MediaType}; use style::properties::{longhands, Importance}; @@ -24,6 +25,7 @@ use style::thread_state::{self, ThreadState}; /// Helper method to get some Rules from selector strings. /// Each sublist of the result contains the Rules for one StyleRule. fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { + let dummy_url = &ServoUrl::parse("about:blank").unwrap(); let shared_lock = SharedRwLock::new(); ( css_selectors @@ -31,7 +33,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { .enumerate() .map(|(i, selectors)| { let selectors = - SelectorParser::parse_author_origin_no_namespace(selectors).unwrap(); + SelectorParser::parse_author_origin_no_namespace(selectors, dummy_url).unwrap(); let locked = Arc::new(shared_lock.wrap(StyleRule { selectors: selectors, @@ -64,10 +66,11 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { } fn parse_selectors(selectors: &[&str]) -> Vec> { + let dummy_url = &ServoUrl::parse("about:blank").unwrap(); selectors .iter() .map(|x| { - SelectorParser::parse_author_origin_no_namespace(x) + SelectorParser::parse_author_origin_no_namespace(x, dummy_url) .unwrap() .0 .into_iter() diff --git a/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-003.html.ini b/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-003.html.ini index 46da28061ea..b035eabec54 100644 --- a/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-003.html.ini +++ b/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-003.html.ini @@ -125,66 +125,11 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%) scaleZ(1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Animations: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - diff --git a/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-004.html.ini b/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-004.html.ini index 0050674ab78..03d48da8895 100644 --- a/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-004.html.ini +++ b/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-004.html.ini @@ -215,222 +215,63 @@ [Web Animations: property from [skewX(10rad) translateY(70%)\] to [skewX(20rad) translateY(90%)\] at (1) should be [skewX(20rad) translateY(90%)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.25) should be [translate3d(7px, -6px, 11px) skewX(1.25rad) matrix3d(1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1, -0.001875, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (-1) should be [translate3d(12px, 4px, 16px) skewX(0rad) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.005, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (-1) should be [translate3d(12px, 4px, 16px) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (-1) should be [translate3d(12px, 4px, 16px) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0) should be [matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (2) should be [matrix3d(1, 0, 0, 0, -11.227342763749263, 3, 0, 0, 0.021237113402061854, -0.010618556701030927, 1.03, -0.0014653608247422677, -8, 4, -12, 0.9861443298969074)\]] - expected: FAIL - [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 400, 600, 1)\]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.25) should be [translate3d(7px, -6px, 11px) skewX(1.25rad) matrix3d(1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1, -0.001875, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0) should be [translate3d(8px, -4px, 12px) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 1.1186572632293585, 1.25, 0, 0, -0.0151159793814433, 0.00755798969072165, 0.9775, -0.002378247422680413, 6, -3, 9, 1.0012989690721648)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (2) should be [translate3d(0px, -20px, 4px) skewX(3rad) matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -0.7525665307288518, 1.75, 0, 0, -0.005115979381443298, 0.002557989690721649, 0.9924999999999999, -0.002128247422680412, 2, -1, 3, 1.001298969072165)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 400, 600, 1)\]] expected: FAIL [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (1) should be [matrix(1, 0, 0, 1, 0, 0) \]] expected: FAIL - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.25) should be [translate3d(7px, -6px, 11px) skewX(1.25rad) matrix3d(1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1, -0.001875, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03876288659793814, 0.01938144329896907, 0.94, -0.0029653608247422686, 16, -8, 24, 0.986144329896907)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.75) should be [translate3d(5px, -10px, 9px) skewX(1.75rad) matrix3d(1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1, -0.000625, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0.621795827675797, 1, 0, 0, 0, 0, 1, 0, 2, -1, 3, 1)\]] - expected: FAIL - [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 75, 150, 225, 1)\]] expected: FAIL [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0) should be [matrix(1, 0, 1.5574077246549023, 1, 0, 0)\]] expected: FAIL - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (2) should be [matrix3d(1, 0, 0, 0, -11.227342763749263, 3, 0, 0, 0.021237113402061854, -0.010618556701030927, 1.03, -0.0014653608247422677, -8, 4, -12, 0.9861443298969074)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03876288659793814, 0.01938144329896907, 0.94, -0.0029653608247422686, 16, -8, 24, 0.986144329896907)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (1) should be [translate3d(4px, -12px, 8px) matrix3d(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.75) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 25, 50, 75, 1)\]] expected: FAIL [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0) should be [matrix(1, 0, 1.5574077246549023, 1, 0, 0)\]] expected: FAIL - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 1, 0, 0, 0, 0, 1, 0, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (2) should be [translate3d(0px, -20px, 4px) matrix3d(1, 0, 0, 0, -4.67222, 3, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0) should be [translate3d(8px, -4px, 12px) skewX(1rad) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -1.2494279662824135, 1, 0, 0, 0, 0, 1, 0, 6, -3, 9, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (2) should be [matrix3d(1, 0, 0, 0, -5.9274874511779405, 1, 0, 0, 0, 0, 1, 0, 16, -8, 24, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (1) should be [matrix(1, 0, 0, 1, 0, 0) \]] expected: FAIL [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 200, 300, 1)\]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (-1) should be [translate3d(12px, 4px, 16px) skewX(0rad) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.005, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0) should be [matrix(1, 0, 1.5574077246549023, 1, 0, 0)\]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (2) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -100, -200, -300, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0) should be [translate3d(8px, -4px, 12px) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (1) should be [matrix(1, 0, 0, 1, 0, 0) \]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0) should be [matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (2) should be [translate3d(0px, -20px, 4px) skewX(3rad) matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 200, 300, 1)\]] expected: FAIL [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (1) should be [translate3d(4px, -12px, 8px) skewX(2rad) matrix(1, 0, 0, 2, 0, 0)\]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 1.1186572632293585, 1.25, 0, 0, -0.0151159793814433, 0.00755798969072165, 0.9775, -0.002378247422680413, 6, -3, 9, 1.0012989690721648)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0.621795827675797, 1, 0, 0, 0, 0, 1, 0, 2, -1, 3, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (2) should be [translate3d(0px, -20px, 4px) matrix3d(1, 0, 0, 0, -4.67222, 3, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (-1) should be [translate3d(12px, 4px, 16px) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -0.7525665307288518, 1.75, 0, 0, -0.005115979381443298, 0.002557989690721649, 0.9924999999999999, -0.002128247422680412, 2, -1, 3, 1.001298969072165)\]] - expected: FAIL - - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 1, 0, 0, 0, 0, 1, 0, 8, -4, 12, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.75) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 25, 50, 75, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 1, 0, 0, 0, 0, 1, 0, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (1) should be [translate3d(4px, -12px, 8px) matrix3d(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.25) should be [translate3d(7px, -6px, 11px) matrix3d(1, 0, 0, 0, 1.46007, 1.25, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (-1) should be [matrix3d(1, 0, 0, 0, 5.2998553125713235, 1, 0, 0, 0, 0, 1, 0, -8, 4, -12, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.75) should be [translate3d(5px, -10px, 9px) matrix3d(1, 0, 0, 0, 0.681366, 1.75, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.25) should be [translate3d(7px, -6px, 11px) matrix3d(1, 0, 0, 0, 1.46007, 1.25, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 75, 150, 225, 1)\]] expected: FAIL [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (2) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -100, -200, -300, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.75) should be [translate3d(5px, -10px, 9px) matrix3d(1, 0, 0, 0, 0.681366, 1.75, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (2) should be [matrix3d(1, 0, 0, 0, -11.227342763749263, 3, 0, 0, 0.021237113402061854, -0.010618556701030927, 1.03, -0.0014653608247422677, -8, 4, -12, 0.9861443298969074)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0) should be [translate3d(8px, -4px, 12px) skewX(1rad) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (2) should be [matrix3d(1, 0, 0, 0, -5.9274874511779405, 1, 0, 0, 0, 0, 1, 0, 16, -8, 24, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.75) should be [translate3d(5px, -10px, 9px) skewX(1.75rad) matrix3d(1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1, -0.000625, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0) should be [translate3d(8px, -4px, 12px) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.75) should be [translate3d(5px, -10px, 9px) matrix3d(1, 0, 0, 0, 0.681366, 1.75, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 1.1186572632293585, 1.25, 0, 0, -0.0151159793814433, 0.00755798969072165, 0.9775, -0.002378247422680413, 6, -3, 9, 1.0012989690721648)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (1) should be [translate3d(4px, -12px, 8px) skewX(2rad) matrix(1, 0, 0, 2, 0, 0)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0) should be [matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0) should be [translate3d(8px, -4px, 12px) skewX(1rad) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 400, 600, 1)\]] expected: FAIL - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -1.2494279662824135, 1, 0, 0, 0, 0, 1, 0, 6, -3, 9, 1)\]] - expected: FAIL - - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -1.2494279662824135, 1, 0, 0, 0, 0, 1, 0, 6, -3, 9, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (1) should be [translate3d(4px, -12px, 8px) skewX(2rad) matrix(1, 0, 0, 2, 0, 0)\]] expected: FAIL @@ -440,48 +281,8 @@ [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (2) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -100, -200, -300, 1)\]] expected: FAIL - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0.621795827675797, 1, 0, 0, 0, 0, 1, 0, 2, -1, 3, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.25) should be [translate3d(7px, -6px, 11px) matrix3d(1, 0, 0, 0, 1.46007, 1.25, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (2) should be [translate3d(0px, -20px, 4px) skewX(3rad) matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (2) should be [translate3d(0px, -20px, 4px) matrix3d(1, 0, 0, 0, -4.67222, 3, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (-1) should be [translate3d(12px, 4px, 16px) skewX(0rad) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.005, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (2) should be [matrix3d(1, 0, 0, 0, -5.9274874511779405, 1, 0, 0, 0, 0, 1, 0, 16, -8, 24, 1)\]] - expected: FAIL - - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (-1) should be [matrix3d(1, 0, 0, 0, 5.2998553125713235, 1, 0, 0, 0, 0, 1, 0, -8, 4, -12, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 75, 150, 225, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -0.7525665307288518, 1.75, 0, 0, -0.005115979381443298, 0.002557989690721649, 0.9924999999999999, -0.002128247422680412, 2, -1, 3, 1.001298969072165)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03876288659793814, 0.01938144329896907, 0.94, -0.0029653608247422686, 16, -8, 24, 0.986144329896907)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (1) should be [translate3d(4px, -12px, 8px) matrix3d(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.75) should be [translate3d(5px, -10px, 9px) skewX(1.75rad) matrix3d(1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1, -0.000625, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 200, 300, 1)\]] expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (-1) should be [matrix3d(1, 0, 0, 0, 5.2998553125713235, 1, 0, 0, 0, 0, 1, 0, -8, 4, -12, 1)\]] - expected: FAIL - diff --git a/tests/wpt/metadata-layout-2020/css/css-variables/variable-substitution-variable-declaration.html.ini b/tests/wpt/metadata-layout-2020/css/css-variables/variable-substitution-variable-declaration.html.ini deleted file mode 100644 index 60c36462bdc..00000000000 --- a/tests/wpt/metadata-layout-2020/css/css-variables/variable-substitution-variable-declaration.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[variable-substitution-variable-declaration.html] - [target10 --varC] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/css-variables/variables-substitute-guaranteed-invalid.html.ini b/tests/wpt/metadata-layout-2020/css/css-variables/variables-substitute-guaranteed-invalid.html.ini index 46514359b87..421bd4d7f80 100644 --- a/tests/wpt/metadata-layout-2020/css/css-variables/variables-substitute-guaranteed-invalid.html.ini +++ b/tests/wpt/metadata-layout-2020/css/css-variables/variables-substitute-guaranteed-invalid.html.ini @@ -7,9 +7,3 @@ [A custom property referencing a non-existent variable is treated as unset] expected: FAIL - - [A custom property referencing a cycle becomes guaranteed-invalid] - expected: FAIL - - [A custom property referencing a non-existent variable becomes guaranteed-invalid] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-transforms/animation/transform-interpolation-003.html.ini b/tests/wpt/metadata/css/css-transforms/animation/transform-interpolation-003.html.ini index ff51a0717b9..5c0ce17c25c 100644 --- a/tests/wpt/metadata/css/css-transforms/animation/transform-interpolation-003.html.ini +++ b/tests/wpt/metadata/css/css-transforms/animation/transform-interpolation-003.html.ini @@ -8,36 +8,24 @@ [Web Animations: property from [skewX(10rad) scaleZ(1)\] to [skewX(20rad) scaleZ(2)\] at (0) should be [skewX(10rad) scaleZ(1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad)\] to [skewX(20rad) scaleZ(2)\] at (0.75) should be [skewX(17.5rad) scaleZ(1.75)\]] expected: FAIL [Web Animations: property from [skewX(10rad) scaleZ(1)\] to [skewX(20rad) scaleZ(2)\] at (-1) should be [skewX(0rad) scaleZ(0)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (1) should be [translateY(90%) scaleZ(2)\]] expected: FAIL [Web Animations: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (-1) should be [translateY(50%) scaleZ(0)\]] expected: FAIL - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewY(10rad)\] to [skewY(20rad)\] at (0.25) should be [skewY(12.5rad)\]] expected: FAIL [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL @@ -50,18 +38,9 @@ [Web Animations: property from [skewX(10rad) scaleZ(1)\] to [skewX(20rad) scaleZ(2)\] at (0.25) should be [skewX(12.5rad) scaleZ(1.25)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad)\] to [skewX(20rad)\] at (2) should be [skewX(30rad)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Animations: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL @@ -71,30 +50,15 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (-1) should be [translateY(50%) scaleZ(0)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] expected: FAIL [CSS Transitions with transition: all: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad)\] to [skewX(20rad)\] at (1) should be [skewX(20rad)\]] expected: FAIL @@ -104,9 +68,6 @@ [Web Animations: property from [skewX(10rad)\] to [skewX(20rad)\] at (0) should be [skewX(10rad)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewY(10rad)\] to [skewY(20rad)\] at (0.75) should be [skewY(17.5rad)\]] expected: FAIL @@ -116,9 +77,6 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (0.75) should be [translateY(85%) scaleZ(1.75)\]] expected: FAIL - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad)\] to [skewX(20rad) scaleZ(2)\] at (0.25) should be [skewX(12.5rad) scaleZ(1.25)\]] expected: FAIL @@ -134,9 +92,6 @@ [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] expected: FAIL @@ -161,12 +116,6 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (1) should be [translateY(90%) scaleZ(2)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad) scaleZ(1)\] to [skewX(20rad) scaleZ(2)\] at (0.75) should be [skewX(17.5rad) scaleZ(1.75)\]] expected: FAIL @@ -187,7 +136,3 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%) scaleZ(1)\]] expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - diff --git a/tests/wpt/metadata/css/css-values/ic-unit-001.html.ini b/tests/wpt/metadata/css/css-values/ic-unit-001.html.ini deleted file mode 100644 index c2cf2787e35..00000000000 --- a/tests/wpt/metadata/css/css-values/ic-unit-001.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[ic-unit-001.html] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-values/ic-unit-008.html.ini b/tests/wpt/metadata/css/css-values/ic-unit-008.html.ini deleted file mode 100644 index 027af6d19c9..00000000000 --- a/tests/wpt/metadata/css/css-values/ic-unit-008.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[ic-unit-008.html] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variable-substitution-variable-declaration.html.ini b/tests/wpt/metadata/css/css-variables/variable-substitution-variable-declaration.html.ini deleted file mode 100644 index 60c36462bdc..00000000000 --- a/tests/wpt/metadata/css/css-variables/variable-substitution-variable-declaration.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[variable-substitution-variable-declaration.html] - [target10 --varC] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variables-substitute-guaranteed-invalid.html.ini b/tests/wpt/metadata/css/css-variables/variables-substitute-guaranteed-invalid.html.ini deleted file mode 100644 index aa840ce33fb..00000000000 --- a/tests/wpt/metadata/css/css-variables/variables-substitute-guaranteed-invalid.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[variables-substitute-guaranteed-invalid.html] - [A custom property referencing a cycle becomes guaranteed-invalid] - expected: FAIL - - [A custom property referencing a non-existent variable becomes guaranteed-invalid] - expected: FAIL