servo: Merge #12685 - Implement history.length (from cbrewster:history_length); r=asajeffrey
authorConnor Brewster <connor.brewster@eagles.oc.edu>
Wed, 03 Aug 2016 22:22:57 -0500
changeset 339430 db7be3dee0adac16d1e76fa8f613701ee0b6e2b3
parent 339429 8c0b61a2f4a4a86af64e1a7acb888b9e18b4a4e0
child 339431 ec09d7ccb461c62b46c4168f2f6ce7cd04b7ea9b
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)
reviewersasajeffrey
servo: Merge #12685 - Implement history.length (from cbrewster:history_length); r=asajeffrey <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [x] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 7aafc0d0ec898f7981a094a6b698fa94e1360a21
servo/components/constellation/constellation.rs
servo/components/script/dom/history.rs
servo/components/script/dom/webidls/History.webidl
servo/components/script_traits/script_msg.rs
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -216,24 +216,26 @@ pub struct InitialConstellationState {
     /// Whether the constellation supports the clipboard.
     pub supports_clipboard: bool,
     /// Optional webrender API reference (if enabled).
     pub webrender_api_sender: Option<webrender_traits::RenderApiSender>,
 }
 
 /// Stores the navigation context for a single frame in the frame tree.
 struct Frame {
+    id: FrameId,
     prev: Vec<(PipelineId, Instant)>,
     current: (PipelineId, Instant),
     next: Vec<(PipelineId, Instant)>,
 }
 
 impl Frame {
-    fn new(pipeline_id: PipelineId) -> Frame {
+    fn new(id: FrameId, pipeline_id: PipelineId) -> Frame {
         Frame {
+            id: id,
             prev: vec!(),
             current: (pipeline_id, Instant::now()),
             next: vec!(),
         }
     }
 
     fn load(&mut self, pipeline_id: PipelineId) {
         self.prev.push(self.current);
@@ -285,16 +287,47 @@ impl<'a> Iterator for FrameTreeIterator<
                 },
             };
             self.stack.extend(pipeline.children.iter().map(|&c| c));
             return Some(frame)
         }
     }
 }
 
