Skip to content

Commit 17d51d3

Browse files
committed
Implement inner slot for cryptographic nonce
Also update the `html/dom/reflection-metadata.html` test to handle the case where `nonce` does not reflect back to the attribute after an IDL change. Part of servo#4577 Fixes web-platform-tests/wpt#43286 Signed-off-by: Tim van der Lippe <[email protected]>
1 parent 991be35 commit 17d51d3

14 files changed

+132
-98
lines changed

components/script/dom/document.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4313,7 +4313,7 @@ impl Document {
43134313
},
43144314
Some(csp_list) => {
43154315
let element = csp::Element {
4316-
nonce: el.nonce_attribute_if_nonceable().map(Cow::Owned),
4316+
nonce: el.nonce_value_if_nonceable().map(Cow::Owned),
43174317
};
43184318
csp_list.should_elements_inline_type_behavior_be_blocked(&element, type_, source)
43194319
},

components/script/dom/element.rs

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ use crate::dom::intersectionobserver::{IntersectionObserver, IntersectionObserve
146146
use crate::dom::mutationobserver::{Mutation, MutationObserver};
147147
use crate::dom::namednodemap::NamedNodeMap;
148148
use crate::dom::node::{
149-
BindContext, ChildrenMutation, LayoutNodeHelpers, Node, NodeDamage, NodeFlags, NodeTraits,
150-
ShadowIncluding, UnbindContext,
149+
BindContext, ChildrenMutation, CloneChildrenFlag, LayoutNodeHelpers, Node, NodeDamage,
150+
NodeFlags, NodeTraits, ShadowIncluding, UnbindContext,
151151
};
152152
use crate::dom::nodelist::NodeList;
153153
use crate::dom::promise::Promise;
@@ -2179,10 +2179,53 @@ impl Element {
21792179
};
21802180
}
21812181

