@@ -2140,31 +2140,69 @@ impl Element {
2140
2140
self . push_attribute ( & attr, can_gc) ;
2141
2141
}
2142
2142
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.
2144
2154
let name = attr. local_name ( ) . clone ( ) ;
2145
2155
let namespace = attr. namespace ( ) . clone ( ) ;
2146
2156
let mutation = LazyCell :: new ( || Mutation :: Attribute {
2147
2157
name : name. clone ( ) ,
2148
2158
namespace : namespace. clone ( ) ,
2149
- old_value : None ,
2159
+ old_value : old_value_string . clone ( ) ,
2150
2160
} ) ;
2151
-
2152
2161
MutationObserver :: queue_a_mutation_record ( & self . node , mutation) ;
2153
2162
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 ».
2154
2168
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
+ ) ;
2157
2175
ScriptThread :: enqueue_callback_reaction ( self , reaction, None ) ;
2158
2176
}
2159
2177
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.
2161
2179
self . will_mutate_attr ( attr) ;
2162
- self . attrs . borrow_mut ( ) . push ( Dom :: from_ref ( attr) ) ;
2163
2180
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) ;
2165
2187
}
2166
2188
}
2167
2189
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
+
2168
2206
pub ( crate ) fn get_attribute (
2169
2207
& self ,
2170
2208
namespace : & Namespace ,
@@ -2329,41 +2367,22 @@ impl Element {
2329
2367
self . remove_first_matching_attribute ( |attr| attr. name ( ) == name, can_gc)
2330
2368
}
2331
2369
2370
+ /// <https://dom.spec.whatwg.org/#concept-element-attributes-remove>
2332
2371
fn remove_first_matching_attribute < F > ( & self , find : F , can_gc : CanGc ) -> Option < DomRoot < Attr > >
2333
2372
where
2334
2373
F : Fn ( & Attr ) -> bool ,
2335
2374
{
2336
2375
let idx = self . attrs . borrow ( ) . iter ( ) . position ( |attr| find ( attr) ) ;
2337
2376
idx. map ( |idx| {
2338
2377
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
- }
2357
2378
2379
+ // Step 2. Remove attribute from element’s attribute list.
2358
2380
self . attrs . borrow_mut ( ) . remove ( idx) ;
2381
+ // Step 3. Set attribute’s element to null.
2359
2382
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
+
2367
2386
attr
2368
2387
} )
2369
2388
}
@@ -2657,6 +2676,93 @@ impl Element {
2657
2676
} ;
2658
2677
}
2659
2678
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
+
2660
2766
/// <https://html.spec.whatwg.org/multipage/#nonce-attributes>
2661
2767
pub ( crate ) fn update_nonce_internal_slot ( & self , nonce : String ) {
2662
2768
self . ensure_rare_data ( ) . cryptographic_nonce = nonce;
@@ -3241,74 +3347,12 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
3241
3347
3242
3348
// https://dom.spec.whatwg.org/#dom-element-setattributenode
3243
3349
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)
3307
3351
}
3308
3352
3309
3353
// https://dom.spec.whatwg.org/#dom-element-setattributenodens
3310
3354
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)
3312
3356
}
3313
3357
3314
3358
// https://dom.spec.whatwg.org/#dom-element-removeattribute
@@ -4563,11 +4607,14 @@ impl VirtualMethods for Element {
4563
4607
} ,
4564
4608
& local_name ! ( "style" ) => self . update_style_attribute ( attr, mutation) ,
4565
4609
& local_name ! ( "id" ) => {
4610
+ // https://dom.spec.whatwg.org/#ref-for-concept-element-attributes-change-ext%E2%91%A2
4566
4611
* self . id_attribute . borrow_mut ( ) = mutation. new_value ( attr) . and_then ( |value| {
4567
4612
let value = value. as_atom ( ) ;
4568
4613
if value != & atom ! ( "" ) {
4614
+ // Step 2. Otherwise, if localName is id, namespace is null, then set element’s ID to value.
4569
4615
Some ( value. clone ( ) )
4570
4616
} else {
4617
+ // Step 1. If localName is id, namespace is null, and value is null or the empty string, then unset element’s ID.
4571
4618
None
4572
4619
}
4573
4620
} ) ;
0 commit comments