Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5
#![allow(unsafe_code)]
6
7
// This is needed for the constants in atom_macro.rs, because we have some
8
// atoms whose names differ only by case, e.g. datetime and dateTime.
9
#![allow(non_upper_case_globals)]
10
11
//! A drop-in replacement for string_cache, but backed by Gecko `nsAtom`s.
12
13
use crate::gecko_bindings::bindings::Gecko_AddRefAtom;
14
use crate::gecko_bindings::bindings::Gecko_Atomize;
15
use crate::gecko_bindings::bindings::Gecko_Atomize16;
16
use crate::gecko_bindings::bindings::Gecko_ReleaseAtom;
17
use crate::gecko_bindings::structs::root::mozilla::detail::gGkAtoms;
18
use crate::gecko_bindings::structs::root::mozilla::detail::kGkAtomsArrayOffset;
19
use crate::gecko_bindings::structs::root::mozilla::detail::GkAtoms_Atoms_AtomsCount;
20
use crate::gecko_bindings::structs::{nsAtom, nsDynamicAtom, nsStaticAtom};
21
use nsstring::{nsAString, nsStr};
22
use precomputed_hash::PrecomputedHash;
23
use std::borrow::{Borrow, Cow};
24
use std::char::{self, DecodeUtf16};
25
use std::fmt::{self, Write};
26
use std::hash::{Hash, Hasher};
27
use std::iter::Cloned;
28
use std::mem::{self, ManuallyDrop};
29
use std::num::NonZeroUsize;
30
use std::ops::Deref;
31
use std::{slice, str};
32
use style_traits::SpecifiedValueInfo;
33
use to_shmem::{SharedMemoryBuilder, ToShmem};
34
35
#[macro_use]
36
#[allow(improper_ctypes, non_camel_case_types, missing_docs)]
37
pub mod atom_macro {
38
include!(concat!(env!("OUT_DIR"), "/gecko/atom_macro.rs"));
39
}
40
41
#[macro_use]
42
pub mod namespace;
43
44
pub use self::namespace::{Namespace, WeakNamespace};
45
46
macro_rules! local_name {
47
($s:tt) => {
48
atom!($s)
49
};
50
}
51
52
/// A handle to a Gecko atom. This is a type that can represent either:
53
///
54
/// * A strong reference to a dynamic atom (an `nsAtom` pointer), in which case
55
/// the `usize` just holds the pointer value.
56
///
57
/// * A byte offset from `gGkAtoms` to the `nsStaticAtom` object (shifted to
58
/// the left one bit, and with the lower bit set to `1` to differentiate it
59
/// from the above), so `(offset << 1 | 1)`.
60
///
61
#[derive(Eq, PartialEq)]
62
#[repr(C)]
63
pub struct Atom(NonZeroUsize);
64
65
/// An atom *without* a strong reference.
66
///
67
/// Only usable as `&'a WeakAtom`,
68
/// where `'a` is the lifetime of something that holds a strong reference to that atom.
69
pub struct WeakAtom(nsAtom);
70
71
/// The number of static atoms we have.
72
const STATIC_ATOM_COUNT: usize = GkAtoms_Atoms_AtomsCount as usize;
73
74
/// Returns the Gecko static atom array.
75
///
76
/// We have this rather than use rust-bindgen to generate
77
/// mozilla::detail::gGkAtoms and then just reference gGkAtoms.mAtoms, so we
78
/// avoid a problem with lld-link.exe on Windows.
79
///
81
#[inline]
82
fn static_atoms() -> &'static [nsStaticAtom; STATIC_ATOM_COUNT] {
83
unsafe {
84
let addr = &gGkAtoms as *const _ as usize + kGkAtomsArrayOffset as usize;
85
&*(addr as *const _)
86
}
87
}
88
89
/// Returns whether the specified address points to one of the nsStaticAtom
90
/// objects in the Gecko static atom array.
91
#[inline]
92
fn valid_static_atom_addr(addr: usize) -> bool {
93
unsafe {
94
let atoms = static_atoms();
95
let start = atoms.as_ptr();
96
let end = atoms.get_unchecked(STATIC_ATOM_COUNT) as *const _;
97
let in_range = addr >= start as usize && addr < end as usize;
98
let aligned = addr % mem::align_of::<nsStaticAtom>() == 0;
99
in_range && aligned
100
}
101
}
102
103
impl Deref for Atom {
104
type Target = WeakAtom;
105
106
#[inline]
107
fn deref(&self) -> &WeakAtom {
108
unsafe {
109
let addr = if self.is_static() {
110
(&gGkAtoms as *const _ as usize) + (self.0.get() >> 1)
111
} else {
112
self.0.get()
113
};
114
debug_assert!(!self.is_static() || valid_static_atom_addr(addr));
115
WeakAtom::new(addr as *const nsAtom)
116
}
117
}
118
}
119
120
impl PrecomputedHash for Atom {
121
#[inline]
122
fn precomputed_hash(&self) -> u32 {
123
self.get_hash()
124
}
125
}
126
127
impl Borrow<WeakAtom> for Atom {
128
#[inline]
129
fn borrow(&self) -> &WeakAtom {
130
self
131
}
132
}
133
134
impl ToShmem for Atom {
135
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
136
assert!(
137
self.is_static(),
138
"ToShmem failed for Atom: must be a static atom: {}",
139
self
140
);
141
142
ManuallyDrop::new(Atom(self.0))
143
}
144
}
145
146
impl Eq for WeakAtom {}
147
impl PartialEq for WeakAtom {
148
#[inline]
149
fn eq(&self, other: &Self) -> bool {
150
let weak: *const WeakAtom = self;
151
let other: *const WeakAtom = other;
152
weak == other
153
}
154
}
155
156
unsafe impl Send for Atom {}
157
unsafe impl Sync for Atom {}
158
unsafe impl Sync for WeakAtom {}
159
160
impl WeakAtom {
161
/// Construct a `WeakAtom` from a raw `nsAtom`.
162
#[inline]
163
pub unsafe fn new<'a>(atom: *const nsAtom) -> &'a mut Self {
164
&mut *(atom as *mut WeakAtom)
165
}
166
167
/// Clone this atom, bumping the refcount if the atom is not static.
168
#[inline]
169
pub fn clone(&self) -> Atom {
170
unsafe { Atom::from_raw(self.as_ptr()) }
171
}
172
173
/// Get the atom hash.
174
#[inline]
175
pub fn get_hash(&self) -> u32 {
176
self.0.mHash
177
}
178
179
/// Get the atom as a slice of utf-16 chars.
180
#[inline]
181
pub fn as_slice(&self) -> &[u16] {
182
let string = if self.is_static() {
183
let atom_ptr = self.as_ptr() as *const nsStaticAtom;
184
let string_offset = unsafe { (*atom_ptr).mStringOffset };
185
let string_offset = -(string_offset as isize);
186
let u8_ptr = atom_ptr as *const u8;
187
// It is safe to use offset() here because both addresses are within
188
// the same struct, e.g. mozilla::detail::gGkAtoms.
189
unsafe { u8_ptr.offset(string_offset) as *const u16 }
190
} else {
191
let atom_ptr = self.as_ptr() as *const nsDynamicAtom;
192
// Dynamic atom chars are stored at the end of the object.
193
unsafe { atom_ptr.offset(1) as *const u16 }
194
};
195
unsafe { slice::from_raw_parts(string, self.len() as usize) }
196
}
197
198
// NOTE: don't expose this, since it's slow, and easy to be misused.
199
fn chars(&self) -> DecodeUtf16<Cloned<slice::Iter<u16>>> {
200
char::decode_utf16(self.as_slice().iter().cloned())
201
}
202
203
/// Execute `cb` with the string that this atom represents.
204
///
205
/// Find alternatives to this function when possible, please, since it's
206
/// pretty slow.
207
pub fn with_str<F, Output>(&self, cb: F) -> Output
208
where
209
F: FnOnce(&str) -> Output,
210
{
211
let mut buffer = mem::MaybeUninit::<[u8; 64]>::uninit();
212
let buffer = unsafe { &mut *buffer.as_mut_ptr() };
213
214
// The total string length in utf16 is going to be less than or equal
215
// the slice length (each utf16 character is going to take at least one
216
// and at most 2 items in the utf16 slice).
217
//
218
// Each of those characters will take at most four bytes in the utf8
219
// one. Thus if the slice is less than 64 / 4 (16) we can guarantee that
220
// we'll decode it in place.
221
let owned_string;
222
let len = self.len();
223
let utf8_slice = if len <= 16 {
224
let mut total_len = 0;
225
226
for c in self.chars() {
227
let c = c.unwrap_or(char::REPLACEMENT_CHARACTER);
228
let utf8_len = c.encode_utf8(&mut buffer[total_len..]).len();
229
total_len += utf8_len;
230
}
231
232
let slice = unsafe { str::from_utf8_unchecked(&buffer[..total_len]) };
233
debug_assert_eq!(slice, String::from_utf16_lossy(self.as_slice()));
234
slice
235
} else {
236
owned_string = String::from_utf16_lossy(self.as_slice());
237
&*owned_string
238
};
239
240
cb(utf8_slice)
241
}
242
243
/// Returns whether this atom is static.
244
#[inline]
245
pub fn is_static(&self) -> bool {
246
self.0.mIsStatic() != 0
247
}
248
249
/// Returns whether this atom is ascii lowercase.
250
#[inline]
251
fn is_ascii_lowercase(&self) -> bool {
252
self.0.mIsAsciiLowercase() != 0
253
}
254
255
/// Returns the length of the atom string.
256
#[inline]
257
pub fn len(&self) -> u32 {
258
self.0.mLength()
259
}
260
261
/// Returns whether this atom is the empty string.
262
#[inline]
263
pub fn is_empty(&self) -> bool {
264
self.len() == 0
265
}
266
267
/// Returns the atom as a mutable pointer.
268
#[inline]
269
pub fn as_ptr(&self) -> *mut nsAtom {
270
let const_ptr: *const nsAtom = &self.0;
271
const_ptr as *mut nsAtom
272
}
273
274
/// Convert this atom to ASCII lower-case
275
pub fn to_ascii_lowercase(&self) -> Atom {
276
if self.is_ascii_lowercase() {
277
return self.clone();
278
}
279
280
let slice = self.as_slice();
281
let mut buffer = mem::MaybeUninit::<[u16; 64]>::uninit();
282
let buffer = unsafe { &mut *buffer.as_mut_ptr() };
283
let mut vec;
284
let mutable_slice = if let Some(buffer_prefix) = buffer.get_mut(..slice.len()) {
285
buffer_prefix.copy_from_slice(slice);
286
buffer_prefix
287
} else {
288
vec = slice.to_vec();
289
&mut vec
290
};
291
for char16 in &mut *mutable_slice {
292
if *char16 <= 0x7F {
293
*char16 = (*char16 as u8).to_ascii_lowercase() as u16
294
}
295
}
296
Atom::from(&*mutable_slice)
297
}
298
299
/// Return whether two atoms are ASCII-case-insensitive matches
300
#[inline]
301
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
302
if self == other {
303
return true;
304
}
305
306
// If we know both atoms are ascii-lowercase, then we can stick with
307
// pointer equality.
308
if self.is_ascii_lowercase() && other.is_ascii_lowercase() {
309
debug_assert!(!self.eq_ignore_ascii_case_slow(other));
310
return false;
311
}
312
313
self.eq_ignore_ascii_case_slow(other)
314
}
315
316
fn eq_ignore_ascii_case_slow(&self, other: &Self) -> bool {
317
let a = self.as_slice();
318
let b = other.as_slice();
319
320
if a.len() != b.len() {
321
return false;
322
}
323
324
a.iter().zip(b).all(|(&a16, &b16)| {
325
if a16 <= 0x7F && b16 <= 0x7F {
326
(a16 as u8).eq_ignore_ascii_case(&(b16 as u8))
327
} else {
328
a16 == b16
329
}
330
})
331
}
332
}
333
334
impl fmt::Debug for WeakAtom {
335
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
336
write!(w, "Gecko WeakAtom({:p}, {})", self, self)
337
}
338
}
339
340
impl fmt::Display for WeakAtom {
341
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
342
for c in self.chars() {
343
w.write_char(c.unwrap_or(char::REPLACEMENT_CHARACTER))?
344
}
345
Ok(())
346
}
347
}
348
349
#[inline]
350
unsafe fn make_handle(ptr: *const nsAtom) -> NonZeroUsize {
351
debug_assert!(!ptr.is_null());
352
if !WeakAtom::new(ptr).is_static() {
353
NonZeroUsize::new_unchecked(ptr as usize)
354
} else {
355
make_static_handle(ptr as *mut nsStaticAtom)
356
}
357
}
358
359
#[inline]
360
unsafe fn make_static_handle(ptr: *const nsStaticAtom) -> NonZeroUsize {
361
// FIXME(heycam): Use offset_from once it's stabilized.
363
debug_assert!(valid_static_atom_addr(ptr as usize));
364
let base = &gGkAtoms as *const _;
365
let offset = ptr as usize - base as usize;
366
NonZeroUsize::new_unchecked((offset << 1) | 1)
367
}
368
369
impl Atom {
370
#[inline]
371
fn is_static(&self) -> bool {
372
self.0.get() & 1 == 1
373
}
374
375
/// Execute a callback with the atom represented by `ptr`.
376
pub unsafe fn with<F, R>(ptr: *const nsAtom, callback: F) -> R
377
where
378
F: FnOnce(&Atom) -> R,
379
{
380
let atom = Atom(make_handle(ptr as *mut nsAtom));
381
let ret = callback(&atom);
382
mem::forget(atom);
383
ret
384
}
385
386
/// Creates a static atom from its index in the static atom table, without
387
/// checking.
388
#[inline]
389
pub const unsafe fn from_index_unchecked(index: u16) -> Self {
390
// FIXME(emilio): No support for debug_assert! in const fn for now. Note
391
// that violating this invariant will debug-assert in the `Deref` impl
392
// though.
393
//
394
// debug_assert!((index as usize) < STATIC_ATOM_COUNT);
395
let offset =
396
(index as usize) * std::mem::size_of::<nsStaticAtom>() + kGkAtomsArrayOffset as usize;
397
Atom(NonZeroUsize::new_unchecked((offset << 1) | 1))
398
}
399
400
/// Creates an atom from an atom pointer.
401
#[inline(always)]
402
pub unsafe fn from_raw(ptr: *mut nsAtom) -> Self {
403
let atom = Atom(make_handle(ptr));
404
if !atom.is_static() {
405
Gecko_AddRefAtom(ptr);
406
}
407
atom
408
}
409
410
/// Creates an atom from an atom pointer that has already had AddRef
411
/// called on it. This may be a static or dynamic atom.
412
#[inline]
413
pub unsafe fn from_addrefed(ptr: *mut nsAtom) -> Self {
414
assert!(!ptr.is_null());
415
Atom(make_handle(ptr))
416
}
417
418
/// Convert this atom into an addrefed nsAtom pointer.
419
#[inline]
420
pub fn into_addrefed(self) -> *mut nsAtom {
421
let ptr = self.as_ptr();
422
mem::forget(self);
423
ptr
424
}
425
}
426
427
impl Hash for Atom {
428
fn hash<H>(&self, state: &mut H)
429
where
430
H: Hasher,
431
{
432
state.write_u32(self.get_hash());
433
}
434
}
435
436
impl Hash for WeakAtom {
437
fn hash<H>(&self, state: &mut H)
438
where
439
H: Hasher,
440
{
441
state.write_u32(self.get_hash());
442
}
443
}
444
445
impl Clone for Atom {
446
#[inline(always)]
447
fn clone(&self) -> Atom {
448
unsafe {
449
let atom = Atom(self.0);
450
if !atom.is_static() {
451
Gecko_AddRefAtom(atom.as_ptr());
452
}
453
atom
454
}
455
}
456
}
457
458
impl Drop for Atom {
459
#[inline]
460
fn drop(&mut self) {
461
if !self.is_static() {
462
unsafe {
463
Gecko_ReleaseAtom(self.as_ptr());
464
}
465
}
466
}
467
}
468
469
impl Default for Atom {
470
#[inline]
471
fn default() -> Self {
472
atom!("")
473
}
474
}
475
476
impl fmt::Debug for Atom {
477
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
478
write!(w, "Atom(0x{:08x}, {})", self.0, self)
479
}
480
}
481
482
impl fmt::Display for Atom {
483
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
484
self.deref().fmt(w)
485
}
486
}
487
488
impl<'a> From<&'a str> for Atom {
489
#[inline]
490
fn from(string: &str) -> Atom {
491
debug_assert!(string.len() <= u32::max_value() as usize);
492
unsafe {
493
Atom::from_addrefed(Gecko_Atomize(
494
string.as_ptr() as *const _,
495
string.len() as u32,
496
))
497
}
498
}
499
}
500
501
impl<'a> From<&'a [u16]> for Atom {
502
#[inline]
503
fn from(slice: &[u16]) -> Atom {
504
Atom::from(&*nsStr::from(slice))
505
}
506
}
507
508
impl<'a> From<&'a nsAString> for Atom {
509
#[inline]
510
fn from(string: &nsAString) -> Atom {
511
unsafe { Atom::from_addrefed(Gecko_Atomize16(string)) }
512
}
513
}
514
515
impl<'a> From<Cow<'a, str>> for Atom {
516
#[inline]
517
fn from(string: Cow<'a, str>) -> Atom {
518
Atom::from(&*string)
519
}
520
}
521
522
impl From<String> for Atom {
523
#[inline]
524
fn from(string: String) -> Atom {
525
Atom::from(&*string)
526
}
527
}
528
529
malloc_size_of_is_0!(Atom);
530
531
impl SpecifiedValueInfo for Atom {}