2182+
/// <https://html.spec.whatwg.org/multipage/#nonce-attributes>
2183+
pub(crate) fn update_nonce_internal_slot(&self, nonce: String) {
2184+
self.ensure_rare_data().cryptographic_nonce = nonce;
2185+
}
2186+
2187+
/// <https://html.spec.whatwg.org/multipage/#nonce-attributes>
2188+
pub(crate) fn nonce_value(&self) -> String {
2189+
match self.rare_data().as_ref() {
2190+
None => String::new(),
2191+
Some(rare_data) => rare_data.cryptographic_nonce.clone(),
2192+
}
2193+
}
2194+
2195+
/// <https://html.spec.whatwg.org/multipage/#nonce-attributes>
2196+
pub(crate) fn update_nonce_post_connection(&self) {
2197+
// Whenever an element including HTMLOrSVGElement becomes browsing-context connected,
2198+
// the user agent must execute the following steps on the element:
2199+
if !self.upcast::<Node>().is_connected_with_browsing_context() {
2200+
return;
2201+
}
2202+
let global = self.owner_global();
2203+
// Step 1: Let CSP list be element's shadow-including root's policy container's CSP list.
2204+
let csp_list = match global.get_csp_list() {
2205+
None => return,
2206+
Some(csp_list) => csp_list,
2207+
};
2208+
// Step 2: If CSP list contains a header-delivered Content Security Policy,
2209+
// and element has a nonce content attribute whose value is not the empty string, then:
2210+
if !csp_list.contains_a_header_delivered_content_security_policy() ||
2211+
self.get_string_attribute(&local_name!("nonce")).is_empty()
2212+
{
2213+
return;
2214+
}
2215+
// Step 2.1: Let nonce be element's [[CryptographicNonce]].
2216+
let nonce = self.nonce_value();
2217+
// Step 2.2: Set an attribute value for element using "nonce" and the empty string.
2218+
self.set_string_attribute(&local_name!("nonce"), "".into(), CanGc::note());
2219+
// Step 2.3: Set element's [[CryptographicNonce]] to nonce.
2220+
self.update_nonce_internal_slot(nonce);
2221+
}
2222+
21822223
/// <https://www.w3.org/TR/CSP/#is-element-nonceable>
2183-
pub(crate) fn nonce_attribute_if_nonceable(&self) -> Option<String> {
2224+
pub(crate) fn nonce_value_if_nonceable(&self) -> Option<String> {
21842225
// Step 1: If element does not have an attribute named "nonce", return "Not Nonceable".
2185-
let nonce_attribute = self.get_attribute(&ns!(), &local_name!("nonce"))?;
2226+
if !self.has_attribute(&local_name!("nonce")) {
2227+
return None;
2228+
}
21862229
// Step 2: If element is a script element, then for each attribute of element’s attribute list:
21872230
if self.downcast::<HTMLScriptElement>().is_some() {
21882231
for attr in self.attrs().iter() {
@@ -2204,7 +2247,7 @@ impl Element {
22042247
// TODO(https://github.com/servo/servo/issues/4577 and https://github.com/whatwg/html/issues/3257):
22052248
// Figure out how to retrieve this information from the parser
22062249
// Step 4: Return "Nonceable".
2207-
Some(nonce_attribute.value().to_string().trim().to_owned())
2250+
Some(self.nonce_value().trim().to_owned())
22082251
}
22092252

22102253
// https://dom.spec.whatwg.org/#insert-adjacent
@@ -4188,6 +4231,31 @@ impl VirtualMethods for Element {
41884231
self.tag_name.clear();
41894232
}
41904233
}
4234+
4235+
fn post_connection_steps(&self) {
4236+
if let Some(s) = self.super_type() {
4237+
s.post_connection_steps();
4238+
}
4239+
4240+
self.update_nonce_post_connection();
4241+
}
4242+
4243+
/// <https://html.spec.whatwg.org/multipage/#nonce-attributes%3Aconcept-node-clone-ext>
4244+
fn cloning_steps(
4245+
&self,
4246+
copy: &Node,
4247+
maybe_doc: Option<&Document>,
4248+
clone_children: CloneChildrenFlag,
4249+
can_gc: CanGc,
4250+
) {
4251+
if let Some(s) = self.super_type() {
4252+
s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
4253+
}
4254+
let elem = copy.downcast::<Element>().unwrap();
4255+
if let Some(rare_data) = self.rare_data().as_ref() {
4256+
elem.update_nonce_internal_slot(rare_data.cryptographic_nonce.clone());
4257+
}
4258+
}
41914259
}
41924260

41934261
#[derive(Clone, PartialEq)]

components/script/dom/htmlelement.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -645,13 +645,16 @@ impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
645645
Ok(internals)
646646
}
647647

648-
// FIXME: The nonce should be stored in an internal slot instead of an
649-
// attribute (https://html.spec.whatwg.org/multipage/#cryptographicnonce)
650648
// https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce
651-
make_getter!(Nonce, "nonce");
649+
fn Nonce(&self) -> DOMString {
650+
self.as_element().nonce_value().into()
651+
}
652652

653653
// https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce
654-
make_setter!(SetNonce, "nonce");
654+
fn SetNonce(&self, value: DOMString) {
655+
self.as_element()
656+
.update_nonce_internal_slot(value.to_string())
657+
}
655658

656659
// https://html.spec.whatwg.org/multipage/#dom-fe-autofocus
657660
fn Autofocus(&self) -> bool {
@@ -1138,6 +1141,15 @@ impl VirtualMethods for HTMLElement {
11381141
},
11391142
}
11401143
},
1144+
(&local_name!("nonce"), mutation) => match mutation {
1145+
AttributeMutation::Set(_) => {
1146+
let nonce = &**attr.value();
1147+
element.update_nonce_internal_slot(nonce.to_owned());
1148+
},
1149+
AttributeMutation::Removed => {
1150+
element.update_nonce_internal_slot("".to_owned());
1151+
},
1152+
},
11411153
_ => {},
11421154
}
11431155
}

