servo: Merge #11044 - Combine Page into BrowsingContext (from cbrewster:page_to_browsing_context); r=jdm
authorConnor Brewster <brewsterc@my.caspercollege.edu>
Wed, 11 May 2016 20:53:02 -0700
changeset 338779 cb5b56f39f3f413d6b8d81be1ef0739c24e686d1
parent 338778 d8755ecef73619f80b23b478c3edd67309ac4a9c
child 338780 a6f822ccae446fc0221a2aeff19fff99e2e6471b
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
servo: Merge #11044 - Combine Page into BrowsingContext (from cbrewster:page_to_browsing_context); r=jdm Fixes #11031. `Page` and `BrowsingContext` have similar use cases and we decided it would be best to join the two. This is the ground work for actually using session history in the `BrowsingContext` to implement the History API. r? @jdm Source-Repo: https://github.com/servo/servo Source-Revision: 685dc99e3ef9cf6c88487704709dc13a26812889
servo/components/script/devtools.rs
servo/components/script/dom/browsingcontext.rs
servo/components/script/dom/htmliframeelement.rs
servo/components/script/dom/storage.rs
servo/components/script/dom/window.rs
servo/components/script/lib.rs
servo/components/script/page.rs
servo/components/script/script_thread.rs
servo/components/script/webdriver_handlers.rs
--- a/servo/components/script/devtools.rs
+++ b/servo/components/script/devtools.rs
@@ -10,27 +10,26 @@ use dom::bindings::codegen::Bindings::CS
 use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
 use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
 use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
 use dom::bindings::conversions::{FromJSValConvertible, jsstring_to_str};
 use dom::bindings::global::GlobalRef;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::Root;
+use dom::browsingcontext::{BrowsingContext, IterableContext};
 use dom::element::Element;
 use dom::node::Node;
 use dom::window::Window;
 use ipc_channel::ipc::IpcSender;
 use js::jsapi::{ObjectClassName, RootedObject, RootedValue};
 use js::jsval::UndefinedValue;
 use msg::constellation_msg::PipelineId;
-use page::{IterablePage, Page};
-use script_thread::get_page;
+use script_thread::get_browsing_context;
 use std::ffi::CStr;
-use std::rc::Rc;
 use std::str;
 use style::properties::longhands::{margin_top, margin_right, margin_bottom, margin_left};
 use util::str::DOMString;
 use uuid::Uuid;
 
 #[allow(unsafe_code)]
 pub fn handle_evaluate_js(global: &GlobalRef, eval: String, reply: IpcSender<EvaluateJSReply>) {
     // global.get_cx() returns a valid `JSContext` pointer, so this is safe.
@@ -61,72 +60,75 @@ pub fn handle_evaluate_js(global: &Globa
                 class: class_name.to_owned(),
                 uuid: Uuid::new_v4().to_string(),
             }
         }
     };
     reply.send(result).unwrap();
 }
 
