diff --git a/Cargo.lock b/Cargo.lock index 73737969de29..a49fc76e7023 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -483,6 +483,15 @@ dependencies = [ "webvr_traits 0.0.1", ] +[[package]] +name = "constellation_tests" +version = "0.0.1" +dependencies = [ + "constellation 0.0.1", + "msg 0.0.1", + "servo_url 0.0.1", +] + [[package]] name = "content-blocker" version = "0.2.2" @@ -1700,6 +1709,8 @@ dependencies = [ "plugins 0.0.1", "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_rand 0.0.1", + "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "webrender_traits 0.15.0 (git+https://github.com/servo/webrender)", ] @@ -2504,6 +2515,7 @@ dependencies = [ "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "browserhtml 0.1.17 (git+https://github.com/browserhtml/browserhtml?branch=crate)", "compiletest_helper 0.0.1", + "constellation_tests 0.0.1", "gfx_tests 0.0.1", "glutin_app 0.0.1", "layout_tests 0.0.1", diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 16475c1a7fa5..30d4535ca39d 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -75,7 +75,7 @@ use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg}; use euclid::scale_factor::ScaleFactor; use euclid::size::{Size2D, TypedSize2D}; use event_loop::EventLoop; -use frame::{Frame, FrameChange, FrameState, FrameTreeIterator, FullFrameTreeIterator}; +use frame::{Frame, FrameChange, FrameState, FrameTreeIterator, FullFrameTreeIterator, ReplaceOrUpdate}; use gfx::font_cache_thread::FontCacheThread; use gfx_traits::Epoch; use ipc_channel::SerializeError; @@ -85,7 +85,7 @@ use layout_traits::LayoutThreadFactory; use log::{Log, LogLevel, LogLevelFilter, LogMetadata, LogRecord}; use msg::constellation_msg::{FrameId, FrameType, PipelineId}; use msg::constellation_msg::{Key, KeyModifiers, KeyState}; -use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, TraversalDirection}; +use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, StateId, TraversalDirection}; use net_traits::{self, IpcSend, ResourceThreads}; use net_traits::image_cache_thread::ImageCacheThread; use net_traits::pub_domains::reg_host; @@ -99,7 +99,7 @@ use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorM use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData}; use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerEventRequest}; use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory}; -use script_traits::{LogEntry, ServiceWorkerMsg, webdriver_msg}; +use script_traits::{LogEntry, ServiceWorkerMsg, webdriver_msg, PushOrReplaceState}; use script_traits::{MozBrowserErrorType, MozBrowserEvent, WebDriverCommandMsg, WindowSizeData}; use script_traits::{SWManagerMsg, ScopeThings, WindowSizeType}; use script_traits::WebVREventMsg; @@ -110,7 +110,6 @@ use servo_remutex::ReentrantMutex; use servo_url::ServoUrl; use std::borrow::ToOwned; use std::collections::{HashMap, VecDeque}; -use std::iter::once; use std::marker::PhantomData; use std::process; use std::rc::{Rc, Weak}; @@ -614,7 +613,7 @@ impl Constellation .map(|pipeline| pipeline.visible); let prev_visibility = self.frames.get(&frame_id) - .and_then(|frame| self.pipelines.get(&frame.pipeline_id)) + .and_then(|frame| self.pipelines.get(&frame.pipeline_id())) .map(|pipeline| pipeline.visible) .or(parent_visibility); @@ -714,7 +713,7 @@ impl Constellation /// frame in the frame tree, sorted reverse chronologically. fn joint_session_past<'a>(&self, frame_id_root: FrameId) -> impl Iterator { let mut past: Vec<(Instant, FrameState)> = self.full_frame_tree_iter(frame_id_root) - .flat_map(|frame| frame.prev.iter().rev().scan(frame.instant, |prev_instant, entry| { + .flat_map(|frame| frame.prev.iter().rev().scan(frame.current.instant, |prev_instant, entry| { let instant = *prev_instant; *prev_instant = entry.instant; Some((instant, entry.clone())) @@ -860,7 +859,7 @@ impl Constellation // Handle a forward or back request FromCompositorMsg::TraverseHistory(pipeline_id, direction) => { debug!("constellation got traverse history message from compositor"); - self.handle_traverse_history_msg(pipeline_id, direction); + self.handle_traverse_history_msg(pipeline_id, direction, None); } FromCompositorMsg::WindowSize(new_size, size_type) => { debug!("constellation got window resize message"); @@ -925,9 +924,9 @@ impl Constellation self.handle_load_complete_msg(pipeline_id) } // Handle a forward or back request - FromScriptMsg::TraverseHistory(pipeline_id, direction) => { + FromScriptMsg::TraverseHistory(pipeline_id, direction, sender) => { debug!("constellation got traverse history message from script"); - self.handle_traverse_history_msg(pipeline_id, direction); + self.handle_traverse_history_msg(pipeline_id, direction, Some(sender)); } // Handle a joint session history length request. FromScriptMsg::JointSessionHistoryLength(pipeline_id, sender) => { @@ -1019,7 +1018,14 @@ impl Constellation debug!("constellation got Alert message"); self.handle_alert(pipeline_id, message, sender); } - + FromScriptMsg::HistoryStateChanged(pipeline_id, state_id, url, replace) => { + debug!("constellation got HistoryStateChanged message"); + self.handle_history_state_changed(pipeline_id, state_id, url, replace); + } + FromScriptMsg::UrlHashChanged(pipeline_id, url) => { + debug!("constellation got UrlHashChanged message"); + self.handle_url_hash_changed(pipeline_id, url); + } FromScriptMsg::ScrollFragmentPoint(pipeline_id, scroll_root_id, point, smooth) => { self.compositor_proxy.send(ToCompositorMsg::ScrollFragmentPoint(pipeline_id, scroll_root_id, @@ -1249,7 +1255,7 @@ impl Constellation // Notify the browser chrome that the pipeline has failed self.trigger_mozbrowsererror(top_level_frame_id, reason, backtrace); - let pipeline_id = self.frames.get(&top_level_frame_id).map(|frame| frame.pipeline_id); + let pipeline_id = self.frames.get(&top_level_frame_id).map(|frame| frame.pipeline_id()); let pipeline_url = pipeline_id.and_then(|id| self.pipelines.get(&id).map(|pipeline| pipeline.url.clone())); let parent_info = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.parent_info)); let window_size = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.size)); @@ -1409,7 +1415,8 @@ impl Constellation }; let replace = if load_info.info.replace { - self.frames.get(&load_info.info.frame_id).map(|frame| frame.current()) + self.frames.get(&load_info.info.frame_id).map(|frame| frame.current.clone()) + .map(|entry| ReplaceOrUpdate::Replace(entry)) } else { None }; @@ -1467,7 +1474,8 @@ impl Constellation }; let replace = if replace { - self.frames.get(&frame_id).map(|frame| frame.current()) + self.frames.get(&frame_id).map(|frame| frame.current.clone()) + .map(|entry| ReplaceOrUpdate::Replace(entry)) } else { None }; @@ -1534,7 +1542,7 @@ impl Constellation match self.frames.get(&self.root_frame_id) { None => warn!("Alert sent after root frame closure."), - Some(root_frame) => match self.pipelines.get(&root_frame.pipeline_id) { + Some(root_frame) => match self.pipelines.get(&root_frame.pipeline_id()) { None => warn!("Alert sent after root pipeline closure."), Some(root_pipeline) => root_pipeline.trigger_mozbrowser_event(Some(top_level_frame_id), event), } @@ -1611,7 +1619,8 @@ impl Constellation let root_frame_id = self.root_frame_id; let sandbox = IFrameSandboxState::IFrameUnsandboxed; let replace = if replace { - self.frames.get(&frame_id).map(|frame| frame.current()) + self.frames.get(&frame_id).map(|frame| frame.current.clone()) + .map(|entry| ReplaceOrUpdate::Replace(entry)) } else { None }; @@ -1658,7 +1667,8 @@ impl Constellation fn handle_traverse_history_msg(&mut self, pipeline_id: Option, - direction: TraversalDirection) { + direction: TraversalDirection, + sender: Option>) { let top_level_frame_id = pipeline_id .map(|pipeline_id| self.get_top_level_frame_for_pipeline(pipeline_id)) .unwrap_or(self.root_frame_id); @@ -1690,6 +1700,10 @@ impl Constellation for (_, entry) in table { self.traverse_to_entry(entry); } + // If the traversal was triggered from script, it must be treated synchronously. + if let Some(sender) = sender { + let _ = sender.send(()); + } } fn handle_joint_session_history_length(&self, pipeline_id: PipelineId, sender: IpcSender) { @@ -1709,7 +1723,7 @@ impl Constellation // frame's current pipeline. If neither exist, fall back to sending to // the compositor below. let root_pipeline_id = self.frames.get(&self.root_frame_id) - .map(|root_frame| root_frame.pipeline_id); + .map(|root_frame| root_frame.pipeline_id()); let pipeline_id = self.focus_pipeline_id.or(root_pipeline_id); match pipeline_id { @@ -1734,7 +1748,7 @@ impl Constellation fn handle_reload_msg(&mut self) { // Send Reload constellation msg to root script channel. let root_pipeline_id = self.frames.get(&self.root_frame_id) - .map(|root_frame| root_frame.pipeline_id); + .map(|root_frame| root_frame.pipeline_id()); if let Some(pipeline_id) = root_pipeline_id { let msg = ConstellationControlMsg::Reload(pipeline_id); @@ -1779,7 +1793,7 @@ impl Constellation resp_chan: IpcSender>) { let frame_id = frame_id.unwrap_or(self.root_frame_id); let current_pipeline_id = self.frames.get(&frame_id) - .map(|frame| frame.pipeline_id); + .map(|frame| frame.pipeline_id()); let pipeline_id_loaded = self.pending_frames.iter().rev() .find(|x| x.old_pipeline_id == current_pipeline_id) .map(|x| x.new_pipeline_id) @@ -1830,9 +1844,7 @@ impl Constellation fn handle_remove_iframe_msg(&mut self, frame_id: FrameId) -> Vec { let result = self.full_frame_tree_iter(frame_id) - .flat_map(|frame| frame.next.iter().chain(frame.prev.iter()) - .filter_map(|entry| entry.pipeline_id) - .chain(once(frame.pipeline_id))) + .flat_map(|frame| frame.entry_iter().filter_map(|entry| entry.pipeline_id())) .collect(); self.close_frame(frame_id, ExitPipelineMode::Normal); result @@ -1845,9 +1857,7 @@ impl Constellation }; let child_pipeline_ids: Vec = self.full_frame_tree_iter(frame_id) - .flat_map(|frame| frame.prev.iter().chain(frame.next.iter()) - .filter_map(|entry| entry.pipeline_id) - .chain(once(frame.pipeline_id))) + .flat_map(|frame| frame.entry_iter().filter_map(|entry| entry.pipeline_id())) .collect(); for id in child_pipeline_ids { @@ -1948,7 +1958,7 @@ impl Constellation }, WebDriverCommandMsg::TakeScreenshot(pipeline_id, reply) => { let current_pipeline_id = self.frames.get(&self.root_frame_id) - .map(|root_frame| root_frame.pipeline_id); + .map(|root_frame| root_frame.pipeline_id()); if Some(pipeline_id) == current_pipeline_id { self.compositor_proxy.send(ToCompositorMsg::CreatePng(reply)); } else { @@ -1960,37 +1970,79 @@ impl Constellation } } + fn handle_history_state_changed(&mut self, + pipeline_id: PipelineId, + state_id: StateId, + url: ServoUrl, + replace: PushOrReplaceState) { + if !self.pipeline_is_in_current_frame(pipeline_id) { + return warn!("History state changed in non-fully active pipeline {}", pipeline_id); + } + let frame_id = match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline.frame_id, + None => return warn!("History state changed after pipeline {} closed.", pipeline_id), + }; + match self.frames.get_mut(&frame_id) { + Some(frame) => { + match replace { + PushOrReplaceState::Push => { + frame.load_state_change(state_id, url); + }, + PushOrReplaceState::Replace => frame.replace_state(state_id, url), + } + }, + None => return warn!("History state changed after frame {} closed.", frame_id), + } + + if replace == PushOrReplaceState::Push { + let top_level_frame_id = self.get_top_level_frame_for_pipeline(pipeline_id); + self.clear_joint_session_future(top_level_frame_id) + } + } + + fn handle_url_hash_changed(&mut self, pipeline_id: PipelineId, url: ServoUrl) { + let frame_id = match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline.frame_id, + None => return warn!("History state changed after pipeline {} closed.", pipeline_id), + }; + match self.frames.get_mut(&frame_id) { + Some(frame) => frame.change_url(url), + None => return warn!("History state changed after frame {} closed.", frame_id), + } + } + // https://html.spec.whatwg.org/multipage/#traverse-the-history fn traverse_to_entry(&mut self, entry: FrameState) { // Step 1. let frame_id = entry.frame_id; - let pipeline_id = match entry.pipeline_id { + let pipeline_id = match entry.pipeline_id() { Some(pipeline_id) => pipeline_id, None => { // If there is no pipeline, then the document for this // entry has been discarded, so we navigate to the entry // URL instead. When the document has activated, it will - // traverse to the entry, but with the new pipeline id. + // traverse to the entry, but the new pipeline will exist now. debug!("Reloading document {} for frame {}.", entry.url, frame_id); // TODO: referrer? let load_data = LoadData::new(entry.url.clone(), None, None); // TODO: save the sandbox state so it can be restored here. let sandbox = IFrameSandboxState::IFrameUnsandboxed; - let new_pipeline_id = PipelineId::new(); let (old_pipeline_id, parent_info, window_size, is_private) = match self.frames.get(&frame_id) { - Some(frame) => match self.pipelines.get(&frame.pipeline_id) { - Some(pipeline) => (frame.pipeline_id, pipeline.parent_info, pipeline.size, pipeline.is_private), - None => (frame.pipeline_id, None, None, false), + Some(frame) => match self.pipelines.get(&frame.pipeline_id()) { + Some(pipeline) => (frame.pipeline_id(), pipeline.parent_info, + pipeline.size, pipeline.is_private), + None => (frame.pipeline_id(), None, None, false), }, None => return warn!("no frame to traverse"), }; + let new_pipeline_id = PipelineId::new(); self.new_pipeline(new_pipeline_id, frame_id, parent_info, window_size, load_data, sandbox, is_private); self.pending_frames.push(FrameChange { frame_id: frame_id, old_pipeline_id: Some(old_pipeline_id), new_pipeline_id: new_pipeline_id, url: entry.url.clone(), - replace: Some(entry), + replace: Some(ReplaceOrUpdate::Update(entry)), }); return; } @@ -2003,29 +2055,8 @@ impl Constellation let old_pipeline_id = match self.frames.get_mut(&frame_id) { Some(frame) => { - let old_pipeline_id = frame.pipeline_id; - let mut curr_entry = frame.current(); - - if entry.instant > frame.instant { - // We are traversing to the future. - while let Some(next) = frame.next.pop() { - frame.prev.push(curr_entry); - curr_entry = next; - if entry.instant <= curr_entry.instant { break; } - } - } else if entry.instant < frame.instant { - // We are traversing to the past. - while let Some(prev) = frame.prev.pop() { - frame.next.push(curr_entry); - curr_entry = prev; - if entry.instant >= curr_entry.instant { break; } - } - } - - debug_assert_eq!(entry.instant, curr_entry.instant); - - frame.update_current(pipeline_id, &entry); - + let old_pipeline_id = frame.pipeline_id(); + frame.traverse_to_entry(entry.clone()); old_pipeline_id }, None => return warn!("no frame to traverse"), @@ -2042,8 +2073,16 @@ impl Constellation } // Deactivate the old pipeline, and activate the new one. - self.update_activity(old_pipeline_id); - self.update_activity(pipeline_id); + if old_pipeline_id != pipeline_id { + self.update_activity(old_pipeline_id); + self.update_activity(pipeline_id); + } + + if let Some(pipeline) = self.pipelines.get(&pipeline_id) { + let msg = ConstellationControlMsg::UpdateActiveState(pipeline_id, entry.state_id, entry.url.clone()); + // TODO(cbrewster): Handle send error. + let _ = pipeline.event_loop.send(msg); + } // Set paint permissions correctly for the compositor layers. self.send_frame_tree(); @@ -2112,20 +2151,29 @@ impl Constellation } } - let (evicted_id, new_frame, navigated, location_changed) = if let Some(mut entry) = frame_change.replace { + let (evicted_id, new_frame, navigated, location_changed) = if let Some(replace) = frame_change.replace { debug!("Replacing pipeline in existing frame."); - let evicted_id = entry.pipeline_id; - entry.replace_pipeline(frame_change.new_pipeline_id, frame_change.url.clone()); + let (evicted_id, entry) = match replace { + ReplaceOrUpdate::Replace(mut entry) => { + let evicted_id = entry.pipeline_id(); + entry.replace_pipeline(frame_change.new_pipeline_id, frame_change.url.clone()); + (evicted_id, entry) + }, + ReplaceOrUpdate::Update(entry) => { + entry.update_pipeline(frame_change.new_pipeline_id); + (None, entry) + }, + }; self.traverse_to_entry(entry); (evicted_id, false, None, false) } else if let Some(frame) = self.frames.get_mut(&frame_change.frame_id) { debug!("Adding pipeline to existing frame."); - let old_pipeline_id = frame.pipeline_id; + let old_pipeline_id = frame.pipeline_id(); frame.load(frame_change.new_pipeline_id, frame_change.url.clone()); let evicted_id = frame.prev.len() .checked_sub(PREFS.get("session-history.max-length").as_u64().unwrap_or(20) as usize) - .and_then(|index| frame.prev.get_mut(index)) - .and_then(|entry| entry.pipeline_id.take()); + .and_then(|index| frame.prev.get(index)) + .and_then(|entry| frame.discard_entry(entry)); (evicted_id, false, Some(old_pipeline_id), true) } else { debug!("Adding pipeline to new frame."); @@ -2192,7 +2240,7 @@ impl Constellation if let Some(frame) = self.frames.get(&self.root_frame_id) { // Send Resize (or ResizeInactive) messages to each // pipeline in the frame tree. - let pipeline_id = frame.pipeline_id; + let pipeline_id = frame.pipeline_id(); let pipeline = match self.pipelines.get(&pipeline_id) { None => return warn!("Pipeline {:?} resized after closing.", pipeline_id), Some(pipeline) => pipeline, @@ -2202,10 +2250,13 @@ impl Constellation new_size, size_type )); - let pipelines = frame.prev.iter().chain(frame.next.iter()) - .filter_map(|entry| entry.pipeline_id) + + // TODO(cbrewster): Once we allow entries to share pipeline ids, we need to dedup. + let inactive_pipelines = frame.entry_iter() + .filter_map(|entry| entry.pipeline_id()) + .filter(|id| id != &pipeline_id) .filter_map(|pipeline_id| self.pipelines.get(&pipeline_id)); - for pipeline in pipelines { + for pipeline in inactive_pipelines { let _ = pipeline.event_loop.send(ConstellationControlMsg::ResizeInactive( pipeline.id, new_size @@ -2274,7 +2325,7 @@ impl Constellation // are met, then the output image should not change and a reftest // screenshot can safely be written. for frame in self.current_frame_tree_iter(self.root_frame_id) { - let pipeline_id = frame.pipeline_id; + let pipeline_id = frame.pipeline_id(); debug!("Checking readiness of frame {}, pipeline {}.", frame.id, pipeline_id); let pipeline = match self.pipelines.get(&pipeline_id) { @@ -2302,7 +2353,7 @@ impl Constellation } // See if this pipeline has reached idle script state yet. - match self.document_states.get(&frame.pipeline_id) { + match self.document_states.get(&frame.pipeline_id()) { Some(&DocumentState::Idle) => {} Some(&DocumentState::Pending) | None => { return ReadyToSave::DocumentLoading; @@ -2322,7 +2373,7 @@ impl Constellation } // Get the epoch that the compositor has drawn for this pipeline. - let compositor_epoch = pipeline_states.get(&frame.pipeline_id); + let compositor_epoch = pipeline_states.get(&frame.pipeline_id()); match compositor_epoch { Some(compositor_epoch) => { // Synchronously query the layout thread to see if the current @@ -2359,7 +2410,7 @@ impl Constellation loop { if let Some(ancestor) = self.pipelines.get(&ancestor_id) { if let Some(frame) = self.frames.get(&ancestor.frame_id) { - if frame.pipeline_id == ancestor_id { + if frame.pipeline_id() == ancestor_id { if let Some((parent_id, FrameType::IFrame)) = ancestor.parent_info { ancestor_id = parent_id; continue; @@ -2389,7 +2440,7 @@ impl Constellation }; for child_id in &pipeline.children { if let Some(child) = self.frames.get(child_id) { - self.set_activity(child.pipeline_id, child_activity); + self.set_activity(child.pipeline_id(), child_activity); } } } @@ -2405,13 +2456,34 @@ impl Constellation .map(|frame| frame.id) .collect(); for frame_id in frame_ids { - let evicted = match self.frames.get_mut(&frame_id) { - Some(frame) => frame.remove_forward_entries(), + let (evicted, current_pipeline) = match self.frames.get_mut(&frame_id) { + Some(frame) => (frame.remove_forward_entries(), frame.pipeline_id()), None => continue, }; + let mut discarded_history_states = vec!(); for entry in evicted { - if let Some(pipeline_id) = entry.pipeline_id { - self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); + if let Some(pipeline_id) = entry.pipeline_id() { + if pipeline_id != current_pipeline { + self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); + } else { + if let Some(state_id) = entry.state_id { + discarded_history_states.push(state_id); + } + } + } + } + + if !discarded_history_states.is_empty() { + let result = match self.pipelines.get(¤t_pipeline) { + Some(pipeline) => { + let msg = ConstellationControlMsg::RemoveStateEntries(current_pipeline, + discarded_history_states); + pipeline.event_loop.send(msg) + }, + None => return warn!("Pipeline {} closed after removing state entries", current_pipeline), + }; + if let Err(e) = result { + self.handle_send_error(current_pipeline, e); } } } @@ -2421,7 +2493,7 @@ impl Constellation fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) { debug!("Closing frame {}.", frame_id); let parent_info = self.frames.get(&frame_id) - .and_then(|frame| self.pipelines.get(&frame.pipeline_id)) + .and_then(|frame| self.pipelines.get(&frame.pipeline_id())) .and_then(|pipeline| pipeline.parent_info); self.close_frame_children(frame_id, DiscardBrowsingContext::Yes, exit_mode); @@ -2454,9 +2526,9 @@ impl Constellation .collect(); if let Some(frame) = self.frames.get(&frame_id) { - pipelines_to_close.extend(frame.next.iter().filter_map(|state| state.pipeline_id)); - pipelines_to_close.push(frame.pipeline_id); - pipelines_to_close.extend(frame.prev.iter().filter_map(|state| state.pipeline_id)); + pipelines_to_close.extend(frame.next.iter().filter_map(|state| state.pipeline_id())); + pipelines_to_close.push(frame.pipeline_id()); + pipelines_to_close.extend(frame.prev.iter().filter_map(|state| state.pipeline_id())); } for pipeline_id in pipelines_to_close { @@ -2476,8 +2548,19 @@ impl Constellation let frames_to_close = { let mut frames_to_close = vec!(); - if let Some(pipeline) = self.pipelines.get(&pipeline_id) { - frames_to_close.extend_from_slice(&pipeline.children); + match self.pipelines.get(&pipeline_id) { + Some(pipeline) => { + frames_to_close.extend_from_slice(&pipeline.children); + // Inform script, compositor that this pipeline has exited. + // Note, we don't remove the pipeline now, we wait for the message to come back from + // the pipeline. + match exit_mode { + ExitPipelineMode::Normal => pipeline.exit(dbc), + ExitPipelineMode::Force => pipeline.force_exit(dbc), + } + debug!("Closed pipeline {:?}.", pipeline_id); + }, + None => return warn!("Closing pipeline {:?} twice.", pipeline_id), } frames_to_close @@ -2488,13 +2571,6 @@ impl Constellation self.close_frame(*child_frame, exit_mode); } - // Note, we don't remove the pipeline now, we wait for the message to come back from - // the pipeline. - let pipeline = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline, - None => return warn!("Closing pipeline {:?} twice.", pipeline_id), - }; - // Remove this pipeline from pending frames if it hasn't loaded yet. let pending_index = self.pending_frames.iter().position(|frame_change| { frame_change.new_pipeline_id == pipeline_id @@ -2502,13 +2578,6 @@ impl Constellation if let Some(pending_index) = pending_index { self.pending_frames.remove(pending_index); } - - // Inform script, compositor that this pipeline has exited. - match exit_mode { - ExitPipelineMode::Normal => pipeline.exit(dbc), - ExitPipelineMode::Force => pipeline.force_exit(dbc), - } - debug!("Closed pipeline {:?}.", pipeline_id); } // Randomly close a pipeline -if --random-pipeline-closure-probability is set @@ -2540,7 +2609,7 @@ impl Constellation // Convert a frame to a sendable form to pass to the compositor fn frame_to_sendable(&self, frame_id: FrameId) -> Option { self.frames.get(&frame_id).and_then(|frame: &Frame| { - self.pipelines.get(&frame.pipeline_id).map(|pipeline: &Pipeline| { + self.pipelines.get(&frame.pipeline_id()).map(|pipeline: &Pipeline| { let mut frame_tree = SendableFrameTree { pipeline: pipeline.to_sendable(), size: pipeline.size, @@ -2621,7 +2690,7 @@ impl Constellation match self.frames.get(&top_level_frame_id) { None => warn!("Mozbrowser error after top-level frame closed."), - Some(frame) => match self.pipelines.get(&frame.pipeline_id) { + Some(frame) => match self.pipelines.get(&frame.pipeline_id()) { None => warn!("Mozbrowser error after top-level pipeline closed."), Some(pipeline) => match pipeline.parent_info { None => pipeline.trigger_mozbrowser_event(None, event), @@ -2648,7 +2717,7 @@ impl Constellation pipeline_id: PipelineId, root_frame_id: FrameId) -> bool { self.current_frame_tree_iter(root_frame_id) - .any(|current_frame| current_frame.pipeline_id == pipeline_id) + .any(|current_frame| current_frame.pipeline_id() == pipeline_id) } } diff --git a/components/constellation/frame.rs b/components/constellation/frame.rs index daa443b2b80f..f60a56681063 100644 --- a/components/constellation/frame.rs +++ b/components/constellation/frame.rs @@ -2,12 +2,14 @@ * 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 msg::constellation_msg::{FrameId, PipelineId}; +use msg::constellation_msg::{FrameId, PipelineId, StateId}; use pipeline::Pipeline; use servo_url::ServoUrl; +use std::cell::Cell; use std::collections::HashMap; use std::iter::once; use std::mem::replace; +use std::rc::Rc; use std::time::Instant; /// A frame in the frame tree. @@ -23,14 +25,8 @@ pub struct Frame { /// The frame id. pub id: FrameId, - /// The timestamp for the current session history entry - pub instant: Instant, - - /// The pipeline for the current session history entry - pub pipeline_id: PipelineId, - - /// The URL for the current session history entry - pub url: ServoUrl, + /// The current session history entry. + pub current: FrameState, /// The past session history, ordered chronologically. pub prev: Vec, @@ -45,31 +41,48 @@ impl Frame { pub fn new(id: FrameId, pipeline_id: PipelineId, url: ServoUrl) -> Frame { Frame { id: id, - pipeline_id: pipeline_id, - instant: Instant::now(), - url: url, + current: FrameState::new(pipeline_id, url, id), prev: vec!(), next: vec!(), } } - /// Get the current frame state. - pub fn current(&self) -> FrameState { - FrameState { - instant: self.instant, - frame_id: self.id, - pipeline_id: Some(self.pipeline_id), - url: self.url.clone(), - } + /// The active pipeline id of the current session history entry + pub fn pipeline_id(&self) -> PipelineId { + self.current.pipeline_id().expect("Active pipeline cannot be None") } /// Set the current frame entry, and push the current frame entry into the past. pub fn load(&mut self, pipeline_id: PipelineId, url: ServoUrl) { - let current = self.current(); + let current = self.current.clone(); self.prev.push(current); - self.instant = Instant::now(); - self.pipeline_id = pipeline_id; - self.url = url; + self.current.instant = Instant::now(); + self.current.replace_pipeline(pipeline_id, url); + } + + /// Set the current frame entry using the given state id and url and push the current frame + /// entry into the past. The pipeline id of the new entry is the same as the pipeline id + /// of the previous entry. + pub fn load_state_change(&mut self, state_id: StateId, url: ServoUrl) { + let current = self.current.clone(); + self.prev.push(current); + self.current.instant = Instant::now(); + self.current.state_id = Some(state_id); + self.current.url = url; + } + + pub fn change_url(&mut self, url: ServoUrl) { + let current = self.current.clone(); + self.prev.push(current); + self.current.instant = Instant::now(); + self.current.state_id = None; + self.current.url = url; + } + + /// Replace the state id and url of the current entry. + pub fn replace_state(&mut self, state_id: StateId, url: ServoUrl) { + self.current.state_id = Some(state_id); + self.current.url = url; } /// Set the future to be empty. @@ -77,11 +90,44 @@ impl Frame { replace(&mut self.next, vec!()) } - /// Update the current entry of the Frame from an entry that has been traversed to. - pub fn update_current(&mut self, pipeline_id: PipelineId, entry: &FrameState) { - self.pipeline_id = pipeline_id; - self.instant = entry.instant; - self.url = entry.url.clone(); + pub fn discard_entry(&self, entry: &FrameState) -> Option { + // Ensure that we are not discarding the active pipeline. + if entry.pipeline_id() != Some(self.pipeline_id()) { + return entry.discard_pipeline() + } + None + } + + pub fn traverse_to_entry(&mut self, entry: FrameState) { + if entry.pipeline_id().is_none() { + return warn!("Attempted to traverse frame {} to entry with a discarded pipeline.", entry.frame_id); + } + debug_assert!(entry.pipeline_id().is_some()); + let mut curr_entry = self.current.clone(); + + if entry.instant > self.current.instant { + // We are traversing to the future. + while let Some(next) = self.next.pop() { + self.prev.push(curr_entry); + curr_entry = next; + if entry.instant <= curr_entry.instant { break; } + } + } else if entry.instant < self.current.instant { + // We are traversing to the past. + while let Some(prev) = self.prev.pop() { + self.next.push(curr_entry); + curr_entry = prev; + if entry.instant >= curr_entry.instant { break; } + } + } + + debug_assert_eq!(entry.instant, curr_entry.instant); + + self.current = entry; + } + + pub fn entry_iter<'a>(&'a self) -> impl Iterator { + self.prev.iter().chain(self.next.iter()).chain(once(&self.current)) } } @@ -95,24 +141,63 @@ pub struct FrameState { /// The timestamp for when the session history entry was created pub instant: Instant, - /// The pipeline for the document in the session history, - /// None if the entry has been discarded - pub pipeline_id: Option, - /// The URL for this entry, used to reload the pipeline if it has been discarded pub url: ServoUrl, + /// The state id of the active history state + pub state_id: Option, + /// The frame that this session history entry is part of pub frame_id: FrameId, + + /// The pipeline for the document in the session history + pipeline_id: Rc>>, } impl FrameState { + fn new(pipeline_id: PipelineId, url: ServoUrl, frame_id: FrameId) -> FrameState { + FrameState { + instant: Instant::now(), + pipeline_id: Rc::new(Cell::new(Some(pipeline_id))), + url: url, + state_id: None, + frame_id: frame_id, + } + } + /// Updates the entry's pipeline and url. This is used when navigating with replacement /// enabled. pub fn replace_pipeline(&mut self, pipeline_id: PipelineId, url: ServoUrl) { - self.pipeline_id = Some(pipeline_id); + self.pipeline_id = Rc::new(Cell::new(Some(pipeline_id))); self.url = url; + self.state_id = None; + } + + fn discard_pipeline(&self) -> Option { + let old_pipeline_id = self.pipeline_id.get(); + self.pipeline_id.set(None); + old_pipeline_id } + + pub fn update_pipeline(&self, pipeline_id: PipelineId) { + self.pipeline_id.set(Some(pipeline_id)); + } + + pub fn pipeline_id(&self) -> Option { + self.pipeline_id.get() + } +} + +/// Whether the pipeline should be updated or replaced when a naviagtion with replacement +/// matures. +pub enum ReplaceOrUpdate { + /// Replaces the pipeline of the entry. This does not affect any other entries even + /// if they share a pipeline id. + Replace(FrameState), + /// Updates the pipeline of the entry. This will update the pipeline of all entries + /// that share the same pipeline. This is only used when reloading a document that + /// was discarded from the distant history. + Update(FrameState), } /// Represents a pending change in the frame tree, that will be applied @@ -133,7 +218,7 @@ pub struct FrameChange { /// Is the new document replacing the current document (e.g. a reload) /// or pushing it into the session history (e.g. a navigation)? - pub replace: Option, + pub replace: Option, } /// An iterator over a frame tree, returning the fully active frames in @@ -168,10 +253,10 @@ impl<'a> Iterator for FrameTreeIterator<'a> { continue; }, }; - let pipeline = match self.pipelines.get(&frame.pipeline_id) { + let pipeline = match self.pipelines.get(&frame.pipeline_id()) { Some(pipeline) => pipeline, None => { - warn!("Pipeline {:?} iterated after closure.", frame.pipeline_id); + warn!("Pipeline {:?} iterated after closure.", frame.pipeline_id()); continue; }, }; @@ -213,9 +298,15 @@ impl<'a> Iterator for FullFrameTreeIterator<'a> { continue; }, }; - let child_frame_ids = frame.prev.iter().chain(frame.next.iter()) - .filter_map(|entry| entry.pipeline_id) - .chain(once(frame.pipeline_id)) + let mut session_pipelines = frame.entry_iter() + .filter_map(|entry| entry.pipeline_id()) + .collect::>(); + + // The session history may contain multiple entries with the same PipelineId. + // These duplicates need to be removed. + session_pipelines.dedup(); + + let child_frame_ids = session_pipelines.iter() .filter_map(|pipeline_id| pipelines.get(&pipeline_id)) .flat_map(|pipeline| pipeline.children.iter()); self.stack.extend(child_frame_ids); diff --git a/components/constellation/lib.rs b/components/constellation/lib.rs index 92417412887b..e1856dd6c092 100644 --- a/components/constellation/lib.rs +++ b/components/constellation/lib.rs @@ -53,3 +53,4 @@ pub use constellation::{Constellation, FromCompositorLogger, FromScriptLogger, I pub use pipeline::UnprivilegedPipelineContent; #[cfg(not(target_os = "windows"))] pub use sandboxing::content_process_sandbox_profile; +pub use frame::Frame; diff --git a/components/msg/Cargo.toml b/components/msg/Cargo.toml index 33abb5cdaacb..9b59901bd97d 100644 --- a/components/msg/Cargo.toml +++ b/components/msg/Cargo.toml @@ -17,6 +17,8 @@ heapsize_derive = "0.1" plugins = {path = "../plugins"} serde = "0.8" serde_derive = "0.8" +servo_rand = {path = "../rand"} +uuid = { version = "0.3.1", default-features = false, features = ["serde"] } [dependencies.webrender_traits] git = "https://github.com/servo/webrender" diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index dddfb0c00422..dac17b342f2b 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -5,8 +5,10 @@ //! The high-level interface from script to constellation. Using this abstract interface helps //! reduce coupling between these two components. +use servo_rand::{self, Rng}; use std::cell::Cell; use std::fmt; +use uuid::Uuid; use webrender_traits; #[derive(PartialEq, Eq, Copy, Clone, Debug, Deserialize, Serialize)] @@ -294,6 +296,15 @@ impl fmt::Display for FrameId { } } +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash, Debug, Deserialize, Serialize, HeapSizeOf)] +pub struct StateId(#[ignore_heap_size_of = "no heapsize for uuid"] Uuid); + +impl StateId { + pub fn new() -> StateId { + StateId(servo_rand::thread_rng().gen()) + } +} + // We provide ids just for unit testing. pub const TEST_NAMESPACE: PipelineNamespaceId = PipelineNamespaceId(1234); pub const TEST_PIPELINE_INDEX: PipelineIndex = PipelineIndex(5678); diff --git a/components/msg/lib.rs b/components/msg/lib.rs index 6257340c3857..3356c4391013 100644 --- a/components/msg/lib.rs +++ b/components/msg/lib.rs @@ -13,6 +13,8 @@ extern crate heapsize; #[macro_use] extern crate heapsize_derive; #[macro_use] extern crate serde_derive; +extern crate servo_rand; +extern crate uuid; extern crate webrender_traits; pub mod constellation_msg; diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 925b5e19cdc0..8af854da6b7c 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -16,6 +16,7 @@ use http_loader::HttpState; use hyper::client::pool::Pool; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcReceiver, IpcReceiverSet, IpcSender}; +use msg::constellation_msg::StateId; use net_traits::{CookieSource, CoreResourceThread}; use net_traits::{CoreResourceMsg, FetchResponseMsg}; use net_traits::{CustomResponseMediator, ResourceId}; @@ -48,6 +49,7 @@ pub struct ResourceGroup { auth_cache: Arc>, hsts_list: Arc>, connector: Arc>, + history_states: Arc>>>, } /// Returns a tuple of (public, private) senders to the new threads. @@ -110,12 +112,14 @@ fn create_resource_groups(config_dir: Option<&Path>) auth_cache: Arc::new(RwLock::new(auth_cache)), hsts_list: Arc::new(RwLock::new(hsts_list.clone())), connector: create_http_connector("certs"), + history_states: Arc::new(RwLock::new(HashMap::new())), }; let private_resource_group = ResourceGroup { cookie_jar: Arc::new(RwLock::new(CookieStorage::new(150))), auth_cache: Arc::new(RwLock::new(AuthCache::new())), hsts_list: Arc::new(RwLock::new(HstsList::new())), connector: create_http_connector("certs"), + history_states: Arc::new(RwLock::new(HashMap::new())), }; (resource_group, private_resource_group) } @@ -169,6 +173,33 @@ impl ResourceChannelManager { let mut cookie_jar = group.cookie_jar.write().unwrap(); consumer.send(cookie_jar.cookies_for_url(&url, source)).unwrap(); } + CoreResourceMsg::SetHistoryState(state_id, state) => { + match group.history_states.write() { + Ok(mut history_states) => { + history_states.insert(state_id, state); + }, + Err(_) => warn!("Error while setting history state."), + } + } + CoreResourceMsg::GetHistoryState(state_id, consumer) => { + match group.history_states.read() { + Ok(history_states) => { + let _ = consumer.send(history_states.get(&state_id).cloned()); + }, + Err(_) => warn!("Error while getting history state."), + } + let _ = consumer.send(None); + } + CoreResourceMsg::RemoveHistoryStates(state_ids) => { + match group.history_states.write() { + Ok(mut history_states) => { + for state_id in state_ids { + history_states.remove(&state_id); + } + }, + Err(_) => warn!("Error while removing unreachable history states."), + } + } CoreResourceMsg::NetworkMediator(mediator_chan) => { self.resource_manager.swmanager_chan = Some(mediator_chan) } diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index 22308edaf249..df88b05f979c 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -42,6 +42,7 @@ use hyper_serde::Serde; use ipc_channel::SerializeError; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; +use msg::constellation_msg::StateId; use request::{Request, RequestInit}; use response::{HttpsState, Response}; use servo_url::ServoUrl; @@ -386,6 +387,12 @@ pub enum CoreResourceMsg { GetCookiesForUrl(ServoUrl, IpcSender>, CookieSource), /// Get a cookie by name for a given originating URL GetCookiesDataForUrl(ServoUrl, IpcSender>>, CookieSource), + /// Set the serialized history state for a given state id + SetHistoryState(StateId, Vec), + /// Get serialized history state for a given state id + GetHistoryState(StateId, IpcSender>>), + /// Remove history states associated with the given state ids + RemoveHistoryStates(Vec), /// Cancel a network request corresponding to a given `ResourceId` Cancel(ResourceId), /// Synchronization message solely for knowing the state of the ResourceChannelManager loop diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index aeddcdabe4b0..97baa0dc06e0 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -58,7 +58,7 @@ use js::glue::{CallObjectTracer, CallValueTracer}; use js::jsapi::{GCTraceKindToAscii, Heap, JSObject, JSTracer, TraceKind}; use js::jsval::JSVal; use js::rust::Runtime; -use msg::constellation_msg::{FrameId, FrameType, PipelineId}; +use msg::constellation_msg::{FrameId, FrameType, PipelineId, StateId}; use net_traits::{Metadata, NetworkError, ReferrerPolicy, ResourceThreads}; use net_traits::filemanager_thread::RelativePos; use net_traits::image::base::{Image, ImageMetadata}; @@ -329,6 +329,7 @@ unsafe_no_jsmanaged_fields!(PropertyDeclarationBlock); // These three are interdependent, if you plan to put jsmanaged data // in one of these make sure it is propagated properly to containing structs unsafe_no_jsmanaged_fields!(DocumentActivity, FrameId, FrameType, WindowSizeData, WindowSizeType, PipelineId); +unsafe_no_jsmanaged_fields!(StateId); unsafe_no_jsmanaged_fields!(TimerEventId, TimerSource); unsafe_no_jsmanaged_fields!(TimelineMarkerType); unsafe_no_jsmanaged_fields!(WorkerId); diff --git a/components/script/dom/hashchangeevent.rs b/components/script/dom/hashchangeevent.rs index cd4b2b0a71fe..91572819e751 100644 --- a/components/script/dom/hashchangeevent.rs +++ b/components/script/dom/hashchangeevent.rs @@ -11,6 +11,7 @@ use dom::bindings::js::Root; use dom::bindings::reflector::reflect_dom_object; use dom::bindings::str::{DOMString, USVString}; use dom::event::Event; +use dom::eventtarget::EventTarget; use dom::window::Window; use servo_atoms::Atom; @@ -65,6 +66,14 @@ impl HashChangeEvent { init.oldURL.0.clone(), init.newURL.0.clone())) } + + pub fn dispatch(target: &EventTarget, + window: &Window, + old_url: String, + new_url: String) { + let event = HashChangeEvent::new(window, Atom::from("hashchange"), true, false, old_url, new_url); + event.upcast::().fire(target); + } } impl HashChangeEventMethods for HashChangeEvent { diff --git a/components/script/dom/history.rs b/components/script/dom/history.rs index 1a971eae0092..0dd1ab36085d 100644 --- a/components/script/dom/history.rs +++ b/components/script/dom/history.rs @@ -6,20 +6,34 @@ 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::error::{Error, ErrorResult}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; +use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; +use dom::bindings::str::{DOMString, USVString}; +use dom::bindings::structuredclone::StructuredCloneData; +use dom::eventtarget::EventTarget; use dom::globalscope::GlobalScope; +use dom::hashchangeevent::HashChangeEvent; +use dom::popstateevent::PopStateEvent; use dom::window::Window; use ipc_channel::ipc; -use msg::constellation_msg::TraversalDirection; -use script_traits::ScriptMsg as ConstellationMsg; +use js::jsapi::{HandleValue, JSAutoCompartment, JSContext, Heap}; +use js::jsval::{JSVal, NullValue}; +use msg::constellation_msg::{StateId, TraversalDirection}; +use net_traits::{CoreResourceMsg, IpcSend}; +use script_traits::{PushOrReplaceState, ScriptMsg as ConstellationMsg}; +use servo_url::ServoUrl; +use std::cell::Cell; +use url::Position; // https://html.spec.whatwg.org/multipage/#the-history-interface #[dom_struct] pub struct History { reflector_: Reflector, window: JS, + active_state: Heap, + latest_state: Cell>, } impl History { @@ -27,6 +41,8 @@ impl History { History { reflector_: Reflector::new(), window: JS::from_ref(&window), + active_state: Heap::new(NullValue()), + latest_state: Cell::new(None), } } @@ -35,14 +51,134 @@ impl History { window, HistoryBinding::Wrap) } -} -impl History { - fn traverse_history(&self, direction: TraversalDirection) { + // https://html.spec.whatwg.org/multipage/#dom-history-pushstate + // https://html.spec.whatwg.org/multipage/#dom-history-replacestate + fn update_state(&self, + cx: *mut JSContext, + data: HandleValue, + url: Option, + replace: PushOrReplaceState) -> ErrorResult { + // Step 1. + let document = self.window.Document(); + + // Step 2. + if !document.is_fully_active() { + return Err(Error::Security); + } + + // TODO Step 3. Optionally abort these steps if push/replace state is being abused. + + // Step 4-5. + let cloned_data = try!(StructuredCloneData::write(cx, data)); + let global_scope = self.window.upcast::(); - let pipeline = global_scope.pipeline_id(); - let msg = ConstellationMsg::TraverseHistory(Some(pipeline), direction); + + let url = match url { + Some(url) => { + // Step 6. + let global_url = global_scope.get_url(); + // 6.1 + let url = match global_url.join(&url.0) { + // 6.2 + Err(_) => return Err(Error::Security), + // 6.3 + Ok(url) => url, + }; + + let document_url = document.url(); + + // 6.4 + if url[Position::BeforeScheme..Position::AfterPort] != + document_url[Position::BeforeScheme..Position::AfterPort] { + return Err(Error::Security); + } + + // 6.5 + if url.origin() != document_url.origin() && (url[Position::BeforePath..Position::AfterQuery] + != document_url[Position::BeforePath..Position::AfterQuery]) { + return Err(Error::Security); + } + url + }, + // Step 7. + None => document.url(), + }; + + // Step 8. + let state_id = match replace { + PushOrReplaceState::Push => StateId::new(), + PushOrReplaceState::Replace => self.latest_state.get().unwrap_or(StateId::new()), + }; + self.latest_state.set(Some(state_id)); + + let state_data = cloned_data.move_to_arraybuffer(); + let _ = global_scope.resource_threads().send(CoreResourceMsg::SetHistoryState(state_id, state_data.clone())); + + let cloned_data = StructuredCloneData::Vector(state_data); + let cx = global_scope.get_cx(); + let globalhandle = global_scope.reflector().get_jsobject(); + rooted!(in(cx) let mut state = NullValue()); + let _ac = JSAutoCompartment::new(cx, globalhandle.get()); + cloned_data.read(global_scope, state.handle_mut()); + self.active_state.set(state.get()); + + // Notify Constellation + let msg = ConstellationMsg::HistoryStateChanged(global_scope.pipeline_id(), state_id, url.clone(), replace); let _ = global_scope.constellation_chan().send(msg); + + // Step 10. + // TODO(cbrewster): We can set the document url without ever notifying the constellation + // This seems like it could be bad in the case of reloading a document that was discarded due to + // being in the distant history. + document.set_url(url); + Ok(()) + } + + pub fn set_active_state(&self, state_id: Option, url: ServoUrl) { + self.latest_state.set(state_id); + match state_id { + Some(state_id) => { + let global_scope = self.window.upcast::(); + let (sender, receiver) = ipc::channel().expect("Failed to create IPC channel."); + let _ = global_scope.resource_threads().send(CoreResourceMsg::GetHistoryState(state_id, sender)); + // The receiver could error out or the received state may be none. + let state_data = receiver.recv().ok().and_then(|s| s).expect("Failed to get state data"); + let cloned_data = StructuredCloneData::Vector(state_data); + + let cx = global_scope.get_cx(); + let globalhandle = global_scope.reflector().get_jsobject(); + rooted!(in(cx) let mut state = NullValue()); + let _ac = JSAutoCompartment::new(cx, globalhandle.get()); + cloned_data.read(global_scope, state.handle_mut()); + self.active_state.set(state.get()); + } + None => self.active_state.set(NullValue()) + }; + PopStateEvent::dispatch_jsval(self.window.upcast::(), &*self.window, self.active_state.handle()); + + let doc_url = self.window.Document().url(); + if doc_url.fragment() != url.fragment() { + let old_url = doc_url.into_string(); + let new_url = url.clone().into_string(); + self.window.Document().set_url(url); + HashChangeEvent::dispatch(self.window.upcast::(), &*self.window, old_url, new_url); + } + } + + pub fn remove_states(&self, state_ids: Vec) { + let global_scope = self.window.upcast::(); + let _ = global_scope.resource_threads().send(CoreResourceMsg::RemoveHistoryStates(state_ids)); + } + + /// Set the active state to `null` + pub fn clear_active_state(&self) { + self.active_state.set(NullValue()); + } + + pub fn traverse_history(&self, direction: TraversalDirection) { + let history_traversal_task_source = self.window.history_traversal_task_source(); + history_traversal_task_source.queue_history_traversal(&*self.window, direction); } } @@ -57,6 +193,12 @@ impl HistoryMethods for History { recv.recv().unwrap() } + #[allow(unsafe_code)] + // https://html.spec.whatwg.org/multipage/#dom-history-state + unsafe fn State(&self, _cx: *mut JSContext) -> JSVal { + self.active_state.get() + } + // https://html.spec.whatwg.org/multipage/#dom-history-go fn Go(&self, delta: i32) { let direction = if delta > 0 { @@ -80,4 +222,24 @@ impl HistoryMethods for History { fn Forward(&self) { self.traverse_history(TraversalDirection::Forward(1)); } + + #[allow(unsafe_code)] + // https://html.spec.whatwg.org/multipage/#dom-history-pushstate + unsafe fn PushState(&self, + cx: *mut JSContext, + data: HandleValue, + _title: DOMString, + url: Option) -> ErrorResult { + self.update_state(cx, data, url, PushOrReplaceState::Push) + } + + #[allow(unsafe_code)] + // https://html.spec.whatwg.org/multipage/#dom-history-replacestate + unsafe fn ReplaceState(&self, + cx: *mut JSContext, + data: HandleValue, + _title: DOMString, + url: Option) -> ErrorResult { + self.update_state(cx, data, url, PushOrReplaceState::Replace) + } } diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 3a1c9daf1c94..a2a81e2cf5c0 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -481,8 +481,7 @@ pub fn Navigate(iframe: &HTMLIFrameElement, direction: TraversalDirection) -> Er if iframe.Mozbrowser() { if iframe.upcast::().is_in_doc_with_browsing_context() { let window = window_from_node(iframe); - let msg = ConstellationMsg::TraverseHistory(iframe.pipeline_id(), direction); - window.upcast::().constellation_chan().send(msg).unwrap(); + window.History().traverse_history(direction); } Ok(()) diff --git a/components/script/dom/popstateevent.rs b/components/script/dom/popstateevent.rs index 1282d847d7c6..3735f282fb14 100644 --- a/components/script/dom/popstateevent.rs +++ b/components/script/dom/popstateevent.rs @@ -12,6 +12,7 @@ use dom::bindings::reflector::reflect_dom_object; use dom::bindings::str::DOMString; use dom::bindings::trace::RootedTraceableBox; use dom::event::Event; +use dom::eventtarget::EventTarget; use dom::window::Window; use js::jsapi::{HandleValue, JSContext}; use js::jsval::JSVal; @@ -64,6 +65,14 @@ impl PopStateEvent { init.parent.cancelable, init.state.handle())) } + + pub fn dispatch_jsval(target: &EventTarget, + window: &Window, + state: HandleValue) { + // TODO(cbrewster): Add popstate to atoms + let event = PopStateEvent::new(window, Atom::from("popstate"), true, false, state); + event.upcast::().fire(target); + } } impl PopStateEventMethods for PopStateEvent { diff --git a/components/script/dom/webidls/History.webidl b/components/script/dom/webidls/History.webidl index c0c1635264a3..918403b35ad4 100644 --- a/components/script/dom/webidls/History.webidl +++ b/components/script/dom/webidls/History.webidl @@ -9,10 +9,12 @@ interface History { readonly attribute unsigned long length; // attribute ScrollRestoration scrollRestoration; - // readonly attribute any state; + 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); + [Throws] + void pushState(any data, DOMString title, optional USVString? url = null); + [Throws] + void replaceState(any data, DOMString title, optional USVString? url = null); }; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index d1eba0245e0c..baa0f966e7ee 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -284,7 +284,7 @@ impl Window { self.networking_task_source.clone() } - pub fn history_traversal_task_source(&self) -> Box { + pub fn history_traversal_task_source(&self) -> HistoryTraversalTaskSource { self.history_traversal_task_source.clone() } @@ -1423,6 +1423,12 @@ impl Window { if let Some(fragment) = url.fragment() { doc.check_and_scroll_fragment(fragment); doc.set_url(url.clone()); + self.History().clear_active_state(); + let global_scope = self.upcast::(); + global_scope + .constellation_chan() + .send(ConstellationMsg::UrlHashChanged(global_scope.pipeline_id(), url.clone())) + .unwrap(); return } } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index fa83eaed6319..4fba46014fd7 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -70,7 +70,7 @@ use js::rust::Runtime; use layout_wrapper::ServoLayoutNode; use mem::heap_size_of_self_and_children; use microtask::{MicrotaskQueue, Microtask}; -use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespace}; +use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespace, StateId}; use net_traits::{CoreResourceMsg, FetchMetadata, FetchResponseListener}; use net_traits::{IpcSend, Metadata, ReferrerPolicy, ResourceThreads}; use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheResult, ImageCacheThread}; @@ -113,7 +113,7 @@ use style::dom::{TNode, UnsafeNode}; use style::thread_state; use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource}; use task_source::file_reading::FileReadingTaskSource; -use task_source::history_traversal::HistoryTraversalTaskSource; +use task_source::history_traversal::{HistoryTraversalTask, HistoryTraversalTaskSource}; use task_source::networking::NetworkingTaskSource; use task_source::user_interaction::{UserInteractionTask, UserInteractionTaskSource}; use time::Tm; @@ -249,6 +249,8 @@ pub enum MainThreadScriptMsg { DOMManipulation(DOMManipulationTask), /// Tasks that originate from the user interaction task source UserInteraction(UserInteractionTask), + /// Tasks that originate from the history traversal task source + HistoryTraversal(HistoryTraversalTask), } impl OpaqueSender for Box { @@ -1021,6 +1023,10 @@ impl ScriptThread { self.handle_css_error_reporting(pipeline_id, filename, line, column, msg), ConstellationControlMsg::Reload(pipeline_id) => self.handle_reload(pipeline_id), + ConstellationControlMsg::UpdateActiveState(pipeline_id, state_id, url) => + self.handle_update_active_state(pipeline_id, state_id, url), + ConstellationControlMsg::RemoveStateEntries(pipeline_id, state_ids) => + self.handle_remove_state_entries(pipeline_id, state_ids), ConstellationControlMsg::ExitPipeline(pipeline_id, discard_browsing_context) => self.handle_exit_pipeline_msg(pipeline_id, discard_browsing_context), ConstellationControlMsg::WebVREvent(pipeline_id, event) => @@ -1053,6 +1059,8 @@ impl ScriptThread { task.handle_task(self), MainThreadScriptMsg::UserInteraction(task) => task.handle_task(self), + MainThreadScriptMsg::HistoryTraversal(task) => + task.handle_task(self), } } @@ -2077,6 +2085,20 @@ impl ScriptThread { } } + fn handle_update_active_state(&self, pipeline_id: PipelineId, state_id: Option, url: ServoUrl) { + let window = self.documents.borrow().find_window(pipeline_id); + if let Some(window) = window { + window.History().set_active_state(state_id, url); + } + } + + fn handle_remove_state_entries(&self, pipeline_id: PipelineId, state_ids: Vec) { + let window = self.documents.borrow().find_window(pipeline_id); + if let Some(window) = window { + window.History().remove_states(state_ids); + } + } + fn handle_webvr_event(&self, pipeline_id: PipelineId, event: WebVREventMsg) { let window = self.documents.borrow().find_window(pipeline_id); if let Some(window) = window { diff --git a/components/script/task_source/history_traversal.rs b/components/script/task_source/history_traversal.rs index e5887264cf64..cc5c122477e6 100644 --- a/components/script/task_source/history_traversal.rs +++ b/components/script/task_source/history_traversal.rs @@ -2,19 +2,67 @@ * 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 script_runtime::{ScriptChan, CommonScriptMsg}; -use script_thread::MainThreadScriptMsg; +use dom::bindings::inheritance::Castable; +use dom::bindings::refcounted::Trusted; +use dom::globalscope::GlobalScope; +use dom::window::Window; +use ipc_channel::ipc; +use msg::constellation_msg::TraversalDirection; +use script_thread::{MainThreadScriptMsg, Runnable, RunnableWrapper, ScriptThread}; +use script_traits::ScriptMsg as ConstellationMsg; use std::sync::mpsc::Sender; +use task_source::TaskSource; -#[derive(JSTraceable)] +#[derive(JSTraceable, Clone)] pub struct HistoryTraversalTaskSource(pub Sender); -impl ScriptChan for HistoryTraversalTaskSource { - fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> { - self.0.send(MainThreadScriptMsg::Common(msg)).map_err(|_| ()) +impl TaskSource for HistoryTraversalTaskSource { + fn queue_with_wrapper(&self, + msg: Box, + wrapper: &RunnableWrapper) + -> Result<(), ()> + where T: Runnable + Send + 'static { + let msg = HistoryTraversalTask(wrapper.wrap_runnable(msg)); + self.0.send(MainThreadScriptMsg::HistoryTraversal(msg)).map_err(|_| ()) } +} + +impl HistoryTraversalTaskSource { + pub fn queue_history_traversal(&self, window: &Window, direction: TraversalDirection) { + let trusted_window = Trusted::new(window); + let runnable = box HistoryTraversalRunnable { + window: trusted_window, + direction: direction, + }; + let _ = self.queue(runnable, window.upcast()); + } +} + +pub struct HistoryTraversalTask(pub Box); + +impl HistoryTraversalTask { + pub fn handle_task(self, script_thread: &ScriptThread) { + if !self.0.is_cancelled() { + self.0.main_thread_handler(script_thread); + } + } +} + +struct HistoryTraversalRunnable { + window: Trusted, + direction: TraversalDirection, +} + +impl Runnable for HistoryTraversalRunnable { + fn name(&self) -> &'static str { "HistoryTraversalRunnable" } - fn clone(&self) -> Box { - box HistoryTraversalTaskSource((&self.0).clone()) + fn handler(self: Box) { + let window = self.window.root(); + let global_scope = window.upcast::(); + let pipeline = global_scope.pipeline_id(); + let (sender, recv) = ipc::channel().expect("Failed to create ipc channel"); + let msg = ConstellationMsg::TraverseHistory(Some(pipeline), self.direction, sender); + let _ = global_scope.constellation_chan().send(msg); + let _ = recv.recv(); } } diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 5be4cb7d2bc2..2ea3ae5a55d6 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -56,7 +56,7 @@ use hyper::method::Method; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use libc::c_void; use msg::constellation_msg::{FrameId, FrameType, Key, KeyModifiers, KeyState}; -use msg::constellation_msg::{PipelineId, PipelineNamespaceId, TraversalDirection}; +use msg::constellation_msg::{PipelineId, PipelineNamespaceId, StateId, TraversalDirection}; use net_traits::{ReferrerPolicy, ResourceThreads}; use net_traits::image::base::Image; use net_traits::image_cache_thread::ImageCacheThread; @@ -185,6 +185,16 @@ pub struct NewLayoutInfo { pub layout_threads: usize, } +/// When updating the state object of the history, should it replace the current state or push +/// a new entry? +#[derive(PartialEq, Debug, Deserialize, Serialize)] +pub enum PushOrReplaceState { + /// Push a new entry + Push, + /// Replace the current entry + Replace, +} + /// When a pipeline is closed, should its browsing context be discarded too? #[derive(Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum DiscardBrowsingContext { @@ -278,6 +288,10 @@ pub enum ConstellationControlMsg { ReportCSSError(PipelineId, String, usize, usize, String), /// Reload the given page. Reload(PipelineId), + /// Notifies the history to update the active state entry. + UpdateActiveState(PipelineId, Option, ServoUrl), + /// Notifies the history to remove inaccessible state entries. + RemoveStateEntries(PipelineId, Vec), /// Notifies the script thread of a WebVR device event WebVREvent(PipelineId, WebVREventMsg) } @@ -311,6 +325,8 @@ impl fmt::Debug for ConstellationControlMsg { FramedContentChanged(..) => "FramedContentChanged", ReportCSSError(..) => "ReportCSSError", Reload(..) => "Reload", + UpdateActiveState(..) => "UpdateActiveState", + RemoveStateEntries(..) => "RemoveStateEntries", WebVREvent(..) => "WebVREvent", }; write!(formatter, "ConstellationMsg::{}", variant) diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs index d35e23a7bb99..ef7525bc6816 100644 --- a/components/script_traits/script_msg.rs +++ b/components/script_traits/script_msg.rs @@ -10,6 +10,7 @@ use IFrameLoadInfoWithData; use LayoutControlMsg; use LoadData; use MozBrowserEvent; +use PushOrReplaceState; use WorkerGlobalScopeInit; use WorkerScriptLoadOrigin; use canvas_traits::CanvasMsg; @@ -18,7 +19,7 @@ use euclid::point::Point2D; use euclid::size::{Size2D, TypedSize2D}; use gfx_traits::ScrollRootId; use ipc_channel::ipc::IpcSender; -use msg::constellation_msg::{FrameId, PipelineId, TraversalDirection}; +use msg::constellation_msg::{FrameId, PipelineId, StateId, TraversalDirection}; use msg::constellation_msg::{Key, KeyModifiers, KeyState}; use net_traits::CoreResourceMsg; use net_traits::storage_thread::StorageType; @@ -96,8 +97,8 @@ pub enum ScriptMsg { /// Dispatch a mozbrowser event to the parent of this pipeline. /// The first PipelineId is for the parent, the second is for the originating pipeline. MozBrowserEvent(PipelineId, PipelineId, MozBrowserEvent), - /// HTMLIFrameElement Forward or Back traversal. - TraverseHistory(Option, TraversalDirection), + /// Traverses the joint session history. This is a synchronous message. + TraverseHistory(Option, TraversalDirection, IpcSender<()>), /// Gets the length of the joint session history from the constellation. JointSessionHistoryLength(PipelineId, IpcSender), /// Favicon detected @@ -151,6 +152,10 @@ pub enum ScriptMsg { RegisterServiceWorker(ScopeThings, ServoUrl), /// Enter or exit fullscreen SetFullscreenState(bool), + /// Notifies the constellation that a new history state has been pushed or replaced. + HistoryStateChanged(PipelineId, StateId, ServoUrl, PushOrReplaceState), + /// Notifies the constellation that url's hash has changed. + UrlHashChanged(PipelineId, ServoUrl), /// Requests that the compositor shut down. Exit, } diff --git a/etc/ci/former_intermittents_wpt.txt b/etc/ci/former_intermittents_wpt.txt index 6001c12b3a23..68daea3b4b46 100644 --- a/etc/ci/former_intermittents_wpt.txt +++ b/etc/ci/former_intermittents_wpt.txt @@ -1,2 +1,4 @@ /_mozilla/mozilla/css/canvas_over_area.html /_mozilla/mozilla/css/iframe/hide_layers2.html +/html/browsers/history/the-history-interface/001.html +/html/browsers/history/the-history-interface/002.html \ No newline at end of file diff --git a/ports/servo/Cargo.toml b/ports/servo/Cargo.toml index 082d17db90bc..83b64bf49d2a 100644 --- a/ports/servo/Cargo.toml +++ b/ports/servo/Cargo.toml @@ -15,6 +15,7 @@ bench = false [dev-dependencies] compiletest_helper = {path = "../../tests/compiletest/helper"} +constellation_tests = {path = "../../tests/unit/constellation"} gfx_tests = {path = "../../tests/unit/gfx"} layout_tests = {path = "../../tests/unit/layout"} net_tests = {path = "../../tests/unit/net"} diff --git a/tests/unit/constellation/Cargo.toml b/tests/unit/constellation/Cargo.toml new file mode 100644 index 000000000000..7dfa6dc5295f --- /dev/null +++ b/tests/unit/constellation/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "constellation_tests" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" + +[lib] +name = "constellation_tests" +path = "lib.rs" +doctest = false + +[dependencies] +constellation = {path = "../../../components/constellation"} +msg = {path = "../../../components/msg"} +servo_url = {path = "../../../components/url"} diff --git a/tests/unit/constellation/frame.rs b/tests/unit/constellation/frame.rs new file mode 100644 index 000000000000..13ce70bfb52b --- /dev/null +++ b/tests/unit/constellation/frame.rs @@ -0,0 +1,105 @@ +/* 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 constellation::Frame; +use msg::constellation_msg::{PipelineId, PipelineNamespace, PipelineNamespaceId, FrameId}; +use msg::constellation_msg::StateId; +use servo_url::ServoUrl; + +#[test] +fn test_entry_replacement() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let pipeline_id = PipelineId::new(); + let frame_id = FrameId::new(); + let url = ServoUrl::parse("about:blank").expect("Infallible"); + let mut frame = Frame::new(frame_id, pipeline_id, url.clone()); + + frame.prev.push(frame.current.clone()); + + let new_pipeline_id = PipelineId::new(); + // Unlink this entry from the previous entry + frame.current.replace_pipeline(new_pipeline_id, url); + + assert_eq!(frame.prev.len(), 1); + let prev_entry = frame.prev.pop().expect("No previous entry!"); + assert_ne!(prev_entry.pipeline_id(), frame.current.pipeline_id()); +} + +#[test] +fn test_entry_update() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let pipeline_id = PipelineId::new(); + let frame_id = FrameId::new(); + FrameId::install(frame_id); + let url = ServoUrl::parse("about:blank").expect("Infallible"); + let mut frame = Frame::new(frame_id, pipeline_id, url); + + // A clone will link the two entry's pipelines + frame.prev.push(frame.current.clone()); + + let new_pipeline_id = PipelineId::new(); + frame.current.update_pipeline(new_pipeline_id); + assert_eq!(frame.pipeline_id(), new_pipeline_id); + + assert_eq!(frame.prev.len(), 1); + let prev_entry = frame.prev.pop().expect("No previous entry!"); + assert_eq!(prev_entry.pipeline_id(), frame.current.pipeline_id()); +} + +#[test] +fn test_entry_discard() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let pipeline_id = PipelineId::new(); + let frame_id = FrameId::new(); + FrameId::install(frame_id); + let url = ServoUrl::parse("about:blank").expect("Infallible"); + let mut frame = Frame::new(frame_id, pipeline_id, url.clone()); + + // A clone will link the two entry's pipelines + frame.prev.push(frame.current.clone()); + + assert_eq!(frame.prev.len(), 1); + // Cannot discard because this entry shares the same pipeline as the current entry. + let evicted_id = frame.discard_entry(&frame.prev[0]); + assert!(evicted_id.is_none()); + + let new_pipeline_id = PipelineId::new(); + frame.current.replace_pipeline(new_pipeline_id, url); + // Discard should work now that current is no longer linked to this entry. + let evicted_id = frame.discard_entry(&frame.prev[0]); + assert_eq!(evicted_id, Some(pipeline_id)); +} + +#[test] +fn test_state_id() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let pipeline_id = PipelineId::new(); + let frame_id = FrameId::new(); + FrameId::install(frame_id); + let url = ServoUrl::parse("about:blank").expect("Infallible"); + let mut frame = Frame::new(frame_id, pipeline_id, url.clone()); + + let state_id = StateId::new(); + frame.load_state_change(state_id, url.clone()); + + assert_eq!(frame.prev.len(), 1); + assert_eq!(frame.current.pipeline_id(), frame.prev[0].pipeline_id()); + + let new_pipeline_id = PipelineId::new(); + frame.load(new_pipeline_id, url); + + let evicted_id = frame.discard_entry(&frame.prev[0]); + assert_eq!(evicted_id, Some(pipeline_id)); + // Both entries in the session history (prev) should no longer have a pipeline id + assert!(frame.prev[0].pipeline_id().is_none()); + assert!(frame.prev[1].pipeline_id().is_none()); + assert_eq!(frame.current.pipeline_id(), Some(new_pipeline_id)); + + // Updating the pipeline id of one of the entries should update the pipeline id of + // the other entry. + let reloaded_pipeline_id = PipelineId::new(); + frame.prev[0].update_pipeline(reloaded_pipeline_id); + assert_eq!(frame.prev[0].pipeline_id(), Some(reloaded_pipeline_id)); + assert_eq!(frame.prev[1].pipeline_id(), Some(reloaded_pipeline_id)); +} diff --git a/tests/unit/constellation/lib.rs b/tests/unit/constellation/lib.rs new file mode 100644 index 000000000000..4ec4553e8414 --- /dev/null +++ b/tests/unit/constellation/lib.rs @@ -0,0 +1,9 @@ +/* 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/. */ + +extern crate constellation; +extern crate msg; +extern crate servo_url; + +#[cfg(test)] mod frame; diff --git a/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html.ini b/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html.ini deleted file mode 100644 index 3322eb2c4412..000000000000 --- a/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[scroll-restoration-fragment-scrolling-samedoc.html] - type: testharness - [Manual scroll restoration should take precedent over scrolling to fragment in cross doc navigation] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/popstate_event.html.ini b/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/popstate_event.html.ini deleted file mode 100644 index bbe5b73b9227..000000000000 --- a/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/popstate_event.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[popstate_event.html] - type: testharness - [Queue a task to fire popstate event] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/002.html.ini b/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/002.html.ini new file mode 100644 index 000000000000..d92b60ac8ed4 --- /dev/null +++ b/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/002.html.ini @@ -0,0 +1,5 @@ +[002.html] + type: testharness + [Multiple simultaneous navigations] + expected: FAIL + diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini index 86c15b26af03..4f5de2d17083 100644 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini @@ -1,102 +1,23 @@ [001.html] type: testharness - disabled: https://github.com/servo/servo/issues/12580 - [history.length should update when setting location.hash] - expected: FAIL - - [history.pushState must exist] - expected: FAIL - - [history.pushState must exist within iframes] - expected: FAIL - - [initial history.state should be null] - expected: FAIL - - [history.length should update when pushing a state] - expected: FAIL - - [history.state should update after a state is pushed] - expected: FAIL - - [traversing history must traverse pushed states] - expected: FAIL - - [pushState must not be allowed to create invalid URLs] - expected: FAIL - - [pushState must not be allowed to create cross-origin URLs] - expected: FAIL - - [pushState must not be allowed to create cross-origin URLs (about:blank)] - expected: FAIL - - [pushState must not be allowed to create cross-origin URLs (data:URI)] - expected: FAIL - - [pushState should not actually load the new URL] - expected: FAIL - - [security errors are expected to be thrown in the context of the document that owns the history object] - expected: FAIL - - [location.hash must be allowed to change (part 1)] - expected: FAIL - - [location.hash must be allowed to change (part 2)] - expected: FAIL - - [pushState must not alter location.hash when no URL is provided] - expected: FAIL - - [pushState must remove all history after the current state] - expected: FAIL - - [pushState must be able to set location.hash] - expected: FAIL - - [pushState must remove any tasks queued by the history traversal task source] - expected: FAIL - - [pushState must be able to set location.pathname] - expected: FAIL - - [pushState must be able to set absolute URLs to the same host] - expected: FAIL - - [pushState must not be able to use a function as data] - expected: FAIL - - [pushState must not be able to use a DOM node as data] - expected: FAIL - - [pushState must not be able to use an error object as data] - expected: FAIL - - [security errors are expected to be thrown in the context of the document that owns the history object (2)] - expected: FAIL - [pushState must be able to make structured clones of complex objects] expected: FAIL [history.state should also reference a clone of the original object] expected: FAIL - [popstate event should fire when navigation occurs] - expected: FAIL - [popstate event should pass the state data] expected: FAIL [state data should cope with circular object references] expected: FAIL - [state data should be a clone of the original object, not a reference to it] - expected: FAIL - [history.state should also reference a clone of the original object (2)] expected: FAIL [history.state should be a separate clone of the object, not a reference to the object passed to the event handler] expected: FAIL + [pushState must remove any tasks queued by the history traversal task source] + expected: FAIL + diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini index c8350da8e2eb..8865cc12bf44 100644 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini @@ -1,96 +1,17 @@ [002.html] type: testharness - disabled: https://github.com/servo/servo/issues/12580 - [history.length should update when setting location.hash] - expected: FAIL - - [history.replaceState must exist] - expected: FAIL - - [history.replaceState must exist within iframes] - expected: FAIL - - [initial history.state should be null] - expected: FAIL - - [history.length should not update when replacing a state with no URL] - expected: FAIL - - [history.state should update after a state is pushed] - expected: FAIL - - [hash should not change when replaceState is called without a URL] - expected: FAIL - - [history.length should not update when replacing a state with a URL] - expected: FAIL - - [hash should change when replaceState is called with a URL] - expected: FAIL - - [replaceState must replace the existing state without altering the forward history] - expected: FAIL - - [replaceState must not be allowed to create invalid URLs] - expected: FAIL - - [replaceState must not be allowed to create cross-origin URLs] - expected: FAIL - - [replaceState must not be allowed to create cross-origin URLs (about:blank)] - expected: FAIL - - [replaceState must not be allowed to create cross-origin URLs (data:URI)] - expected: FAIL - - [security errors are expected to be thrown in the context of the document that owns the history object] - expected: FAIL - - [replaceState must be able to set location.pathname] - expected: FAIL - - [replaceState must be able to set absolute URLs to the same host] - expected: FAIL - - [replaceState must not remove any tasks queued by the history traversal task source] - expected: FAIL - - [.go must queue a task with the history traversal task source (run asynchronously)] - expected: FAIL - - [replaceState must not be able to use a function as data] - expected: FAIL - - [replaceState must not be able to use a DOM node as data] - expected: FAIL - - [replaceState must not be able to use an error object as data] - expected: FAIL - - [replaceState should not actually load the new URL] - expected: FAIL - - [security errors are expected to be thrown in the context of the document that owns the history object (2)] - expected: FAIL - [replaceState must be able to make structured clones of complex objects] expected: FAIL [history.state should also reference a clone of the original object] expected: FAIL - [popstate event should fire when navigation occurs] - expected: FAIL - [popstate event should pass the state data] expected: FAIL [state data should cope with circular object references] expected: FAIL - [state data should be a clone of the original object, not a reference to it] - expected: FAIL - [history.state should also reference a clone of the original object (2)] expected: FAIL diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/004.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/004.html.ini deleted file mode 100644 index 278160ab559f..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/004.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[004.html] - type: testharness - [browser needs to support hashchange events for this testcase] - expected: FAIL - - [queued .go commands should all be executed when the queue is processed] - expected: FAIL - - [history position should be calculated when executing, not when calling the .go command] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/005.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/005.html.ini deleted file mode 100644 index 760996066882..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/005.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[005.html] - type: testharness - [history.pushState support is needed for this testcase] - expected: FAIL - - [ should register a listener for the popstate event] - expected: FAIL - - [window.onpopstate should register a listener for the popstate event] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/006.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/006.html.ini deleted file mode 100644 index 505c9b8cf838..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/006.html.ini +++ /dev/null @@ -1,14 +0,0 @@ -[006.html] - type: testharness - [history.state should initially be null] - expected: FAIL - - [history.state should still be null onload] - expected: FAIL - - [history.state should still be null after onload] - expected: FAIL - - [writing to history.state should be silently ignored and not throw an error] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/007.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/007.html.ini index 5c3b95e454e7..620417b64b03 100644 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/007.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-history-interface/007.html.ini @@ -1,14 +1,5 @@ [007.html] type: testharness - [history.state should initially be null] - expected: FAIL - - [history.pushState support is needed for this testcase] - expected: FAIL - - [history.state should reflect pushed state] - expected: FAIL - [popstate event should fire before onload fires] expected: FAIL @@ -18,6 +9,6 @@ [history.state should reflect the navigated state onload] expected: FAIL - [history.state should reflect the navigated state after onload] + [popstate event should not fire after onload fires] expected: FAIL diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/008.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/008.html.ini deleted file mode 100644 index 5af01f618f1d..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/008.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[008.html] - type: testharness - [history.pushState URL resolving should be done relative to the document, not the script] - expected: FAIL - - [history.replaceState URL resolving should be done relative to the document, not the script] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/009.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/009.html.ini deleted file mode 100644 index 775bd6adf7d8..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/009.html.ini +++ /dev/null @@ -1,14 +0,0 @@ -[009.html] - type: testharness - [HTTP Referer should use the pushed state] - expected: FAIL - - [document.referrer should use the pushed state] - expected: FAIL - - [HTTP Referer should use the replaced state] - expected: FAIL - - [document.referrer should use the replaced state] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/010.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/010.html.ini deleted file mode 100644 index cd895f71c05b..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/010.html.ini +++ /dev/null @@ -1,14 +0,0 @@ -[010.html] - type: testharness - [HTTP Referer should use the pushed state (before onload)] - expected: FAIL - - [document.referrer should use the pushed state (before onload)] - expected: FAIL - - [HTTP Referer should use the replaced state (before onload)] - expected: FAIL - - [document.referrer should use the replaced state (before onload)] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/011.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/011.html.ini deleted file mode 100644 index 0867b0401e09..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/011.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[011.html] - type: testharness - [pushState should be able to set the location state] - expected: FAIL - - [pushed location should be reflected immediately] - expected: FAIL - - [pushed location should be retained after the page has loaded] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/012.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/012.html.ini deleted file mode 100644 index b153bc5f21ce..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/012.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[012.html] - type: testharness - [replaceState should be able to set the location state] - expected: FAIL - - [replaced location should be reflected immediately] - expected: FAIL - - [replaced location should be retained after the page has loaded] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_001.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_001.html.ini deleted file mode 100644 index 6c1948b3884f..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_001.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_001.html] - type: testharness - [Combine pushState and replaceSate methods] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_002.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_002.html.ini deleted file mode 100644 index b298a5d7c07c..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_002.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_002.html] - type: testharness - [After calling of pushState method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_003.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_003.html.ini deleted file mode 100644 index 0329eef96dec..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_003.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_003.html] - type: testharness - [After calling of pushState and replaceState methods, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_004.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_004.html.ini deleted file mode 100644 index 703bccbb3347..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_004.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_004.html] - type: testharness - [After calling of back method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_005.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_005.html.ini deleted file mode 100644 index 969bd2d84f1b..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_005.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_005.html] - type: testharness - [After calling of forward method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_006.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_006.html.ini deleted file mode 100644 index 416b9e58d13c..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_006.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_006.html] - type: testharness - [After calling of go method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_007.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_007.html.ini deleted file mode 100644 index 5db6960533d4..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_007.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_007.html] - type: testharness - [After calling of back and pushState method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_back.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_back.html.ini deleted file mode 100644 index 5e01759025de..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_back.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_back.html] - type: testharness - [history back] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_forward.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_forward.html.ini deleted file mode 100644 index d3c12080dfa9..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_forward.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_forward.html] - type: testharness - [history forward] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_minus.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_minus.html.ini deleted file mode 100644 index 01f21b309605..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_minus.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_go_minus.html] - type: testharness - [history go minus] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_plus.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_plus.html.ini deleted file mode 100644 index e2999746053f..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_plus.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_go_plus.html] - type: testharness - [history go plus] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate.html.ini deleted file mode 100644 index d9c5e0b38af1..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_pushstate.html] - type: testharness - [history pushState] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_err.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_err.html.ini deleted file mode 100644 index 5a4e399ac99d..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_err.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_pushstate_err.html] - type: testharness - [history pushState SECURITY_ERR] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html.ini deleted file mode 100644 index 40a1deb0cc3e..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_pushstate_nooptionalparam.html] - type: testharness - [history pushState NoOptionalParam] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate.html.ini deleted file mode 100644 index e2068698c0d6..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_replacestate.html] - type: testharness - [history replaceState] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_err.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_err.html.ini deleted file mode 100644 index 316097c5b4d1..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_err.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_replacestate_err.html] - type: testharness - [history replaceState SECURITY_ERR] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html.ini deleted file mode 100644 index d523d0b78d0c..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_replacestate_nooptionalparam.html] - type: testharness - [history replaceStateNoOptionalParam] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_state.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_state.html.ini deleted file mode 100644 index 2819759ee82b..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_state.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_state.html] - type: testharness - [history state] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-location-interface/location_hash.html.ini b/tests/wpt/metadata/html/browsers/history/the-location-interface/location_hash.html.ini index 2cf94f6e2e68..14fb78311f2e 100644 --- a/tests/wpt/metadata/html/browsers/history/the-location-interface/location_hash.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-location-interface/location_hash.html.ini @@ -1,8 +1,5 @@ [location_hash.html] type: testharness - [location hash] - expected: FAIL - [Setting location.hash on srcdoc iframe] expected: FAIL diff --git a/tests/wpt/metadata/html/browsers/history/the-location-interface/location_search.html.ini b/tests/wpt/metadata/html/browsers/history/the-location-interface/location_search.html.ini deleted file mode 100644 index fa2ec8514b8e..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-location-interface/location_search.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[location_search.html] - type: testharness - [location search] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/windows/browsing-context-names/001.html.ini b/tests/wpt/metadata/html/browsers/windows/browsing-context-names/001.html.ini deleted file mode 100644 index b3538369e2e9..000000000000 --- a/tests/wpt/metadata/html/browsers/windows/browsing-context-names/001.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[001.html] - type: testharness - expected: TIMEOUT diff --git a/tests/wpt/metadata/html/dom/interfaces.html.ini b/tests/wpt/metadata/html/dom/interfaces.html.ini index e7f2a58aca8b..2d4a4f0bffed 100644 --- a/tests/wpt/metadata/html/dom/interfaces.html.ini +++ b/tests/wpt/metadata/html/dom/interfaces.html.ini @@ -4524,15 +4524,6 @@ [BarProp interface: attribute visible] expected: FAIL - [History interface: attribute state] - expected: FAIL - - [History interface: operation pushState(any,DOMString,DOMString)] - expected: FAIL - - [History interface: operation replaceState(any,DOMString,DOMString)] - expected: FAIL - [History interface: window.history must inherit property "state" with the proper type (1)] expected: FAIL @@ -4548,15 +4539,9 @@ [History interface: window.history must inherit property "pushState" with the proper type (5)] expected: FAIL - [History interface: calling pushState(any,DOMString,DOMString) on window.history with too few arguments must throw TypeError] - expected: FAIL - [History interface: window.history must inherit property "replaceState" with the proper type (6)] expected: FAIL - [History interface: calling replaceState(any,DOMString,DOMString) on window.history with too few arguments must throw TypeError] - expected: FAIL - [Location interface: calling replace(DOMString) on window.location with too few arguments must throw TypeError] expected: FAIL @@ -6009,15 +5994,6 @@ [History interface: window.history must inherit property "scrollRestoration" with the proper type (1)] expected: FAIL - [History interface: window.history must inherit property "state" with the proper type (2)] - expected: FAIL - - [History interface: window.history must inherit property "pushState" with the proper type (6)] - expected: FAIL - - [History interface: window.history must inherit property "replaceState" with the proper type (7)] - expected: FAIL - [HTMLAllCollection interface: attribute length] expected: FAIL diff --git a/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html b/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html index eb0c15aab896..70d796dd2db0 100644 --- a/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html +++ b/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html @@ -73,7 +73,7 @@ assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test3' ); }, 'hash should change when replaceState is called with a URL'); history.go(-1); - setTimeout(tests3,50); //.go is queued to end of thread + setTimeout(tests3,100); //.go is queued to end of thread } function tests3() { test(function () {