@@ -2140,31 +2140,69 @@ impl Element {
21402140 self . push_attribute ( & attr, can_gc) ;
21412141 }
21422142
2143- pub ( crate ) fn push_attribute ( & self , attr : & Attr , can_gc : CanGc ) {
2143+ /// <https://dom.spec.whatwg.org/#handle-attribute-changes>
2144+ fn handle_attribute_changes (
2145+ & self ,
2146+ attr : & Attr ,
2147+ old_value : Option < & AttrValue > ,
2148+ new_value : Option < DOMString > ,
2149+ can_gc : CanGc ,
2150+ ) {
2151+ let old_value_string = old_value. map ( |old_value| DOMString :: from ( & * * old_value) ) ;
2152+ // Step 1. Queue a mutation record of "attributes" for element with attribute’s local name,
2153+ // attribute’s namespace, oldValue, « », « », null, and null.
21442154 let name = attr. local_name ( ) . clone ( ) ;
21452155 let namespace = attr. namespace ( ) . clone ( ) ;
21462156 let mutation = LazyCell :: new ( || Mutation :: Attribute {
21472157 name : name. clone ( ) ,
21482158 namespace : namespace. clone ( ) ,
2149- old_value : None ,
2159+ old_value : old_value_string . clone ( ) ,
21502160 } ) ;
2151-
21522161 MutationObserver :: queue_a_mutation_record ( & self . node , mutation) ;
21532162
2163+ // Avoid double borrow
2164+ let has_new_value = new_value. is_none ( ) ;
2165+
2166+ // Step 2. If element is custom, then enqueue a custom element callback reaction with element,
2167+ // callback name "attributeChangedCallback", and « attribute’s local name, oldValue, newValue, attribute’s namespace ».
21542168 if self . is_custom ( ) {
2155- let value = DOMString :: from ( & * * attr. value ( ) ) ;
2156- let reaction = CallbackReaction :: AttributeChanged ( name, None , Some ( value) , namespace) ;
2169+ let reaction = CallbackReaction :: AttributeChanged (
2170+ attr. local_name ( ) . clone ( ) ,
2171+ old_value_string,
2172+ new_value,
2173+ attr. namespace ( ) . clone ( ) ,
2174+ ) ;
21572175 ScriptThread :: enqueue_callback_reaction ( self , reaction, None ) ;
21582176 }
21592177
2160- assert ! ( attr . GetOwnerElement ( ) . as_deref ( ) == Some ( self ) ) ;
2178+ // Step 3. Run the attribute change steps with element, attribute’s local name, oldValue, newValue, and attribute’s namespace.
21612179 self . will_mutate_attr ( attr) ;
2162- self . attrs . borrow_mut ( ) . push ( Dom :: from_ref ( attr) ) ;
21632180 if is_relevant_attribute ( attr. namespace ( ) , attr. local_name ( ) ) {
2164- vtable_for ( self . upcast ( ) ) . attribute_mutated ( attr, AttributeMutation :: Set ( None ) , can_gc) ;
2181+ let attribute_mutation = if has_new_value {
2182+ AttributeMutation :: Removed
2183+ } else {
2184+ AttributeMutation :: Set ( old_value)
2185+ } ;
2186+ vtable_for ( self . upcast ( ) ) . attribute_mutated ( attr, attribute_mutation, can_gc) ;
21652187 }
21662188 }
21672189
2190+ /// <https://dom.spec.whatwg.org/#concept-element-attributes-append>
2191+ pub ( crate ) fn push_attribute ( & self , attr : & Attr , can_gc : CanGc ) {
2192+ // Step 2. Set attribute’s element to element.
2193+ //
2194+ // Handled by callers of this function and asserted here.
2195+ assert ! ( attr. GetOwnerElement ( ) . as_deref( ) == Some ( self ) ) ;
2196+ // Step 3. Set attribute’s node document to element’s node document.
2197+ //
2198+ // Handled by callers of this function and asserted here.
2199+ assert ! ( attr. upcast:: <Node >( ) . owner_doc( ) == self . node. owner_doc( ) ) ;
2200+ // Step 1. Append attribute to element’s attribute list.
2201+ self . attrs . borrow_mut ( ) . push ( Dom :: from_ref ( attr) ) ;
2202+ // Step 4. Handle attribute changes for attribute with element, null, and attribute’s value.
2203+ self . handle_attribute_changes ( attr, None , Some ( DOMString :: from ( & * * attr. value ( ) ) ) , can_gc) ;
2204+ }
2205+
21682206 pub ( crate ) fn get_attribute (
21692207 & self ,
21702208 namespace : & Namespace ,
@@ -2329,41 +2367,22 @@ impl Element {
23292367 self . remove_first_matching_attribute ( |attr| attr. name ( ) == name, can_gc)
23302368 }
23312369
2370+ /// <https://dom.spec.whatwg.org/#concept-element-attributes-remove>
23322371 fn remove_first_matching_attribute < F > ( & self , find : F , can_gc : CanGc ) -> Option < DomRoot < Attr > >
23332372 where
23342373 F : Fn ( & Attr ) -> bool ,
23352374 {
23362375 let idx = self . attrs . borrow ( ) . iter ( ) . position ( |attr| find ( attr) ) ;
23372376 idx. map ( |idx| {
23382377 let attr = DomRoot :: from_ref ( & * ( * self . attrs . borrow ( ) ) [ idx] ) ;
2339- self . will_mutate_attr ( & attr) ;
2340-
2341- let name = attr. local_name ( ) . clone ( ) ;
2342- let namespace = attr. namespace ( ) . clone ( ) ;
2343- let old_value = DOMString :: from ( & * * attr. value ( ) ) ;
2344- let mutation = LazyCell :: new ( || Mutation :: Attribute {
2345- name : name. clone ( ) ,
2346- namespace : namespace. clone ( ) ,
2347- old_value : Some ( old_value. clone ( ) ) ,
2348- } ) ;
2349-
2350- MutationObserver :: queue_a_mutation_record ( & self . node , mutation) ;
2351-
2352- if self . is_custom ( ) {
2353- let reaction =
2354- CallbackReaction :: AttributeChanged ( name, Some ( old_value) , None , namespace) ;
2355- ScriptThread :: enqueue_callback_reaction ( self , reaction, None ) ;
2356- }
23572378
2379+ // Step 2. Remove attribute from element’s attribute list.
23582380 self . attrs . borrow_mut ( ) . remove ( idx) ;
2381+ // Step 3. Set attribute’s element to null.
23592382 attr. set_owner ( None ) ;
2360- if is_relevant_attribute ( attr. namespace ( ) , attr. local_name ( ) ) {
2361- vtable_for ( self . upcast ( ) ) . attribute_mutated (
2362- & attr,
2363- AttributeMutation :: Removed ,
2364- can_gc,
2365- ) ;
2366- }
2383+ // Step 4. Handle attribute changes for attribute with element, attribute’s value, and null.
2384+ self . handle_attribute_changes ( & attr, Some ( & attr. value ( ) ) , None , can_gc) ;
2385+
23672386 attr
23682387 } )
23692388 }
@@ -2657,6 +2676,93 @@ impl Element {
26572676 } ;
26582677 }
26592678
2679+ /// <https://dom.spec.whatwg.org/#concept-element-attributes-set>
2680+ /// including steps of
2681+ /// <https://dom.spec.whatwg.org/#concept-element-attributes-replace>
2682+ fn set_attribute_node ( & self , attr : & Attr , can_gc : CanGc ) -> Fallible < Option < DomRoot < Attr > > > {
2683+ // Step 1. Let verifiedValue be the result of calling
2684+ // get Trusted Types-compliant attribute value with attr’s local name,
2685+ // attr’s namespace, element, and attr’s value. [TRUSTED-TYPES]
2686+ let verified_value = TrustedTypePolicyFactory :: get_trusted_types_compliant_attribute_value (
2687+ self . namespace ( ) ,
2688+ self . local_name ( ) ,
2689+ attr. local_name ( ) ,
2690+ Some ( attr. namespace ( ) ) ,
2691+ TrustedTypeOrString :: String ( attr. Value ( ) ) ,
2692+ & self . owner_global ( ) ,
2693+ can_gc,
2694+ ) ?;
2695+
2696+ // Step 2. If attr’s element is neither null nor element,
2697+ // throw an "InUseAttributeError" DOMException.
2698+ if let Some ( owner) = attr. GetOwnerElement ( ) {
2699+ if & * owner != self {
2700+ return Err ( Error :: InUseAttribute ) ;
2701+ }
2702+ }
2703+
2704+ let vtable = vtable_for ( self . upcast ( ) ) ;
2705+
2706+ // Step 5. Set attr’s value to verifiedValue.
2707+ //
2708+ // This ensures that the attribute is of the expected kind for this
2709+ // specific element. This is inefficient and should probably be done
2710+ // differently.
2711+ attr. swap_value (
2712+ & mut vtable. parse_plain_attribute ( attr. local_name ( ) , verified_value. clone ( ) ) ,
2713+ ) ;
2714+
2715+ // Step 3. Let oldAttr be the result of getting an attribute given attr’s namespace, attr’s local name, and element.
2716+ let position = self . attrs . borrow ( ) . iter ( ) . position ( |old_attr| {
2717+ attr. namespace ( ) == old_attr. namespace ( ) && attr. local_name ( ) == old_attr. local_name ( )
2718+ } ) ;
2719+
2720+ let old_attr = if let Some ( position) = position {
2721+ let old_attr = DomRoot :: from_ref ( & * self . attrs . borrow ( ) [ position] ) ;
2722+
2723+ // Step 4. If oldAttr is attr, return attr.
2724+ if & * old_attr == attr {
2725+ return Ok ( Some ( DomRoot :: from_ref ( attr) ) ) ;
2726+ }
2727+
2728+ // Step 6. If oldAttr is non-null, then replace oldAttr with attr.
2729+ //
2730+ // Start of steps for https://dom.spec.whatwg.org/#concept-element-attributes-replace
2731+
2732+ // Step 1. Let element be oldAttribute’s element.
2733+ //
2734+ // Skipped, as that points to self.
2735+
2736+ // Step 2. Replace oldAttribute by newAttribute in element’s attribute list.
2737+ self . attrs . borrow_mut ( ) [ position] = Dom :: from_ref ( attr) ;
2738+ // Step 3. Set newAttribute’s element to element.
2739+ attr. set_owner ( Some ( self ) ) ;
2740+ // Step 4. Set newAttribute’s node document to element’s node document.
2741+ attr. upcast :: < Node > ( ) . set_owner_doc ( & self . node . owner_doc ( ) ) ;
2742+ // Step 5. Set oldAttribute’s element to null.
2743+ old_attr. set_owner ( None ) ;
2744+ // Step 6. Handle attribute changes for oldAttribute with element, oldAttribute’s value, and newAttribute’s value.
2745+ self . handle_attribute_changes (
2746+ attr,
2747+ Some ( & old_attr. value ( ) ) ,
2748+ Some ( verified_value) ,
2749+ can_gc,
2750+ ) ;
2751+
2752+ Some ( old_attr)
2753+ } else {
2754+ // Step 7. Otherwise, append attr to element.
2755+ attr. set_owner ( Some ( self ) ) ;
2756+ attr. upcast :: < Node > ( ) . set_owner_doc ( & self . node . owner_doc ( ) ) ;
2757+ self . push_attribute ( attr, can_gc) ;
2758+
2759+ None
2760+ } ;
2761+
2762+ // Step 8. Return oldAttr.
2763+ Ok ( old_attr)
2764+ }
2765+
26602766 /// <https://html.spec.whatwg.org/multipage/#nonce-attributes>
26612767 pub ( crate ) fn update_nonce_internal_slot ( & self , nonce : String ) {
26622768 self . ensure_rare_data ( ) . cryptographic_nonce = nonce;
@@ -3241,74 +3347,12 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
32413347
32423348 // https://dom.spec.whatwg.org/#dom-element-setattributenode
32433349 fn SetAttributeNode ( & self , attr : & Attr , can_gc : CanGc ) -> Fallible < Option < DomRoot < Attr > > > {
3244- // Step 1.
3245- if let Some ( owner) = attr. GetOwnerElement ( ) {
3246- if & * owner != self {
3247- return Err ( Error :: InUseAttribute ) ;
3248- }
3249- }
3250-
3251- let vtable = vtable_for ( self . upcast ( ) ) ;
3252-
3253- // This ensures that the attribute is of the expected kind for this
3254- // specific element. This is inefficient and should probably be done
3255- // differently.
3256- attr. swap_value ( & mut vtable. parse_plain_attribute ( attr. local_name ( ) , attr. Value ( ) ) ) ;
3257-
3258- // Step 2.
3259- let position = self . attrs . borrow ( ) . iter ( ) . position ( |old_attr| {
3260- attr. namespace ( ) == old_attr. namespace ( ) && attr. local_name ( ) == old_attr. local_name ( )
3261- } ) ;
3262-
3263- if let Some ( position) = position {
3264- let old_attr = DomRoot :: from_ref ( & * self . attrs . borrow ( ) [ position] ) ;
3265-
3266- // Step 3.
3267- if & * old_attr == attr {
3268- return Ok ( Some ( DomRoot :: from_ref ( attr) ) ) ;
3269- }
3270-
3271- // Step 4.
3272- if self . is_custom ( ) {
3273- let old_name = old_attr. local_name ( ) . clone ( ) ;
3274- let old_value = DOMString :: from ( & * * old_attr. value ( ) ) ;
3275- let new_value = DOMString :: from ( & * * attr. value ( ) ) ;
3276- let namespace = old_attr. namespace ( ) . clone ( ) ;
3277- let reaction = CallbackReaction :: AttributeChanged (
3278- old_name,
3279- Some ( old_value) ,
3280- Some ( new_value) ,
3281- namespace,
3282- ) ;
3283- ScriptThread :: enqueue_callback_reaction ( self , reaction, None ) ;
3284- }
3285- self . will_mutate_attr ( attr) ;
3286- attr. set_owner ( Some ( self ) ) ;
3287- self . attrs . borrow_mut ( ) [ position] = Dom :: from_ref ( attr) ;
3288- old_attr. set_owner ( None ) ;
3289- if is_relevant_attribute ( attr. namespace ( ) , attr. local_name ( ) ) {
3290- vtable. attribute_mutated (
3291- attr,
3292- AttributeMutation :: Set ( Some ( & old_attr. value ( ) ) ) ,
3293- can_gc,
3294- ) ;
3295- }
3296-
3297- // Step 6.
3298- Ok ( Some ( old_attr) )
3299- } else {
3300- // Step 5.
3301- attr. set_owner ( Some ( self ) ) ;
3302- self . push_attribute ( attr, can_gc) ;
3303-
3304- // Step 6.
3305- Ok ( None )
3306- }
3350+ self . set_attribute_node ( attr, can_gc)
33073351 }
33083352
33093353 // https://dom.spec.whatwg.org/#dom-element-setattributenodens
33103354 fn SetAttributeNodeNS ( & self , attr : & Attr , can_gc : CanGc ) -> Fallible < Option < DomRoot < Attr > > > {
3311- self . SetAttributeNode ( attr, can_gc)
3355+ self . set_attribute_node ( attr, can_gc)
33123356 }
33133357
33143358 // https://dom.spec.whatwg.org/#dom-element-removeattribute
@@ -4563,11 +4607,14 @@ impl VirtualMethods for Element {
45634607 } ,
45644608 & local_name ! ( "style" ) => self . update_style_attribute ( attr, mutation) ,
45654609 & local_name ! ( "id" ) => {
4610+ // https://dom.spec.whatwg.org/#ref-for-concept-element-attributes-change-ext%E2%91%A2
45664611 * self . id_attribute . borrow_mut ( ) = mutation. new_value ( attr) . and_then ( |value| {
45674612 let value = value. as_atom ( ) ;
45684613 if value != & atom ! ( "" ) {
4614+ // Step 2. Otherwise, if localName is id, namespace is null, then set element’s ID to value.
45694615 Some ( value. clone ( ) )
45704616 } else {
4617+ // Step 1. If localName is id, namespace is null, and value is null or the empty string, then unset element’s ID.
45714618 None
45724619 }
45734620 } ) ;
0 commit comments