-pub fn handle_get_root_node(page: &Rc<Page>, pipeline: PipelineId, reply: IpcSender<NodeInfo>) {
-    let page = get_page(&*page, pipeline);
-    let document = page.document();
+pub fn handle_get_root_node(context: &BrowsingContext, pipeline: PipelineId, reply: IpcSender<NodeInfo>) {
+    let context = get_browsing_context(context, pipeline);
+    let document = context.active_document();
 
     let node = document.upcast::<Node>();
     reply.send(node.summarize()).unwrap();
 }
 
-pub fn handle_get_document_element(page: &Rc<Page>,
+pub fn handle_get_document_element(context: &BrowsingContext,
                                    pipeline: PipelineId,
                                    reply: IpcSender<NodeInfo>) {
-    let page = get_page(&*page, pipeline);
-    let document = page.document();
+    let context = get_browsing_context(context, pipeline);
+    let document = context.active_document();
     let document_element = document.GetDocumentElement().unwrap();
 
     let node = document_element.upcast::<Node>();
     reply.send(node.summarize()).unwrap();
 }
 
-fn find_node_by_unique_id(page: &Rc<Page>, pipeline: PipelineId, node_id: String) -> Root<Node> {
-    let page = get_page(&*page, pipeline);
-    let document = page.document();
+fn find_node_by_unique_id(context: &BrowsingContext,
+                          pipeline: PipelineId,
+                          node_id: String)
+                          -> Root<Node> {
+    let context = get_browsing_context(context, pipeline);
+    let document = context.active_document();
     let node = document.upcast::<Node>();
 
     for candidate in node.traverse_preorder() {
         if candidate.unique_id() == node_id {
             return candidate;
         }
     }
 
     panic!("couldn't find node with unique id {}", node_id)
 }
 
-pub fn handle_get_children(page: &Rc<Page>,
+pub fn handle_get_children(context: &BrowsingContext,
                            pipeline: PipelineId,
                            node_id: String,
                            reply: IpcSender<Vec<NodeInfo>>) {
-    let parent = find_node_by_unique_id(&*page, pipeline, node_id);
+    let parent = find_node_by_unique_id(context, pipeline, node_id);
     let children = parent.children()
                          .map(|child| child.summarize())
                          .collect();
     reply.send(children).unwrap();
 }
 
-pub fn handle_get_layout(page: &Rc<Page>,
+pub fn handle_get_layout(context: &BrowsingContext,
                          pipeline: PipelineId,
                          node_id: String,
                          reply: IpcSender<ComputedNodeLayout>) {
-    let node = find_node_by_unique_id(&*page, pipeline, node_id);
+    let node = find_node_by_unique_id(context, pipeline, node_id);
 
     let elem = node.downcast::<Element>().expect("should be getting layout of element");
     let rect = elem.GetBoundingClientRect();
     let width = rect.Width() as f32;
     let height = rect.Height() as f32;
 
-    let window = page.window();
+    let window = context.active_window();
     let elem = node.downcast::<Element>().expect("should be getting layout of element");
     let computed_style = window.r().GetComputedStyle(elem, None);
 
     reply.send(ComputedNodeLayout {
         display: String::from(computed_style.Display()),
         position: String::from(computed_style.Position()),
         zIndex: String::from(computed_style.ZIndex()),
         boxSizing: String::from(computed_style.BoxSizing()),
@@ -195,21 +197,21 @@ pub fn handle_get_cached_messages(_pipel
             private: false,
             arguments: vec!["console error test".to_owned()],
         };
         messages.push(CachedConsoleMessage::ConsoleAPI(msg));
     }
     reply.send(messages).unwrap();
 }
 
-pub fn handle_modify_attribute(page: &Rc<Page>,
+pub fn handle_modify_attribute(context: &BrowsingContext,
                                pipeline: PipelineId,
                                node_id: String,
                                modifications: Vec<Modification>) {
-    let node = find_node_by_unique_id(&*page, pipeline, node_id);
+    let node = find_node_by_unique_id(context, pipeline, node_id);
     let elem = node.downcast::<Element>().expect("should be getting layout of element");
 
     for modification in modifications {
         match modification.newValue {
             Some(string) => {
                 let _ = elem.SetAttribute(DOMString::from(modification.attributeName),
                                           DOMString::from(string));
             },
@@ -217,29 +219,32 @@ pub fn handle_modify_attribute(page: &Rc
         }
     }
 }
 
 pub fn handle_wants_live_notifications(global: &GlobalRef, send_notifications: bool) {
     global.set_devtools_wants_updates(send_notifications);
 }
 
-pub fn handle_set_timeline_markers(page: &Rc<Page>,
+pub fn handle_set_timeline_markers(context: &BrowsingContext,
                                    marker_types: Vec<TimelineMarkerType>,
                                    reply: IpcSender<TimelineMarker>) {
-    let window = page.window();
+    let window = context.active_window();
     window.set_devtools_timeline_markers(marker_types, reply);
 }
 
-pub fn handle_drop_timeline_markers(page: &Rc<Page>, marker_types: Vec<TimelineMarkerType>) {
-    let window = page.window();
+pub fn handle_drop_timeline_markers(context: &BrowsingContext,
+                                    marker_types: Vec<TimelineMarkerType>) {
+    let window = context.active_window();
     window.drop_devtools_timeline_markers(marker_types);
 }
 
-pub fn handle_request_animation_frame(page: &Rc<Page>, id: PipelineId, actor_name: String) {
-    let page = page.find(id).expect("There is no such page");
-    let doc = page.document();
-    let devtools_sender = page.window().devtools_chan().unwrap();
+pub fn handle_request_animation_frame(context: &BrowsingContext,
+                                      id: PipelineId,
+                                      actor_name: String) {
+    let context = context.find(id).expect("There is no such context");
+    let doc = context.active_document();
+    let devtools_sender = context.active_window().devtools_chan().unwrap();
     doc.request_animation_frame(box move |time| {
         let msg = ScriptToDevtoolsControlMsg::FramerateTick(actor_name, time);
         devtools_sender.send(msg).unwrap();
     });
 }
--- a/servo/components/script/dom/browsingcontext.rs
+++ b/servo/components/script/dom/browsingcontext.rs
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use dom::bindings::cell::DOMRefCell;
+use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
 use dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
 use dom::bindings::js::{JS, Root, RootedReference};
 use dom::bindings::proxyhandler::{fill_property_descriptor, get_property_descriptor};
 use dom::bindings::reflector::{Reflectable, Reflector};
 use dom::bindings::trace::JSTraceable;
 use dom::bindings::utils::WindowProxyHandler;
 use dom::bindings::utils::get_array_index_from_id;
 use dom::document::Document;
@@ -17,102 +18,219 @@ use js::JSCLASS_IS_GLOBAL;
 use js::glue::{CreateWrapperProxyHandler, ProxyTraps, NewWindowProxy};
 use js::glue::{GetProxyPrivate, SetProxyExtra, GetProxyExtra};
 use js::jsapi::{Handle, HandleId, HandleObject, HandleValue, JSAutoCompartment, JSAutoRequest};
 use js::jsapi::{JSContext, JSPROP_READONLY, JSErrNum, JSObject, PropertyDescriptor, JS_DefinePropertyById};
 use js::jsapi::{JS_ForwardGetPropertyTo, JS_ForwardSetPropertyTo, JS_GetClass, JSTracer, FreeOp};
 use js::jsapi::{JS_GetOwnPropertyDescriptorById, JS_HasPropertyById, MutableHandle};
 use js::jsapi::{MutableHandleValue, ObjectOpResult, RootedObject, RootedValue};
 use js::jsval::{UndefinedValue, PrivateValue};
+use msg::constellation_msg::{PipelineId, SubpageId};
+use std::cell::Cell;
+use url::Url;
+use util::str::DOMString;
 
 #[dom_struct]
 pub struct BrowsingContext {
     reflector: Reflector,
+
+    /// Pipeline id associated with this context.
+    id: PipelineId,
+
+    /// Indicates if reflow is required when reloading.
+    needs_reflow: Cell<bool>,
+
+    /// Stores this context's session history
     history: DOMRefCell<Vec<SessionHistoryEntry>>,
-    active_index: usize,
+
+    /// The index of the active session history entry
+    active_index: Cell<usize>,
+
+    /// Stores the child browsing contexts (ex. iframe browsing context)
+    children: DOMRefCell<Vec<JS<BrowsingContext>>>,
+
     frame_element: Option<JS<Element>>,
 }
 
 impl BrowsingContext {
-    pub fn new_inherited(frame_element: Option<&Element>) -> BrowsingContext {
+    pub fn new_inherited(frame_element: Option<&Element>, id: PipelineId) -> BrowsingContext {
         BrowsingContext {
             reflector: Reflector::new(),
+            id: id,
+            needs_reflow: Cell::new(true),
             history: DOMRefCell::new(vec![]),
-            active_index: 0,
+            active_index: Cell::new(0),
+            children: DOMRefCell::new(vec![]),
             frame_element: frame_element.map(JS::from_ref),
         }
     }
 
     #[allow(unsafe_code)]
-    pub fn new(window: &Window, frame_element: Option<&Element>) -> Root<BrowsingContext> {
+    pub fn new(window: &Window, frame_element: Option<&Element>, id: PipelineId) -> Root<BrowsingContext> {
         unsafe {
             let WindowProxyHandler(handler) = window.windowproxy_handler();
             assert!(!handler.is_null());
 
             let cx = window.get_cx();
             let _ar = JSAutoRequest::new(cx);
             let parent = window.reflector().get_jsobject();
             assert!(!parent.get().is_null());
             assert!(((*JS_GetClass(parent.get())).flags & JSCLASS_IS_GLOBAL) != 0);
             let _ac = JSAutoCompartment::new(cx, parent.get());
             let window_proxy = RootedObject::new(cx,
                 NewWindowProxy(cx, parent, handler));
             assert!(!window_proxy.ptr.is_null());
 
-            let object = box BrowsingContext::new_inherited(frame_element);
+            let object = box BrowsingContext::new_inherited(frame_element, id);
 
             let raw = Box::into_raw(object);
             SetProxyExtra(window_proxy.ptr, 0, &PrivateValue(raw as *const _));
 
             (*raw).init_reflector(window_proxy.ptr);
 
             Root::from_ref(&*raw)
         }
     }
 
     pub fn init(&self, document: &Document) {
         assert!(self.history.borrow().is_empty());
-        assert_eq!(self.active_index, 0);
-        self.history.borrow_mut().push(SessionHistoryEntry::new(document));
+        assert_eq!(self.active_index.get(), 0);
+        self.history.borrow_mut().push(SessionHistoryEntry::new(document, document.url().clone(), document.Title()));
+    }
+
+    pub fn push_history(&self, document: &Document) {
+        let mut history = self.history.borrow_mut();
+        // Clear all session history entries after the active index
+        history.drain((self.active_index.get() + 1)..);
+        history.push(SessionHistoryEntry::new(document, document.url().clone(), document.Title()));
+        self.active_index.set(self.active_index.get() + 1);
+        assert_eq!(self.active_index.get(), history.len() - 1);
     }
 
     pub fn active_document(&self) -> Root<Document> {
-        Root::from_ref(&*self.history.borrow()[self.active_index].document)
+        Root::from_ref(&self.history.borrow()[self.active_index.get()].document)
     }
 
     pub fn active_window(&self) -> Root<Window> {
         Root::from_ref(self.active_document().window())
     }
 
     pub fn frame_element(&self) -> Option<&Element> {
         self.frame_element.r()
     }
 
     pub fn window_proxy(&self) -> *mut JSObject {
         let window_proxy = self.reflector.get_jsobject();
         assert!(!window_proxy.get().is_null());
         window_proxy.get()
     }
+
+    pub fn remove(&self, id: PipelineId) -> Option<Root<BrowsingContext>> {
+        let remove_idx = self.children
+                             .borrow()
+                             .iter()
+                             .position(|context| context.id == id);
+        match remove_idx {
+            Some(idx) => Some(Root::from_ref(&*self.children.borrow_mut().remove(idx))),
+            None => {
+                self.children
+                    .borrow_mut()
+                    .iter_mut()
+                    .filter_map(|context| context.remove(id))
+                    .next()
+            }
+        }
+    }
+
+    pub fn set_reflow_status(&self, status: bool) -> bool {
+        let old = self.needs_reflow.get();
+        self.needs_reflow.set(status);
+        old
+    }
+
+    pub fn pipeline(&self) -> PipelineId {
+        self.id
+    }
+
+    pub fn push_child_context(&self, context: &BrowsingContext) {
+        self.children.borrow_mut().push(JS::from_ref(&context));
+    }
+
+    pub fn find_child_by_subpage(&self, subpage_id: SubpageId) -> Option<Root<Window>> {
+        self.children.borrow().iter().find(|context| {
+            let window = context.active_window();
+            window.subpage() == Some(subpage_id)
+        }).map(|context| context.active_window())
+    }
+
+    pub fn clear_session_history(&self) {
+        self.active_index.set(0);
+        self.history.borrow_mut().clear();
+    }
+}
+
+pub struct ContextIterator {
+    stack: Vec<Root<BrowsingContext>>,
+}
+
+pub trait IterableContext {
+    fn iter(&self) -> ContextIterator;
+    fn find(&self, id: PipelineId) -> Option<Root<BrowsingContext>>;
+}
+
+impl IterableContext for BrowsingContext {
+    fn iter(&self) -> ContextIterator {
+        ContextIterator {
+            stack: vec!(Root::from_ref(self)),
+        }
+    }
+
+    fn find(&self, id: PipelineId) -> Option<Root<BrowsingContext>> {
+        if self.id == id {
+            return Some(Root::from_ref(self));
+        }
+
+        self.children.borrow()
+                     .iter()
+                     .filter_map(|c| c.find(id))
+                     .next()
+    }
+}
+
+impl Iterator for ContextIterator {
+    type Item = Root<BrowsingContext>;
+
+    fn next(&mut self) -> Option<Root<BrowsingContext>> {
+        let popped = self.stack.pop();
+        if let Some(ref context) = popped {
+            self.stack.extend(context.children.borrow()
+                                              .iter()
+                                              .map(|c| Root::from_ref(&**c)));
+        }
+        popped
+    }
 }
 
 // This isn't a DOM struct, just a convenience struct
 // without a reflector, so we don't mark this as #[dom_struct]
 #[must_root]
 #[privatize]
 #[derive(JSTraceable, HeapSizeOf)]
 pub struct SessionHistoryEntry {
     document: JS<Document>,
-    children: Vec<JS<BrowsingContext>>,
+    url: Url,
+    title: DOMString,
 }
 
 impl SessionHistoryEntry {
-    fn new(document: &Document) -> SessionHistoryEntry {
+    fn new(document: &Document, url: Url, title: DOMString) -> SessionHistoryEntry {
         SessionHistoryEntry {
             document: JS::from_ref(document),
-            children: vec![],
+            url: url,
+            title: title,
         }
     }
 }
 
 #[allow(unsafe_code)]
 unsafe fn GetSubframeWindow(cx: *mut JSContext,
                             proxy: HandleObject,
                             id: HandleId)
--- a/servo/components/script/dom/htmliframeelement.rs
+++ b/servo/components/script/dom/htmliframeelement.rs
@@ -31,17 +31,16 @@ use dom::virtualmethods::VirtualMethods;
 use dom::window::{ReflowReason, Window};
 use ipc_channel::ipc;
 use js::jsapi::{JSAutoCompartment, JSAutoRequest, RootedValue, JSContext, MutableHandleValue};
 use js::jsval::{UndefinedValue, NullValue};
 use layout_interface::ReflowQueryType;
 use msg::constellation_msg::{ConstellationChan, LoadData};
 use msg::constellation_msg::{NavigationDirection, PipelineId, SubpageId};
 use net_traits::response::HttpsState;
-use page::IterablePage;
 use script_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
 use script_traits::{IFrameLoadInfo, MozBrowserEvent, ScriptMsg as ConstellationMsg};
 use std::ascii::AsciiExt;
 use std::cell::Cell;
 use string_cache::Atom;
 use style::context::ReflowGoal;
 use url::Url;
 use util::prefs;
@@ -413,21 +412,18 @@ impl HTMLIFrameElementMethods for HTMLIF
         self.upcast::<Element>().set_tokenlist_attribute(&atom!("sandbox"), sandbox);
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-iframe-contentwindow
     fn GetContentWindow(&self) -> Option<Root<Window>> {
         self.subpage_id.get().and_then(|subpage_id| {
             let window = window_from_node(self);
             let window = window.r();
-            let children = window.page().children.borrow();
-            children.iter().find(|page| {
-                let window = page.window();
-                window.subpage() == Some(subpage_id)
-            }).map(|page| page.window())
+            let browsing_context = window.browsing_context();
+            browsing_context.find_child_by_subpage(subpage_id)
         })
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-iframe-contentdocument
     fn GetContentDocument(&self) -> Option<Root<Document>> {
         self.GetContentWindow().and_then(|window| {
             // FIXME(#10964): this should use the Document's origin and the
             //                origin of the incumbent settings object.
--- a/servo/components/script/dom/storage.rs
+++ b/servo/components/script/dom/storage.rs
@@ -5,22 +5,22 @@
 use dom::bindings::codegen::Bindings::StorageBinding;
 use dom::bindings::codegen::Bindings::StorageBinding::StorageMethods;
 use dom::bindings::error::{Error, ErrorResult};
 use dom::bindings::global::GlobalRef;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{Root, RootedReference};
 use dom::bindings::refcounted::Trusted;
 use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
+use dom::browsingcontext::IterableContext;
 use dom::event::{Event, EventBubbles, EventCancelable};
 use dom::storageevent::StorageEvent;
 use dom::urlhelper::UrlHelper;
 use ipc_channel::ipc;
 use net_traits::storage_thread::{StorageThread, StorageThreadMsg, StorageType};
-use page::IterablePage;
 use script_runtime::ScriptChan;
 use script_thread::{MainThreadRunnable, ScriptThread};
 use task_source::dom_manipulation::DOMManipulationTask;
 use url::Url;
 use util::str::DOMString;
 
 #[dom_struct]
 pub struct Storage {
@@ -194,19 +194,19 @@ impl MainThreadRunnable for StorageEvent
             global_ref,
             atom!("storage"),
             EventBubbles::DoesNotBubble, EventCancelable::NotCancelable,
             this.key.map(DOMString::from), this.old_value.map(DOMString::from), this.new_value.map(DOMString::from),
             DOMString::from(ev_url.to_string()),
             Some(storage)
         );
 
-        let root_page = script_thread.root_page();
-        for it_page in root_page.iter() {
-            let it_window_root = it_page.window();
+        let root_context = script_thread.root_browsing_context();
+        for it_context in root_context.iter() {
+            let it_window_root = it_context.active_window();
             let it_window = it_window_root.r();
             assert!(UrlHelper::SameOrigin(&ev_url, &it_window.get_url()));
             // TODO: Such a Document object is not necessarily fully active, but events fired on such
             // objects are ignored by the event loop until the Document becomes fully active again.
             if ev_window.pipeline() != it_window.pipeline() {
                 storage_event.upcast::<Event>().fire(it_window.upcast());
             }
         }
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -46,17 +46,16 @@ use libc;
 use msg::constellation_msg::{ConstellationChan, LoadData, PipelineId, SubpageId};
 use msg::constellation_msg::{WindowSizeData, WindowSizeType};
 use msg::webdriver_msg::{WebDriverJSError, WebDriverJSResult};
 use net_traits::ResourceThread;
 use net_traits::bluetooth_thread::BluetoothMethodMsg;
 use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread};
 use net_traits::storage_thread::{StorageThread, StorageType};
 use num_traits::ToPrimitive;
-use page::Page;
 use profile_traits::mem;
 use reporter::CSSErrorReporter;
 use rustc_serialize::base64::{FromBase64, STANDARD, ToBase64};
 use script_runtime::{ScriptChan, ScriptPort};
 use script_thread::SendableMainThreadScriptChan;
 use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, RunnableWrapper};
 use script_traits::{ConstellationControlMsg, UntrustedNodeAddress};
 use script_traits::{DocumentState, MsDuration, ScriptToCompositorMsg, TimerEvent, TimerEventId};
@@ -146,17 +145,16 @@ pub struct Window {
     navigator: MutNullableHeap<JS<Navigator>>,
     #[ignore_heap_size_of = "channels are hard"]
     image_cache_thread: ImageCacheThread,
     #[ignore_heap_size_of = "channels are hard"]
     image_cache_chan: ImageCacheChan,
     #[ignore_heap_size_of = "TODO(#6911) newtypes containing unmeasurable types are hard"]
     compositor: IpcSender<ScriptToCompositorMsg>,
     browsing_context: MutNullableHeap<JS<BrowsingContext>>,
-    page: Rc<Page>,
     performance: MutNullableHeap<JS<Performance>>,
     navigation_start: u64,
     navigation_start_precise: f64,
     screen: MutNullableHeap<JS<Screen>>,
     session_storage: MutNullableHeap<JS<Storage>>,
     local_storage: MutNullableHeap<JS<Storage>>,
     #[ignore_heap_size_of = "channels are hard"]
     scheduler_chan: IpcSender<TimerEventRequest>,
@@ -332,20 +330,16 @@ impl Window {
     pub fn compositor(&self) -> &IpcSender<ScriptToCompositorMsg> {
         &self.compositor
     }
 
     pub fn browsing_context(&self) -> Root<BrowsingContext> {
         self.browsing_context.get().unwrap()
     }
 
-    pub fn page(&self) -> &Page {
-        &*self.page
-    }
-
     pub fn bluetooth_thread(&self) -> IpcSender<BluetoothMethodMsg> {
         self.bluetooth_thread.clone()
     }
 
     pub fn storage_thread(&self) -> StorageThread {
         self.storage_thread.clone()
     }
 
@@ -1415,17 +1409,16 @@ impl Window {
             let context = window.browsing_context();
             context.active_window()
         })
     }
 }
 
 impl Window {
     pub fn new(runtime: Rc<Runtime>,
-               page: Rc<Page>,
                script_chan: MainThreadScriptChan,
                dom_task_source: DOMManipulationTaskSource,
                user_task_source: UserInteractionTaskSource,
                network_task_source: NetworkingTaskSource,
                history_task_source: HistoryTraversalTaskSource,
                file_task_source: FileReadingTaskSource,
                image_cache_chan: ImageCacheChan,
                compositor: IpcSender<ScriptToCompositorMsg>,
@@ -1462,17 +1455,16 @@ impl Window {
             user_interaction_task_source: user_task_source,
             networking_task_source: network_task_source,
             history_traversal_task_source: history_task_source,
             file_reading_task_source: file_task_source,
             image_cache_chan: image_cache_chan,
             console: Default::default(),
             crypto: Default::default(),
             compositor: compositor,
-            page: page,
             navigator: Default::default(),
             image_cache_thread: image_cache_thread,
             mem_profiler_chan: mem_profiler_chan,
             devtools_chan: devtools_chan,
             browsing_context: Default::default(),
             performance: Default::default(),
             navigation_start: (current_time.sec * 1000 + current_time.nsec as i64 / 1000000) as u64,
             navigation_start_precise: time::precise_time_ns() as f64,
--- a/servo/components/script/lib.rs
+++ b/servo/components/script/lib.rs
@@ -90,17 +90,16 @@ pub mod cors;
 mod devtools;
 pub mod document_loader;
 #[macro_use]
 pub mod dom;
 pub mod layout_interface;
 mod mem;
 mod network_listener;
 pub mod origin;
-pub mod page;
 pub mod parse;
 pub mod reporter;
 pub mod script_runtime;
 #[allow(unsafe_code)]
 pub mod script_thread;
 mod task_source;
 pub mod textinput;
 mod timers;
deleted file mode 100644
--- a/servo/components/script/page.rs
+++ /dev/null
@@ -1,134 +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 http://mozilla.org/MPL/2.0/. */
-
-use dom::bindings::cell::DOMRefCell;
-use dom::bindings::js::{JS, Root};
-use dom::document::Document;
-use dom::window::Window;
-use msg::constellation_msg::PipelineId;
-use std::cell::Cell;
-use std::rc::Rc;
-
-/// Encapsulates a handle to a frame in a frame tree.
-#[derive(JSTraceable, HeapSizeOf)]
-#[allow(unrooted_must_root)] // FIXME(#6687) this is wrong
-pub struct Page {
-    /// Pipeline id associated with this page.
-    id: PipelineId,
-
-    /// The outermost frame containing the document and window.
-    frame: DOMRefCell<Option<Frame>>,
-
-    /// Indicates if reflow is required when reloading.
-    needs_reflow: Cell<bool>,
-
-    // Child Pages.
-    pub children: DOMRefCell<Vec<Rc<Page>>>,
-}
-
-pub struct PageIterator {
-    stack: Vec<Rc<Page>>,
-}
-
-pub trait IterablePage {
-    fn iter(&self) -> PageIterator;
-    fn find(&self, id: PipelineId) -> Option<Rc<Page>>;
-}
-
-impl IterablePage for Rc<Page> {
-    fn iter(&self) -> PageIterator {
-        PageIterator {
-            stack: vec!(self.clone()),
-        }
-    }
-    fn find(&self, id: PipelineId) -> Option<Rc<Page>> {
-        if self.id == id {
-            return Some(self.clone());
-        }
-
-        self.children.borrow()
-                     .iter()
-                     .filter_map(|p| p.find(id))
-                     .next()
-    }
-
-}
-
-impl Page {
-    pub fn new(id: PipelineId) -> Page {
-        Page {
-            id: id,
-            frame: DOMRefCell::new(None),
-            needs_reflow: Cell::new(true),
-            children: DOMRefCell::new(vec!()),
-        }
-    }
-
-    pub fn pipeline(&self) -> PipelineId {
-        self.id
-    }
-
-    pub fn window(&self) -> Root<Window> {
-        Root::from_ref(&*self.frame.borrow().as_ref().unwrap().window)
-    }
-
-    pub fn document(&self) -> Root<Document> {
-        Root::from_ref(&*self.frame.borrow().as_ref().unwrap().document)
-    }
-
-    // must handle root case separately
-    pub fn remove(&self, id: PipelineId) -> Option<Rc<Page>> {
-        let remove_idx = {
-            self.children
-                .borrow()
-                .iter()
-                .position(|page_tree| page_tree.id == id)
-        };
-        match remove_idx {
-            Some(idx) => Some(self.children.borrow_mut().remove(idx)),
-            None => {
-                self.children
-                    .borrow_mut()
-                    .iter_mut()
-                    .filter_map(|page_tree| page_tree.remove(id))
-                    .next()
-            }
-        }
-    }
-}
-
-impl Iterator for PageIterator {
-    type Item = Rc<Page>;
-
-    fn next(&mut self) -> Option<Rc<Page>> {
-        let popped = self.stack.pop();
-        if let Some(ref page) = popped {
-            self.stack.extend(page.children.borrow().iter().cloned());
-        }
-        popped
-    }
-}
-
-impl Page {
-    pub fn set_reflow_status(&self, status: bool) -> bool {
-        let old = self.needs_reflow.get();
-        self.needs_reflow.set(status);
-        old
-    }
-
-    #[allow(unrooted_must_root)]
-    pub fn set_frame(&self, frame: Option<Frame>) {
-        *self.frame.borrow_mut() = frame;
-    }
-}
-
-/// Information for one frame in the browsing context.
-#[derive(JSTraceable, HeapSizeOf)]
-#[must_root]
-pub struct Frame {
-    /// The document for this frame.
-    pub document: JS<Document>,
-    /// The window object for this frame.
-    pub window: JS<Window>,
-}
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -27,17 +27,17 @@ use dom::bindings::codegen::Bindings::Do
 use dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior};
 use dom::bindings::global::GlobalRef;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{JS, MutNullableHeap, Root, RootCollection};
 use dom::bindings::js::{RootCollectionPtr, RootedReference};
 use dom::bindings::refcounted::{LiveDOMReferences, Trusted};
 use dom::bindings::trace::JSTraceable;
 use dom::bindings::utils::WRAP_CALLBACKS;
-use dom::browsingcontext::BrowsingContext;
+use dom::browsingcontext::{BrowsingContext, IterableContext};
 use dom::document::{Document, DocumentProgressHandler, DocumentSource, FocusType, IsHTMLDocument};
 use dom::element::Element;
 use dom::event::{Event, EventBubbles, EventCancelable};
 use dom::htmlanchorelement::HTMLAnchorElement;
 use dom::node::{Node, NodeDamage, window_from_node};
 use dom::servohtmlparser::ParserContext;
 use dom::uievent::UIEvent;
 use dom::window::{ReflowReason, ScriptHelpers, Window};
@@ -64,17 +64,16 @@ use msg::constellation_msg::{PipelineId,
 use msg::constellation_msg::{SubpageId, WindowSizeData, WindowSizeType};
 use msg::webdriver_msg::WebDriverScriptCommand;
 use net_traits::LoadData as NetLoadData;
 use net_traits::bluetooth_thread::BluetoothMethodMsg;
 use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheResult, ImageCacheThread};
 use net_traits::storage_thread::StorageThread;
 use net_traits::{AsyncResponseTarget, ControlMsg, LoadConsumer, LoadContext, Metadata, ResourceThread};
 use network_listener::NetworkListener;
-use page::{Frame, IterablePage, Page};
 use parse::ParserRoot;
 use parse::html::{ParseContext, parse_html};
 use parse::xml::{self, parse_xml};
 use profile_traits::mem::{self, OpaqueSender, Report, ReportKind, ReportsChan};
 use profile_traits::time::{self, ProfilerCategory, profile};
 use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory};
 use script_runtime::{ScriptPort, StackRootTLS, new_rt_and_cx, get_reports};
 use script_traits::CompositorEvent::{KeyEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent};
@@ -299,17 +298,17 @@ impl OpaqueSender<CommonScriptMsg> for S
 
 /// Information for an entire page. Pages are top-level browsing contexts and can contain multiple
 /// frames.
 #[derive(JSTraceable)]
 // ScriptThread instances are rooted on creation, so this is okay
 #[allow(unrooted_must_root)]
 pub struct ScriptThread {
     /// A handle to the information pertaining to page layout
-    page: DOMRefCell<Option<Rc<Page>>>,
+    browsing_context: MutNullableHeap<JS<BrowsingContext>>,
     /// A list of data pertaining to loads that have not yet received a network response
     incomplete_loads: DOMRefCell<Vec<InProgressLoad>>,
     /// A handle to the image cache thread.
     image_cache_thread: ImageCacheThread,
     /// A handle to the resource thread. This is an `Arc` to avoid running out of file descriptors if
     /// there are many iframes.
     resource_thread: Arc<ResourceThread>,
     /// A handle to the bluetooth thread.
@@ -404,22 +403,20 @@ impl<'a> ScriptMemoryFailsafe<'a> {
     }
 }
 
 impl<'a> Drop for ScriptMemoryFailsafe<'a> {
     #[allow(unrooted_must_root)]
     fn drop(&mut self) {
         match self.owner {
             Some(owner) => {
-                unsafe {
-                    let page = owner.page.borrow_for_script_deallocation();
-                    for page in page.iter() {
-                        let window = page.window();
-                        window.clear_js_runtime_for_script_deallocation();
-                    }
+                let context = owner.browsing_context.get();
+                for context in context.iter() {
+                    let window = context.active_window();
+                    window.clear_js_runtime_for_script_deallocation();
                 }
             }
             None => (),
         }
     }
 }
 
 impl ScriptThreadFactory for ScriptThread {
@@ -545,17 +542,17 @@ impl ScriptThread {
             ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_image_cache_port);
 
         let (timer_event_chan, timer_event_port) = channel();
 
         // Ask the router to proxy IPC messages from the control port to us.
         let control_port = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(state.control_port);
 
         ScriptThread {
-            page: DOMRefCell::new(None),
+            browsing_context: MutNullableHeap::new(None),
             incomplete_loads: DOMRefCell::new(vec!()),
 
             image_cache_thread: state.image_cache_thread,
             image_cache_channel: ImageCacheChan(ipc_image_cache_channel),
             image_cache_port: image_cache_port,
 
             resource_thread: Arc::new(state.resource_thread),
             bluetooth_thread: state.bluetooth_thread,
@@ -588,29 +585,29 @@ impl ScriptThread {
             scheduler_chan: state.scheduler_chan,
             timer_event_chan: timer_event_chan,
             timer_event_port: timer_event_port,
 
             content_process_shutdown_chan: state.content_process_shutdown_chan,
         }
     }
 
-    // Return the root page in the frame tree. Panics if it doesn't exist.
-    pub fn root_page(&self) -> Rc<Page> {
-        self.page.borrow().as_ref().unwrap().clone()
+    // Return the root browsing context in the frame tree. Panics if it doesn't exist.
+    pub fn root_browsing_context(&self) -> Root<BrowsingContext> {
+        self.browsing_context.get().unwrap()
     }
 
-    fn root_page_exists(&self) -> bool {
-        self.page.borrow().is_some()
+    fn root_browsing_context_exists(&self) -> bool {
+        self.browsing_context.get().is_some()
     }
 
-    /// Find a child page of the root page by pipeline id. Returns `None` if the root page does
-    /// not exist or the subpage cannot be found.
-    fn find_subpage(&self, pipeline_id: PipelineId) -> Option<Rc<Page>> {
-        self.page.borrow().as_ref().and_then(|page| page.find(pipeline_id))
+    /// Find a child browsing context of the root context by pipeline id. Returns `None` if the
+    /// root context does not exist or the child context cannot be found.
+    fn find_child_context(&self, pipeline_id: PipelineId) -> Option<Root<BrowsingContext>> {
+        self.browsing_context.get().and_then(|context| context.find(pipeline_id))
     }
 
     pub fn get_cx(&self) -> *mut JSContext {
         self.js_runtime.cx()
     }
 
     /// Starts the script thread. After calling this method, the script thread will loop receiving
     /// messages on its port.
@@ -623,27 +620,25 @@ impl ScriptThread {
     /// Handle incoming control messages.
     fn handle_msgs(&self) -> bool {
         use self::MixedMessage::{FromScript, FromConstellation, FromScheduler, FromDevtools, FromImageCache};
 
         // Handle pending resize events.
         // Gather them first to avoid a double mut borrow on self.
         let mut resizes = vec!();
 
-        {
-            let page = self.page.borrow();
-            if let Some(page) = page.as_ref() {
-                for page in page.iter() {
-                    // Only process a resize if layout is idle.
-                    let window = page.window();
-                    let resize_event = window.steal_resize_event();
-                    match resize_event {
-                        Some(size) => resizes.push((window.pipeline(), size)),
-                        None => ()
-                    }
+        let context = self.browsing_context.get();
+        if let Some(context) = context {
+            for context in context.iter() {
+                // Only process a resize if layout is idle.
+                let window = context.active_window();
+                let resize_event = window.steal_resize_event();
+                match resize_event {
+                    Some(size) => resizes.push((window.pipeline(), size)),
+                    None => ()
                 }
             }
         }
 
         for (id, (size, size_type)) in resizes {
             self.handle_event(id, ResizeEvent(size, size_type));
         }
 
@@ -777,20 +772,20 @@ impl ScriptThread {
             if let Some(retval) = result {
                 return retval
             }
         }
 
         // Issue batched reflows on any pages that require it (e.g. if images loaded)
         // TODO(gw): In the future we could probably batch other types of reflows
         // into this loop too, but for now it's only images.
-        let page = self.page.borrow();
-        if let Some(page) = page.as_ref() {
-            for page in page.iter() {
-                let window = page.window();
+        let context = self.browsing_context.get();
+        if let Some(context) = context {
+            for context in context.iter() {
+                let window = context.active_window();
                 let pending_reflows = window.get_pending_reflow_count();
                 if pending_reflows > 0 {
                     window.reflow(ReflowGoal::ForDisplay,
                                   ReflowQueryType::NoQuery,
                                   ReflowReason::ImageLoaded);
                 } else {
                     // Reflow currently happens when explicitly invoked by code that
                     // knows the document could have been modified. This should really
@@ -941,124 +936,124 @@ impl ScriptThread {
     fn handle_timer_event(&self, timer_event: TimerEvent) {
         let TimerEvent(source, id) = timer_event;
 
         let pipeline_id = match source {
             TimerSource::FromWindow(pipeline_id) => pipeline_id,
             TimerSource::FromWorker => panic!("Worker timeouts must not be sent to script thread"),
         };
 
-        let page = self.root_page();
-        let page = page.find(pipeline_id).expect("ScriptThread: received fire timer msg for a
+        let context = self.root_browsing_context();
+        let context = context.find(pipeline_id).expect("ScriptThread: received fire timer msg for a
             pipeline ID not associated with this script thread. This is a bug.");
-        let window = page.window();
+        let window = context.active_window();
 
         window.handle_fire_timer(id);
     }
 
     fn handle_msg_from_devtools(&self, msg: DevtoolScriptControlMsg) {
-        let page = self.root_page();
+        let context = self.root_browsing_context();
         match msg {
             DevtoolScriptControlMsg::EvaluateJS(id, s, reply) => {
-                let window = get_page(&page, id).window();
+                let window = get_browsing_context(&context, id).active_window();
                 let global_ref = GlobalRef::Window(window.r());
                 devtools::handle_evaluate_js(&global_ref, s, reply)
             },
             DevtoolScriptControlMsg::GetRootNode(id, reply) =>
-                devtools::handle_get_root_node(&page, id, reply),
+                devtools::handle_get_root_node(&context, id, reply),
             DevtoolScriptControlMsg::GetDocumentElement(id, reply) =>
-                devtools::handle_get_document_element(&page, id, reply),
+                devtools::handle_get_document_element(&context, id, reply),
             DevtoolScriptControlMsg::GetChildren(id, node_id, reply) =>
-                devtools::handle_get_children(&page, id, node_id, reply),
+                devtools::handle_get_children(&context, id, node_id, reply),
             DevtoolScriptControlMsg::GetLayout(id, node_id, reply) =>
-                devtools::handle_get_layout(&page, id, node_id, reply),
+                devtools::handle_get_layout(&context, id, node_id, reply),
             DevtoolScriptControlMsg::GetCachedMessages(pipeline_id, message_types, reply) =>
                 devtools::handle_get_cached_messages(pipeline_id, message_types, reply),
             DevtoolScriptControlMsg::ModifyAttribute(id, node_id, modifications) =>
-                devtools::handle_modify_attribute(&page, id, node_id, modifications),
+                devtools::handle_modify_attribute(&context, id, node_id, modifications),
             DevtoolScriptControlMsg::WantsLiveNotifications(id, to_send) => {
-                let window = get_page(&page, id).window();
+                let window = get_browsing_context(&context, id).active_window();
                 let global_ref = GlobalRef::Window(window.r());
                 devtools::handle_wants_live_notifications(&global_ref, to_send)
             },
             DevtoolScriptControlMsg::SetTimelineMarkers(_pipeline_id, marker_types, reply) =>
-                devtools::handle_set_timeline_markers(&page, marker_types, reply),
+                devtools::handle_set_timeline_markers(&context, marker_types, reply),
             DevtoolScriptControlMsg::DropTimelineMarkers(_pipeline_id, marker_types) =>
-                devtools::handle_drop_timeline_markers(&page, marker_types),
+                devtools::handle_drop_timeline_markers(&context, marker_types),
             DevtoolScriptControlMsg::RequestAnimationFrame(pipeline_id, name) =>
-                devtools::handle_request_animation_frame(&page, pipeline_id, name),
+                devtools::handle_request_animation_frame(&context, pipeline_id, name),
         }
     }
 
     fn handle_msg_from_image_cache(&self, msg: ImageCacheResult) {
         msg.responder.unwrap().respond(msg.image_response);
     }
 
     fn handle_webdriver_msg(&self, pipeline_id: PipelineId, msg: WebDriverScriptCommand) {
-        let page = self.root_page();
+        let context = self.root_browsing_context();
         match msg {
             WebDriverScriptCommand::ExecuteScript(script, reply) =>
-                webdriver_handlers::handle_execute_script(&page, pipeline_id, script, reply),
+                webdriver_handlers::handle_execute_script(&context, pipeline_id, script, reply),
             WebDriverScriptCommand::FindElementCSS(selector, reply) =>
-                webdriver_handlers::handle_find_element_css(&page, pipeline_id, selector, reply),
+                webdriver_handlers::handle_find_element_css(&context, pipeline_id, selector, reply),
             WebDriverScriptCommand::FindElementsCSS(selector, reply) =>
-                webdriver_handlers::handle_find_elements_css(&page, pipeline_id, selector, reply),
+                webdriver_handlers::handle_find_elements_css(&context, pipeline_id, selector, reply),
             WebDriverScriptCommand::FocusElement(element_id, reply) =>
-                webdriver_handlers::handle_focus_element(&page, pipeline_id, element_id, reply),
+                webdriver_handlers::handle_focus_element(&context, pipeline_id, element_id, reply),
             WebDriverScriptCommand::GetActiveElement(reply) =>
-                webdriver_handlers::handle_get_active_element(&page, pipeline_id, reply),
+                webdriver_handlers::handle_get_active_element(&context, pipeline_id, reply),
             WebDriverScriptCommand::GetElementTagName(node_id, reply) =>
-                webdriver_handlers::handle_get_name(&page, pipeline_id, node_id, reply),
+                webdriver_handlers::handle_get_name(&context, pipeline_id, node_id, reply),
             WebDriverScriptCommand::GetElementAttribute(node_id, name, reply) =>
-                webdriver_handlers::handle_get_attribute(&page, pipeline_id, node_id, name, reply),
+                webdriver_handlers::handle_get_attribute(&context, pipeline_id, node_id, name, reply),
             WebDriverScriptCommand::GetElementCSS(node_id, name, reply) =>
-                webdriver_handlers::handle_get_css(&page, pipeline_id, node_id, name, reply),
+                webdriver_handlers::handle_get_css(&context, pipeline_id, node_id, name, reply),
             WebDriverScriptCommand::GetElementRect(node_id, reply) =>
-                webdriver_handlers::handle_get_rect(&page, pipeline_id, node_id, reply),
+                webdriver_handlers::handle_get_rect(&context, pipeline_id, node_id, reply),
             WebDriverScriptCommand::GetElementText(node_id, reply) =>
-                webdriver_handlers::handle_get_text(&page, pipeline_id, node_id, reply),
+                webdriver_handlers::handle_get_text(&context, pipeline_id, node_id, reply),
             WebDriverScriptCommand::GetFrameId(frame_id, reply) =>
-                webdriver_handlers::handle_get_frame_id(&page, pipeline_id, frame_id, reply),
+                webdriver_handlers::handle_get_frame_id(&context, pipeline_id, frame_id, reply),
             WebDriverScriptCommand::GetUrl(reply) =>
-                webdriver_handlers::handle_get_url(&page, pipeline_id, reply),
+                webdriver_handlers::handle_get_url(&context, pipeline_id, reply),
             WebDriverScriptCommand::GetWindowSize(reply) =>
-                webdriver_handlers::handle_get_window_size(&page, pipeline_id, reply),
+                webdriver_handlers::handle_get_window_size(&context, pipeline_id, reply),
             WebDriverScriptCommand::IsEnabled(element_id, reply) =>
-                webdriver_handlers::handle_is_enabled(&page, pipeline_id, element_id, reply),
+                webdriver_handlers::handle_is_enabled(&context, pipeline_id, element_id, reply),
             WebDriverScriptCommand::IsSelected(element_id, reply) =>
-                webdriver_handlers::handle_is_selected(&page, pipeline_id, element_id, reply),
+                webdriver_handlers::handle_is_selected(&context, pipeline_id, element_id, reply),
             WebDriverScriptCommand::GetTitle(reply) =>
-                webdriver_handlers::handle_get_title(&page, pipeline_id, reply),
+                webdriver_handlers::handle_get_title(&context, pipeline_id, reply),
             WebDriverScriptCommand::ExecuteAsyncScript(script, reply) =>
-                webdriver_handlers::handle_execute_async_script(&page, pipeline_id, script, reply),
+                webdriver_handlers::handle_execute_async_script(&context, pipeline_id, script, reply),
         }
     }
 
     fn handle_resize(&self, id: PipelineId, size: WindowSizeData, size_type: WindowSizeType) {
-        if let Some(ref page) = self.find_subpage(id) {
-            let window = page.window();
+        if let Some(ref context) = self.find_child_context(id) {
+            let window = context.active_window();
             window.set_resize_event(size, size_type);
             return;
         }
         let mut loads = self.incomplete_loads.borrow_mut();
         if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
             load.window_size = Some(size);
             return;
         }
         panic!("resize sent to nonexistent pipeline");
     }
 
     fn handle_viewport(&self, id: PipelineId, rect: Rect<f32>) {
-        let page = self.page.borrow();
-        if let Some(page) = page.as_ref() {
-            if let Some(ref inner_page) = page.find(id) {
-                let window = inner_page.window();
+        let context = self.browsing_context.get();
+        if let Some(context) = context {
+            if let Some(inner_context) = context.find(id) {
+                let window = inner_context.active_window();
                 if window.set_page_clip_rect_with_new_viewport(rect) {
-                    let page = get_page(page, id);
-                    self.rebuild_and_force_reflow(&*page, ReflowReason::Viewport);
+                    let context = get_browsing_context(&context, id);
+                    self.rebuild_and_force_reflow(&context, ReflowReason::Viewport);
                 }
                 return;
             }
         }
         let mut loads = self.incomplete_loads.borrow_mut();
         if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
             load.clip_rect = Some(rect);
             return;
@@ -1094,38 +1089,38 @@ impl ScriptThread {
             panic_chan: panic_chan,
             paint_chan: paint_chan,
             script_chan: self.control_chan.clone(),
             image_cache_thread: self.image_cache_thread.clone(),
             layout_shutdown_chan: layout_shutdown_chan,
             content_process_shutdown_chan: content_process_shutdown_chan,
         };
 
-        let page = self.root_page();
-        let parent_page = page.find(containing_pipeline_id).expect("ScriptThread: received a layout
+        let context = self.root_browsing_context();
+        let parent_context = context.find(containing_pipeline_id).expect("ScriptThread: received a layout
             whose parent has a PipelineId which does not correspond to a pipeline in the script
-            thread's page tree. This is a bug.");
-        let parent_window = parent_page.window();
+            thread's browsing context tree. This is a bug.");
+        let parent_window = parent_context.active_window();
 
         // Tell layout to actually spawn the thread.
         parent_window.layout_chan()
                      .0
                      .send(layout_interface::Msg::CreateLayoutThread(layout_creation_info))
                      .unwrap();
 
         // Kick off the fetch for the new resource.
         let new_load = InProgressLoad::new(new_pipeline_id, Some((containing_pipeline_id, subpage_id)),
                                            layout_chan, parent_window.window_size(),
                                            load_data.url.clone());
         self.start_page_load(new_load, load_data);
     }
 
     fn handle_loads_complete(&self, pipeline: PipelineId) {
-        let page = get_page(&self.root_page(), pipeline);
-        let doc = page.document();
+        let context = get_browsing_context(&self.root_browsing_context(), pipeline);
+        let doc = context.active_document();
         let doc = doc.r();
         if doc.loader().is_blocked() {
             return;
         }
 
         doc.mut_loader().inhibit_events();
 
         // https://html.spec.whatwg.org/multipage/#the-end step 7
@@ -1136,24 +1131,24 @@ impl ScriptThread {
         chan.send(ConstellationMsg::LoadComplete(pipeline)).unwrap();
     }
 
     fn collect_reports(&self, reports_chan: ReportsChan) {
         let mut urls = vec![];
         let mut dom_tree_size = 0;
         let mut reports = vec![];
 
-        if let Some(root_page) = self.page.borrow().as_ref() {
-            for it_page in root_page.iter() {
-                let current_url = it_page.document().url().to_string();
+        if let Some(root_context) = self.browsing_context.get() {
+            for it_context in root_context.iter() {
+                let current_url = it_context.active_document().url().to_string();
 
-                for child in it_page.document().upcast::<Node>().traverse_preorder() {
+                for child in it_context.active_document().upcast::<Node>().traverse_preorder() {
                     dom_tree_size += heap_size_of_self_and_children(&*child);
                 }
-                let window = it_page.window();
+                let window = it_context.active_window();
                 dom_tree_size += heap_size_of_self_and_children(&*window);
 
                 reports.push(Report {
                     path: path![format!("url({})", current_url), "dom-tree"],
                     kind: ReportKind::ExplicitJemallocHeapSize,
                     size: dom_tree_size,
                 });
                 urls.push(current_url);
@@ -1161,123 +1156,123 @@ impl ScriptThread {
         }
         let path_seg = format!("url({})", urls.join(", "));
         reports.extend(get_reports(self.get_cx(), path_seg));
         reports_chan.send(reports);
     }
 
     /// Handles freeze message
     fn handle_freeze_msg(&self, id: PipelineId) {
-        if let Some(root_page) = self.page.borrow().as_ref() {
-            if let Some(ref inner_page) = root_page.find(id) {
-                let window = inner_page.window();
+        if let Some(root_context) = self.browsing_context.get() {
+            if let Some(ref inner_context) = root_context.find(id) {
+                let window = inner_context.active_window();
                 window.freeze();
                 return;
             }
         }
         let mut loads = self.incomplete_loads.borrow_mut();
         if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
             load.is_frozen = true;
             return;
         }
         panic!("freeze sent to nonexistent pipeline");
     }
 
     /// Handles thaw message
     fn handle_thaw_msg(&self, id: PipelineId) {
-        if let Some(ref inner_page) = self.root_page().find(id) {
-            let needed_reflow = inner_page.set_reflow_status(false);
+        if let Some(inner_context) = self.root_browsing_context().find(id) {
+            let needed_reflow = inner_context.set_reflow_status(false);
             if needed_reflow {
-                self.rebuild_and_force_reflow(&*inner_page, ReflowReason::CachedPageNeededReflow);
+                self.rebuild_and_force_reflow(&inner_context, ReflowReason::CachedPageNeededReflow);
             }
-            let window = inner_page.window();
+            let window = inner_context.active_window();
             window.thaw();
             return;
         }
         let mut loads = self.incomplete_loads.borrow_mut();
         if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
             load.is_frozen = false;
             return;
         }
         panic!("thaw sent to nonexistent pipeline");
     }
 
     fn handle_focus_iframe_msg(&self,
                                parent_pipeline_id: PipelineId,
                                subpage_id: SubpageId) {
-        let borrowed_page = self.root_page();
-        let page = borrowed_page.find(parent_pipeline_id).unwrap();
+        let borrowed_context = self.root_browsing_context();
+        let context = borrowed_context.find(parent_pipeline_id).unwrap();
 
-        let doc = page.document();
+        let doc = context.active_document();
         let frame_element = doc.find_iframe(subpage_id);
 
         if let Some(ref frame_element) = frame_element {
             doc.begin_focus_transaction();
             doc.request_focus(frame_element.upcast());
             doc.commit_focus_transaction(FocusType::Parent);
         }
     }
 
     fn handle_framed_content_changed(&self,
                                      parent_pipeline_id: PipelineId,
                                      subpage_id: SubpageId) {
-        let borrowed_page = self.root_page();
-        let page = borrowed_page.find(parent_pipeline_id).unwrap();
-        let doc = page.document();
+        let root_context = self.root_browsing_context();
+        let context = root_context.find(parent_pipeline_id).unwrap();
+        let doc = context.active_document();
         let frame_element = doc.find_iframe(subpage_id);
         if let Some(ref frame_element) = frame_element {
             frame_element.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
-            let window = page.window();
+            let window = context.active_window();
             window.reflow(ReflowGoal::ForDisplay,
                           ReflowQueryType::NoQuery,
                           ReflowReason::FramedContentChanged);
         }
     }
 
     /// Handles a mozbrowser event, for example see:
     /// https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadstart
     fn handle_mozbrowser_event_msg(&self,
                                    parent_pipeline_id: PipelineId,
                                    subpage_id: SubpageId,
                                    event: MozBrowserEvent) {
-        let borrowed_page = self.root_page();
+        let borrowed_context = self.root_browsing_context();
 
-        let frame_element = borrowed_page.find(parent_pipeline_id).and_then(|page| {
-            let doc = page.document();
+        let frame_element = borrowed_context.find(parent_pipeline_id).and_then(|context| {
+            let doc = context.active_document();
             doc.find_iframe(subpage_id)
         });
 
         if let Some(ref frame_element) = frame_element {
             frame_element.dispatch_mozbrowser_event(event);
         }
     }
 
     fn handle_update_subpage_id(&self,
                                 containing_pipeline_id: PipelineId,
                                 old_subpage_id: SubpageId,
                                 new_subpage_id: SubpageId,
                                 new_pipeline_id: PipelineId) {
-        let borrowed_page = self.root_page();
+        let borrowed_context = self.root_browsing_context();
 
-        let frame_element = borrowed_page.find(containing_pipeline_id).and_then(|page| {
-            let doc = page.document();
+        let frame_element = borrowed_context.find(containing_pipeline_id).and_then(|context| {
+            let doc = context.active_document();
             doc.find_iframe(old_subpage_id)
         });
 
         frame_element.unwrap().update_subpage_id(new_subpage_id, new_pipeline_id);
     }
 
     /// Window was resized, but this script was not active, so don't reflow yet
     fn handle_resize_inactive_msg(&self, id: PipelineId, new_size: WindowSizeData) {
-        let page = self.root_page();
-        let page = page.find(id).expect("Received resize message for PipelineId not associated
-            with a page in the page tree. This is a bug.");
-        let window = page.window();
+        let context = self.root_browsing_context();
+        let context = context.find(id).expect("Received resize message for PipelineId not associated
+            with a browsing context in the browsing context tree. This is a bug.");
+        let window = context.active_window();
         window.set_window_size(new_size);
-        page.set_reflow_status(true);
+        context.set_reflow_status(true);
     }
 
     /// We have gotten a window.close from script, which we pass on to the compositor.
     /// We do not shut down the script thread now, because the compositor will ask the
     /// constellation to shut down the pipeline, which will clean everything up
     /// normally. If we do exit, we will tear down the DOM nodes, possibly at a point
     /// where layout is still accessing them.
     fn handle_exit_window_msg(&self, _: PipelineId) {
@@ -1307,18 +1302,18 @@ impl ScriptThread {
                 assert!(self.closed_pipelines.borrow().contains(id));
                 None
             }
         }
     }
 
     /// Handles a request for the window title.
     fn handle_get_title_msg(&self, pipeline_id: PipelineId) {
-        let page = get_page(&self.root_page(), pipeline_id);
-        let document = page.document();
+        let context = get_browsing_context(&self.root_browsing_context(), pipeline_id);
+        let document = context.active_document();
         document.send_title_to_compositor();
     }
 
     /// Handles a request to exit the script thread and shut down layout.
     /// Returns true if the script thread should shut down and false otherwise.
     fn handle_exit_pipeline_msg(&self, id: PipelineId) -> bool {
         self.closed_pipelines.borrow_mut().insert(id);
 
@@ -1336,57 +1331,56 @@ impl ScriptThread {
             let LayoutChan(chan) = load.layout_chan;
             if chan.send(layout_interface::Msg::PrepareToExit(response_chan)).is_ok() {
                 debug!("shutting down layout for page {:?}", id);
                 response_port.recv().unwrap();
                 chan.send(layout_interface::Msg::ExitNow).ok();
             }
 
             let has_pending_loads = self.incomplete_loads.borrow().len() > 0;
-            let has_root_page = self.page.borrow().is_some();
+            let has_root_context = self.root_browsing_context_exists();
 
-            // Exit if no pending loads and no root page
-            return !has_pending_loads && !has_root_page;
+            // Exit if no pending loads and no root context
+            return !has_pending_loads && !has_root_context;
         }
 
-        // If root is being exited, shut down all pages
-        let page = self.root_page();
-        let window = page.window();
+        // If root is being exited, shut down all contexts
+        let context = self.root_browsing_context();
+        let window = context.active_window();
         if window.pipeline() == id {
-            debug!("shutting down layout for root page {:?}", id);
-            shut_down_layout(&page);
+            debug!("shutting down layout for root context {:?}", id);
+            shut_down_layout(&context);
             return true
         }
 
-        // otherwise find just the matching page and exit all sub-pages
-        if let Some(ref mut child_page) = page.remove(id) {
-            debug!("shutting down layout for child context {:?}", id);
-            shut_down_layout(&*child_page);
+        // otherwise find just the matching context and exit all sub-contexts
+        if let Some(ref mut child_context) = context.remove(id) {
+            shut_down_layout(&child_context);
         }
         false
     }
 
     /// Handles when layout thread finishes all animation in one tick
     fn handle_tick_all_animations(&self, id: PipelineId) {
-        let page = get_page(&self.root_page(), id);
-        let document = page.document();
+        let context = get_browsing_context(&self.root_browsing_context(), id);
+        let document = context.active_document();
         document.run_the_animation_frame_callbacks();
     }
 
     /// Handles a Web font being loaded. Does nothing if the page no longer exists.
     fn handle_web_font_loaded(&self, pipeline_id: PipelineId) {
-        if let Some(ref page) = self.find_subpage(pipeline_id)  {
-            self.rebuild_and_force_reflow(page, ReflowReason::WebFontLoaded);
+        if let Some(context) = self.find_child_context(pipeline_id)  {
+            self.rebuild_and_force_reflow(&context, ReflowReason::WebFontLoaded);
         }
     }
 
     /// Notify the containing document of a child frame that has completed loading.
     fn handle_frame_load_event(&self, containing_pipeline: PipelineId, id: PipelineId) {
-        let page = get_page(&self.root_page(), containing_pipeline);
-        let document = page.document();
+        let context = get_browsing_context(&self.root_browsing_context(), containing_pipeline);
+        let document = context.active_document();
         if let Some(iframe) = document.find_iframe_by_pipeline(id) {
             iframe.iframe_load_event_steps(id);
         }
     }
 
     /// The entry point to document loading. Defines bindings, sets up the window and document
     /// objects, parses HTML and CSS, and kicks off initial layout.
     fn load(&self, metadata: Metadata, incomplete: InProgressLoad) -> ParserRoot {
@@ -1395,108 +1389,51 @@ impl ScriptThread {
             // send the final url to the layout thread.
             let LayoutChan(ref chan) = incomplete.layout_chan;
             chan.send(layout_interface::Msg::SetFinalUrl(final_url.clone())).unwrap();
 
             // update the pipeline url
             let ConstellationChan(ref chan) = self.constellation_chan;
             chan.send(ConstellationMsg::SetFinalUrl(incomplete.pipeline_id, final_url.clone())).unwrap();
         }
-        debug!("ScriptThread: loading {} on page {:?}", incomplete.url, incomplete.pipeline_id);
+        debug!("ScriptThread: loading {} on pipeline {:?}", incomplete.url, incomplete.pipeline_id);
 
         let frame_element = incomplete.parent_info.and_then(|(parent_id, subpage_id)| {
-            // The root page may not exist yet, if the parent of this frame
+            // The root context may not exist yet, if the parent of this frame
             // exists in a different script thread.
-            let borrowed_page = self.page.borrow();
+            let root_context = self.browsing_context.get();
 
-            // In the case a parent id exists but the matching page
-            // cannot be found, this means the page exists in a different
+            // In the case a parent id exists but the matching context
+            // cannot be found, this means the context exists in a different
             // script thread (due to origin) so it shouldn't be returned.
             // TODO: window.parent will continue to return self in that
             // case, which is wrong. We should be returning an object that
             // denies access to most properties (per
             // https://github.com/servo/servo/issues/3939#issuecomment-62287025).
-            borrowed_page.as_ref().and_then(|borrowed_page| {
-                borrowed_page.find(parent_id).and_then(|page| {
-                    let doc = page.document();
+            root_context.and_then(|root_context| {
+                root_context.find(parent_id).and_then(|context| {
+                    let doc = context.active_document();
                     doc.find_iframe(subpage_id)
                 })
             })
         });
 
-        // Create a new frame tree entry.
-        let page = Rc::new(Page::new(incomplete.pipeline_id));
-        if !self.root_page_exists() {
-            // We have a new root frame tree.
-            *self.page.borrow_mut() = Some(page.clone());
-        } else if let Some((parent, _)) = incomplete.parent_info {
-            // We have a new child frame.
-            let parent_page = self.root_page();
-            // TODO(gw): This find will fail when we are sharing script threads
-            // between cross origin iframes in the same TLD.
-            let parent_page = parent_page.find(parent)
-                                         .expect("received load for subpage with missing parent");
-            parent_page.children.borrow_mut().push(page.clone());
-        }
-
-        enum PageToRemove {
-            Root,
-            Child(PipelineId),
-        }
-        struct AutoPageRemover<'a> {
-            page: PageToRemove,
-            script_thread: &'a ScriptThread,
-            neutered: bool,
-        }
-        impl<'a> AutoPageRemover<'a> {
-            fn new(script_thread: &'a ScriptThread, page: PageToRemove) -> AutoPageRemover<'a> {
-                AutoPageRemover {
-                    page: page,
-                    script_thread: script_thread,
-                    neutered: false,
-                }
-            }
-
-            fn neuter(&mut self) {
-                self.neutered = true;
-            }
-        }
-        impl<'a> Drop for AutoPageRemover<'a> {
-            fn drop(&mut self) {
-                if !self.neutered {
-                    match self.page {
-                        PageToRemove::Root => *self.script_thread.page.borrow_mut() = None,
-                        PageToRemove::Child(id) => {
-                            self.script_thread.root_page().remove(id).unwrap();
-                        }
-                    }
-                }
-            }
-        }
-
-        let page_to_remove = if !self.root_page_exists() {
-            PageToRemove::Root
-        } else {
-            PageToRemove::Child(incomplete.pipeline_id)
-        };
-        let mut page_remover = AutoPageRemover::new(self, page_to_remove);
         let MainThreadScriptChan(ref sender) = self.chan;
         let DOMManipulationTaskSource(ref dom_sender) = self.dom_manipulation_task_source;
         let UserInteractionTaskSource(ref user_sender) = self.user_interaction_task_source;
         let NetworkingTaskSource(ref network_sender) = self.networking_task_source;
         let HistoryTraversalTaskSource(ref history_sender) = self.history_traversal_task_source;
         let FileReadingTaskSource(ref file_sender) = self.file_reading_task_source;
 
         let (ipc_timer_event_chan, ipc_timer_event_port) = ipc::channel().unwrap();
         ROUTER.route_ipc_receiver_to_mpsc_sender(ipc_timer_event_port,
                                                  self.timer_event_chan.clone());
 
         // Create the window and document objects.
         let window = Window::new(self.js_runtime.clone(),
-                                 page.clone(),
                                  MainThreadScriptChan(sender.clone()),
                                  DOMManipulationTaskSource(dom_sender.clone()),
                                  UserInteractionTaskSource(user_sender.clone()),
                                  NetworkingTaskSource(network_sender.clone()),
                                  HistoryTraversalTaskSource(history_sender.clone()),
                                  FileReadingTaskSource(file_sender.clone()),
                                  self.image_cache_channel.clone(),
                                  self.compositor.borrow_mut().clone(),
@@ -1509,68 +1446,130 @@ impl ScriptThread {
                                  self.constellation_chan.clone(),
                                  self.control_chan.clone(),
                                  self.scheduler_chan.clone(),
                                  ipc_timer_event_chan,
                                  incomplete.layout_chan,
                                  incomplete.pipeline_id,
                                  incomplete.parent_info,
                                  incomplete.window_size);
+        let frame_element = frame_element.r().map(Castable::upcast);
 
-        let frame_element = frame_element.r().map(Castable::upcast);
-        let browsing_context = BrowsingContext::new(&window, frame_element);
+        enum ContextToRemove {
+            Root,
+            Child(PipelineId),
+            None,
+        }
+        struct AutoContextRemover<'a> {
+            context: ContextToRemove,
+            script_thread: &'a ScriptThread,
+            neutered: bool,
+        }
+        impl<'a> AutoContextRemover<'a> {
+            fn new(script_thread: &'a ScriptThread, context: ContextToRemove) -> AutoContextRemover<'a> {
+                AutoContextRemover {
+                    context: context,
+                    script_thread: script_thread,
+                    neutered: false,
+                }
+            }
+
+            fn neuter(&mut self) {
+                self.neutered = true;
+            }
+        }
+
+        impl<'a> Drop for AutoContextRemover<'a> {
+            fn drop(&mut self) {
+                if !self.neutered {
+                    match self.context {
+                        ContextToRemove::Root => {
+                            self.script_thread.browsing_context.set(None)
+                        },
+                        ContextToRemove::Child(id) => {
+                            self.script_thread.root_browsing_context().remove(id).unwrap();
+                        },
+                        ContextToRemove::None => {},
+                    }
+                }
+            }
+        }
+
+        let mut using_new_context = true;
+
+        let (browsing_context, context_to_remove) = if !self.root_browsing_context_exists() {
+            // Create a new context tree entry. This will become the root context.
+            let new_context = BrowsingContext::new(&window, frame_element, incomplete.pipeline_id);
+            // We have a new root frame tree.
+            self.browsing_context.set(Some(&new_context));
+            (new_context, ContextToRemove::Root)
+        } else if let Some((parent, _)) = incomplete.parent_info {
+            // Create a new context tree entry. This will be a child context.
+            let new_context = BrowsingContext::new(&window, frame_element, incomplete.pipeline_id);
+
+            let root_context = self.root_browsing_context();
+            // TODO(gw): This find will fail when we are sharing script threads
+            // between cross origin iframes in the same TLD.
+            let parent_context = root_context.find(parent)
+                                             .expect("received load for child context with missing parent");
+            parent_context.push_child_context(&*new_context);
+            (new_context, ContextToRemove::Child(incomplete.pipeline_id))
+        } else {
+            using_new_context = false;
+            (self.root_browsing_context(), ContextToRemove::None)
+        };
+
         window.init_browsing_context(&browsing_context);
+        let mut context_remover = AutoContextRemover::new(self, context_to_remove);
 
         let last_modified = metadata.headers.as_ref().and_then(|headers| {
             headers.get().map(|&LastModified(HttpDate(ref tm))| dom_last_modified(tm))
         });
 
         let content_type = metadata.content_type.as_ref().and_then(|&ContentType(ref mimetype)| {
             match *mimetype {
                 Mime(TopLevel::Application, SubLevel::Xml, _) |
                 Mime(TopLevel::Application, SubLevel::Ext(_), _) |
                 Mime(TopLevel::Text, SubLevel::Xml, _) |
                 Mime(TopLevel::Text, SubLevel::Plain, _) => Some(DOMString::from(mimetype.to_string())),
                 _ => None,
             }
         });
 
         let loader = DocumentLoader::new_with_thread(self.resource_thread.clone(),
-                                                   Some(page.pipeline()),
-                                                   Some(incomplete.url.clone()));
+                                                     Some(browsing_context.pipeline()),
+                                                     Some(incomplete.url.clone()));
 
         let is_html_document = match metadata.content_type {
             Some(ContentType(Mime(TopLevel::Application, SubLevel::Xml, _))) |
             Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) =>
                 IsHTMLDocument::NonHTMLDocument,
             _ => IsHTMLDocument::HTMLDocument,
         };
 
         let document = Document::new(window.r(),
                                      Some(&browsing_context),
                                      Some(final_url.clone()),
                                      is_html_document,
                                      content_type,
                                      last_modified,
                                      DocumentSource::FromParser,
                                      loader);
-        browsing_context.init(&document);
+        if using_new_context {
+            browsing_context.init(&document);
+        } else {
+            browsing_context.push_history(&document);
+        }
         document.set_ready_state(DocumentReadyState::Loading);
 
-        // Create the root frame
-        page.set_frame(Some(Frame {
-            document: JS::from_rooted(&document),
-            window: JS::from_rooted(&window),
-        }));
-
         let ConstellationChan(ref chan) = self.constellation_chan;
         chan.send(ConstellationMsg::ActivateDocument(incomplete.pipeline_id)).unwrap();
 
         // Notify devtools that a new script global exists.
-        self.notify_devtools(document.Title(), final_url.clone(), (page.pipeline(), None));
+        self.notify_devtools(document.Title(), final_url.clone(), (browsing_context.pipeline(), None));
 
         let is_javascript = incomplete.url.scheme() == "javascript";
         let parse_input = if is_javascript {
             use url::percent_encoding::percent_decode;
 
             // Turn javascript: URL into JS code to eval, according to the steps in
             // https://html.spec.whatwg.org/multipage/#javascript-protocol
             let _ar = JSAutoRequest::new(self.get_cx());
@@ -1620,17 +1619,17 @@ impl ScriptThread {
                        final_url,
                        ParseContext::Owner(Some(incomplete.pipeline_id)));
         }
 
         if incomplete.is_frozen {
             window.freeze();
         }
 
-        page_remover.neuter();
+        context_remover.neuter();
 
         document.get_current_parser().unwrap()
     }
 
     fn notify_devtools(&self, title: DOMString, url: Url, ids: (PipelineId, Option<WorkerId>)) {
         if let Some(ref chan) = self.devtools_chan {
             let page_info = DevtoolsPageInfo {
                 title: String::from(title),
@@ -1659,45 +1658,45 @@ impl ScriptThread {
         let point = Point2D::new(rect.origin.x.to_nearest_px() as f32,
                                  rect.origin.y.to_nearest_px() as f32);
 
         self.compositor.borrow_mut().send(ScriptToCompositorMsg::ScrollFragmentPoint(
                                                  pipeline_id, LayerId::null(), point, false)).unwrap();
     }
 
     /// Reflows non-incrementally, rebuilding the entire layout tree in the process.
-    fn rebuild_and_force_reflow(&self, page: &Page, reason: ReflowReason) {
-        let document = page.document();
+    fn rebuild_and_force_reflow(&self, context: &BrowsingContext, reason: ReflowReason) {
+        let document = context.active_document();
         document.dirty_all_nodes();
         let window = window_from_node(document.r());
         window.reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, reason);
     }
 
     /// This is the main entry point for receiving and dispatching DOM events.
     ///
     /// TODO: Actually perform DOM event dispatch.
     fn handle_event(&self, pipeline_id: PipelineId, event: CompositorEvent) {
 
-        // DOM events can only be handled if there's a root page.
-        if !self.root_page_exists() {
+        // DOM events can only be handled if there's a root browsing context.
+        if !self.root_browsing_context_exists() {
             return;
         }
 
         match event {
             ResizeEvent(new_size, size_type) => {
                 self.handle_resize_event(pipeline_id, new_size, size_type);
             }
 
             MouseButtonEvent(event_type, button, point) => {
                 self.handle_mouse_event(pipeline_id, event_type, button, point);
             }
 
             MouseMoveEvent(point) => {
-                let page = get_page(&self.root_page(), pipeline_id);
-                let document = page.document();
+                let context = get_browsing_context(&self.root_browsing_context(), pipeline_id);
+                let document = context.active_document();
 
                 // Get the previous target temporarily
                 let prev_mouse_over_target = self.topmost_mouse_over_target.get();
 
                 document.handle_mouse_move_event(self.js_runtime.rt(), point,
                                                  &self.topmost_mouse_over_target);
 
                 // Short-circuit if nothing changed
@@ -1760,61 +1759,61 @@ impl ScriptThread {
                     }
                     _ => {
                         // TODO: Calling preventDefault on a touchup event should prevent clicks.
                     }
                 }
             }
 
             TouchpadPressureEvent(point, pressure, phase) => {
-                let page = get_page(&self.root_page(), pipeline_id);
-                let document = page.document();
+                let context = get_browsing_context(&self.root_browsing_context(), pipeline_id);
+                let document = context.active_document();
                 document.r().handle_touchpad_pressure_event(self.js_runtime.rt(), point, pressure, phase);
             }
 
             KeyEvent(key, state, modifiers) => {
-                let page = get_page(&self.root_page(), pipeline_id);
-                let document = page.document();
+                let context = get_browsing_context(&self.root_browsing_context(), pipeline_id);
+                let document = context.active_document();
                 document.dispatch_key_event(
                     key, state, modifiers, &mut self.compositor.borrow_mut());
             }
         }
     }
 
     fn handle_mouse_event(&self,
                           pipeline_id: PipelineId,
                           mouse_event_type: MouseEventType,
                           button: MouseButton,
                           point: Point2D<f32>) {
-        let page = get_page(&self.root_page(), pipeline_id);
-        let document = page.document();
+        let context = get_browsing_context(&self.root_browsing_context(), pipeline_id);
+        let document = context.active_document();
         document.handle_mouse_event(self.js_runtime.rt(), button, point, mouse_event_type);
     }
 
     fn handle_touch_event(&self,
                           pipeline_id: PipelineId,
                           event_type: TouchEventType,
                           identifier: TouchId,
                           point: Point2D<f32>)
                           -> bool {
-        let page = get_page(&self.root_page(), pipeline_id);
-        let document = page.document();
+        let context = get_browsing_context(&self.root_browsing_context(), pipeline_id);
+        let document = context.active_document();
         document.handle_touch_event(self.js_runtime.rt(), event_type, identifier, point)
     }
 
     /// https://html.spec.whatwg.org/multipage/#navigating-across-documents
     /// The entry point for content to notify that a new load has been requested
     /// for the given pipeline (specifically the "navigate" algorithm).
     fn handle_navigate(&self, pipeline_id: PipelineId, subpage_id: Option<SubpageId>, load_data: LoadData) {
         // Step 8.
         {
             let nurl = &load_data.url;
             if let Some(fragment) = nurl.fragment() {
-                let page = get_page(&self.root_page(), pipeline_id);
-                let document = page.document();
+                let context = get_browsing_context(&self.root_browsing_context(), pipeline_id);
+                let document = context.active_document();
                 let document = document.r();
                 let url = document.url();
                 if &url[..Position::AfterQuery] == &nurl[..Position::AfterQuery] &&
                     load_data.method == Method::Get {
                     match document.find_fragment_node(fragment) {
                         Some(ref node) => {
                             self.scroll_fragment_point(pipeline_id, node.r());
                         }
@@ -1822,41 +1821,41 @@ impl ScriptThread {
                     }
                     return;
                 }
             }
         }
 
         match subpage_id {
             Some(subpage_id) => {
-                let borrowed_page = self.root_page();
-                let iframe = borrowed_page.find(pipeline_id).and_then(|page| {
-                    let doc = page.document();
+                let root_context = self.root_browsing_context();
+                let iframe = root_context.find(pipeline_id).and_then(|context| {
+                    let doc = context.active_document();
                     doc.find_iframe(subpage_id)
                 });
                 if let Some(iframe) = iframe.r() {
                     iframe.navigate_or_reload_child_browsing_context(Some(load_data));
                 }
             }
             None => {
                 let ConstellationChan(ref const_chan) = self.constellation_chan;
                 const_chan.send(ConstellationMsg::LoadUrl(pipeline_id, load_data)).unwrap();
             }
         }
     }
 
     fn handle_resize_event(&self, pipeline_id: PipelineId, new_size: WindowSizeData, size_type: WindowSizeType) {
-        let page = get_page(&self.root_page(), pipeline_id);
-        let window = page.window();
+        let context = get_browsing_context(&self.root_browsing_context(), pipeline_id);
+        let window = context.active_window();
         window.set_window_size(new_size);
         window.force_reflow(ReflowGoal::ForDisplay,
                             ReflowQueryType::NoQuery,
                             ReflowReason::WindowResize);
 
-        let document = page.document();
+        let document = context.active_document();
         let fragment_node = window.steal_fragment_name()
                                   .and_then(|name| document.find_fragment_node(&*name));
         match fragment_node {
             Some(ref node) => self.scroll_fragment_point(pipeline_id, node.r()),
             None => {}
         }
 
         // http://dev.w3.org/csswg/cssom-view/#resizing-viewports
@@ -1906,64 +1905,64 @@ impl ScriptThread {
             referrer_policy: load_data.referrer_policy,
             referrer_url: load_data.referrer_url,
         }, LoadConsumer::Listener(response_target), None)).unwrap();
 
         self.incomplete_loads.borrow_mut().push(incomplete);
     }
 
     fn handle_parsing_complete(&self, id: PipelineId) {
-        let parent_page = self.root_page();
-        let page = match parent_page.find(id) {
-            Some(page) => page,
+        let parent_context = self.root_browsing_context();
+        let context = match parent_context.find(id) {
+            Some(context) => context,
             None => return,
         };
 
-        let document = page.document();
+        let document = context.active_document();
         let final_url = document.url();
 
         // https://html.spec.whatwg.org/multipage/#the-end step 1
         document.set_ready_state(DocumentReadyState::Interactive);
 
         // TODO: Execute step 2 here.
 
         // Kick off the initial reflow of the page.
         debug!("kicking off initial reflow of {:?}", final_url);
         document.disarm_reflow_timeout();
         document.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
         let window = window_from_node(document.r());
         window.reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, ReflowReason::FirstLoad);
 
         // No more reflow required
-        page.set_reflow_status(false);
+        context.set_reflow_status(false);
 
         // https://html.spec.whatwg.org/multipage/#the-end steps 3-4.
         document.process_deferred_scripts();
 
         window.set_fragment_name(final_url.fragment().map(str::to_owned));
     }
 
     fn handle_css_error_reporting(&self, pipeline_id: PipelineId, filename: String,
                                   line: usize, column: usize, msg: String) {
-        let parent_page = self.root_page();
-        let page = match parent_page.find(pipeline_id) {
-            Some(page) => page,
+        let parent_context = self.root_browsing_context();
+        let context = match parent_context.find(pipeline_id) {
+            Some(context) => context,
             None => return,
         };
 
-        let document = page.document();
+        let document = context.active_document();
         let css_error = CSSError {
             filename: filename,
             line: line,
             column: column,
             msg: msg
         };
 
         document.report_css_error(css_error.clone());
-        let window = page.window();
+        let window = context.active_window();
 
         if window.live_devtools_updates() {
             if let Some(ref chan) = self.devtools_chan {
                 chan.send(ScriptToDevtoolsControlMsg::ReportCSSError(
                     pipeline_id,
                     css_error)).unwrap();
              }
         }
@@ -1973,47 +1972,50 @@ impl ScriptThread {
 impl Drop for ScriptThread {
     fn drop(&mut self) {
         SCRIPT_THREAD_ROOT.with(|root| {
             *root.borrow_mut() = None;
         });
     }
 }
 
-/// Shuts down layout for the given page tree.
-fn shut_down_layout(page_tree: &Rc<Page>) {
+/// Shuts down layout for the given browsing context tree.
+fn shut_down_layout(context_tree: &BrowsingContext) {
     let mut channels = vec!();
 
-    for page in page_tree.iter() {
+    for context in context_tree.iter() {
         // Tell the layout thread to begin shutting down, and wait until it
         // processed this message.
         let (response_chan, response_port) = channel();
-        let window = page.window();
+        let window = context.active_window();
         let LayoutChan(chan) = window.layout_chan().clone();
         if chan.send(layout_interface::Msg::PrepareToExit(response_chan)).is_ok() {
             channels.push(chan);
             response_port.recv().unwrap();
         }
     }
 
     // Drop our references to the JSContext and DOM objects.
-    for page in page_tree.iter() {
-        let window = page.window();
+    for context in context_tree.iter() {
+        let window = context.active_window();
         window.clear_js_runtime();
+
         // Sever the connection between the global and the DOM tree
-        page.set_frame(None);
+        context.clear_session_history();
     }
 
     // Destroy the layout thread. If there were node leaks, layout will now crash safely.
     for chan in channels {
         chan.send(layout_interface::Msg::ExitNow).ok();
     }
 }
 
-pub fn get_page(page: &Rc<Page>, pipeline_id: PipelineId) -> Rc<Page> {
-    page.find(pipeline_id).expect("ScriptThread: received an event \
-        message for a layout channel that is not associated with this script thread.\
-         This is a bug.")
+pub fn get_browsing_context(context: &BrowsingContext,
+                            pipeline_id: PipelineId)
+                            -> Root<BrowsingContext> {
+    context.find(pipeline_id).expect("ScriptThread: received an event \
+            message for a layout channel that is not associated with this script thread.\
+            This is a bug.")
 }
 
 fn dom_last_modified(tm: &Tm) -> String {
     tm.to_local().strftime("%m/%d/%Y %H:%M:%S").unwrap().to_string()
 }
--- a/servo/components/script/webdriver_handlers.rs
+++ b/servo/components/script/webdriver_handlers.rs
@@ -10,41 +10,43 @@ use dom::bindings::codegen::Bindings::HT
 use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
 use dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
 use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
 use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
 use dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior};
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::Root;
+use dom::browsingcontext::BrowsingContext;
 use dom::element::Element;
 use dom::htmlelement::HTMLElement;
 use dom::htmliframeelement::HTMLIFrameElement;
 use dom::htmlinputelement::HTMLInputElement;
 use dom::htmloptionelement::HTMLOptionElement;
 use dom::node::Node;
 use dom::window::ScriptHelpers;
 use euclid::point::Point2D;
 use euclid::rect::Rect;
 use euclid::size::Size2D;
 use ipc_channel::ipc::IpcSender;
 use js::jsapi::JSContext;
 use js::jsapi::{HandleValue, RootedValue};
 use js::jsval::UndefinedValue;
 use msg::constellation_msg::{PipelineId, WindowSizeData};
 use msg::webdriver_msg::{WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue};
-use page::Page;
-use script_thread::get_page;
-use std::rc::Rc;
+use script_thread::get_browsing_context;
 use url::Url;
 use util::str::DOMString;
 
-fn find_node_by_unique_id(page: &Rc<Page>, pipeline: PipelineId, node_id: String) -> Option<Root<Node>> {
-    let page = get_page(&*page, pipeline);
-    let document = page.document();
+fn find_node_by_unique_id(context: &BrowsingContext,
+                          pipeline: PipelineId,
+                          node_id: String)
+                          -> Option<Root<Node>> {
+    let context = get_browsing_context(&context, pipeline);
+    let document = context.active_document();
     document.upcast::<Node>().traverse_preorder().find(|candidate| candidate.unique_id() == node_id)
 }
 
 #[allow(unsafe_code)]
 pub unsafe fn jsval_to_webdriver(cx: *mut JSContext, val: HandleValue) -> WebDriverJSResult {
     if val.get().is_undefined() {
         Ok(WebDriverJSValue::Undefined)
     } else if val.get().is_boolean() {
@@ -58,138 +60,138 @@ pub unsafe fn jsval_to_webdriver(cx: *mu
     } else if val.get().is_null() {
         Ok(WebDriverJSValue::Null)
     } else {
         Err(WebDriverJSError::UnknownType)
     }
 }
 
 #[allow(unsafe_code)]
-pub fn handle_execute_script(page: &Rc<Page>,
+pub fn handle_execute_script(context: &BrowsingContext,
                              pipeline: PipelineId,
                              eval: String,
                              reply: IpcSender<WebDriverJSResult>) {
-    let page = get_page(&*page, pipeline);
-    let window = page.window();
+    let context = get_browsing_context(&context, pipeline);
+    let window = context.active_window();
     let result = unsafe {
         let cx = window.get_cx();
         let mut rval = RootedValue::new(cx, UndefinedValue());
         window.evaluate_js_on_global_with_result(&eval, rval.handle_mut());
         jsval_to_webdriver(cx, rval.handle())
     };
     reply.send(result).unwrap();
 }
 
-pub fn handle_execute_async_script(page: &Rc<Page>,
+pub fn handle_execute_async_script(context: &BrowsingContext,
                                    pipeline: PipelineId,
                                    eval: String,
                                    reply: IpcSender<WebDriverJSResult>) {
-    let page = get_page(&*page, pipeline);
-    let window = page.window();
+    let context = get_browsing_context(&context, pipeline);
+    let window = context.active_window();
     let cx = window.get_cx();
     window.set_webdriver_script_chan(Some(reply));
     let mut rval = RootedValue::new(cx, UndefinedValue());
     window.evaluate_js_on_global_with_result(&eval, rval.handle_mut());
 }
 
-pub fn handle_get_frame_id(page: &Rc<Page>,
+pub fn handle_get_frame_id(context: &BrowsingContext,
                            pipeline: PipelineId,
                            webdriver_frame_id: WebDriverFrameId,
                            reply: IpcSender<Result<Option<PipelineId>, ()>>) {
     let window = match webdriver_frame_id {
         WebDriverFrameId::Short(_) => {
             // This isn't supported yet
             Ok(None)
         },
         WebDriverFrameId::Element(x) => {
-            match find_node_by_unique_id(page, pipeline, x) {
+            match find_node_by_unique_id(context, pipeline, x) {
                 Some(ref node) => {
                     match node.downcast::<HTMLIFrameElement>() {
                         Some(ref elem) => Ok(elem.GetContentWindow()),
                         None => Err(())
                     }
                 },
                 None => Err(())
             }
         },
         WebDriverFrameId::Parent => {
-            let window = page.window();
+            let window = context.active_window();
             Ok(window.parent())
         }
     };
 
     let frame_id = window.map(|x| x.map(|x| x.pipeline()));
     reply.send(frame_id).unwrap()
 }
 
-pub fn handle_find_element_css(page: &Rc<Page>, _pipeline: PipelineId, selector: String,
+pub fn handle_find_element_css(context: &BrowsingContext, _pipeline: PipelineId, selector: String,
                                reply: IpcSender<Result<Option<String>, ()>>) {
-    reply.send(match page.document().QuerySelector(DOMString::from(selector)) {
+    reply.send(match context.active_document().QuerySelector(DOMString::from(selector)) {
         Ok(node) => {
             Ok(node.map(|x| x.upcast::<Node>().unique_id()))
         }
         Err(_) => Err(())
     }).unwrap();
 }
 
-pub fn handle_find_elements_css(page: &Rc<Page>,
+pub fn handle_find_elements_css(context: &BrowsingContext,
                                 _pipeline: PipelineId,
                                 selector: String,
                                 reply: IpcSender<Result<Vec<String>, ()>>) {
-    reply.send(match page.document().QuerySelectorAll(DOMString::from(selector)) {
+    reply.send(match context.active_document().QuerySelectorAll(DOMString::from(selector)) {
         Ok(ref nodes) => {
             let mut result = Vec::with_capacity(nodes.Length() as usize);
             for i in 0..nodes.Length() {
                 if let Some(ref node) = nodes.Item(i) {
                     result.push(node.unique_id());
                 }
             }
             Ok(result)
         },
         Err(_) => {
             Err(())
         }
     }).unwrap();
 }
 
-pub fn handle_focus_element(page: &Rc<Page>,
+pub fn handle_focus_element(context: &BrowsingContext,
                             pipeline: PipelineId,
                             element_id: String,
                             reply: IpcSender<Result<(), ()>>) {
-    reply.send(match find_node_by_unique_id(page, pipeline, element_id) {
+    reply.send(match find_node_by_unique_id(context, pipeline, element_id) {
         Some(ref node) => {
             match node.downcast::<HTMLElement>() {
                 Some(ref elem) => {
                     // Need a way to find if this actually succeeded
                     elem.Focus();
                     Ok(())
                 }
                 None => Err(())
             }
         },
         None => Err(())
     }).unwrap();
 }
 
-pub fn handle_get_active_element(page: &Rc<Page>,
+pub fn handle_get_active_element(context: &BrowsingContext,
                                  _pipeline: PipelineId,
                                  reply: IpcSender<Option<String>>) {
-    reply.send(page.document().GetActiveElement().map(
+    reply.send(context.active_document().GetActiveElement().map(
         |elem| elem.upcast::<Node>().unique_id())).unwrap();
 }
 
-pub fn handle_get_title(page: &Rc<Page>, _pipeline: PipelineId, reply: IpcSender<String>) {
-    reply.send(String::from(page.document().Title())).unwrap();
+pub fn handle_get_title(context: &BrowsingContext, _pipeline: PipelineId, reply: IpcSender<String>) {
+    reply.send(String::from(context.active_document().Title())).unwrap();
 }
 
-pub fn handle_get_rect(page: &Rc<Page>,
+pub fn handle_get_rect(context: &BrowsingContext,
                        pipeline: PipelineId,
                        element_id: String,
                        reply: IpcSender<Result<Rect<f64>, ()>>) {
-    reply.send(match find_node_by_unique_id(&*page, pipeline, element_id) {
+    reply.send(match find_node_by_unique_id(context, pipeline, element_id) {
         Some(elem) => {
             // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-calculate-the-absolute-position
             match elem.downcast::<HTMLElement>() {
                 Some(html_elem) => {
                     // Step 1
                     let mut x = 0;
                     let mut y = 0;
 
@@ -213,106 +215,106 @@ pub fn handle_get_rect(page: &Rc<Page>,
                 },
                 None => Err(())
             }
         },
         None => Err(())
     }).unwrap();
 }
 
-pub fn handle_get_text(page: &Rc<Page>,
+pub fn handle_get_text(context: &BrowsingContext,
                        pipeline: PipelineId,
                        node_id: String,
                        reply: IpcSender<Result<String, ()>>) {
-    reply.send(match find_node_by_unique_id(&*page, pipeline, node_id) {
+    reply.send(match find_node_by_unique_id(context, pipeline, node_id) {
         Some(ref node) => {
             Ok(node.GetTextContent().map_or("".to_owned(), String::from))
         },
         None => Err(())
     }).unwrap();
 }
 
-pub fn handle_get_name(page: &Rc<Page>,
+pub fn handle_get_name(context: &BrowsingContext,
                        pipeline: PipelineId,
                        node_id: String,
                        reply: IpcSender<Result<String, ()>>) {
-    reply.send(match find_node_by_unique_id(&*page, pipeline, node_id) {
+    reply.send(match find_node_by_unique_id(context, pipeline, node_id) {
         Some(node) => {
             Ok(String::from(node.downcast::<Element>().unwrap().TagName()))
         },
         None => Err(())
     }).unwrap();
 }
 
-pub fn handle_get_attribute(page: &Rc<Page>,
+pub fn handle_get_attribute(context: &BrowsingContext,
                             pipeline: PipelineId,
                             node_id: String,
                             name: String,
                             reply: IpcSender<Result<Option<String>, ()>>) {
-    reply.send(match find_node_by_unique_id(&*page, pipeline, node_id) {
+    reply.send(match find_node_by_unique_id(context, pipeline, node_id) {
         Some(node) => {
             Ok(node.downcast::<Element>().unwrap().GetAttribute(DOMString::from(name))
                .map(String::from))
         },
         None => Err(())
     }).unwrap();
 }
 
-pub fn handle_get_css(page: &Rc<Page>,
+pub fn handle_get_css(context: &BrowsingContext,
                       pipeline: PipelineId,
                       node_id: String,
                       name: String,
                       reply: IpcSender<Result<String, ()>>) {
-    reply.send(match find_node_by_unique_id(&*page, pipeline, node_id) {
+    reply.send(match find_node_by_unique_id(context, pipeline, node_id) {
         Some(node) => {
-            let window = page.window();
+            let window = context.active_window();
             let elem = node.downcast::<Element>().unwrap();
             Ok(String::from(
                 window.GetComputedStyle(&elem, None).GetPropertyValue(DOMString::from(name))))
         },
         None => Err(())
     }).unwrap();
 }
 
-pub fn handle_get_url(page: &Rc<Page>,
+pub fn handle_get_url(context: &BrowsingContext,
                       _pipeline: PipelineId,
                       reply: IpcSender<Url>) {
-    let document = page.document();
+    let document = context.active_document();
     let url = document.url();
     reply.send((*url).clone()).unwrap();
 }
 
-pub fn handle_get_window_size(page: &Rc<Page>,
+pub fn handle_get_window_size(context: &BrowsingContext,
                               _pipeline: PipelineId,
                               reply: IpcSender<Option<WindowSizeData>>) {
-    let window = page.window();
+    let window = context.active_window();
     let size = window.window_size();
     reply.send(size).unwrap();
 }
 
-pub fn handle_is_enabled(page: &Rc<Page>,
+pub fn handle_is_enabled(context: &BrowsingContext,
                          pipeline: PipelineId,
                          element_id: String,
                          reply: IpcSender<Result<bool, ()>>) {
-    reply.send(match find_node_by_unique_id(page, pipeline, element_id) {
+    reply.send(match find_node_by_unique_id(&context, pipeline, element_id) {
         Some(ref node) => {
             match node.downcast::<Element>() {
                 Some(elem) => Ok(elem.enabled_state()),
                 None => Err(())
             }
         },
         None => Err(())
     }).unwrap();
 }
 
-pub fn handle_is_selected(page: &Rc<Page>,
+pub fn handle_is_selected(context: &BrowsingContext,
                           pipeline: PipelineId,
                           element_id: String,
                           reply: IpcSender<Result<bool, ()>>) {
-    reply.send(match find_node_by_unique_id(page, pipeline, element_id) {
+    reply.send(match find_node_by_unique_id(context, pipeline, element_id) {
         Some(ref node) => {
             if let Some(input_element) = node.downcast::<HTMLInputElement>() {
                 Ok(input_element.Checked())
             }
             else if let Some(option_element) = node.downcast::<HTMLOptionElement>() {
                 Ok(option_element.Selected())
             }
             else if let Some(_) = node.downcast::<HTMLElement>() {