servo: Merge #11781 - Issue 8719: Add basic support for :active selector (from sjmelia:8719_support_for_css_active); r=Manishearth
authorSteve Melia <steve.j.melia@gmail.com>
Fri, 08 Jul 2016 05:06:57 -0700
changeset 339243 e32e3826568bbfe61354d7c2080aa9f2400dd9d7
parent 339242 b3a21401450396975119672f12449ea60fa3e564
child 339244 45b905a7cbbe67fbce1f5e9f429f6127cdb1eed3
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth
servo: Merge #11781 - Issue 8719: Add basic support for :active selector (from sjmelia:8719_support_for_css_active); r=Manishearth <!-- Please describe your changes on the following line: --> Added toggling of active state for element and parents on mousedown/mouseup. Active state is removed when mouseout. (hover) - As with my other PR i'm struggling a bit with the automated testing. I've added a manual test case and found quirks-mode/active-and-hover-manual.html which - aside from also being a manual test, is functional in Firefox but does not render correctly in Servo. - Not implemented: In Firefox, behaviour differs with a <!DOCTYPE HTML> and an anchor does not lose it's activation on mouseout; whereas a button does. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #8719 (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 5afdf7fb5c9a4c997a287f6d61ec05857f073ce2
servo/components/script/dom/activation.rs
servo/components/script/dom/document.rs
servo/components/script/dom/element.rs
servo/components/script/dom/window.rs
servo/tests/html/active_selector.html
--- a/servo/components/script/dom/activation.rs
+++ b/servo/components/script/dom/activation.rs
@@ -5,16 +5,19 @@
 use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::str::DOMString;
 use dom::element::Element;
 use dom::event::{Event, EventBubbles, EventCancelable};
 use dom::eventtarget::EventTarget;
 use dom::mouseevent::MouseEvent;
 use dom::node::window_from_node;
