diff --git a/components/script/dom/htmlanchorelement.rs b/components/script/dom/htmlanchorelement.rs
index d571a2f997f..c0f648603af 100644
--- a/components/script/dom/htmlanchorelement.rs
+++ b/components/script/dom/htmlanchorelement.rs
@@ -2,48 +2,46 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+use std::cell::Cell;
use std::default::Default;
use dom_struct::dom_struct;
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix};
use js::rust::HandleObject;
-use net_traits::request::Referrer;
use num_traits::ToPrimitive;
-use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin};
use servo_atoms::Atom;
use servo_url::ServoUrl;
use style::attr::AttrValue;
use crate::dom::activation::Activatable;
+use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
-use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use crate::dom::bindings::codegen::Bindings::HTMLAnchorElementBinding::HTMLAnchorElementMethods;
use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
-use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::Document;
use crate::dom::domtokenlist::DOMTokenList;
-use crate::dom::element::{referrer_policy_for_element, Element};
+use crate::dom::element::{AttributeMutation, Element};
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
-use crate::dom::globalscope::GlobalScope;
-use crate::dom::htmlareaelement::HTMLAreaElement;
use crate::dom::htmlelement::HTMLElement;
-use crate::dom::htmlformelement::HTMLFormElement;
use crate::dom::htmlimageelement::HTMLImageElement;
use crate::dom::mouseevent::MouseEvent;
-use crate::dom::node::{document_from_node, Node};
+use crate::dom::node::{document_from_node, BindContext, Node};
use crate::dom::urlhelper::UrlHelper;
use crate::dom::virtualmethods::VirtualMethods;
-use crate::task_source::TaskSource;
+use crate::links::{follow_hyperlink, LinkRelations};
#[dom_struct]
pub struct HTMLAnchorElement {
htmlelement: HTMLElement,
rel_list: MutNullableDom,
+
+ #[no_trace]
+ relations: Cell,
#[no_trace]
url: DomRefCell>,
}
@@ -57,6 +55,7 @@ impl HTMLAnchorElement {
HTMLAnchorElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
rel_list: Default::default(),
+ relations: Cell::new(LinkRelations::empty()),
url: DomRefCell::new(None),
}
}
@@ -121,6 +120,27 @@ impl VirtualMethods for HTMLAnchorElement {
.parse_plain_attribute(name, value),
}
}
+
+ fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
+ self.super_type().unwrap().attribute_mutated(attr, mutation);
+
+ match *attr.local_name() {
+ local_name!("rel") | local_name!("rev") => {
+ self.relations
+ .set(LinkRelations::for_element(self.upcast()));
+ },
+ _ => {},
+ }
+ }
+
+ fn bind_to_tree(&self, context: &BindContext) {
+ if let Some(s) = self.super_type() {
+ s.bind_to_tree(context);
+ }
+
+ self.relations
+ .set(LinkRelations::for_element(self.upcast()));
+ }
}
impl HTMLAnchorElementMethods for HTMLAnchorElement {
@@ -580,153 +600,6 @@ impl Activatable for HTMLAnchorElement {
// Step 2.
//TODO: Download the link is `download` attribute is set.
- follow_hyperlink(element, ismap_suffix);
+ follow_hyperlink(element, self.relations.get(), ismap_suffix);
}
}
-
-///
-pub fn get_element_target(subject: &Element) -> Option {
- if !(subject.is::() ||
- subject.is::() ||
- subject.is::())
- {
- return None;
- }
- if subject.has_attribute(&local_name!("target")) {
- return Some(subject.get_string_attribute(&local_name!("target")));
- }
-
- let doc = document_from_node(subject).base_element();
- match doc {
- Some(doc) => {
- let element = doc.upcast::();
- if element.has_attribute(&local_name!("target")) {
- Some(element.get_string_attribute(&local_name!("target")))
- } else {
- None
- }
- },
- None => None,
- }
-}
-
-///
-pub fn get_element_noopener(subject: &Element, target_attribute_value: Option) -> bool {
- if !(subject.is::() ||
- subject.is::() ||
- subject.is::())
- {
- return false;
- }
- let target_is_blank = target_attribute_value
- .as_ref()
- .is_some_and(|target| target.to_lowercase() == "_blank");
- let link_types = match subject.get_attribute(&ns!(), &local_name!("rel")) {
- Some(rel) => rel.Value(),
- None => return target_is_blank,
- };
- link_types.contains("noreferrer") ||
- link_types.contains("noopener") ||
- (!link_types.contains("opener") && target_is_blank)
-}
-
-///
-pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option) {
- // Step 1.
- if subject.cannot_navigate() {
- return;
- }
- // Step 2, done in Step 7.
-
- let document = document_from_node(subject);
- let window = document.window();
-
- // Step 3: source browsing context.
- let source = document.browsing_context().unwrap();
-
- // Step 4-5: target attribute.
- let target_attribute_value =
- if subject.is::() || subject.is::() {
- get_element_target(subject)
- } else {
- None
- };
- // Step 6.
- let noopener = get_element_noopener(subject, target_attribute_value.clone());
-
- // Step 7.
- let (maybe_chosen, replace) = match target_attribute_value {
- Some(name) => {
- let (maybe_chosen, new) = source.choose_browsing_context(name, noopener);
- let replace = if new {
- HistoryEntryReplacement::Enabled
- } else {
- HistoryEntryReplacement::Disabled
- };
- (maybe_chosen, replace)
- },
- None => (
- Some(window.window_proxy()),
- HistoryEntryReplacement::Disabled,
- ),
- };
-
- // Step 8.
- let chosen = match maybe_chosen {
- Some(proxy) => proxy,
- None => return,
- };
-
- if let Some(target_document) = chosen.document() {
- let target_window = target_document.window();
- // Step 9, dis-owning target's opener, if necessary
- // will have been done as part of Step 7 above
- // in choose_browsing_context/create_auxiliary_browsing_context.
-
- // Step 10, 11. TODO: if parsing the URL failed, navigate to error page.
- let attribute = subject.get_attribute(&ns!(), &local_name!("href")).unwrap();
- let mut href = attribute.Value();
- // Step 11: append a hyperlink suffix.
- // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28925
- if let Some(suffix) = hyperlink_suffix {
- href.push_str(&suffix);
- }
- let url = match document.base_url().join(&href) {
- Ok(url) => url,
- Err(_) => return,
- };
-
- // Step 12.
- let referrer_policy = referrer_policy_for_element(subject);
-
- // Step 13
- let referrer = match subject.get_attribute(&ns!(), &local_name!("rel")) {
- Some(ref link_types) if link_types.Value().contains("noreferrer") => {
- Referrer::NoReferrer
- },
- _ => target_window.upcast::().get_referrer(),
- };
-
- // Step 14
- let pipeline_id = target_window.upcast::().pipeline_id();
- let secure = target_window.upcast::().is_secure_context();
- let load_data = LoadData::new(
- LoadOrigin::Script(document.origin().immutable().clone()),
- url,
- Some(pipeline_id),
- referrer,
- referrer_policy,
- Some(secure),
- );
- let target = Trusted::new(target_window);
- let task = task!(navigate_follow_hyperlink: move || {
- debug!("following hyperlink to {}", load_data.url);
- target.root().load_url(replace, false, load_data);
- });
- target_window
- .task_manager()
- .dom_manipulation_task_source()
- .queue(task, target_window.upcast())
- .unwrap();
- };
-}
diff --git a/components/script/dom/htmlareaelement.rs b/components/script/dom/htmlareaelement.rs
index 59a55b22f04..19224834240 100644
--- a/components/script/dom/htmlareaelement.rs
+++ b/components/script/dom/htmlareaelement.rs
@@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+use std::cell::Cell;
use std::default::Default;
use std::{f32, str};
@@ -14,19 +15,20 @@ use servo_atoms::Atom;
use style::attr::AttrValue;
use crate::dom::activation::Activatable;
+use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLAreaElementBinding::HTMLAreaElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::domtokenlist::DOMTokenList;
-use crate::dom::element::Element;
+use crate::dom::element::{AttributeMutation, Element};
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
-use crate::dom::htmlanchorelement::follow_hyperlink;
use crate::dom::htmlelement::HTMLElement;
-use crate::dom::node::Node;
+use crate::dom::node::{BindContext, Node};
use crate::dom::virtualmethods::VirtualMethods;
+use crate::links::{follow_hyperlink, LinkRelations};
#[derive(Debug, PartialEq)]
pub enum Area {
@@ -75,7 +77,7 @@ impl Area {
index += 1;
}
- //This vector will hold all parsed coordinates
+ // This vector will hold all parsed coordinates
let mut number_list = Vec::new();
let mut array = Vec::new();
@@ -237,6 +239,9 @@ impl Area {
pub struct HTMLAreaElement {
htmlelement: HTMLElement,
rel_list: MutNullableDom,
+
+ #[no_trace]
+ relations: Cell,
}
impl HTMLAreaElement {
@@ -248,6 +253,7 @@ impl HTMLAreaElement {
HTMLAreaElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
rel_list: Default::default(),
+ relations: Cell::new(LinkRelations::empty()),
}
}
@@ -300,6 +306,27 @@ impl VirtualMethods for HTMLAreaElement {
.parse_plain_attribute(name, value),
}
}
+
+ fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
+ self.super_type().unwrap().attribute_mutated(attr, mutation);
+
+ match *attr.local_name() {
+ local_name!("rel") | local_name!("rev") => {
+ self.relations
+ .set(LinkRelations::for_element(self.upcast()));
+ },
+ _ => {},
+ }
+ }
+
+ fn bind_to_tree(&self, context: &BindContext) {
+ if let Some(s) = self.super_type() {
+ s.bind_to_tree(context);
+ }
+
+ self.relations
+ .set(LinkRelations::for_element(self.upcast()));
+ }
}
impl HTMLAreaElementMethods for HTMLAreaElement {
@@ -345,6 +372,6 @@ impl Activatable for HTMLAreaElement {
}
fn activation_behavior(&self, _event: &Event, _target: &EventTarget) {
- follow_hyperlink(self.as_element(), None);
+ follow_hyperlink(self.as_element(), self.relations.get(), None);
}
}
diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs
index 875b66a22ba..1f32d793d2f 100644
--- a/components/script/dom/htmlformelement.rs
+++ b/components/script/dom/htmlformelement.rs
@@ -24,6 +24,7 @@ use style_dom::ElementState;
use super::bindings::trace::{HashMapTracedValues, NoTrace};
use crate::body::Extractable;
+use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::AttrBinding::Attr_Binding::AttrMethods;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
@@ -56,7 +57,6 @@ use crate::dom::file::File;
use crate::dom::formdata::FormData;
use crate::dom::formdataevent::FormDataEvent;
use crate::dom::globalscope::GlobalScope;
-use crate::dom::htmlanchorelement::{get_element_noopener, get_element_target};
use crate::dom::htmlbuttonelement::HTMLButtonElement;
use crate::dom::htmlcollection::CollectionFilter;
use crate::dom::htmldatalistelement::HTMLDataListElement;
@@ -72,7 +72,7 @@ use crate::dom::htmloutputelement::HTMLOutputElement;
use crate::dom::htmlselectelement::HTMLSelectElement;
use crate::dom::htmltextareaelement::HTMLTextAreaElement;
use crate::dom::node::{
- document_from_node, window_from_node, Node, NodeFlags, UnbindContext,
+ document_from_node, window_from_node, BindContext, Node, NodeFlags, UnbindContext,
VecPreOrderInsertionHelper,
};
use crate::dom::nodelist::{NodeList, RadioListMode};
@@ -80,6 +80,7 @@ use crate::dom::radionodelist::RadioNodeList;
use crate::dom::submitevent::SubmitEvent;
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window;
+use crate::links::{get_element_target, LinkRelations};
use crate::script_thread::ScriptThread;
use crate::task_source::TaskSource;
@@ -98,6 +99,9 @@ pub struct HTMLFormElement {
past_names_map: DomRefCell, NoTrace)>>,
firing_submission_events: Cell,
rel_list: MutNullableDom,
+
+ #[no_trace]
+ relations: Cell,
}
impl HTMLFormElement {
@@ -121,6 +125,7 @@ impl HTMLFormElement {
past_names_map: DomRefCell::new(HashMapTracedValues::new()),
firing_submission_events: Cell::new(false),
rel_list: Default::default(),
+ relations: Cell::new(LinkRelations::empty()),
}
}
@@ -798,8 +803,10 @@ impl HTMLFormElement {
};
// Step 18
- let noopener =
- get_element_noopener(self.upcast::(), target_attribute_value.clone());
+ let noopener = self
+ .relations
+ .get()
+ .get_element_noopener(target_attribute_value.as_ref());
// Step 19
let source = doc.browsing_context().unwrap();
@@ -1685,6 +1692,27 @@ impl VirtualMethods for HTMLFormElement {
.parse_plain_attribute(name, value),
}
}
+
+ fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
+ self.super_type().unwrap().attribute_mutated(attr, mutation);
+
+ match *attr.local_name() {
+ local_name!("rel") | local_name!("rev") => {
+ self.relations
+ .set(LinkRelations::for_element(self.upcast()));
+ },
+ _ => {},
+ }
+ }
+
+ fn bind_to_tree(&self, context: &BindContext) {
+ if let Some(s) = self.super_type() {
+ s.bind_to_tree(context);
+ }
+
+ self.relations
+ .set(LinkRelations::for_element(self.upcast()));
+ }
}
pub trait FormControlElementHelpers {
diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs
index df970d498c4..566d7896f02 100644
--- a/components/script/dom/htmllinkelement.rs
+++ b/components/script/dom/htmllinkelement.rs
@@ -55,7 +55,7 @@ use crate::dom::performanceresourcetiming::InitiatorType;
use crate::dom::stylesheet::StyleSheet as DOMStyleSheet;
use crate::dom::virtualmethods::VirtualMethods;
use crate::fetch::create_a_potential_cors_request;
-use crate::link_relations::LinkRelations;
+use crate::links::LinkRelations;
use crate::network_listener::{submit_timing, NetworkListener, PreInvoke, ResourceTimingListener};
use crate::stylesheet_loader::{StylesheetContextSource, StylesheetLoader, StylesheetOwner};
diff --git a/components/script/lib.rs b/components/script/lib.rs
index 1db1a94730b..ca70f062e64 100644
--- a/components/script/lib.rs
+++ b/components/script/lib.rs
@@ -94,7 +94,7 @@ mod webdriver_handlers;
#[warn(deprecated)]
mod window_named_properties;
-mod link_relations;
+mod links;
pub use init::init;
pub use script_runtime::JSEngineSetup;
diff --git a/components/script/link_relations.rs b/components/script/link_relations.rs
deleted file mode 100644
index d32ea9e4e92..00000000000
--- a/components/script/link_relations.rs
+++ /dev/null
@@ -1,131 +0,0 @@
-/* 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 https://mozilla.org/MPL/2.0/. */
-
-use html5ever::{local_name, namespace_url, ns};
-use malloc_size_of::malloc_size_of_is_0;
-use style::str::HTML_SPACE_CHARACTERS;
-
-use crate::dom::types::Element;
-
-bitflags::bitflags! {
- #[derive(Clone, Copy, Debug)]
- pub struct LinkRelations: u32 {
- const ALTERNATE = 1;
- const AUTHOR = 1 << 1;
- const BOOKMARK = 1 << 2;
- const CANONICAL = 1 << 3;
- const DNS_PREFETCH = 1 << 4;
- const EXPECT = 1 << 5;
- const EXTERNAL = 1 << 6;
- const HELP = 1 << 7;
- const ICON = 1 << 8;
- const LICENSE = 1 << 9;
- const NEXT = 1 << 10;
- const MANIFEST = 1 << 11;
- const MODULE_PRELOAD = 1 << 12;
- const NO_FOLLOW = 1 << 13;
- const NO_OPENER = 1 << 14;
- const NO_REFERRER = 1 << 15;
- const OPENER = 1 << 16;
- const PING_BACK = 1 << 17;
- const PRECONNECT = 1 << 18;
- const PREFETCH = 1 << 19;
- const PRELOAD = 1 << 20;
- const PREV = 1 << 21;
- const PRIVACY_POLICY = 1 << 22;
- const SEARCH = 1 << 23;
- const STYLESHEET = 1 << 24;
- const TAG = 1 << 25;
- const TermsOfService = 1 << 26;
- }
-}
-
-impl LinkRelations {
- pub fn for_element(element: &Element) -> Self {
- let rel = element.get_attribute(&ns!(), &local_name!("rel")).map(|e| {
- let value = e.value();
- (**value).to_owned()
- });
-
- // FIXME: for a, area and form elements we need to allow a different
- // set of attributes
- let mut relations = rel
- .map(|attribute| {
- attribute
- .split(HTML_SPACE_CHARACTERS)
- .map(Self::from_single_keyword_for_link_element)
- .collect()
- })
- .unwrap_or(Self::empty());
-
- // For historical reasons, "rev=made" is treated as if the "author" relation was specified
- let has_legacy_author_relation = element
- .get_attribute(&ns!(), &local_name!("rev"))
- .is_some_and(|rev| &**rev.value() == "made");
- if has_legacy_author_relation {
- relations |= Self::AUTHOR;
- }
-
- relations
- }
-
- /// Parse one of the relations allowed for the ` ` element
- ///
- /// If the keyword is invalid then `Self::empty` is returned.
- fn from_single_keyword_for_link_element(keyword: &str) -> Self {
- if keyword.eq_ignore_ascii_case("alternate") {
- Self::ALTERNATE
- } else if keyword.eq_ignore_ascii_case("canonical") {
- Self::CANONICAL
- } else if keyword.eq_ignore_ascii_case("author") {
- Self::AUTHOR
- } else if keyword.eq_ignore_ascii_case("dns-prefetch") {
- Self::DNS_PREFETCH
- } else if keyword.eq_ignore_ascii_case("expect") {
- Self::EXPECT
- } else if keyword.eq_ignore_ascii_case("help") {
- Self::HELP
- } else if keyword.eq_ignore_ascii_case("icon") ||
- keyword.eq_ignore_ascii_case("shortcut icon") ||
- keyword.eq_ignore_ascii_case("apple-touch-icon")
- {
- // TODO: "apple-touch-icon" is not in the spec. Where did it come from? Do we need it?
- // There is also "apple-touch-icon-precomposed" listed in
- // https://github.com/servo/servo/blob/e43e4778421be8ea30db9d5c553780c042161522/components/script/dom/htmllinkelement.rs#L452-L467
- Self::ICON
- } else if keyword.eq_ignore_ascii_case("manifest") {
- Self::MANIFEST
- } else if keyword.eq_ignore_ascii_case("modulepreload") {
- Self::MODULE_PRELOAD
- } else if keyword.eq_ignore_ascii_case("license") ||
- keyword.eq_ignore_ascii_case("copyright")
- {
- Self::LICENSE
- } else if keyword.eq_ignore_ascii_case("next") {
- Self::NEXT
- } else if keyword.eq_ignore_ascii_case("pingback") {
- Self::PING_BACK
- } else if keyword.eq_ignore_ascii_case("preconnect") {
- Self::PRECONNECT
- } else if keyword.eq_ignore_ascii_case("prefetch") {
- Self::PREFETCH
- } else if keyword.eq_ignore_ascii_case("preload") {
- Self::PRELOAD
- } else if keyword.eq_ignore_ascii_case("prev") || keyword.eq_ignore_ascii_case("previous") {
- Self::PREV
- } else if keyword.eq_ignore_ascii_case("privacy-policy") {
- Self::PRIVACY_POLICY
- } else if keyword.eq_ignore_ascii_case("search") {
- Self::SEARCH
- } else if keyword.eq_ignore_ascii_case("stylesheet") {
- Self::STYLESHEET
- } else if keyword.eq_ignore_ascii_case("terms-of-service") {
- Self::TermsOfService
- } else {
- Self::empty()
- }
- }
-}
-
-malloc_size_of_is_0!(LinkRelations);
diff --git a/components/script/links.rs b/components/script/links.rs
new file mode 100644
index 00000000000..863de553303
--- /dev/null
+++ b/components/script/links.rs
@@ -0,0 +1,443 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Defines shared hyperlink behaviour for ` `, ``, ` ` and `