diff --git a/components/script/dom/servohtmlparser.rs b/components/script/dom/servohtmlparser.rs index c4d594186c82..9fe69afb0f05 100644 --- a/components/script/dom/servohtmlparser.rs +++ b/components/script/dom/servohtmlparser.rs @@ -15,7 +15,8 @@ use dom::document::Document; use parse::html::JSMessage; use std::default::Default; -use std::cell::RefCell; +use std::cell::{Cell, UnsafeCell}; +use std::kinds::marker; use url::Url; use js::jsapi::JSTracer; use html5ever::tokenizer; @@ -32,13 +33,73 @@ pub struct Sink { pub type Tokenizer = tokenizer::Tokenizer>; +/// Like `RefCell`, but lets us break the rules when we need to, +/// for garbage collector integration. See rust-lang/rust#18131. +pub struct TokenizerCell { + value: UnsafeCell, + borrowed: Cell, + nocopy: marker::NoCopy, + nosync: marker::NoSync, +} + +pub struct TokenizerRefMut<'a> { + _parent: &'a TokenizerCell, +} + +impl TokenizerCell { + fn new(value: Tokenizer) -> TokenizerCell { + TokenizerCell { + value: UnsafeCell::new(value), + borrowed: Cell::new(false), + nocopy: marker::NoCopy, + nosync: marker::NoSync, + } + } + + pub fn borrow_mut<'a>(&'a self) -> TokenizerRefMut<'a> { + if self.borrowed.get() { + fail!("TokenizerCell already borrowed!"); + } + self.borrowed.set(true); + TokenizerRefMut { + _parent: self, + } + } +} + +#[unsafe_destructor] // because we have lifetime parameters +impl<'a> Drop for TokenizerRefMut<'a> { + fn drop(&mut self) { + debug_assert!(self._parent.borrowed.get()); + self._parent.borrowed.set(false); + } +} + +impl<'b> Deref for TokenizerRefMut<'b> { + #[inline] + fn deref<'a>(&'a self) -> &'a Tokenizer { + unsafe { + &*self._parent.value.get() + } + } +} + +impl<'b> DerefMut for TokenizerRefMut<'b> { + #[inline] + fn deref_mut<'a>(&'a mut self) -> &'a mut Tokenizer { + unsafe { + &mut *self._parent.value.get() + } + } +} + // NB: JSTraceable is *not* auto-derived. // You must edit the impl below if you add fields! #[must_root] #[privatize] pub struct ServoHTMLParser { reflector_: Reflector, - tokenizer: RefCell, + tokenizer: TokenizerCell, } impl ServoHTMLParser { @@ -61,14 +122,14 @@ impl ServoHTMLParser { let parser = ServoHTMLParser { reflector_: Reflector::new(), - tokenizer: RefCell::new(tok), + tokenizer: TokenizerCell::new(tok), }; reflect_dom_object(box parser, &global::Window(*window), ServoHTMLParserBinding::Wrap) } #[inline] - pub fn tokenizer<'a>(&'a self) -> &'a RefCell { + pub fn tokenizer<'a>(&'a self) -> &'a TokenizerCell { &self.tokenizer } } @@ -91,15 +152,23 @@ impl tree_builder::Tracer for Tracer { impl JSTraceable for ServoHTMLParser { fn trace(&self, trc: *mut JSTracer) { + self.reflector_.trace(trc); + let tracer = Tracer { trc: trc, }; let tracer = &tracer as &tree_builder::Tracer; - self.reflector_.trace(trc); - let tokenizer = self.tokenizer.borrow(); - let tree_builder = tokenizer.sink(); - tree_builder.trace_handles(tracer); - tree_builder.sink().trace(trc); + unsafe { + // There might be an active TokenizerRefMut, especially if parsing + // triggered a garbage collection. It's safe to ignore the borrow + // flag, because that mutable reference won't be used along the + // code paths reachable from tracing. + + let tokenizer: &Tokenizer = &*self.tokenizer.value.get(); + let tree_builder = tokenizer.sink(); + tree_builder.trace_handles(tracer); + tree_builder.sink().trace(trc); + } } }