+use dom::window::ReflowReason;
+use script_layout_interface::message::ReflowQueryType;
+use style::context::ReflowGoal;
 
 /// Trait for elements with defined activation behavior
 pub trait Activatable {
     fn as_element(&self) -> &Element;
 
     // Is this particular instance of the element activatable?
     fn is_instance_activatable(&self) -> bool;
 
@@ -24,16 +27,35 @@ pub trait Activatable {
     // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps
     fn canceled_activation(&self);
 
     // https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps
     fn activation_behavior(&self, event: &Event, target: &EventTarget);
 
     // https://html.spec.whatwg.org/multipage/#implicit-submission
     fn implicit_submission(&self, ctrlKey: bool, shiftKey: bool, altKey: bool, metaKey: bool);
+
+    // https://html.spec.whatwg.org/multipage/#concept-selector-active
+    fn enter_formal_activation_state(&self) {
+        self.as_element().set_active_state(true);
+
+        let win = window_from_node(self.as_element());
+        win.reflow(ReflowGoal::ForDisplay,
+                   ReflowQueryType::NoQuery,
+                   ReflowReason::ElementStateChanged);
+    }
+
+    fn exit_formal_activation_state(&self) {
+        self.as_element().set_active_state(false);
+
+        let win = window_from_node(self.as_element());
+        win.reflow(ReflowGoal::ForDisplay,
+                   ReflowQueryType::NoQuery,
+                   ReflowReason::ElementStateChanged);
+    }
 }
 
 /// Whether an activation was initiated via the click() method
 #[derive(PartialEq)]
 pub enum ActivationSource {
     FromClick,
     NotFromClick,
 }
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -730,19 +730,32 @@ impl Document {
                                     false,
                                     0i16,
                                     None);
         let event = event.upcast::<Event>();
 
         // https://w3c.github.io/uievents/#trusted-events
         event.set_trusted(true);
         // https://html.spec.whatwg.org/multipage/#run-authentic-click-activation-steps
+        let activatable = el.as_maybe_activatable();
         match mouse_event_type {
             MouseEventType::Click => el.authentic_click_activation(event),
-            _ => {
+            MouseEventType::MouseDown => {
+                if let Some(a) = activatable {
+                    a.enter_formal_activation_state();
+                }
+
+                let target = node.upcast();
+                event.fire(target);
+            },
+            MouseEventType::MouseUp => {
+                if let Some(a) = activatable {
+                    a.exit_formal_activation_state();
+                }
+
                 let target = node.upcast();
                 event.fire(target);
             },
         }
 
         if let MouseEventType::Click = mouse_event_type {
             self.commit_focus_transaction(FocusType::Element);
         }
@@ -899,16 +912,17 @@ impl Document {
         if let Some(old_target) = prev_mouse_over_target.get() {
             // If the old target is an ancestor of the new target, this can be skipped
             // completely, since the node's hover state will be reseted below.
             if !old_target_is_ancestor_of_new_target {
                 for element in old_target.upcast::<Node>()
                                          .inclusive_ancestors()
                                          .filter_map(Root::downcast::<Element>) {
                     element.set_hover_state(false);
+                    element.set_active_state(false);
                 }
             }
 
             // Remove hover state to old target and its parents
             self.fire_mouse_event(client_point, old_target.upcast(), "mouseout".to_owned());
 
             // TODO: Fire mouseleave here only if the old target is
             // not an ancestor of the new target.
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -2517,18 +2517,23 @@ impl Element {
         }
         self.state.set(state);
     }
 
     pub fn active_state(&self) -> bool {
         self.state.get().contains(IN_ACTIVE_STATE)
     }
 
+    /// https://html.spec.whatwg.org/multipage/#concept-selector-active
     pub fn set_active_state(&self, value: bool) {
-        self.set_state(IN_ACTIVE_STATE, value)
+        self.set_state(IN_ACTIVE_STATE, value);
+
+        if let Some(parent) = self.upcast::<Node>().GetParentElement() {
+            parent.set_active_state(value);
+        }
     }
 
     pub fn focus_state(&self) -> bool {
         self.state.get().contains(IN_FOCUS_STATE)
     }
 
     pub fn set_focus_state(&self, value: bool) {
         self.set_state(IN_FOCUS_STATE, value);
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -123,16 +123,17 @@ pub enum ReflowReason {
     DocumentLoaded,
     StylesheetLoaded,
     ImageLoaded,
     RequestAnimationFrame,
     WebFontLoaded,
     FramedContentChanged,
     IFrameLoadEvent,
     MissingExplicitReflow,
+    ElementStateChanged,
 }
 
 pub type ScrollPoint = Point2D<Au>;
 
 #[dom_struct]
 pub struct Window {
     eventtarget: EventTarget,
     #[ignore_heap_size_of = "trait objects are hard"]
@@ -1748,12 +1749,13 @@ fn debug_reflow_events(id: PipelineId, g
         ReflowReason::DocumentLoaded => "\tDocumentLoaded",
         ReflowReason::StylesheetLoaded => "\tStylesheetLoaded",
         ReflowReason::ImageLoaded => "\tImageLoaded",
         ReflowReason::RequestAnimationFrame => "\tRequestAnimationFrame",
         ReflowReason::WebFontLoaded => "\tWebFontLoaded",
         ReflowReason::FramedContentChanged => "\tFramedContentChanged",
         ReflowReason::IFrameLoadEvent => "\tIFrameLoadEvent",
         ReflowReason::MissingExplicitReflow => "\tMissingExplicitReflow",
+        ReflowReason::ElementStateChanged => "\tElementStateChanged",
     });
 
     println!("{}", debug_msg);
 }
new file mode 100644
--- /dev/null
+++ b/servo/tests/html/active_selector.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<style>
+:active {border:1px solid #A61D61; background-color:#DC2F85; color:#333232;}
+</style>
+<body>
+	<fieldset>
+		<a href="https://servo.org/">
+			Link
+		</a>
+		<button>Click Me!</button>
+		<button disabled>You can't activate me</button>
+		<a>Anchor with no href</a>
+		<link href="www.mozilla.com">Link</link>
+		<link>Link with no href</link>
+	</fieldset>
+</body>
+</html>