+struct FullFrameTreeIterator<'a> {
+    stack: Vec<FrameId>,
+    frames: &'a HashMap<FrameId, Frame>,
+    pipelines: &'a HashMap<PipelineId, Pipeline>,
+}
+
+impl<'a> Iterator for FullFrameTreeIterator<'a> {
+    type Item = &'a Frame;
+    fn next(&mut self) -> Option<&'a Frame> {
+        loop {
+            let frame_id = match self.stack.pop() {
+                Some(frame_id) => frame_id,
+                None => return None,
+            };
+            let frame = match self.frames.get(&frame_id) {
+                Some(frame) => frame,
+                None => {
+                    warn!("Frame {:?} iterated after closure.", frame_id);
+                    continue;
+                },
+            };
+            for &(pipeline_id, _) in frame.prev.iter().chain(frame.next.iter()).chain(once(&frame.current)) {
+                if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
+                    self.stack.extend(pipeline.children.iter().map(|&c| c));
+                }
+            }
+            return Some(frame)
+        }
+    }
+}
+
 struct WebDriverData {
     load_channel: Option<(PipelineId, IpcSender<webdriver_msg::LoadStatus>)>,
     resize_channel: Option<IpcSender<WindowSizeData>>,
 }
 
 impl WebDriverData {
     fn new() -> WebDriverData {
         WebDriverData {
@@ -573,87 +606,56 @@ impl<Message, LTF, STF> Constellation<Me
     fn current_frame_tree_iter(&self, frame_id_root: Option<FrameId>) -> FrameTreeIterator {
         FrameTreeIterator {
             stack: frame_id_root.into_iter().collect(),
             pipelines: &self.pipelines,
             frames: &self.frames,
         }
     }
 
+    fn full_frame_tree_iter(&self, frame_id_root: FrameId) -> FullFrameTreeIterator {
+        FullFrameTreeIterator {
+            stack: vec!(frame_id_root),
+            pipelines: &self.pipelines,
+            frames: &self.frames,
+        }
+    }
+
     fn joint_session_future(&self, frame_id_root: FrameId) -> Vec<(Instant, FrameId, PipelineId)> {
         let mut future = vec!();
-        self.get_future_entries(frame_id_root, &mut future);
+        for frame in self.full_frame_tree_iter(frame_id_root) {
+            future.extend(frame.next.iter().map(|&(pipeline_id, instant)| (instant, frame.id, pipeline_id)));
+        }
 
         // reverse sorting
         future.sort_by(|a, b| b.cmp(a));
         future
     }
 
-    fn get_future_entries(&self, frame_id_root: FrameId, mut future: &mut Vec<(Instant, FrameId, PipelineId)>) {
-        let frame = match self.frames.get(&frame_id_root) {
-            Some(frame) => frame,
-            None => return warn!("Tried to get frame future after frame {:?} closed.", frame_id_root),
-        };
-
-        future.extend(frame.next.iter().map(|&(pipeline_id, instant)| (instant, frame_id_root, pipeline_id)));
-
-        for &(pipeline_id, _) in frame.next.iter().chain(once(&frame.current)) {
-            let pipeline = match self.pipelines.get(&pipeline_id) {
-                Some(pipeline) => pipeline,
-                None => {
-                    warn!("Tried to get pipeline {:?} for child lookup after closure.", pipeline_id);
-                    continue;
-                }
-            };
-            for &frame_id in &pipeline.children {
-                self.get_future_entries(frame_id, &mut future);
+    fn joint_session_past(&self, frame_id_root: FrameId) -> Vec<(Instant, FrameId, PipelineId)> {
+        let mut past = vec!();
+        for frame in self.full_frame_tree_iter(frame_id_root) {
+            let mut prev_instant = frame.current.1;
+            for &(pipeline_id, instant) in frame.prev.iter().rev() {
+                past.push((prev_instant, frame.id, pipeline_id));
+                prev_instant = instant;
             }
         }
-    }
-
-    fn joint_session_past(&self, frame_id_root: FrameId) -> Vec<(Instant, FrameId, PipelineId)> {
-        let mut past = vec!();
-        self.get_past_entries(frame_id_root, &mut past);
 
         past.sort();
         past
     }
 
-    fn get_past_entries(&self, frame_id_root: FrameId, mut past: &mut Vec<(Instant, FrameId, PipelineId)>) {
-        let frame = match self.frames.get(&frame_id_root) {
-            Some(frame) => frame,
-            None => return warn!("Tried to get frame past after frame {:?} closed.", frame_id_root),
-        };
-
-        let mut prev_instant = frame.current.1;
-        for &(pipeline_id, instant) in frame.prev.iter().rev() {
-            past.push((prev_instant, frame_id_root, pipeline_id));
-            prev_instant = instant;
-        }
-        for &(pipeline_id, _) in frame.prev.iter().chain(once(&frame.current)) {
-            let pipeline = match self.pipelines.get(&pipeline_id) {
-                Some(pipeline) => pipeline,
-                None => {
-                    warn!("Tried to get pipeline {:?} for child lookup after closure.", pipeline_id);
-                    continue;
-                }
-            };
-            for frame_id in &pipeline.children {
-                self.get_past_entries(*frame_id, &mut past);
-            }
-        }
-    }
-
     // Create a new frame and update the internal bookkeeping.
     fn new_frame(&mut self, pipeline_id: PipelineId) -> FrameId {
         let id = self.next_frame_id;
         let FrameId(ref mut i) = self.next_frame_id;
         *i += 1;
 
-        let frame = Frame::new(pipeline_id);
+        let frame = Frame::new(id, pipeline_id);
 
         assert!(self.pipelines.get(&pipeline_id).and_then(|pipeline| pipeline.frame).is_none());
         assert!(!self.frames.contains_key(&id));
 
         self.pipelines.get_mut(&pipeline_id).map(|pipeline| pipeline.frame = Some(id));
         self.frames.insert(id, frame);
 
         id
@@ -826,16 +828,21 @@ impl<Message, LTF, STF> Constellation<Me
                 debug!("constellation got load complete message");
                 self.handle_load_complete_msg(pipeline_id)
             }
             // Handle a forward or back request
             FromScriptMsg::TraverseHistory(pipeline_id, direction) => {
                 debug!("constellation got traverse history message from script");
                 self.handle_traverse_history_msg(pipeline_id, direction);
             }
+            // Handle a joint session history length request.
+            FromScriptMsg::JointSessionHistoryLength(pipeline_id, sender) => {
+                debug!("constellation got joint session history length message from script");
+                self.handle_joint_session_history_length(pipeline_id, sender);
+            }
             // Notification that the new document is ready to become active
             FromScriptMsg::ActivateDocument(pipeline_id) => {
                 debug!("constellation got activate document message");
                 self.handle_activate_document_msg(pipeline_id);
             }
             // Update pipeline url after redirections
             FromScriptMsg::SetFinalUrl(pipeline_id, final_url) => {
                 // The script may have finished loading after we already started shutting down.
@@ -1480,16 +1487,35 @@ impl<Message, LTF, STF> Constellation<Me
                 }
             },
         };
         for (frame_id, pipeline_id) in traversal_info {
             self.traverse_frame_to_pipeline(frame_id, pipeline_id);
         }
     }
 
+    fn handle_joint_session_history_length(&self, pipeline_id: PipelineId, sender: IpcSender<u32>) {
+        let frame_id = match self.get_top_level_frame_for_pipeline(Some(pipeline_id)) {
+            Some(frame_id) => frame_id,
+            None => {
+                warn!("Jsh length message received after root's closure.");
+                let _ = sender.send(0);
+                return;
+            },
+        };
+
+        // Initialize length at 1 to count for the current active entry
+        let mut length = 1;
+        for frame in self.full_frame_tree_iter(frame_id) {
+            length += frame.next.len();
+            length += frame.prev.len();
+        }
+        let _ = sender.send(length as u32);
+    }
+
     fn handle_key_msg(&mut self, ch: Option<char>, key: Key, state: KeyState, mods: KeyModifiers) {
         // Send to the explicitly focused pipeline (if it exists), or the root
         // frame's current pipeline. If neither exist, fall back to sending to
         // the compositor below.
         let root_pipeline_id = self.root_frame_id
             .and_then(|root_frame_id| self.frames.get(&root_frame_id))
             .map(|root_frame| root_frame.current.0);
         let pipeline_id = self.focus_pipeline_id.or(root_pipeline_id);
--- a/servo/components/script/dom/history.rs
+++ b/servo/components/script/dom/history.rs
@@ -5,16 +5,17 @@
 use dom::bindings::codegen::Bindings::HistoryBinding;
 use dom::bindings::codegen::Bindings::HistoryBinding::HistoryMethods;
 use dom::bindings::codegen::Bindings::LocationBinding::LocationMethods;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
 use dom::bindings::global::GlobalRef;
 use dom::bindings::js::{JS, Root};
 use dom::bindings::reflector::{Reflector, reflect_dom_object};
 use dom::window::Window;
+use ipc_channel::ipc;
 use msg::constellation_msg::TraversalDirection;
 use script_traits::ScriptMsg as ConstellationMsg;
 
 // https://html.spec.whatwg.org/multipage/#the-history-interface
 #[dom_struct]
 pub struct History {
     reflector_: Reflector,
     window: JS<Window>,
@@ -39,16 +40,25 @@ impl History {
     fn traverse_history(&self, direction: TraversalDirection) {
         let pipeline = self.window.pipeline();
         let msg = ConstellationMsg::TraverseHistory(Some(pipeline), direction);
         let _ = self.window.constellation_chan().send(msg);
     }
 }
 
 impl HistoryMethods for History {
+    // https://html.spec.whatwg.org/multipage/#dom-history-length
+    fn Length(&self) -> u32 {
+        let pipeline = self.window.pipeline();
+        let (sender, recv) = ipc::channel().expect("Failed to create channel to send jsh length.");
+        let msg = ConstellationMsg::JointSessionHistoryLength(pipeline, sender);
+        let _ = self.window.constellation_chan().send(msg);
+        recv.recv().unwrap()
+    }
+
     // https://html.spec.whatwg.org/multipage/#dom-history-go
     fn Go(&self, delta: i32) {
         let direction = if delta > 0 {
             TraversalDirection::Forward(delta as usize)
         } else if delta < 0 {
             TraversalDirection::Back(-delta as usize)
         } else {
             self.window.Location().Reload();
--- a/servo/components/script/dom/webidls/History.webidl
+++ b/servo/components/script/dom/webidls/History.webidl
@@ -2,17 +2,17 @@
  * 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/. */
 
 // enum ScrollRestoration { "auto", "manual" };
 
 // https://html.spec.whatwg.org/multipage/#the-history-interface
 [Exposed=(Window,Worker)]
 interface History {
-  // readonly attribute unsigned long length;
+  readonly attribute unsigned long length;
   // attribute ScrollRestoration scrollRestoration;
   // readonly attribute any state;
   void go(optional long delta = 0);
   void back();
   void forward();
   // void pushState(any data, DOMString title, optional USVString? url = null);
   // void replaceState(any data, DOMString title, optional USVString? url = null);
 };
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -86,16 +86,18 @@ pub enum ScriptMsg {
     LoadComplete(PipelineId),
     /// A new load has been requested.
     LoadUrl(PipelineId, LoadData),
     /// Dispatch a mozbrowser event to a given iframe,
     /// or to the window if no subpage id is provided.
     MozBrowserEvent(PipelineId, Option<SubpageId>, MozBrowserEvent),
     /// HTMLIFrameElement Forward or Back traversal.
     TraverseHistory(Option<PipelineId>, TraversalDirection),
+    /// Gets the length of the joint session history from the constellation.
+    JointSessionHistoryLength(PipelineId, IpcSender<u32>),
     /// Favicon detected
     NewFavicon(Url),
     /// Status message to be displayed in the chrome, eg. a link URL on mouseover.
     NodeStatus(Option<String>),
     /// Notification that this iframe should be removed.
     RemoveIFrame(PipelineId, Option<IpcSender<()>>),
     /// Change pipeline visibility
     SetVisible(PipelineId, bool),