components/script/dom/htmllinkelement.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ use crate::dom::attr::Attr;
3131
use crate::dom::bindings::cell::DomRefCell;
3232
use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenList_Binding::DOMTokenListMethods;
3333
use crate::dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods;
34-
use crate::dom::bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElement_Binding::HTMLElementMethods;
3534
use crate::dom::bindings::inheritance::Castable;
3635
use crate::dom::bindings::refcounted::Trusted;
3736
use crate::dom::bindings::reflector::DomGlobal;
@@ -344,7 +343,7 @@ impl HTMLLinkElement {
344343
destination: Some(destination),
345344
integrity: String::new(),
346345
link_type: String::new(),
347-
cryptographic_nonce_metadata: self.upcast::<HTMLElement>().Nonce().into(),
346+
cryptographic_nonce_metadata: self.upcast::<Element>().nonce_value(),
348347
cross_origin: cors_setting_for_element(element),
349348
referrer_policy: referrer_policy_for_element(element),
350349
policy_container: document.policy_container().to_owned(),

components/script/dom/htmlscriptelement.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ impl HTMLScriptElement {
781781
};
782782

783783
// Step 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
784-
let cryptographic_nonce = self.upcast::<HTMLElement>().Nonce().into();
784+
let cryptographic_nonce = self.upcast::<Element>().nonce_value();
785785

786786
// Step 25. If el has an integrity attribute, then let integrity metadata be that attribute's value.
787787
// Otherwise, let integrity metadata be the empty string.

components/script/dom/raredata.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,5 @@ pub(crate) struct ElementRareData {
7575
/// > Element objects have an internal [[RegisteredIntersectionObservers]] slot,
7676
/// > which is initialized to an empty list. This list holds IntersectionObserverRegistration records, which have:
7777
pub(crate) registered_intersection_observers: Vec<IntersectionObserverRegistration>,
78+
pub(crate) cryptographic_nonce: String,
7879
}

components/script/dom/svgelement.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ use js::rust::HandleObject;
88
use script_bindings::str::DOMString;
99
use stylo_dom::ElementState;
1010

11+
use crate::dom::attr::Attr;
1112
use crate::dom::bindings::codegen::Bindings::SVGElementBinding::SVGElementMethods;
1213
use crate::dom::bindings::inheritance::Castable;
1314
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
1415
use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
1516
use crate::dom::document::Document;
16-
use crate::dom::element::Element;
17+
use crate::dom::element::{AttributeMutation, Element};
1718
use crate::dom::node::{Node, NodeTraits};
1819
use crate::dom::virtualmethods::VirtualMethods;
1920
use crate::script_runtime::CanGc;
@@ -59,11 +60,33 @@ impl SVGElement {
5960
can_gc,
6061
)
6162
}
63+
64+
fn as_element(&self) -> &Element {
65+
self.upcast::<Element>()
66+
}
6267
}
6368

6469
impl VirtualMethods for SVGElement {
6570
fn super_type(&self) -> Option<&dyn VirtualMethods> {
66-
Some(self.upcast::<Element>() as &dyn VirtualMethods)
71+
Some(self.as_element() as &dyn VirtualMethods)
72+
}
73+
74+
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
75+
self.super_type()
76+
.unwrap()
77+
.attribute_mutated(attr, mutation, can_gc);
78+
let element = self.as_element();
79+
if let (&local_name!("nonce"), mutation) = (attr.local_name(), mutation) {
80+
match mutation {
81+
AttributeMutation::Set(_) => {
82+
let nonce = &**attr.value();
83+
element.update_nonce_internal_slot(nonce.to_owned());
84+
},
85+
AttributeMutation::Removed => {
86+
element.update_nonce_internal_slot(String::new());
87+
},
88+
}
89+
}
6790
}
6891
}
6992

