diff options
author | bors-servo <metajack+bors@gmail.com> | 2015-04-19 18:13:59 -0500 |
---|---|---|
committer | bors-servo <metajack+bors@gmail.com> | 2015-04-19 18:13:59 -0500 |
commit | 8b8daa24b8d25c531ea74a70b4b6e25cb3d7d58c (patch) | |
tree | 075bee0237ce4c383c2255de8c64f8967a34b15e | |
parent | 9c7c289acae3ea012338a5b25bc50a10e7f7074d (diff) | |
parent | 18d465bcd273f4629998b62ba37937878211e81a (diff) | |
download | servo-8b8daa24b8d25c531ea74a70b4b6e25cb3d7d58c.tar.gz servo-8b8daa24b8d25c531ea74a70b4b6e25cb3d7d58c.zip |
Auto merge of #5559 - glennw:iframe-focus, r=jdm
<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/5559)
<!-- Reviewable:end -->
-rw-r--r-- | components/compositing/constellation.rs | 80 | ||||
-rw-r--r-- | components/msg/constellation_msg.rs | 11 | ||||
-rw-r--r-- | components/script/dom/document.rs | 19 | ||||
-rw-r--r-- | components/script/dom/htmlelement.rs | 5 | ||||
-rw-r--r-- | components/script/script_task.rs | 63 | ||||
-rw-r--r-- | components/script_traits/lib.rs | 2 |
6 files changed, 141 insertions, 39 deletions
diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index 3d098fbe01e..0520fa4859f 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -87,6 +87,9 @@ pub struct Constellation<LTF, STF> { /// The next free ID to assign to a frame. next_frame_id: FrameId, + /// Pipeline ID that has currently focused element for key events. + focus_pipeline_id: Option<PipelineId>, + /// Navigation operations that are in progress. pending_frames: Vec<FrameChange>, @@ -198,6 +201,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { next_pipeline_id: PipelineId(0), root_frame_id: None, next_frame_id: FrameId(0), + focus_pipeline_id: None, time_profiler_chan: time_profiler_chan, mem_profiler_chan: mem_profiler_chan, window_size: WindowSizeData { @@ -387,6 +391,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { debug!("constellation got get root pipeline message"); self.handle_get_root_pipeline(resp_chan); } + ConstellationMsg::FocusMsg(pipeline_id) => { + debug!("constellation got focus message"); + self.handle_focus_msg(pipeline_id); + } } true } @@ -594,6 +602,11 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { self.pipeline_to_frame_map.get(&pipeline_id).map(|id| *id) }).unwrap(); + // Check if the currently focused pipeline is the pipeline being replaced + // (or a child of it). This has to be done here, before the current + // frame tree is modified below. + let update_focus_pipeline = self.focused_pipeline_in_tree(frame_id); + // Get the ids for the previous and next pipelines. let (prev_pipeline_id, next_pipeline_id) = { let frame = self.mut_frame(frame_id); @@ -621,6 +634,13 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { (prev, next) }; + // If the currently focused pipeline is the one being changed (or a child + // of the pipeline being changed) then update the focus pipeline to be + // the replacement. + if update_focus_pipeline { + self.focus_pipeline_id = Some(next_pipeline_id); + } + // Suspend the old pipeline, and resume the new one. self.pipeline(prev_pipeline_id).freeze(); self.pipeline(next_pipeline_id).thaw(); @@ -645,10 +665,16 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { } fn handle_key_msg(&self, key: Key, state: KeyState, mods: KeyModifiers) { - match self.root_frame_id { - Some(root_frame_id) => { - let root_frame = self.frame(root_frame_id); - let pipeline = self.pipeline(root_frame.current); + // Send to the explicitly focused pipeline (if it exists), or the root + // frame's current pipeline. If neither exist, fall back to sending to + // the compositor below. + let target_pipeline_id = self.focus_pipeline_id.or(self.root_frame_id.map(|frame_id| { + self.frame(frame_id).current + })); + + match target_pipeline_id { + Some(target_pipeline_id) => { + let pipeline = self.pipeline(target_pipeline_id); let ScriptControlChan(ref chan) = pipeline.script_chan; let event = CompositorEvent::KeyEvent(key, state, mods); chan.send(ConstellationControlMsg::SendEvent(pipeline.id, @@ -691,7 +717,39 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { resp_chan.send(pipeline_id).unwrap(); } + fn focus_parent_pipeline(&self, pipeline_id: PipelineId) { + // Send a message to the parent of the provided pipeline (if it exists) + // telling it to mark the iframe element as focused. + if let Some((containing_pipeline_id, subpage_id)) = self.pipeline(pipeline_id).parent_info { + let pipeline = self.pipeline(containing_pipeline_id); + let ScriptControlChan(ref script_channel) = pipeline.script_chan; + let event = ConstellationControlMsg::FocusIFrameMsg(containing_pipeline_id, + subpage_id); + script_channel.send(event).unwrap(); + + self.focus_parent_pipeline(containing_pipeline_id); + } + } + + fn handle_focus_msg(&mut self, pipeline_id: PipelineId) { + self.focus_pipeline_id = Some(pipeline_id); + + // Focus parent iframes recursively + self.focus_parent_pipeline(pipeline_id); + } + fn add_or_replace_pipeline_in_frame_tree(&mut self, frame_change: FrameChange) { + + // If the currently focused pipeline is the one being changed (or a child + // of the pipeline being changed) then update the focus pipeline to be + // the replacement. + if let Some(old_pipeline_id) = frame_change.old_pipeline_id { + let old_frame_id = *self.pipeline_to_frame_map.get(&old_pipeline_id).unwrap(); + if self.focused_pipeline_in_tree(old_frame_id) { + self.focus_pipeline_id = Some(frame_change.new_pipeline_id); + } + } + let evicted_frames = match frame_change.old_pipeline_id { Some(old_pipeline_id) => { // The new pipeline is replacing an old one. @@ -923,8 +981,20 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { } } + fn focused_pipeline_in_tree(&self, frame_id: FrameId) -> bool { + self.focus_pipeline_id.map_or(false, |pipeline_id| { + self.pipeline_exists_in_tree(pipeline_id, Some(frame_id)) + }) + } + fn pipeline_is_in_current_frame(&self, pipeline_id: PipelineId) -> bool { - self.current_frame_tree_iter(self.root_frame_id) + self.pipeline_exists_in_tree(pipeline_id, self.root_frame_id) + } + + fn pipeline_exists_in_tree(&self, + pipeline_id: PipelineId, + root_frame_id: Option<FrameId>) -> bool { + self.current_frame_tree_iter(root_frame_id) .any(|current_frame| current_frame.current == pipeline_id) } diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index 2097d658b0f..0f911a7e523 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -194,6 +194,13 @@ bitflags! { } } +/// Specifies the type of focus event that is sent to a pipeline +#[derive(Copy, PartialEq)] +pub enum FocusType { + Element, // The first focus message - focus the element itself + Parent, // Focusing a parent element (an iframe) +} + /// Messages from the compositor and script to the constellation. pub enum Msg { Exit, @@ -219,7 +226,9 @@ pub enum Msg { /// Requests that the constellation instruct layout to begin a new tick of the animation. TickAnimation(PipelineId), // Request that the constellation send the current root pipeline id over a provided channel - GetRootPipeline(Sender<Option<PipelineId>>) + GetRootPipeline(Sender<Option<PipelineId>>), + /// Notifies the constellation that this frame has received focus. + FocusMsg(PipelineId), } // https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API#Events diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 3fc82215167..aa360edd3a3 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -64,7 +64,7 @@ use dom::window::{Window, WindowHelpers, ReflowReason}; use layout_interface::{HitTestResponse, MouseOverResponse}; use msg::compositor_msg::ScriptListener; use msg::constellation_msg::Msg as ConstellationMsg; -use msg::constellation_msg::{ConstellationChan, Key, KeyState, KeyModifiers, MozBrowserEvent}; +use msg::constellation_msg::{ConstellationChan, FocusType, Key, KeyState, KeyModifiers, MozBrowserEvent}; use msg::constellation_msg::{SUPER, ALT, SHIFT, CONTROL}; use net_traits::CookieSource::NonHTTP; use net_traits::ControlMsg::{SetCookiesForUrl, GetCookiesForUrl}; @@ -216,7 +216,7 @@ pub trait DocumentHelpers<'a> { fn is_scripting_enabled(self) -> bool; fn begin_focus_transaction(self); fn request_focus(self, elem: JSRef<Element>); - fn commit_focus_transaction(self); + fn commit_focus_transaction(self, focus_type: FocusType); fn title_changed(self); fn send_title_to_compositor(self); fn dirty_all_nodes(self); @@ -460,7 +460,7 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { /// Reassign the focus context to the element that last requested focus during this /// transaction, or none if no elements requested it. - fn commit_focus_transaction(self) { + fn commit_focus_transaction(self, focus_type: FocusType) { //TODO: dispatch blur, focus, focusout, and focusin events if let Some(ref elem) = self.focused.get().root() { @@ -473,9 +473,16 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { if let Some(ref elem) = self.focused.get().root() { let node: JSRef<Node> = NodeCast::from_ref(elem.r()); node.set_focus_state(true); + + // Update the focus state for all elements in the focus chain. + // https://html.spec.whatwg.org/multipage/#focus-chain + if focus_type == FocusType::Element { + let window = self.window.root(); + let ConstellationChan(ref chan) = window.r().constellation_chan(); + let event = ConstellationMsg::FocusMsg(window.r().pipeline()); + chan.send(event).unwrap(); + } } - // TODO: Update the focus state for all elements in the focus chain. - // https://html.spec.whatwg.org/multipage/#focus-chain } /// Handles any updates when the document's title has changed. @@ -555,7 +562,7 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { // https://html.spec.whatwg.org/multipage/#run-authentic-click-activation-steps el.r().authentic_click_activation(event); - self.commit_focus_transaction(); + self.commit_focus_transaction(FocusType::Element); window.r().reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, ReflowReason::MouseEvent); } diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 84f1d698cdf..68c097158dc 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -28,6 +28,7 @@ use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_n use dom::virtualmethods::VirtualMethods; use dom::window::WindowHelpers; +use msg::constellation_msg::FocusType; use util::str::DOMString; use string_cache::Atom; @@ -145,7 +146,7 @@ impl<'a> HTMLElementMethods for JSRef<'a, HTMLElement> { let document = document.r(); document.begin_focus_transaction(); document.request_focus(element); - document.commit_focus_transaction(); + document.commit_focus_transaction(FocusType::Element); } // https://html.spec.whatwg.org/multipage/#dom-blur @@ -159,7 +160,7 @@ impl<'a> HTMLElementMethods for JSRef<'a, HTMLElement> { let document = document_from_node(self).root(); document.r().begin_focus_transaction(); // If `request_focus` is not called, focus will be set to None. - document.r().commit_focus_transaction(); + document.r().commit_focus_transaction(FocusType::Element); } } diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 4956e810d0f..c9c51eaeb9b 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -24,7 +24,7 @@ use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, Documen use dom::bindings::codegen::InheritTypes::{ElementCast, EventTargetCast, HTMLIFrameElementCast, NodeCast, EventCast}; use dom::bindings::conversions::FromJSValConvertible; use dom::bindings::conversions::StringificationBehavior; -use dom::bindings::js::{JS, JSRef, OptionalRootable, RootedReference}; +use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, RootedReference}; use dom::bindings::js::{RootCollection, RootCollectionPtr}; use dom::bindings::refcounted::{LiveDOMReferences, Trusted, TrustedReference}; use dom::bindings::structuredclone::StructuredCloneData; @@ -33,7 +33,7 @@ use dom::bindings::utils::{wrap_for_same_compartment, pre_wrap}; use dom::document::{Document, IsHTMLDocument, DocumentHelpers, DocumentProgressHandler, DocumentProgressTask, DocumentSource}; use dom::element::{Element, AttributeHandlers}; use dom::event::{Event, EventHelpers, EventBubbles, EventCancelable}; -use dom::htmliframeelement::HTMLIFrameElementHelpers; +use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementHelpers}; use dom::uievent::UIEvent; use dom::eventtarget::EventTarget; use dom::node::{self, Node, NodeHelpers, NodeDamage, window_from_node}; @@ -58,7 +58,7 @@ use script_traits::{ConstellationControlMsg, ScriptControlChan}; use script_traits::ScriptTaskFactory; use msg::compositor_msg::ReadyState::{FinishedLoading, Loading, PerformingLayout}; use msg::compositor_msg::{LayerId, ScriptListener}; -use msg::constellation_msg::{ConstellationChan}; +use msg::constellation_msg::{ConstellationChan, FocusType}; use msg::constellation_msg::{LoadData, PipelineId, SubpageId, MozBrowserEvent, WorkerId}; use msg::constellation_msg::{Failure, WindowSizeData, PipelineExitType}; use msg::constellation_msg::Msg as ConstellationMsg; @@ -694,6 +694,8 @@ impl ScriptTask { old_subpage_id, new_subpage_id) => self.handle_update_subpage_id(containing_pipeline_id, old_subpage_id, new_subpage_id), + ConstellationControlMsg::FocusIFrameMsg(containing_pipeline_id, subpage_id) => + self.handle_focus_iframe_msg(containing_pipeline_id, subpage_id), } } @@ -841,6 +843,23 @@ impl ScriptTask { window.r().thaw(); } + fn handle_focus_iframe_msg(&self, + parent_pipeline_id: PipelineId, + subpage_id: SubpageId) { + let borrowed_page = self.root_page(); + let page = borrowed_page.find(parent_pipeline_id).unwrap(); + + let doc = page.document().root(); + let frame_element = self.find_iframe(doc.r(), subpage_id).root(); + + if let Some(frame_element) = frame_element { + let element: JSRef<Element> = ElementCast::from_ref(frame_element.r()); + doc.r().begin_focus_transaction(); + doc.r().request_focus(element); + doc.r().commit_focus_transaction(FocusType::Parent); + } + } + /// Handles a mozbrowser event, for example see: /// https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadstart fn handle_mozbrowser_event_msg(&self, @@ -851,11 +870,7 @@ impl ScriptTask { let frame_element = borrowed_page.find(parent_pipeline_id).and_then(|page| { let doc = page.document().root(); - let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); - - doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_temporary) - .find(|node| node.root().r().subpage_id() == Some(subpage_id)) + self.find_iframe(doc.r(), subpage_id) }).root(); if let Some(frame_element) = frame_element { @@ -871,11 +886,7 @@ impl ScriptTask { let frame_element = borrowed_page.find(containing_pipeline_id).and_then(|page| { let doc = page.document().root(); - let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); - - doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_temporary) - .find(|node| node.root().r().subpage_id() == Some(old_subpage_id)) + self.find_iframe(doc.r(), old_subpage_id) }).root(); frame_element.unwrap().r().update_subpage_id(new_subpage_id); @@ -990,12 +1001,7 @@ impl ScriptTask { borrowed_page.as_ref().and_then(|borrowed_page| { borrowed_page.find(parent_id).and_then(|page| { let doc = page.document().root(); - let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); - - doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_temporary) - .find(|node| node.root().r().subpage_id() == Some(subpage_id)) - .map(ElementCast::from_temporary) + self.find_iframe(doc.r(), subpage_id) }) }) }).root(); @@ -1094,7 +1100,8 @@ impl ScriptTask { last_modified, DocumentSource::FromParser).root(); - window.r().init_browser_context(document.r(), frame_element.r()); + let frame_element = frame_element.r().map(|elem| ElementCast::from_ref(elem)); + window.r().init_browser_context(document.r(), frame_element); // Create the root frame page.set_frame(Some(Frame { @@ -1186,6 +1193,16 @@ impl ScriptTask { window.r().reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, reason); } + /// Find an iframe element in a provided document. + fn find_iframe(&self, doc: JSRef<Document>, subpage_id: SubpageId) + -> Option<Temporary<HTMLIFrameElement>> { + let doc: JSRef<Node> = NodeCast::from_ref(doc); + + doc.traverse_preorder() + .filter_map(HTMLIFrameElementCast::to_temporary) + .find(|node| node.root().r().subpage_id() == Some(subpage_id)) + } + /// This is the main entry point for receiving and dispatching DOM events. /// /// TODO: Actually perform DOM event dispatch. @@ -1272,11 +1289,7 @@ impl ScriptTask { let borrowed_page = self.root_page(); let iframe = borrowed_page.find(pipeline_id).and_then(|page| { let doc = page.document().root(); - let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); - - doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_temporary) - .find(|node| node.root().r().subpage_id() == Some(subpage_id)) + self.find_iframe(doc.r(), subpage_id) }).root(); if let Some(iframe) = iframe.r() { iframe.navigate_child_browsing_context(load_data.url); diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 57ceb73f2b3..a21a6ae3d1f 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -74,6 +74,8 @@ pub enum ConstellationControlMsg { MozBrowserEventMsg(PipelineId, SubpageId, MozBrowserEvent), /// Updates the current subpage id of a given iframe UpdateSubpageId(PipelineId, SubpageId, SubpageId), + /// Set an iframe to be focused. Used when an element in an iframe gains focus. + FocusIFrameMsg(PipelineId, SubpageId), } /// The mouse button involved in the event. |