diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2016-08-03 14:53:30 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-08-03 14:53:30 -0500 |
commit | 7a7bdf51adc3c5051f93c8044797c8fcaf89953c (patch) | |
tree | ae84bf2a6626f5acf58520cd341d8da7f0f476ef | |
parent | ef0f728f1eacb77d4ed82e35891bd77f0c882ff7 (diff) | |
parent | 04f536957728948b59fc3269f45cf469f7771c63 (diff) | |
download | servo-7a7bdf51adc3c5051f93c8044797c8fcaf89953c.tar.gz servo-7a7bdf51adc3c5051f93c8044797c8fcaf89953c.zip |
Auto merge of #11726 - sjmelia:7720_add_target_selector, r=mbrubeck
Issue 7720: Add target selector and update when scrolling to fragment
<!-- Please describe your changes on the following line: -->
Add the target pseudo selector and set/unset it during scrolling to fragment. This change is not complete as no repaint is triggered after the selector is added - it will only take effect after a repaint is triggered by e.g. hovering over another element. (See manual test)
I would like some help because i'm not sure how to resolve this; I can only think to call window.reflow.
I added a manual test case, don't think this counts really! I think the applicable automated test is in /tests/wpt/web-platform-tests/dom/nodes/Element-matches.html but it currently fails, I think due to the above.
---
<!-- 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 #7720 (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. -->
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/11726)
<!-- Reviewable:end -->
-rw-r--r-- | components/script/dom/document.rs | 19 | ||||
-rw-r--r-- | components/script/dom/element.rs | 11 | ||||
-rw-r--r-- | components/script/layout_wrapper.rs | 3 | ||||
-rw-r--r-- | components/script/script_thread.rs | 25 | ||||
-rw-r--r-- | components/style/element_state.rs | 2 | ||||
-rw-r--r-- | components/style/servo_selector_impl.rs | 3 | ||||
-rw-r--r-- | tests/html/test_target_pseudoselector.html | 51 | ||||
-rw-r--r-- | tests/wpt/metadata/dom/nodes/Element-matches.html.ini | 2 | ||||
-rw-r--r-- | tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All-xht.xht.ini | 24 | ||||
-rw-r--r-- | tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All.html.ini | 24 |
10 files changed, 105 insertions, 59 deletions
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 78ccbbdf1f5..e70501d3711 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -241,6 +241,8 @@ pub struct Document { referrer_policy: Cell<Option<ReferrerPolicy>>, /// https://html.spec.whatwg.org/multipage/#dom-document-referrer referrer: Option<String>, + /// https://html.spec.whatwg.org/multipage/#target-element + target_element: MutNullableHeap<JS<Element>>, } #[derive(JSTraceable, HeapSizeOf)] @@ -1735,6 +1737,7 @@ impl Document { origin: origin, referrer: referrer, referrer_policy: Cell::new(referrer_policy), + target_element: MutNullableHeap::new(None), } } @@ -1886,6 +1889,22 @@ impl Document { pub fn get_referrer_policy(&self) -> Option<ReferrerPolicy> { return self.referrer_policy.get(); } + + pub fn set_target_element(&self, node: Option<&Element>) { + if let Some(ref element) = self.target_element.get() { + element.set_target_state(false); + } + + self.target_element.set(node); + + if let Some(ref element) = self.target_element.get() { + element.set_target_state(true); + } + + self.window.reflow(ReflowGoal::ForDisplay, + ReflowQueryType::NoQuery, + ReflowReason::ElementStateChanged); + } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index ae3c31d151c..c0affb40aea 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -2324,7 +2324,8 @@ impl<'a> ::selectors::Element for Root<Element> { NonTSPseudoClass::Checked | NonTSPseudoClass::Indeterminate | NonTSPseudoClass::ReadWrite | - NonTSPseudoClass::PlaceholderShown => + NonTSPseudoClass::PlaceholderShown | + NonTSPseudoClass::Target => Element::state(self).contains(pseudo_class.state_flag()), } } @@ -2586,6 +2587,14 @@ impl Element { self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } } + + pub fn target_state(&self) -> bool { + self.state.get().contains(IN_TARGET_STATE) + } + + pub fn set_target_state(&self, value: bool) { + self.set_state(IN_TARGET_STATE, value) + } } impl Element { diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index bfd7a8273d9..83245efe9f1 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -551,7 +551,8 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { NonTSPseudoClass::Checked | NonTSPseudoClass::Indeterminate | NonTSPseudoClass::ReadWrite | - NonTSPseudoClass::PlaceholderShown => + NonTSPseudoClass::PlaceholderShown | + NonTSPseudoClass::Target => self.element.get_state_for_layout().contains(pseudo_class.state_flag()) } } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index e3d2766c84c..c7e7dde4ad3 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -1198,6 +1198,22 @@ impl ScriptThread { // https://html.spec.whatwg.org/multipage/#the-end step 7 let handler = box DocumentProgressHandler::new(Trusted::new(doc)); self.dom_manipulation_task_source.queue(handler, GlobalRef::Window(doc.window())).unwrap(); + + if let Some(fragment) = doc.url().fragment() { + self.check_and_scroll_fragment(fragment, pipeline, doc); + } + } + + fn check_and_scroll_fragment(&self, fragment: &str, pipeline_id: PipelineId, doc: &Document) { + match doc.find_fragment_node(fragment) { + Some(ref node) => { + doc.set_target_element(Some(node.r())); + self.scroll_fragment_point(pipeline_id, node.r()); + } + None => { + doc.set_target_element(None); + } + } } fn collect_reports(&self, reports_chan: ReportsChan) { @@ -1996,7 +2012,7 @@ impl ScriptThread { /// The entry point for content to notify that a new load has been requested /// for the given pipeline (specifically the "navigate" algorithm). fn handle_navigate(&self, pipeline_id: PipelineId, subpage_id: Option<SubpageId>, load_data: LoadData) { - // Step 8. + // Step 7. { let nurl = &load_data.url; if let Some(fragment) = nurl.fragment() { @@ -2007,12 +2023,7 @@ impl ScriptThread { let url = document.url(); if &url[..Position::AfterQuery] == &nurl[..Position::AfterQuery] && load_data.method == Method::Get { - match document.find_fragment_node(fragment) { - Some(ref node) => { - self.scroll_fragment_point(pipeline_id, node.r()); - } - None => {} - } + self.check_and_scroll_fragment(fragment, pipeline_id, document.r()); return; } } diff --git a/components/style/element_state.rs b/components/style/element_state.rs index 1f0439cfa44..f394a7003ef 100644 --- a/components/style/element_state.rs +++ b/components/style/element_state.rs @@ -33,5 +33,7 @@ bitflags! { const IN_READ_WRITE_STATE = 0x80, #[doc = "https://html.spec.whatwg.org/multipage/#selector-placeholder-shown"] const IN_PLACEHOLDER_SHOWN_STATE = 0x0100, + #[doc = "https://html.spec.whatwg.org/multipage/#selector-target"] + const IN_TARGET_STATE = 0x0200, } } diff --git a/components/style/servo_selector_impl.rs b/components/style/servo_selector_impl.rs index cee046e62f3..5904cc3a478 100644 --- a/components/style/servo_selector_impl.rs +++ b/components/style/servo_selector_impl.rs @@ -66,6 +66,7 @@ pub enum NonTSPseudoClass { ReadWrite, ReadOnly, PlaceholderShown, + Target, } impl NonTSPseudoClass { @@ -82,6 +83,7 @@ impl NonTSPseudoClass { Indeterminate => IN_INDETERMINATE_STATE, ReadOnly | ReadWrite => IN_READ_WRITE_STATE, PlaceholderShown => IN_PLACEHOLDER_SHOWN_STATE, + Target => IN_TARGET_STATE, AnyLink | Link | @@ -117,6 +119,7 @@ impl SelectorImpl for ServoSelectorImpl { "read-write" => ReadWrite, "read-only" => ReadOnly, "placeholder-shown" => PlaceholderShown, + "target" => Target, "-servo-nonzero-border" => { if !context.in_user_agent_stylesheet { return Err(()); diff --git a/tests/html/test_target_pseudoselector.html b/tests/html/test_target_pseudoselector.html new file mode 100644 index 00000000000..80b19520999 --- /dev/null +++ b/tests/html/test_target_pseudoselector.html @@ -0,0 +1,51 @@ +<html> + <head> + <style> + .pass { color: green; } + .fail { color: red; } + :target { color: red; } + span:hover { color: green; } + </style> + </head> + <body> + <div> + <a href="#foo">Target Foo By Link</a> + <span id="clickme">Target Foo By JS</span> + </div> + + <div> + <button id="findme">Check for element with :target selector</button> + <span id="result"></span> + </div> + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc sodales leo in orci pulvinar, ut tincidunt ipsum vestibulum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis et vestibulum metus. Maecenas erat elit, ultrices eu gravida et, sollicitudin ac est. Sed in tellus ut tortor euismod aliquet non egestas metus. Sed pellentesque arcu ut lectus feugiat molestie. Fusce et justo non dui fermentum fermentum. Nunc nec ullamcorper urna. Morbi ultricies ornare arcu nec tincidunt. In sit amet risus lectus. Nam ac lacus urna. Phasellus semper eu enim quis rutrum. Suspendisse convallis orci vel nunc blandit, ut venenatis urna elementum. Curabitur a elit elementum sapien hendrerit laoreet eget in nunc. In vitae tempus neque. + Cras nec porta ipsum. Ut consectetur ut elit ac vestibulum. Suspendisse efficitur congue eros rutrum porta. Suspendisse sit amet tortor id massa varius ultrices. Integer ex nulla, tempus et malesuada sit amet, viverra vel tellus. Fusce dictum risus a sapien feugiat dignissim. Nullam iaculis suscipit ante varius semper. Curabitur ut ipsum mattis, ullamcorper leo a, vulputate arcu. + Fusce a gravida lectus, nec vestibulum enim. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam vitae lorem lorem. Maecenas interdum nunc nec ex vulputate, in efficitur purus tempus. Pellentesque lacinia consectetur arcu vel posuere. Suspendisse id mi congue, pellentesque elit eu, pharetra neque. Integer vel dignissim nisi. Nunc sagittis augue et rhoncus dignissim. Donec ut orci ac sapien egestas ultrices. + Aliquam accumsan auctor felis in rhoncus. Cras bibendum pharetra mollis. Donec rhoncus, orci quis varius rutrum, turpis arcu varius augue, pharetra cursus velit sem in nisl. Donec nec nisi quis tortor ultricies mollis eget in urna. Aliquam pellentesque felis ipsum, luctus luctus libero congue eget. In at orci ut magna luctus tristique et sit amet sem. Nam non posuere nisi. Ut ante tellus, accumsan eu ullamcorper egestas, pulvinar laoreet sapien. Nullam pulvinar at tellus et ullamcorper. Proin malesuada purus augue, id aliquet augue fringilla ut. Ut a rutrum libero. Vivamus semper faucibus purus, eu hendrerit velit vehicula a. + Aenean eu commodo lorem. Integer mollis vitae ipsum quis pretium. Nullam nec nisl quis erat dictum tincidunt venenatis sed est. Donec semper viverra tellus, pharetra faucibus arcu pellentesque a. Nullam faucibus efficitur pretium. Nullam risus diam, dictum vel ultricies a, interdum vel lacus. Nullam lacinia quam ac lectus imperdiet, non tincidunt ex pretium. Nullam pretium vehicula faucibus. Aliquam erat volutpat. Pellentesque tortor nisl, egestas a ante et, aliquam suscipit eros. Vestibulum porttitor sem lacus, ac vulputate dui venenatis et. Cras ultrices aliquam est, ac ultrices lorem semper et. Vivamus molestie orci quis justo condimentum tincidunt. Integer ornare pharetra ante tempus elementum. Ut vulputate eleifend dolor, ac sodales velit auctor sed. + + <ul><li id="foo">hello</li></ul> + <script> + document.querySelector("ul").addEventListener("click", function() { + this.insertBefore(this.firstChild.cloneNode(true), this.firstChild); + }); + + document.querySelector("#clickme").addEventListener("click", function() { + window.location.href = "#foo" + }); + + var result = document.querySelector("#result"); + document.querySelector("#findme").addEventListener("click", function() { + var target = document.querySelector(":target"); + if (target) { + result.className = "pass"; + result.textContent = ":target exists in document"; + } else { + result.className = "fail"; + result.textContent = ":target does not exist in document"; + } + }); + + </script> + </body> +</html> diff --git a/tests/wpt/metadata/dom/nodes/Element-matches.html.ini b/tests/wpt/metadata/dom/nodes/Element-matches.html.ini index d4fb35117e3..1d50c07cbe1 100644 --- a/tests/wpt/metadata/dom/nodes/Element-matches.html.ini +++ b/tests/wpt/metadata/dom/nodes/Element-matches.html.ini @@ -1,7 +1,5 @@ [Element-matches.html] type: testharness - [In-document Element.matches: :target pseudo-class selector, matching the element referenced by the URL fragment identifier (with no refNodes): :target] - expected: FAIL [In-document Element.matches: :lang pseudo-class selector, matching inherited language (with no refNodes): #pseudo-lang-div1:lang(en)] expected: FAIL diff --git a/tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All-xht.xht.ini b/tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All-xht.xht.ini index ab64f999369..bf9ca1b19c3 100644 --- a/tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All-xht.xht.ini +++ b/tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All-xht.xht.ini @@ -6,12 +6,6 @@ [Document.querySelector: Attribute whitespace-separated list selector, not matching class attribute with empty value: #attr-whitespace [class~=""\]] expected: FAIL - [Document.querySelectorAll: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - - [Document.querySelector: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - [Document.querySelectorAll: :lang pseudo-class selector, matching inherited language: #pseudo-lang-div1:lang(en)] expected: FAIL @@ -66,12 +60,6 @@ [Detached Element.querySelector: Attribute whitespace-separated list selector, not matching class attribute with empty value: #attr-whitespace [class~=""\]] expected: FAIL - [Detached Element.querySelectorAll: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - - [Detached Element.querySelector: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - [Detached Element.querySelectorAll: :lang pseudo-class selector, not matching element with no inherited language: #pseudo-lang-div1:lang(en)] expected: FAIL @@ -126,12 +114,6 @@ [Fragment.querySelector: Attribute whitespace-separated list selector, not matching class attribute with empty value: #attr-whitespace [class~=""\]] expected: FAIL - [Fragment.querySelectorAll: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - - [Fragment.querySelector: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - [Fragment.querySelectorAll: :lang pseudo-class selector, not matching element with no inherited language: #pseudo-lang-div1:lang(en)] expected: FAIL @@ -186,12 +168,6 @@ [In-document Element.querySelector: Attribute whitespace-separated list selector, not matching class attribute with empty value: #attr-whitespace [class~=""\]] expected: FAIL - [In-document Element.querySelectorAll: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - - [In-document Element.querySelector: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - [In-document Element.querySelectorAll: :lang pseudo-class selector, matching inherited language: #pseudo-lang-div1:lang(en)] expected: FAIL diff --git a/tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All.html.ini b/tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All.html.ini index a6e80b6a0f0..fee1e0a7f3c 100644 --- a/tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All.html.ini +++ b/tests/wpt/metadata/dom/nodes/ParentNode-querySelector-All.html.ini @@ -6,12 +6,6 @@ [Document.querySelector: Attribute whitespace-separated list selector, not matching class attribute with empty value: #attr-whitespace [class~=""\]] expected: FAIL - [Document.querySelectorAll: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - - [Document.querySelector: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - [Document.querySelectorAll: :lang pseudo-class selector, matching inherited language: #pseudo-lang-div1:lang(en)] expected: FAIL @@ -66,12 +60,6 @@ [Detached Element.querySelector: Attribute whitespace-separated list selector, not matching class attribute with empty value: #attr-whitespace [class~=""\]] expected: FAIL - [Detached Element.querySelectorAll: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - - [Detached Element.querySelector: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - [Detached Element.querySelectorAll: :lang pseudo-class selector, not matching element with no inherited language: #pseudo-lang-div1:lang(en)] expected: FAIL @@ -126,12 +114,6 @@ [Fragment.querySelector: Attribute whitespace-separated list selector, not matching class attribute with empty value: #attr-whitespace [class~=""\]] expected: FAIL - [Fragment.querySelectorAll: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - - [Fragment.querySelector: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - [Fragment.querySelectorAll: :lang pseudo-class selector, not matching element with no inherited language: #pseudo-lang-div1:lang(en)] expected: FAIL @@ -186,12 +168,6 @@ [In-document Element.querySelector: Attribute whitespace-separated list selector, not matching class attribute with empty value: #attr-whitespace [class~=""\]] expected: FAIL - [In-document Element.querySelectorAll: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - - [In-document Element.querySelector: :target pseudo-class selector, matching the element referenced by the URL fragment identifier: :target] - expected: FAIL - [In-document Element.querySelectorAll: :lang pseudo-class selector, matching inherited language: #pseudo-lang-div1:lang(en)] expected: FAIL |