@@ -85,13 +108,16 @@ impl SVGElementMethods<crate::DomTypeHolder> for SVGElement {
85108
// <https://html.spec.whatwg.org/multipage/#globaleventhandlers>
86109
global_event_handlers!();
87110

88-
// FIXME: The nonce should be stored in an internal slot instead of an
89-
// attribute (https://html.spec.whatwg.org/multipage/#cryptographicnonce)
90111
// https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce
91-
make_getter!(Nonce, "nonce");
112+
fn Nonce(&self) -> DOMString {
113+
self.as_element().nonce_value().into()
114+
}
92115

93116
// https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce
94-
make_setter!(SetNonce, "nonce");
117+
fn SetNonce(&self, value: DOMString) {
118+
self.as_element()
119+
.update_nonce_internal_slot(value.to_string())
120+
}
95121

96122
// https://html.spec.whatwg.org/multipage/#dom-fe-autofocus
97123
fn Autofocus(&self) -> bool {

tests/wpt/meta/MANIFEST.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474524,7 +474524,7 @@
474524474524
[]
474525474525
],
474526474526
"reflection.js": [
474527-
"b2c3b30aae36b390a60c05b39901826ba71e0b1a",
474527+
"eeecd450fca8139e924affb298e7feb1a1fb46fb",
474528474528
[]
474529474529
],
474530474530
"render-blocking": {

tests/wpt/meta/content-security-policy/nonce-hiding/nonces.html.ini

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
[script-nonces-hidden-meta.sub.html]
2-
[Writing 'nonce' IDL attribute.]
3-
expected: FAIL
4-
52
[createElement.nonce.]
63
expected: FAIL
Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,3 @@
11
[script-nonces-hidden.html]
2-
[Reading 'nonce' content attribute and IDL attribute.]
3-
expected: FAIL
4-
5-
[Cloned node retains nonce.]
6-
expected: FAIL
7-
8-
[Cloned node retains nonce when inserted.]
9-
expected: FAIL
10-
11-
[Writing 'nonce' IDL attribute.]
12-
expected: FAIL
13-
14-
[Document-written script's nonce value.]
15-
expected: FAIL
16-
172
[createElement.nonce.]
183
expected: FAIL
19-
20-
[setAttribute('nonce') overwrites '.nonce' upon insertion.]
21-
expected: FAIL
22-
23-
[createElement.setAttribute.]
24-
expected: FAIL
25-
26-
[Custom elements expose the correct events.]
27-
expected: FAIL
28-
29-
[Nonces don't leak via CSS side-channels.]
30-
expected: FAIL

tests/wpt/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html.ini

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,3 @@
22
expected: TIMEOUT
33
[Document-written script executes.]
44
expected: NOTRUN
5-
6-
[createElement.nonce.]
7-
expected: FAIL
8-
9-
[Writing 'nonce' IDL attribute.]
10-
expected: FAIL
Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,4 @@
11
[svgscript-nonces-hidden.html]
22
expected: TIMEOUT
3-
[Reading 'nonce' content attribute and IDL attribute.]
4-
expected: FAIL
5-
6-
[Cloned node retains nonce.]
7-
expected: FAIL
8-
9-
[Cloned node retains nonce when inserted.]
10-
expected: FAIL
11-
123
[Document-written script executes.]
134
expected: NOTRUN
14-
15-
[createElement.nonce.]
16-
expected: FAIL
17-
18-
[createElement.setAttribute.]
19-
expected: FAIL
20-
21-
[Writing 'nonce' IDL attribute.]
22-
expected: FAIL

tests/wpt/tests/html/dom/reflection.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ ReflectionTests.reflects = function(data, idlName, idlObj, domName, domObj) {
967967
"previous value", "getAttribute()");
968968
ReflectionHarness.assertEquals(idlObj[idlName], previousIdl, "IDL get");
969969
} else {
970+
var previousValue = domObj.getAttribute(domName);
970971
idlObj[idlName] = idlTests[i];
971972
if (data.type == "boolean") {
972973
// Special case yay
@@ -976,6 +977,11 @@ ReflectionTests.reflects = function(data, idlName, idlObj, domName, domObj) {
976977
var expected = idlDomExpected[i] + "";
977978
if (data.isNullable && idlDomExpected[i] === null) {
978979
expected = null;
980+
} else if (idlName == "nonce") {
981+
// nonce doesn't reflect the value, as per /content-security-policy/nonce-hiding/
982+
// tests that confirm that retrieving the nonce value post IDL change does not
983+
// reflect back to the attribute (for security reasons)
984+
expected = previousValue;
979985
}
980986
ReflectionHarness.assertEquals(domObj.getAttribute(domName), expected,
981987
"getAttribute()");

0 commit comments

Comments
 (0)