summaryrefslogtreecommitdiffstats
path: root/layout/style/test
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/test')
-rw-r--r--layout/style/test/Ahem.ttfbin0 -> 12480 bytes
-rw-r--r--layout/style/test/BitPattern.woffbin0 -> 6248 bytes
-rw-r--r--layout/style/test/ListCSSProperties.cpp178
-rw-r--r--layout/style/test/ParseCSS.cpp80
-rw-r--r--layout/style/test/additional_sheets_helper.html7
-rw-r--r--layout/style/test/animation_utils.js935
-rw-r--r--layout/style/test/browser.toml19
-rw-r--r--layout/style/test/browser_bug453896.js16
-rw-r--r--layout/style/test/browser_sourcemap.js41
-rw-r--r--layout/style/test/browser_sourcemap_comment.js47
-rw-r--r--layout/style/test/browser_sourceurl_comment.js43
-rw-r--r--layout/style/test/bug1382568-iframe.html8
-rw-r--r--layout/style/test/bug1729861.js81
-rw-r--r--layout/style/test/bug453896_iframe.html66
-rw-r--r--layout/style/test/bug517224.sjs27
-rw-r--r--layout/style/test/bug732209-css.sjs30
-rw-r--r--layout/style/test/ccd-quirks.html129
-rw-r--r--layout/style/test/ccd-standards.html128
-rw-r--r--layout/style/test/ccd.sjs71
-rw-r--r--layout/style/test/chrome/bug418986-2.js318
-rw-r--r--layout/style/test/chrome/bug535806-css.css1
-rw-r--r--layout/style/test/chrome/bug535806-html.html8
-rw-r--r--layout/style/test/chrome/bug535806-xul.xhtml8
-rw-r--r--layout/style/test/chrome/chrome-only-media-queries.js34
-rw-r--r--layout/style/test/chrome/chrome.toml51
-rw-r--r--layout/style/test/chrome/display_mode.html122
-rw-r--r--layout/style/test/chrome/display_mode_reflow.html84
-rw-r--r--layout/style/test/chrome/display_mode_reflow_iframe.html23
-rw-r--r--layout/style/test/chrome/hover_empty.html4
-rw-r--r--layout/style/test/chrome/hover_helper.html270
-rw-r--r--layout/style/test/chrome/import_useless1.css3
-rw-r--r--layout/style/test/chrome/import_useless2.css3
-rw-r--r--layout/style/test/chrome/match.pngbin0 -> 1210 bytes
-rw-r--r--layout/style/test/chrome/mismatch.pngbin0 -> 1573 bytes
-rw-r--r--layout/style/test/chrome/moz_document_helper.html2
-rw-r--r--layout/style/test/chrome/test_bug1157097.html27
-rw-r--r--layout/style/test/chrome/test_bug1346623.html60
-rw-r--r--layout/style/test/chrome/test_bug1371453.html33
-rw-r--r--layout/style/test/chrome/test_bug418986-2.xhtml29
-rw-r--r--layout/style/test/chrome/test_bug511909.html194
-rw-r--r--layout/style/test/chrome/test_bug535806.xhtml43
-rw-r--r--layout/style/test/chrome/test_chrome_only_media_queries.html84
-rw-r--r--layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html11
-rw-r--r--layout/style/test/chrome/test_display_mode.html39
-rw-r--r--layout/style/test/chrome/test_display_mode_reflow.html41
-rw-r--r--layout/style/test/chrome/test_hover.html29
-rw-r--r--layout/style/test/chrome/test_moz_document_rules.html97
-rw-r--r--layout/style/test/chrome/test_moz_document_serialization.html58
-rw-r--r--layout/style/test/chrome/test_scrollbar_inline_size.html36
-rw-r--r--layout/style/test/chrome/test_stylesheet_clone_import_rule.html86
-rw-r--r--layout/style/test/css_properties_like_longhand.js1
-rw-r--r--layout/style/test/descriptor_database.js142
-rw-r--r--layout/style/test/empty.html1
-rw-r--r--layout/style/test/file_animations_async_tests.html77
-rw-r--r--layout/style/test/file_animations_omta_scroll.html392
-rw-r--r--layout/style/test/file_animations_omta_scroll_rtl.html112
-rw-r--r--layout/style/test/file_animations_with_disabled_properties.html50
-rw-r--r--layout/style/test/file_bug1055933_circle-xxl.pngbin0 -> 4857 bytes
-rw-r--r--layout/style/test/file_bug1089417_iframe.html17
-rw-r--r--layout/style/test/file_bug1375944.html13
-rw-r--r--layout/style/test/file_bug1381233.html4
-rw-r--r--layout/style/test/file_bug1443344.css1
-rw-r--r--layout/style/test/file_bug645998-1.css1
-rw-r--r--layout/style/test/file_bug645998-2.css1
-rw-r--r--layout/style/test/file_bug829816.cssbin0 -> 76 bytes
-rw-r--r--layout/style/test/file_computed_style_bfcache_display_none.html6
-rw-r--r--layout/style/test/file_computed_style_bfcache_display_none2.html6
-rw-r--r--layout/style/test/file_font_loading_api_vframe.html2
-rw-r--r--layout/style/test/file_shared_sheet_caching.css3
-rw-r--r--layout/style/test/file_shared_sheet_caching.html12
-rw-r--r--layout/style/test/file_specified_value_serialization_individual_transforms.html65
-rw-r--r--layout/style/test/flexbox_layout_testcases.js1317
-rw-r--r--layout/style/test/gen-css-properties.py24
-rw-r--r--layout/style/test/gtest/ImportScannerTest.cpp119
-rw-r--r--layout/style/test/gtest/StyloParsingBench.cpp116
-rw-r--r--layout/style/test/gtest/example.css2925
-rw-r--r--layout/style/test/gtest/generate_example_stylesheet.py16
-rw-r--r--layout/style/test/gtest/moz.build24
-rw-r--r--layout/style/test/mapped.css3
-rw-r--r--layout/style/test/mapped.css^headers^1
-rw-r--r--layout/style/test/mapped2.css4
-rw-r--r--layout/style/test/mapped2.css^headers^2
-rw-r--r--layout/style/test/media_queries_iframe.html15
-rw-r--r--layout/style/test/media_queries_iframe2.html37
-rw-r--r--layout/style/test/mochitest.toml783
-rw-r--r--layout/style/test/moz.build152
-rw-r--r--layout/style/test/mq_changes_child.html8
-rw-r--r--layout/style/test/neverending_font_load.sjs5
-rw-r--r--layout/style/test/neverending_stylesheet_load.sjs5
-rw-r--r--layout/style/test/post-redirect-1.css1
-rw-r--r--layout/style/test/post-redirect-2.css1
-rw-r--r--layout/style/test/post-redirect-3.css1
-rw-r--r--layout/style/test/property_database.js14128
-rw-r--r--layout/style/test/redirect.sjs4
-rw-r--r--layout/style/test/redundant_font_download.sjs63
-rw-r--r--layout/style/test/slow_broken_sheet.sjs19
-rw-r--r--layout/style/test/slow_load.sjs29
-rw-r--r--layout/style/test/slow_ok_sheet.sjs21
-rw-r--r--layout/style/test/sourcemap_css.html11
-rw-r--r--layout/style/test/style_attribute_tests.js25
-rw-r--r--layout/style/test/support/1x1-transparent.pngbin0 -> 89 bytes
-rw-r--r--layout/style/test/support/blue-100x100.pngbin0 -> 40279 bytes
-rw-r--r--layout/style/test/support/external-variable-url.css3
-rw-r--r--layout/style/test/test_acid3_test46.html140
-rw-r--r--layout/style/test/test_addSheet.html44
-rw-r--r--layout/style/test/test_additional_sheets.html310
-rw-r--r--layout/style/test/test_align_justify_computed_values.html484
-rw-r--r--layout/style/test/test_all_shorthand.html157
-rw-r--r--layout/style/test/test_animations.html2107
-rw-r--r--layout/style/test/test_animations_async_tests.html24
-rw-r--r--layout/style/test/test_animations_dynamic_changes.html65
-rw-r--r--layout/style/test/test_animations_effect_timing_duration.html81
-rw-r--r--layout/style/test/test_animations_effect_timing_enddelay.html141
-rw-r--r--layout/style/test/test_animations_effect_timing_iterations.html68
-rw-r--r--layout/style/test/test_animations_event_handler_attribute.html204
-rw-r--r--layout/style/test/test_animations_event_order.html710
-rw-r--r--layout/style/test/test_animations_iterationstart.html53
-rw-r--r--layout/style/test/test_animations_omta.html2969
-rw-r--r--layout/style/test/test_animations_omta_scroll.html25
-rw-r--r--layout/style/test/test_animations_omta_scroll_rtl.html25
-rw-r--r--layout/style/test/test_animations_omta_start.html187
-rw-r--r--layout/style/test/test_animations_pausing.html79
-rw-r--r--layout/style/test/test_animations_playbackrate.html94
-rw-r--r--layout/style/test/test_animations_reverse.html64
-rw-r--r--layout/style/test/test_animations_styles_on_event.html66
-rw-r--r--layout/style/test/test_animations_variable_changes.html58
-rw-r--r--layout/style/test/test_animations_with_disabled_properties.html33
-rw-r--r--layout/style/test/test_any_dynamic.html49
-rw-r--r--layout/style/test/test_area_url_cursor.html34
-rw-r--r--layout/style/test/test_asyncopen.html54
-rw-r--r--layout/style/test/test_at_rule_parse_serialize.html43
-rw-r--r--layout/style/test/test_attribute_selector_eof_behavior.html18
-rw-r--r--layout/style/test/test_backdrop_filter_enabled_state.html21
-rw-r--r--layout/style/test/test_background_blend_mode.html57
-rw-r--r--layout/style/test/test_border_device_pixel_rounding_initial_style.html20
-rw-r--r--layout/style/test/test_box_size_keywords.html170
-rw-r--r--layout/style/test/test_bug1055933.html41
-rw-r--r--layout/style/test/test_bug1089417.html47
-rw-r--r--layout/style/test/test_bug1112014.html89
-rw-r--r--layout/style/test/test_bug1203766.html112
-rw-r--r--layout/style/test/test_bug1232829.html37
-rw-r--r--layout/style/test/test_bug1292447.html350
-rw-r--r--layout/style/test/test_bug1330375.html59
-rw-r--r--layout/style/test/test_bug1371488.html23
-rw-r--r--layout/style/test/test_bug1375944.html34
-rw-r--r--layout/style/test/test_bug1382568.html14
-rw-r--r--layout/style/test/test_bug1394302.html32
-rw-r--r--layout/style/test/test_bug1443344-1.html48
-rw-r--r--layout/style/test/test_bug1443344-2.html48
-rw-r--r--layout/style/test/test_bug1451199-1.html42
-rw-r--r--layout/style/test/test_bug1451199-2.html43
-rw-r--r--layout/style/test/test_bug1490890.html112
-rw-r--r--layout/style/test/test_bug1505254.html152
-rw-r--r--layout/style/test/test_bug160403.html73
-rw-r--r--layout/style/test/test_bug1729861.html26
-rw-r--r--layout/style/test/test_bug200089.html30
-rw-r--r--layout/style/test/test_bug221428.html68
-rw-r--r--layout/style/test/test_bug229915.html95
-rw-r--r--layout/style/test/test_bug302186.html508
-rw-r--r--layout/style/test/test_bug319381.html67
-rw-r--r--layout/style/test/test_bug357614.html73
-rw-r--r--layout/style/test/test_bug363146.html62
-rw-r--r--layout/style/test/test_bug372770.html85
-rw-r--r--layout/style/test/test_bug373293.html29
-rw-r--r--layout/style/test/test_bug377947.html110
-rw-r--r--layout/style/test/test_bug379440.html74
-rw-r--r--layout/style/test/test_bug379741.html43
-rw-r--r--layout/style/test/test_bug382027.html37
-rw-r--r--layout/style/test/test_bug383075.html84
-rw-r--r--layout/style/test/test_bug387615.html53
-rw-r--r--layout/style/test/test_bug389464.html48
-rw-r--r--layout/style/test/test_bug391034.html71
-rw-r--r--layout/style/test/test_bug391221.html43
-rw-r--r--layout/style/test/test_bug397427.html91
-rw-r--r--layout/style/test/test_bug399349.html80
-rw-r--r--layout/style/test/test_bug401046.html82
-rw-r--r--layout/style/test/test_bug405818.html72
-rw-r--r--layout/style/test/test_bug412901.html42
-rw-r--r--layout/style/test/test_bug413958.html75
-rw-r--r--layout/style/test/test_bug418986-2.html32
-rw-r--r--layout/style/test/test_bug437915.html70
-rw-r--r--layout/style/test/test_bug450191.html64
-rw-r--r--layout/style/test/test_bug470769.html31
-rw-r--r--layout/style/test/test_bug499655.html45
-rw-r--r--layout/style/test/test_bug499655.xhtml48
-rw-r--r--layout/style/test/test_bug517224.html45
-rw-r--r--layout/style/test/test_bug524175.html28
-rw-r--r--layout/style/test/test_bug525952.html46
-rw-r--r--layout/style/test/test_bug534804.html89
-rw-r--r--layout/style/test/test_bug573255.html32
-rw-r--r--layout/style/test/test_bug580685.html41
-rw-r--r--layout/style/test/test_bug621351.html35
-rw-r--r--layout/style/test/test_bug635286.html52
-rw-r--r--layout/style/test/test_bug645998.html29
-rw-r--r--layout/style/test/test_bug652486.html192
-rw-r--r--layout/style/test/test_bug657143.html120
-rw-r--r--layout/style/test/test_bug667520.html50
-rw-r--r--layout/style/test/test_bug716226.html52
-rw-r--r--layout/style/test/test_bug732153.html22
-rw-r--r--layout/style/test/test_bug732209.html95
-rw-r--r--layout/style/test/test_bug73586.html189
-rw-r--r--layout/style/test/test_bug74880.html119
-rw-r--r--layout/style/test/test_bug765590.html21
-rw-r--r--layout/style/test/test_bug771043.html69
-rw-r--r--layout/style/test/test_bug795520.html39
-rw-r--r--layout/style/test/test_bug798843_pref.html53
-rw-r--r--layout/style/test/test_bug829816.html56
-rw-r--r--layout/style/test/test_bug874919.html55
-rw-r--r--layout/style/test/test_bug887741_at-rules_in_declaration_lists.html75
-rw-r--r--layout/style/test/test_bug892929.html74
-rw-r--r--layout/style/test/test_bug98997.html144
-rw-r--r--layout/style/test/test_cascade.html91
-rw-r--r--layout/style/test/test_ch_ex_no_infloops.html60
-rw-r--r--layout/style/test/test_change_hint_optimizations.html57
-rw-r--r--layout/style/test/test_clip-path_polygon.html40
-rw-r--r--layout/style/test/test_color_rounding.html38
-rw-r--r--layout/style/test/test_compute_data_with_start_struct.html87
-rw-r--r--layout/style/test/test_computed_style.html664
-rw-r--r--layout/style/test/test_computed_style_bfcache_display_none.html60
-rw-r--r--layout/style/test/test_computed_style_difference.html104
-rw-r--r--layout/style/test/test_computed_style_grid_with_pseudo.html91
-rw-r--r--layout/style/test/test_computed_style_in_created_document.html52
-rw-r--r--layout/style/test/test_computed_style_min_size_auto.html129
-rw-r--r--layout/style/test/test_computed_style_no_flush.html63
-rw-r--r--layout/style/test/test_computed_style_no_pseudo.html53
-rw-r--r--layout/style/test/test_computed_style_prefs.html94
-rw-r--r--layout/style/test/test_condition_text.html74
-rw-r--r--layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html17
-rw-r--r--layout/style/test/test_counter_descriptor_storage.html268
-rw-r--r--layout/style/test/test_counter_style.html121
-rw-r--r--layout/style/test/test_crash_with_content_policy.html75
-rw-r--r--layout/style/test/test_css_cross_domain.html158
-rw-r--r--layout/style/test/test_css_cross_domain_no_orb.html147
-rw-r--r--layout/style/test/test_css_eof_handling.html268
-rw-r--r--layout/style/test/test_css_escape_api.html94
-rw-r--r--layout/style/test/test_css_function_mismatched_parenthesis.html63
-rw-r--r--layout/style/test/test_css_loader_crossorigin_data_url.html17
-rw-r--r--layout/style/test/test_css_parse_error_smoketest.html160
-rw-r--r--layout/style/test/test_css_supports.html134
-rw-r--r--layout/style/test/test_css_supports_variables.html247
-rw-r--r--layout/style/test/test_cue_restrictions.html34
-rw-r--r--layout/style/test/test_custom_content_inheritance.html24
-rw-r--r--layout/style/test/test_default_bidi_css.html80
-rw-r--r--layout/style/test/test_default_computed_style.html58
-rw-r--r--layout/style/test/test_descriptor_storage.html118
-rw-r--r--layout/style/test/test_descriptor_syntax_errors.html53
-rw-r--r--layout/style/test/test_display_mode.html70
-rw-r--r--layout/style/test/test_dont_use_document_colors.html201
-rw-r--r--layout/style/test/test_dont_use_document_fonts.html116
-rw-r--r--layout/style/test/test_dynamic_change_causing_reflow.html1014
-rw-r--r--layout/style/test/test_exposed_prop_accessors.html41
-rw-r--r--layout/style/test/test_extra_inherit_initial.html109
-rw-r--r--layout/style/test/test_first_letter_restrictions.html56
-rw-r--r--layout/style/test/test_first_line_restrictions.html56
-rw-r--r--layout/style/test/test_flexbox_child_display_values.xhtml178
-rw-r--r--layout/style/test/test_flexbox_flex_grow_and_shrink.html154
-rw-r--r--layout/style/test/test_flexbox_flex_shorthand.html280
-rw-r--r--layout/style/test/test_flexbox_focus_order.html83
-rw-r--r--layout/style/test/test_flexbox_layout.html184
-rw-r--r--layout/style/test/test_flexbox_order.html194
-rw-r--r--layout/style/test/test_flexbox_order_abspos.html217
-rw-r--r--layout/style/test/test_flexbox_order_table.html198
-rw-r--r--layout/style/test/test_flexbox_reflow_counts.html199
-rw-r--r--layout/style/test/test_flushing_frame.html40
-rw-r--r--layout/style/test/test_font_face_cascade.html35
-rw-r--r--layout/style/test/test_font_face_parser.html386
-rw-r--r--layout/style/test/test_font_family_parsing.html272
-rw-r--r--layout/style/test/test_font_family_serialization.html51
-rw-r--r--layout/style/test/test_font_loading_api.html1895
-rw-r--r--layout/style/test/test_garbage_at_end_of_declarations.html156
-rw-r--r--layout/style/test/test_grid_computed_values.html113
-rw-r--r--layout/style/test/test_grid_container_shorthands.html271
-rw-r--r--layout/style/test/test_grid_item_shorthands.html153
-rw-r--r--layout/style/test/test_grid_shorthand_serialization.html221
-rw-r--r--layout/style/test/test_group_insertRule.html243
-rw-r--r--layout/style/test/test_hover_on_part.html52
-rw-r--r--layout/style/test/test_hover_quirk.html118
-rw-r--r--layout/style/test/test_html_attribute_computed_values.html84
-rw-r--r--layout/style/test/test_ident_escaping.html56
-rw-r--r--layout/style/test/test_img_src_causing_reflow.html36
-rw-r--r--layout/style/test/test_import_preload.html22
-rw-r--r--layout/style/test/test_inherit_computation.html176
-rw-r--r--layout/style/test/test_inherit_storage.html120
-rw-r--r--layout/style/test/test_initial_computation.html159
-rw-r--r--layout/style/test/test_initial_storage.html134
-rw-r--r--layout/style/test/test_invalidation_basic.html45
-rw-r--r--layout/style/test/test_keyframes_rules.html134
-rw-r--r--layout/style/test/test_keyframes_vendor_prefix.html167
-rw-r--r--layout/style/test/test_load_events_on_stylesheets.html176
-rw-r--r--layout/style/test/test_logical_properties.html422
-rw-r--r--layout/style/test/test_marker_restrictions.html35
-rw-r--r--layout/style/test/test_mask_image_CORS.html63
-rw-r--r--layout/style/test/test_media_queries.html867
-rw-r--r--layout/style/test/test_media_queries_dynamic.html207
-rw-r--r--layout/style/test/test_media_query_list.html373
-rw-r--r--layout/style/test/test_media_query_serialization.html53
-rw-r--r--layout/style/test/test_moz_device_pixel_ratio.html73
-rw-r--r--layout/style/test/test_moz_prefixed_cursor.html14
-rw-r--r--layout/style/test/test_mq_any_hover_and_any_pointer.html97
-rw-r--r--layout/style/test/test_mq_changes_in_iframe.html54
-rw-r--r--layout/style/test/test_mq_hover_and_pointer.html127
-rw-r--r--layout/style/test/test_mq_prefers_contrast_dynamic.html82
-rw-r--r--layout/style/test/test_mq_prefers_reduced_motion_dynamic.html86
-rw-r--r--layout/style/test/test_mql_event_listener_leaks.html43
-rw-r--r--layout/style/test/test_namespace_rule.html461
-rw-r--r--layout/style/test/test_non_content_accessible_env_vars.html39
-rw-r--r--layout/style/test/test_non_content_accessible_properties.html85
-rw-r--r--layout/style/test/test_non_content_accessible_pseudos.html69
-rw-r--r--layout/style/test/test_non_content_accessible_values.html172
-rw-r--r--layout/style/test/test_non_matching_sheet_media.html30
-rw-r--r--layout/style/test/test_of_type_selectors.xhtml97
-rw-r--r--layout/style/test/test_overscroll_behavior_pref.html24
-rw-r--r--layout/style/test/test_page_parser.html93
-rw-r--r--layout/style/test/test_parse_eof.html69
-rw-r--r--layout/style/test/test_parse_ident.html56
-rw-r--r--layout/style/test/test_parse_rule.html261
-rw-r--r--layout/style/test/test_parse_url.html195
-rw-r--r--layout/style/test/test_parser_diagnostics_unprintables.html220
-rw-r--r--layout/style/test/test_pixel_lengths.html61
-rw-r--r--layout/style/test/test_placeholder_restrictions.html57
-rw-r--r--layout/style/test/test_pointer-events.html114
-rw-r--r--layout/style/test/test_position_float_display.html111
-rw-r--r--layout/style/test/test_position_sticky.html89
-rw-r--r--layout/style/test/test_prefers_contrast_color_pairs.html49
-rw-r--r--layout/style/test/test_priority_preservation.html141
-rw-r--r--layout/style/test/test_property_database.html173
-rw-r--r--layout/style/test/test_property_syntax_errors.html155
-rw-r--r--layout/style/test/test_pseudo_display_fixup.html29
-rw-r--r--layout/style/test/test_pseudoelement_parsing.html43
-rw-r--r--layout/style/test/test_pseudoelement_state.html185
-rw-r--r--layout/style/test/test_query_container_for.html62
-rw-r--r--layout/style/test/test_redundant_font_download.html131
-rw-r--r--layout/style/test/test_reframe_cb.html56
-rw-r--r--layout/style/test/test_reframe_image_loading.html29
-rw-r--r--layout/style/test/test_reframe_input.html48
-rw-r--r--layout/style/test/test_reframe_pseudo_element.html46
-rw-r--r--layout/style/test/test_rem_unit.html80
-rw-r--r--layout/style/test/test_restyle_table_wrapper.html33
-rw-r--r--layout/style/test/test_restyles_in_smil_animation.html137
-rw-r--r--layout/style/test/test_revert.html103
-rw-r--r--layout/style/test/test_root_node_display.html74
-rw-r--r--layout/style/test/test_rule_insertion.html240
-rw-r--r--layout/style/test/test_rules_out_of_sheets.html115
-rw-r--r--layout/style/test/test_selectors.html1348
-rw-r--r--layout/style/test/test_setPropertyWithNull.html47
-rw-r--r--layout/style/test/test_shape_outside_CORS.html59
-rw-r--r--layout/style/test/test_shared_sheet_caching.html35
-rw-r--r--layout/style/test/test_sheet_privilege.html33
-rw-r--r--layout/style/test/test_shorthand_property_getters.html248
-rw-r--r--layout/style/test/test_specified_value_serialization.html281
-rw-r--r--layout/style/test/test_style_attr_listener.html52
-rw-r--r--layout/style/test/test_style_attribute_quirks.html18
-rw-r--r--layout/style/test/test_style_attribute_standards.html19
-rw-r--r--layout/style/test/test_style_struct_copy_constructors.html93
-rw-r--r--layout/style/test/test_stylesheet_additions.html68
-rw-r--r--layout/style/test/test_stylesheet_clone_font_face.html26
-rw-r--r--layout/style/test/test_supports_rules.html88
-rw-r--r--layout/style/test/test_system_font_serialization.html84
-rw-r--r--layout/style/test/test_text_decoration_shorthands.html136
-rw-r--r--layout/style/test/test_transitions.html787
-rw-r--r--layout/style/test/test_transitions_and_reframes.html298
-rw-r--r--layout/style/test/test_transitions_and_restyles.html48
-rw-r--r--layout/style/test/test_transitions_and_zoom.html49
-rw-r--r--layout/style/test/test_transitions_at_start.html39
-rw-r--r--layout/style/test/test_transitions_bug537151.html51
-rw-r--r--layout/style/test/test_transitions_cancel_near_end.html82
-rw-r--r--layout/style/test/test_transitions_computed_value_combinations.html170
-rw-r--r--layout/style/test/test_transitions_computed_values.html113
-rw-r--r--layout/style/test/test_transitions_dynamic_changes.html106
-rw-r--r--layout/style/test/test_transitions_events.html294
-rw-r--r--layout/style/test/test_transitions_per_property.html3245
-rw-r--r--layout/style/test/test_transitions_replacement_on_busy_frame.html100
-rw-r--r--layout/style/test/test_transitions_replacement_with_setKeyframes.html88
-rw-r--r--layout/style/test/test_transitions_step_functions.html131
-rw-r--r--layout/style/test/test_unclosed_parentheses.html262
-rw-r--r--layout/style/test/test_unicode_range_loading.html366
-rw-r--r--layout/style/test/test_units_angle.html52
-rw-r--r--layout/style/test/test_units_frequency.html56
-rw-r--r--layout/style/test/test_units_length.html60
-rw-r--r--layout/style/test/test_units_time.html47
-rw-r--r--layout/style/test/test_use_counters.html159
-rw-r--r--layout/style/test/test_user_sheet_shadow_dom.html48
-rw-r--r--layout/style/test/test_value_cloning.html181
-rw-r--r--layout/style/test/test_value_computation.html236
-rw-r--r--layout/style/test/test_value_storage.html365
-rw-r--r--layout/style/test/test_variable_serialization_computed.html79
-rw-r--r--layout/style/test/test_variable_serialization_specified.html116
-rw-r--r--layout/style/test/test_variables.html129
-rw-r--r--layout/style/test/test_variables_loop.html31
-rw-r--r--layout/style/test/test_variables_order.html53
-rw-r--r--layout/style/test/test_video_object_fit.html53
-rw-r--r--layout/style/test/test_viewport_scrollbar_causing_reflow.html130
-rw-r--r--layout/style/test/test_viewport_units.html66
-rw-r--r--layout/style/test/test_visited_image_loading.html68
-rw-r--r--layout/style/test/test_visited_image_loading_empty.html68
-rw-r--r--layout/style/test/test_visited_lying.html97
-rw-r--r--layout/style/test/test_visited_pref.html112
-rw-r--r--layout/style/test/test_visited_reftests.html210
-rw-r--r--layout/style/test/test_webkit_device_pixel_ratio.html73
-rw-r--r--layout/style/test/test_webkit_flex_display.html46
-rw-r--r--layout/style/test/unstyled-frame.css0
-rw-r--r--layout/style/test/unstyled-frame.xml4
-rw-r--r--layout/style/test/unstyled.css2
-rw-r--r--layout/style/test/unstyled.xml3
-rw-r--r--layout/style/test/viewport_units_iframe.html6
-rw-r--r--layout/style/test/visited-lying-inner.html8
-rw-r--r--layout/style/test/visited-pref-iframe.html7
-rw-r--r--layout/style/test/visited_image_loading.sjs83
-rw-r--r--layout/style/test/visited_image_loading_frame.html15
-rw-r--r--layout/style/test/visited_image_loading_frame_empty.html15
410 files changed, 69570 insertions, 0 deletions
diff --git a/layout/style/test/Ahem.ttf b/layout/style/test/Ahem.ttf
new file mode 100644
index 0000000000..ac81cb0316
--- /dev/null
+++ b/layout/style/test/Ahem.ttf
Binary files differ
diff --git a/layout/style/test/BitPattern.woff b/layout/style/test/BitPattern.woff
new file mode 100644
index 0000000000..e4e8244057
--- /dev/null
+++ b/layout/style/test/BitPattern.woff
Binary files differ
diff --git a/layout/style/test/ListCSSProperties.cpp b/layout/style/test/ListCSSProperties.cpp
new file mode 100644
index 0000000000..13ac6ae93d
--- /dev/null
+++ b/layout/style/test/ListCSSProperties.cpp
@@ -0,0 +1,178 @@
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/* build (from code) lists of all supported CSS properties */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "mozilla/ArrayUtils.h"
+
+// Do not consider properties not valid in style rules
+#define CSS_PROP_LIST_EXCLUDE_NOT_IN_STYLE
+
+// Need an extra level of macro nesting to force expansion of method_
+// params before they get pasted.
+#define STRINGIFY_METHOD(method_) #method_
+
+struct PropertyInfo {
+ const char* propName;
+ const char* domName;
+ const char* pref;
+};
+
+const PropertyInfo gLonghandProperties[] = {
+
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_
+#define CSS_PROP_LONGHAND(name_, id_, method_, flags_, pref_, ...) \
+ {#name_, STRINGIFY_METHOD(method_), pref_},
+
+#include "mozilla/ServoCSSPropList.h"
+
+#undef CSS_PROP_LONGHAND
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+
+};
+
+/*
+ * These are the properties for which domName in the above list should
+ * be used. They're in the same order as the above list, with some
+ * items skipped.
+ */
+const char* gLonghandPropertiesWithDOMProp[] = {
+
+#define CSS_PROP_LIST_EXCLUDE_INTERNAL
+#define CSS_PROP_LONGHAND(name_, ...) #name_,
+
+#include "mozilla/ServoCSSPropList.h"
+
+#undef CSS_PROP_LONGHAND
+#undef CSS_PROP_LIST_EXCLUDE_INTERNAL
+
+};
+
+const PropertyInfo gShorthandProperties[] = {
+
+#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+ {#name_, STRINGIFY_METHOD(method_), pref_},
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, flags_, pref_) \
+ {#name_, #method_, pref_},
+
+#include "mozilla/ServoCSSPropList.h"
+
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_PUBLIC_OR_PRIVATE
+
+};
+
+/* see gLonghandPropertiesWithDOMProp */
+const char* gShorthandPropertiesWithDOMProp[] = {
+
+#define CSS_PROP_LIST_EXCLUDE_INTERNAL
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) #name_,
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, flags_, pref_) #name_,
+
+#include "mozilla/ServoCSSPropList.h"
+
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LIST_EXCLUDE_INTERNAL
+
+};
+
+#undef STRINGIFY_METHOD
+
+const char* gInaccessibleProperties[] = {
+ // Don't print the properties that aren't accepted by the parser, per
+ // CSSParserImpl::ParseProperty
+ "-x-cols",
+ "-x-lang",
+ "-x-span",
+ "-x-text-scale",
+ "-moz-default-appearance",
+ "-moz-theme",
+ "-moz-inert",
+ "-moz-script-level", // parsed by UA sheets only
+ "-moz-math-variant",
+ "-moz-math-display", // parsed by UA sheets only
+ "-moz-top-layer", // parsed by UA sheets only
+ "-moz-min-font-size-ratio", // parsed by UA sheets only
+ "-moz-box-collapse", // chrome-only internal properties
+ "-moz-subtree-hidden-only-visually", // chrome-only internal properties
+ "-moz-user-focus", // chrome-only internal properties
+ "-moz-window-input-region-margin", // chrome-only internal properties
+ "-moz-window-opacity", // chrome-only internal properties
+ "-moz-window-transform", // chrome-only internal properties
+ "-moz-window-transform-origin", // chrome-only internal properties
+ "-moz-window-shadow", // chrome-only internal properties
+};
+
+inline int is_inaccessible(const char* aPropName) {
+ for (unsigned j = 0; j < MOZ_ARRAY_LENGTH(gInaccessibleProperties); ++j) {
+ if (strcmp(aPropName, gInaccessibleProperties[j]) == 0) return 1;
+ }
+ return 0;
+}
+
+void print_array(const char* aName, const PropertyInfo* aProps,
+ unsigned aPropsLength, const char* const* aDOMProps,
+ unsigned aDOMPropsLength) {
+ printf("var %s = [\n", aName);
+
+ int first = 1;
+ unsigned j = 0; // index into DOM prop list
+ for (unsigned i = 0; i < aPropsLength; ++i) {
+ const PropertyInfo* p = aProps + i;
+
+ if (is_inaccessible(p->propName))
+ // inaccessible properties never have DOM props, so don't
+ // worry about incrementing j. The assertion below will
+ // catch if they do.
+ continue;
+
+ if (first)
+ first = 0;
+ else
+ printf(",\n");
+
+ printf("\t{ name: \"%s\", prop: ", p->propName);
+ if (j >= aDOMPropsLength || strcmp(p->propName, aDOMProps[j]) != 0)
+ printf("null");
+ else {
+ ++j;
+ if (strncmp(p->domName, "Moz", 3) == 0)
+ printf("\"%s\"", p->domName);
+ else
+ // lowercase the first letter
+ printf("\"%c%s\"", p->domName[0] + 32, p->domName + 1);
+ }
+ if (p->pref[0]) {
+ printf(", pref: \"%s\"", p->pref);
+ }
+ printf(" }");
+ }
+
+ if (j != aDOMPropsLength) {
+ fprintf(stderr, "Assertion failure %s:%d\n", __FILE__, __LINE__);
+ fprintf(stderr, "j==%d, aDOMPropsLength == %d\n", j, aDOMPropsLength);
+ exit(1);
+ }
+
+ printf("\n];\n\n");
+}
+
+int main() {
+ print_array("gLonghandProperties", gLonghandProperties,
+ MOZ_ARRAY_LENGTH(gLonghandProperties),
+ gLonghandPropertiesWithDOMProp,
+ MOZ_ARRAY_LENGTH(gLonghandPropertiesWithDOMProp));
+ print_array("gShorthandProperties", gShorthandProperties,
+ MOZ_ARRAY_LENGTH(gShorthandProperties),
+ gShorthandPropertiesWithDOMProp,
+ MOZ_ARRAY_LENGTH(gShorthandPropertiesWithDOMProp));
+ return 0;
+}
diff --git a/layout/style/test/ParseCSS.cpp b/layout/style/test/ParseCSS.cpp
new file mode 100644
index 0000000000..04e37d48e2
--- /dev/null
+++ b/layout/style/test/ParseCSS.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=8:et:sw=4:
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file is meant to be used with |#define CSS_REPORT_PARSE_ERRORS|
+ * in mozilla/dom/html/style/src/nsCSSScanner.h uncommented, and the
+ * |#ifdef DEBUG| block in nsCSSScanner::OutputError (in
+ * nsCSSScanner.cpp in the same directory) used (even if not a debug
+ * build).
+ */
+
+#include "nsXPCOM.h"
+#include "nsCOMPtr.h"
+
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+
+#include "nsContentCID.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+
+using namespace mozilla;
+
+static already_AddRefed<nsIURI> FileToURI(const char* aFilename,
+ nsresult* aRv = 0) {
+ nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, aRv));
+ NS_ENSURE_TRUE(lf, nullptr);
+ // XXX Handle relative paths somehow.
+ lf->InitWithNativePath(nsDependentCString(aFilename));
+
+ nsIURI* uri = nullptr;
+ nsresult rv = NS_NewFileURI(&uri, lf);
+ if (aRv) *aRv = rv;
+ return uri;
+}
+
+static int ParseCSSFile(nsIURI* aSheetURI) {
+ RefPtr<mozilla::css::Loader> = new mozilla::css::Loader();
+ RefPtr<CSSStyleSheet> sheet;
+ loader->LoadSheetSync(aSheetURI, getter_AddRefs(sheet));
+ NS_ASSERTION(sheet, "sheet load failed");
+ /* This can happen if the file can't be found (e.g. you
+ * ask for a relative path and xpcom/io rejects it)
+ */
+ if (!sheet) return -1;
+ bool complete;
+ sheet->GetComplete(complete);
+ NS_ASSERTION(complete, "synchronous load did not complete");
+ if (!complete) return -2;
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ if (argc < 2) {
+ fprintf(stderr, "%s [FILE]...\n", argv[0]);
+ }
+ nsresult rv = NS_InitXPCOM(nullptr, nullptr, nullptr);
+ if (NS_FAILED(rv)) return (int)rv;
+
+ int res = 0;
+ for (int i = 1; i < argc; ++i) {
+ const char* filename = argv[i];
+
+ printf("\nParsing %s.\n", filename);
+
+ nsCOMPtr<nsIURI> uri = FileToURI(filename, &rv);
+ if (rv == NS_ERROR_OUT_OF_MEMORY) {
+ fprintf(stderr, "Out of memory.\n");
+ return 1;
+ }
+ if (uri) res = ParseCSSFile(uri);
+ }
+
+ NS_ShutdownXPCOM(nullptr);
+
+ return res;
+}
diff --git a/layout/style/test/additional_sheets_helper.html b/layout/style/test/additional_sheets_helper.html
new file mode 100644
index 0000000000..306ddbf5bd
--- /dev/null
+++ b/layout/style/test/additional_sheets_helper.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+ some text
+</body>
+</html>
diff --git a/layout/style/test/animation_utils.js b/layout/style/test/animation_utils.js
new file mode 100644
index 0000000000..6f7ededcd4
--- /dev/null
+++ b/layout/style/test/animation_utils.js
@@ -0,0 +1,935 @@
+//----------------------------------------------------------------------
+//
+// Common testing functions
+//
+//----------------------------------------------------------------------
+
+function advance_clock(milliseconds) {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
+}
+
+// Test-element creation/destruction and event checking
+(function () {
+ var gElem;
+ var gEventsReceived = [];
+
+ function new_div(style) {
+ return new_element("div", style);
+ }
+
+ // Creates a new |tagname| element with inline style |style| and appends
+ // it as a child of the element with ID 'display'.
+ // The element will also be given the class 'target' which can be used
+ // for additional styling.
+ function new_element(tagname, style) {
+ if (gElem) {
+ ok(false, "test author forgot to call done_div/done_elem");
+ }
+ if (typeof style != "string") {
+ ok(false, "test author forgot to pass argument");
+ }
+ if (!document.getElementById("display")) {
+ ok(false, "no 'display' element to append to");
+ }
+ gElem = document.createElement(tagname);
+ gElem.setAttribute("style", style);
+ gElem.classList.add("target");
+ document.getElementById("display").appendChild(gElem);
+ return [gElem, getComputedStyle(gElem, "")];
+ }
+
+ function listen() {
+ if (!gElem) {
+ ok(false, "test author forgot to call new_div before listen");
+ }
+ gEventsReceived = [];
+ function listener(event) {
+ gEventsReceived.push(event);
+ }
+ gElem.addEventListener("animationstart", listener);
+ gElem.addEventListener("animationiteration", listener);
+ gElem.addEventListener("animationend", listener);
+ }
+
+ function check_events(eventsExpected, desc) {
+ // This function checks that the list of eventsExpected matches
+ // the received events -- but it only checks the properties that
+ // are present on eventsExpected.
+ is(
+ gEventsReceived.length,
+ eventsExpected.length,
+ "number of events received for " + desc
+ );
+ for (
+ var i = 0,
+ i_end = Math.min(eventsExpected.length, gEventsReceived.length);
+ i != i_end;
+ ++i
+ ) {
+ var exp = eventsExpected[i];
+ var rec = gEventsReceived[i];
+ for (var prop in exp) {
+ if (prop == "elapsedTime") {
+ // Allow floating point error.
+ ok(
+ Math.abs(rec.elapsedTime - exp.elapsedTime) < 0.000002,
+ "events[" +
+ i +
+ "]." +
+ prop +
+ " for " +
+ desc +
+ " received=" +
+ rec.elapsedTime +
+ " expected=" +
+ exp.elapsedTime
+ );
+ } else {
+ is(
+ rec[prop],
+ exp[prop],
+ "events[" + i + "]." + prop + " for " + desc
+ );
+ }
+ }
+ }
+ for (var i = eventsExpected.length; i < gEventsReceived.length; ++i) {
+ ok(false, "unexpected " + gEventsReceived[i].type + " event for " + desc);
+ }
+ gEventsReceived = [];
+ }
+
+ function done_element() {
+ if (!gElem) {
+ ok(
+ false,
+ "test author called done_element/done_div without matching" +
+ " call to new_element/new_div"
+ );
+ }
+ gElem.remove();
+ gElem = null;
+ if (gEventsReceived.length) {
+ ok(false, "caller should have called check_events");
+ }
+ }
+
+ [new_div, new_element, listen, check_events, done_element].forEach(function (
+ fn
+ ) {
+ window[fn.name] = fn;
+ });
+ window.done_div = done_element;
+})();
+
+function px_to_num(str) {
+ return Number(String(str).match(/^([\d.]+)px$/)[1]);
+}
+
+function bezier(x1, y1, x2, y2) {
+ // Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1).
+ function x_for_t(t) {
+ var omt = 1 - t;
+ return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t;
+ }
+ function y_for_t(t) {
+ var omt = 1 - t;
+ return 3 * omt * omt * t * y1 + 3 * omt * t * t * y2 + t * t * t;
+ }
+ function t_for_x(x) {
+ // Binary subdivision.
+ var mint = 0,
+ maxt = 1;
+ for (var i = 0; i < 30; ++i) {
+ var guesst = (mint + maxt) / 2;
+ var guessx = x_for_t(guesst);
+ if (x < guessx) {
+ maxt = guesst;
+ } else {
+ mint = guesst;
+ }
+ }
+ return (mint + maxt) / 2;
+ }
+ return function bezier_closure(x) {
+ if (x == 0) {
+ return 0;
+ }
+ if (x == 1) {
+ return 1;
+ }
+ return y_for_t(t_for_x(x));
+ };
+}
+
+function step_end(nsteps) {
+ return function step_end_closure(x) {
+ return Math.floor(x * nsteps) / nsteps;
+ };
+}
+
+function step_start(nsteps) {
+ var stepend = step_end(nsteps);
+ return function step_start_closure(x) {
+ return 1.0 - stepend(1.0 - x);
+ };
+}
+
+var gTF = {
+ ease: bezier(0.25, 0.1, 0.25, 1),
+ linear: function (x) {
+ return x;
+ },
+ ease_in: bezier(0.42, 0, 1, 1),
+ ease_out: bezier(0, 0, 0.58, 1),
+ ease_in_out: bezier(0.42, 0, 0.58, 1),
+ step_start: step_start(1),
+ step_end: step_end(1),
+};
+
+function is_approx(float1, float2, error, desc) {
+ ok(
+ Math.abs(float1 - float2) < error,
+ desc + ": " + float1 + " and " + float2 + " should be within " + error
+ );
+}
+
+function findKeyframesRule(name) {
+ for (var i = 0; i < document.styleSheets.length; i++) {
+ var match = [].find.call(document.styleSheets[i].cssRules, function (rule) {
+ return rule.type == CSSRule.KEYFRAMES_RULE && rule.name == name;
+ });
+ if (match) {
+ return match;
+ }
+ }
+ return undefined;
+}
+
+// Checks if off-main thread animation (OMTA) is available, and if it is, runs
+// the provided callback function. If OMTA is not available or is not
+// functioning correctly, the second callback, aOnSkip, is run instead.
+//
+// This function also does an internal test to verify that OMTA is working at
+// all so that if OMTA is not functioning correctly when it is expected to
+// function only a single failure is produced.
+//
+// Since this function relies on various asynchronous operations, the caller is
+// responsible for calling SimpleTest.waitForExplicitFinish() before calling
+// this and SimpleTest.finish() within aTestFunction and aOnSkip.
+//
+// specialPowersForPrefs exists because some SpecialPowers objects apparently
+// can get prefs and some can't; callers that would normally have one of the
+// latter but can get their hands on one of the former can pass it in
+// explicitly.
+function runOMTATest(aTestFunction, aOnSkip, specialPowersForPrefs) {
+ const OMTAPrefKey = "layers.offmainthreadcomposition.async-animations";
+ var utils = SpecialPowers.DOMWindowUtils;
+ if (!specialPowersForPrefs) {
+ specialPowersForPrefs = SpecialPowers;
+ }
+ var expectOMTA =
+ utils.layerManagerRemote &&
+ // ^ Off-main thread animation cannot be used if off-main
+ // thread composition (OMTC) is not available
+ specialPowersForPrefs.getBoolPref(OMTAPrefKey);
+
+ isOMTAWorking()
+ .then(function (isWorking) {
+ if (expectOMTA) {
+ if (isWorking) {
+ aTestFunction();
+ } else {
+ // We only call this when we know it will fail as otherwise in the
+ // regular success case we will end up inflating the "passed tests"
+ // count by 1
+ ok(isWorking, "OMTA should work");
+ aOnSkip();
+ }
+ } else {
+ todo(
+ isWorking,
+ "OMTA should ideally work, though we don't expect it to work on " +
+ "this platform/configuration"
+ );
+ aOnSkip();
+ }
+ })
+ .catch(function (err) {
+ ok(false, err);
+ aOnSkip();
+ });
+
+ function isOMTAWorking() {
+ // Create keyframes rule
+ const animationName = "a6ce3091ed85"; // Random name to avoid clashes
+ var ruleText =
+ "@keyframes " +
+ animationName +
+ " { from { opacity: 0.5 } to { opacity: 0.5 } }";
+ var style = document.createElement("style");
+ style.appendChild(document.createTextNode(ruleText));
+ document.head.appendChild(style);
+
+ // Create animation target
+ var div = document.createElement("div");
+ document.body.appendChild(div);
+
+ // Give the target geometry so it is eligible for layerization
+ div.style.width = "100px";
+ div.style.height = "100px";
+ div.style.backgroundColor = "white";
+
+ // Common clean up code
+ var cleanUp = function () {
+ div.remove();
+ style.remove();
+ if (utils.isTestControllingRefreshes) {
+ utils.restoreNormalRefresh();
+ }
+ };
+
+ return waitForDocumentLoad()
+ .then(loadPaintListener)
+ .then(function () {
+ // Put refresh driver under test control and flush all pending style,
+ // layout and paint to avoid the situation that waitForPaintsFlush()
+ // receives unexpected MozAfterpaint event for those pending
+ // notifications.
+ utils.advanceTimeAndRefresh(0);
+ return waitForPaintsFlushed();
+ })
+ .then(function () {
+ div.style.animation = animationName + " 10s";
+
+ return waitForPaintsFlushed();
+ })
+ .then(function () {
+ var opacity = utils.getOMTAStyle(div, "opacity");
+ cleanUp();
+ return Promise.resolve(opacity == 0.5);
+ })
+ .catch(function (err) {
+ cleanUp();
+ return Promise.reject(err);
+ });
+ }
+
+ function waitForDocumentLoad() {
+ return new Promise(function (resolve, reject) {
+ if (document.readyState === "complete") {
+ resolve();
+ } else {
+ window.addEventListener("load", resolve);
+ }
+ });
+ }
+
+ function loadPaintListener() {
+ return new Promise(function (resolve, reject) {
+ if (typeof window.waitForAllPaints !== "function") {
+ var script = document.createElement("script");
+ script.onload = resolve;
+ script.onerror = function () {
+ reject(new Error("Failed to load paint listener"));
+ };
+ script.src = "/tests/SimpleTest/paint_listener.js";
+ var firstScript = document.scripts[0];
+ firstScript.parentNode.insertBefore(script, firstScript);
+ } else {
+ resolve();
+ }
+ });
+ }
+}
+
+// Common architecture for setting up a series of asynchronous animation tests
+//
+// Usage example:
+//
+// addAsyncAnimTest(function *() {
+// .. do work ..
+// yield functionThatReturnsAPromise();
+// .. do work ..
+// });
+// runAllAsyncAnimTests().then(SimpleTest.finish());
+//
+(function () {
+ var tests = [];
+
+ window.addAsyncAnimTest = function (generator) {
+ tests.push(generator);
+ };
+
+ // Returns a promise when all tests have run
+ window.runAllAsyncAnimTests = function (aOnAbort) {
+ // runAsyncAnimTest returns a Promise that is resolved when the
+ // test is finished so we can chain them together
+ return tests.reduce(function (sequence, test) {
+ return sequence.then(function () {
+ return runAsyncAnimTest(test, aOnAbort);
+ });
+ }, Promise.resolve() /* the start of the sequence */);
+ };
+
+ // Takes a generator function that represents a test case. Each point in the
+ // test case that waits asynchronously for some result yields a Promise that
+ // is resolved when the asynchronous action has completed. By chaining these
+ // intermediate results together we run the test to completion.
+ //
+ // This method itself returns a Promise that is resolved when the generator
+ // function has completed.
+ //
+ // This arrangement is based on add_task() which is currently only available
+ // in mochitest-chrome (bug 872229). If add_task becomes available in
+ // mochitest-plain, we can remove this function and use add_task instead.
+ function runAsyncAnimTest(aTestFunc, aOnAbort) {
+ var generator;
+
+ function step(arg) {
+ var next;
+ try {
+ next = generator.next(arg);
+ } catch (e) {
+ return Promise.reject(e);
+ }
+ if (next.done) {
+ return Promise.resolve(next.value);
+ }
+ return Promise.resolve(next.value).then(step, function (err) {
+ throw err;
+ });
+ }
+
+ // Put refresh driver under test control
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
+
+ // Run test
+ var promise = aTestFunc();
+ if (!promise.then) {
+ generator = promise;
+ promise = step();
+ }
+ return promise
+ .catch(function (err) {
+ ok(false, err.message);
+ if (typeof aOnAbort == "function") {
+ aOnAbort();
+ }
+ })
+ .then(function () {
+ // Restore clock
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ });
+ }
+})();
+
+//----------------------------------------------------------------------
+//
+// Helper functions for testing animated values on the compositor
+//
+//----------------------------------------------------------------------
+
+const RunningOn = {
+ MainThread: 0,
+ Compositor: 1,
+ Either: 2,
+ TodoMainThread: 3,
+ TodoCompositor: 4,
+};
+
+const ExpectComparisonTo = {
+ Pass: 1,
+ Fail: 2,
+};
+
+(function () {
+ window.omta_todo_is = function (
+ elem,
+ property,
+ expected,
+ runningOn,
+ desc,
+ pseudo
+ ) {
+ return omta_is_approx(
+ elem,
+ property,
+ expected,
+ 0,
+ runningOn,
+ desc,
+ ExpectComparisonTo.Fail,
+ pseudo
+ );
+ };
+
+ window.omta_is = function (
+ elem,
+ property,
+ expected,
+ runningOn,
+ desc,
+ pseudo
+ ) {
+ return omta_is_approx(
+ elem,
+ property,
+ expected,
+ 0,
+ runningOn,
+ desc,
+ ExpectComparisonTo.Pass,
+ pseudo
+ );
+ };
+
+ // Many callers of this method will pass 'undefined' for
+ // expectedComparisonResult.
+ window.omta_is_approx = function (
+ elem,
+ property,
+ expected,
+ tolerance,
+ runningOn,
+ desc,
+ expectedComparisonResult,
+ pseudo
+ ) {
+ // Check input
+ // FIXME: Auto generate this array.
+ const omtaProperties = [
+ "transform",
+ "translate",
+ "rotate",
+ "scale",
+ "offset-path",
+ "offset-distance",
+ "offset-rotate",
+ "offset-anchor",
+ "offset-position",
+ "opacity",
+ "background-color",
+ ];
+ if (!omtaProperties.includes(property)) {
+ ok(false, property + " is not an OMTA property");
+ return;
+ }
+ var normalize;
+ var compare;
+ var normalizedToString = JSON.stringify;
+ switch (property) {
+ case "offset-path":
+ case "offset-distance":
+ case "offset-rotate":
+ case "offset-anchor":
+ case "offset-position":
+ case "translate":
+ case "rotate":
+ case "scale":
+ if (runningOn == RunningOn.MainThread) {
+ normalize = value => value;
+ compare = function (a, b, error) {
+ return a == b;
+ };
+ break;
+ }
+ // fall through
+ case "transform":
+ normalize = convertTo3dMatrix;
+ compare = matricesRoughlyEqual;
+ normalizedToString = convert3dMatrixToString;
+ break;
+ case "opacity":
+ normalize = parseFloat;
+ compare = function (a, b, error) {
+ return Math.abs(a - b) <= error;
+ };
+ break;
+ default:
+ normalize = value => value;
+ compare = function (a, b, error) {
+ return a == b;
+ };
+ break;
+ }
+
+ if (!!expected.compositorValue) {
+ const originalNormalize = normalize;
+ normalize = value =>
+ !!value.compositorValue
+ ? originalNormalize(value.compositorValue)
+ : originalNormalize(value);
+ }
+
+ // Get actual values
+ var compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(
+ elem,
+ property,
+ pseudo
+ );
+ var computedStr = window.getComputedStyle(elem, pseudo)[property];
+
+ // Prepare expected value
+ var expectedValue = normalize(expected);
+ if (expectedValue === null) {
+ ok(
+ false,
+ desc +
+ ": test author should provide a valid 'expected' value" +
+ " - got " +
+ expected.toString()
+ );
+ return;
+ }
+
+ // Check expected value appears in the right place
+ var actualStr;
+ switch (runningOn) {
+ case RunningOn.Either:
+ runningOn =
+ compositorStr !== "" ? RunningOn.Compositor : RunningOn.MainThread;
+ actualStr = compositorStr !== "" ? compositorStr : computedStr;
+ break;
+
+ case RunningOn.Compositor:
+ if (compositorStr === "") {
+ ok(false, desc + ": should be animating on compositor");
+ return;
+ }
+ actualStr = compositorStr;
+ break;
+
+ case RunningOn.TodoMainThread:
+ todo(
+ compositorStr === "",
+ desc + ": should NOT be animating on compositor"
+ );
+ actualStr = compositorStr === "" ? computedStr : compositorStr;
+ break;
+
+ case RunningOn.TodoCompositor:
+ todo(
+ compositorStr !== "",
+ desc + ": should be animating on compositor"
+ );
+ actualStr = compositorStr !== "" ? computedStr : compositorStr;
+ break;
+
+ default:
+ if (compositorStr !== "") {
+ ok(false, desc + ": should NOT be animating on compositor");
+ return;
+ }
+ actualStr = computedStr;
+ break;
+ }
+
+ var okOrTodo =
+ expectedComparisonResult == ExpectComparisonTo.Fail ? todo : ok;
+
+ // Compare animated value with expected
+ var actualValue = normalize(actualStr);
+ // Note: the actualStr should be empty string when using todoCompositor, so
+ // actualValue is null in this case. However, compare() should handle null
+ // well.
+ okOrTodo(
+ compare(expectedValue, actualValue, tolerance),
+ desc +
+ " - got " +
+ actualStr +
+ ", expected " +
+ normalizedToString(expectedValue)
+ );
+
+ // For transform-like properties, if we have multiple transform-like
+ // properties, the OMTA value and getComputedStyle() must be different,
+ // so use this flag to skip the following tests.
+ // FIXME: Putting this property on the expected value is a little bit odd.
+ // It's not really a product of the expected value, but rather the kind of
+ // test we're running. That said, the omta_is, omta_todo_is etc. methods are
+ // already pretty complex and adding another parameter would probably
+ // complicate things too much so this is fine for now. If we extend these
+ // functions any more, though, we should probably reconsider this API.
+ if (expected.usesMultipleProperties) {
+ return;
+ }
+
+ if (typeof expected.computed !== "undefined") {
+ // For some tests we specify a separate computed value for comparing
+ // with getComputedStyle.
+ //
+ // In particular, we do this for the individual transform functions since
+ // the form returned from getComputedStyle() reflects the individual
+ // properties (e.g. 'translate: 100px') while the form we read back from
+ // the compositor represents the combined result of all the transform
+ // properties as a single transform matrix (e.g. [0, 0, 0, 0, 100, 0]).
+ //
+ // Despite the fact that we can't directly compare the OMTA value against
+ // the getComputedStyle value in this case, it is still worth checking the
+ // result of getComputedStyle since it will help to alert us if some
+ // discrepancy arises between the way we calculate values on the main
+ // thread and compositor.
+ okOrTodo(
+ computedStr == expected.computed,
+ desc + ": Computed style should be equal to " + expected.computed
+ );
+ } else if (actualStr === compositorStr) {
+ // For compositor animations do an additional check that they match
+ // the value calculated on the main thread
+ var computedValue = normalize(computedStr);
+ if (computedValue === null) {
+ ok(
+ false,
+ desc +
+ ": test framework should parse computed style" +
+ " - got " +
+ computedStr
+ );
+ return;
+ }
+ okOrTodo(
+ compare(computedValue, actualValue, 0.0),
+ desc +
+ ": OMTA style and computed style should be equal" +
+ " - OMTA " +
+ actualStr +
+ ", computed " +
+ computedStr
+ );
+ }
+ };
+
+ window.matricesRoughlyEqual = function (a, b, tolerance) {
+ // Error handle if a or b is invalid.
+ if (!a || !b) {
+ return false;
+ }
+
+ tolerance = tolerance || 0.00011;
+ for (var i = 0; i < 4; i++) {
+ for (var j = 0; j < 4; j++) {
+ var diff = Math.abs(a[i][j] - b[i][j]);
+ if (diff > tolerance || isNaN(diff)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ // Converts something representing an transform into a 3d matrix in
+ // column-major order.
+ // The following are supported:
+ // "matrix(...)"
+ // "matrix3d(...)"
+ // [ 1, 0, 0, ... ]
+ // { a: 1, ty: 23 } etc.
+ window.convertTo3dMatrix = function (matrixLike) {
+ if (typeof matrixLike == "string") {
+ return convertStringTo3dMatrix(matrixLike);
+ } else if (Array.isArray(matrixLike)) {
+ return convertArrayTo3dMatrix(matrixLike);
+ } else if (typeof matrixLike == "object") {
+ return convertObjectTo3dMatrix(matrixLike);
+ }
+ return null;
+ };
+
+ // In future most of these methods should be able to be replaced
+ // with DOMMatrix
+ window.isInvertible = function (matrix) {
+ return getDeterminant(matrix) != 0;
+ };
+
+ // Converts strings of the format "matrix(...)" and "matrix3d(...)" to a 3d
+ // matrix
+ function convertStringTo3dMatrix(str) {
+ if (str == "none") {
+ return convertArrayTo3dMatrix([1, 0, 0, 1, 0, 0]);
+ }
+ var result = str.match("^matrix(3d)?\\(");
+ if (result === null) {
+ return null;
+ }
+
+ return convertArrayTo3dMatrix(
+ str
+ .substring(result[0].length, str.length - 1)
+ .split(",")
+ .map(function (component) {
+ return Number(component);
+ })
+ );
+ }
+
+ // Takes an array of numbers of length 6 (2d matrix) or 16 (3d matrix)
+ // representing a matrix specified in column-major order and returns a 3d
+ // matrix represented as an array of arrays
+ function convertArrayTo3dMatrix(array) {
+ if (array.length == 6) {
+ return convertObjectTo3dMatrix({
+ a: array[0],
+ b: array[1],
+ c: array[2],
+ d: array[3],
+ e: array[4],
+ f: array[5],
+ });
+ } else if (array.length == 16) {
+ return [
+ array.slice(0, 4),
+ array.slice(4, 8),
+ array.slice(8, 12),
+ array.slice(12, 16),
+ ];
+ }
+ return null;
+ }
+
+ // Return the first defined value in args.
+ function defined(...args) {
+ return args.find(arg => typeof arg !== "undefined");
+ }
+
+ // Takes an object of the form { a: 1.1, e: 23 } and builds up a 3d matrix
+ // with unspecified values filled in with identity values.
+ function convertObjectTo3dMatrix(obj) {
+ return [
+ [
+ defined(obj.a, obj.sx, obj.m11, 1),
+ obj.b || obj.m12 || 0,
+ obj.m13 || 0,
+ obj.m14 || 0,
+ ],
+ [
+ obj.c || obj.m21 || 0,
+ defined(obj.d, obj.sy, obj.m22, 1),
+ obj.m23 || 0,
+ obj.m24 || 0,
+ ],
+ [obj.m31 || 0, obj.m32 || 0, defined(obj.sz, obj.m33, 1), obj.m34 || 0],
+ [
+ obj.e || obj.tx || obj.m41 || 0,
+ obj.f || obj.ty || obj.m42 || 0,
+ obj.tz || obj.m43 || 0,
+ defined(obj.m44, 1),
+ ],
+ ];
+ }
+
+ function convert3dMatrixToString(matrix) {
+ if (is2d(matrix)) {
+ return (
+ "matrix(" +
+ [
+ matrix[0][0],
+ matrix[0][1],
+ matrix[1][0],
+ matrix[1][1],
+ matrix[3][0],
+ matrix[3][1],
+ ].join(", ") +
+ ")"
+ );
+ }
+ return (
+ "matrix3d(" +
+ matrix
+ .reduce(function (outer, inner) {
+ return outer.concat(inner);
+ })
+ .join(", ") +
+ ")"
+ );
+ }
+
+ function is2d(matrix) {
+ return (
+ matrix[0][2] === 0 &&
+ matrix[0][3] === 0 &&
+ matrix[1][2] === 0 &&
+ matrix[1][3] === 0 &&
+ matrix[2][0] === 0 &&
+ matrix[2][1] === 0 &&
+ matrix[2][2] === 1 &&
+ matrix[2][3] === 0 &&
+ matrix[3][2] === 0 &&
+ matrix[3][3] === 1
+ );
+ }
+
+ function getDeterminant(matrix) {
+ if (is2d(matrix)) {
+ return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
+ }
+
+ return (
+ matrix[0][3] * matrix[1][2] * matrix[2][1] * matrix[3][0] -
+ matrix[0][2] * matrix[1][3] * matrix[2][1] * matrix[3][0] -
+ matrix[0][3] * matrix[1][1] * matrix[2][2] * matrix[3][0] +
+ matrix[0][1] * matrix[1][3] * matrix[2][2] * matrix[3][0] +
+ matrix[0][2] * matrix[1][1] * matrix[2][3] * matrix[3][0] -
+ matrix[0][1] * matrix[1][2] * matrix[2][3] * matrix[3][0] -
+ matrix[0][3] * matrix[1][2] * matrix[2][0] * matrix[3][1] +
+ matrix[0][2] * matrix[1][3] * matrix[2][0] * matrix[3][1] +
+ matrix[0][3] * matrix[1][0] * matrix[2][2] * matrix[3][1] -
+ matrix[0][0] * matrix[1][3] * matrix[2][2] * matrix[3][1] -
+ matrix[0][2] * matrix[1][0] * matrix[2][3] * matrix[3][1] +
+ matrix[0][0] * matrix[1][2] * matrix[2][3] * matrix[3][1] +
+ matrix[0][3] * matrix[1][1] * matrix[2][0] * matrix[3][2] -
+ matrix[0][1] * matrix[1][3] * matrix[2][0] * matrix[3][2] -
+ matrix[0][3] * matrix[1][0] * matrix[2][1] * matrix[3][2] +
+ matrix[0][0] * matrix[1][3] * matrix[2][1] * matrix[3][2] +
+ matrix[0][1] * matrix[1][0] * matrix[2][3] * matrix[3][2] -
+ matrix[0][0] * matrix[1][1] * matrix[2][3] * matrix[3][2] -
+ matrix[0][2] * matrix[1][1] * matrix[2][0] * matrix[3][3] +
+ matrix[0][1] * matrix[1][2] * matrix[2][0] * matrix[3][3] +
+ matrix[0][2] * matrix[1][0] * matrix[2][1] * matrix[3][3] -
+ matrix[0][0] * matrix[1][2] * matrix[2][1] * matrix[3][3] -
+ matrix[0][1] * matrix[1][0] * matrix[2][2] * matrix[3][3] +
+ matrix[0][0] * matrix[1][1] * matrix[2][2] * matrix[3][3]
+ );
+ }
+})();
+
+//----------------------------------------------------------------------
+//
+// Promise wrappers for paint_listener.js
+//
+//----------------------------------------------------------------------
+
+// Returns a Promise that resolves once all paints have completed
+function waitForPaints() {
+ return new Promise(function (resolve, reject) {
+ waitForAllPaints(resolve);
+ });
+}
+
+// As with waitForPaints but also flushes pending style changes before waiting
+function waitForPaintsFlushed() {
+ return new Promise(function (resolve, reject) {
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+function waitForVisitedLinkColoring(visitedLink, waitProperty, waitValue) {
+ function checkLink(resolve) {
+ if (
+ SpecialPowers.DOMWindowUtils.getVisitedDependentComputedStyle(
+ visitedLink,
+ "",
+ waitProperty
+ ) == waitValue
+ ) {
+ // Our link has been styled as visited. Resolve.
+ resolve(true);
+ } else {
+ // Our link is not yet styled as visited. Poll for completion.
+ setTimeout(checkLink, 0, resolve);
+ }
+ }
+ return new Promise(function (resolve, reject) {
+ checkLink(resolve);
+ });
+}
diff --git a/layout/style/test/browser.toml b/layout/style/test/browser.toml
new file mode 100644
index 0000000000..7c16123562
--- /dev/null
+++ b/layout/style/test/browser.toml
@@ -0,0 +1,19 @@
+[DEFAULT]
+support-files = [
+ "bug453896_iframe.html",
+ "media_queries_iframe.html",
+ "mapped.css",
+ "mapped.css^headers^",
+ "mapped2.css",
+ "mapped2.css^headers^",
+ "sourcemap_css.html"
+]
+
+["browser_bug453896.js"]
+
+["browser_sourcemap.js"]
+
+["browser_sourcemap_comment.js"]
+
+["browser_sourceurl_comment.js"]
+
diff --git a/layout/style/test/browser_bug453896.js b/layout/style/test/browser_bug453896.js
new file mode 100644
index 0000000000..6b8e180c38
--- /dev/null
+++ b/layout/style/test/browser_bug453896.js
@@ -0,0 +1,16 @@
+add_task(async function () {
+ let uri = getRootDirectory(gTestPath) + "bug453896_iframe.html";
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: uri,
+ },
+ function (browser) {
+ return SpecialPowers.spawn(browser, [], async function () {
+ var fake_window = { ok: ok };
+ content.wrappedJSObject.run(fake_window);
+ });
+ }
+ );
+});
diff --git a/layout/style/test/browser_sourcemap.js b/layout/style/test/browser_sourcemap.js
new file mode 100644
index 0000000000..56ad067818
--- /dev/null
+++ b/layout/style/test/browser_sourcemap.js
@@ -0,0 +1,41 @@
+add_task(async function () {
+ let uri = "http://example.com/browser/layout/style/test/sourcemap_css.html";
+ info(`URI is ${uri}`);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: uri,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], function () {
+ let seenSheets = 0;
+
+ for (let i = 0; i < content.document.styleSheets.length; ++i) {
+ let sheet = content.document.styleSheets[i];
+
+ info(`Checking ${sheet.href}`);
+ if (/mapped\.css/.test(sheet.href)) {
+ is(
+ sheet.sourceMapURL,
+ "mapped.css.map",
+ "X-SourceMap header took effect"
+ );
+ seenSheets |= 1;
+ } else if (/mapped2\.css/.test(sheet.href)) {
+ is(
+ sheet.sourceMapURL,
+ "mapped2.css.map",
+ "SourceMap header took effect"
+ );
+ seenSheets |= 2;
+ } else {
+ ok(false, "sheet does not have source map URL");
+ }
+ }
+
+ is(seenSheets, 3, "seen all source-mapped sheets");
+ });
+ }
+ );
+});
diff --git a/layout/style/test/browser_sourcemap_comment.js b/layout/style/test/browser_sourcemap_comment.js
new file mode 100644
index 0000000000..e0bbff8de4
--- /dev/null
+++ b/layout/style/test/browser_sourcemap_comment.js
@@ -0,0 +1,47 @@
+add_task(async function () {
+ // Test text and expected results.
+ let test_cases = [
+ ["/*# sourceMappingURL=here*/", "here"],
+ ["/*# sourceMappingURL=here */", "here"],
+ ["/*@ sourceMappingURL=here*/", "here"],
+ ["/*@ sourceMappingURL=there*/ /*# sourceMappingURL=here*/", "here"],
+ ["/*# sourceMappingURL=here there */", "here"],
+
+ ["/*# sourceMappingURL= here */", ""],
+ ["/*# sourceMappingURL=*/", ""],
+ ["/*# sourceMappingUR=here */", ""],
+ ["/*! sourceMappingURL=here */", ""],
+ ["/*# sourceMappingURL = here */", ""],
+ ["/* # sourceMappingURL=here */", ""],
+ ];
+
+ let page = "<!DOCTYPE HTML>\n<html>\n<head>\n";
+ for (let i = 0; i < test_cases.length; ++i) {
+ page += `<style type="text/css"> #x${i} { color: red; }${test_cases[i][0]}</style>\n`;
+ }
+ page += "</head><body>some text</body></html>";
+
+ let uri = "data:text/html;base64," + btoa(page);
+ info(`URI is ${uri}`);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: uri,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [test_cases], function (tests) {
+ for (let i = 0; i < content.document.styleSheets.length; ++i) {
+ let sheet = content.document.styleSheets[i];
+
+ info(`Checking sheet #${i}`);
+ is(
+ sheet.sourceMapURL,
+ tests[i][1],
+ `correct source map for sheet ${i}`
+ );
+ }
+ });
+ }
+ );
+});
diff --git a/layout/style/test/browser_sourceurl_comment.js b/layout/style/test/browser_sourceurl_comment.js
new file mode 100644
index 0000000000..9baf6c9ce5
--- /dev/null
+++ b/layout/style/test/browser_sourceurl_comment.js
@@ -0,0 +1,43 @@
+add_task(async function () {
+ // Test text and expected results.
+ let test_cases = [
+ ["/*# sourceURL=here*/", "here"],
+ ["/*# sourceURL=here */", "here"],
+ ["/*@ sourceURL=here*/", "here"],
+ ["/*@ sourceURL=there*/ /*# sourceURL=here*/", "here"],
+ ["/*# sourceURL=here there */", "here"],
+
+ ["/*# sourceURL= here */", ""],
+ ["/*# sourceURL=*/", ""],
+ ["/*# sourceUR=here */", ""],
+ ["/*! sourceURL=here */", ""],
+ ["/*# sourceURL = here */", ""],
+ ["/* # sourceURL=here */", ""],
+ ];
+
+ let page = "<!DOCTYPE HTML>\n<html>\n<head>\n";
+ for (let i = 0; i < test_cases.length; ++i) {
+ page += `<style type="text/css"> #x${i} { color: red; }${test_cases[i][0]}</style>\n`;
+ }
+ page += "</head><body>some text</body></html>";
+
+ let uri = "data:text/html;base64," + btoa(page);
+ info(`URI is ${uri}`);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: uri,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [test_cases], function (tests) {
+ for (let i = 0; i < content.document.styleSheets.length; ++i) {
+ let sheet = content.document.styleSheets[i];
+
+ info(`Checking sheet #${i}`);
+ is(sheet.sourceURL, tests[i][1], `correct source URL for sheet ${i}`);
+ }
+ });
+ }
+ );
+});
diff --git a/layout/style/test/bug1382568-iframe.html b/layout/style/test/bug1382568-iframe.html
new file mode 100644
index 0000000000..b1448703e5
--- /dev/null
+++ b/layout/style/test/bug1382568-iframe.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<iframe src="http://example.com/doesnt-matter-because-it-gets-blocked-due-to-mixed-content"></iframe>
+<script>
+window.addEventListener('load', function(){
+ window[0].document.body.innerText;
+ window.parent.postMessage({ result: "ok" }, "*");
+});
+</script>
diff --git a/layout/style/test/bug1729861.js b/layout/style/test/bug1729861.js
new file mode 100644
index 0000000000..dbfa060ab1
--- /dev/null
+++ b/layout/style/test/bug1729861.js
@@ -0,0 +1,81 @@
+// # Bug 1729861
+
+// Expected values. Format: [name, pref_off_value, pref_on_value]
+var expected_values = [
+ [
+ "device-aspect-ratio",
+ screen.width + "/" + screen.height,
+ window.innerWidth + "/" + window.innerHeight,
+ ],
+ ["device-height", screen.height + "px", window.innerHeight + "px"],
+ ["device-width", screen.width + "px", window.innerWidth + "px"],
+];
+
+// Create a line containing a CSS media query and a rule to set the bg color.
+var mediaQueryCSSLine = function (key, val, color) {
+ return (
+ "@media (" +
+ key +
+ ": " +
+ val +
+ ") { #" +
+ key +
+ " { background-color: " +
+ color +
+ "; } }\n"
+ );
+};
+
+var green = "rgb(0, 128, 0)";
+var blue = "rgb(0, 0, 255)";
+
+// Set a pref value asynchronously, returning a promise that resolves
+// when it succeeds.
+var pushPref = function (key, value) {
+ return SpecialPowers.pushPrefEnv({ set: [[key, value]] });
+};
+
+// Set the resistFingerprinting pref to the given value, and then check that the background
+// color has been updated properly as a result of re-evaluating the media queries.
+var checkColorForPref = async function (setting, testDivs, expectedColor) {
+ await pushPref("privacy.resistFingerprinting", setting);
+ for (let div of testDivs) {
+ let color = window.getComputedStyle(div).backgroundColor;
+ is(color, expectedColor, "background for '" + div.id + "' is " + color);
+ }
+};
+
+var test = async function () {
+ // If the "off" and "on" expected values are the same, we can't actually
+ // test anything here. (Might this be the case on mobile?)
+ let skipTest = false;
+ for (let [key, offVal, onVal] of expected_values) {
+ if (offVal == onVal) {
+ todo(false, "Unable to test because '" + key + "' is invariant");
+ return;
+ }
+ }
+
+ let css =
+ ".test { margin: 1em; padding: 1em; color: white; width: max-content; font: 2em monospace }\n";
+
+ // Create a test element for each of the media queries we're checking, and append the matching
+ // "on" and "off" media queries to the CSS.
+ let testDivs = [];
+ for (let [key, offVal, onVal] of expected_values) {
+ let div = document.createElement("div");
+ div.textContent = key;
+ div.setAttribute("class", "test");
+ div.setAttribute("id", key);
+ testDivs.push(div);
+ document.getElementById("display").appendChild(div);
+ css += mediaQueryCSSLine(key, onVal, "green");
+ css += mediaQueryCSSLine(key, offVal, "blue");
+ }
+ document.getElementById("test-css").textContent = css;
+
+ // Check that the test elements change color as expected when we flip the resistFingerprinting pref.
+ await checkColorForPref(true, testDivs, green);
+ await checkColorForPref(false, testDivs, blue);
+ await checkColorForPref(true, testDivs, green);
+};
diff --git a/layout/style/test/bug453896_iframe.html b/layout/style/test/bug453896_iframe.html
new file mode 100644
index 0000000000..e5414cc0df
--- /dev/null
+++ b/layout/style/test/bug453896_iframe.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US">
+<head>
+ <title>Bug 453896 Test middle frame</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <script type="application/javascript">
+
+function run(test_window)
+{
+ var subdoc = document.getElementById("subdoc").contentDocument;
+ var subwin = document.getElementById("subdoc").contentWindow;
+ var style = subdoc.getElementById("style");
+ var iframe_style = document.getElementById("subdoc").style;
+ var body_cs = subdoc.defaultView.getComputedStyle(subdoc.body);
+
+ function query_applies(q) {
+ style.setAttribute("media", q);
+ return body_cs.getPropertyValue("text-decoration-line") == "underline";
+ }
+
+ function should_apply(q) {
+ test_window.ok(query_applies(q), q + " should apply");
+ }
+
+ function should_not_apply(q) {
+ test_window.ok(!query_applies(q), q + " should not apply");
+ }
+
+ // in this test, assume the common underlying implementation is correct
+ let width_val = 157; // pick two not-too-round numbers
+ let height_val = 182;
+ iframe_style.width = width_val + "px";
+ iframe_style.height = height_val + "px";
+ for (let [feature, value] of
+ Object.entries({ "width": width_val, "height": height_val })) {
+ should_apply("all and (" + feature + ": " + value + "px)");
+ should_not_apply("all and (" + feature + ": " + (value + 1) + "px)");
+ should_not_apply("all and (" + feature + ": " + (value - 1) + "px)");
+ }
+
+ iframe_style.width = "0";
+ should_apply("all and (height)");
+ should_not_apply("all and (width)");
+ iframe_style.height = "0";
+ should_not_apply("all and (height)");
+ should_not_apply("all and (width)");
+ should_apply("all and (device-height)");
+ should_apply("all and (device-width)");
+ iframe_style.width = width_val + "px";
+ should_not_apply("all and (height)");
+ should_apply("all and (width)");
+ iframe_style.height = height_val + "px";
+ should_apply("all and (height)");
+ should_apply("all and (width)");
+}
+
+ </script>
+</head>
+<body>
+
+<iframe id="subdoc" src="media_queries_iframe.html"></iframe>
+
+</body>
+</html>
diff --git a/layout/style/test/bug517224.sjs b/layout/style/test/bug517224.sjs
new file mode 100644
index 0000000000..bd1fe4f336
--- /dev/null
+++ b/layout/style/test/bug517224.sjs
@@ -0,0 +1,27 @@
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ switch (request.queryString) {
+ case "reset":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ setState("imageloaded", "");
+ break;
+ case "image":
+ setState("imageloaded", "imageloaded");
+ response.setStatusLine("1.1", 302, "Found");
+ // redirect to a solid blue image
+ response.setHeader(
+ "Location",
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12NgYPgPAAEDAQDZqt2zAAAAAElFTkSuQmCC"
+ );
+ response.setHeader("Content-Type", "text/plain", false);
+ break;
+ case "result":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ var state = getState("imageloaded");
+ response.write(
+ "is('" + state + "', '', 'image should not have been loaded')\n"
+ );
+ response.write("SimpleTest.finish()");
+ break;
+ }
+}
diff --git a/layout/style/test/bug732209-css.sjs b/layout/style/test/bug732209-css.sjs
new file mode 100644
index 0000000000..a572cf3942
--- /dev/null
+++ b/layout/style/test/bug732209-css.sjs
@@ -0,0 +1,30 @@
+function handleRequest(request, response) {
+ // First item will be the ID; other items are optional
+ var query = request.queryString.split(/&/);
+
+ response.setHeader("Content-Type", "text/css", false);
+
+ if (query.indexOf("cors-anonymous") != -1) {
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ } else if (
+ query.indexOf("cors-credentials") != -1 &&
+ request.hasHeader("Origin")
+ ) {
+ response.setHeader(
+ "Access-Control-Allow-Origin",
+ request.getHeader("Origin"),
+ false
+ );
+ response.setHeader("Access-Control-Allow-Credentials", "true", false);
+ }
+
+ response.write(
+ "#" +
+ query[0] +
+ " { color: green !important }" +
+ "\n" +
+ "#" +
+ query[0] +
+ ".reverse { color: red !important }"
+ );
+}
diff --git a/layout/style/test/ccd-quirks.html b/layout/style/test/ccd-quirks.html
new file mode 100644
index 0000000000..570b5d5a1b
--- /dev/null
+++ b/layout/style/test/ccd-quirks.html
@@ -0,0 +1,129 @@
+<!-- Intentionally in quirks mode. -->
+<html><head>
+<!-- baseline -->
+<style>
+body, html { margin: 0; padding: 0; overflow: hidden }
+div {
+ width: 60px;
+ height: 20px;
+ position: relative;
+}
+p {
+ position: absolute;
+ top: 2px; left: 2px;
+ width: 16px;
+ height: 16px;
+ margin: 0;
+ padding: 0;
+}
+p + p { left: 22px }
+
+#IA1i, #IA1l, #IA2i, #IA2l, #IB1i, #IB1l, #IB2i, #IB2l,
+#IC1i, #IC1l, #IC2i, #IC2l, #ID1i, #ID1l, #ID2i, #ID2l,
+#JA1i, #JA1l, #JA2i, #JA2l, #JD1i, #JD1l, #JD2i, #JD2l
+ { background-color: red }
+
+#JB1i, #JB1l, #JC1i, #JC1l,
+#JB2i, #JB2l, #JC2i, #JC2l
+ { background-color: lime }
+
+#IA3i, #IA3l, #IB3i, #IB3l, #IC3i, #IC3l, #ID3i, #ID3l,
+#JA3i, #JA3l, #JB3i, #JB3l, #JC3i, #JC3l, #JD3i, #JD3l
+ { background-color: lime }
+</style>
+
+<!-- @import rules -->
+<style>
+@import url("ccd.sjs?IA1iq");
+@import url("ccd.sjs?IA2iq");
+@import url("ccd.sjs?IA3iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB1iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB2iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB3iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3iq");
+@import url("ccd.sjs?JA1iq");
+@import url("ccd.sjs?JA2iq");
+@import url("ccd.sjs?JA3iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB1iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB2iq");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB3iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2iq");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2iq");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3iq");
+</style>
+
+<!-- link directives -->
+<link rel="stylesheet" href="ccd.sjs?IA1lq">
+<link rel="stylesheet" href="ccd.sjs?IA2lq">
+<link rel="stylesheet" href="ccd.sjs?IA3lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB1lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB2lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB3lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3lq">
+<link rel="stylesheet" href="ccd.sjs?JA1lq">
+<link rel="stylesheet" href="ccd.sjs?JA2lq">
+<link rel="stylesheet" href="ccd.sjs?JA3lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB1lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB2lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB3lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2lq">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2lq">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3lq">
+
+</head><body>
+<div></div>
+<div></div>
+<div><p id="IA1i"></p><p id="IA1l"></p></div>
+<div><p id="IA2i"></p><p id="IA2l"></p></div>
+<div><p id="IA3i"></p><p id="IA3l"></p></div>
+<div></div>
+<div><p id="IB1i"></p><p id="IB1l"></p></div>
+<div><p id="IB2i"></p><p id="IB2l"></p></div>
+<div><p id="IB3i"></p><p id="IB3l"></p></div>
+<div></div>
+<div><p id="IC1i"></p><p id="IC1l"></p></div>
+<div><p id="IC2i"></p><p id="IC2l"></p></div>
+<div><p id="IC3i"></p><p id="IC3l"></p></div>
+<div></div>
+<div><p id="ID1i"></p><p id="ID1l"></p></div>
+<div><p id="ID2i"></p><p id="ID2l"></p></div>
+<div><p id="ID3i"></p><p id="ID3l"></p></div>
+<div></div>
+<div></div>
+<div><p id="JA1i"></p><p id="JA1l"></p></div>
+<div><p id="JA2i"></p><p id="JA2l"></p></div>
+<div><p id="JA3i"></p><p id="JA3l"></p></div>
+<div></div>
+<div><p id="JB1i"></p><p id="JB1l"></p></div>
+<div><p id="JB2i"></p><p id="JB2l"></p></div>
+<div><p id="JB3i"></p><p id="JB3l"></p></div>
+<div></div>
+<div><p id="JC1i"></p><p id="JC1l"></p></div>
+<div><p id="JC2i"></p><p id="JC2l"></p></div>
+<div><p id="JC3i"></p><p id="JC3l"></p></div>
+<div></div>
+<div><p id="JD1i"></p><p id="JD1l"></p></div>
+<div><p id="JD2i"></p><p id="JD2l"></p></div>
+<div><p id="JD3i"></p><p id="JD3l"></p></div>
+<script>
+ window.onload = function() {
+ window.parent.quirksLoaded()
+ }
+</script>
+</body></html>
diff --git a/layout/style/test/ccd-standards.html b/layout/style/test/ccd-standards.html
new file mode 100644
index 0000000000..693402df4c
--- /dev/null
+++ b/layout/style/test/ccd-standards.html
@@ -0,0 +1,128 @@
+<!doctype html>
+<html><head>
+<!-- baseline -->
+<style>
+body, html { margin: 0; padding: 0; overflow: hidden }
+div {
+ width: 60px;
+ height: 20px;
+ position: relative;
+}
+p {
+ position: absolute;
+ top: 2px; left: 2px;
+ width: 16px;
+ height: 16px;
+ margin: 0;
+ padding: 0;
+}
+p + p { left: 22px }
+
+#IA1i, #IA1l, #IA2i, #IA2l, #IB1i, #IB1l, #IB2i, #IB2l,
+#IC1i, #IC1l, #IC2i, #IC2l, #ID1i, #ID1l, #ID2i, #ID2l
+ { background-color: red }
+
+#JA1i, #JA1l, #JA2i, #JA2l, #JB1i, #JB1l, #JB2i, #JB2l,
+#JC1i, #JC1l, #JC2i, #JC2l, #JD1i, #JD1l, #JD2i, #JD2l
+ { background-color: lime }
+
+#IA3i, #IA3l, #IB3i, #IB3l, #IC3i, #IC3l, #ID3i, #ID3l,
+#JA3i, #JA3l, #JB3i, #JB3l, #JC3i, #JC3l, #JD3i, #JD3l
+ { background-color: lime }
+</style>
+
+<!-- @import rules -->
+<style>
+@import url("ccd.sjs?IA1is");
+@import url("ccd.sjs?IA2is");
+@import url("ccd.sjs?IA3is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB1is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB2is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB3is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3is");
+@import url("ccd.sjs?JA1is");
+@import url("ccd.sjs?JA2is");
+@import url("ccd.sjs?JA3is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB1is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB2is");
+@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB3is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2is");
+@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2is");
+@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3is");
+</style>
+
+<!-- link directives -->
+<link rel="stylesheet" href="ccd.sjs?IA1ls">
+<link rel="stylesheet" href="ccd.sjs?IA2ls">
+<link rel="stylesheet" href="ccd.sjs?IA3ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB1ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB2ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB3ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3ls">
+<link rel="stylesheet" href="ccd.sjs?JA1ls">
+<link rel="stylesheet" href="ccd.sjs?JA2ls">
+<link rel="stylesheet" href="ccd.sjs?JA3ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB1ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB2ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB3ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2ls">
+<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2ls">
+<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3ls">
+
+</head><body>
+<div></div>
+<div></div>
+<div><p id="IA1i"></p><p id="IA1l"></p></div>
+<div><p id="IA2i"></p><p id="IA2l"></p></div>
+<div><p id="IA3i"></p><p id="IA3l"></p></div>
+<div></div>
+<div><p id="IB1i"></p><p id="IB1l"></p></div>
+<div><p id="IB2i"></p><p id="IB2l"></p></div>
+<div><p id="IB3i"></p><p id="IB3l"></p></div>
+<div></div>
+<div><p id="IC1i"></p><p id="IC1l"></p></div>
+<div><p id="IC2i"></p><p id="IC2l"></p></div>
+<div><p id="IC3i"></p><p id="IC3l"></p></div>
+<div></div>
+<div><p id="ID1i"></p><p id="ID1l"></p></div>
+<div><p id="ID2i"></p><p id="ID2l"></p></div>
+<div><p id="ID3i"></p><p id="ID3l"></p></div>
+<div></div>
+<div></div>
+<div><p id="JA1i"></p><p id="JA1l"></p></div>
+<div><p id="JA2i"></p><p id="JA2l"></p></div>
+<div><p id="JA3i"></p><p id="JA3l"></p></div>
+<div></div>
+<div><p id="JB1i"></p><p id="JB1l"></p></div>
+<div><p id="JB2i"></p><p id="JB2l"></p></div>
+<div><p id="JB3i"></p><p id="JB3l"></p></div>
+<div></div>
+<div><p id="JC1i"></p><p id="JC1l"></p></div>
+<div><p id="JC2i"></p><p id="JC2l"></p></div>
+<div><p id="JC3i"></p><p id="JC3l"></p></div>
+<div></div>
+<div><p id="JD1i"></p><p id="JD1l"></p></div>
+<div><p id="JD2i"></p><p id="JD2l"></p></div>
+<div><p id="JD3i"></p><p id="JD3l"></p></div>
+<script>
+ window.onload = function() {
+ window.parent.standardsLoaded();
+ }
+</script>
+</body></html>
diff --git a/layout/style/test/ccd.sjs b/layout/style/test/ccd.sjs
new file mode 100644
index 0000000000..22a4294d59
--- /dev/null
+++ b/layout/style/test/ccd.sjs
@@ -0,0 +1,71 @@
+const DEBUG_all_valid = false;
+const DEBUG_all_stub = false;
+
+function handleRequest(request, response) {
+ // Decode the query string to know what test we're doing.
+
+ // character 1: 'I' = text/css response, 'J' = text/html response
+ let responseCSS = request.queryString[0] == "I";
+
+ // character 2: redirection type - we only care about whether we're
+ // ultimately same-origin with the requesting document ('A', 'D') or
+ // not ('B', 'C').
+ let sameOrigin =
+ request.queryString[1] == "A" || request.queryString[1] == "D";
+
+ // character 3: '1' = syntactically valid, '2' = invalid, '3' = http error
+ let malformed = request.queryString[2] == "2";
+ let httpError = request.queryString[2] == "3";
+
+ // character 4: loaded with <link> or @import (no action required)
+
+ // character 5: loading document mode: 'q' = quirks, 's' = standards
+ let quirksMode = request.queryString[4] == "q";
+
+ // Our response contains a CSS rule that selects an element whose
+ // ID is the first four characters of the query string.
+ let selector = "#" + request.queryString.substring(0, 4);
+
+ // "Malformed" responses wrap the CSS rule in the construct
+ // <html>{} ... </html>
+ // This mimics what the CSS parser might see if an actual HTML
+ // document were fed to it. Because CSS parsers recover from
+ // errors by skipping tokens until they find something
+ // recognizable, a style rule appearing where I wrote '...' above
+ // will be honored!
+ let leader = malformed ? "<html>{}" : "";
+ let trailer = malformed ? "</html>" : "";
+
+ // Standards mode documents will ignore the style sheet if it is being
+ // served as text/html (regardless of its contents). Quirks mode
+ // documents will ignore the style sheet if it is being served as
+ // text/html _and_ it is not same-origin. Regardless, style sheets
+ // are ignored if they come as the body of an HTTP error response.
+ //
+ // Style sheets that should be ignored paint the element red; those
+ // that should be honored paint it lime.
+ let color =
+ (responseCSS || (quirksMode && sameOrigin)) && !httpError ? "lime" : "red";
+
+ // For debugging the test itself, we have the capacity to make every style
+ // sheet well-formed, or every style sheet do nothing.
+ if (DEBUG_all_valid) {
+ // In this mode, every test chip should turn blue.
+ response.setHeader("Content-Type", "text/css");
+ response.write(selector + "{background-color:blue}\n");
+ } else if (DEBUG_all_stub) {
+ // In this mode, every test chip for a case where the true test
+ // sheet would be honored, should turn red.
+ response.setHeader("Content-Type", "text/css");
+ response.write(selector + "{}\n");
+ } else {
+ // Normal operation.
+ if (httpError) {
+ response.setStatusLine(request.httpVersion, 500, "Internal Server Error");
+ }
+ response.setHeader("Content-Type", responseCSS ? "text/css" : "text/html");
+ response.write(
+ leader + selector + "{background-color:" + color + "}" + trailer + "\n"
+ );
+ }
+}
diff --git a/layout/style/test/chrome/bug418986-2.js b/layout/style/test/chrome/bug418986-2.js
new file mode 100644
index 0000000000..6d2af235c3
--- /dev/null
+++ b/layout/style/test/chrome/bug418986-2.js
@@ -0,0 +1,318 @@
+// # Bug 418986, part 2.
+
+const is_chrome_window = window.location.protocol === "chrome:";
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+// Expected values. Format: [name, pref_off_value, pref_on_value]
+// If pref_*_value is an array with two values, then we will match
+// any value in between those two values. If a value is null, then
+// we skip the media query.
+var expected_values = [
+ ["color", null, 8],
+ ["color-index", null, 0],
+ ["aspect-ratio", null, window.innerWidth + "/" + window.innerHeight],
+ [
+ "device-aspect-ratio",
+ screen.width + "/" + screen.height,
+ window.innerWidth + "/" + window.innerHeight,
+ ],
+ ["device-height", screen.height + "px", window.innerHeight + "px"],
+ ["device-width", screen.width + "px", window.innerWidth + "px"],
+ ["grid", null, 0],
+ ["height", window.innerHeight + "px", window.innerHeight + "px"],
+ ["monochrome", null, 0],
+ // Square is defined as portrait:
+ [
+ "orientation",
+ null,
+ window.innerWidth > window.innerHeight ? "landscape" : "portrait",
+ ],
+ ["resolution", null, "96dpi"],
+ [
+ "resolution",
+ [
+ 0.999 * window.devicePixelRatio + "dppx",
+ 1.001 * window.devicePixelRatio + "dppx",
+ ],
+ "1dppx",
+ ],
+ ["width", window.innerWidth + "px", window.innerWidth + "px"],
+ ["-moz-device-pixel-ratio", window.devicePixelRatio, 1],
+ [
+ "-moz-device-orientation",
+ screen.width > screen.height ? "landscape" : "portrait",
+ window.innerWidth > window.innerHeight ? "landscape" : "portrait",
+ ],
+];
+
+// These media queries return value 0 or 1 when the pref is off.
+// When the pref is on, they should not match.
+var suppressed_toggles = [
+ // Not available on most OSs.
+ "-moz-scrollbar-end-backward",
+ "-moz-scrollbar-end-forward",
+ "-moz-scrollbar-start-backward",
+ "-moz-scrollbar-start-forward",
+ "-moz-gtk-csd-available",
+ "-moz-gtk-csd-minimize-button",
+ "-moz-gtk-csd-maximize-button",
+ "-moz-gtk-csd-close-button",
+ "-moz-gtk-csd-reversed-placement",
+];
+
+var toggles_enabled_in_content = [];
+
+// Read the current OS.
+var OS = SpecialPowers.Services.appinfo.OS;
+
+// __keyValMatches(key, val)__.
+// Runs a media query and returns true if key matches to val.
+var keyValMatches = (key, val) =>
+ matchMedia("(" + key + ":" + val + ")").matches;
+
+// __testMatch(key, val)__.
+// Attempts to run a media query match for the given key and value.
+// If value is an array of two elements [min max], then matches any
+// value in-between.
+var testMatch = function (key, val) {
+ if (val === null) {
+ return;
+ } else if (Array.isArray(val)) {
+ ok(
+ keyValMatches("min-" + key, val[0]) &&
+ keyValMatches("max-" + key, val[1]),
+ "Expected " + key + " between " + val[0] + " and " + val[1]
+ );
+ } else {
+ ok(keyValMatches(key, val), "Expected " + key + ":" + val);
+ }
+};
+
+// __testToggles(resisting)__.
+// Test whether we are able to match the "toggle" media queries.
+var testToggles = function (resisting) {
+ suppressed_toggles.forEach(function (key) {
+ var exists = keyValMatches(key, 0) || keyValMatches(key, 1);
+ if (!toggles_enabled_in_content.includes(key) && !is_chrome_window) {
+ ok(!exists, key + " should not exist.");
+ } else {
+ ok(exists, key + " should exist.");
+ if (resisting) {
+ ok(
+ keyValMatches(key, 0) && !keyValMatches(key, 1),
+ "Should always match as false"
+ );
+ }
+ }
+ });
+};
+
+// __generateHtmlLines(resisting)__.
+// Create a series of div elements that look like:
+// `<div class='spoof' id='resolution'>resolution</div>`,
+// where each line corresponds to a different media query.
+var generateHtmlLines = function (resisting) {
+ let fragment = document.createDocumentFragment();
+ expected_values.forEach(function ([key, offVal, onVal]) {
+ let val = resisting ? onVal : offVal;
+ if (val) {
+ let div = document.createElementNS(HTML_NS, "div");
+ div.setAttribute("class", "spoof");
+ div.setAttribute("id", key);
+ div.textContent = key;
+ fragment.appendChild(div);
+ }
+ });
+ suppressed_toggles.forEach(function (key) {
+ let div = document.createElementNS(HTML_NS, "div");
+ div.setAttribute("class", "suppress");
+ div.setAttribute("id", key);
+ div.textContent = key;
+ fragment.appendChild(div);
+ });
+ return fragment;
+};
+
+// __cssLine__.
+// Creates a line of css that looks something like
+// `@media (resolution: 1ppx) { .spoof#resolution { background-color: green; } }`.
+var cssLine = function (query, clazz, id, color) {
+ return (
+ "@media " +
+ query +
+ " { ." +
+ clazz +
+ "#" +
+ id +
+ " { background-color: " +
+ color +
+ "; } }\n"
+ );
+};
+
+// __constructQuery(key, val)__.
+// Creates a CSS media query from key and val. If key is an array of
+// two elements, constructs a range query (using min- and max-).
+var constructQuery = function (key, val) {
+ return Array.isArray(val)
+ ? "(min-" + key + ": " + val[0] + ") and (max-" + key + ": " + val[1] + ")"
+ : "(" + key + ": " + val + ")";
+};
+
+// __mediaQueryCSSLine(key, val, color)__.
+// Creates a line containing a CSS media query and a CSS expression.
+var mediaQueryCSSLine = function (key, val, color) {
+ if (val === null) {
+ return "";
+ }
+ return cssLine(constructQuery(key, val), "spoof", key, color);
+};
+
+// __suppressedMediaQueryCSSLine(key, color)__.
+// Creates a CSS line that matches the existence of a
+// media query that is supposed to be suppressed.
+var suppressedMediaQueryCSSLine = function (key, color, suppressed) {
+ let query = "(" + key + ": 0), (" + key + ": 1)";
+ return cssLine(query, "suppress", key, color);
+};
+
+// __generateCSSLines(resisting)__.
+// Creates a series of lines of CSS, each of which corresponds to
+// a different media query. If the query produces a match to the
+// expected value, then the element will be colored green.
+var generateCSSLines = function (resisting) {
+ let lines = ".spoof { background-color: red;}\n";
+ expected_values.forEach(function ([key, offVal, onVal]) {
+ lines += mediaQueryCSSLine(key, resisting ? onVal : offVal, "green");
+ });
+ lines +=
+ ".suppress { background-color: " + (resisting ? "green" : "red") + ";}\n";
+ suppressed_toggles.forEach(function (key) {
+ if (
+ !toggles_enabled_in_content.includes(key) &&
+ !resisting &&
+ !is_chrome_window
+ ) {
+ lines += "#" + key + " { background-color: green; }\n";
+ } else {
+ lines += suppressedMediaQueryCSSLine(key, "green");
+ }
+ });
+ return lines;
+};
+
+// __green__.
+// Returns the computed color style corresponding to green.
+var green = "rgb(0, 128, 0)";
+
+// __testCSS(resisting)__.
+// Creates a series of divs and CSS using media queries to set their
+// background color. If all media queries match as expected, then
+// all divs should have a green background color.
+var testCSS = function (resisting) {
+ document.getElementById("display").appendChild(generateHtmlLines(resisting));
+ document.getElementById("test-css").textContent = generateCSSLines(resisting);
+ let cssTestDivs = document.querySelectorAll(".spoof,.suppress");
+ for (let div of cssTestDivs) {
+ let color = window.getComputedStyle(div).backgroundColor;
+ ok(color === green, "CSS for '" + div.id + "'");
+ }
+};
+
+// __testOSXFontSmoothing(resisting)__.
+// When fingerprinting resistance is enabled, the `getComputedStyle`
+// should always return `undefined` for `MozOSXFontSmoothing`.
+var testOSXFontSmoothing = function (resisting) {
+ let div = document.createElementNS(HTML_NS, "div");
+ div.style.MozOsxFontSmoothing = "unset";
+ document.documentElement.appendChild(div);
+ let readBack = window.getComputedStyle(div).MozOsxFontSmoothing;
+ div.remove();
+ let smoothingPref = SpecialPowers.getBoolPref(
+ "layout.css.osx-font-smoothing.enabled",
+ false
+ );
+ is(
+ readBack,
+ resisting ? "" : smoothingPref ? "auto" : "",
+ "-moz-osx-font-smoothing"
+ );
+};
+
+// __sleep(timeoutMs)__.
+// Returns a promise that resolves after the given timeout.
+var sleep = function (timeoutMs) {
+ return new Promise(function (resolve, reject) {
+ window.setTimeout(resolve);
+ });
+};
+
+// __testMediaQueriesInPictureElements(resisting)__.
+// Test to see if media queries are properly spoofed in picture elements
+// when we are resisting fingerprinting.
+var testMediaQueriesInPictureElements = async function (resisting) {
+ const MATCH = "/tests/layout/style/test/chrome/match.png";
+ let container = document.getElementById("pictures");
+ let testImages = [];
+ for (let [key, offVal, onVal] of expected_values) {
+ let expected = resisting ? onVal : offVal;
+ if (expected) {
+ let picture = document.createElementNS(HTML_NS, "picture");
+ let query = constructQuery(key, expected);
+ ok(matchMedia(query).matches, `${query} should match`);
+
+ let source = document.createElementNS(HTML_NS, "source");
+ source.setAttribute("srcset", MATCH);
+ source.setAttribute("media", query);
+
+ let image = document.createElementNS(HTML_NS, "img");
+ image.setAttribute("title", key + ":" + expected);
+ image.setAttribute("class", "testImage");
+ image.setAttribute("src", "/tests/layout/style/test/chrome/mismatch.png");
+ image.setAttribute("alt", key);
+
+ testImages.push(image);
+
+ picture.appendChild(source);
+ picture.appendChild(image);
+ container.appendChild(picture);
+ }
+ }
+ const matchURI = new URL(MATCH, document.baseURI).href;
+ await sleep(0);
+ for (let testImage of testImages) {
+ is(
+ testImage.currentSrc,
+ matchURI,
+ "Media query '" + testImage.title + "' in picture should match."
+ );
+ }
+};
+
+// __pushPref(key, value)__.
+// Set a pref value asynchronously, returning a promise that resolves
+// when it succeeds.
+var pushPref = function (key, value) {
+ return new Promise(function (resolve, reject) {
+ SpecialPowers.pushPrefEnv({ set: [[key, value]] }, resolve);
+ });
+};
+
+// __test(isContent)__.
+// Run all tests.
+var test = async function (isContent) {
+ for (prefValue of [false, true]) {
+ await pushPref("privacy.resistFingerprinting", prefValue);
+ let resisting = prefValue && isContent;
+ expected_values.forEach(function ([key, offVal, onVal]) {
+ testMatch(key, resisting ? onVal : offVal);
+ });
+ testToggles(resisting);
+ testCSS(resisting);
+ if (OS === "Darwin") {
+ testOSXFontSmoothing(resisting);
+ }
+ await testMediaQueriesInPictureElements(resisting);
+ }
+};
diff --git a/layout/style/test/chrome/bug535806-css.css b/layout/style/test/chrome/bug535806-css.css
new file mode 100644
index 0000000000..bda339f776
--- /dev/null
+++ b/layout/style/test/chrome/bug535806-css.css
@@ -0,0 +1 @@
+fooBar[fooBar] { color: green; }
diff --git a/layout/style/test/chrome/bug535806-html.html b/layout/style/test/chrome/bug535806-html.html
new file mode 100644
index 0000000000..e4395da3f3
--- /dev/null
+++ b/layout/style/test/chrome/bug535806-html.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="bug535806-css.css">
+ </head>
+ <body onload="window.parent.wrappedJSObject.htmlLoaded()">
+ </body>
+</html>
diff --git a/layout/style/test/chrome/bug535806-xul.xhtml b/layout/style/test/chrome/bug535806-xul.xhtml
new file mode 100644
index 0000000000..3d9a82b91e
--- /dev/null
+++ b/layout/style/test/chrome/bug535806-xul.xhtml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="data:text/css,fooBar{color:red;}"?>
+<?xml-stylesheet type="text/css" href="bug535806-css.css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="window.parent.wrappedJSObject.xulLoaded()">
+ <fooBar fooBar="" id="s"/>
+</window>
diff --git a/layout/style/test/chrome/chrome-only-media-queries.js b/layout/style/test/chrome/chrome-only-media-queries.js
new file mode 100644
index 0000000000..aaf313a526
--- /dev/null
+++ b/layout/style/test/chrome/chrome-only-media-queries.js
@@ -0,0 +1,34 @@
+const CHROME_ONLY_TOGGLES = [
+ "-moz-is-glyph",
+ "-moz-print-preview",
+ "-moz-scrollbar-start-backward",
+ "-moz-scrollbar-start-forward",
+ "-moz-scrollbar-end-backward",
+ "-moz-scrollbar-end-forward",
+ "-moz-overlay-scrollbars",
+ "-moz-mac-big-sur-theme",
+ "-moz-menubar-drag",
+ "-moz-windows-accent-color-in-titlebar",
+ "-moz-swipe-animation-enabled",
+ "-moz-gtk-csd-available",
+ "-moz-gtk-csd-minimize-button",
+ "-moz-gtk-csd-maximize-button",
+ "-moz-gtk-csd-close-button",
+ "-moz-gtk-csd-reversed-placement",
+ "-moz-panel-animations",
+];
+
+// Non-parseable queries can be tested directly in
+// `test_chrome_only_media_queries.html`.
+const CHROME_ONLY_QUERIES = [
+ "(-moz-platform: linux)",
+ "(-moz-platform: windows)",
+ "(-moz-platform: macos)",
+ "(-moz-platform: android)",
+ "(-moz-content-prefers-color-scheme: dark)",
+ "(-moz-content-prefers-color-scheme: light)",
+ "(-moz-gtk-theme-family: unknown)",
+ "(-moz-gtk-theme-family: adwaita)",
+ "(-moz-gtk-theme-family: breeze)",
+ "(-moz-gtk-theme-family: yaru)",
+];
diff --git a/layout/style/test/chrome/chrome.toml b/layout/style/test/chrome/chrome.toml
new file mode 100644
index 0000000000..8c4c6045d8
--- /dev/null
+++ b/layout/style/test/chrome/chrome.toml
@@ -0,0 +1,51 @@
+[DEFAULT]
+skip-if = ["os == 'android'"]
+support-files = [
+ "bug418986-2.js",
+ "bug535806-css.css",
+ "bug535806-html.html",
+ "bug535806-xul.xhtml",
+ "hover_helper.html",
+ "match.png",
+ "mismatch.png",
+]
+
+["test_bug418986-2.xhtml"]
+
+["test_bug511909.html"]
+
+["test_bug535806.xhtml"]
+
+["test_bug1157097.html"]
+
+["test_bug1346623.html"]
+
+["test_bug1371453.html"]
+
+["test_chrome_only_media_queries.html"]
+support-files = ["chrome-only-media-queries.js"]
+
+["test_constructable_stylesheets_chrome_only_rules.html"]
+
+["test_display_mode.html"]
+support-files = ["display_mode.html"]
+tags = "fullscreen"
+
+["test_display_mode_reflow.html"]
+support-files = ["display_mode_reflow.html"]
+tags = "fullscreen"
+
+["test_hover.html"]
+skip-if = ["true"] # bug 1346353
+
+["test_moz_document_rules.html"]
+
+["test_moz_document_serialization.html"]
+
+["test_scrollbar_inline_size.html"]
+
+["test_stylesheet_clone_import_rule.html"]
+support-files = [
+ "import_useless1.css",
+ "import_useless2.css",
+]
diff --git a/layout/style/test/chrome/display_mode.html b/layout/style/test/chrome/display_mode.html
new file mode 100644
index 0000000000..a4a0afb57e
--- /dev/null
+++ b/layout/style/test/chrome/display_mode.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1104916
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+ for (var n of imports) {
+ window[n] = window.opener.wrappedJSObject[n];
+ }
+
+ /** Test for Display Mode **/
+
+ function waitOneEvent(element, name) {
+ return new Promise(function(resolve, reject) {
+ element.addEventListener(name, function() {
+ resolve();
+ }, {once: true});
+ });
+ }
+
+ function promiseNextTick() {
+ return new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ async function test_task() {
+ var iframe = document.getElementById("subdoc");
+ var subdoc = iframe.contentDocument;
+ var style = subdoc.getElementById("style");
+ var bodyComputedStyled = subdoc.defaultView.getComputedStyle(subdoc.body);
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ function queryApplies(q) {
+ style.setAttribute("media", q);
+ return bodyComputedStyled.getPropertyValue("text-decoration-line") ==
+ "underline";
+ }
+
+ function shouldApply(q) {
+ ok(queryApplies(q), q + " should apply");
+ }
+
+ function shouldNotApply(q) {
+ ok(!queryApplies(q), q + " should not apply");
+ }
+
+ function setDisplayMode(mode) {
+ window.browsingContext.top.displayMode = mode;
+ }
+
+ shouldApply("all and (display-mode: browser)");
+ shouldNotApply("all and (display-mode: fullscreen)");
+ shouldNotApply("all and (display-mode: standalone)");
+ shouldNotApply("all and (display-mode: minimal-ui)");
+
+ // Test entering the OS's fullscreen mode.
+ var fullScreenEntered = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("KEY_F11");
+ await fullScreenEntered;
+ // Wait for the next tick to apply media feature changes. See bug 1430380.
+ await promiseNextTick();
+ shouldApply("all and (display-mode: fullscreen)");
+ shouldNotApply("all and (display-mode: browser)");
+ var fullScreenExited = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("KEY_F11");
+ await fullScreenExited;
+ // Wait for the next tick to apply media feature changes. See bug 1430380.
+ await promiseNextTick();
+ shouldNotApply("all and (display-mode: fullscreen)");
+ shouldApply("all and (display-mode: browser)");
+
+ // Test entering fullscreen through document requestFullScreen.
+ fullScreenEntered = waitOneEvent(document, "mozfullscreenchange");
+ document.body.mozRequestFullScreen();
+ await fullScreenEntered
+ ok(document.mozFullScreenElement, "window entered fullscreen");
+ shouldApply("all and (display-mode: fullscreen)");
+ shouldNotApply("all and (display-mode: browser)");
+ fullScreenExited = waitOneEvent(document, "mozfullscreenchange");
+ document.mozCancelFullScreen();
+ await fullScreenExited;
+ ok(!document.mozFullScreenElement, "window exited fullscreen");
+ shouldNotApply("all and (display-mode: fullscreen)");
+ shouldApply("all and (display-mode: browser)");
+
+ // Test entering display mode mode through docshell
+ setDisplayMode("standalone");
+ shouldApply("all and (display-mode: standalone)");
+ shouldNotApply("all and (display-mode: fullscreen)");
+ shouldNotApply("all and (display-mode: browser)");
+ shouldNotApply("all and (display-mode: minimal-ui)");
+
+ // Test that changes in the display mode are reflected
+ setDisplayMode("minimal-ui");
+ shouldApply("all and (display-mode: minimal-ui)");
+ shouldNotApply("all and (display-mode: standalone)");
+
+ // Set the display mode back.
+ setDisplayMode("browser");
+
+ window.close();
+ window.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="test_task()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1104916">Mozilla Bug 1104916</a>
+<iframe id="subdoc" src="http://mochi.test:8888/tests/layout/style/test/chrome/media_queries_iframe.html" allowfullscreen></iframe>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/display_mode_reflow.html b/layout/style/test/chrome/display_mode_reflow.html
new file mode 100644
index 0000000000..7b2a118cd6
--- /dev/null
+++ b/layout/style/test/chrome/display_mode_reflow.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1256084
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+ for (var n of imports) {
+ window[n] = window.opener.wrappedJSObject[n];
+ }
+
+ /** Test for Display Mode **/
+
+ function waitOneEvent(element, name) {
+ return new Promise(function(resolve, reject) {
+ element.addEventListener(name, function() {
+ resolve();
+ }, {once: true});
+ });
+ }
+
+ function promiseNextTick() {
+ return new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ async function test_task() {
+ var iframe = document.getElementById("subdoc");
+ var subdoc = iframe.contentDocument;
+ var style = subdoc.getElementById("style");
+ var bodyComputedStyled = subdoc.defaultView.getComputedStyle(subdoc.body);
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ var secondDiv = subdoc.getElementById("b");
+ var offsetTop = secondDiv.offsetTop;
+
+ // Test entering the OS's fullscreen mode.
+ var fullScreenEntered = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("KEY_F11");
+ await fullScreenEntered;
+
+ // Wait for the next tick to apply media feature changes. See bug 1430380.
+ await promiseNextTick();
+ ok(offsetTop !== secondDiv.offsetTop, "offset top changes");
+ var fullScreenExited = waitOneEvent(win, "sizemodechange");
+ synthesizeKey("KEY_F11");
+ await fullScreenExited;
+
+ // Wait for the next tick to apply media feature changes. See bug 1430380.
+ await promiseNextTick();
+ ok(offsetTop === secondDiv.offsetTop, "offset top returns to original value");
+
+ offsetTop = secondDiv.offsetTop;
+ // Test entering fullscreen through document requestFullScreen.
+ fullScreenEntered = waitOneEvent(document, "mozfullscreenchange");
+ document.body.mozRequestFullScreen();
+ await fullScreenEntered
+ ok(offsetTop !== secondDiv.offsetTop, "offset top changes");
+ fullScreenExited = waitOneEvent(document, "mozfullscreenchange");
+ document.mozCancelFullScreen();
+ await fullScreenExited;
+ ok(offsetTop === secondDiv.offsetTop, "offset top returns to original value");
+
+ window.close();
+ window.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="test_task()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1256084">Mozilla Bug 1256084</a>
+<iframe id="subdoc" src="http://mochi.test:8888/tests/layout/style/test/chrome/display_mode_reflow_iframe.html"></iframe>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/display_mode_reflow_iframe.html b/layout/style/test/chrome/display_mode_reflow_iframe.html
new file mode 100644
index 0000000000..c05880ce7f
--- /dev/null
+++ b/layout/style/test/chrome/display_mode_reflow_iframe.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US">
+<head>
+ <title>Display Mode Reflow inner frame</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <style type="text/css" id="style" media="all">
+ div {
+ border: 2px solid black;
+ width: 50px;
+ height: 50px;
+ }
+ @media (display-mode: fullscreen) {
+ #a { height: 100px; }
+ }
+ </style>
+</head>
+<body>
+ <div id="a"></div>
+ <div id="b"></div>
+</body>
+</html>
diff --git a/layout/style/test/chrome/hover_empty.html b/layout/style/test/chrome/hover_empty.html
new file mode 100644
index 0000000000..7879e1ce9f
--- /dev/null
+++ b/layout/style/test/chrome/hover_empty.html
@@ -0,0 +1,4 @@
+<html>
+<body>
+</body>
+</html>
diff --git a/layout/style/test/chrome/hover_helper.html b/layout/style/test/chrome/hover_helper.html
new file mode 100644
index 0000000000..b1ae14e8cc
--- /dev/null
+++ b/layout/style/test/chrome/hover_helper.html
@@ -0,0 +1,270 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for :hover</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <style type="text/css">
+
+ div#one { height: 10px; width: 10px; }
+ div#one:hover { background: #00f; }
+ div#one > div { height: 5px; width: 20px; }
+ div#one > div:hover { background: #f00; }
+
+ div#twoparent { overflow: hidden; height: 20px; }
+ div#two { width: 10px; height: 10px; }
+ div#two:hover { margin-left: 5px; background: #0f0; }
+ div#two + iframe { width: 50px; height: 10px; }
+ div#two:hover + iframe { width: 100px; }
+
+ </style>
+</head>
+<!-- need a set timeout because we need things to start after painting suppression ends -->
+<body onload="setTimeout(step1, 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="display" style="position: absolute; top: 0; left: 0; width: 300px; height: 300px">
+
+ <div id="one"><div></div></div>
+
+ <div id="twoparent">
+ <div id="two"></div>
+ <iframe id="twoi" src="hover_empty.html"></iframe>
+ <div style="width: 5000px; height: 10px;"></div>
+ </div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+for (var name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+}
+
+var div = document.getElementById("display");
+var divtwo = document.getElementById("two");
+var iframe = document.getElementById("twoi");
+var divtwoparent = document.getElementById("twoparent");
+
+iframe.contentDocument.open();
+iframe.contentDocument.write("<style type='text/css'>html, body { margin: 0; padding: 0; }<\/style><body>");
+iframe.contentDocument.close();
+
+var moveEvent = { type: "mousemove", clickCount: "0" };
+
+function setResize(str) {
+ var handler = function() {
+ iframe.contentWindow.removeEventListener("resize", arguments.callee);
+ setTimeout(str, 100);
+ };
+ iframe.contentWindow.addEventListener("resize", handler);
+}
+
+function step1() {
+ /** test basic hover **/
+ var divone = document.getElementById("one");
+ synthesizeMouse(divone, 5, 7, moveEvent, window);
+ is(getComputedStyle(divone, "").backgroundColor, "rgb(0, 0, 255)",
+ ":hover applies");
+ is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ synthesizeMouse(divone, 5, 2, moveEvent, window);
+ is(getComputedStyle(divone, "").backgroundColor, "rgb(0, 0, 255)",
+ ":hover applies hierarchically");
+ is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgb(255, 0, 0)",
+ ":hover applies");
+ synthesizeMouse(divone, 15, 7, moveEvent, window);
+ is(getComputedStyle(divone, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ synthesizeMouse(divone, 15, 2, moveEvent, window);
+ is(getComputedStyle(divone, "").backgroundColor, "rgb(0, 0, 255)",
+ ":hover applies hierarchically");
+ is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgb(255, 0, 0)",
+ ":hover applies");
+
+ /** Test for Bug 302561 **/
+ setResize("step2();");
+ is(iframe.contentDocument.body.offsetWidth, 50,
+ ":hover does not apply (iframe body width)");
+ synthesizeMouse(divtwoparent, 7, 5, moveEvent, window);
+ is(iframe.contentDocument.body.offsetWidth, 100,
+ ":hover applies (iframe body width)");
+}
+
+var step2called = false;
+function step2() {
+ is(step2called, false, "step2 called only once");
+ step2called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ is(iframe.contentDocument.body.offsetWidth, 100,
+ ":hover applies (iframe body width)");
+ setResize("step3()");
+ synthesizeMouse(divtwoparent, 2, 5, moveEvent, window);
+ is(iframe.contentDocument.body.offsetWidth, 50,
+ ":hover does not apply (iframe body width)");
+}
+
+var step3called = false;
+function step3() {
+ is(step3called, false, "step3 called only once");
+ step3called = true;
+ if (getComputedStyle(iframe, "").width == "100px") {
+ // The two resize events may be coalesced into a single one.
+ step4();
+ return;
+ }
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setResize("step4()");
+ /* expect to get a second resize from the oscillation */
+}
+
+var step4called = false;
+function step4() {
+ is(step4called, false, "step4 called only once (more than two cycles of oscillation)");
+ if (step4called)
+ return;
+ step4called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ setTimeout(step5, 500); // time to detect oscillations if they exist
+}
+
+var step5called = false;
+function step5() {
+ is(step5called, false, "step5 called only once");
+ step5called = true;
+ setResize("step6()");
+ synthesizeMouse(divtwoparent, 25, 5, moveEvent, window);
+}
+
+var step6called = false;
+function step6() {
+ is(step6called, false, "step6 called only once");
+ step6called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ synthesizeMouse(divtwoparent, 2, 5, moveEvent, window);
+ setTimeout(step7, 500); // time to detect oscillations if they exist
+}
+
+var step7called = false;
+function step7() {
+ is(step7called, false, "step7 called only once (more than two cycles of oscillation)");
+ if (step7called)
+ return;
+ step7called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setTimeout(step8, 500); // time to detect oscillations if they exist
+}
+
+/* test the same case with scrolltop */
+
+var step8called = false;
+function step8() {
+ is(step8called, false, "step8 called only once");
+ step8called = true;
+ iframe.contentDocument.body.removeAttribute("onresize");
+ /* move the mouse out of the way */
+ synthesizeMouse(divtwoparent, 200, 5, moveEvent, window);
+ divtwoparent.scrollLeft = 5;
+ setResize("step9()");
+ synthesizeMouse(divtwoparent, 2, 5, moveEvent, window);
+ /* mouse now over 7, 5 */
+}
+
+var step9called = false;
+function step9() {
+ is(step9called, false, "step9 called only once");
+ step9called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ setResize("step10()");
+ divtwoparent.scrollLeft = 0; /* mouse now over 2,5 */
+}
+
+var step10called = false;
+function step10() {
+ is(step10called, false, "step10 called only once");
+ step10called = true;
+ if (getComputedStyle(iframe, "").width == "100px") {
+ // The two resize events may be coalesced into a single one.
+ step11();
+ return;
+ }
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setResize("step11()");
+ /* expect to get a second resize from the oscillation */
+}
+
+var step11called = false;
+function step11() {
+ is(step11called, false, "step11 called only once (more than two cycles of oscillation)");
+ if (step11called)
+ return;
+ step11called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ setTimeout(step12, 500); // time to detect oscillations if they exist
+}
+
+var step12called = false;
+function step12() {
+ is(step12called, false, "step12 called only once");
+ step12called = true;
+ setResize("step13()");
+ divtwoparent.scrollLeft = 25; /* mouse now over 27,5 */
+}
+
+var step13called = false;
+function step13() {
+ is(step13called, false, "step13 called only once");
+ step13called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setResize("step14()");
+ divtwoparent.scrollLeft = 0; /* mouse now over 2,5 */
+}
+
+var step14called = false;
+function step14() {
+ is(step14called, false, "step14 called only once");
+ step14called = true;
+ if (getComputedStyle(iframe, "").width == "50px") {
+ // The two resize events may be coalesced into a single one.
+ step15();
+ return;
+ }
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)",
+ ":hover applies");
+ setResize("step15()");
+ /* expect to get a second resize from the oscillation */
+}
+
+var step15called = false;
+function step15() {
+ is(step15called, false, "step15 called only once (more than two cycles of oscillation)");
+ if (step15called)
+ return;
+ step15called = true;
+ is(getComputedStyle(divtwo, "").backgroundColor, "rgba(0, 0, 0, 0)",
+ ":hover does not apply");
+ setTimeout(finish, 500); // time to detect oscillations if they exist
+}
+
+function finish() {
+ document.getElementById("display").style.display = "none";
+
+ var tester = window.SimpleTest;
+ window.close();
+ tester.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/import_useless1.css b/layout/style/test/chrome/import_useless1.css
new file mode 100644
index 0000000000..37e1a3d1d9
--- /dev/null
+++ b/layout/style/test/chrome/import_useless1.css
@@ -0,0 +1,3 @@
+.unlikely_to_match_anything {
+ color: black;
+}
diff --git a/layout/style/test/chrome/import_useless2.css b/layout/style/test/chrome/import_useless2.css
new file mode 100644
index 0000000000..37e1a3d1d9
--- /dev/null
+++ b/layout/style/test/chrome/import_useless2.css
@@ -0,0 +1,3 @@
+.unlikely_to_match_anything {
+ color: black;
+}
diff --git a/layout/style/test/chrome/match.png b/layout/style/test/chrome/match.png
new file mode 100644
index 0000000000..d3f299bf58
--- /dev/null
+++ b/layout/style/test/chrome/match.png
Binary files differ
diff --git a/layout/style/test/chrome/mismatch.png b/layout/style/test/chrome/mismatch.png
new file mode 100644
index 0000000000..8f9da3f00f
--- /dev/null
+++ b/layout/style/test/chrome/mismatch.png
Binary files differ
diff --git a/layout/style/test/chrome/moz_document_helper.html b/layout/style/test/chrome/moz_document_helper.html
new file mode 100644
index 0000000000..8b331b19e0
--- /dev/null
+++ b/layout/style/test/chrome/moz_document_helper.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<div id="display" style="position: relative"></div>
diff --git a/layout/style/test/chrome/test_bug1157097.html b/layout/style/test/chrome/test_bug1157097.html
new file mode 100644
index 0000000000..febf4952fb
--- /dev/null
+++ b/layout/style/test/chrome/test_bug1157097.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Test for bug 1157097</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<style>
+.blue { color: blue; }
+.red { color: red; }
+.inline-block { display: inline-block; }
+</style>
+<body onload=run()>
+<p><span id=s1 class=blue><b></b></span><span id=s2 class=red><b></b></span></p>
+<script>
+function run() {
+ window.windowUtils.postRestyleSelfEvent(document.querySelector("p"));
+ document.querySelectorAll("span")[0].className = "";
+ document.querySelectorAll("b")[0].className = "inline-block";
+ document.querySelectorAll("span")[1].className = "blue";
+ window.windowUtils.postRestyleSelfEvent(document.querySelectorAll("b")[1]);
+
+ document.body.offsetTop;
+
+ ok(true, "finished (hopefully we didn't assert)");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
diff --git a/layout/style/test/chrome/test_bug1346623.html b/layout/style/test/chrome/test_bug1346623.html
new file mode 100644
index 0000000000..027f839ace
--- /dev/null
+++ b/layout/style/test/chrome/test_bug1346623.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 1346623</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="startTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1346623">Mozilla Bug 1346623</a>
+<div id="display">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+var winUtils = window.windowUtils;
+
+function startTest() {
+ // load some styles at the agent level
+ var css = `
+ #ac-parent { color: green; }
+ #ac-child.abc { }
+ `;
+ var sheetURL = "data:text/css," + encodeURIComponent(css);
+ winUtils.loadSheetUsingURIString(sheetURL, winUtils.AGENT_SHEET);
+
+ // add canvas anonymous content
+ var bq = document.createElement("blockquote");
+ bq.id = "ac-parent";
+ bq.textContent = "This blockquote text should be green.";
+ var div = document.createElement("div");
+ div.id = "ac-child";
+ div.textContent = " This div text should be green.";
+ bq.appendChild(div);
+ var ac = document.insertAnonymousContent();
+ ac.root.appendChild(bq);
+ document.body.offsetWidth;
+
+ is(getComputedStyle(div).color, "rgb(0, 128, 0)",
+ "color before reframing");
+
+ // reframe the root
+ document.documentElement.style.display = "flex";
+ document.body.offsetWidth;
+
+ // restyle the div
+ div.className = "abc";
+ document.body.offsetWidth;
+
+ is(getComputedStyle(div).color, "rgb(0, 128, 0)",
+ "color after reframing");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_bug1371453.html b/layout/style/test/chrome/test_bug1371453.html
new file mode 100644
index 0000000000..6b3b4cb6eb
--- /dev/null
+++ b/layout/style/test/chrome/test_bug1371453.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Test for Bug 1371453</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<link rel="stylesheet" href="data:text/css,{}">
+<body>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+const Cu = SpecialPowers.Components.utils;
+
+document.styleSheetChangeEventsEnabled = true;
+
+onload = runTest;
+
+async function runTest() {
+ const sheet = document.getElementsByTagName("link")[1].sheet;
+ sheet.insertRule('@import url("blahblah")', 0);
+
+ const rule = sheet.cssRules[0];
+ is(rule.type, CSSRule.IMPORT_RULE, "Got expected import rule.");
+ isnot(rule.styleSheet, null, "Import rule contains a stylesheet.");
+ isnot(rule.media, null, "Import rule contains a media list.");
+ is(rule.href, "blahblah", "Import rule contains expected href.");
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_bug418986-2.xhtml b/layout/style/test/chrome/test_bug418986-2.xhtml
new file mode 100644
index 0000000000..152cac004e
--- /dev/null
+++ b/layout/style/test/chrome/test_bug418986-2.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418986
+-->
+<window title="Mozilla Bug 418986"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <style id="test-css" scoped="true"></style>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986"
+ target="_blank">Mozilla Bug 418986</a>
+ <p id="display"></p>
+ <p id="pictures"></p>
+ </body>
+
+ <script type="text/javascript" src="bug418986-2.js"></script>
+ <!-- test code goes here -->
+ <script type="text/javascript">
+ // Run all tests now.
+ window.onload = function () {
+ add_task(async function() {
+ await test(false);
+ });
+ };
+ </script>
+</window>
diff --git a/layout/style/test/chrome/test_bug511909.html b/layout/style/test/chrome/test_bug511909.html
new file mode 100644
index 0000000000..fa28bbe854
--- /dev/null
+++ b/layout/style/test/chrome/test_bug511909.html
@@ -0,0 +1,194 @@
+<html><!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=511909
+ --><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>@media and @-moz-document testcases</title>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+
+<style type="text/css">
+a {
+ font-weight: bold;
+}
+ #pink {
+ color: pink;
+ }
+
+ #green {
+ color: green;
+ }
+
+ #blue {
+ color: blue;
+ }
+
+pre {
+ border: 1px solid black;
+}
+</style>
+
+<style type="text/css">
+@-moz-document regexp(".*test_bug511909.*"){
+ #d {
+ color: pink;
+ }
+}
+</style>
+
+<style type="text/css">
+@media screen {
+ #m {
+ color: green;
+ }
+}
+</style>
+
+<style type="text/css">
+@-moz-document regexp(".*test_bug511909.*"){
+ @media screen {
+ #dm {
+ color: blue;
+ }
+ }
+}
+</style>
+
+<!-- should parse -->
+<style type="text/css">
+@media print {
+ @-moz-document regexp("not_this_url"),}
+ #mx {
+ color: pink;
+ }
+ }
+}
+</style>
+
+<!-- should parse -->
+<style type="text/css">
+@-moz-document regexp("not_this_url"){
+ @media print ,}
+ #mxx {
+ color: blue;
+ }
+ }
+}
+</style>
+
+<style type="text/css">
+@media screen {
+ @-moz-document regexp(".*test_bug511909.*"){
+ #md {
+ color: green;
+ }
+ }
+}
+</style>
+
+<style type="text/css">
+@media screen {
+ @-moz-document regexp(".*test_bug511909.*"){
+ @media screen {
+ @-moz-document regexp(".*test_bug511909.*"){
+ @media screen {
+ #me {
+ color: blue;
+ }
+ }
+ }
+ }
+ }
+}
+</style>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511909">Mozilla Bug 511909</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <script class="testbody" type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ // Ensure all the sheets are re-parsed, so that the pref applies.
+ for (const sheet of Array.from(document.querySelectorAll('style'))) {
+ sheet.textContent += "/* dummy */";
+ }
+
+ var pink = getComputedStyle(document.getElementById("pink"), "");
+ var green = getComputedStyle(document.getElementById("green"), "");
+ var blue = getComputedStyle(document.getElementById("blue"), "");
+
+ var cs1 = getComputedStyle(document.getElementById("d"), "");
+ var cs2 = getComputedStyle(document.getElementById("m"), "");
+ var cs3 = getComputedStyle(document.getElementById("dm"), "");
+ var cs4 = getComputedStyle(document.getElementById("md"), "");
+ var cs5 = getComputedStyle(document.getElementById("mx"), "");
+ var cs6 = getComputedStyle(document.getElementById("mxx"), "");
+ var cs7 = getComputedStyle(document.getElementById("me"), "");
+
+ is(cs1.color, pink.color, "@-moz-document applies");
+ is(cs2.color, green.color, "@media applies");
+ is(cs3.color, blue.color, "@media nested in @-moz-document applies");
+ is(cs4.color, green.color, "@-moz-document nested in @media applies");
+ is(cs5.color, pink.color, "broken @media nested in @-moz-document correctly handled");
+ is(cs6.color, blue.color, "broken @-moz-document nested in @media correctly handled");
+ is(cs7.color, blue.color, "@media nested in @-moz-document nested in @media applies");
+ SimpleTest.finish();
+ });
+ </script>
+<div>
+<pre>default style
+</pre>
+<a id="pink">This line should be pink</a><br>
+
+<a id="green">This line should be green</a><br>
+
+<a id="blue">This line should be blue</a><br>
+
+<pre>@-moz-document {...}
+</pre>
+<a id="d">This line should be pink</a><br>
+<pre>@media screen {...}
+</pre>
+<a id="m">This line should be green</a><br>
+<pre>@-moz-document {
+ @media screen {...}
+}
+</pre>
+<a id="dm">This line should be blue</a><br>
+<pre>@media print {
+ @-moz-document regexp("not_this_url"),}
+ #mx {
+ color: pink;
+ }
+ }
+}
+</pre>
+<a id="mx">This line should be pink</a><br></div>
+<pre>@-moz-document regexp("not_this_url"){
+ @media print ,}
+ #mxx {
+ color: blue;
+ }
+ }
+}
+</pre>
+<a id="mxx">This line should be blue</a><br>
+<pre>@media screen {
+ @-moz-documen {...}
+}
+</pre>
+<a id="md">This line should be green</a><br>
+<pre>@media screen {
+ @-moz-document {
+ @media screen {...}
+ }
+}
+</pre>
+<a id="me">This line should be blue</a><br>
+
+
+</body></html>
diff --git a/layout/style/test/chrome/test_bug535806.xhtml b/layout/style/test/chrome/test_bug535806.xhtml
new file mode 100644
index 0000000000..7f4ec286bc
--- /dev/null
+++ b/layout/style/test/chrome/test_bug535806.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=535806
+-->
+<window title="Mozilla Bug 535806"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=535806"
+ target="_blank">Mozilla Bug 535806</a>
+ </body>
+
+ <iframe id="f"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 535806 **/
+ SimpleTest.waitForExplicitFinish();
+
+ window.addEventListener("load", function() {
+ $("f").setAttribute("src", "bug535806-html.html");
+ });
+
+ function htmlLoaded() {
+ $("f").setAttribute("src", "bug535806-xul.xhtml");
+ }
+
+ function xulLoaded() {
+ var doc = $("f").contentDocument;
+ is(doc.defaultView.getComputedStyle(doc.getElementById("s")).color,
+ "rgb(0, 128, 0)");
+ SimpleTest.finish();
+ }
+
+
+ ]]>
+ </script>
+</window>
diff --git a/layout/style/test/chrome/test_chrome_only_media_queries.html b/layout/style/test/chrome/test_chrome_only_media_queries.html
new file mode 100644
index 0000000000..1a2fb098c0
--- /dev/null
+++ b/layout/style/test/chrome/test_chrome_only_media_queries.html
@@ -0,0 +1,84 @@
+<!doctype html>
+<title>Test for parsing of non-content-exposed media-queries.</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script src="chrome-only-media-queries.js"></script>
+<style></style>
+<script>
+const SHEET = document.querySelector('style');
+
+SimpleTest.waitForExplicitFinish();
+
+async function testWithPref() {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["layout.css.forced-colors.enabled", false],
+ ],
+ },
+ r
+ );
+ });
+ expectKnown("(forced-colors: none)");
+ expectKnown("(forced-colors: active)");
+ expectKnown("(forced-colors)");
+ SimpleTest.finish();
+}
+
+function expect(q, shouldBeKnown) {
+ is(matchMedia(q).media, q, "Serialization should roundtrip");
+ is(matchMedia(`${q} or (not ${q})`).matches, shouldBeKnown, `Query should${shouldBeKnown ? "" : " not"} be known`);
+}
+
+function expectKnown(q) {
+ expect(q, true);
+}
+
+function expectUnkown(q) {
+ expect(q, false);
+}
+
+// Test a toggle that should always match for `1` or `0`.
+function testToggle(toggle) {
+ expectKnown(`(${toggle})`);
+ expectKnown(`(${toggle}: 1)`);
+ expectKnown(`(${toggle}: 0)`);
+
+ expectUnkown(`(${toggle}: foo)`);
+ expectUnkown(`(${toggle}: true)`);
+ expectUnkown(`(${toggle}: false)`);
+ expectUnkown(`(${toggle}: -1)`);
+ expectUnkown(`(min-${toggle}: 0)`);
+ expectUnkown(`(max-${toggle}: 0)`);
+ expectUnkown(`(max-${toggle})`);
+ expectUnkown(`(min-${toggle})`);
+
+ let matches_1 = matchMedia(`(${toggle}: 1)`).matches;
+ let matches_0 = matchMedia(`(${toggle}: 0)`).matches;
+ isnot(matches_0, matches_1, `Should not match both true and false: ${toggle}`);
+ is(matches_0 || matches_1, true, `Should match at least one: ${toggle}`);
+}
+
+for (let toggle of CHROME_ONLY_TOGGLES) {
+ testToggle(toggle)
+}
+
+for (let query of CHROME_ONLY_QUERIES) {
+ expectKnown(query);
+}
+
+// These might be exposed to content by pref, we just want to make sure they're
+// always exposed to chrome.
+expectKnown("(prefers-contrast: more)")
+expectKnown("(prefers-contrast: no-preference)")
+expectKnown("(prefers-contrast: less)");
+expectKnown("(prefers-contrast)")
+
+expectKnown("(forced-colors: none)");
+expectKnown("(forced-colors: active)");
+expectKnown("(forced-colors)");
+
+expectUnkown("(-moz-platform: )");
+
+testWithPref();
+</script>
diff --git a/layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html b/layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html
new file mode 100644
index 0000000000..4d9647ba27
--- /dev/null
+++ b/layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Test for chrome-only rules in constructable stylesheets</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ add_task(async function chrome_rules_constructable_stylesheets() {
+ let sheet = new CSSStyleSheet();
+ sheet.replaceSync(".foo { -moz-default-appearance: none }");
+ is(sheet.cssRules[0].style.length, 1, "Should parse chrome-only property in chrome document");
+ });
+</script>
diff --git a/layout/style/test/chrome/test_display_mode.html b/layout/style/test/chrome/test_display_mode.html
new file mode 100644
index 0000000000..69e72d5ab8
--- /dev/null
+++ b/layout/style/test/chrome/test_display_mode.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1104916
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ async function startTest() {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ },
+ r
+ );
+ });
+ // Chrome test run tests in iframe, and fullscreen is disabled by default.
+ // So run the test in a separate window.
+ window.open("display_mode.html", "display_mode", "width=500,height=500,resizable");
+ }
+ </script>
+</head>
+<body onload="startTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1104916">Mozilla Bug 1104916</a>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_display_mode_reflow.html b/layout/style/test/chrome/test_display_mode_reflow.html
new file mode 100644
index 0000000000..01022207f3
--- /dev/null
+++ b/layout/style/test/chrome/test_display_mode_reflow.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1256084
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Display Mode</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ async function startTest() {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ },
+ r
+ );
+ });
+ // Chrome test run tests in iframe, and fullscreen is disabled by default.
+ // So run the test in a separate window.
+ window.open("display_mode_reflow.html", "display_mode_reflow", "width=500,height=500,resizable");
+ }
+ </script>
+</head>
+<body onload="startTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1256084">Mozilla Bug 1256084</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_hover.html b/layout/style/test/chrome/test_hover.html
new file mode 100644
index 0000000000..019f537e8c
--- /dev/null
+++ b/layout/style/test/chrome/test_hover.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for :hover</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="startTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="display">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function startTest() {
+ // Run the test in a separate window so that the parent document doesn't have
+ // anything that will cause reflows and dispatch synth mouse moves when we don't
+ // want them and disturb our test.
+ window.open("hover_helper.html", "hover_helper", "width=200,height=300");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_moz_document_rules.html b/layout/style/test/chrome/test_moz_document_rules.html
new file mode 100644
index 0000000000..c28fc964ed
--- /dev/null
+++ b/layout/style/test/chrome/test_moz_document_rules.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for @-moz-document rules</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=398962">Mozilla Bug 398962</a>
+<iframe id="iframe" src="http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+var [gStyleSheetService, gIOService] = (function() {
+ return [
+ Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(Ci.nsIStyleSheetService),
+ Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ ];
+})();
+function set_user_sheet(sheeturi)
+{
+ var uri = gIOService.newURI(sheeturi);
+ gStyleSheetService.loadAndRegisterSheet(uri, gStyleSheetService.USER_SHEET);
+}
+function remove_user_sheet(sheeturi)
+{
+ var uri = gIOService.newURI(sheeturi);
+ gStyleSheetService.unregisterSheet(uri, gStyleSheetService.USER_SHEET);
+}
+
+function run()
+{
+ var iframe = document.getElementById("iframe");
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+ var cs = subwin.getComputedStyle(subdoc.getElementById("display"));
+ var zIndexCounter = 0;
+
+ function test_document_rule(urltests, shouldapply)
+ {
+ var zIndex = ++zIndexCounter;
+ var encodedRule = encodeURI("@-moz-document " + urltests + " { ") +
+ "%23" + // encoded hash character for "#display"
+ encodeURI("display { z-index: " + zIndex + " } }");
+ var sheeturi = "data:text/css," + encodedRule;
+ set_user_sheet(sheeturi);
+ if (shouldapply) {
+ is(cs.zIndex, String(zIndex),
+ "@-moz-document " + urltests +
+ " should apply to this document");
+ } else {
+ is(cs.zIndex, "auto",
+ "@-moz-document " + urltests +
+ " should NOT apply to this document");
+ }
+ remove_user_sheet(sheeturi);
+ }
+
+ test_document_rule("domain(mochi.test)", true);
+ test_document_rule("domain(\"mochi.test\")", true);
+ test_document_rule("domain('mochi.test')", true);
+ test_document_rule("domain('test')", true);
+ test_document_rule("domain(.test)", false);
+ test_document_rule("domain('.test')", false);
+ test_document_rule("domain('ochi.test')", false);
+ test_document_rule("domain(ochi.test)", false);
+ test_document_rule("url-prefix(http://moch)", true);
+ test_document_rule("url-prefix(http://och)", false);
+ test_document_rule("url-prefix(http://mochi.test)", true);
+ test_document_rule("url-prefix(http://mochi.test:88)", true);
+ test_document_rule("url-prefix(http://mochi.test:8888)", true);
+ test_document_rule("url-prefix(http://mochi.test:8888/)", true);
+ test_document_rule("url-prefix('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html')", true);
+ test_document_rule("url-prefix('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.htmlx')", false);
+ test_document_rule("url(http://mochi.test:8888/)", false);
+ test_document_rule("url('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html')", true);
+ test_document_rule("url('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.htmlx')", false);
+ test_document_rule("regexp(.*ochi.*)", false); // syntax error
+ test_document_rule("regexp('.*ochi.*')", true);
+ test_document_rule("regexp('ochi.*')", false);
+ test_document_rule("regexp('.*ochi')", false);
+ test_document_rule("regexp('http:.*ochi.*')", true);
+ test_document_rule("regexp('http:.*ochi')", false);
+ test_document_rule("regexp('http:.*oCHi.*')", false); // case sensitive
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_moz_document_serialization.html b/layout/style/test/chrome/test_moz_document_serialization.html
new file mode 100644
index 0000000000..0707880507
--- /dev/null
+++ b/layout/style/test/chrome/test_moz_document_serialization.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug </title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <style type="text/css" id="style"></style>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+
+var rules = [
+ { rule: "@-moz-document url(http://www.example.com/) {}" },
+ { rule: "@-moz-document url('http://www.example.com/') {}" },
+ { rule: '@-moz-document url("http://www.example.com/") {}' },
+ { rule: "@-moz-document url-prefix('http://www.example.com/') {}" },
+ { rule: '@-moz-document url-prefix("http://www.example.com/") {}' },
+ { rule: "@-moz-document domain('example.com') {}" },
+ { rule: '@-moz-document domain("example.com") {}' },
+ { rule: "@-moz-document regexp('http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/') {}" },
+ { rule: '@-moz-document regexp("http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/") {}' },
+];
+
+SimpleTest.waitForExplicitFinish();
+
+ var style = document.getElementById("style");
+ var style_text = document.createTextNode("");
+ style.appendChild(style_text);
+
+ for (var i in rules) {
+ var obj = rules[i];
+ var rule = obj.rule;
+
+ style_text.data = rule;
+ is(style.sheet.cssRules.length, 1, "should have one rule");
+ var ser1 = style.sheet.cssRules[0].cssText;
+ if ("is_canonical" in obj) {
+ is(ser1, rule, "rule '" + rule + "' should serialize to itself");
+ }
+
+ style_text.data = ser1;
+ is(style.sheet.cssRules.length, 1, "should have one rule");
+ var ser2 = style.sheet.cssRules[0].cssText;
+ is(ser2, ser1,
+ "parse+serialize for rule '" + rule + "' should be idempotent");
+ }
+
+ SimpleTest.finish();
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/chrome/test_scrollbar_inline_size.html b/layout/style/test/chrome/test_scrollbar_inline_size.html
new file mode 100644
index 0000000000..31161a9caf
--- /dev/null
+++ b/layout/style/test/chrome/test_scrollbar_inline_size.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Test for env(scrollbar-inline-size)</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="chrome://global/skin"/>
+<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<div id="scroller" style="width: 100px; height: 100px; overflow: scroll"></div>
+<div id="ref" style="width: env(scrollbar-inline-size, 1000px)"></div>
+<script>
+ SimpleTest.waitForExplicitFinish();
+ async function runTest() {
+ // We need to disable overlay scrollbars to measure the real scrollbar
+ // size.
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.useOverlayScrollbars", 0]],
+ });
+ runOnce();
+
+ info("with full zoom");
+ SpecialPowers.setFullZoom(window, 2.0);
+
+ runOnce();
+ }
+
+ function runOnce() {
+ let scroller = document.getElementById("scroller");
+ let ref = document.getElementById("ref");
+ let scrollbarSize = scroller.getBoundingClientRect().width - scroller.clientWidth;
+ ok(scrollbarSize > 0, "Should have a scrollbar");
+ // clientWidth rounds, so we might see a bit of rounding error
+ isfuzzy(ref.getBoundingClientRect().width, scrollbarSize, 1, "env() should match the scrollbar size");
+ }
+
+ runTest().then(SimpleTest.finish);
+</script>
diff --git a/layout/style/test/chrome/test_stylesheet_clone_import_rule.html b/layout/style/test/chrome/test_stylesheet_clone_import_rule.html
new file mode 100644
index 0000000000..37c3b9ccaa
--- /dev/null
+++ b/layout/style/test/chrome/test_stylesheet_clone_import_rule.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+
+<style>div { color: green; }</style>
+
+<link id="theOnlyLink" rel="stylesheet" type="text/css" href="import_useless1.css">
+
+<div id="theOnlyDiv">This text will change colors several times.</div>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ const Cu = SpecialPowers.Components.utils;
+
+
+ let theOnlyDiv = document.getElementById("theOnlyDiv");
+ let link = document.getElementById("theOnlyLink");
+ let stylesheet = link.sheet;
+
+ runTest().catch(function(reason) {
+ ok(false, "Failed with reason: " + reason);
+ }).then(function() {
+ SimpleTest.finish();
+ });
+
+ function cssRulesToString(cssRules) {
+ return Array.from(cssRules).map(rule => rule.cssText).join('');
+ }
+
+ async function runTest() {
+ // Test that the div is initially red (from base.css)
+ is(getComputedStyle(theOnlyDiv).color, "rgb(0, 128, 0)", "div begins as green.");
+
+ // Insert some import rules.
+ stylesheet.insertRule('@import url("import_useless2.css")', 0);
+ stylesheet.insertRule('@import url("import_useless2.css")', 1);
+
+ // Do some sanity checking of our import rules.
+ let primaryRules = stylesheet.cssRules;
+ await SimpleTest.promiseWaitForCondition(function() {
+ try {
+ primaryRules[0].styleSheet.cssRules;
+ primaryRules[1].styleSheet.cssRules;
+ return true;
+ } catch (ex) {
+ return false;
+ }
+ });
+
+ // Make some helper variables for the comparison tests.
+ let importSheet1 = primaryRules[0].styleSheet;
+ let rules1 = importSheet1.cssRules;
+
+ let importSheet2 = primaryRules[1].styleSheet;
+ let rules2 = importSheet2.cssRules;
+
+ // Confirm that these two sheets are meaningfully the same.
+ is(cssRulesToString(rules1), cssRulesToString(rules2), "Cloned sheet rules are equivalent.");
+
+ // Add a color-changing rule to the first stylesheet.
+ importSheet1.insertRule('div { color: blue; }');
+ rules1 = importSheet1.cssRules;
+
+ // And make sure that it has an effect.
+ is(getComputedStyle(theOnlyDiv).color, "rgb(0, 0, 255)", "div becomes blue.");
+
+ // Make sure that the two sheets have different rules now.
+ isnot(cssRulesToString(rules1), cssRulesToString(rules2), "Cloned sheet rules are no longer equivalent.");
+
+ // Add a color-changing rule to the second stylesheet (that will mask the first).
+ importSheet2.insertRule('div { color: red; }');
+ // And make sure that it has an effect.
+ is(getComputedStyle(theOnlyDiv).color, "rgb(255, 0, 0)", "div becomes red.");
+
+ // Delete the second sheet by removing the import rule, and make sure the color changes back.
+ stylesheet.deleteRule(1);
+ is(getComputedStyle(theOnlyDiv).color, "rgb(0, 0, 255)", "div goes back to blue.");
+
+ // Delete the first sheet by removing the import rule, and make sure the color changes back.
+ stylesheet.deleteRule(0);
+ is(getComputedStyle(theOnlyDiv).color, "rgb(0, 128, 0)", "div goes back to green.");
+ }
+</script>
+</html>
diff --git a/layout/style/test/css_properties_like_longhand.js b/layout/style/test/css_properties_like_longhand.js
new file mode 100644
index 0000000000..606de3ad68
--- /dev/null
+++ b/layout/style/test/css_properties_like_longhand.js
@@ -0,0 +1 @@
+var gShorthandPropertiesLikeLonghand = [{ name: "overflow", prop: "overflow" }];
diff --git a/layout/style/test/descriptor_database.js b/layout/style/test/descriptor_database.js
new file mode 100644
index 0000000000..f5abc8576d
--- /dev/null
+++ b/layout/style/test/descriptor_database.js
@@ -0,0 +1,142 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* vim: set shiftwidth=4 tabstop=4 autoindent cindent noexpandtab: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+// Each property has the following fields:
+// domProp: The name of the relevant member of nsIDOM[NS]CSS2Properties
+// values: Strings that are values for the descriptor and should be accepted.
+// invalid_values: Things that are not values for the descriptor and
+// should be rejected.
+
+var gCSSFontFaceDescriptors = {
+ "font-family": {
+ domProp: "fontFamily",
+ values: [
+ '"serif"',
+ '"cursive"',
+ "seriff",
+ "Times New Roman",
+ "TimesRoman",
+ '"Times New Roman"',
+ ],
+ /* not clear that the generics are really invalid */
+ invalid_values: [
+ "sans-serif",
+ "Times New Roman, serif",
+ "'Times New Roman', serif",
+ "cursive",
+ "fantasy",
+ "Times )",
+ "Times !",
+ "Times ! foo",
+ "Times ! important",
+ ],
+ },
+ "font-stretch": {
+ domProp: "fontStretch",
+ values: [
+ "normal",
+ "ultra-condensed",
+ "extra-condensed",
+ "condensed",
+ "semi-condensed",
+ "semi-expanded",
+ "expanded",
+ "extra-expanded",
+ "ultra-expanded",
+ ],
+ invalid_values: ["wider", "narrower", "normal ! important", "normal )"],
+ },
+ "font-style": {
+ domProp: "fontStyle",
+ values: ["normal", "italic", "oblique"],
+ invalid_values: [],
+ },
+ "font-weight": {
+ domProp: "fontWeight",
+ values: [
+ "normal",
+ "400",
+ "bold",
+ "100",
+ "200",
+ "300",
+ "500",
+ "600",
+ "700",
+ "800",
+ "900",
+ "107",
+ "399",
+ "401",
+ "699",
+ "710",
+ "calc(1001)",
+ "calc(100 + 1)",
+ "calc(1)",
+ "100.6",
+ "99",
+ "700 900",
+ "300.4 500.4",
+ "calc(200.4) calc(400.4)",
+ ],
+ invalid_values: ["bolder", "lighter", "1001", "0", "0 100", "100 1001"],
+ },
+ src: {
+ domProp: null,
+ values: [
+ "url(404.ttf)",
+ 'url("404.eot")',
+ "url('404.otf')",
+ 'url(404.ttf) format("truetype")',
+ "local(Times New Roman)",
+ "local('Times New Roman')",
+ 'local("Times New Roman")',
+ 'local("serif")',
+ "url(404.ttf) format(truetype)",
+ 'url(404.ttf) format("truetype", "opentype"), url(\'404.eot\')',
+ 'url(404.ttf) format("truetype", "unknown"), local(Times New Roman), url(\'404.eot\')',
+ ],
+ invalid_values: [
+ 'url(404.ttf) format("truetype" "opentype")',
+ 'url(404.ttf) format("truetype",)',
+ 'local("Times New" Roman)',
+ "local(serif)" /* is this valid? */,
+ "url(404.ttf) )",
+ "url(404.ttf) ) foo",
+ "url(404.ttf) ! important",
+ "url(404.ttf) ! hello",
+ 'url(404.ttf) format("truetype", "opentype")',
+ ],
+ },
+ "unicode-range": {
+ domProp: null,
+ values: [
+ "U+0-10FFFF",
+ "U+3-7B3",
+ "U+3??",
+ "U+6A",
+ "U+3????",
+ "U+???",
+ "U+302-302",
+ "U+0-7,U+A-C",
+ "U+3??, U+500-513 ,U+612 , U+4????",
+ "U+1FFF,U+200-27F",
+ ],
+ invalid_values: [
+ "U+1????-2????",
+ "U+0-7,A-C",
+ "U+100-17F,U+200-17F",
+ "U+100-17F,200-27F",
+ "U+6A!important",
+ "U+6A)",
+ ],
+ },
+ "font-display": {
+ domProp: null,
+ values: ["auto", "block", "swap", "fallback", "optional"],
+ invalid_values: ["normal", "initial"],
+ },
+};
diff --git a/layout/style/test/empty.html b/layout/style/test/empty.html
new file mode 100644
index 0000000000..734c5a1c09
--- /dev/null
+++ b/layout/style/test/empty.html
@@ -0,0 +1 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"><html><head><title></title></head><body></body></html> \ No newline at end of file
diff --git a/layout/style/test/file_animations_async_tests.html b/layout/style/test/file_animations_async_tests.html
new file mode 100644
index 0000000000..9d4dfa1fed
--- /dev/null
+++ b/layout/style/test/file_animations_async_tests.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1086937</title>
+ <script>
+ var is = opener.is.bind(opener);
+ var ok = opener.ok.bind(opener);
+ var todo = opener.todo.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <style>
+ /* must use implicit value at one end */
+ @keyframes slide-left { from { margin-left: -1000px } }
+ </style>
+ <script type="application/javascript">
+
+ var gDisplay;
+
+ function run() {
+ gDisplay = document.getElementById("display");
+ opener.SimpleTest.executeSoon(test1);
+ }
+
+ /*
+ * Bug 1086937 - Animations continue correctly across load of
+ * downloadable font.
+ */
+ function test1() {
+ var animdiv = document.createElement("div");
+ // Take control of the refresh driver right from the start
+ advance_clock(0);
+ animdiv.style.animation = "slide-left 100s linear"; // 10px per second
+ gDisplay.appendChild(animdiv);
+ var cs = getComputedStyle(animdiv, "");
+ is(cs.marginLeft, "-1000px", "initial value of animation (force flush)");
+ advance_clock(1000);
+ is(cs.marginLeft, "-990px", "value of animation before font load");
+
+ var font = new FontFace("DownloadedAhem", "url(Ahem.ttf)");
+ document.fonts.add(font);
+
+ var fontdiv = document.createElement("div");
+ fontdiv.appendChild(document.createTextNode("A"));
+ fontdiv.style.fontFamily = "DownloadedAhem";
+ gDisplay.appendChild(fontdiv);
+
+ font.load().then(function(loadedFace) {
+ is(cs.marginLeft, "-990px", "value of animation after font load " +
+ "(clock only advances when we say so)");
+ advance_clock(1000);
+ is(cs.marginLeft, "-980px",
+ "animation should still be advancing after font load");
+
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ document.fonts.delete(font);
+ animdiv.remove();
+ fontdiv.remove();
+
+ finish();
+ });
+ }
+
+ </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086937">Mozilla Bug 1086937</a>
+<div id="display"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/file_animations_omta_scroll.html b/layout/style/test/file_animations_omta_scroll.html
new file mode 100644
index 0000000000..625b4b6c19
--- /dev/null
+++ b/layout/style/test/file_animations_omta_scroll.html
@@ -0,0 +1,392 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <title>Test for css-animations running on the compositor thread with scroll-timeline</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <style type="text/css">
+ @keyframes transform_anim {
+ from { transform: translate(50px); }
+ to { transform: translate(150px); }
+ }
+
+ @keyframes always_fifty {
+ from, to { transform: translate(50px); }
+ }
+
+ @keyframes geometry {
+ from { width: 50px; }
+ to { width: 100px; }
+ }
+
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+
+ .scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ scroll-timeline-name: scroll_timeline;
+ }
+
+ .content {
+ block-size: 100%;
+ padding-block-end: 100px;
+ }
+ </style>
+</head>
+<body>
+ <div id="display"></div>
+ <pre id="test"></pre>
+</body>
+<script type="application/javascript">
+"use strict";
+
+// Global state
+var gScroller = null;
+var gDiv = null;
+
+// Shortcut omta_is and friends by filling in the initial 'elem' argument
+// with gDiv.
+[ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) {
+ var origFn = window[fn];
+ window[fn] = function() {
+ var args = Array.from(arguments);
+ if (!(args[0] instanceof Element)) {
+ args.unshift(gDiv);
+ }
+ return origFn.apply(window, args);
+ };
+});
+
+// Shortcut new_div and done_div to update gDiv
+var originalNewDiv = window.new_div;
+window.new_div = function(style) {
+ [ gDiv ] = originalNewDiv(style);
+};
+var originalDoneDiv = window.done_div;
+window.done_div = function() {
+ originalDoneDiv();
+ gDiv = null;
+};
+
+// Bind the ok and todo to the opener, and close this window when we finish.
+var ok = opener.ok.bind(opener);
+var todo = opener.todo.bind(opener);
+function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+}
+
+function new_scroller() {
+ gScroller = document.createElement('div');
+ gScroller.className = `scroller`;
+
+ let content = document.createElement('div');
+ content.className = 'content';
+
+ gScroller.appendChild(content);
+ document.getElementById("display").appendChild(gScroller);
+ return gScroller;
+}
+
+function done_scroller() {
+ gScroller.firstChild.remove();
+ gScroller.remove();
+ gScroller = null;
+}
+
+waitUntilApzStable().then(() => {
+ runOMTATest(function() {
+ var onAbort = function() {
+ if (gDiv) {
+ done_div();
+ }
+ if (gScroller) {
+ done_scroller();
+ }
+ };
+ runAllAsyncAnimTests(onAbort).then(finish);
+ }, finish);
+});
+
+//----------------------------------------------------------------------
+//
+// Test cases
+//
+//----------------------------------------------------------------------
+
+// The non-omta property with scroll-timeline together with an omta property
+// with document-timeline.
+addAsyncAnimTest(async function() {
+ new_scroller();
+ new_div("animation: geometry 10s scroll_timeline, always_fifty 1s infinite;");
+ await waitForPaintsFlushed();
+
+ // Note: width is not a OMTA property, so it must be running on the main
+ // thread.
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "transform animations should runs on compositor thread");
+
+ done_div();
+ done_scroller();
+});
+
+// transform property with scroll-driven animations.
+addAsyncAnimTest(async function() {
+ let scroller = new_scroller();
+ new_div("animation: transform_anim 1s linear scroll_timeline;");
+ await waitForPaintsFlushed();
+
+ scroller.scrollTop = 50;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ done_div();
+ done_scroller();
+});
+
+
+// The scroll-driven animation with an underlying value and make it go from the
+// active phase to the before phase.
+addAsyncAnimTest(async function() {
+ let scroller = new_scroller();
+ new_div("animation: always_fifty 5s linear 5s scroll_timeline; " +
+ "transform: translate(25px);");
+ await waitForPaintsFlushed();
+
+ // NOTE: getOMTAStyle() can't detect the animation is running on the
+ // compositor during the delay phase.
+ omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either,
+ "The scroll animation is in delay");
+
+ scroller.scrollTop = 75;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ // Use setAsyncScrollOffset() to update apz (compositor thread only) to make
+ // sure Bug 1776077 is reproducible.
+ let utils = SpecialPowers.wrap(window).windowUtils;
+ utils.setAsyncScrollOffset(scroller, 0, -50);
+ utils.advanceTimeAndRefresh(16);
+ utils.restoreNormalRefresh();
+ await waitForPaints();
+
+ // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the
+ // OMTA style directly.
+ let compositorStr =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "matrix(1, 0, 0, 1, 25, 0)",
+ "scroll animations is in delay phase before calling main thread style " +
+ "udpate");
+
+ scroller.scrollTop = 25;
+ await waitForPaintsFlushed();
+
+ compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "",
+ "scroll animation in delay phase clears its OMTA style");
+ omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either,
+ "The scroll animation is in delay");
+
+ done_div();
+ done_scroller();
+});
+
+// The scroll-driven animation without an underlying value and make it go from
+// the active phase to the before phase.
+addAsyncAnimTest(async function() {
+ let scroller = new_scroller();
+ new_div("animation: always_fifty 5s linear 5s scroll_timeline; ");
+ await waitForPaintsFlushed();
+
+ // NOTE: getOMTAStyle() can't detect the animation is running on the
+ // compositor during the delay phase.
+ omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.Either,
+ "The scroll animation is in delay");
+
+ scroller.scrollTop = 75;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ // Use setAsyncScrollOffset() to update apz (compositor thread only) to make
+ // sure Bug 1776077 is reproducible.
+ let utils = SpecialPowers.wrap(window).windowUtils;
+ utils.setAsyncScrollOffset(scroller, 0, -50);
+ utils.advanceTimeAndRefresh(16);
+ utils.restoreNormalRefresh();
+ await waitForPaints();
+
+ // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the
+ // OMTA style directly.
+ let compositorStr =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "matrix(1, 0, 0, 1, 0, 0)",
+ "scroll animations is in delay phase before calling main thread style " +
+ "udpate");
+
+ done_div();
+ done_scroller();
+});
+
+// The scroll-driven animation is in delay, together with other runing
+// animations.
+addAsyncAnimTest(async function() {
+ let scroller = new_scroller();
+ new_div("animation: transform_anim 10s linear -5s paused, " +
+ " always_fifty 5s linear 5s scroll_timeline;");
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor,
+ "The scroll animation is in delay");
+
+ scroller.scrollTop = 75;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ let utils = SpecialPowers.wrap(window).windowUtils;
+ utils.setAsyncScrollOffset(scroller, 0, -50);
+ utils.advanceTimeAndRefresh(16);
+ utils.restoreNormalRefresh();
+ await waitForPaints();
+
+ // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the
+ // OMTA style directly.
+ let compositorStr =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "matrix(1, 0, 0, 1, 100, 0)",
+ "scroll animations is in delay phase before calling main thread style " +
+ "udpate");
+
+ done_div();
+ done_scroller();
+});
+
+addAsyncAnimTest(async function() {
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("style", "width: 200px; height: 200px");
+ iframe.setAttribute("scrolling", "no");
+ iframe.srcdoc =
+ "<!DOCTYPE HTML>" +
+ "<html style='min-height: 100%; padding-bottom: 100px;'>" +
+ "<style>" +
+ "@keyframes anim { from, to { transform: translate(50px) } }" +
+ "</style>" +
+ "<div id='target_in_iframe' " +
+ " style='width:50px; height:50px; background:green;" +
+ " animation: anim 10s linear scroll(root);'>" +
+ "</div>" +
+ "</html>";
+
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ document.body.appendChild(iframe);
+ });
+
+ gDiv = iframe.contentDocument.getElementById("target_in_iframe");
+
+ const root = iframe.contentDocument.scrollingElement;
+ const maxScroll = root.scrollHeight - root.clientHeight;
+ root.scrollTop = 0.5 * maxScroll;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0, RunningOn.MainThread,
+ "scroll transform animations inside an iframe with " +
+ "scrolling:no should run on the main thread");
+
+ gDiv = null;
+ iframe.remove();
+});
+
+// FIXME: Bug 1818346. Support OMTA for view-timeline.
+addAsyncAnimTest(async function() {
+ let scroller = document.createElement("div");
+ scroller.style.width = "100px";
+ scroller.style.height = "100px";
+ // Use hidden so we don't have scrollbar, to make sure the scrollport size
+ // is 100px x 100px, so view progress visibility range is 100px on block axis.
+ scroller.style.overflow = "hidden";
+
+ let content1 = document.createElement("div");
+ content1.style.height = "150px";
+ scroller.appendChild(content1);
+
+ let subject = document.createElement("div");
+ subject.style.width = "50px";
+ subject.style.height = "50px";
+ subject.style.viewTimelineName = "view_timeline";
+ scroller.appendChild(subject);
+
+ // Let |target| be the child of |subject|, so view-timeline-name property of
+ // |subject| is referenceable.
+ let target = document.createElement("div");
+ target.style.width = "10px";
+ target.style.height = "10px";
+ subject.appendChild(target);
+ gDiv = target;
+
+ let content2 = document.createElement("div");
+ content2.style.height = "150px";
+ scroller.appendChild(content2);
+
+ // So the DOM tree looks like this:
+ // <div class=scroller> <!-- "scroller", height: 100px; -->
+ // <div></div> <!-- "", height: 150px -->
+ // <div></div> <!-- "subject", height: 50px; -->
+ // <div></div> <!-- "", height: 150px; -->
+ // </div>
+ // The subject is in view when scroller.scrollTop is [50px, 200px].
+ document.getElementById("display").appendChild(scroller);
+ await waitForPaintsFlushed();
+
+ scroller.scrollTop = 0;
+ target.style.animation = "transform_anim 10s linear";
+ target.style.animationTimeline = "view_timeline";
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.OnMainThread,
+ "The scroll animation is out of view");
+
+ scroller.scrollTop = 50;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.OnMainThread,
+ "The scroll animation is 0%");
+
+ scroller.scrollTop = 125;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.OnMainThread,
+ "The scroll animation is 50%");
+
+ target.remove();
+ content2.remove();
+ subject.remove();
+ content1.remove();
+ scroller.remove();
+ gDiv = null;
+});
+
+</script>
+</html>
diff --git a/layout/style/test/file_animations_omta_scroll_rtl.html b/layout/style/test/file_animations_omta_scroll_rtl.html
new file mode 100644
index 0000000000..771cf6c38f
--- /dev/null
+++ b/layout/style/test/file_animations_omta_scroll_rtl.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+ <title>Test for css-animations running on the compositor thread with
+ scroll-timeline and right to left writing mode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <style type="text/css">
+ @keyframes transform_anim {
+ from { transform: translateX(-100px) }
+ to { transform: translateX(-200px) }
+ }
+
+ html {
+ min-width: 100%;
+ padding-left: 100px;
+ direction: rtl;
+ }
+
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+ </style>
+</head>
+<body>
+ <div id="display"></div>
+ <pre id="test"></pre>
+</body>
+<script type="application/javascript">
+"use strict";
+
+// Global state
+var gDiv = null;
+
+// Shortcut omta_is and friends by filling in the initial 'elem' argument
+// with gDiv.
+[ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) {
+ var origFn = window[fn];
+ window[fn] = function() {
+ var args = Array.from(arguments);
+ if (!(args[0] instanceof Element)) {
+ args.unshift(gDiv);
+ }
+ return origFn.apply(window, args);
+ };
+});
+
+// Shortcut new_div and done_div to update gDiv
+var originalNewDiv = window.new_div;
+window.new_div = function(style) {
+ [ gDiv ] = originalNewDiv(style);
+};
+var originalDoneDiv = window.done_div;
+window.done_div = function() {
+ originalDoneDiv();
+ gDiv = null;
+};
+
+// Bind the ok and todo to the opener, and close this window when we finish.
+var ok = opener.ok.bind(opener);
+var todo = opener.todo.bind(opener);
+function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+}
+
+waitUntilApzStable().then(() => {
+ runOMTATest(function() {
+ var onAbort = function() {
+ if (gDiv) {
+ done_div();
+ }
+ };
+ runAllAsyncAnimTests(onAbort).then(finish);
+ }, finish);
+});
+
+//----------------------------------------------------------------------
+//
+// Test cases
+//
+//----------------------------------------------------------------------
+
+// transform property with scroll-driven animations. The writing mode is from
+// right to left, so we have to use the negative scroll offset.
+addAsyncAnimTest(async function() {
+ new_div("animation: transform_anim 1s linear scroll(horizontal root);");
+ await waitForPaintsFlushed();
+
+ const root = document.scrollingElement;
+ const maxScroll = root.scrollWidth - root.clientWidth;
+ root.scrollLeft = -0.5 * maxScroll;
+ await waitForPaintsFlushed();
+
+ omta_is_approx("transform", { tx: -150 }, 0.1, RunningOn.Compositor,
+ "scroll transform animations should runs on compositor " +
+ "thread");
+
+ root.scrollLeft = 0;
+ done_div();
+});
+
+</script>
+</html>
diff --git a/layout/style/test/file_animations_with_disabled_properties.html b/layout/style/test/file_animations_with_disabled_properties.html
new file mode 100644
index 0000000000..5b206df693
--- /dev/null
+++ b/layout/style/test/file_animations_with_disabled_properties.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<head>
+ <meta charset=utf-8>
+ <style>
+ @keyframes enabled-and-disabled {
+ from {
+ left: 0px;
+ overflow-clip-box: padding-box;
+ }
+ to {
+ left: 100px;
+ overflow-clip-box: padding-box;
+ }
+ }
+ </style>
+ <script>
+ var is = opener.is.bind(opener);
+ var ok = opener.ok.bind(opener);
+ function finish() {
+ var o = opener;
+ self.close();
+ o.SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<div id="display"></div>
+<script>
+'use strict';
+
+var display = document.getElementById('display');
+display.style.animation = 'enabled-and-disabled 0.01s';
+
+var animation = display.getAnimations()[0];
+is(animation.effect.getKeyframes().length, 2,
+ 'Got two frames on the generated animation');
+
+ok(animation.effect.getKeyframes()[0].hasOwnProperty('left'),
+ 'Enabled property is set on initial keyframe');
+ok(!animation.effect.getKeyframes()[0].hasOwnProperty('overflowClipBox'),
+ 'Disabled property is not set on initial keyframe');
+
+ok(animation.effect.getKeyframes()[1].hasOwnProperty('left'),
+ 'Enabled property is set on final keyframe');
+ok(!animation.effect.getKeyframes()[1].hasOwnProperty('overflowClipBox'),
+ 'Disabled property is not set on final keyframe');
+
+finish();
+</script>
+</body>
diff --git a/layout/style/test/file_bug1055933_circle-xxl.png b/layout/style/test/file_bug1055933_circle-xxl.png
new file mode 100644
index 0000000000..3223a56900
--- /dev/null
+++ b/layout/style/test/file_bug1055933_circle-xxl.png
Binary files differ
diff --git a/layout/style/test/file_bug1089417_iframe.html b/layout/style/test/file_bug1089417_iframe.html
new file mode 100644
index 0000000000..95208dbc56
--- /dev/null
+++ b/layout/style/test/file_bug1089417_iframe.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1089417</title>
+ <style>
+ html { background: red }
+ @media (min-height: 300px) { html { background: green } }
+ </style>
+ <style id="s">/* empty */</style>
+ <script>
+ document.getElementById("s").disabled = true;
+ </script>
+</head>
+<body>
+
+</body>
+</html>
diff --git a/layout/style/test/file_bug1375944.html b/layout/style/test/file_bug1375944.html
new file mode 100644
index 0000000000..809ea4205b
--- /dev/null
+++ b/layout/style/test/file_bug1375944.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+@font-face {
+ font-family: Ahem;
+ src: url(Ahem.ttf);
+}
+#test {
+ display: inline-block;
+ font: 64px/1 Ahem;
+ margin-right: 1ex;
+}
+</style>
+<div id="test">X</div>
diff --git a/layout/style/test/file_bug1381233.html b/layout/style/test/file_bug1381233.html
new file mode 100644
index 0000000000..b82d309128
--- /dev/null
+++ b/layout/style/test/file_bug1381233.html
@@ -0,0 +1,4 @@
+<link rel="stylesheet" href="somestylesheet">
+<table><td>
+<embed src="whatever" type="application/x-shockwave-flash"></embed>
+</td></table>
diff --git a/layout/style/test/file_bug1443344.css b/layout/style/test/file_bug1443344.css
new file mode 100644
index 0000000000..74e81d1823
--- /dev/null
+++ b/layout/style/test/file_bug1443344.css
@@ -0,0 +1 @@
+#importTarget { color: red ! important }
diff --git a/layout/style/test/file_bug645998-1.css b/layout/style/test/file_bug645998-1.css
new file mode 100644
index 0000000000..328e6ed797
--- /dev/null
+++ b/layout/style/test/file_bug645998-1.css
@@ -0,0 +1 @@
+@import url("file_bug645998-2.css");
diff --git a/layout/style/test/file_bug645998-2.css b/layout/style/test/file_bug645998-2.css
new file mode 100644
index 0000000000..2d5edbe217
--- /dev/null
+++ b/layout/style/test/file_bug645998-2.css
@@ -0,0 +1 @@
+@import url("file_bug645998-1.css");
diff --git a/layout/style/test/file_bug829816.css b/layout/style/test/file_bug829816.css
new file mode 100644
index 0000000000..8f12ba6f56
--- /dev/null
+++ b/layout/style/test/file_bug829816.css
Binary files differ
diff --git a/layout/style/test/file_computed_style_bfcache_display_none.html b/layout/style/test/file_computed_style_bfcache_display_none.html
new file mode 100644
index 0000000000..d366aa68ee
--- /dev/null
+++ b/layout/style/test/file_computed_style_bfcache_display_none.html
@@ -0,0 +1,6 @@
+<div id=div style="display:none">Page 1</div>
+<script>
+ window.onload = function() {
+ opener.postMessage("loaded", "*");
+ }
+</script>
diff --git a/layout/style/test/file_computed_style_bfcache_display_none2.html b/layout/style/test/file_computed_style_bfcache_display_none2.html
new file mode 100644
index 0000000000..c337ca7214
--- /dev/null
+++ b/layout/style/test/file_computed_style_bfcache_display_none2.html
@@ -0,0 +1,6 @@
+<div>Page 2</div>
+<script>
+ window.onload = function() {
+ opener.postMessage("loaded", "*");
+ }
+</script>;
diff --git a/layout/style/test/file_font_loading_api_vframe.html b/layout/style/test/file_font_loading_api_vframe.html
new file mode 100644
index 0000000000..51dbbbee91
--- /dev/null
+++ b/layout/style/test/file_font_loading_api_vframe.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<style></style>
diff --git a/layout/style/test/file_shared_sheet_caching.css b/layout/style/test/file_shared_sheet_caching.css
new file mode 100644
index 0000000000..2ceb1b7e0b
--- /dev/null
+++ b/layout/style/test/file_shared_sheet_caching.css
@@ -0,0 +1,3 @@
+:root {
+ background-color: lime;
+}
diff --git a/layout/style/test/file_shared_sheet_caching.html b/layout/style/test/file_shared_sheet_caching.html
new file mode 100644
index 0000000000..1eb9a8ce31
--- /dev/null
+++ b/layout/style/test/file_shared_sheet_caching.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="stylesheet" href="file_shared_sheet_caching.css">
+<script>
+onload = function() {
+ if (parent != window) {
+ parent.childWindowLoaded(window);
+ } else {
+ window.opener.childWindowLoaded(window);
+ }
+}
+</script>
diff --git a/layout/style/test/file_specified_value_serialization_individual_transforms.html b/layout/style/test/file_specified_value_serialization_individual_transforms.html
new file mode 100644
index 0000000000..9dcf85f955
--- /dev/null
+++ b/layout/style/test/file_specified_value_serialization_individual_transforms.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for Bug 1207734 (individual transforms)</title>
+<!--
+ FIXME: This is only here in a separate file since it needs the
+ layout.css.individual-transform.enabled pref to be set when it runs and the
+ pref= annotation in mochitest.ini doesn't work on Android (bug 1393326).
+ Once we turn on that pref by default or fix bug 1393326 we can move this back
+ into test_specified_value_serialization.html.
+-->
+<script>
+const is = opener.is.bind(opener);
+function finish() {
+ const o = opener;
+ self.close();
+ o.SimpleTest.finish();
+}
+
+function runTest() {
+ // Test for rotate property serialization.
+ [
+ [" 90deg ", "90deg"],
+ [" 100grad ", "100grad"],
+ [" 100gRaD ", "100grad"],
+ [" 0.25turn ", "0.25turn"],
+ [" 0.25tUrN ", "0.25turn"],
+ [" 1.57RaD ", "1.57rad"],
+ ].forEach(function(arr) {
+ document.documentElement.style.rotate = arr[0];
+ is(document.documentElement.style.rotate, arr[1],
+ "bug-1207734: incorrect rotate serialization");
+ });
+ document.documentElement.style.rotate = "";
+
+ // Test for translate property serialization.
+ [
+ [" 50% 5px 6px ", "50% 5px 6px"],
+ [" 50% 10px 100px ", "50% 10px 100px"],
+ [" 4px 5px ", "4px 5px"],
+ [" 10% 10% 99px ", "10% 10% 99px"],
+ [" 50px ", "50px"],
+ ].forEach(function(arr) {
+ document.documentElement.style.translate = arr[0];
+ is(document.documentElement.style.translate, arr[1],
+ "bug-1207734: incorrect translate serialization");
+ });
+ document.documentElement.style.translate = "";
+
+ // Test for scale property serialization.
+ [
+ [" 10 ", "10"],
+ [" 10 20.5 ", "10 20.5"],
+ [" 10 20 30 ", "10 20 30"],
+ ].forEach(function(arr) {
+ document.documentElement.style.scale = arr[0];
+ is(document.documentElement.style.scale, arr[1],
+ "bug-1207734: incorrect scale serialization");
+ });
+
+ document.documentElement.style.scale = "";
+}
+
+runTest();
+finish();
+</script>
diff --git a/layout/style/test/flexbox_layout_testcases.js b/layout/style/test/flexbox_layout_testcases.js
new file mode 100644
index 0000000000..c8990877ab
--- /dev/null
+++ b/layout/style/test/flexbox_layout_testcases.js
@@ -0,0 +1,1317 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et: */
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/**
+ * For the purposes of this test, flex items are specified as a hash with a
+ * hash-entry for each CSS property that is to be set. In these per-property
+ * entries, the key is the property-name, and the value can be either of the
+ * following:
+ * (a) the property's specified value (which indicates that we don't need to
+ * bother checking the computed value of this particular property)
+ * ...OR...
+ * (b) an array with 2-3 entries...
+ * [specifiedValue, expectedComputedValue (, epsilon) ]
+ * ...which indicates that the property's computed value should be
+ * checked. The array's first entry (for the specified value) may be
+ * null; this means that no value should be explicitly specified for this
+ * property. The second entry is the property's expected computed
+ * value. The third (optional) entry is an epsilon value, which allows for
+ * fuzzy equality when testing the computed value.
+ *
+ * To allow these testcases to be re-used in both horizontal and vertical
+ * flex containers, we specify "width"/"min-width"/etc. using the aliases
+ * "_main-size", "_min-main-size", etc. The test code can map these
+ * placeholder names to their corresponding property-names using the maps
+ * defined below -- gRowPropertyMapping, gColumnPropertyMapping, etc.
+ *
+ * If the testcase needs to customize its flex container at all (e.g. by
+ * specifying a custom container-size), it can do so by including a hash
+ * called "container_properties", with propertyName:propertyValue mappings.
+ * (This hash can use aliased property-names like "_main-size" as well.)
+ */
+
+// The standard main-size we'll use for our flex container when setting up
+// the testcases defined below:
+var gDefaultFlexContainerSize = "200px";
+
+// Left-to-right versions of placeholder property-names used in
+// testcases below:
+var gRowPropertyMapping = {
+ "_main-size": "width",
+ "_min-main-size": "min-width",
+ "_max-main-size": "max-width",
+ "_border-main-start-width": "border-left-width",
+ "_border-main-end-width": "border-right-width",
+ "_padding-main-start": "padding-left",
+ "_padding-main-end": "padding-right",
+ "_margin-main-start": "margin-left",
+ "_margin-main-end": "margin-right",
+};
+
+// Right-to-left versions of placeholder property-names used in
+// testcases below:
+var gRowReversePropertyMapping = {
+ "_main-size": "width",
+ "_min-main-size": "min-width",
+ "_max-main-size": "max-width",
+ "_border-main-start-width": "border-right-width",
+ "_border-main-end-width": "border-left-width",
+ "_padding-main-start": "padding-right",
+ "_padding-main-end": "padding-left",
+ "_margin-main-start": "margin-right",
+ "_margin-main-end": "margin-left",
+};
+
+// Top-to-bottom versions of placeholder property-names used in
+// testcases below:
+var gColumnPropertyMapping = {
+ "_main-size": "height",
+ "_min-main-size": "min-height",
+ "_max-main-size": "max-height",
+ "_border-main-start-width": "border-top-width",
+ "_border-main-end-width": "border-bottom-width",
+ "_padding-main-start": "padding-top",
+ "_padding-main-end": "padding-bottom",
+ "_margin-main-start": "margin-top",
+ "_margin-main-end": "margin-bottom",
+};
+
+// Bottom-to-top versions of placeholder property-names used in
+// testcases below:
+var gColumnReversePropertyMapping = {
+ "_main-size": "height",
+ "_min-main-size": "min-height",
+ "_max-main-size": "max-height",
+ "_border-main-start-width": "border-bottom-width",
+ "_border-main-end-width": "border-top-width",
+ "_padding-main-start": "padding-bottom",
+ "_padding-main-end": "padding-top",
+ "_margin-main-start": "margin-bottom",
+ "_margin-main-end": "margin-top",
+};
+
+// The list of actual testcase definitions:
+var gFlexboxTestcases = [
+ // No flex properties specified --> should just use 'width' for sizing
+ {
+ items: [
+ { "_main-size": ["40px", "40px"] },
+ { "_main-size": ["65px", "65px"] },
+ ],
+ },
+ // flex-basis is specified:
+ {
+ items: [
+ { "flex-basis": "50px", "_main-size": [null, "50px"] },
+ {
+ "flex-basis": "20px",
+ "_main-size": [null, "20px"],
+ },
+ ],
+ },
+ // flex-basis is *large* -- sum of flex-basis values is > flex container size:
+ // (w/ 0 flex-shrink so we don't shrink):
+ {
+ items: [
+ {
+ flex: "0 0 150px",
+ "_main-size": [null, "150px"],
+ },
+ {
+ flex: "0 0 90px",
+ "_main-size": [null, "90px"],
+ },
+ ],
+ },
+ // flex-basis is *large* -- each flex-basis value is > flex container size:
+ // (w/ 0 flex-shrink so we don't shrink):
+ {
+ items: [
+ {
+ flex: "0 0 250px",
+ "_main-size": [null, "250px"],
+ },
+ {
+ flex: "0 0 400px",
+ "_main-size": [null, "400px"],
+ },
+ ],
+ },
+ // flex-basis has percentage value:
+ {
+ items: [
+ {
+ "flex-basis": "30%",
+ "_main-size": [null, "60px"],
+ },
+ {
+ "flex-basis": "45%",
+ "_main-size": [null, "90px"],
+ },
+ ],
+ },
+ // flex-basis has calc(percentage) value:
+ {
+ items: [
+ {
+ "flex-basis": "calc(20%)",
+ "_main-size": [null, "40px"],
+ },
+ {
+ "flex-basis": "calc(80%)",
+ "_main-size": [null, "160px"],
+ },
+ ],
+ },
+ // flex-basis has calc(percentage +/- length) value:
+ {
+ items: [
+ {
+ "flex-basis": "calc(10px + 20%)",
+ "_main-size": [null, "50px"],
+ },
+ {
+ "flex-basis": "calc(60% - 1px)",
+ "_main-size": [null, "119px"],
+ },
+ ],
+ },
+ // flex-grow is specified:
+ {
+ items: [
+ {
+ flex: "1",
+ "_main-size": [null, "60px"],
+ },
+ {
+ flex: "2",
+ "_main-size": [null, "120px"],
+ },
+ {
+ flex: "0 20px",
+ "_main-size": [null, "20px"],
+ },
+ ],
+ },
+ // Same ratio as prev. testcase; making sure we handle float inaccuracy
+ {
+ items: [
+ {
+ flex: "100000",
+ "_main-size": [null, "60px"],
+ },
+ {
+ flex: "200000",
+ "_main-size": [null, "120px"],
+ },
+ {
+ flex: "0.000001 20px",
+ "_main-size": [null, "20px"],
+ },
+ ],
+ },
+ // Same ratio as prev. testcase, but with items cycled and w/
+ // "flex: none" & explicit size instead of "flex: 0 20px"
+ {
+ items: [
+ {
+ flex: "none",
+ "_main-size": ["20px", "20px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "60px"],
+ },
+ {
+ flex: "2",
+ "_main-size": [null, "120px"],
+ },
+ ],
+ },
+
+ // ...and now with flex-grow:[huge] to be sure we handle infinite float values
+ // gracefully.
+ {
+ items: [
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "200px"],
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "9999999999999999999999999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "99999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "99999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "99999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "99999999999999999999999999999999999",
+ "_main-size": [null, "50px"],
+ },
+ ],
+ },
+
+ // And now, some testcases to check that we handle float accumulation error
+ // gracefully.
+
+ // First, a testcase with just a custom-sized huge container, to be sure we'll
+ // be able to handle content on that scale, in the subsequent more-complex
+ // testcases:
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "1",
+ "_main-size": [null, "9000000px"],
+ },
+ ],
+ },
+ // ...and now with two flex items dividing up that container's huge size:
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "2",
+ "_main-size": [null, "6000000px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "3000000px"],
+ },
+ ],
+ },
+
+ // OK, now to actually test accumulation error. Below, we have six flex items
+ // splitting up the container's size, with huge differences between flex
+ // weights. For simplicity, I've set up the weights so that they sum exactly
+ // to the container's size in px. So 1 unit of flex *should* get you 1px.
+ //
+ // NOTE: The expected computed "_main-size" values for the flex items below
+ // appear to add up to more than their container's size, which would suggest
+ // that they overflow their container unnecessarily. But they don't actually
+ // overflow -- this discrepancy is simply because Gecko's code for reporting
+ // computed-sizes rounds to 6 significant figures (in particular, the method
+ // (nsTSubstring_CharT::AppendFloat() does this). Internally, in app-units,
+ // the child frames' main-sizes add up exactly to the container's main-size,
+ // as you'd hope & expect.
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "3000000",
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px"],
+ },
+ {
+ flex: "2999999",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999998",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px", 0.2],
+ },
+ ],
+ },
+ // Same flex items as previous testcase, but now reordered such that the items
+ // with tiny flex weights are all listed last:
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "3000000",
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999999",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999998",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "1px", 0.2],
+ },
+ ],
+ },
+ // Same flex items as previous testcase, but now reordered such that the items
+ // with tiny flex weights are all listed first:
+ {
+ container_properties: {
+ "_main-size": "9000000px",
+ },
+ items: [
+ {
+ flex: "1",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths:
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "1",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths:
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "1",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths:
+ "_main-size": [null, "1px", 0.2],
+ },
+ {
+ flex: "3000000",
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999999",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ {
+ flex: "2999998",
+ // NOTE: Expected value is off slightly, from float error when
+ // resolving flexible lengths & when generating computed value string:
+ "_main-size": [null, "3000000px"],
+ },
+ ],
+ },
+
+ // Trying "flex: auto" (== "1 1 auto") w/ a mix of flex-grow/flex-basis values
+ {
+ items: [
+ {
+ flex: "auto",
+ "_main-size": [null, "45px"],
+ },
+ {
+ flex: "2",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "20px 1 0",
+ "_main-size": [null, "65px"],
+ },
+ ],
+ },
+ // Same as previous, but with items cycled & different syntax
+ {
+ items: [
+ {
+ flex: "20px",
+ "_main-size": [null, "65px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "45px"],
+ },
+ {
+ flex: "2",
+ "_main-size": [null, "90px"],
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "2",
+ "_main-size": [null, "100px"],
+ border: "0px dashed",
+ "_border-main-start-width": ["5px", "5px"],
+ "_border-main-end-width": ["15px", "15px"],
+ "_margin-main-start": ["22px", "22px"],
+ "_margin-main-end": ["8px", "8px"],
+ },
+ {
+ flex: "1",
+ "_main-size": [null, "50px"],
+ "_margin-main-start": ["auto", "0px"],
+ "_padding-main-end": ["auto", "0px"],
+ },
+ ],
+ },
+ // Test negative flexibility:
+
+ // Basic testcase: just 1 item (relying on initial "flex-shrink: 1") --
+ // should shrink to container size.
+ {
+ items: [{ "_main-size": ["400px", "200px"] }],
+ },
+ // ...and now with a "flex" specification and a different flex-shrink value:
+ {
+ items: [
+ {
+ flex: "4 2 250px",
+ "_main-size": [null, "200px"],
+ },
+ ],
+ },
+ // ...and now with multiple items, which all shrink proportionally (by 50%)
+ // to fit to the container, since they have the same (initial) flex-shrink val
+ {
+ items: [
+ { "_main-size": ["80px", "40px"] },
+ { "_main-size": ["40px", "20px"] },
+ { "_main-size": ["30px", "15px"] },
+ { "_main-size": ["250px", "125px"] },
+ ],
+ },
+ // ...and now with positive flexibility specified. (should have no effect, so
+ // everything still shrinks by the same proportion, since the flex-shrink
+ // values are all the same).
+ {
+ items: [
+ {
+ flex: "4 3 100px",
+ "_main-size": [null, "80px"],
+ },
+ {
+ flex: "5 3 50px",
+ "_main-size": [null, "40px"],
+ },
+ {
+ flex: "0 3 100px",
+ "_main-size": [null, "80px"],
+ },
+ ],
+ },
+ // ...and now with *different* flex-shrink values:
+ {
+ items: [
+ {
+ flex: "4 2 50px",
+ "_main-size": [null, "30px"],
+ },
+ {
+ flex: "5 3 50px",
+ "_main-size": [null, "20px"],
+ },
+ {
+ flex: "0 0 150px",
+ "_main-size": [null, "150px"],
+ },
+ ],
+ },
+ // Same ratio as prev. testcase; making sure we handle float inaccuracy
+ {
+ items: [
+ {
+ flex: "4 20000000 50px",
+ "_main-size": [null, "30px"],
+ },
+ {
+ flex: "5 30000000 50px",
+ "_main-size": [null, "20px"],
+ },
+ {
+ flex: "0 0.0000001 150px",
+ "_main-size": [null, "150px"],
+ },
+ ],
+ },
+ // Another "different flex-shrink values" testcase:
+ {
+ items: [
+ {
+ flex: "4 2 115px",
+ "_main-size": [null, "69px"],
+ },
+ {
+ flex: "5 1 150px",
+ "_main-size": [null, "120px"],
+ },
+ {
+ flex: "1 4 30px",
+ "_main-size": [null, "6px"],
+ },
+ {
+ flex: "1 0 5px",
+ "_main-size": [null, "5px"],
+ },
+ ],
+ },
+
+ // ...and now with min-size (clamping the effects of flex-shrink on one item):
+ {
+ items: [
+ {
+ flex: "4 5 75px",
+ "_min-main-size": "50px",
+ "_main-size": [null, "50px"],
+ },
+ {
+ flex: "5 5 100px",
+ "_main-size": [null, "62.5px"],
+ },
+ {
+ flex: "0 4 125px",
+ "_main-size": [null, "87.5px"],
+ },
+ ],
+ },
+
+ // Test a min-size that's much larger than initial preferred size, but small
+ // enough that our flexed size pushes us over it:
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "110px",
+ "_main-size": ["50px", "125px"],
+ },
+ {
+ flex: "auto",
+ "_main-size": [null, "75px"],
+ },
+ ],
+ },
+
+ // Test a min-size that's much larger than initial preferred size, and is
+ // even larger than our positively-flexed size, so that we have to increase it
+ // (as a 'min violation') after we've flexed.
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "150px",
+ "_main-size": ["50px", "150px"],
+ },
+ {
+ flex: "auto",
+ "_main-size": [null, "50px"],
+ },
+ ],
+ },
+
+ // Test min-size on multiple items simultaneously:
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "20px",
+ "_main-size": [null, "20px"],
+ },
+ {
+ flex: "9 auto",
+ "_min-main-size": "150px",
+ "_main-size": ["50px", "180px"],
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "1 1 0px",
+ "_min-main-size": "90px",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "1 1 0px",
+ "_min-main-size": "80px",
+ "_main-size": [null, "80px"],
+ },
+ {
+ flex: "1 1 40px",
+ "_main-size": [null, "30px"],
+ },
+ ],
+ },
+
+ // Test a case where _min-main-size will be violated on different items in
+ // successive iterations of the "resolve the flexible lengths" loop
+ {
+ items: [
+ {
+ flex: "1 2 100px",
+ "_min-main-size": "90px",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "1 1 100px",
+ "_min-main-size": "70px",
+ "_main-size": [null, "70px"],
+ },
+ {
+ flex: "1 1 100px",
+ "_main-size": [null, "40px"],
+ },
+ ],
+ },
+
+ // Test some cases that have a min-size violation on one item and a
+ // max-size violation on another:
+
+ // Here, both items initially grow to 100px. That violates both
+ // items' sizing constraints (it's smaller than the min-size and larger than
+ // the max-size), so we clamp both of them and sum the clamping-differences:
+ //
+ // (130px - 100px) + (50px - 100px) = (30px) + (-50px) = -20px
+ //
+ // This sum is negative, so (per spec) we freeze the item that had its
+ // max-size violated (the second one) and restart the algorithm. This time,
+ // all the available space (200px - 50px = 150px) goes to the not-yet-frozen
+ // first item, and that puts it above its min-size, so all is well.
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "130px",
+ "_main-size": [null, "150px"],
+ },
+ {
+ flex: "auto",
+ "_max-main-size": "50px",
+ "_main-size": [null, "50px"],
+ },
+ ],
+ },
+
+ // As above, both items initially grow to 100px, and that violates both items'
+ // constraints. However, now the sum of the clamping differences is:
+ //
+ // (130px - 100px) + (80px - 100px) = (30px) + (-20px) = 10px
+ //
+ // This sum is positive, so (per spec) we freeze the item that had its
+ // min-size violated (the first one) and restart the algorithm. This time,
+ // all the available space (200px - 130px = 70px) goes to the not-yet-frozen
+ // second item, and that puts it below its max-size, so all is well.
+ {
+ items: [
+ {
+ flex: "auto",
+ "_min-main-size": "130px",
+ "_main-size": [null, "130px"],
+ },
+ {
+ flex: "auto",
+ "_max-main-size": "80px",
+ "_main-size": [null, "70px"],
+ },
+ ],
+ },
+
+ // As above, both items initially grow to 100px, and that violates both items'
+ // constraints. So we clamp both items and sum the clamping differences to
+ // see what to do next. The sum is:
+ //
+ // (80px - 100px) + (120px - 100px) = (-20px) + (20px) = 0px
+ //
+ // Per spec, if the sum is 0, we're done -- we leave both items at their
+ // clamped sizes.
+ {
+ items: [
+ {
+ flex: "auto",
+ "_max-main-size": "80px",
+ "_main-size": [null, "80px"],
+ },
+ {
+ flex: "auto",
+ "_min-main-size": "120px",
+ "_main-size": [null, "120px"],
+ },
+ ],
+ },
+
+ // Test cases where flex-grow sums to less than 1:
+ // ===============================================
+ // This makes us treat the flexibilities like "fraction of free space"
+ // instead of weights, so that e.g. a single item with "flex-grow: 0.1"
+ // will only get 10% of the free space instead of all of the free space.
+
+ // Basic cases where flex-grow sum is less than 1:
+ {
+ items: [
+ {
+ flex: "0.1 100px",
+ "_main-size": [null, "110px"], // +10% of free space
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "0.8 0px",
+ "_main-size": [null, "160px"], // +80% of free space
+ },
+ ],
+ },
+
+ // ... and now with two flex items:
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "110px"], // +40% of free space
+ },
+ {
+ flex: "0.2 30px",
+ "_main-size": [null, "50px"], // +20% of free space
+ },
+ ],
+ },
+
+ // ...and now with max-size modifying how much free space one item can take:
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "110px"], // +40% of free space
+ },
+ {
+ flex: "0.2 30px",
+ "_max-main-size": "35px",
+ "_main-size": [null, "35px"], // +20% free space, then clamped
+ },
+ ],
+ },
+ // ...and now with a max-size smaller than our flex-basis:
+ // (This makes us freeze the second item right away, before we compute
+ // the initial free space.)
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "118px"], // +40% of 200px-70px-10px
+ },
+ {
+ flex: "0.2 30px",
+ "_max-main-size": "10px",
+ "_main-size": [null, "10px"], // immediately frozen
+ },
+ ],
+ },
+ // ...and now with a max-size and a huge flex-basis, such that we initially
+ // have negative free space, which makes the "% of [original] free space"
+ // calculations a bit more subtle. We set the "original free space" after
+ // we've clamped the second item (the first time the free space is positive).
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "118px"], // +40% of free space _after freezing
+ // the other item_
+ },
+ {
+ flex: "0.2 150px",
+ "_max-main-size": "10px",
+ "_main-size": [null, "10px"], // clamped immediately
+ },
+ ],
+ },
+
+ // Now with min-size modifying how much free space our items take:
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "110px"], // +40% of free space
+ },
+ {
+ flex: "0.2 30px",
+ "_min-main-size": "70px",
+ "_main-size": [null, "70px"], // +20% free space, then clamped
+ },
+ ],
+ },
+
+ // ...and now with a large enough min-size that it prevents the other flex
+ // item from taking its full desired portion of the original free space:
+ {
+ items: [
+ {
+ flex: "0.4 70px",
+ "_main-size": [null, "80px"], // (Can't take my full +40% of
+ // free space due to other item's
+ // large min-size.)
+ },
+ {
+ flex: "0.2 30px",
+ "_min-main-size": "120px",
+ "_main-size": [null, "120px"], // +20% free space, then clamped
+ },
+ ],
+ },
+ // ...and now with a large-enough min-size that it pushes the other flex item
+ // to actually shrink a bit (with default "flex-shrink:1"):
+ {
+ items: [
+ {
+ flex: "0.3 30px",
+ "_main-size": [null, "20px"], // -10px, instead of desired +45px
+ },
+ {
+ flex: "0.2 20px",
+ "_min-main-size": "180px",
+ "_main-size": [null, "180px"], // +160px, instead of desired +30px
+ },
+ ],
+ },
+
+ // In this case, the items' flexibilities don't initially sum to < 1, but they
+ // do after we freeze the third item for violating its max-size.
+ {
+ items: [
+ {
+ flex: "0.3 30px",
+ "_main-size": [null, "75px"],
+ // 1st loop: desires (0.3 / 5) * 150px = 9px. Tentatively granted.
+ // 2nd loop: desires 0.3 * 150px = 45px. Tentatively granted.
+ // 3rd loop: desires 0.3 * 150px = 45px. Granted +45px.
+ },
+ {
+ flex: "0.2 20px",
+ "_max-main-size": "30px",
+ "_main-size": [null, "30px"],
+ // First loop: desires (0.2 / 5) * 150px = 6px. Tentatively granted.
+ // Second loop: desires 0.2 * 150px = 30px. Frozen at +10px.
+ },
+ {
+ flex: "4.5 0px",
+ "_max-main-size": "20px",
+ "_main-size": [null, "20px"],
+ // First loop: desires (4.5 / 5) * 150px = 135px. Frozen at +20px.
+ },
+ ],
+ },
+
+ // Make sure we calculate "original free space" correctly when one of our
+ // flex items will be clamped right away, due to max-size preventing it from
+ // growing at all:
+ {
+ // Here, the second flex item is effectively inflexible; it's
+ // immediately frozen at 40px since we're growing & this item's max size
+ // trivially prevents it from growing. This leaves us with an "original
+ // free space" of 60px. The first flex item takes half of that, due to
+ // its flex-grow value of 0.5.
+ items: [
+ {
+ flex: "0.5 100px",
+ "_main-size": [null, "130px"],
+ },
+ {
+ flex: "1 98px",
+ "_max-main-size": "40px",
+ "_main-size": [null, "40px"],
+ },
+ ],
+ },
+ {
+ // Same as previous example, but with a larger flex-basis on the second
+ // element (which shouldn't ultimately matter, because its max size clamps
+ // its size immediately anyway).
+ items: [
+ {
+ flex: "0.5 100px",
+ "_main-size": [null, "130px"],
+ },
+ {
+ flex: "1 101px",
+ "_max-main-size": "40px",
+ "_main-size": [null, "40px"],
+ },
+ ],
+ },
+
+ {
+ // Here, the third flex item is effectively inflexible; it's immediately
+ // frozen at 0px since we're growing & this item's max size trivially
+ // prevents it from growing. This leaves us with an "original free space" of
+ // 100px. The first flex item takes 40px, and the third takes 50px, due to
+ // their flex values of 0.4 and 0.5.
+ items: [
+ {
+ flex: "0.4 50px",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "0.5 50px",
+ "_main-size": [null, "100px"],
+ },
+ {
+ flex: "0 90px",
+ "_max-main-size": "0px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+ {
+ // Same as previous example, but with slightly larger flex-grow values on
+ // the first and second items, which sum to 1.0 and produce slightly larger
+ // main sizes. This demonstrates that there's no discontinuity between the
+ // "< 1.0 sum" to ">= 1.0 sum" behavior, in this situation at least.
+ items: [
+ {
+ flex: "0.45 50px",
+ "_main-size": [null, "95px"],
+ },
+ {
+ flex: "0.55 50px",
+ "_main-size": [null, "105px"],
+ },
+ {
+ flex: "0 90px",
+ "_max-main-size": "0px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+
+ // Test cases where flex-shrink sums to less than 1:
+ // =================================================
+ // This makes us treat the flexibilities more like "fraction of (negative)
+ // free space" instead of weights, so that e.g. a single item with
+ // "flex-shrink: 0.1" will only shrink by 10% of amount that it overflows
+ // its container by.
+ //
+ // It gets a bit more complex when there are multiple flex items, because
+ // flex-shrink is scaled by the flex-basis before it's used as a weight. But
+ // even with that scaling, the general principal is that e.g. if the
+ // flex-shrink values *sum* to 0.6, then the items will collectively only
+ // shrink by 60% (and hence will still overflow).
+
+ // Basic cases where flex-grow sum is less than 1:
+ {
+ items: [
+ {
+ flex: "0 0.1 300px",
+ "_main-size": [null, "290px"], // +10% of (negative) free space
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ flex: "0 0.8 400px",
+ "_main-size": [null, "240px"], // +80% of (negative) free space
+ },
+ ],
+ },
+
+ // ...now with two flex items, with the same flex-basis value:
+ {
+ items: [
+ {
+ flex: "0 0.4 150px",
+ "_main-size": [null, "110px"], // +40% of (negative) free space
+ },
+ {
+ flex: "0 0.2 150px",
+ "_main-size": [null, "130px"], // +20% of (negative) free space
+ },
+ ],
+ },
+
+ // ...now with two flex items, with different flex-basis values (and hence
+ // differently-scaled flex factors):
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "76px"],
+ },
+ {
+ flex: "0 0.1 200px",
+ "_main-size": [null, "184px"],
+ },
+ ],
+ // Notes:
+ // - Free space: -100px
+ // - Sum of flex-shrink factors: 0.3 + 0.1 = 0.4
+ // - Since that sum ^ is < 1, we'll only distribute that fraction of
+ // the free space. We'll distribute: -100px * 0.4 = -40px
+ //
+ // - 1st item's scaled flex factor: 0.3 * 100px = 30
+ // - 2nd item's scaled flex factor: 0.1 * 200px = 20
+ // - 1st item's share of distributed free space: 30/(30+20) = 60%
+ // - 2nd item's share of distributed free space: 20/(30+20) = 40%
+ //
+ // SO:
+ // - 1st item gets 60% * -40px = -24px. 100px-24px = 76px
+ // - 2nd item gets 40% * -40px = -16px. 200px-16px = 184px
+ },
+
+ // ...now with min-size modifying how much one item can shrink:
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "70px"],
+ },
+ {
+ flex: "0 0.1 200px",
+ "_min-main-size": "190px",
+ "_main-size": [null, "190px"],
+ },
+ ],
+ // Notes:
+ // - We proceed as in previous testcase, but clamp the second flex item
+ // at its min main size.
+ // - After that point, we have a total flex-shrink of = 0.3, so we
+ // distribute 0.3 * -100px = -30px to the remaining unfrozen flex
+ // items. Since there's only one unfrozen item left, it gets all of it.
+ },
+
+ // ...now with min-size larger than our flex-basis:
+ // (This makes us freeze the second item right away, before we compute
+ // the initial free space.)
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "55px"], // +30% of 200px-100px-250px
+ },
+ {
+ flex: "0 0.1 200px",
+ "_min-main-size": "250px",
+ "_main-size": [null, "250px"], // immediately frozen
+ },
+ ],
+ // (Same as previous example, except the min-main-size prevents the
+ // second item from shrinking at all)
+ },
+
+ // ...and now with a min-size and a small flex-basis, such that we initially
+ // have positive free space, which makes the "% of [original] free space"
+ // calculations a bit more subtle. We set the "original free space" after
+ // we've clamped the second item (the first time the free space is negative).
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "70px"],
+ },
+ {
+ flex: "0 0.1 50px",
+ "_min-main-size": "200px",
+ "_main-size": [null, "200px"],
+ },
+ ],
+ },
+
+ // Now with max-size making an item shrink more than its flex-shrink value
+ // calls for:
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "70px"],
+ },
+ {
+ flex: "0 0.1 200px",
+ "_max-main-size": "150px",
+ "_main-size": [null, "150px"],
+ },
+ ],
+ // Notes:
+ // - We proceed as in an earlier testcase, but clamp the second flex item
+ // at its max main size.
+ // - After that point, we have a total flex-shrink of = 0.3, so we
+ // distribute 0.3 * -100px = -30px to the remaining unfrozen flex
+ // items. Since there's only one unfrozen item left, it gets all of it.
+ },
+
+ // ...and now with a small enough max-size that it prevents the other flex
+ // item from taking its full desired portion of the (negative) original free
+ // space:
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "90px"],
+ },
+ {
+ flex: "0 0.1 200px",
+ "_max-main-size": "110px",
+ "_main-size": [null, "110px"],
+ },
+ ],
+ // Notes:
+ // - We proceed as in an earlier testcase, but clamp the second flex item
+ // at its max main size.
+ // - After that point, we have a total flex-shrink of 0.3, which would
+ // have us distribute 0.3 * -100px = -30px to the (one) remaining
+ // unfrozen flex item. But our remaining free space is only -10px at
+ // that point, so we distribute that instead.
+ },
+
+ // ...and now with a small enough max-size that it pushes the other flex item
+ // to actually grow a bit (with custom "flex-grow: 1" for this testcase):
+ {
+ items: [
+ {
+ flex: "1 0.3 100px",
+ "_main-size": [null, "120px"],
+ },
+ {
+ flex: "1 0.1 200px",
+ "_max-main-size": "80px",
+ "_main-size": [null, "80px"],
+ },
+ ],
+ },
+
+ // In this case, the items' flexibilities don't initially sum to < 1, but they
+ // do after we freeze the third item for violating its min-size.
+ {
+ items: [
+ {
+ flex: "0 0.3 100px",
+ "_main-size": [null, "76px"],
+ },
+ {
+ flex: "0 0.1 150px",
+ "_main-size": [null, "138px"],
+ },
+ {
+ flex: "0 0.8 10px",
+ "_min-main-size": "40px",
+ "_main-size": [null, "40px"],
+ },
+ ],
+ // Notes:
+ // - We immediately freeze the 3rd item, since we're shrinking and its
+ // min size obviously prevents it from shrinking at all. This leaves
+ // 200px - 100px - 150px - 40px = -90px of "initial free space".
+ //
+ // - Our remaining flexible items have a total flex-shrink of 0.4,
+ // so we can distribute a total of 0.4 * -90px = -36px
+ //
+ // - We distribute that space using *scaled* flex factors:
+ // * 1st item's scaled flex factor: 0.3 * 100px = 30
+ // * 2nd item's scaled flex factor: 0.1 * 150px = 15
+ // ...which means...
+ // * 1st item's share of distributed free space: 30/(30+15) = 2/3
+ // * 2nd item's share of distributed free space: 15/(30+15) = 1/3
+ //
+ // SO:
+ // - 1st item gets 2/3 * -36px = -24px. 100px - 24px = 76px
+ // - 2nd item gets 1/3 * -36px = -12px. 150px - 12px = 138px
+ },
+
+ // In this case, the items' flexibilities sum to > 1, in part due to an item
+ // that *can't actually shrink* due to its 0 flex-basis (which gives it a
+ // "scaled flex factor" of 0). This prevents us from triggering the special
+ // behavior for flexibilities that sum to less than 1, and as a result, the
+ // first item ends up absorbing all of the free space.
+ {
+ items: [
+ {
+ flex: "0 .5 300px",
+ "_main-size": [null, "200px"],
+ },
+ {
+ flex: "0 5 0px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+
+ // This case is similar to the one above, but with a *barely* nonzero base
+ // size for the second item. This should produce a result similar to the case
+ // above. (In particular, we should first distribute a very small amount of
+ // negative free space to the second item, getting it to approximately zero,
+ // and distribute the bulk of the negative free space to the first item,
+ // getting it to approximately 200px.)
+ {
+ items: [
+ {
+ flex: "0 .5 300px",
+ "_main-size": [null, "200px"],
+ },
+ {
+ flex: "0 1 0.01px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+ // This case is similar to the ones above, but now we've increased the
+ // flex-shrink value on the second-item so that it claims enough of the
+ // negative free space to go below its min-size (0px). So, it triggers a min
+ // violation & is frozen. For the loop *after* the min violation, the sum of
+ // the remaining flex items' flex-shrink values is less than 1, so we trigger
+ // the special <1 behavior and only distribute half of the remaining
+ // (negative) free space to the first item (instead of all of it).
+ {
+ items: [
+ {
+ flex: "0 .5 300px",
+ "_main-size": [null, "250px"],
+ },
+ {
+ flex: "0 5 0.01px",
+ "_main-size": [null, "0px"],
+ },
+ ],
+ },
+];
diff --git a/layout/style/test/gen-css-properties.py b/layout/style/test/gen-css-properties.py
new file mode 100644
index 0000000000..1d6fad226d
--- /dev/null
+++ b/layout/style/test/gen-css-properties.py
@@ -0,0 +1,24 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+import os
+import sys
+import subprocess
+
+
+def main(output, css_properties, exe):
+ # moz.build passes in the exe name without any path, so to run it we need to
+ # prepend the './'
+ run_exe = exe if os.path.isabs(exe) else "./%s" % exe
+
+ # Use universal_newlines so everything is '\n', which gets converted to
+ # '\r\n' when writing out the file in Windows.
+ data = subprocess.check_output([run_exe], universal_newlines=True)
+ with open(css_properties) as f:
+ data += f.read()
+ output.write(data)
+
+
+if __name__ == "__main__":
+ main(sys.stdout, *sys.argv[1:])
diff --git a/layout/style/test/gtest/ImportScannerTest.cpp b/layout/style/test/gtest/ImportScannerTest.cpp
new file mode 100644
index 0000000000..c0b43221dd
--- /dev/null
+++ b/layout/style/test/gtest/ImportScannerTest.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/ImportScanner.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+using namespace mozilla;
+
+static nsTArray<nsString> Scan(const char* aCssCode) {
+ nsTArray<nsString> urls;
+ ImportScanner scanner;
+ scanner.Start();
+ urls.AppendElements(scanner.Scan(NS_ConvertUTF8toUTF16(aCssCode)));
+ urls.AppendElements(scanner.Stop());
+ return urls;
+}
+
+TEST(ImportScanner, Simple)
+{
+ auto urls = Scan(
+ "/* Something something */ "
+ "@charset \"utf-8\";"
+ "@import url(bar);"
+ "@import uRL( baz );"
+ "@import \"bazz)\"");
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz)"_ns);
+}
+
+TEST(ImportScanner, UrlWithQuotes)
+{
+ auto urls = Scan(
+ "/* Something something */ "
+ "@import url(\"bar\");"
+ "@import\tuRL( \"baz\" );"
+ "@imPort\turL( 'bazz' );"
+ "something else {}"
+ "@import\turL( 'bazz' ); ");
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz"_ns);
+}
+
+TEST(ImportScanner, MediaIsIgnored)
+{
+ auto urls = Scan(
+ "@chArset \"utf-8\";"
+ "@import url(\"bar\") print;"
+ "/* Something something */ "
+ "@import\tuRL( \"baz\" ) (min-width: 100px);"
+ "@import\turL( bazz ) (max-width: 100px);");
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz"_ns);
+}
+
+TEST(ImportScanner, Layers)
+{
+ auto urls = Scan(
+ "@layer foo, bar;\n"
+ "@import url(\"bar\") layer(foo);"
+ "@import url(\"baz\");"
+ "@import url(bazz);"
+ "@layer block {}"
+ // This one below is invalid now and shouldn't be scanned.
+ "@import\turL( 'bazzz' ); ");
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz"_ns);
+}
+
+TEST(ImportScanner, Supports)
+{
+ auto urls = Scan(
+ // Supported feature, should be included.
+ "@import url(bar) supports(display: block);"
+ // Unsupported feature, should not be included.
+ "@import url(baz) supports(foo: bar);"
+ // Supported condition with operator, should be included.
+ "@import url(bazz) supports((display: flex) and (display: block));"
+ // Unsupported condition with function, should be not included.
+ "@import url(bazzz) supports(selector(foo:bar(baz)));"
+ // Supported large condition with layer, supports, media list
+ "@import url(bazzzz) layer(A.B) supports(display: flex) (max-width: "
+ "100px)");
+
+ if (StaticPrefs::layout_css_import_supports_enabled()) {
+ // If the pref is enabled, expect the supports conditions to be evaluated
+ // and unsupported to not be emitted.
+
+ ASSERT_EQ(urls.Length(), 3u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"bazz"_ns);
+ ASSERT_EQ(urls[2], u"bazzzz"_ns);
+ } else {
+ // If disabled, all imports should be included as the supports conditions
+ // should be ignored.
+
+ ASSERT_EQ(urls.Length(), 5u);
+ ASSERT_EQ(urls[0], u"bar"_ns);
+ ASSERT_EQ(urls[1], u"baz"_ns);
+ ASSERT_EQ(urls[2], u"bazz"_ns);
+ ASSERT_EQ(urls[3], u"bazzz"_ns);
+ ASSERT_EQ(urls[4], u"bazzzz"_ns);
+ }
+}
diff --git a/layout/style/test/gtest/StyloParsingBench.cpp b/layout/style/test/gtest/StyloParsingBench.cpp
new file mode 100644
index 0000000000..4c14ebed7c
--- /dev/null
+++ b/layout/style/test/gtest/StyloParsingBench.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "gtest/MozGTestBench.h"
+#include "nsString.h"
+#include "ExampleStylesheet.h"
+#include "ServoBindings.h"
+#include "mozilla/dom/DOMString.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/css/SheetParsingMode.h"
+#include "ReferrerInfo.h"
+#include "nsCSSValue.h"
+#include "ReferrerInfo.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+using namespace mozilla::net;
+
+// Bug 1436018 - Disable Stylo microbenchmark on Windows
+#if !defined(_WIN32) && !defined(_WIN64)
+
+# define PARSING_REPETITIONS 20
+# define SETPROPERTY_REPETITIONS (1000 * 1000)
+# define GETPROPERTY_REPETITIONS (1000 * 1000)
+
+static void ServoParsingBench(const StyleUseCounters* aCounters) {
+ auto css = AsBytes(MakeStringSpan(EXAMPLE_STYLESHEET));
+ nsCString cssStr;
+ cssStr.Append(css);
+ ASSERT_EQ(Encoding::UTF8ValidUpTo(css), css.Length());
+
+ RefPtr<nsIURI> uri = NullPrincipal::CreateURI();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
+ RefPtr<URLExtraData> data =
+ new URLExtraData(uri.forget(), referrerInfo.forget(),
+ NullPrincipal::CreateWithoutOriginAttributes());
+ for (int i = 0; i < PARSING_REPETITIONS; i++) {
+ RefPtr<StyleStylesheetContents> stylesheet =
+ Servo_StyleSheet_FromUTF8Bytes(
+ nullptr, nullptr, nullptr, &cssStr, eAuthorSheetFeatures, data,
+ eCompatibility_FullStandards, nullptr, aCounters,
+ StyleAllowImportRules::Yes, StyleSanitizationKind::None, nullptr)
+ .Consume();
+ }
+}
+
+static constexpr auto STYLE_RULE = StyleCssRuleType::Style;
+
+static void ServoSetPropertyByIdBench(const nsACString& css) {
+ RefPtr<StyleLockedDeclarationBlock> block =
+ Servo_DeclarationBlock_CreateEmpty().Consume();
+ RefPtr<nsIURI> uri = NullPrincipal::CreateURI();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
+ RefPtr<URLExtraData> data =
+ new URLExtraData(uri.forget(), referrerInfo.forget(),
+ NullPrincipal::CreateWithoutOriginAttributes());
+ ASSERT_TRUE(IsUtf8(css));
+
+ for (int i = 0; i < SETPROPERTY_REPETITIONS; i++) {
+ Servo_DeclarationBlock_SetPropertyById(
+ block, eCSSProperty_width, &css,
+ /* is_important = */ false, data, StyleParsingMode::DEFAULT,
+ eCompatibility_FullStandards, nullptr, STYLE_RULE, {});
+ }
+}
+
+static void ServoGetPropertyValueById() {
+ RefPtr<StyleLockedDeclarationBlock> block =
+ Servo_DeclarationBlock_CreateEmpty().Consume();
+
+ RefPtr<nsIURI> uri = NullPrincipal::CreateURI();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
+ RefPtr<URLExtraData> data =
+ new URLExtraData(uri.forget(), referrerInfo.forget(),
+ NullPrincipal::CreateWithoutOriginAttributes());
+ constexpr auto css_ = "10px"_ns;
+ const nsACString& css = css_;
+ Servo_DeclarationBlock_SetPropertyById(
+ block, eCSSProperty_width, &css,
+ /* is_important = */ false, data, StyleParsingMode::DEFAULT,
+ eCompatibility_FullStandards, nullptr, STYLE_RULE, {});
+
+ for (int i = 0; i < GETPROPERTY_REPETITIONS; i++) {
+ nsAutoCString value;
+ Servo_DeclarationBlock_GetPropertyValueById(block, eCSSProperty_width,
+ &value);
+ ASSERT_TRUE(value.EqualsLiteral("10px"));
+ }
+}
+
+MOZ_GTEST_BENCH(Stylo, Servo_StyleSheet_FromUTF8Bytes_Bench,
+ [] { ServoParsingBench(nullptr); });
+
+MOZ_GTEST_BENCH(Stylo, Servo_StyleSheet_FromUTF8Bytes_Bench_UseCounters, [] {
+ UniquePtr<StyleUseCounters> counters(Servo_UseCounters_Create());
+ ServoParsingBench(counters.get());
+});
+
+MOZ_GTEST_BENCH(Stylo, Servo_DeclarationBlock_SetPropertyById_Bench,
+ [] { ServoSetPropertyByIdBench("10px"_ns); });
+
+MOZ_GTEST_BENCH(Stylo,
+ Servo_DeclarationBlock_SetPropertyById_WithInitialSpace_Bench,
+ [] { ServoSetPropertyByIdBench(" 10px"_ns); });
+
+MOZ_GTEST_BENCH(Stylo, Servo_DeclarationBlock_GetPropertyById_Bench,
+ ServoGetPropertyValueById);
+
+#endif
diff --git a/layout/style/test/gtest/example.css b/layout/style/test/gtest/example.css
new file mode 100644
index 0000000000..12a0fb9a4f
--- /dev/null
+++ b/layout/style/test/gtest/example.css
@@ -0,0 +1,2925 @@
+/* Copied from devtools/client/debugger/debugger.css on 2017-04-03 */
+
+.landing-page {
+ flex: 1;
+ display: flex;
+ width: 100vw;
+ height: 100vh;
+ flex-direction: row;
+ align-items: stretch;
+ /* Customs properties */
+ --title-font-size: 24px;
+ --ui-element-font-size: 16px;
+ --primary-line-height: 30px;
+ --secondary-line-height: 25px;
+ --base-spacing: 20px;
+ --base-transition: all 0.25s ease;
+}
+
+.landing-popup {
+ min-width: 0;
+}
+
+.landing-page .panel {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.landing-page .panel header {
+ display: flex;
+ align-items: baseline;
+ margin: calc(2 * var(--base-spacing)) 0 0;
+ padding-bottom: var(--base-spacing);
+}
+
+.landing-page .panel header input[type=search] {
+ flex: 1;
+ background-color: var(--theme-tab-toolbar-background);
+ color: var(--theme-comment);
+ font-size: var(--ui-element-font-size);
+ border: 1px solid var(--theme-splitter-color);
+ padding: calc(var(--base-spacing) / 2);
+ margin: 0 var(--base-spacing);
+ transition: var(--base-transition);
+}
+
+.landing-page .panel header input[type=button] {
+ background-color: var(--theme-tab-toolbar-background);
+ color: var(--theme-comment);
+ font-size: var(--ui-element-font-size);
+ border: 1px solid var(--theme-splitter-color);
+ padding: calc(var(--base-spacing) / 2);
+ margin: 0 var(--base-spacing);
+ transition: var(--base-transition);
+}
+
+.landing-page .panel header h1 {
+ color: var(--theme-body-color);
+ font-size: var(--title-font-size);
+ margin: 0;
+ line-height: var(--primary-line-height);
+ font-weight: normal;
+ padding-left: calc(2 * var(--base-spacing));
+}
+
+.landing-page .panel h3 {
+ padding-left: calc(2 * var(--base-spacing));
+}
+
+.landing-page .panel header input::placeholder {
+ color: var(--theme-body-color-inactive);
+}
+
+.landing-page .panel header input:focus {
+ border: 1px solid var(--theme-selection-background);
+}
+
+.landing-page .panel .center-message {
+ font-size: var(--ui-element-font-size);
+ line-height: var(--secondary-line-height);
+ padding: calc(var(--base-spacing) / 2);
+}
+
+.landing-page .center a {
+ color: var(--theme-highlight-bluegrey);
+ text-decoration: none;
+}
+
+.landing-page .panel .footer-note {
+ padding: var(--base-spacing) 0;
+ text-align: center;
+ font-size: 14px;
+ color: var(--theme-comment);
+}
+.landing-page .tab-group {
+ flex: 1;
+ overflow-y: auto;
+}
+
+.landing-page .tab-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.landing-page .tab {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding: calc(var(--base-spacing) / 2) var(--base-spacing);
+ font-family: sans-serif;
+}
+
+.landing-page .tab-sides {
+ display: flex;
+ justify-content: space-between;
+ margin: 0 calc(var(--base-spacing) * 2);
+}
+
+.landing-page .tab-title {
+ line-height: var(--secondary-line-height);
+ font-size: var(--ui-element-font-size);
+ color: var(--theme-highlight-bluegrey);
+ word-break: break-all;
+}
+
+.landing-page .tab-url {
+ color: var(--theme-comment);
+ word-break: break-all;
+}
+
+.landing-page .tab-value {
+ color: var(--theme-comment);
+ line-height: var(--secondary-line-height);
+ font-size: var(--ui-element-font-size);
+}
+
+.landing-page .tab:focus,
+.landing-page .tab.active {
+ background: var(--theme-selection-background);
+ color: var(--theme-selection-color);
+ cursor: pointer;
+ transition: var(--base-transition);
+}
+
+.landing-page .tab:focus .tab-title,
+.landing-page .tab.active .tab-title {
+ color: inherit;
+}
+
+.landing-page .tab:focus .tab-url,
+.landing-page .tab.active .tab-url {
+ color: var(--theme-highlight-gray);
+}
+.landing-page .sidebar {
+ display: flex;
+ background-color: var(--theme-tab-toolbar-background);
+ width: 200px;
+ flex-direction: column;
+ border-right: 1px solid var(--theme-splitter-color);
+}
+
+.landing-page .sidebar h1 {
+ color: var(--theme-body-color);
+ font-size: var(--title-font-size);
+ margin: 0;
+ line-height: var(--primary-line-height);
+ font-weight: normal;
+ padding: calc(2 * var(--base-spacing)) var(--base-spacing);
+}
+
+.landing-page .sidebar ul {
+ list-style: none;
+ padding: 0;
+ line-height: var(--primary-line-height);
+ font-size: var(--ui-element-font-size);
+}
+
+.landing-page .sidebar li {
+ padding: calc(var(--base-spacing) / 4) var(--base-spacing);
+}
+
+.landing-page .sidebar li a {
+ color: var(--theme-body-color);
+}
+
+.landing-page .sidebar li.selected {
+ background: var(--theme-highlight-bluegrey);
+ color: var(--theme-selection-color);
+ transition: var(--base-transition);
+}
+
+.landing-page .sidebar li.selected a {
+ color: inherit;
+}
+
+.landing-page .sidebar li:hover,
+.landing-page .sidebar li:focus {
+ background: var(--theme-selection-background);
+ color: var(--theme-selection-color);
+ cursor: pointer;
+}
+
+.landing-page .sidebar li:hover a,
+.landing-page .sidebar li:focus a {
+ color: inherit;
+}
+:root.theme-light,
+:root .theme-light {
+ --theme-search-overlays-semitransparent: rgba(221, 225, 228, 0.66);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+#mount {
+ display: flex;
+ height: 100%;
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+ background: transparent;
+}
+
+::-webkit-scrollbar-track {
+ border-radius: 8px;
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ border-radius: 8px;
+ background: rgba(113, 113, 113, 0.5);
+}
+
+:root.theme-dark .CodeMirror-scrollbar-filler {
+ background: transparent;
+}
+/* BASICS */
+
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px;
+ color: black;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+ white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+ border-left: 1px solid black;
+ border-right: none;
+ width: 0;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.cm-fat-cursor .CodeMirror-cursor {
+ width: auto;
+ border: 0 !important;
+ background: #7e7;
+}
+.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+@-moz-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@-webkit-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror-overwrite .CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-rulers {
+ position: absolute;
+ left: 0; right: 0; top: -50px; bottom: -20px;
+ overflow: hidden;
+}
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ top: 0; bottom: 0;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+ background: white;
+}
+
+.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actual scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ min-height: 100%;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ display: inline-block;
+ vertical-align: top;
+ margin-bottom: -30px;
+}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+.CodeMirror-gutter-wrapper {
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: contextual;
+ font-variant-ligatures: contextual;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+}
+
+.CodeMirror-cursor {
+ position: absolute;
+ pointer-events: none;
+}
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
+}
+div.CodeMirror-dragcursors {
+ visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursors {
+ visibility: hidden;
+ }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+:root {
+ /* --breakpoint-background: url("chrome://devtools/skin/images/breakpoint.svg#light"); */
+ /* --breakpoint-hover-background: url("chrome://devtools/skin/images/breakpoint.svg#light-hover"); */
+ --breakpoint-active-color: rgba(44,187,15,.2);
+ --breakpoint-active-color-hover: rgba(44,187,15,.5);
+ /* --breakpoint-conditional-background: url("chrome://devtools/skin/images/breakpoint.svg#light-conditional"); */
+}
+
+.theme-dark:root {
+ /* --breakpoint-background: url("chrome://devtools/skin/images/breakpoint.svg#dark"); */
+ /* --breakpoint-hover-background: url("chrome://devtools/skin/images/breakpoint.svg#dark-hover"); */
+ --breakpoint-active-color: rgba(0,255,175,.4);
+ --breakpoint-active-color-hover: rgba(0,255,175,.7);
+ /* --breakpoint-conditional-background: url("chrome://devtools/skin/images/breakpoint.svg#dark-conditional"); */
+}
+
+.CodeMirror .errors {
+ width: 16px;
+}
+
+.CodeMirror .error {
+ display: inline-block;
+ margin-left: 5px;
+ width: 12px;
+ height: 12px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+ /* background-image: url("chrome://devtools/skin/images/editor-error.png"); */
+ opacity: 0.75;
+}
+
+.CodeMirror .hit-counts {
+ width: 6px;
+}
+
+.CodeMirror .hit-count {
+ display: inline-block;
+ height: 12px;
+ border: solid rgba(0,0,0,0.2);
+ border-width: 1px 1px 1px 0;
+ border-radius: 0 3px 3px 0;
+ padding: 0 3px;
+ font-size: 10px;
+ pointer-events: none;
+}
+
+.CodeMirror-linenumber:before {
+ content: " ";
+ display: block;
+ width: calc(100% - 3px);
+ position: absolute;
+ top: 1px;
+ left: 0;
+ height: 12px;
+ z-index: -1;
+ background-size: calc(100% - 2px) 12px;
+ background-repeat: no-repeat;
+ background-position: right center;
+ padding-inline-end: 9px;
+}
+
+.breakpoint .CodeMirror-linenumber {
+ color: var(--theme-body-background);
+}
+
+.breakpoint .CodeMirror-linenumber:before {
+ background-image: var(--breakpoint-background) !important;
+}
+
+.conditional .CodeMirror-linenumber:before {
+ background-image: var(--breakpoint-conditional-background) !important;
+}
+
+.debug-line .CodeMirror-linenumber {
+ background-color: var(--breakpoint-active-color);
+}
+
+.theme-dark .debug-line .CodeMirror-linenumber {
+ color: #c0c0c0;
+}
+
+.debug-line .CodeMirror-line {
+ background-color: var(--breakpoint-active-color) !important;
+}
+
+/* Don't display the highlight color since the debug line
+ is already highlighted */
+.debug-line .CodeMirror-activeline-background {
+ display: none;
+}
+
+.CodeMirror {
+ cursor: text;
+ height: 100%;
+}
+
+.CodeMirror-gutters {
+ cursor: default;
+}
+
+/* This is to avoid the fake horizontal scrollbar div of codemirror to go 0
+height when floating scrollbars are active. Make sure that this value is equal
+to the maximum of `min-height` specific to the `scrollbar[orient="horizontal"]`
+selector in floating-scrollbar-light.css across all platforms. */
+.CodeMirror-hscrollbar {
+ min-height: 10px;
+}
+
+/* This is to avoid the fake vertical scrollbar div of codemirror to go 0
+width when floating scrollbars are active. Make sure that this value is equal
+to the maximum of `min-width` specific to the `scrollbar[orient="vertical"]`
+selector in floating-scrollbar-light.css across all platforms. */
+.CodeMirror-vscrollbar {
+ min-width: 10px;
+}
+
+.cm-trailingspace {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==");
+ opacity: 0.75;
+ background-position: left bottom;
+ background-repeat: repeat-x;
+}
+
+.cm-highlight {
+ position: relative;
+}
+
+.cm-highlight:before {
+ position: absolute;
+ border-top-style: solid;
+ border-bottom-style: solid;
+ border-top-color: var(--theme-comment-alt);
+ border-bottom-color: var(--theme-comment-alt);
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+ top: -1px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ content: "";
+ margin-bottom: -1px;
+}
+
+.cm-highlight-full:before {
+ border: 1px solid var(--theme-comment-alt);
+}
+
+.cm-highlight-start:before {
+ border-left-width: 1px;
+ border-left-style: solid;
+ border-left-color: var(--theme-comment-alt);
+ margin: 0 0 -1px -1px;
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+}
+
+.cm-highlight-end:before {
+ border-right-width: 1px;
+ border-right-style: solid;
+ border-right-color: var(--theme-comment-alt);
+ margin: 0 -1px -1px 0;
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+}
+
+/* CodeMirror dialogs styling */
+
+.CodeMirror-dialog {
+ padding: 4px 3px;
+}
+
+.CodeMirror-dialog,
+.CodeMirror-dialog input {
+ font: message-box;
+}
+
+/* Fold addon */
+
+.CodeMirror-foldmarker {
+ color: blue;
+ text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
+ font-family: sans-serif;
+ line-height: .3;
+ cursor: pointer;
+}
+
+.CodeMirror-foldgutter {
+ width: 16px; /* Same as breakpoints gutter above */
+}
+
+.CodeMirror-foldgutter-open,
+.CodeMirror-foldgutter-folded {
+ color: #555;
+ cursor: pointer;
+}
+
+.CodeMirror-foldgutter-open:after {
+ font-size: 120%;
+ content: "\25BE";
+}
+
+.CodeMirror-foldgutter-folded:after {
+ font-size: 120%;
+ content: "\25B8";
+}
+
+.CodeMirror-hints {
+ position: absolute;
+ z-index: 10;
+ overflow: hidden;
+ list-style: none;
+ margin: 0;
+ padding: 2px;
+ border-radius: 3px;
+ font-size: 90%;
+ max-height: 20em;
+ overflow-y: auto;
+}
+
+.CodeMirror-hint {
+ margin: 0;
+ padding: 0 4px;
+ border-radius: 2px;
+ max-width: 19em;
+ overflow: hidden;
+ white-space: pre;
+ cursor: pointer;
+}
+
+.CodeMirror-Tern-completion {
+ padding-inline-start: 22px;
+ position: relative;
+ line-height: 18px;
+}
+
+.CodeMirror-Tern-completion:before {
+ position: absolute;
+ left: 2px;
+ bottom: 2px;
+ border-radius: 50%;
+ font-size: 12px;
+ font-weight: bold;
+ height: 15px;
+ width: 15px;
+ line-height: 16px;
+ text-align: center;
+ color: #ffffff;
+ box-sizing: border-box;
+}
+
+.CodeMirror-Tern-completion-unknown:before {
+ content: "?";
+}
+
+.CodeMirror-Tern-completion-object:before {
+ content: "O";
+}
+
+.CodeMirror-Tern-completion-fn:before {
+ content: "F";
+}
+
+.CodeMirror-Tern-completion-array:before {
+ content: "A";
+}
+
+.CodeMirror-Tern-completion-number:before {
+ content: "N";
+}
+
+.CodeMirror-Tern-completion-string:before {
+ content: "S";
+}
+
+.CodeMirror-Tern-completion-bool:before {
+ content: "B";
+}
+
+.CodeMirror-Tern-completion-guess {
+ color: #999;
+}
+
+.CodeMirror-Tern-tooltip {
+ border-radius: 3px;
+ padding: 2px 5px;
+ white-space: pre-wrap;
+ max-width: 40em;
+ position: absolute;
+ z-index: 10;
+}
+
+.CodeMirror-Tern-hint-doc {
+ max-width: 25em;
+}
+
+.CodeMirror-Tern-farg-current {
+ text-decoration: underline;
+}
+
+.CodeMirror-Tern-fhint-guess {
+ opacity: .7;
+}
+:root.theme-light,
+:root .theme-light {
+ --search-overlays-semitransparent: rgba(221, 225, 228, 0.66);
+}
+
+:root.theme-dark,
+:root .theme-dark {
+ --search-overlays-semitransparent: rgba(42, 46, 56, 0.66);
+}
+.debugger {
+ display: flex;
+ flex: 1;
+ height: 100%;
+}
+
+.editor-pane {
+ display: flex;
+ position: relative;
+ flex: 1;
+ background-color: var(--theme-tab-toolbar-background);
+ height: calc(100% - 1px);
+ overflow: hidden;
+}
+
+.editor-container {
+ width: 100%;
+}
+
+.subsettings:hover {
+ cursor: pointer;
+}
+
+.search-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ z-index: 200;
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.search-container .close-button {
+ width: 16px;
+ margin-top: 25px;
+ margin-right: 20px;
+}
+menupopup {
+ position: fixed;
+ z-index: 10000;
+ background: white;
+ border: 1px solid #cccccc;
+ padding: 5px 0;
+ background: #f2f2f2;
+ border-radius: 5px;
+ color: #585858;
+ box-shadow: 0 0 4px 0 rgba(190, 190, 190, 0.8);
+ min-width: 130px;
+}
+
+menuitem {
+ display: block;
+ padding: 0 20px;
+ line-height: 20px;
+ font-weight: 500;
+ font-size: 13px;
+ user-select: none;
+}
+
+menuitem:hover {
+ background: #3780fb;
+ color: white;
+ cursor: pointer;
+}
+
+menuitem[disabled=true] {
+ color: #cccccc;
+}
+
+menuitem[disabled=true]:hover {
+ background-color: transparent;
+ cursor: default;
+}
+
+menuseparator {
+ border-bottom: 1px solid #cacdd3;
+ width: 100%;
+ height: 5px;
+ display: block;
+ margin-bottom: 5px;
+}
+
+#contextmenu-mask.show {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 999;
+}
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+.split-box {
+ display: flex;
+ flex: 1;
+ min-width: 0;
+ height: 100%;
+ width: 100%;
+}
+
+.split-box.vert {
+ flex-direction: row;
+}
+
+.split-box.horz {
+ flex-direction: column;
+}
+
+.split-box > .uncontrolled {
+ display: flex;
+ flex: 1;
+ min-width: 0;
+ overflow: auto;
+}
+
+.split-box > .controlled {
+ display: flex;
+ overflow: auto;
+}
+
+.split-box > .splitter {
+ background-image: none;
+ border: 0;
+ border-style: solid;
+ border-color: transparent;
+ background-color: var(--theme-splitter-color);
+ background-clip: content-box;
+ position: relative;
+
+ box-sizing: border-box;
+
+ /* Positive z-index positions the splitter on top of its siblings and makes
+ it clickable on both sides. */
+ z-index: 1;
+}
+
+.split-box.vert > .splitter {
+ min-width: calc(var(--devtools-splitter-inline-start-width) +
+ var(--devtools-splitter-inline-end-width) + 1px);
+
+ border-left-width: var(--devtools-splitter-inline-start-width);
+ border-right-width: var(--devtools-splitter-inline-end-width);
+
+ margin-left: calc(-1 * var(--devtools-splitter-inline-start-width) - 1px);
+ margin-right: calc(-1 * var(--devtools-splitter-inline-end-width));
+
+ cursor: ew-resize;
+}
+
+.split-box.horz > .splitter {
+ min-height: calc(var(--devtools-splitter-top-width) +
+ var(--devtools-splitter-bottom-width) + 1px);
+
+ border-top-width: var(--devtools-splitter-top-width);
+ border-bottom-width: var(--devtools-splitter-bottom-width);
+
+ margin-top: calc(-1 * var(--devtools-splitter-top-width) - 1px);
+ margin-bottom: calc(-1 * var(--devtools-splitter-bottom-width));
+
+ cursor: ns-resize;
+}
+
+.split-box.disabled {
+ pointer-events: none;
+}
+
+/**
+ * Make sure splitter panels are not processing any mouse
+ * events. This is good for performance during splitter
+ * bar dragging.
+ */
+.split-box.dragging > .controlled,
+.split-box.dragging > .uncontrolled {
+ pointer-events: none;
+}
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+.theme-dark,
+.theme-light {
+ --number-color: var(--theme-highlight-green);
+ --string-color: var(--theme-highlight-orange);
+ --null-color: var(--theme-comment);
+ --object-color: var(--theme-body-color);
+ --caption-color: var(--theme-highlight-blue);
+ --location-color: var(--theme-content-color1);
+ --source-link-color: var(--theme-highlight-blue);
+ --node-color: var(--theme-highlight-bluegrey);
+ --reference-color: var(--theme-highlight-purple);
+}
+
+.theme-firebug {
+ --number-color: #000088;
+ --string-color: #FF0000;
+ --null-color: #787878;
+ --object-color: DarkGreen;
+ --caption-color: #444444;
+ --location-color: #555555;
+ --source-link-color: blue;
+ --node-color: rgb(0, 0, 136);
+ --reference-color: rgb(102, 102, 255);
+}
+
+/******************************************************************************/
+
+.objectLink:hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.inline {
+ display: inline;
+ white-space: normal;
+}
+
+.objectBox-object {
+ font-weight: bold;
+ color: var(--object-color);
+ white-space: pre-wrap;
+}
+
+.objectBox-string,
+.objectBox-text,
+.objectLink-textNode,
+.objectBox-table {
+ white-space: pre-wrap;
+}
+
+.objectBox-number,
+.objectLink-styleRule,
+.objectLink-element,
+.objectLink-textNode,
+.objectBox-array > .length {
+ color: var(--number-color);
+}
+
+.objectBox-string {
+ color: var(--string-color);
+}
+
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+ color: var(--object-color);
+}
+
+.objectLink-Location {
+ font-style: italic;
+ color: var(--location-color);
+}
+
+.objectBox-null,
+.objectBox-undefined,
+.objectBox-hint,
+.logRowHint {
+ font-style: italic;
+ color: var(--null-color);
+}
+
+.objectLink-sourceLink {
+ position: absolute;
+ right: 4px;
+ top: 2px;
+ padding-left: 8px;
+ font-weight: bold;
+ color: var(--source-link-color);
+}
+
+/******************************************************************************/
+
+.objectLink-event,
+.objectLink-eventLog,
+.objectLink-regexp,
+.objectLink-object,
+.objectLink-Date {
+ font-weight: bold;
+ color: var(--object-color);
+ white-space: pre-wrap;
+}
+
+/******************************************************************************/
+
+.objectLink-object .nodeName,
+.objectLink-NamedNodeMap .nodeName,
+.objectLink-NamedNodeMap .objectEqual,
+.objectLink-NamedNodeMap .arrayLeftBracket,
+.objectLink-NamedNodeMap .arrayRightBracket,
+.objectLink-Attr .attrEqual,
+.objectLink-Attr .attrTitle {
+ color: var(--node-color);
+}
+
+.objectLink-object .nodeName {
+ font-weight: normal;
+}
+
+/******************************************************************************/
+
+.objectLeftBrace,
+.objectRightBrace,
+.arrayLeftBracket,
+.arrayRightBracket {
+ cursor: pointer;
+ font-weight: bold;
+}
+
+.objectLeftBrace,
+.arrayLeftBracket {
+ margin-right: 4px;
+}
+
+.objectRightBrace,
+.arrayRightBracket {
+ margin-left: 4px;
+}
+
+/******************************************************************************/
+/* Cycle reference*/
+
+.objectLink-Reference {
+ font-weight: bold;
+ color: var(--reference-color);
+}
+
+.objectBox-array > .objectTitle {
+ font-weight: bold;
+ color: var(--object-color);
+}
+
+.caption {
+ font-weight: bold;
+ color: var(--caption-color);
+}
+
+/******************************************************************************/
+/* Themes */
+
+.theme-dark .objectBox-null,
+.theme-dark .objectBox-undefined,
+.theme-light .objectBox-null,
+.theme-light .objectBox-undefined {
+ font-style: normal;
+}
+
+.theme-dark .objectBox-object,
+.theme-light .objectBox-object {
+ font-weight: normal;
+ white-space: pre-wrap;
+}
+
+.theme-dark .caption,
+.theme-light .caption {
+ font-weight: normal;
+}
+
+.search-container {
+ position: absolute;
+ top: 30px;
+ left: 0;
+ width: calc(100% - 1px);
+ height: calc(100% - 31px);
+ display: flex;
+ z-index: 200;
+ background-color: var(--theme-body-background);
+}
+
+.searchinput-container {
+ display: flex;
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.theme-dark .result-list li .subtitle {
+ color: var(--theme-comment-alt);
+}
+
+.arrow,
+.folder,
+.domain,
+.file,
+.worker,
+.refresh,
+.add-button {
+ fill: var(--theme-splitter-color);
+}
+
+.worker,
+.folder {
+ position: relative;
+ top: 2px;
+}
+
+.domain,
+.file,
+.worker,
+.refresh,
+.add-button {
+ position: relative;
+ top: 1px;
+}
+
+.domain svg,
+.folder svg,
+.worker svg,
+.refresh svg,
+.add-button svg {
+ width: 15px;
+}
+
+.file svg {
+ width: 13px;
+}
+
+.file svg,
+.domain svg,
+.folder svg,
+.refresh svg,
+.worker svg {
+ margin-inline-end: 5px;
+}
+
+.arrow svg {
+ fill: var(--theme-splitter-color);
+ margin-top: 3px;
+ transition: transform 0.25s ease;
+ width: 10px;
+}
+
+html:not([dir="rtl"]) .arrow svg {
+ margin-right: 5px;
+ transform: rotate(-90deg);
+}
+
+html[dir="rtl"] .arrow svg {
+ margin-left: 5px;
+ transform: rotate(90deg);
+}
+
+/* TODO (Amit): html is just for specificity. keep it like this? */
+html .arrow.expanded svg {
+ transform: rotate(0deg);
+}
+
+.arrow.hidden {
+ visibility: hidden;
+}
+.close-btn path {
+ fill: var(--theme-comment-alt);
+}
+
+.close-btn .close {
+ cursor: pointer;
+ width: 14px;
+ height: 14px;
+ padding: 2px;
+ text-align: center;
+ margin-top: 2px;
+ line-height: 7px;
+ transition: all 0.25s easeinout;
+}
+
+.close-btn .close svg {
+ width: 8px;
+}
+
+.close-btn:hover {
+ display: block;
+}
+
+.close-btn:hover .close {
+ background: var(--theme-selection-background);
+ border-radius: 2px;
+}
+
+.close-btn:hover .close path {
+ fill: white;
+}
+
+.close-btn-big {
+ padding: 11px;
+ margin-right: 7px;
+ width: 27px;
+ height: 40px;
+}
+
+.close-btn-big .close {
+ cursor: pointer;
+ display: inline-block;
+ padding: 2px;
+ text-align: center;
+ transition: all 0.25s easeinout;
+ line-height: 100%;
+ width: 16px;
+ height: 16px;
+}
+
+.close-btn-big .close svg {
+ width: 9px;
+ height: 9px;
+}
+
+.close-btn-big .close:hover {
+ border-radius: 2px;
+}
+
+.search-field {
+ width: calc(100% - 1px);
+ height: 27px;
+ background-color: var(--theme-toolbar-background);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding-right: 10px;
+ display: flex;
+ flex-shrink: 0;
+}
+
+.search-field.big {
+ height: 40px;
+ font-size: 14px;
+}
+
+.search-field.big input {
+ font-size: 14px;
+}
+
+.search-field i {
+ display: block;
+ padding: 0;
+ width: 16px;
+}
+
+.search-field i svg {
+ width: 16px;
+}
+
+.search-field.big input {
+ line-height: 40px;
+}
+
+.search-field input {
+ border: none;
+ line-height: 30px;
+ background-color: var(--theme-toolbar-background);
+ color: var(--theme-body-color-inactive);
+ width: calc(100% - 38px);
+ flex: 1;
+}
+
+.theme-dark .search-field input {
+ color: var(--theme-body-color-inactive);
+}
+
+.search-field i.magnifying-glass,
+.search-field i.sad-face {
+ padding: 6px;
+ width: 24px;
+}
+
+.search-field.big i.magnifying-glass,
+.search-field.big i.sad-face {
+ padding: 14px;
+ width: 40px;
+}
+
+.search-field .magnifying-glass path,
+.search-field .magnifying-glass ellipse {
+ stroke: var(--theme-splitter-color);
+}
+
+.search-field ::-webkit-input-placeholder {
+ color: var(--theme-body-color-inactive);
+}
+
+.search-field input::placeholder {
+ color: var(--theme-body-color-inactive);
+}
+
+.search-field input:focus {
+ outline-width: 0;
+}
+
+.search-field input.empty {
+ color: var(--theme-highlight-orange);
+}
+
+.search-field.big .summary {
+ line-height: 40px;
+}
+
+.search-field .summary {
+ line-height: 27px;
+ padding-right: 10px;
+ color: var(--theme-body-color-inactive);
+}
+
+.result-list {
+ list-style: none;
+ width: 100%;
+ background-color: var(--theme-toolbar-background);
+ margin: 0px;
+ padding: 0px;
+ overflow: auto;
+}
+
+.result-list.big {
+ max-height: calc(100% - 32px);
+}
+
+.result-list * {
+ user-select: none;
+}
+
+.result-list li {
+ color: var(--theme-body-color);
+ background-color: var(--theme-tab-toolbar-background);
+ padding: 4px 13px;
+ display: flex;
+ justify-content: space-between;
+}
+
+.result-list li:first-child {
+ border-top: none;
+}
+
+.result-list.big li {
+ padding: 10px;
+ flex-direction: column;
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.result-list li:hover {
+ background: var(--theme-tab-toolbar-background);
+ cursor: pointer;
+}
+
+.result-list li.selected {
+ border: 1px solid var(--theme-selection-background);
+}
+
+.result-list.big li.selected {
+ padding-left: 9px;
+ padding-top: 9px;
+}
+
+.search-bar .result-list li.selected {
+ background-color: var(--theme-selection-background);
+ color: white;
+}
+
+.result-list li .title {
+ line-height: 1.5em;
+ word-break: break-all;
+}
+
+.result-list li.selected .title {
+ color: white;
+}
+
+.result-list.big li.selected .title {
+ color: var(--theme-body-color);
+}
+
+.result-list.big li .subtitle {
+ word-break: break-all;
+ color: var(--theme-body-color-inactive);
+}
+
+.result-list.big li .subtitle {
+ line-height: 1.5em;
+}
+
+.search-bar .result-list li.selected .subtitle {
+ color: white;
+}
+
+.search-bar .result-list {
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.theme-dark .result-list {
+ background-color: var(--theme-body-background);
+}
+
+.autocomplete {
+ flex: 1;
+ width: 100%;
+}
+
+.autocomplete .no-result-msg {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ color: var(--theme-graphs-full-red);
+ font-size: 24px;
+ padding: 4px;
+ word-break: break-all;
+}
+
+.autocomplete .no-result-msg .sad-face {
+ width: 24px;
+ margin: 0 4px;
+ line-height: 0;
+ flex-shrink: 0;
+}
+
+.autocomplete .no-result-msg .sad-face svg {
+ fill: var(--theme-graphs-full-red);
+}
+.tree {
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+
+ white-space: nowrap;
+ overflow: auto;
+ min-width: 100%;
+}
+
+.tree button {
+ display: block;
+}
+
+.tree .node {
+ padding: 2px 5px;
+ position: relative;
+ cursor: pointer;
+}
+
+.tree .node.focused {
+ color: white;
+ background-color: var(--theme-selection-background);
+}
+
+html:not([dir="rtl"]) .tree .node > div {
+ margin-left: 10px;
+}
+
+html[dir="rtl"] .tree .node > div {
+ margin-right: 10px;
+}
+
+.tree .node.focused svg {
+ fill: white;
+}
+
+.tree-node button {
+ position: fixed;
+}
+.sources-panel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.sources-panel * {
+ user-select: none;
+}
+
+.sources-header {
+ height: 30px;
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding-top: 0px;
+ padding-bottom: 0px;
+ line-height: 30px;
+ font-size: 1.2em;
+ display: flex;
+ align-items: baseline;
+ user-select: none;
+ justify-content: flex-end;
+}
+
+.theme-dark .sources-header {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.sources-header {
+ padding-inline-start: 10px;
+}
+
+.sources-header-info {
+ font-size: 12px;
+ color: var(--theme-comment-alt);
+ font-weight: lighter;
+ white-space: nowrap;
+ padding-inline-end: 10px;
+ cursor: pointer;
+}
+
+.sources-list {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+}
+
+.theme-dark .sources-list .tree .node:not(.focused) svg {
+ fill: var(--theme-comment);
+}
+
+.theme-dark .source-list .tree .node.focused {
+ background-color: var(--theme-tab-toolbar-background);
+}
+.toggle-button-start,
+.toggle-button-end {
+ transform: translate(0, 2px);
+ transition: transform 0.25s ease-in-out;
+ cursor: pointer;
+ padding: 4px 2px;
+}
+
+.toggle-button-start svg,
+.toggle-button-end svg {
+ width: 16px;
+ fill: var(--theme-comment);
+}
+
+.theme-dark .toggle-button-start svg,
+.theme-dark .toggle-button-end svg {
+ fill: var(--theme-comment-alt);
+}
+
+.toggle-button-end {
+ margin-left: auto;
+ margin-right: 5px;
+}
+
+.toggle-button-start {
+ margin-left: 5px;
+}
+
+html:not([dir="rtl"]) .toggle-button-end svg,
+html[dir="rtl"] .toggle-button-start svg {
+ transform: rotate(180deg);
+}
+
+html .toggle-button-end.vertical svg {
+ transform: rotate(-90deg);
+}
+
+.toggle-button-end.vertical {
+ margin-bottom: 2px;
+}
+
+.toggle-button-start.collapsed,
+.toggle-button-end.collapsed {
+ transform: rotate(180deg);
+}
+
+.source-footer {
+ background: var(--theme-toolbar-background);
+ border-top: 1px solid var(--theme-splitter-color);
+ position: absolute;
+ display: flex;
+ bottom: 0;
+ left: 0;
+ right: 1px;
+ opacity: 1;
+ z-index: 100;
+ user-select: none;
+ height: 27px;
+ box-sizing: border-box;
+}
+
+.source-footer .commands {
+ display: flex;
+}
+
+.source-footer .commands * {
+ user-select: none;
+}
+
+.source-footer > .commands > .action {
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ transition: opacity 200ms;
+ border: none;
+ background: transparent;
+ padding: 8px 0.7em;
+}
+
+.source-footer > .commands > .action i {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+:root.theme-dark .source-footer > .commands > .action {
+ fill: var(--theme-body-color);
+}
+
+:root.theme-dark .source-footer > .commands > .action:hover {
+ fill: var(--theme-selection-color);
+}
+
+.source-footer > .commands > .action svg {
+ height: 16px;
+ width: 16px;
+}
+
+.source-footer .commands .coverage {
+ color: var(--theme-body-color);
+}
+
+.coverage-on .source-footer .commands .coverage {
+ color: var(--theme-highlight-blue);
+ border: 1px solid var(--theme-body-color-inactive);
+ border-radius: 2px;
+}
+
+.search-bar {
+ display: flex;
+ flex-direction: column;
+ max-height: 50%;
+}
+
+.search-bar .search-field {
+ padding-left: 7px;
+}
+
+.search-bar .close-btn {
+ padding: 6px;
+}
+
+.search-bottom-bar * {
+ user-select: none;
+}
+
+.search-bottom-bar {
+ display: flex;
+ flex-shrink: 0;
+ justify-content: space-between;
+ width: calc(100% - 1px);
+ height: 27px;
+ background-color: var(--theme-toolbar-background);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding: 0 13px;
+}
+
+.search-bottom-bar button:focus {
+ outline: none;
+}
+
+.search-bottom-bar .search-modifiers {
+ display: flex;
+ align-items: center;
+}
+
+.search-bottom-bar .search-modifiers button {
+ padding: 0 3px;
+ margin: 0 3px;
+ border: none;
+ background: none;
+ width: 20px;
+ height: 20px;
+ border-radius: 3px;
+}
+
+.search-bottom-bar .search-modifiers button i {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 0;
+ width: 16px;
+}
+
+.search-bottom-bar .search-modifiers button svg {
+ fill: var(--theme-comment-alt);
+ height: 16px;
+ width: 16px;
+}
+
+.search-bottom-bar .search-modifiers button svg:hover {
+ cursor: pointer;
+}
+
+.search-bottom-bar .search-modifiers button:active {
+ outline: none;
+}
+
+.search-bottom-bar .search-modifiers button.active svg {
+ fill: var(--theme-selection-background);
+}
+
+.theme-dark .search-bottom-bar .search-modifiers button.active svg {
+ fill: white;
+}
+
+.search-bottom-bar .search-modifiers button.disabled svg {
+ fill: var(--theme-comment-alt);
+}
+
+.search-bottom-bar .search-type-toggles {
+ display: flex;
+ align-items: center;
+}
+
+.search-bottom-bar .search-type-toggles .search-toggle-title {
+ color: var(--theme-body-color-inactive);
+ font-size: 11px;
+ font-weight: normal;
+ margin: 0;
+}
+
+.search-bottom-bar .search-type-toggles .search-type-btn {
+ margin: 0 6px;
+ border: none;
+ background: transparent;
+ color: var(--theme-comment-alt);
+}
+
+.search-bottom-bar .search-type-toggles .search-type-btn:hover {
+ cursor: pointer;
+}
+
+.search-bottom-bar .search-type-toggles .search-type-btn:active {
+ outline: none;
+}
+
+.search-bottom-bar .search-type-toggles .search-type-btn.active {
+ color: var(--theme-selection-background);
+}
+
+.theme-dark .search-bottom-bar .search-type-toggles .search-type-btn.active {
+ color: white;
+}
+
+.search-bar .result-list {
+ max-height: 230px;
+}
+.popover {
+ position: fixed;
+ z-index: 4;
+}
+
+.popover-gap {
+ height: 10px;
+ padding-top: 10px;
+}
+
+.popover::before,
+.popover::after {
+ content: '';
+ height: 0;
+ width: 0;
+ position: absolute;
+ border: 10px solid transparent;
+ left: calc(20% - 10px); /* corresponds to calculation in Popover.js */
+}
+
+.popover::before {
+ border-bottom-color: var(--theme-comment);
+ top: -10px;
+}
+
+.popover::after {
+ border-bottom-color: var(--theme-body-background);
+ top: -8px;
+}
+.preview {
+ background: var(--theme-body-background);
+ min-width: 200px;
+ min-height: 80px;
+ border: 1px solid var(--theme-comment);
+ padding: 10px;
+ height: auto;
+ min-height: inherit;
+ max-height: 130px;
+ overflow: auto;
+ max-width: 400px;
+}
+
+.preview .header {
+ width: 100%;
+ line-height: 20px;
+ border-bottom: 1px solid #cccccc;
+ display: flex;
+ flex-direction: column;
+}
+
+.preview .header .link {
+ align-self: flex-end;
+ color: var(--theme-highlight-blue);
+ text-decoration: underline;
+}
+.preview .header .link:hover {
+ cursor: pointer;
+}
+
+.selected-token {
+ background-color: var(--theme-search-overlays-semitransparent);
+ color: var(--theme-selection-color);
+}
+
+.selected-token:hover {
+ cursor: pointer;
+}
+
+.preview .function-signature {
+ padding-top: 10px;
+}
+
+.function-signature {
+ line-height: 20px;
+ align-self: center;
+}
+
+.function-signature .function-name {
+ color: var(--theme-highlight-blue);
+}
+
+.function-signature .param {
+ color: var(--string-color);
+}
+
+.function-signature .paren {
+ color: var(--object-color);
+}
+
+.function-signature .comma {
+ color: var(--object-color);
+}
+
+.theme-dark .preview {
+ border-color: var(--theme-body-color);
+}
+
+.theme-dark .preview .arrow svg {
+ fill: var(--theme-comment);
+}
+.conditional-breakpoint-panel {
+ cursor: initial;
+ margin: 1em 0;
+ position: relative;
+ display: flex;
+ align-items: center;
+ background: var(--theme-toolbar-background);
+ border-top: 1px solid var(--theme-splitter-color);
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.conditional-breakpoint-panel .prompt {
+ font-size: 1.8em;
+ color: var(--theme-comment-alt);
+ padding-left: 3px;
+}
+
+.conditional-breakpoint-panel input {
+ margin: 5px 10px;
+ width: calc(100% - 4em);
+ border: none;
+ background: var(--theme-toolbar-background);
+ font-size: 14px;
+ color: var(--theme-comment);
+ line-height: 30px;
+}
+
+.conditional-breakpoint-panel input:focus {
+ outline-width: 0;
+}
+/* vim:set ts=2 sw=2 sts=2 et: */
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/**
+ * There's a known codemirror flex issue with chrome that this addresses.
+ * BUG https://github.com/devtools-html/debugger.html/issues/63
+ */
+.editor-wrapper {
+ position: absolute;
+ height: calc(100% - 31px);
+ width: 100%;
+ top: 30px;
+ left: 0px;
+}
+
+html[dir="rtl"] .editor-mount {
+ direction: ltr;
+}
+
+.editor-wrapper .breakpoints {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.function-search {
+ max-height: 300px;
+ overflow: hidden;
+}
+
+.function-search .results {
+ height: auto;
+}
+
+.editor.hit-marker {
+ height: 14px;
+}
+
+.coverage-on .CodeMirror-code :not(.hit-marker) .CodeMirror-line,
+.coverage-on .CodeMirror-code :not(.hit-marker) .CodeMirror-gutter-wrapper {
+ opacity: 0.5;
+}
+
+.editor.new-breakpoint svg {
+ fill: var(--theme-selection-background);
+ width: 60px;
+ height: 14px;
+ position: absolute;
+ top: 0px;
+ right: -4px;
+}
+
+.new-breakpoint.has-condition svg {
+ fill: var(--theme-graphs-yellow);
+}
+
+.editor.new-breakpoint.breakpoint-disabled svg {
+ opacity: 0.3;
+}
+
+.CodeMirror {
+ width: 100%;
+ height: 100%;
+}
+
+.editor-wrapper .editor-mount {
+ width: 100%;
+ height: calc(100% - 32px);
+ background-color: var(--theme-body-background);
+}
+
+.search-bar ~ .editor-mount {
+ height: calc(100% - 72px);
+}
+
+.CodeMirror-linenumber {
+ font-size: 11px;
+ line-height: 14px;
+}
+
+/* set the linenumber white when there is a breakpoint */
+.new-breakpoint .CodeMirror-gutter-wrapper .CodeMirror-linenumber {
+ color: white;
+}
+
+/* move the breakpoint below the linenumber */
+.new-breakpoint .CodeMirror-gutter-elt:last-child {
+ z-index: 0;
+}
+
+.editor-wrapper .CodeMirror-line {
+ font-size: 11px;
+ line-height: 14px;
+}
+
+.theme-dark .editor-wrapper .CodeMirror-line .cm-comment {
+ color: var(--theme-content-color3);
+}
+
+.debug-line .CodeMirror-line {
+ background-color: var(--breakpoint-active-color) !important;
+}
+
+/* Don't display the highlight color since the debug line
+ is already highlighted */
+.debug-line .CodeMirror-activeline-background {
+ display: none;
+}
+
+.highlight-line .CodeMirror-line {
+ animation: fade-highlight-out 1.5s normal forwards;
+}
+
+@keyframes fade-highlight-out {
+ 0% { background-color: var(--theme-highlight-gray); }
+ 100% { background-color: transparent; }
+}
+
+.welcomebox {
+ width: calc(100% - 1px);
+
+ /* Offsetting it by 30px for the sources-header area */
+ height: calc(100% - 30px);
+ position: absolute;
+ top: 30px;
+ left: 0;
+ padding: 50px 0;
+ text-align: center;
+ font-size: 1.25em;
+ color: var(--theme-comment-alt);
+ background-color: var(--theme-tab-toolbar-background);
+ font-weight: lighter;
+ z-index: 100;
+ user-select: none;
+}
+.input-expression {
+ width: 100%;
+ margin: 0px;
+ border: 1px;
+ cursor: pointer;
+ background-color: var(--theme-body-background);
+ font-size: 12px;
+ padding: 0px 20px;
+ color: var(--theme-highlight-blue);
+}
+
+.input-expression::-webkit-input-placeholder {
+ text-align: center;
+ font-style: italic;
+ color: var(--theme-comment-alt);
+}
+
+.input-expression::placeholder {
+ text-align: center;
+ font-style: italic;
+ color: var(--theme-comment-alt);
+ opacity: 1;
+}
+
+.input-expression:focus {
+ outline: none;
+ cursor: text;
+}
+
+.expression-input-container {
+ padding: 0.5em;
+ display: flex;
+}
+
+.expression-container {
+ border: 1px;
+ padding: 8px 5px 0px 0px;
+ width: 100%;
+ color: var(--theme-body-color);
+ background-color: var(--theme-body-background);
+ display: flex;
+ position: relative;
+}
+
+.expression-container > .tree {
+ width: 100%;
+ overflow: hidden;
+}
+
+:root.theme-light .expression-container:hover {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+:root.theme-dark .expression-container:hover {
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.expression-container .close-btn {
+ position: absolute;
+ inset-inline-end: 6px;
+ top: 6px;
+}
+
+.expression-container .close {
+ display: none;
+}
+
+.expression-container:hover .close {
+ display: block;
+}
+
+.expression-input {
+ cursor: pointer;
+ max-width: 50%;
+}
+
+.expression-separator {
+ padding: 0px 5px;
+}
+
+.expression-value {
+ overflow-x: scroll;
+ color: var(--theme-content-color2);
+ max-width: 50% !important;
+}
+
+.expression-error {
+ color: var(--theme-highlight-red);
+}
+
+.why-paused {
+ background-color: var(--breakpoint-active-color);
+ border: 1.7px solid var(--breakpoint-active-color);
+ color: var(--theme-highlight-blue);
+ padding: 10px 10px 10px 20px;
+ white-space: normal;
+ opacity: 0.9;
+ font-size: 12px;
+ font-weight: bold;
+ flex: 0 1 auto;
+}
+
+.theme-dark .secondary-panes .why-paused {
+ color: white;
+}
+.breakpoints-list * {
+ user-select: none;
+}
+
+.breakpoints-list .breakpoint {
+ font-size: 12px;
+ color: var(--theme-content-color1);
+ padding: 0.5em 1px;
+ line-height: 1em;
+ position: relative;
+ transition: all 0.25s ease;
+}
+
+html[dir="rtl"] .breakpoints-list .breakpoint {
+ border-right: 4px solid transparent;
+}
+
+html:not([dir="rtl"]) .breakpoints-list .breakpoint {
+ border-left: 4px solid transparent;
+}
+
+.breakpoints-list .breakpoint:last-of-type {
+ padding-bottom: 0.45em;
+}
+
+html:not([dir="rtl"]) .breakpoints-list .breakpoint.is-conditional {
+ border-left-color: var(--theme-graphs-yellow);
+}
+
+html[dir="rtl"] .breakpoints-list .breakpoint.is-conditional {
+ border-right-color: var(--theme-graphs-yellow);
+}
+
+html .breakpoints-list .breakpoint.paused {
+ background-color: var(--theme-toolbar-background-alt);
+ border-color: var(--breakpoint-active-color);
+}
+
+.breakpoints-list .breakpoint.disabled .breakpoint-label {
+ color: var(--theme-content-color3);
+ transition: color 0.5s linear;
+}
+
+.breakpoints-list .breakpoint:hover {
+ cursor: pointer;
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.breakpoints-list .breakpoint.paused:hover {
+ border-color: var(--breakpoint-active-color-hover);
+}
+
+.breakpoints-list .breakpoint-checkbox {
+ margin-inline-start: 0;
+}
+
+.breakpoints-list .breakpoint-label {
+ display: inline-block;
+ padding-inline-start: 2px;
+ padding-bottom: 4px;
+}
+
+.breakpoints-list .pause-indicator {
+ flex: 0 1 content;
+ order: 3;
+}
+
+:root.theme-light .breakpoint-snippet,
+:root.theme-firebug .breakpoint-snippet {
+ color: var(--theme-comment);
+}
+
+:root.theme-dark .breakpoint-snippet {
+ color: var(--theme-body-color);
+ opacity: 0.6;
+}
+
+.breakpoint-snippet {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding-inline-start: 18px;
+ padding-inline-end: 18px;
+}
+
+.breakpoint .close-btn {
+ position: absolute;
+ inset-inline-end: 6px;
+ top: 12px;
+}
+
+.breakpoint .close {
+ display: none;
+}
+
+.breakpoint:hover .close {
+ display: block;
+}
+
+.object-node.default-property {
+ opacity: 0.6;
+}
+
+.object-label {
+ color: var(--theme-highlight-blue);
+}
+
+.objectBox-object,
+.objectBox-string,
+.objectBox-text,
+.objectBox-table,
+.objectLink-textNode,
+.objectLink-event,
+.objectLink-eventLog,
+.objectLink-regexp,
+.objectLink-object,
+.objectLink-Date,
+.theme-dark .objectBox-object,
+.theme-light .objectBox-object {
+ white-space: nowrap;
+}
+
+.scopes-list .tree-node {
+ overflow: hidden;
+}
+.frames ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.frames ul li {
+ cursor: pointer;
+ padding: 7px 10px 7px 21px;
+ overflow: hidden;
+ display: flex;
+ justify-content: space-between;
+}
+
+/* Style the focused call frame like so:
+.frames ul li:focus {
+ border: 3px solid red;
+}
+*/
+
+.frames ul li * {
+ user-select: none;
+}
+
+.frames ul li:nth-of-type(2n) {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.frames .location {
+ font-weight: lighter;
+}
+
+:root.theme-light .frames .location,
+:root.theme-firebug .frames .location {
+ color: var(--theme-comment);
+}
+
+:root.theme-dark .frames .location {
+ color: var(--theme-body-color);
+ opacity: 0.6;
+}
+
+.frames .title {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ margin-right: 1em;
+}
+
+.frames ul li:hover,
+.frames ul li:focus {
+ background-color: var(--theme-toolbar-background-alt);
+ outline: none;
+}
+
+.frames ul li.selected {
+ background-color: var(--theme-selection-background);
+ color: white;
+}
+
+:root.theme-light .frames ul li.selected .location,
+:root.theme-firebug .frames ul li.selected .location,
+:root.theme-dark .frames ul li.selected .location {
+ color: white;
+}
+
+:root.theme-dark .frames ul li:hover .location,
+:root.theme-dark .frames ul li.selected .location {
+ opacity: 1;
+}
+
+.show-more {
+ cursor: pointer;
+ text-align: center;
+ padding: 8px 0px;
+ border-top: 1px solid var(--theme-splitter-color);
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.show-more:hover {
+ background-color: var(--search-overlays-semitransparent);
+}
+.event-listeners {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.event-listeners .listener {
+ cursor: pointer;
+ padding: 7px 10px 7px 21px;
+ clear: both;
+ overflow: hidden;
+}
+
+.event-listeners .listener * {
+ user-select: none;
+}
+
+.event-listeners .listener:nth-of-type(2n) {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.event-listeners .listener .type {
+ color: var(--theme-highlight-bluegrey);
+ padding-right: 5px;
+}
+
+.event-listeners .listener .selector {
+ color: var(--theme-content-color2);
+}
+
+.event-listeners .listener-checkbox {
+ margin-left: 0;
+}
+
+.event-listeners .listener .close-btn {
+ float: right;
+}
+
+.event-listeners .listener .close {
+ display: none;
+}
+
+.event-listeners .listener:hover .close {
+ display: block;
+}
+.accordion {
+ background-color: var(--theme-body-background);
+ width: 100%;
+}
+
+.accordion ._header {
+ background-color: var(--theme-toolbar-background);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ cursor: pointer;
+ font-size: 12px;
+ padding: 5px;
+ transition: all 0.25s ease;
+ width: 100%;
+
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+.accordion ._header {
+ display: flex;
+}
+
+.accordion ._header:hover {
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.accordion ._header button svg,
+.accordion ._header:hover button svg {
+ fill: currentColor;
+ height: 16px;
+}
+
+.accordion ._content {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ font-size: 12px;
+}
+
+.accordion ._header .header-buttons {
+ display: flex;
+ margin-left: auto;
+ padding-right: 5px;
+}
+
+.accordion .header-buttons .add-button {
+ font-size: 180%;
+ text-align: center;
+ line-height: 16px;
+}
+
+.accordion .header-buttons button {
+ color: var(--theme-body-color);
+ border: none;
+ background: none;
+ outline: 0;
+ padding: 0;
+ width: 16px;
+ height: 16px;
+}
+
+.accordion .header-buttons button::-moz-focus-inner {
+ border: none;
+}
+.command-bar {
+ flex: 0 0 30px;
+ border-bottom: 1px solid var(--theme-splitter-color);
+ display: flex;
+ height: 30px;
+ overflow: hidden;
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ background-color: var(--theme-body-background);
+}
+
+.theme-dark .command-bar {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.command-bar > button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ display: inline-block;
+ text-align: center;
+ transition: all 0.25s ease;
+ padding: 8px 5px;
+ position: relative;
+ fill: currentColor;
+}
+
+:root.theme-dark .command-bar > button {
+ color: var(--theme-body-color);
+}
+
+.command-bar > button {
+ margin-inline-end: 0.7em;
+}
+
+html .command-bar > button:disabled {
+ opacity: 0.3;
+ cursor: default;
+}
+
+.command-bar > button > i {
+ height: 100%;
+ width: 100%;
+ display: block;
+}
+
+.command-bar > button > i > svg {
+ width: 16px;
+ height: 16px;
+}
+
+.command-bar button.pause-exceptions {
+ margin-inline-start: 0.5em;
+}
+
+.command-bar .subSettings {
+ float: right;
+}
+
+.command-bar button.pause-exceptions.uncaught {
+ color: var(--theme-highlight-purple);
+}
+
+.command-bar button.pause-exceptions.all {
+ color: var(--theme-highlight-blue);
+}
+.secondary-panes {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ white-space: nowrap;
+}
+
+.secondary-panes * {
+ user-select: none;
+}
+
+.secondary-panes .accordion {
+ overflow-y: auto;
+ overflow-x: hidden;
+ flex: 1 0 auto;
+}
+
+.pane {
+ color: var(--theme-body-color);
+}
+
+.pane .pane-info {
+ font-style: italic;
+ text-align: center;
+ padding: 0.5em;
+ user-select: none;
+}
+
+.theme-dark .secondary-panes .accordion .arrow svg {
+ fill: var(--theme-comment);
+}
+.welcomebox {
+ width: calc(100% - 1px);
+
+ /* Offsetting it by 30px for the sources-header area */
+ height: calc(100% - 30px);
+ position: absolute;
+ top: 30px;
+ left: 0;
+ padding: 50px 0 0 0;
+ text-align: center;
+ font-size: 1.25em;
+ color: var(--theme-comment-alt);
+ background-color: var(--theme-tab-toolbar-background);
+ font-weight: lighter;
+ z-index: 100;
+}
+
+html .welcomebox .toggle-button-end {
+ bottom: 11px;
+ position: absolute;
+ top: auto;
+}
+.dropdown {
+ --width: 150px;
+ background: var(--theme-body-background);
+ border: 1px solid var(--theme-splitter-color);
+ box-shadow: 0 4px 4px 0 var(--search-overlays-semitransparent);
+ max-height: 300px;
+ position: absolute;
+ right: 8px;
+ top: 35px;
+ width: var(--width);
+ z-index: 1000;
+}
+
+html[dir="rtl"] .dropdown {
+ right: calc((var(--width) - 11px) * (-1));
+}
+
+.dropdown-block {
+ padding: 0px 2px;
+ position: relative;
+ align-self: center;
+}
+
+.dropdown-button {
+ cursor: pointer;
+ color: var(--theme-comment);
+ background: none;
+ border: none;
+ padding: 0;
+ font-weight: 100;
+ margin-top: 6px;
+ font-size: 14px;
+}
+
+.dropdown li {
+ transition: all 0.25s ease;
+ padding: 2px 10px 10px 5px;
+ overflow: hidden;
+ height: 30px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.dropdown li:hover {
+ background-color: var(--search-overlays-semitransparent);
+ cursor: pointer;
+}
+
+.dropdown ul {
+ list-style: none;
+ line-height: 2em;
+ font-size: 1em;
+ margin: 0;
+ padding: 0;
+}
+
+.dropdown-mask {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ background: transparent;
+ z-index: 999;
+ left: 0;
+ top: 0;
+}
+.source-header {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ width: 100%;
+ height: 30px;
+ display: flex;
+ align-items: flex-end;
+}
+
+.source-header * {
+ user-select: none;
+}
+
+.source-header .new-tab-btn {
+ padding: 0px 4px;
+ margin-top: 8px;
+ cursor: pointer;
+ fill: var(--theme-comment);
+ transition: 0.1s ease;
+ align-self: center;
+}
+
+.source-header .new-tab-btn svg {
+ width: 12px;
+}
+
+.source-tabs {
+ max-width: calc(100% - 80px);
+ align-self: flex-start;
+}
+
+.source-tab {
+ border: 1px solid transparent;
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ height: 30px;
+ display: inline-flex;
+ align-items: center;
+ position: relative;
+ transition: all 0.25s ease;
+ min-width: 40px;
+ overflow: hidden;
+ padding: 6px;
+ margin-inline-start: 3px;
+ margin-top: 2px;
+}
+
+.source-tab:hover {
+ background-color: var(--theme-toolbar-background-alt);
+ border-color: var(--theme-splitter-color);
+ cursor: pointer;
+}
+
+.source-tab.active {
+ color: var(--theme-body-color);
+ background-color: var(--theme-body-background);
+ border-color: var(--theme-splitter-color);
+ border-bottom-color: transparent;
+}
+
+.source-tab.active path,
+.source-tab:hover path {
+ fill: var(--theme-body-color);
+}
+
+.source-tab .prettyPrint {
+ line-height: 0;
+}
+
+.source-tab .prettyPrint svg {
+ height: 12px;
+ width: 12px;
+}
+
+.source-tab .prettyPrint path {
+ fill: var(--theme-textbox-box-shadow);
+}
+
+.source-tab .filename {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.source-tab.pretty .filename {
+ padding-inline-start: 8px;
+}
+
+.source-tab .close-btn {
+ visibility: hidden;
+ line-height: 0;
+ margin-inline-start: 6px;
+}
+
+.source-tab.active .close-btn {
+ visibility: visible;
+}
+
+.source-tab:hover .close-btn {
+ visibility: visible;
+}
+
+.source-tab .close-btn .close {
+ padding: 0;
+ margin-top: 0;
+ display: inline-flex;
+ justify-content: center;
+}
diff --git a/layout/style/test/gtest/generate_example_stylesheet.py b/layout/style/test/gtest/generate_example_stylesheet.py
new file mode 100644
index 0000000000..5c69f5c702
--- /dev/null
+++ b/layout/style/test/gtest/generate_example_stylesheet.py
@@ -0,0 +1,16 @@
+def main(output, stylesheet):
+ css = open(stylesheet, "r").read()
+ css = (
+ css.replace("\\", "\\\\")
+ .replace("\r", "\\r")
+ .replace("\n", "\\n")
+ .replace('"', '\\"')
+ )
+
+ # Work around "error C2026: string too big"
+ # https://msdn.microsoft.com/en-us/library/dddywwsc.aspx
+ chunk_size = 10000
+ chunks = ('"%s"' % css[i : i + chunk_size] for i in range(0, len(css), chunk_size))
+
+ header = "#define EXAMPLE_STYLESHEET " + " ".join(chunks)
+ output.write(header)
diff --git a/layout/style/test/gtest/moz.build b/layout/style/test/gtest/moz.build
new file mode 100644
index 0000000000..aa3adbc9ab
--- /dev/null
+++ b/layout/style/test/gtest/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+Library("style-gtest")
+
+UNIFIED_SOURCES = [
+ "ImportScannerTest.cpp",
+ "StyloParsingBench.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/layout/style",
+]
+
+GeneratedFile(
+ "ExampleStylesheet.h",
+ script="generate_example_stylesheet.py",
+ inputs=["example.css"],
+)
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/layout/style/test/mapped.css b/layout/style/test/mapped.css
new file mode 100644
index 0000000000..7aebaecc82
--- /dev/null
+++ b/layout/style/test/mapped.css
@@ -0,0 +1,3 @@
+div {
+ color: #f06;
+}
diff --git a/layout/style/test/mapped.css^headers^ b/layout/style/test/mapped.css^headers^
new file mode 100644
index 0000000000..3b74491556
--- /dev/null
+++ b/layout/style/test/mapped.css^headers^
@@ -0,0 +1 @@
+X-SourceMap: mapped.css.map
diff --git a/layout/style/test/mapped2.css b/layout/style/test/mapped2.css
new file mode 100644
index 0000000000..fc42a5abef
--- /dev/null
+++ b/layout/style/test/mapped2.css
@@ -0,0 +1,4 @@
+span {
+ color: #f06;
+}
+//# sourceMappingURL: overridden-by-headers
diff --git a/layout/style/test/mapped2.css^headers^ b/layout/style/test/mapped2.css^headers^
new file mode 100644
index 0000000000..1656e66589
--- /dev/null
+++ b/layout/style/test/mapped2.css^headers^
@@ -0,0 +1,2 @@
+SourceMap: mapped2.css.map
+X-SourceMap: ignored.css.map
diff --git a/layout/style/test/media_queries_iframe.html b/layout/style/test/media_queries_iframe.html
new file mode 100644
index 0000000000..141ecdcd94
--- /dev/null
+++ b/layout/style/test/media_queries_iframe.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US">
+<head>
+ <title>Media Queries Test inner frame</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <style type="text/css" id="style" media="all">
+ body { text-decoration: underline; }
+ </style>
+</head>
+<body>
+
+</body>
+</html>
diff --git a/layout/style/test/media_queries_iframe2.html b/layout/style/test/media_queries_iframe2.html
new file mode 100644
index 0000000000..8768cd53c6
--- /dev/null
+++ b/layout/style/test/media_queries_iframe2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <title>Media Queries Test inner frame</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <style>
+ html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ }
+ </style>
+ <style type="text/css" id="style" media="all and (display-mode: browser)">
+ body { background: yellow; }
+ </style>
+ <style type="text/css" id="style" media="all and (display-mode: standalone)">
+ body { background: green; }
+ </style>
+ <style type="text/css" id="style" media="all and (display-mode: minimal-ui)">
+ body { background: red; }
+ </style>
+ <style type="text/css" id="style" media="all and (display-mode: fullscreen)">
+ body { background: blue; }
+ </style>
+</head>
+<body>
+<script>
+window.addEventListener("message", e => {
+ if (e.data == "get-background-color") {
+ let { backgroundColor } = document.defaultView.getComputedStyle(document.body);
+ e.source.postMessage({ backgroundColor }, e.origin);
+ }
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/mochitest.toml b/layout/style/test/mochitest.toml
new file mode 100644
index 0000000000..9df86ea539
--- /dev/null
+++ b/layout/style/test/mochitest.toml
@@ -0,0 +1,783 @@
+[DEFAULT]
+prefs = [
+ "dom.animations-api.compositing.enabled=true",
+ "dom.animations-api.timelines.enabled=true",
+ "gfx.omta.background-color=true",
+ "gfx.font_loader.delay=0",
+ "layout.css.container-queries.enabled=true",
+ "layout.css.individual-transform.enabled=true",
+ "layout.css.motion-path-ray.enabled=true",
+ "layout.css.motion-path-basic-shapes.enabled=true",
+ "layout.css.motion-path-coord-box.enabled=true",
+ "layout.css.motion-path-offset-position.enabled=true",
+ "layout.css.motion-path-url.enabled=true",
+ "layout.css.backdrop-filter.enabled=true",
+ "layout.css.fit-content-function.enabled=true",
+ "layout.css.scroll-driven-animations.enabled=true",
+ "layout.css.animation-composition.enabled=true",
+ "layout.css.basic-shape-rect.enabled=true",
+ "layout.css.basic-shape-xywh.enabled=true",
+ "layout.css.transform-box-content-stroke.enabled=true",
+]
+support-files = [
+ "animation_utils.js",
+ "bug1729861.js",
+ "ccd-quirks.html",
+ "ccd.sjs",
+ "ccd-standards.html",
+ "chrome/bug418986-2.js",
+ "chrome/match.png",
+ "chrome/mismatch.png",
+ "descriptor_database.js",
+ "!/dom/events/test/event_leak_utils.js",
+ "empty.html",
+ "file_computed_style_bfcache_display_none.html",
+ "file_computed_style_bfcache_display_none2.html",
+ "media_queries_iframe.html",
+ "media_queries_iframe2.html",
+ "neverending_font_load.sjs",
+ "neverending_stylesheet_load.sjs",
+ "post-redirect-1.css",
+ "post-redirect-2.css",
+ "post-redirect-3.css",
+ "property_database.js",
+ "redirect.sjs",
+ "style_attribute_tests.js",
+ "support/blue-100x100.png",
+ "support/1x1-transparent.png",
+ "unstyled.css",
+ "unstyled-frame.css",
+ "unstyled-frame.xml",
+ "unstyled.xml",
+ "viewport_units_iframe.html",
+ "visited_image_loading_frame_empty.html",
+ "visited_image_loading_frame.html",
+ "visited_image_loading.sjs",
+ "visited-lying-inner.html",
+ "visited-pref-iframe.html",
+]
+
+["test_acid3_test46.html"]
+
+["test_addSheet.html"]
+support-files = ["additional_sheets_helper.html"]
+
+["test_additional_sheets.html"]
+support-files = ["additional_sheets_helper.html"]
+
+["test_align_justify_computed_values.html"]
+
+["test_all_shorthand.html"]
+
+["test_animations.html"]
+
+["test_animations_async_tests.html"]
+support-files = [
+ "Ahem.ttf",
+ "file_animations_async_tests.html",
+]
+
+["test_animations_dynamic_changes.html"]
+
+["test_animations_effect_timing_duration.html"]
+
+["test_animations_effect_timing_enddelay.html"]
+
+["test_animations_effect_timing_iterations.html"]
+
+["test_animations_event_handler_attribute.html"]
+
+["test_animations_event_order.html"]
+
+["test_animations_iterationstart.html"]
+
+["test_animations_omta.html"]
+
+["test_animations_omta_scroll.html"]
+support-files = ["file_animations_omta_scroll.html"]
+
+["test_animations_omta_scroll_rtl.html"]
+support-files = ["file_animations_omta_scroll_rtl.html"]
+
+["test_animations_omta_start.html"]
+
+["test_animations_pausing.html"]
+
+["test_animations_playbackrate.html"]
+
+["test_animations_reverse.html"]
+
+["test_animations_styles_on_event.html"]
+
+["test_animations_variable_changes.html"]
+
+["test_animations_with_disabled_properties.html"]
+support-files = ["file_animations_with_disabled_properties.html"]
+
+["test_any_dynamic.html"]
+
+["test_area_url_cursor.html"]
+
+["test_asyncopen.html"]
+
+["test_at_rule_parse_serialize.html"]
+
+["test_attribute_selector_eof_behavior.html"]
+
+["test_backdrop_filter_enabled_state.html"]
+
+["test_background_blend_mode.html"]
+
+["test_border_device_pixel_rounding_initial_style.html"]
+
+["test_box_size_keywords.html"]
+
+["test_bug73586.html"]
+
+["test_bug74880.html"]
+
+["test_bug98997.html"]
+
+["test_bug160403.html"]
+
+["test_bug200089.html"]
+
+["test_bug221428.html"]
+
+["test_bug229915.html"]
+
+["test_bug302186.html"]
+
+["test_bug319381.html"]
+
+["test_bug357614.html"]
+
+["test_bug363146.html"]
+
+["test_bug372770.html"]
+
+["test_bug373293.html"]
+
+["test_bug377947.html"]
+
+["test_bug379440.html"]
+
+["test_bug379741.html"]
+
+["test_bug382027.html"]
+
+["test_bug383075.html"]
+
+["test_bug387615.html"]
+
+["test_bug389464.html"]
+
+["test_bug391034.html"]
+
+["test_bug391221.html"]
+
+["test_bug397427.html"]
+fail-if = ["xorigin"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_bug399349.html"]
+
+["test_bug401046.html"]
+skip-if = ["true"] # Bug 701060
+
+["test_bug405818.html"]
+
+["test_bug412901.html"]
+
+["test_bug413958.html"]
+
+["test_bug418986-2.html"]
+
+["test_bug437915.html"]
+
+["test_bug450191.html"]
+
+["test_bug470769.html"]
+
+["test_bug499655.html"]
+
+["test_bug499655.xhtml"]
+
+["test_bug517224.html"]
+support-files = ["bug517224.sjs"]
+
+["test_bug524175.html"]
+
+["test_bug525952.html"]
+
+["test_bug534804.html"]
+
+["test_bug573255.html"]
+
+["test_bug580685.html"]
+
+["test_bug621351.html"]
+
+["test_bug635286.html"]
+
+["test_bug645998.html"]
+support-files = [
+ "file_bug645998-1.css",
+ "file_bug645998-2.css",
+]
+
+["test_bug652486.html"]
+
+["test_bug657143.html"]
+
+["test_bug667520.html"]
+
+["test_bug716226.html"]
+
+["test_bug732153.html"]
+
+["test_bug732209.html"]
+support-files = ["bug732209-css.sjs"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_bug765590.html"]
+
+["test_bug771043.html"]
+
+["test_bug795520.html"]
+
+["test_bug798843_pref.html"]
+
+["test_bug829816.html"]
+support-files = ["file_bug829816.css"]
+
+["test_bug874919.html"]
+
+["test_bug887741_at-rules_in_declaration_lists.html"]
+
+["test_bug892929.html"]
+
+["test_bug1055933.html"]
+support-files = ["file_bug1055933_circle-xxl.png"]
+
+["test_bug1089417.html"]
+support-files = ["file_bug1089417_iframe.html"]
+
+["test_bug1112014.html"]
+
+["test_bug1203766.html"]
+
+["test_bug1232829.html"]
+
+["test_bug1292447.html"]
+
+["test_bug1330375.html"]
+
+["test_bug1371488.html"]
+
+["test_bug1375944.html"]
+support-files = [
+ "file_bug1375944.html",
+ "Ahem.ttf",
+]
+
+["test_bug1382568.html"]
+support-files = ["bug1382568-iframe.html"]
+
+["test_bug1394302.html"]
+
+["test_bug1443344-1.html"]
+scheme = "https"
+support-files = ["file_bug1443344.css"]
+
+["test_bug1443344-2.html"]
+scheme = "https"
+support-files = ["file_bug1443344.css"]
+
+["test_bug1451199-1.html"]
+
+["test_bug1451199-2.html"]
+
+["test_bug1490890.html"]
+
+["test_bug1505254.html"]
+
+["test_bug1729861.html"]
+
+["test_cascade.html"]
+
+["test_ch_ex_no_infloops.html"]
+
+["test_change_hint_optimizations.html"]
+
+["test_clip-path_polygon.html"]
+
+["test_color_rounding.html"]
+
+["test_compute_data_with_start_struct.html"]
+skip-if = ["os == 'android'"]
+
+["test_computed_style.html"]
+
+["test_computed_style_bfcache_display_none.html"]
+
+["test_computed_style_difference.html"]
+
+["test_computed_style_grid_with_pseudo.html"]
+
+["test_computed_style_in_created_document.html"]
+
+["test_computed_style_min_size_auto.html"]
+
+["test_computed_style_no_flush.html"]
+
+["test_computed_style_no_pseudo.html"]
+
+["test_computed_style_prefs.html"]
+
+["test_condition_text.html"]
+
+["test_constructable_stylesheets_chrome_only_rules_in_content.html"]
+
+["test_counter_descriptor_storage.html"]
+
+["test_counter_style.html"]
+
+["test_crash_with_content_policy.html"]
+support-files = ["file_bug1381233.html"]
+
+["test_css_cross_domain.html"]
+skip-if = [
+ "http3",
+ "http2",
+ "socketprocess_networking",
+]
+
+["test_css_cross_domain_no_orb.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_css_eof_handling.html"]
+
+["test_css_escape_api.html"]
+
+["test_css_function_mismatched_parenthesis.html"]
+
+["test_css_loader_crossorigin_data_url.html"]
+
+["test_css_parse_error_smoketest.html"]
+
+["test_css_supports.html"]
+
+["test_css_supports_variables.html"]
+
+["test_cue_restrictions.html"]
+
+["test_custom_content_inheritance.html"]
+
+["test_default_bidi_css.html"]
+
+["test_default_computed_style.html"]
+
+["test_descriptor_storage.html"]
+
+["test_descriptor_syntax_errors.html"]
+
+["test_display_mode.html"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_dont_use_document_colors.html"]
+
+["test_dont_use_document_fonts.html"]
+
+["test_dynamic_change_causing_reflow.html"]
+
+["test_exposed_prop_accessors.html"]
+
+["test_extra_inherit_initial.html"]
+
+["test_first_letter_restrictions.html"]
+
+["test_first_line_restrictions.html"]
+
+["test_flexbox_child_display_values.xhtml"]
+
+["test_flexbox_flex_grow_and_shrink.html"]
+
+["test_flexbox_flex_shorthand.html"]
+
+["test_flexbox_focus_order.html"]
+
+["test_flexbox_layout.html"]
+support-files = ["flexbox_layout_testcases.js"]
+
+["test_flexbox_order.html"]
+
+["test_flexbox_order_abspos.html"]
+
+["test_flexbox_order_table.html"]
+
+["test_flexbox_reflow_counts.html"]
+skip-if = ["verify"]
+
+["test_flushing_frame.html"]
+
+["test_font_face_cascade.html"]
+
+["test_font_face_parser.html"]
+
+["test_font_family_parsing.html"]
+
+["test_font_family_serialization.html"]
+
+["test_font_loading_api.html"]
+support-files = [
+ "BitPattern.woff",
+ "file_font_loading_api_vframe.html",
+]
+# This test checks font loading state. When loaded second time, fonts may be
+# loaded synchronously, causing this test to fail in test-verify task.
+skip-if = [
+ "verify", # Bug 1455824
+ "os == 'android'", # Bug 1455824
+ "http3",
+ "http2",
+]
+
+["test_garbage_at_end_of_declarations.html"]
+
+["test_grid_computed_values.html"]
+
+["test_grid_container_shorthands.html"]
+
+["test_grid_item_shorthands.html"]
+
+["test_grid_shorthand_serialization.html"]
+
+["test_group_insertRule.html"]
+
+["test_hover_on_part.html"]
+
+["test_hover_quirk.html"]
+
+["test_html_attribute_computed_values.html"]
+
+["test_ident_escaping.html"]
+
+["test_img_src_causing_reflow.html"]
+
+["test_import_preload.html"]
+support-files = ["slow_load.sjs"]
+# Test is slightly racy and on Android it fails frequently enough to be
+# annoying.
+skip-if = ["os == 'android'"]
+
+["test_inherit_computation.html"]
+
+["test_inherit_storage.html"]
+
+["test_initial_computation.html"]
+
+["test_initial_storage.html"]
+
+["test_invalidation_basic.html"]
+
+["test_keyframes_rules.html"]
+
+["test_keyframes_vendor_prefix.html"]
+
+["test_load_events_on_stylesheets.html"]
+support-files = [
+ "slow_broken_sheet.sjs",
+ "slow_ok_sheet.sjs",
+]
+
+["test_logical_properties.html"]
+
+["test_marker_restrictions.html"]
+
+["test_mask_image_CORS.html"]
+
+["test_media_queries.html"]
+# times out on verify, see bug 1461033.
+skip-if = ["verify"]
+support-files = ["chrome/chrome-only-media-queries.js"]
+
+["test_media_queries_dynamic.html"]
+skip-if = ["xorigin"] # Crashes, Assertion failure: mInFlightProcessId == 0, at /builds/worker/checkouts/gecko/docshell/base/CanonicalBrowsingContext.cpp:110, [Child][MessageChannel] Error: (msgtype=0xFFF7,name=<unknown IPC msg name>) Channel error: cannot send/recv
+
+["test_media_query_list.html"]
+
+["test_media_query_serialization.html"]
+
+["test_moz_device_pixel_ratio.html"]
+
+["test_moz_prefixed_cursor.html"]
+
+["test_mq_any_hover_and_any_pointer.html"]
+
+["test_mq_changes_in_iframe.html"]
+support-files = ["mq_changes_child.html"]
+skip-if = [
+ "headless",
+ "os == 'win'",
+]
+
+["test_mq_hover_and_pointer.html"]
+
+["test_mq_prefers_contrast_dynamic.html"]
+skip-if = [
+ "headless",
+ "os == 'win'",
+]
+
+["test_mq_prefers_reduced_motion_dynamic.html"]
+skip-if = [
+ "headless",
+ "os == 'win'",
+]
+
+["test_mql_event_listener_leaks.html"]
+
+["test_namespace_rule.html"]
+
+["test_non_content_accessible_env_vars.html"]
+
+["test_non_content_accessible_properties.html"]
+
+["test_non_content_accessible_pseudos.html"]
+
+["test_non_content_accessible_values.html"]
+
+["test_non_matching_sheet_media.html"]
+
+["test_of_type_selectors.xhtml"]
+
+["test_overscroll_behavior_pref.html"]
+
+["test_page_parser.html"]
+
+["test_parse_eof.html"]
+
+["test_parse_ident.html"]
+
+["test_parse_rule.html"]
+
+["test_parse_url.html"]
+
+["test_parser_diagnostics_unprintables.html"]
+
+["test_pixel_lengths.html"]
+
+["test_placeholder_restrictions.html"]
+
+["test_pointer-events.html"]
+
+["test_position_float_display.html"]
+
+["test_position_sticky.html"]
+
+["test_prefers_contrast_color_pairs.html"]
+
+["test_priority_preservation.html"]
+
+["test_property_database.html"]
+
+["test_property_syntax_errors.html"]
+
+["test_pseudo_display_fixup.html"]
+
+["test_pseudoelement_parsing.html"]
+
+["test_pseudoelement_state.html"]
+skip-if = ["verify && debug && os == 'linux'"]
+
+["test_query_container_for.html"]
+
+["test_redundant_font_download.html"]
+support-files = ["redundant_font_download.sjs"]
+
+["test_reframe_cb.html"]
+
+["test_reframe_image_loading.html"]
+
+["test_reframe_input.html"]
+
+["test_reframe_pseudo_element.html"]
+
+["test_rem_unit.html"]
+
+["test_restyle_table_wrapper.html"]
+
+["test_restyles_in_smil_animation.html"]
+
+["test_revert.html"]
+
+["test_root_node_display.html"]
+
+["test_rule_insertion.html"]
+
+["test_rules_out_of_sheets.html"]
+
+["test_selectors.html"]
+
+["test_setPropertyWithNull.html"]
+skip-if = ["xorigin && debug"]
+
+["test_shape_outside_CORS.html"]
+
+["test_shared_sheet_caching.html"]
+support-files = [
+ "file_shared_sheet_caching.css",
+ "file_shared_sheet_caching.html",
+]
+fail-if = ["xorigin"]
+
+["test_sheet_privilege.html"]
+
+["test_shorthand_property_getters.html"]
+
+["test_specified_value_serialization.html"]
+support-files = ["file_specified_value_serialization_individual_transforms.html"]
+
+["test_style_attr_listener.html"]
+
+["test_style_attribute_quirks.html"]
+
+["test_style_attribute_standards.html"]
+
+["test_style_struct_copy_constructors.html"]
+
+["test_stylesheet_additions.html"]
+
+["test_stylesheet_clone_font_face.html"]
+
+["test_supports_rules.html"]
+
+["test_system_font_serialization.html"]
+
+["test_text_decoration_shorthands.html"]
+
+["test_transitions.html"]
+
+["test_transitions_and_reframes.html"]
+
+["test_transitions_and_restyles.html"]
+
+["test_transitions_and_zoom.html"]
+
+["test_transitions_at_start.html"]
+
+["test_transitions_bug537151.html"]
+
+["test_transitions_cancel_near_end.html"]
+
+["test_transitions_computed_value_combinations.html"]
+
+["test_transitions_computed_values.html"]
+
+["test_transitions_dynamic_changes.html"]
+
+["test_transitions_events.html"]
+
+["test_transitions_per_property.html"]
+
+["test_transitions_replacement_on_busy_frame.html"]
+
+["test_transitions_replacement_with_setKeyframes.html"]
+
+["test_transitions_step_functions.html"]
+
+["test_unclosed_parentheses.html"]
+
+["test_unicode_range_loading.html"]
+support-files = [
+ "../../reftests/fonts/markA.woff",
+ "../../reftests/fonts/markB.woff",
+ "../../reftests/fonts/markC.woff",
+ "../../reftests/fonts/markD.woff",
+]
+
+["test_units_angle.html"]
+
+["test_units_frequency.html"]
+
+["test_units_length.html"]
+
+["test_units_time.html"]
+
+["test_use_counters.html"]
+skip-if = ["!nightly_build"]
+
+["test_user_sheet_shadow_dom.html"]
+
+["test_value_cloning.html"]
+# This test requires too much memory on TSan (bug 1612707)
+# See bug 775227 for android
+skip-if = [
+ "os == 'android'",
+ "tsan",
+]
+
+["test_value_computation.html"]
+# This test requires too much memory on TSan (bug 1612707)
+skip-if = ["tsan"]
+
+["test_value_storage.html"]
+
+["test_variable_serialization_computed.html"]
+
+["test_variable_serialization_specified.html"]
+
+["test_variables.html"]
+support-files = ["support/external-variable-url.css"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_variables_loop.html"]
+
+["test_variables_order.html"]
+support-files = ["support/external-variable-url.css"]
+
+["test_video_object_fit.html"]
+
+["test_viewport_scrollbar_causing_reflow.html"]
+skip-if = ["verify && (os == 'win' || os == 'mac')"]
+
+["test_viewport_units.html"]
+
+["test_visited_image_loading.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+
+["test_visited_image_loading_empty.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+
+["test_visited_lying.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+fail-if = ["xorigin"]
+
+["test_visited_pref.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+fail-if = ["xorigin"]
+
+["test_visited_reftests.html"]
+skip-if = ["os == 'android'"] # TIMED_OUT for android
+
+["test_webkit_device_pixel_ratio.html"]
+skip-if = ["xorigin"] # process crash: Assertion failure: mInFlightProcessId == 0, at /builds/worker/checkouts/gecko/docshell/base/CanonicalBrowsingContext.cpp:110
+
+["test_webkit_flex_display.html"]
+skip-if = ["xorigin"] # Crashes, Assertion failure: mInFlightProcessId == 0, at /builds/worker/checkouts/gecko/docshell/base/CanonicalBrowsingContext.cpp:110
diff --git a/layout/style/test/moz.build b/layout/style/test/moz.build
new file mode 100644
index 0000000000..ee826be9ed
--- /dev/null
+++ b/layout/style/test/moz.build
@@ -0,0 +1,152 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+# ** Note: The comment below along with the CPP_UNIT_TESTS and LIBS variables
+# ** were commented out in the original Makefile.in, and should be restored
+# ** some day, perhaps as a gtest.
+#
+# ParseCSS.cpp used to be built as a test program, but it was not
+# being used for anything, and recent changes to the CSS loader have
+# made it fail to link. Further changes are planned which should make
+# it buildable again.
+
+DIRS += ["gtest"]
+
+HostSimplePrograms(
+ [
+ "host_ListCSSProperties",
+ ]
+)
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.toml",
+]
+BROWSER_CHROME_MANIFESTS += ["browser.toml"]
+MOCHITEST_CHROME_MANIFESTS += ["chrome/chrome.toml"]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test.chrome += [
+ "chrome/display_mode_reflow_iframe.html",
+ "chrome/moz_document_helper.html",
+ "media_queries_iframe.html",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test["css-visited"] += [
+ "/layout/reftests/css-visited/border-1-ref.html",
+ "/layout/reftests/css-visited/border-1.html",
+ "/layout/reftests/css-visited/border-2-ref.html",
+ "/layout/reftests/css-visited/border-2a.html",
+ "/layout/reftests/css-visited/border-2b.html",
+ "/layout/reftests/css-visited/border-collapse-1-ref.html",
+ "/layout/reftests/css-visited/border-collapse-1.html",
+ "/layout/reftests/css-visited/caret-color-on-visited-1-ref.html",
+ "/layout/reftests/css-visited/caret-color-on-visited-1.html",
+ "/layout/reftests/css-visited/color-choice-1-ref.html",
+ "/layout/reftests/css-visited/color-choice-1.html",
+ "/layout/reftests/css-visited/color-on-bullets-1-ref.html",
+ "/layout/reftests/css-visited/color-on-bullets-1.html",
+ "/layout/reftests/css-visited/color-on-link-1-ref.html",
+ "/layout/reftests/css-visited/color-on-link-1.html",
+ "/layout/reftests/css-visited/color-on-link-before-1.html",
+ "/layout/reftests/css-visited/color-on-text-decoration-1-ref.html",
+ "/layout/reftests/css-visited/color-on-text-decoration-1.html",
+ "/layout/reftests/css-visited/color-on-visited-1-ref.html",
+ "/layout/reftests/css-visited/color-on-visited-1.html",
+ "/layout/reftests/css-visited/color-on-visited-before-1.html",
+ "/layout/reftests/css-visited/color-on-visited-text-1-ref.html",
+ "/layout/reftests/css-visited/color-on-visited-text-1.html",
+ "/layout/reftests/css-visited/column-rule-1-notref.html",
+ "/layout/reftests/css-visited/column-rule-1-ref.html",
+ "/layout/reftests/css-visited/column-rule-1.html",
+ "/layout/reftests/css-visited/content-before-1-ref.html",
+ "/layout/reftests/css-visited/content-color-on-link-before-1-ref.html",
+ "/layout/reftests/css-visited/content-color-on-link-before-1.html",
+ "/layout/reftests/css-visited/content-color-on-visited-before-1-ref.html",
+ "/layout/reftests/css-visited/content-color-on-visited-before-1.html",
+ "/layout/reftests/css-visited/content-on-link-before-1.html",
+ "/layout/reftests/css-visited/content-on-visited-before-1.html",
+ "/layout/reftests/css-visited/first-line-1-ref.html",
+ "/layout/reftests/css-visited/first-line-1.html",
+ "/layout/reftests/css-visited/inherit-keyword-1-ref.html",
+ "/layout/reftests/css-visited/inherit-keyword-1.xhtml",
+ "/layout/reftests/css-visited/link-root-1-ref.xhtml",
+ "/layout/reftests/css-visited/link-root-1.xhtml",
+ "/layout/reftests/css-visited/logical-box-border-color-visited-link-001.html",
+ "/layout/reftests/css-visited/logical-box-border-color-visited-link-002.html",
+ "/layout/reftests/css-visited/logical-box-border-color-visited-link-003.html",
+ "/layout/reftests/css-visited/logical-box-border-color-visited-link-ref.html",
+ "/layout/reftests/css-visited/mathml-links-ref.html",
+ "/layout/reftests/css-visited/mathml-links.html",
+ "/layout/reftests/css-visited/outline-1-ref.html",
+ "/layout/reftests/css-visited/outline-1.html",
+ "/layout/reftests/css-visited/placeholder-1-ref.html",
+ "/layout/reftests/css-visited/placeholder-1.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-1-ref.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-1.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-2-ref.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-2.html",
+ "/layout/reftests/css-visited/selector-adj-sibling-3-ref.xhtml",
+ "/layout/reftests/css-visited/selector-adj-sibling-3.xhtml",
+ "/layout/reftests/css-visited/selector-any-sibling-1-ref.html",
+ "/layout/reftests/css-visited/selector-any-sibling-1.html",
+ "/layout/reftests/css-visited/selector-any-sibling-2-ref.html",
+ "/layout/reftests/css-visited/selector-any-sibling-2.html",
+ "/layout/reftests/css-visited/selector-child-1-ref.html",
+ "/layout/reftests/css-visited/selector-child-1.html",
+ "/layout/reftests/css-visited/selector-child-2-ref.xhtml",
+ "/layout/reftests/css-visited/selector-child-2.xhtml",
+ "/layout/reftests/css-visited/selector-descendant-1-ref.html",
+ "/layout/reftests/css-visited/selector-descendant-1.html",
+ "/layout/reftests/css-visited/selector-descendant-2-ref.xhtml",
+ "/layout/reftests/css-visited/selector-descendant-2.xhtml",
+ "/layout/reftests/css-visited/subject-of-selector-1-ref.html",
+ "/layout/reftests/css-visited/subject-of-selector-adj-sibling-1.html",
+ "/layout/reftests/css-visited/subject-of-selector-any-sibling-1.html",
+ "/layout/reftests/css-visited/subject-of-selector-child-1.html",
+ "/layout/reftests/css-visited/subject-of-selector-descendant-1.html",
+ "/layout/reftests/css-visited/subject-of-selector-descendant-2-ref.xhtml",
+ "/layout/reftests/css-visited/subject-of-selector-descendant-2.xhtml",
+ "/layout/reftests/css-visited/svg-paint-currentcolor-visited-ref.svg",
+ "/layout/reftests/css-visited/svg-paint-currentcolor-visited.svg",
+ "/layout/reftests/css-visited/transition-on-visited-ref.html",
+ "/layout/reftests/css-visited/transition-on-visited.html",
+ "/layout/reftests/css-visited/variables-visited-ref.html",
+ "/layout/reftests/css-visited/variables-visited.html",
+ "/layout/reftests/css-visited/visited-inherit-1-ref.html",
+ "/layout/reftests/css-visited/visited-inherit-1.html",
+ "/layout/reftests/css-visited/visited-page.html",
+ "/layout/reftests/css-visited/white-to-transparent-1-ref.html",
+ "/layout/reftests/css-visited/white-to-transparent-1.html",
+ "/layout/reftests/css-visited/width-1-ref.html",
+ "/layout/reftests/css-visited/width-on-link-1.html",
+ "/layout/reftests/css-visited/width-on-visited-1.html",
+ "/layout/reftests/fonts/Ahem.ttf",
+ "/layout/reftests/svg/as-image/svg-image-visited-1-ref.html",
+ "/layout/reftests/svg/as-image/svg-image-visited-1a-helper.svg",
+ "/layout/reftests/svg/as-image/svg-image-visited-1a.html",
+ "/layout/reftests/svg/as-image/svg-image-visited-1b-helper.svg",
+ "/layout/reftests/svg/as-image/svg-image-visited-1b.html",
+ "/layout/reftests/svg/as-image/svg-image-visited-1c-helper.svg",
+ "/layout/reftests/svg/as-image/svg-image-visited-1c.html",
+ "/layout/reftests/svg/as-image/svg-image-visited-1d-helper.svg",
+ "/layout/reftests/svg/as-image/svg-image-visited-1d.html",
+ "/layout/reftests/svg/pseudo-classes-02-ref.svg",
+ "/layout/reftests/svg/pseudo-classes-02.svg",
+]
+
+DEFINES["MOZILLA_INTERNAL_API"] = True
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ GeneratedFile(
+ "css_properties.js",
+ script="gen-css-properties.py",
+ inputs=[
+ "css_properties_like_longhand.js",
+ "!host_ListCSSProperties%s" % CONFIG["HOST_BIN_SUFFIX"],
+ ],
+ )
+ TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test += [
+ "!css_properties.js"
+ ]
diff --git a/layout/style/test/mq_changes_child.html b/layout/style/test/mq_changes_child.html
new file mode 100644
index 0000000000..f594e9f00d
--- /dev/null
+++ b/layout/style/test/mq_changes_child.html
@@ -0,0 +1,8 @@
+<script>
+const mql = matchMedia("(prefers-reduced-motion: reduce)");
+mql.addEventListener("change", event => {
+ parent.postMessage({ "matches": event.matches }, "*");
+});
+
+window.onload = () => { parent.postMessage("ready", "*"); };
+</script>
diff --git a/layout/style/test/neverending_font_load.sjs b/layout/style/test/neverending_font_load.sjs
new file mode 100644
index 0000000000..b02fc377a8
--- /dev/null
+++ b/layout/style/test/neverending_font_load.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "application/octet-stream", false);
+ response.write("");
+}
diff --git a/layout/style/test/neverending_stylesheet_load.sjs b/layout/style/test/neverending_stylesheet_load.sjs
new file mode 100644
index 0000000000..048263fe54
--- /dev/null
+++ b/layout/style/test/neverending_stylesheet_load.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "text/css", false);
+ response.write("");
+}
diff --git a/layout/style/test/post-redirect-1.css b/layout/style/test/post-redirect-1.css
new file mode 100644
index 0000000000..3620c9f377
--- /dev/null
+++ b/layout/style/test/post-redirect-1.css
@@ -0,0 +1 @@
+#one { color: green; background: url("?1"); }
diff --git a/layout/style/test/post-redirect-2.css b/layout/style/test/post-redirect-2.css
new file mode 100644
index 0000000000..3bdf3279d3
--- /dev/null
+++ b/layout/style/test/post-redirect-2.css
@@ -0,0 +1 @@
+#two { color: green; background: url("?1"); }
diff --git a/layout/style/test/post-redirect-3.css b/layout/style/test/post-redirect-3.css
new file mode 100644
index 0000000000..dd98be8e66
--- /dev/null
+++ b/layout/style/test/post-redirect-3.css
@@ -0,0 +1 @@
+#three { color: green; background: url("?1"); }
diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js
new file mode 100644
index 0000000000..422f2ffe5c
--- /dev/null
+++ b/layout/style/test/property_database.js
@@ -0,0 +1,14128 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* eslint-disable dot-notation */
+/* vim: set ts=2 sw=2 sts=2 et: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+// Utility function. Returns true if the given boolean pref...
+// (a) exists and (b) is set to true.
+// Otherwise, returns false.
+//
+// This function also reports a test failure if the pref isn't set at all. This
+// ensures that we remove pref-checks from mochitests (instead of accidentally
+// disabling the tests that are controlled by that check) when we remove a
+// mature feature's pref from the rest of the codebase.
+function IsCSSPropertyPrefEnabled(prefName) {
+ try {
+ if (SpecialPowers.getBoolPref(prefName)) {
+ return true;
+ }
+ } catch (ex) {
+ ok(
+ false,
+ "Failed to look up property-controlling pref '" +
+ prefName +
+ "' (" +
+ ex +
+ ")"
+ );
+ }
+
+ return false;
+}
+
+// True longhand properties.
+const CSS_TYPE_LONGHAND = 0;
+
+// True shorthand properties.
+const CSS_TYPE_TRUE_SHORTHAND = 1;
+
+// Properties that we handle as shorthands but were longhands either in
+// the current spec or earlier versions of the spec.
+const CSS_TYPE_SHORTHAND_AND_LONGHAND = 2;
+
+// Legacy shorthand properties, that behave mostly like an alias
+// (CSS_TYPE_SHORTHAND_AND_LONGHAND) but not quite because their syntax may not
+// match, plus they shouldn't serialize in cssText.
+const CSS_TYPE_LEGACY_SHORTHAND = 3;
+
+// Each property has the following fields:
+// domProp: The name of the relevant member of nsIDOM[NS]CSS2Properties
+// inherited: Whether the property is inherited by default (stated as
+// yes or no in the property header in all CSS specs)
+// type: see above
+// alias_for: optional, indicates that the property is an alias for
+// some other property that is the preferred serialization. (Type
+// must not be CSS_TYPE_LONGHAND.)
+// logical: optional, indicates that the property is a logical directional
+// property. (Type must be CSS_TYPE_LONGHAND.)
+// axis: optional, indicates that the property is an axis-related logical
+// directional property. (Type must be CSS_TYPE_LONGHAND and 'logical'
+// must be true.)
+// initial_values: Values whose computed value should be the same as the
+// computed value for the property's initial value.
+// other_values: Values whose computed value should be different from the
+// computed value for the property's initial value.
+// XXX Should have a third field for values whose computed value may or
+// may not be the same as for the property's initial value.
+// invalid_values: Things that are not values for the property and
+// should be rejected, but which are balanced and should not absorb
+// what follows
+// quirks_values: Values that should be accepted in quirks mode only,
+// mapped to the values they are equivalent to.
+// unbalanced_values: Things that are not values for the property and
+// should be rejected, and which also contain unbalanced constructs
+// that should absorb what follows
+//
+// Note: By default, an alias is assumed to accept/reject the same values as
+// the property that it aliases, and to have the same prerequisites. So, if
+// "alias_for" is set, the "*_values" and "prerequisites" fields can simply
+// be omitted, and they'll be populated automatically to match the aliased
+// property's fields.
+
+// Helper functions used to construct gCSSProperties.
+
+function initial_font_family_is_sans_serif() {
+ // The initial value of 'font-family' might be 'serif' or
+ // 'sans-serif'.
+ const meta = document.createElement("meta");
+ meta.setAttribute("style", "font: initial;");
+ document.documentElement.appendChild(meta);
+ const family = getComputedStyle(meta).fontFamily;
+ meta.remove();
+ return family == "sans-serif";
+}
+
+var gInitialFontFamilyIsSansSerif = initial_font_family_is_sans_serif();
+
+// shared by background-image and border-image-source
+var validNonUrlImageValues = [
+ "-moz-element(#a)",
+ "-moz-element( #a )",
+ "-moz-element(#a-1)",
+ "-moz-element(#a\\:1)",
+ /* gradient torture test */
+ "linear-gradient(red, blue)",
+ "linear-gradient(red, yellow, blue)",
+ "linear-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "linear-gradient(red, yellow, green, blue 50%)",
+ "linear-gradient(red -50%, yellow -25%, green, blue)",
+ "linear-gradient(red -99px, yellow, green, blue 120%)",
+ "linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+ "linear-gradient(red, green calc(50% + 20px), blue)",
+ "linear-gradient(180deg, red, blue)",
+
+ "linear-gradient(to top, red, blue)",
+ "linear-gradient(to bottom, red, blue)",
+ "linear-gradient(to left, red, blue)",
+ "linear-gradient(to right, red, blue)",
+ "linear-gradient(to top left, red, blue)",
+ "linear-gradient(to top right, red, blue)",
+ "linear-gradient(to bottom left, red, blue)",
+ "linear-gradient(to bottom right, red, blue)",
+ "linear-gradient(to left top, red, blue)",
+ "linear-gradient(to left bottom, red, blue)",
+ "linear-gradient(to right top, red, blue)",
+ "linear-gradient(to right bottom, red, blue)",
+
+ "linear-gradient(-33deg, red, blue)",
+ "linear-gradient(30grad, red, blue)",
+ "linear-gradient(10deg, red, blue)",
+ "linear-gradient(1turn, red, blue)",
+ "linear-gradient(.414rad, red, blue)",
+
+ "linear-gradient(.414rad, red, 50%, blue)",
+ "linear-gradient(.414rad, red, 0%, blue)",
+ "linear-gradient(.414rad, red, 100%, blue)",
+
+ "linear-gradient(.414rad, red 50%, 50%, blue 50%)",
+ "linear-gradient(.414rad, red 50%, 20%, blue 50%)",
+ "linear-gradient(.414rad, red 50%, 30%, blue 10%)",
+ "linear-gradient(to right bottom, red, 20%, green 50%, 65%, blue)",
+ "linear-gradient(to right bottom, red, 20%, green 10%, blue)",
+ "linear-gradient(to right bottom, red, 50%, green 50%, 50%, blue)",
+ "linear-gradient(to right bottom, red, 0%, green 50%, 100%, blue)",
+
+ "linear-gradient(red 0% 100%)",
+ "linear-gradient(red 0% 50%, blue 50%)",
+ "linear-gradient(red 0% 50%, blue 50% 100%)",
+ "linear-gradient(red 0% 50%, 0%, blue 50%)",
+ "linear-gradient(red 0% 50%, 0%, blue 50% 100%)",
+
+ /* Unitless 0 is valid as an <angle> */
+ "linear-gradient(0, red, blue)",
+
+ "radial-gradient(red, blue)",
+ "radial-gradient(red, yellow, blue)",
+ "radial-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "radial-gradient(red, yellow, green, blue 50%)",
+ "radial-gradient(red -50%, yellow -25%, green, blue)",
+ "radial-gradient(red -99px, yellow, green, blue 120%)",
+ "radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+
+ "radial-gradient(0 0, red, blue)",
+ "radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "radial-gradient(at top left, red, blue)",
+ "radial-gradient(at 20% bottom, red, blue)",
+ "radial-gradient(at center 20%, red, blue)",
+ "radial-gradient(at left 35px, red, blue)",
+ "radial-gradient(at 10% 10em, red, blue)",
+ "radial-gradient(at 44px top, red, blue)",
+ "radial-gradient(at 0 0, red, blue)",
+
+ "radial-gradient(farthest-corner, red, blue)",
+ "radial-gradient(circle, red, blue)",
+ "radial-gradient(ellipse closest-corner, red, blue)",
+ "radial-gradient(closest-corner ellipse, red, blue)",
+ "radial-gradient(farthest-side circle, red, blue)",
+
+ "radial-gradient(at 43px, red, blue)",
+ "radial-gradient(at 43px 43px, red, blue)",
+ "radial-gradient(at 50% 50%, red, blue)",
+ "radial-gradient(at 43px 50%, red, blue)",
+ "radial-gradient(at 50% 43px, red, blue)",
+ "radial-gradient(circle 43px, red, blue)",
+ "radial-gradient(43px circle, red, blue)",
+ "radial-gradient(ellipse 43px 43px, red, blue)",
+ "radial-gradient(ellipse 50% 50%, red, blue)",
+ "radial-gradient(ellipse 43px 50%, red, blue)",
+ "radial-gradient(ellipse 50% 43px, red, blue)",
+ "radial-gradient(50% 43px ellipse, red, blue)",
+
+ "radial-gradient(farthest-corner at top left, red, blue)",
+ "radial-gradient(ellipse closest-corner at 45px, red, blue)",
+ "radial-gradient(circle farthest-side at 45px, red, blue)",
+ "radial-gradient(closest-side ellipse at 50%, red, blue)",
+ "radial-gradient(farthest-corner circle at 4em, red, blue)",
+
+ "radial-gradient(30% 40% at top left, red, blue)",
+ "radial-gradient(50px 60px at 15% 20%, red, blue)",
+ "radial-gradient(7em 8em at 45px, red, blue)",
+
+ "radial-gradient(circle at 15% 20%, red, blue)",
+
+ "radial-gradient(red 0% 100%)",
+ "radial-gradient(red 0% 50%, blue 50%)",
+ "radial-gradient(red 0% 50%, blue 50% 100%)",
+ "radial-gradient(red 0% 50%, 0%, blue 50%)",
+ "radial-gradient(red 0% 50%, 0%, blue 50% 100%)",
+
+ "repeating-radial-gradient(red, blue)",
+ "repeating-radial-gradient(red, yellow, blue)",
+ "repeating-radial-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "repeating-radial-gradient(red, yellow, green, blue 50%)",
+ "repeating-radial-gradient(red -50%, yellow -25%, green, blue)",
+ "repeating-radial-gradient(red -99px, yellow, green, blue 120%)",
+ "repeating-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "repeating-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "repeating-radial-gradient(at top left, red, blue)",
+ "repeating-radial-gradient(at 0 0, red, blue)",
+ "repeating-radial-gradient(at 20% bottom, red, blue)",
+ "repeating-radial-gradient(at center 20%, red, blue)",
+ "repeating-radial-gradient(at left 35px, red, blue)",
+ "repeating-radial-gradient(at 10% 10em, red, blue)",
+ "repeating-radial-gradient(at 44px top, red, blue)",
+
+ "repeating-radial-gradient(farthest-corner at top left, red, blue)",
+ "repeating-radial-gradient(closest-corner ellipse at 45px, red, blue)",
+ "repeating-radial-gradient(farthest-side circle at 45px, red, blue)",
+ "repeating-radial-gradient(ellipse closest-side at 50%, red, blue)",
+ "repeating-radial-gradient(circle farthest-corner at 4em, red, blue)",
+
+ "repeating-radial-gradient(30% 40% at top left, red, blue)",
+ "repeating-radial-gradient(50px 60px at 15% 20%, red, blue)",
+ "repeating-radial-gradient(7em 8em at 45px, red, blue)",
+
+ // When that happens this should be moved to the `invalid` list.
+ "repeating-radial-gradient(circle closest-side at left 0px bottom 7in, hsl(2,2%,5%), rgb(1,6,0))",
+
+ "radial-gradient(at calc(25%) top, red, blue)",
+ "radial-gradient(at left calc(25%), red, blue)",
+ "radial-gradient(at calc(25px) top, red, blue)",
+ "radial-gradient(at left calc(25px), red, blue)",
+ "radial-gradient(at calc(-25%) top, red, blue)",
+ "radial-gradient(at left calc(-25%), red, blue)",
+ "radial-gradient(at calc(-25px) top, red, blue)",
+ "radial-gradient(at left calc(-25px), red, blue)",
+ "radial-gradient(at calc(100px + -25%) top, red, blue)",
+ "radial-gradient(at left calc(100px + -25%), red, blue)",
+ "radial-gradient(at calc(100px + -25px) top, red, blue)",
+ "radial-gradient(at left calc(100px + -25px), red, blue)",
+
+ "image-set(linear-gradient(green, green) 1x, url(foobar.png) 2x)",
+ "image-set(linear-gradient(red, red), url(foobar.png) 2x)",
+ "image-set(url(foobar.png) 2x)",
+ "image-set(url(foobar.png) 1x, url(bar.png) 2x, url(baz.png) 3x)",
+ "image-set('foobar.png', 'bar.png' 2x, url(baz.png) 3x)",
+ "image-set(url(foobar.png) type('image/png'))",
+ "image-set(url(foobar.png) 1x type('image/png'))",
+ "image-set(url(foobar.png) type('image/png') 1x)",
+
+ ...(IsCSSPropertyPrefEnabled("layout.css.cross-fade.enabled")
+ ? [
+ "cross-fade(red, blue)",
+ "cross-fade(red)",
+ "cross-fade(red 50%)",
+ // see: <https://github.com/w3c/csswg-drafts/issues/5333>. This
+ // may become invalid depending on how discussion on that issue
+ // goes.
+ "cross-fade(red -50%, blue 150%)",
+ "cross-fade(red -50%, url(www.example.com))",
+
+ "cross-fade(url(http://placekitten.com/200/300), 55% linear-gradient(red, blue))",
+ "cross-fade(cross-fade(red, white), cross-fade(blue))",
+ "cross-fade(gold 77%, 60% blue)",
+
+ "cross-fade(url(http://placekitten.com/200/300), url(http://placekitten.com/200/300))",
+ "cross-fade(#F0F8FF, rgb(0, 0, 0), rgba(0, 255, 0, 1) 25%)",
+ ]
+ : []),
+
+ // Conic gradient
+ "conic-gradient(red, blue)",
+ "conic-gradient(red,blue,yellow)",
+ "conic-gradient( red , blue, yellow)",
+ "conic-gradient(red 0, blue 50deg)",
+ "conic-gradient(red 10%, blue 50%)",
+ "conic-gradient(red -50deg, blue 50deg)",
+ "conic-gradient(red 50deg, blue 0.3turn, yellow 200grad, orange 60% 5rad)",
+
+ "conic-gradient(red 0 100%)",
+ "conic-gradient(red 0 50%, blue 50%)",
+ "conic-gradient(red 0 50deg, blue 50% 100%)",
+ "conic-gradient(red 0 50%, 0deg, blue 50%)",
+ "conic-gradient(red 0deg 50%, 0%, blue 50% 100%)",
+
+ "conic-gradient(from 0, red, blue)",
+ "conic-gradient(from 40deg, red, blue)",
+ "conic-gradient(from 0.4turn, red, blue)",
+ "conic-gradient(from 200grad, red, blue)",
+ "conic-gradient(from 5rad, red, blue)",
+
+ "conic-gradient(at top, red, blue)",
+ "conic-gradient(at top left, red, blue)",
+ "conic-gradient(at left top, red, blue)",
+ "conic-gradient(at center center, red, blue)",
+ "conic-gradient(at 20% bottom, red, blue)",
+ "conic-gradient(at center 20%, red, blue)",
+ "conic-gradient(at left 35px, red, blue)",
+ "conic-gradient(at 10% 10em, red, blue)",
+ "conic-gradient(at 44px top, red, blue)",
+ "conic-gradient(at 0 0, red, blue)",
+ "conic-gradient(at 10px, red, blue)",
+
+ "conic-gradient(at calc(25%) top, red, blue)",
+ "conic-gradient(at left calc(25%), red, blue)",
+ "conic-gradient(at calc(25px) top, red, blue)",
+ "conic-gradient(at left calc(25px), red, blue)",
+ "conic-gradient(at calc(-25%) top, red, blue)",
+ "conic-gradient(at left calc(-25%), red, blue)",
+ "conic-gradient(at calc(-25px) top, red, blue)",
+ "conic-gradient(at left calc(-25px), red, blue)",
+ "conic-gradient(at calc(100px + -25%) top, red, blue)",
+ "conic-gradient(at left calc(100px + -25%), red, blue)",
+ "conic-gradient(at calc(100px + -25px) top, red, blue)",
+ "conic-gradient(at left calc(100px + -25px), red, blue)",
+
+ "conic-gradient(from 0 at 0 0, red, blue)",
+ "conic-gradient(from 40deg at 50%, red, blue)",
+ "conic-gradient(from 0.4turn at left 30%, red, blue)",
+ "conic-gradient(from 200grad at calc(100px + -25%) top, red, blue)",
+ "conic-gradient(from 5rad at 10px, red, blue)",
+
+ "repeating-conic-gradient(red, blue)",
+ "repeating-conic-gradient(red, yellow, blue)",
+ "repeating-conic-gradient(red 1deg, yellow 20%, blue 5rad, green)",
+ "repeating-conic-gradient(red, yellow, green, blue 50%)",
+ "repeating-conic-gradient(red -50%, yellow -25%, green, blue)",
+ "repeating-conic-gradient(red -99deg, yellow, green, blue 120%)",
+ "repeating-conic-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "repeating-conic-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "repeating-conic-gradient(from 0, red, blue)",
+ "repeating-conic-gradient(from 40deg, red, blue)",
+ "repeating-conic-gradient(from 0.4turn, red, blue)",
+ "repeating-conic-gradient(from 200grad, red, blue)",
+ "repeating-conic-gradient(from 5rad, red, blue)",
+
+ "repeating-conic-gradient(at top left, red, blue)",
+ "repeating-conic-gradient(at 0 0, red, blue)",
+ "repeating-conic-gradient(at 20% bottom, red, blue)",
+ "repeating-conic-gradient(at center 20%, red, blue)",
+ "repeating-conic-gradient(at left 35px, red, blue)",
+ "repeating-conic-gradient(at 10% 10em, red, blue)",
+ "repeating-conic-gradient(at 44px top, red, blue)",
+
+ "repeating-conic-gradient(from 0 at 0 0, red, blue)",
+ "repeating-conic-gradient(from 40deg at 50%, red, blue)",
+ "repeating-conic-gradient(from 0.4turn at left 30%, red, blue)",
+ "repeating-conic-gradient(from 200grad at calc(100px + -25%) top, red, blue)",
+ "repeating-conic-gradient(from 5rad at 10px, red, blue)",
+
+ // 2008 GRADIENTS: -webkit-gradient()
+ // ----------------------------------
+ // linear w/ no color stops (valid) and a variety of position values:
+ "-webkit-gradient(linear, 1 2, 3 4)",
+ "-webkit-gradient(linear,1 2,3 4)", // (no extra space)
+ "-webkit-gradient(linear , 1 2 , 3 4 )", // (lots of extra space)
+ "-webkit-gradient(linear, 1 10% , 0% 4)", // percentages
+ "-webkit-gradient(linear, +1.0 -2%, +5.3% -0)", // (+/- & decimals are valid)
+ "-webkit-gradient(linear, left top, right bottom)", // keywords
+ "-webkit-gradient(linear, right center, center top)",
+ "-webkit-gradient(linear, center center, center center)",
+ "-webkit-gradient(linear, center 5%, 30 top)", // keywords mixed w/ nums
+
+ // linear w/ just 1 color stop:
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, to(lime))",
+ // * testing the various allowable stop values (<number> & <percent>):
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-0, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-30, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(+9999, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-.1, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0%, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(100%, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(9999%, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-.5%, lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(+0%, lime))",
+ // * testing the various color values:
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, transparent))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, rgb(1,2,3)))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, #00ff00))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, #00f))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, hsla(240, 30%, 50%, 0.8)))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, rgba(255, 230, 10, 0.5)))",
+
+ // linear w/ multiple color stops:
+ // * using from()/to() -- note that out-of-order is OK:
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime), from(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, to(lime), to(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime), to(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, to(lime), from(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime), to(blue), from(purple))",
+ // * using color-stop():
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, lime), color-stop(30%, blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, lime), color-stop(30%, blue), color-stop(100%, purple))",
+ // * using color-stop() intermixed with from()/to() functions:
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime), color-stop(30%, blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(30%, blue), to(lime))",
+ // * overshooting endpoints (0 & 1.0)
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(-30%, lime), color-stop(.4, blue), color-stop(1.5, purple))",
+ // * repeating a stop position (valid)
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(30%, lime), color-stop(30%, blue))",
+ // * stops out of order (valid)
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(70%, lime), color-stop(20%, blue), color-stop(40%, purple))",
+
+ // radial w/ no color stops (valid) and a several different radius values:
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9)",
+ "-webkit-gradient(radial, 0 0, 10, 0 0, 5)",
+
+ // radial w/ color stops
+ // (mostly leaning on more-robust 'linear' tests above; just testing a few
+ // examples w/ radial as a sanity-check):
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9, from(lime))",
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9, to(blue))",
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9, color-stop(0.5, #00f), color-stop(0.8, rgba(100, 200, 0, 0.5)))",
+
+ // 2011 GRADIENTS: -webkit-linear-gradient(), -webkit-radial -gradient()
+ // ---------------------------------------------------------------------
+ // Basic linear-gradient syntax (valid when prefixed or unprefixed):
+ "-webkit-linear-gradient(red, green, blue)",
+
+ // Angled linear-gradients (valid when prefixed or unprefixed):
+ "-webkit-linear-gradient(135deg, red, blue)",
+ "-webkit-linear-gradient( 135deg , red , blue )",
+ "-webkit-linear-gradient(280deg, red 60%, blue)",
+
+ // Linear-gradient with unitless-0 <angle> (normally invalid for <angle>
+ // but accepted here for better webkit emulation):
+ "-webkit-linear-gradient(0, red, blue)",
+
+ // Linear-gradient with calc expression (bug 1363349)
+ "-webkit-gradient(linear, calc(5 + 5) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, calc(5 - 5) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, calc(5 * 5) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, calc(5 / 5) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, left calc(25% - 10%), right calc(75% + 10%), from(blue), to(lime))",
+ "-webkit-gradient(linear, calc(1) 2, 3 4)",
+
+ // Radial-gradient with calc expression (bug 1363349)
+ "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 - 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 * 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 / 2), 3 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(8), 3 4, 9)",
+
+ // Basic radial-gradient syntax (valid when prefixed or unprefixed):
+ "-webkit-radial-gradient(circle, white, black)",
+ "-webkit-radial-gradient(circle, white, black)",
+ "-webkit-radial-gradient(ellipse closest-side, white, black)",
+ "-webkit-radial-gradient(circle farthest-corner, white, black)",
+
+ // Contain/cover keywords (valid only for -moz/-webkit prefixed):
+ "-webkit-radial-gradient(cover, red, blue)",
+ "-webkit-radial-gradient(cover circle, red, blue)",
+ "-webkit-radial-gradient(contain, red, blue)",
+ "-webkit-radial-gradient(contain ellipse, red, blue)",
+
+ // Initial side/corner/point (valid only for -moz/-webkit prefixed):
+ "-webkit-linear-gradient(top, red, blue)",
+ "-webkit-linear-gradient(left, red, blue)",
+ "-webkit-linear-gradient(bottom, red, blue)",
+ "-webkit-linear-gradient(right top, red, blue)",
+ "-webkit-linear-gradient(top right, red, blue)",
+ "-webkit-radial-gradient(right, red, blue)",
+ "-webkit-radial-gradient(left bottom, red, blue)",
+ "-webkit-radial-gradient(bottom left, red, blue)",
+ "-webkit-radial-gradient(center, red, blue)",
+ "-webkit-radial-gradient(center right, red, blue)",
+ "-webkit-radial-gradient(center center, red, blue)",
+ "-webkit-radial-gradient(center top, red, blue)",
+ "-webkit-radial-gradient(left 50%, red, blue)",
+ "-webkit-radial-gradient(20px top, red, blue)",
+ "-webkit-radial-gradient(20em 30%, red, blue)",
+
+ // Point + keyword-sized shape (valid only for -moz/-webkit prefixed):
+ "-webkit-radial-gradient(center, circle closest-corner, red, blue)",
+ "-webkit-radial-gradient(10px 20px, cover circle, red, blue)",
+ "-webkit-radial-gradient(5em 50%, ellipse contain, red, blue)",
+
+ // Repeating examples:
+ "-webkit-repeating-linear-gradient(red 10%, blue 30%)",
+ "-webkit-repeating-linear-gradient(30deg, pink 20px, orange 70px)",
+ "-webkit-repeating-linear-gradient(left, red, blue)",
+ "-webkit-repeating-linear-gradient(left, red 10%, blue 30%)",
+ "-webkit-repeating-radial-gradient(circle, red, blue 10%, red 20%)",
+ "-webkit-repeating-radial-gradient(circle farthest-corner, gray 10px, yellow 20px)",
+ "-webkit-repeating-radial-gradient(top left, circle, red, blue 4%, red 8%)",
+];
+var invalidNonUrlImageValues = [
+ "-moz-element(#a:1)",
+ "-moz-element(a#a)",
+ "-moz-element(#a a)",
+ "-moz-element(#a+a)",
+ "-moz-element(#a())",
+ /* no quirks mode colors */
+ "linear-gradient(red, ff00ff)",
+ /* no quirks mode colors */
+ "radial-gradient(at 10% bottom, ffffff, black) scroll no-repeat",
+ /* no quirks mode lengths */
+ "linear-gradient(red -99, yellow, green, blue 120%)",
+ /* Unitless nonzero numbers are valid as an <angle> */
+ "linear-gradient(30, red, blue)",
+ /* There must be a comma between gradient-line (e.g. <angle>) and colors */
+ "linear-gradient(30deg red, blue)",
+ "linear-gradient(to top left red, blue)",
+ "linear-gradient(to right red, blue)",
+ /* Invalid color or calc() function */
+ "linear-gradient(red, rgb(0, rubbish, 0) 50%, red)",
+ "linear-gradient(red, red calc(50% + rubbish), red)",
+ "linear-gradient(to top calc(50% + rubbish), red, blue)",
+
+ "radial-gradient(circle 175px 20px, black, white)",
+ "radial-gradient(175px 20px circle, black, white)",
+ "radial-gradient(ellipse 175px, black, white)",
+ "radial-gradient(175px ellipse, black, white)",
+ "radial-gradient(50%, red, blue)",
+ "radial-gradient(circle 50%, red, blue)",
+ "radial-gradient(50% circle, red, blue)",
+
+ /* Invalid units */
+ "conic-gradient(red, blue 50px, yellow 30px)",
+ "repeating-conic-gradient(red 1deg, yellow 20%, blue 24em, green)",
+ "conic-gradient(from 0%, black, white)",
+ "conic-gradient(from 60%, black, white)",
+ "conic-gradient(from 40px, black, white)",
+ "conic-gradient(from 50, black, white)",
+ "conic-gradient(at 50deg, black, white)",
+ "conic-gradient(from 40deg at 50deg, black, white)",
+ "conic-gradient(from 40deg at 50deg 60deg, black, white)",
+ /* Invalid keywords (or ordering) */
+ "conic-gradient(at 40% from 50deg, black, white)",
+ "conic-gradient(to 50deg, black, white)",
+
+ /* Used to be valid only when prefixed */
+ "linear-gradient(top left, red, blue)",
+ "linear-gradient(0 0, red, blue)",
+ "linear-gradient(20% bottom, red, blue)",
+ "linear-gradient(center 20%, red, blue)",
+ "linear-gradient(left 35px, red, blue)",
+ "linear-gradient(10% 10em, red, blue)",
+ "linear-gradient(44px top, red, blue)",
+
+ "linear-gradient(top left 45deg, red, blue)",
+ "linear-gradient(20% bottom -300deg, red, blue)",
+ "linear-gradient(center 20% 1.95929rad, red, blue)",
+ "linear-gradient(left 35px 30grad, red, blue)",
+ "linear-gradient(left 35px 0.1turn, red, blue)",
+ "linear-gradient(10% 10em 99999deg, red, blue)",
+ "linear-gradient(44px top -33deg, red, blue)",
+
+ "linear-gradient(30grad left 35px, red, blue)",
+ "linear-gradient(10deg 20px, red, blue)",
+ "linear-gradient(1turn 20px, red, blue)",
+ "linear-gradient(.414rad bottom, red, blue)",
+
+ "linear-gradient(to top, 0%, blue)",
+ "linear-gradient(to top, red, 100%)",
+ "linear-gradient(to top, red, 45%, 56%, blue)",
+ "linear-gradient(to top, red,, blue)",
+ "linear-gradient(to top, red, green 35%, 15%, 54%, blue)",
+
+ "linear-gradient(unset, 10px 10px, from(blue))",
+ "linear-gradient(unset, 10px 10px, blue 0)",
+ "repeating-linear-gradient(unset, 10px 10px, blue 0)",
+
+ "radial-gradient(top left 45deg, red, blue)",
+ "radial-gradient(20% bottom -300deg, red, blue)",
+ "radial-gradient(center 20% 1.95929rad, red, blue)",
+ "radial-gradient(left 35px 30grad, red, blue)",
+ "radial-gradient(10% 10em 99999deg, red, blue)",
+ "radial-gradient(44px top -33deg, red, blue)",
+
+ "radial-gradient(-33deg, red, blue)",
+ "radial-gradient(30grad left 35px, red, blue)",
+ "radial-gradient(10deg 20px, red, blue)",
+ "radial-gradient(.414rad bottom, red, blue)",
+
+ "radial-gradient(cover, red, blue)",
+ "radial-gradient(ellipse contain, red, blue)",
+ "radial-gradient(cover circle, red, blue)",
+
+ "radial-gradient(top left, cover, red, blue)",
+ "radial-gradient(15% 20%, circle, red, blue)",
+ "radial-gradient(45px, ellipse closest-corner, red, blue)",
+ "radial-gradient(45px, farthest-side circle, red, blue)",
+
+ "radial-gradient(99deg, cover, red, blue)",
+ "radial-gradient(-1.2345rad, circle, red, blue)",
+ "radial-gradient(399grad, ellipse closest-corner, red, blue)",
+ "radial-gradient(399grad, farthest-side circle, red, blue)",
+
+ "radial-gradient(top left 99deg, cover, red, blue)",
+ "radial-gradient(15% 20% -1.2345rad, circle, red, blue)",
+ "radial-gradient(45px 399grad, ellipse closest-corner, red, blue)",
+ "radial-gradient(45px 399grad, farthest-side circle, red, blue)",
+ "radial-gradient(circle red, blue)",
+
+ /* don't allow more than two positions with multi-position syntax */
+ "linear-gradient(red 0% 50% 100%)",
+ "linear-gradient(red 0% 50% 75%, blue 75%)",
+ "linear-gradient(to bottom, red 0% 50% 100%)",
+ "linear-gradient(to bottom, red 0% 50% 75%, blue 75%)",
+ "radial-gradient(red 0% 50% 100%)",
+ "radial-gradient(red 0% 50% 75%, blue 75%)",
+ "radial-gradient(center, red 0% 50% 100%)",
+ "radial-gradient(center, red 0% 50% 75%, blue 75%)",
+ "conic-gradient(red 0% 50% 100%)",
+ "conic-gradient(red 0% 50% 75%, blue 75%)",
+ "conic-gradient(center, red 0% 50% 100%)",
+ "conic-gradient(center, red 0% 50% 75%, blue 75%)",
+
+ // missing color in color stop
+ "conic-gradient(red 50deg, blue 0.3turn, yellow 200grad, orange 60%, 5rad)",
+
+ "-moz-linear-gradient(unset, 10px 10px, from(blue))",
+ "-moz-linear-gradient(unset, 10px 10px, blue 0)",
+ "-moz-repeating-linear-gradient(unset, 10px 10px, blue 0)",
+
+ // 2008 GRADIENTS: -webkit-gradient()
+ // https://www.webkit.org/blog/175/introducing-css-gradients/
+ // ----------------------------------
+ // Mostly-empty expressions (missing most required pieces):
+ "-webkit-gradient()",
+ "-webkit-gradient( )",
+ "-webkit-gradient(,)",
+ "-webkit-gradient(bogus)",
+ "-webkit-gradient(linear)",
+ "-webkit-gradient(linear,)",
+ "-webkit-gradient(,linear)",
+ "-webkit-gradient(radial)",
+ "-webkit-gradient(radial,)",
+
+ // linear w/ partial/missing <point> expression(s)
+ "-webkit-gradient(linear, 1)", // Incomplete <point>
+ "-webkit-gradient(linear, left)", // Incomplete <point>
+ "-webkit-gradient(linear, center)", // Incomplete <point>
+ "-webkit-gradient(linear, top)", // Incomplete <point>
+ "-webkit-gradient(linear, 5%)", // Incomplete <point>
+ "-webkit-gradient(linear, 1 2)", // Missing 2nd <point>
+ "-webkit-gradient(linear, 1, 3)", // 2 incomplete <point>s
+ "-webkit-gradient(linear, 1, 3 4)", // Incomplete 1st <point>
+ "-webkit-gradient(linear, 1 2, 3)", // Incomplete 2nd <point>
+ "-webkit-gradient(linear, 1 2, 3, 4)", // Comma inside <point>
+ "-webkit-gradient(linear, 1, 2, 3 4)", // Comma inside <point>
+ "-webkit-gradient(linear, 1, 2, 3, 4)", // Comma inside <point>
+
+ // linear w/ invalid units in <point> expression
+ "-webkit-gradient(linear, 1px 2, 3 4)",
+ "-webkit-gradient(linear, 1 2, 3 4px)",
+ "-webkit-gradient(linear, 1px 2px, 3px 4px)",
+ "-webkit-gradient(linear, 1 2em, 3 4)",
+
+ // linear w/ <radius> (only valid for radial)
+ "-webkit-gradient(linear, 1 2, 8, 3 4, 9)",
+
+ // linear w/ out-of-order position keywords in <point> expression
+ // (horizontal keyword is supposed to come first, for "x" coord)
+ "-webkit-gradient(linear, 0 0, top right)",
+ "-webkit-gradient(linear, bottom center, 0 0)",
+ "-webkit-gradient(linear, top bottom, 0 0)",
+ "-webkit-gradient(linear, bottom top, 0 0)",
+ "-webkit-gradient(linear, bottom top, 0 0)",
+
+ // linear w/ trailing comma (which implies missing color-stops):
+ "-webkit-gradient(linear, 1 2, 3 4,)",
+
+ // linear w/ invalid color values:
+ "-webkit-gradient(linear, 1 2, 3 4, from(invalidcolorname))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(inherit))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(initial))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(currentColor))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(00ff00))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(##00ff00))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(#00fff))", // wrong num hex digits
+ "-webkit-gradient(linear, 1 2, 3 4, from(xyz(0,0,0)))", // bogus color func
+ // Mixing <number> and <percentage> is invalid.
+ "-webkit-gradient(linear, 1 2, 3 4, from(rgb(100, 100%, 30)))",
+
+ // linear w/ color stops that have comma issues
+ "-webkit-gradient(linear, 1 2, 3 4 from(lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime,))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime),)",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime) to(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(lime),, to(blue))",
+ "-webkit-gradient(linear, 1 2, 3 4, from(rbg(0, 0, 0,)))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0 lime))",
+ "-webkit-gradient(linear, 1 2, 3 4, color-stop(0,, lime))",
+
+ // radial w/ broken <point>/radius expression(s)
+ "-webkit-gradient(radial, 1)", // Incomplete <point>
+ "-webkit-gradient(radial, 1 2)", // Missing radius + 2nd <point>
+ "-webkit-gradient(radial, 1 2, 8)", // Missing 2nd <point>
+ "-webkit-gradient(radial, 1 2, 8, 3)", // Incomplete 2nd <point>
+ "-webkit-gradient(radial, 1 2, 8, 3 4)", // Missing 2nd radius
+ "-webkit-gradient(radial, 1 2, 3 4, 9)", // Missing 1st radius
+ "-webkit-gradient(radial, 1 2, -1.5, center center, +99999.9999)", // Negative radius
+
+ // radial w/ incorrect units on radius (invalid; expecting <number>)
+ "-webkit-gradient(radial, 1 2, 8%, 3 4, 9)",
+ "-webkit-gradient(radial, 1 2, 8px, 3 4, 9)",
+ "-webkit-gradient(radial, 1 2, 8em, 3 4, 9)",
+ "-webkit-gradient(radial, 1 2, top, 3 4, 9)",
+
+ // radial w/ trailing comma (which implies missing color-stops):
+ "-webkit-gradient(linear, 1 2, 8, 3 4, 9,)",
+
+ // radial w/ invalid color value (mostly leaning on 'linear' test above):
+ "-webkit-gradient(radial, 1 2, 8, 3 4, 9, from(invalidcolorname))",
+
+ // 2011 GRADIENTS: -webkit-linear-gradient(), -webkit-radial -gradient()
+ // ---------------------------------------------------------------------
+ // Syntax that's invalid for all types of gradients:
+ // * empty gradient expressions:
+ "-webkit-linear-gradient()",
+ "-webkit-radial-gradient()",
+ "-webkit-repeating-linear-gradient()",
+ "-webkit-repeating-radial-gradient()",
+
+ // * missing comma between <legacy-gradient-line> and color list:
+ "-webkit-linear-gradient(0 red, blue)",
+ "-webkit-linear-gradient(30deg red, blue)",
+ "-webkit-linear-gradient(top right red, blue)",
+ "-webkit-linear-gradient(bottom red, blue)",
+
+ // Linear-gradient with calc expression containing mixed units
+ // (bug 1363349)
+ "-webkit-gradient(linear, calc(5 + 5%) top, calc(10 + 10) top, from(blue), to(lime))",
+ "-webkit-gradient(linear, left calc(25 - 10%), right calc(75% + 10%), from(blue), to(lime))",
+
+ // Radial-gradient with calc expression containing mixed units, or a
+ // percentage in the radius (bug 1363349)
+ "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1% + 5%), from(blue), to(lime))",
+ "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5%), from(blue), to(lime))",
+ "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1% + 2%), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))",
+
+ // Linear syntax that's invalid for both -webkit & unprefixed, but valid
+ // for -moz:
+ // * initial <legacy-gradient-line> which includes a length:
+ "-webkit-linear-gradient(10px, red, blue)",
+ "-webkit-linear-gradient(10px top, red, blue)",
+ // * initial <legacy-gradient-line> which includes a side *and* an angle:
+ "-webkit-linear-gradient(bottom 30deg, red, blue)",
+ "-webkit-linear-gradient(30deg bottom, red, blue)",
+ "-webkit-linear-gradient(10px top 50deg, red, blue)",
+ "-webkit-linear-gradient(50deg 10px top, red, blue)",
+ // * initial <legacy-gradient-line> which includes explicit "center":
+ "-webkit-linear-gradient(center, red, blue)",
+ "-webkit-linear-gradient(left center, red, blue)",
+ "-webkit-linear-gradient(top center, red, blue)",
+ "-webkit-linear-gradient(center top, red, blue)",
+
+ // Linear syntax that's invalid for -webkit, but valid for -moz & unprefixed:
+ // * "to" syntax:
+ "-webkit-linear-gradient(to top, red, blue)",
+
+ // * <shape> followed by angle:
+ "-webkit-radial-gradient(circle 10deg, red, blue)",
+
+ // Radial syntax that's invalid for both -webkit & -moz, but valid for
+ // unprefixed:
+ // * "<shape> at <position>" syntax:
+ "-webkit-radial-gradient(circle at left bottom, red, blue)",
+ // * explicitly-sized shape:
+ "-webkit-radial-gradient(circle 10px, red, blue)",
+ "-webkit-radial-gradient(ellipse 40px 20px, red, blue)",
+
+ // Radial syntax that's invalid for both -webkit & unprefixed, but valid
+ // for -moz:
+ // * initial angle
+ "-webkit-radial-gradient(30deg, red, blue)",
+ // * initial angle/position combo
+ "-webkit-radial-gradient(top 30deg, red, blue)",
+ "-webkit-radial-gradient(left top 30deg, red, blue)",
+ "-webkit-radial-gradient(10px 20px 30deg, red, blue)",
+
+ // Conic gradients should not support prefixed syntax
+ "-webkit-gradient(conic, 1 2, 3 4, color-stop(0, lime))",
+ "-webkit-conic-gradient(red, blue)",
+ "-moz-conic-gradient(red, blue)",
+ "-webkit-repeating-conic-gradient(red, blue)",
+ "-moz-repeating-conic-gradient(red, blue)",
+
+ "image-set(url(foobar.png) 1x, none)",
+ "image-set(garbage)",
+ "image-set(image-set('foobar.png', 'bar.png' 2x) 1x, url(baz.png) 3x)", // Nested image-sets should fail to parse
+ "image-set(image-set(garbage))",
+ "image-set()",
+ "image-set(type('image/png') url(foobar.png) 1x)",
+ "image-set(url(foobar.png) type('image/png') 1x type('image/png'))",
+ "image-set(url(foobar.png) type('image/png') type('image/png'))",
+ "image-set(url(foobar.png) type(image/png))",
+ "image-set(url(foobar.png) epyt('image/png'))",
+
+ ...(IsCSSPropertyPrefEnabled("layout.css.cross-fade.enabled")
+ ? [
+ "cross-fade(red blue)",
+ "cross-fade()",
+ "cross-fade(50%, blue 50%)",
+ // Old syntax
+ "cross-fade(red, white, 50%)",
+ // see: <https://github.com/w3c/csswg-drafts/issues/5333>. This
+ // may become invalid depending on how discussion on that issue
+ // goes.
+ "cross-fade(red, 150%, blue)",
+ "cross-fade(red auto, blue 10%)",
+
+ // nested invalidity should propagate.
+ "cross-fade(url(http://placekitten.com/200/300), 55% linear-gradient(center, red, blue))",
+ "cross-fade(cross-fade(red, white, 50%), cross-fade(blue))",
+
+ "cross-fade(url(http://placekitten.com/200/300) url(http://placekitten.com/200/300))",
+ "cross-fade(#F0F8FF, rgb(0, 0, 0), rgba(0, 255, 0, 1), 25%)",
+ ]
+ : []),
+];
+var unbalancedGradientAndElementValues = ["-moz-element(#a()"];
+
+var basicShapeSVGBoxValues = [
+ "fill-box",
+ "stroke-box",
+ "view-box",
+
+ "polygon(evenodd, 20pt 20cm) fill-box",
+ "polygon(evenodd, 20ex 20pc) stroke-box",
+ "polygon(evenodd, 20rem 20in) view-box",
+];
+
+var basicShapeOtherValues = [
+ "polygon(20px 20px)",
+ "polygon(20px 20%)",
+ "polygon(20% 20%)",
+ "polygon(20rem 20em)",
+ "polygon(20cm 20mm)",
+ "polygon(20px 20px, 30px 30px)",
+ "polygon(20px 20px, 30% 30%, 30px 30px)",
+
+ "content-box",
+ "padding-box",
+ "border-box",
+
+ "polygon(0 0) content-box",
+ "border-box polygon(0 0)",
+ "padding-box polygon( 0 20px , 30px 20% ) ",
+
+ "circle()",
+ "circle(at center)",
+ "circle(at top 0px left 20px)",
+ "circle(at bottom right)",
+ "circle(20%)",
+ "circle(300px)",
+ "circle(calc(20px + 30px))",
+ "circle(farthest-side)",
+ "circle(closest-side)",
+ "circle(closest-side at center)",
+ "circle(farthest-side at top)",
+ "circle(20px at top right)",
+ "circle(40% at 50% 100%)",
+ "circle(calc(20% + 20%) at right bottom)",
+ "circle() padding-box",
+
+ "ellipse()",
+ "ellipse(at center)",
+ "ellipse(at top 0px left 20px)",
+ "ellipse(at bottom right)",
+ "ellipse(20% 20%)",
+ "ellipse(300px 50%)",
+ "ellipse(calc(20px + 30px) 10%)",
+ "ellipse(farthest-side closest-side)",
+ "ellipse(closest-side farthest-side)",
+ "ellipse(farthest-side farthest-side)",
+ "ellipse(closest-side closest-side)",
+ "ellipse(closest-side closest-side at center)",
+ "ellipse(20% farthest-side at top)",
+ "ellipse(20px 50% at top right)",
+ "ellipse(closest-side 40% at 50% 100%)",
+ "ellipse(calc(20% + 20%) calc(20px + 20cm) at right bottom)",
+
+ "inset(1px)",
+ "inset(20% -20px)",
+ "inset(20em 4rem calc(20% + 20px))",
+ "inset(20vh 20vw 20pt 3%)",
+ "inset(5px round 3px)",
+ "inset(1px 2px round 3px / 3px)",
+ "inset(1px 2px 3px round 3px 2em / 20%)",
+ "inset(1px 2px 3px 4px round 3px 2vw 20% / 20px 3em 2vh 20%)",
+];
+
+var basicShapeOtherValuesWithFillRule = [
+ "polygon(nonzero, 20px 20px, 30% 30%, 30px 30px)",
+ "polygon(evenodd, 20px 20px, 30% 30%, 30px 30px)",
+ "polygon(evenodd, 20% 20em) content-box",
+ "polygon(evenodd, 20vh 20em) padding-box",
+ "polygon(evenodd, 20vh calc(20% + 20em)) border-box",
+ "polygon(evenodd, 20vh 20vw) margin-box",
+];
+
+var basicShapeInvalidValues = [
+ "url(#test) url(#tes2)",
+ "polygon (0 0)",
+ "polygon(20px, 40px)",
+ "border-box content-box",
+ "polygon(0 0) polygon(0 0)",
+ "polygon(nonzero 0 0)",
+ "polygon(evenodd 20px 20px)",
+ "polygon(20px 20px, evenodd)",
+ "polygon(20px 20px, nonzero)",
+ "polygon(0 0) conten-box content-box",
+ "content-box polygon(0 0) conten-box",
+ "padding-box polygon(0 0) conten-box",
+ "polygon(0 0) polygon(0 0) content-box",
+ "polygon(0 0) content-box polygon(0 0)",
+ "polygon(0 0), content-box",
+ "polygon(0 0), polygon(0 0)",
+ "content-box polygon(0 0) polygon(0 0)",
+ "content-box polygon(0 0) none",
+ "none content-box polygon(0 0)",
+ "inherit content-box polygon(0 0)",
+ "initial polygon(0 0)",
+ "polygon(0 0) farthest-side",
+ "farthest-corner polygon(0 0)",
+ "polygon(0 0) farthest-corner",
+ "polygon(0 0) conten-box",
+ "polygon(0 0) polygon(0 0) farthest-corner",
+ "polygon(0 0) polygon(0 0) polygon(0 0)",
+ "border-box polygon(0, 0)",
+ "border-box padding-box",
+ "margin-box farthest-side",
+ "nonsense() border-box",
+ "border-box nonsense()",
+
+ "circle(at)",
+ "circle(at 20% 20% 30%)",
+ "circle(20px 2px at center)",
+ "circle(2at center)",
+ "circle(closest-corner)",
+ "circle(at center top closest-side)",
+ "circle(-20px)",
+ "circle(farthest-side closest-side)",
+ "circle(20% 20%)",
+ "circle(at farthest-side)",
+ "circle(calc(20px + rubbish))",
+ "circle(at top left 20px)",
+
+ "ellipse(at)",
+ "ellipse(at 20% 20% 30%)",
+ "ellipse(20px at center)",
+ "ellipse(-20px 20px)",
+ "ellipse(closest-corner farthest-corner)",
+ "ellipse(20px -20px)",
+ "ellipse(-20px -20px)",
+ "ellipse(farthest-side)",
+ "ellipse(20%)",
+ "ellipse(at farthest-side farthest-side)",
+ "ellipse(at top left calc(20px + rubbish))",
+ "ellipse(at top left 20px)",
+
+ "polygon(at)",
+ "polygon(at 20% 20% 30%)",
+ "polygon(20px at center)",
+ "polygon(2px 2at center)",
+ "polygon(closest-corner farthest-corner)",
+ "polygon(at center top closest-side closest-side)",
+ "polygon(40% at 50% 100%)",
+ "polygon(40% farthest-side 20px at 50% 100%)",
+
+ "inset()",
+ "inset(round)",
+ "inset(round 3px)",
+ "inset(1px round 1px 2px 3px 4px 5px)",
+ "inset(1px 2px 3px 4px 5px)",
+ "inset(1px, round 3px)",
+ "inset(1px, 2px)",
+ "inset(1px 2px, 3px)",
+ "inset(1px at 3px)",
+ "inset(1px round 1px // 2px)",
+ "inset(1px round)",
+ "inset(1px calc(2px + rubbish))",
+ "inset(1px round 2px calc(3px + rubbish))",
+];
+
+var basicShapeUnbalancedValues = [
+ "polygon(30% 30%",
+ "polygon(nonzero, 20% 20px",
+ "polygon(evenodd, 20px 20px",
+
+ "circle(",
+ "circle(40% at 50% 100%",
+ "ellipse(",
+ "ellipse(40% at 50% 100%",
+
+ "inset(1px",
+ "inset(1px 2px",
+ "inset(1px 2px 3px",
+ "inset(1px 2px 3px 4px",
+ "inset(1px 2px 3px 4px round 5px",
+ "inset(1px 2px 3px 4px round 5px / 6px",
+];
+
+var basicShapeXywhRectValues = [];
+if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-xywh.enabled")) {
+ basicShapeXywhRectValues.push(
+ "xywh(1px 2% 3px 4em)",
+ "xywh(1px 2% 3px 4em round 0px)",
+ "xywh(1px 2% 3px 4em round 0px 1%)",
+ "xywh(1px 2% 3px 4em round 0px 1% 2px)",
+ "xywh(1px 2% 3px 4em round 0px 1% 2px 3em)"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-rect.enabled")) {
+ basicShapeXywhRectValues.push(
+ "rect(auto auto auto auto)",
+ "rect(1px 2% auto 4em)",
+ "rect(1px 2% auto 4em round 0px)",
+ "rect(1px 2% auto 4em round 0px 1%)",
+ "rect(1px 2% auto 4em round 0px 1% 2px)",
+ "rect(1px 2% auto 4em round 0px 1% 2px 3em)"
+ );
+}
+
+if (/* mozGradientsEnabled */ true) {
+ // Maybe one day :(
+ // Extend gradient lists with valid/invalid moz-prefixed expressions:
+ validNonUrlImageValues.push(
+ "-moz-linear-gradient(red, blue)",
+ "-moz-linear-gradient(red, yellow, blue)",
+ "-moz-linear-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "-moz-linear-gradient(red, yellow, green, blue 50%)",
+ "-moz-linear-gradient(red -50%, yellow -25%, green, blue)",
+ "-moz-linear-gradient(red -99px, yellow, green, blue 120%)",
+ "-moz-linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "-moz-linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "-moz-linear-gradient(top, red, blue)",
+
+ "-moz-linear-gradient(to top, red, blue)",
+ "-moz-linear-gradient(to bottom, red, blue)",
+ "-moz-linear-gradient(to left, red, blue)",
+ "-moz-linear-gradient(to right, red, blue)",
+ "-moz-linear-gradient(to top left, red, blue)",
+ "-moz-linear-gradient(to top right, red, blue)",
+ "-moz-linear-gradient(to bottom left, red, blue)",
+ "-moz-linear-gradient(to bottom right, red, blue)",
+ "-moz-linear-gradient(to left top, red, blue)",
+ "-moz-linear-gradient(to left bottom, red, blue)",
+ "-moz-linear-gradient(to right top, red, blue)",
+ "-moz-linear-gradient(to right bottom, red, blue)",
+
+ "-moz-linear-gradient(top left, red, blue)",
+ "-moz-linear-gradient(left, red, blue)",
+ "-moz-linear-gradient(bottom, red, blue)",
+
+ "-moz-linear-gradient(0, red, blue)",
+
+ "-moz-linear-gradient(-33deg, red, blue)",
+
+ "-moz-linear-gradient(blue calc(0px) ,green calc(25%) ,red calc(40px) ,blue calc(60px) , yellow calc(100px))",
+ "-moz-linear-gradient(-33deg, blue calc(-25%) ,red 40px)",
+ "-moz-linear-gradient(10deg, blue calc(100px + -25%),red calc(40px))",
+ "-moz-linear-gradient(10deg, blue calc(-25px),red calc(100%))",
+ "-moz-linear-gradient(.414rad, blue calc(100px + -25px) ,green calc(100px + -25px) ,red calc(100px + -25%) ,blue calc(-25px) , yellow calc(-25px))",
+ "-moz-linear-gradient(1turn, blue calc(-25%) ,green calc(25px) ,red calc(25%),blue calc(0px),white 50px, yellow calc(-25px))",
+
+ "-moz-radial-gradient(red, blue)",
+ "-moz-radial-gradient(red, yellow, blue)",
+ "-moz-radial-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "-moz-radial-gradient(red, yellow, green, blue 50%)",
+ "-moz-radial-gradient(red -50%, yellow -25%, green, blue)",
+ "-moz-radial-gradient(red -99px, yellow, green, blue 120%)",
+ "-moz-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+
+ "-moz-radial-gradient(top left, red, blue)",
+ "-moz-radial-gradient(20% bottom, red, blue)",
+ "-moz-radial-gradient(center 20%, red, blue)",
+ "-moz-radial-gradient(left 35px, red, blue)",
+ "-moz-radial-gradient(10% 10em, red, blue)",
+ "-moz-radial-gradient(44px top, red, blue)",
+
+ "-moz-radial-gradient(0 0, red, blue)",
+ "-moz-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "-moz-radial-gradient(cover, red, blue)",
+ "-moz-radial-gradient(cover circle, red, blue)",
+ "-moz-radial-gradient(contain, red, blue)",
+ "-moz-radial-gradient(contain ellipse, red, blue)",
+ "-moz-radial-gradient(circle, red, blue)",
+ "-moz-radial-gradient(ellipse closest-corner, red, blue)",
+ "-moz-radial-gradient(farthest-side circle, red, blue)",
+
+ "-moz-radial-gradient(top left, cover, red, blue)",
+ "-moz-radial-gradient(15% 20%, circle, red, blue)",
+ "-moz-radial-gradient(45px, ellipse closest-corner, red, blue)",
+ "-moz-radial-gradient(45px, farthest-side circle, red, blue)",
+
+ "-moz-repeating-linear-gradient(red, blue)",
+ "-moz-repeating-linear-gradient(red, yellow, blue)",
+ "-moz-repeating-linear-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "-moz-repeating-linear-gradient(red, yellow, green, blue 50%)",
+ "-moz-repeating-linear-gradient(red -50%, yellow -25%, green, blue)",
+ "-moz-repeating-linear-gradient(red -99px, yellow, green, blue 120%)",
+ "-moz-repeating-linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "-moz-repeating-linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "-moz-repeating-linear-gradient(to top, red, blue)",
+ "-moz-repeating-linear-gradient(to bottom, red, blue)",
+ "-moz-repeating-linear-gradient(to left, red, blue)",
+ "-moz-repeating-linear-gradient(to right, red, blue)",
+ "-moz-repeating-linear-gradient(to top left, red, blue)",
+ "-moz-repeating-linear-gradient(to top right, red, blue)",
+ "-moz-repeating-linear-gradient(to bottom left, red, blue)",
+ "-moz-repeating-linear-gradient(to bottom right, red, blue)",
+ "-moz-repeating-linear-gradient(to left top, red, blue)",
+ "-moz-repeating-linear-gradient(to left bottom, red, blue)",
+ "-moz-repeating-linear-gradient(to right top, red, blue)",
+ "-moz-repeating-linear-gradient(to right bottom, red, blue)",
+
+ "-moz-repeating-linear-gradient(top left, red, blue)",
+
+ "-moz-repeating-radial-gradient(red, blue)",
+ "-moz-repeating-radial-gradient(red, yellow, blue)",
+ "-moz-repeating-radial-gradient(red 1px, yellow 20%, blue 24em, green)",
+ "-moz-repeating-radial-gradient(red, yellow, green, blue 50%)",
+ "-moz-repeating-radial-gradient(red -50%, yellow -25%, green, blue)",
+ "-moz-repeating-radial-gradient(red -99px, yellow, green, blue 120%)",
+ "-moz-repeating-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
+ "-moz-repeating-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
+
+ "-moz-repeating-radial-gradient(farthest-corner, red, blue)",
+ "-moz-repeating-radial-gradient(circle, red, blue)",
+ "-moz-repeating-radial-gradient(ellipse closest-corner, red, blue)",
+
+ "-moz-radial-gradient(calc(25%) top, red, blue)",
+ "-moz-radial-gradient(left calc(25%), red, blue)",
+ "-moz-radial-gradient(calc(25px) top, red, blue)",
+ "-moz-radial-gradient(left calc(25px), red, blue)",
+ "-moz-radial-gradient(calc(-25%) top, red, blue)",
+ "-moz-radial-gradient(left calc(-25%), red, blue)",
+ "-moz-radial-gradient(calc(-25px) top, red, blue)",
+ "-moz-radial-gradient(left calc(-25px), red, blue)",
+ "-moz-radial-gradient(calc(100px + -25%) top, red, blue)",
+ "-moz-radial-gradient(left calc(100px + -25%), red, blue)",
+ "-moz-radial-gradient(calc(100px + -25px) top, red, blue)",
+ "-moz-radial-gradient(left calc(100px + -25px), red, blue)"
+ );
+
+ invalidNonUrlImageValues.push(
+ // The entries in this block used to be valid with the older more-complex
+ // -moz prefixed gradient syntax, but we've since simplified the syntax for
+ // consistency with -webkit prefixed gradients, in a way that makes these
+ // invalid now.
+ "-moz-linear-gradient(center 0%, red, blue)",
+ "-moz-linear-gradient(50% top, red, blue)",
+ "-moz-linear-gradient(50% 0%, red, blue)",
+ "-moz-linear-gradient(0 0, red, blue)",
+ "-moz-linear-gradient(20% bottom, red, blue)",
+ "-moz-linear-gradient(center 20%, red, blue)",
+ "-moz-linear-gradient(left 35px, red, blue)",
+ "-moz-linear-gradient(10% 10em, red, blue)",
+ "-moz-linear-gradient(44px top, red, blue)",
+ "-moz-linear-gradient(0px, red, blue)",
+ "-moz-linear-gradient(top left 45deg, red, blue)",
+ "-moz-linear-gradient(20% bottom -300deg, red, blue)",
+ "-moz-linear-gradient(center 20% 1.95929rad, red, blue)",
+ "-moz-linear-gradient(left 35px 30grad, red, blue)",
+ "-moz-linear-gradient(left 35px 0.1turn, red, blue)",
+ "-moz-linear-gradient(10% 10em 99999deg, red, blue)",
+ "-moz-linear-gradient(44px top -33deg, red, blue)",
+ "-moz-linear-gradient(30grad left 35px, red, blue)",
+ "-moz-linear-gradient(10deg 20px, red, blue)",
+ "-moz-linear-gradient(1turn 20px, red, blue)",
+ "-moz-linear-gradient(.414rad bottom, red, blue)",
+ "-moz-radial-gradient(top left 45deg, red, blue)",
+ "-moz-radial-gradient(20% bottom -300deg, red, blue)",
+ "-moz-radial-gradient(center 20% 1.95929rad, red, blue)",
+ "-moz-radial-gradient(left 35px 30grad, red, blue)",
+ "-moz-radial-gradient(10% 10em 99999deg, red, blue)",
+ "-moz-radial-gradient(44px top -33deg, red, blue)",
+ "-moz-radial-gradient(-33deg, red, blue)",
+ "-moz-radial-gradient(30grad left 35px, red, blue)",
+ "-moz-radial-gradient(10deg 20px, red, blue)",
+ "-moz-radial-gradient(.414rad bottom, red, blue)",
+ "-moz-radial-gradient(99deg, cover, red, blue)",
+ "-moz-radial-gradient(-1.2345rad, circle, red, blue)",
+ "-moz-radial-gradient(399grad, ellipse closest-corner, red, blue)",
+ "-moz-radial-gradient(399grad, farthest-side circle, red, blue)",
+ "-moz-radial-gradient(top left 99deg, cover, red, blue)",
+ "-moz-radial-gradient(15% 20% -1.2345rad, circle, red, blue)",
+ "-moz-radial-gradient(45px 399grad, ellipse closest-corner, red, blue)",
+ "-moz-radial-gradient(45px 399grad, farthest-side circle, red, blue)",
+ "-moz-repeating-linear-gradient(0 0, red, blue)",
+ "-moz-repeating-linear-gradient(20% bottom, red, blue)",
+ "-moz-repeating-linear-gradient(center 20%, red, blue)",
+ "-moz-repeating-linear-gradient(left 35px, red, blue)",
+ "-moz-repeating-linear-gradient(10% 10em, red, blue)",
+ "-moz-repeating-linear-gradient(44px top, red, blue)",
+ "-moz-repeating-linear-gradient(top left 45deg, red, blue)",
+ "-moz-repeating-linear-gradient(20% bottom -300deg, red, blue)",
+ "-moz-repeating-linear-gradient(center 20% 1.95929rad, red, blue)",
+ "-moz-repeating-linear-gradient(left 35px 30grad, red, blue)",
+ "-moz-repeating-linear-gradient(10% 10em 99999deg, red, blue)",
+ "-moz-repeating-linear-gradient(44px top -33deg, red, blue)",
+ "-moz-repeating-linear-gradient(30grad left 35px, red, blue)",
+ "-moz-repeating-linear-gradient(10deg 20px, red, blue)",
+ "-moz-repeating-linear-gradient(.414rad bottom, red, blue)",
+
+ /* Negative radii */
+ "-moz-radial-gradient(40%, -100px -10%, red, blue)",
+
+ /* no quirks mode colors */
+ "-moz-radial-gradient(10% bottom, ffffff, black) scroll no-repeat",
+ /* no quirks mode lengths */
+ "-moz-linear-gradient(10 10px -45deg, red, blue) repeat",
+ "-moz-linear-gradient(10px 10 -45deg, red, blue) repeat",
+ /* Unitless 0 is invalid as an <angle> */
+ "-moz-linear-gradient(top left 0, red, blue)",
+ "-moz-linear-gradient(5px 5px 0, red, blue)",
+ /* There must be a comma between gradient-line (e.g. <angle>) and colors */
+ "-moz-linear-gradient(30deg red, blue)",
+ "-moz-linear-gradient(5px 5px 30deg red, blue)",
+ "-moz-linear-gradient(5px 5px red, blue)",
+ "-moz-linear-gradient(top left 30deg red, blue)",
+
+ /* Old syntax */
+ "-moz-linear-gradient(10px 10px, 20px, 30px 30px, 40px, from(blue), to(red))",
+ "-moz-radial-gradient(20px 20px, 10px 10px, from(green), to(#ff00ff))",
+ "-moz-radial-gradient(10px 10px, 20%, 40px 40px, 10px, from(green), to(#ff00ff))",
+ "-moz-linear-gradient(10px, 20px, 30px, 40px, color-stop(0.5, #00ccff))",
+ "-moz-linear-gradient(20px 20px, from(blue), to(red))",
+ "-moz-linear-gradient(40px 40px, 10px 10px, from(blue) to(red) color-stop(10%, fuchsia))",
+ "-moz-linear-gradient(20px 20px 30px, 10px 10px, from(red), to(#ff0000))",
+ "-moz-radial-gradient(left top, center, 20px 20px, 10px, from(blue), to(red))",
+ "-moz-linear-gradient(left left, top top, from(blue))",
+ "-moz-linear-gradient(inherit, 10px 10px, from(blue))",
+ /* New syntax */
+ "-moz-linear-gradient(10px 10px, 20px, 30px 30px, 40px, blue 0, red 100%)",
+ "-moz-radial-gradient(20px 20px, 10px 10px, from(green), to(#ff00ff))",
+ "-moz-radial-gradient(10px 10px, 20%, 40px 40px, 10px, from(green), to(#ff00ff))",
+ "-moz-linear-gradient(10px, 20px, 30px, 40px, #00ccff 50%)",
+ "-moz-linear-gradient(40px 40px, 10px 10px, blue 0 fuchsia 10% red 100%)",
+ "-moz-linear-gradient(20px 20px 30px, 10px 10px, red 0, #ff0000 100%)",
+ "-moz-radial-gradient(left top, center, 20px 20px, 10px, from(blue), to(red))",
+ "-moz-linear-gradient(left left, top top, blue 0)",
+ "-moz-linear-gradient(inherit, 10px 10px, blue 0)",
+ "-moz-linear-gradient(left left blue red)",
+ "-moz-linear-gradient(left left blue, red)",
+ "-moz-linear-gradient()",
+ "-moz-linear-gradient(cover, red, blue)",
+ "-moz-linear-gradient(auto, red, blue)",
+ "-moz-linear-gradient(22 top, red, blue)",
+ "-moz-linear-gradient(10% red blue)",
+ "-moz-linear-gradient(10%, red blue)",
+ "-moz-linear-gradient(10%,, red, blue)",
+ "-moz-linear-gradient(45px, center, red, blue)",
+ "-moz-linear-gradient(45px, center red, blue)",
+ "-moz-radial-gradient(contain, ellipse, red, blue)",
+ "-moz-radial-gradient(10deg contain, red, blue)",
+ "-moz-radial-gradient(10deg, contain,, red, blue)",
+ "-moz-radial-gradient(contain contain, red, blue)",
+ "-moz-radial-gradient(ellipse circle, red, blue)",
+ "-moz-radial-gradient(to top left, red, blue)",
+ "-moz-radial-gradient(center, 10%, red, blue)",
+ "-moz-radial-gradient(5rad, 20px, red, blue)",
+
+ "-moz-radial-gradient(at top left to cover, red, blue)",
+ "-moz-radial-gradient(at 15% 20% circle, red, blue)",
+
+ "-moz-radial-gradient(to cover, red, blue)",
+ "-moz-radial-gradient(to contain, red, blue)",
+ "-moz-radial-gradient(to closest-side circle, red, blue)",
+ "-moz-radial-gradient(to farthest-corner ellipse, red, blue)",
+
+ "-moz-radial-gradient(ellipse at 45px closest-corner, red, blue)",
+ "-moz-radial-gradient(circle at 45px farthest-side, red, blue)",
+ "-moz-radial-gradient(ellipse 45px, closest-side, red, blue)",
+ "-moz-radial-gradient(circle 45px, farthest-corner, red, blue)",
+ "-moz-radial-gradient(ellipse, ellipse closest-side, red, blue)",
+ "-moz-radial-gradient(circle, circle farthest-corner, red, blue)",
+
+ "-moz-radial-gradient(99deg to farthest-corner, red, blue)",
+ "-moz-radial-gradient(-1.2345rad circle, red, blue)",
+ "-moz-radial-gradient(ellipse 399grad to closest-corner, red, blue)",
+ "-moz-radial-gradient(circle 399grad to farthest-side, red, blue)",
+
+ "-moz-radial-gradient(at top left 99deg, to farthest-corner, red, blue)",
+ "-moz-radial-gradient(circle at 15% 20% -1.2345rad, red, blue)",
+ "-moz-radial-gradient(to top left at 30% 40%, red, blue)",
+ "-moz-radial-gradient(ellipse at 45px 399grad, to closest-corner, red, blue)",
+ "-moz-radial-gradient(at 45px 399grad to farthest-side circle, red, blue)",
+
+ "-moz-radial-gradient(to 50%, red, blue)",
+ "-moz-radial-gradient(circle to 50%, red, blue)",
+ "-moz-radial-gradient(circle to 43px 43px, red, blue)",
+ "-moz-radial-gradient(circle to 50% 50%, red, blue)",
+ "-moz-radial-gradient(circle to 43px 50%, red, blue)",
+ "-moz-radial-gradient(circle to 50% 43px, red, blue)",
+ "-moz-radial-gradient(ellipse to 43px, red, blue)",
+ "-moz-radial-gradient(ellipse to 50%, red, blue)",
+
+ "-moz-linear-gradient(to 0 0, red, blue)",
+ "-moz-linear-gradient(to 20% bottom, red, blue)",
+ "-moz-linear-gradient(to center 20%, red, blue)",
+ "-moz-linear-gradient(to left 35px, red, blue)",
+ "-moz-linear-gradient(to 10% 10em, red, blue)",
+ "-moz-linear-gradient(to 44px top, red, blue)",
+ "-moz-linear-gradient(to top left 45deg, red, blue)",
+ "-moz-linear-gradient(to 20% bottom -300deg, red, blue)",
+ "-moz-linear-gradient(to center 20% 1.95929rad, red, blue)",
+ "-moz-linear-gradient(to left 35px 30grad, red, blue)",
+ "-moz-linear-gradient(to 10% 10em 99999deg, red, blue)",
+ "-moz-linear-gradient(to 44px top -33deg, red, blue)",
+ "-moz-linear-gradient(to -33deg, red, blue)",
+ "-moz-linear-gradient(to 30grad left 35px, red, blue)",
+ "-moz-linear-gradient(to 10deg 20px, red, blue)",
+ "-moz-linear-gradient(to .414rad bottom, red, blue)",
+
+ "-moz-linear-gradient(to top top, red, blue)",
+ "-moz-linear-gradient(to bottom bottom, red, blue)",
+ "-moz-linear-gradient(to left left, red, blue)",
+ "-moz-linear-gradient(to right right, red, blue)",
+
+ "-moz-repeating-linear-gradient(10px 10px, 20px, 30px 30px, 40px, blue 0, red 100%)",
+ "-moz-repeating-radial-gradient(20px 20px, 10px 10px, from(green), to(#ff00ff))",
+ "-moz-repeating-radial-gradient(10px 10px, 20%, 40px 40px, 10px, from(green), to(#ff00ff))",
+ "-moz-repeating-linear-gradient(10px, 20px, 30px, 40px, #00ccff 50%)",
+ "-moz-repeating-linear-gradient(40px 40px, 10px 10px, blue 0 fuchsia 10% red 100%)",
+ "-moz-repeating-linear-gradient(20px 20px 30px, 10px 10px, red 0, #ff0000 100%)",
+ "-moz-repeating-radial-gradient(left top, center, 20px 20px, 10px, from(blue), to(red))",
+ "-moz-repeating-linear-gradient(left left, top top, blue 0)",
+ "-moz-repeating-linear-gradient(inherit, 10px 10px, blue 0)",
+ "-moz-repeating-linear-gradient(left left blue red)",
+ "-moz-repeating-linear-gradient()",
+
+ "-moz-repeating-linear-gradient(to 0 0, red, blue)",
+ "-moz-repeating-linear-gradient(to 20% bottom, red, blue)",
+ "-moz-repeating-linear-gradient(to center 20%, red, blue)",
+ "-moz-repeating-linear-gradient(to left 35px, red, blue)",
+ "-moz-repeating-linear-gradient(to 10% 10em, red, blue)",
+ "-moz-repeating-linear-gradient(to 44px top, red, blue)",
+ "-moz-repeating-linear-gradient(to top left 45deg, red, blue)",
+ "-moz-repeating-linear-gradient(to 20% bottom -300deg, red, blue)",
+ "-moz-repeating-linear-gradient(to center 20% 1.95929rad, red, blue)",
+ "-moz-repeating-linear-gradient(to left 35px 30grad, red, blue)",
+ "-moz-repeating-linear-gradient(to 10% 10em 99999deg, red, blue)",
+ "-moz-repeating-linear-gradient(to 44px top -33deg, red, blue)",
+ "-moz-repeating-linear-gradient(to -33deg, red, blue)",
+ "-moz-repeating-linear-gradient(to 30grad left 35px, red, blue)",
+ "-moz-repeating-linear-gradient(to 10deg 20px, red, blue)",
+ "-moz-repeating-linear-gradient(to .414rad bottom, red, blue)",
+
+ "-moz-repeating-linear-gradient(to top top, red, blue)",
+ "-moz-repeating-linear-gradient(to bottom bottom, red, blue)",
+ "-moz-repeating-linear-gradient(to left left, red, blue)",
+ "-moz-repeating-linear-gradient(to right right, red, blue)",
+
+ "-moz-repeating-radial-gradient(to top left at 30% 40%, red, blue)",
+ "-moz-repeating-radial-gradient(ellipse at 45px closest-corner, red, blue)",
+ "-moz-repeating-radial-gradient(circle at 45px farthest-side, red, blue)",
+
+ /* Valid only when unprefixed */
+ "-moz-radial-gradient(at top left, red, blue)",
+ "-moz-radial-gradient(at 20% bottom, red, blue)",
+ "-moz-radial-gradient(at center 20%, red, blue)",
+ "-moz-radial-gradient(at left 35px, red, blue)",
+ "-moz-radial-gradient(at 10% 10em, red, blue)",
+ "-moz-radial-gradient(at 44px top, red, blue)",
+ "-moz-radial-gradient(at 0 0, red, blue)",
+
+ "-moz-radial-gradient(circle 43px, red, blue)",
+ "-moz-radial-gradient(ellipse 43px 43px, red, blue)",
+ "-moz-radial-gradient(ellipse 50% 50%, red, blue)",
+ "-moz-radial-gradient(ellipse 43px 50%, red, blue)",
+ "-moz-radial-gradient(ellipse 50% 43px, red, blue)",
+
+ "-moz-radial-gradient(farthest-corner at top left, red, blue)",
+ "-moz-radial-gradient(ellipse closest-corner at 45px, red, blue)",
+ "-moz-radial-gradient(circle farthest-side at 45px, red, blue)",
+ "-moz-radial-gradient(closest-side ellipse at 50%, red, blue)",
+ "-moz-radial-gradient(farthest-corner circle at 4em, red, blue)",
+
+ "-moz-radial-gradient(30% 40% at top left, red, blue)",
+ "-moz-radial-gradient(50px 60px at 15% 20%, red, blue)",
+ "-moz-radial-gradient(7em 8em at 45px, red, blue)"
+ );
+}
+
+const pathValues = {
+ other_values: [
+ "path('M 10 10 20 20 H 90 V 90 Z')",
+ "path('M10 10 20,20H90V90Z')",
+ "path('M 10 10 C 20 20, 40 20, 50 10')",
+ "path('M 10 80 C 40 10, 65 10, 95 80 S 1.5e2 150, 180 80')",
+ "path('M 10 80 Q 95 10 180 80')",
+ "path('M 10 80 Q 52.5 10, 95 80 T 180 80')",
+ "path('M 80 80 A 45 45, 0, 0, 0, 1.25e2 1.25e2 L 125 80 Z')",
+ "path('M100-200h20z')",
+ "path('M10,10L20.6.5z')",
+ ],
+ invalid_values: [
+ "path()",
+ "path(a)",
+ "path('M 10 Z')",
+ "path('M 10-10 20')",
+ "path('M 10 10 C 20 20 40 20')",
+ ],
+};
+
+var gCSSProperties = {
+ animation: {
+ domProp: "animation",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ subproperties: [
+ "animation-name",
+ "animation-duration",
+ "animation-timing-function",
+ "animation-delay",
+ "animation-direction",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-play-state",
+ ],
+ initial_values: [
+ "none none 0s 0s ease normal running 1.0",
+ "none",
+ "0s",
+ "ease",
+ "normal",
+ "running",
+ "1.0",
+ ],
+ other_values: [
+ "none none 0s 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) normal running 1.0",
+ "bounce 1s linear 2s",
+ "bounce 1s 2s linear",
+ "bounce linear 1s 2s",
+ "linear bounce 1s 2s",
+ "linear 1s bounce 2s",
+ "linear 1s 2s bounce",
+ "1s bounce linear 2s",
+ "1s bounce 2s linear",
+ "1s 2s bounce linear",
+ "1s linear bounce 2s",
+ "1s linear 2s bounce",
+ "1s 2s linear bounce",
+ "bounce linear 1s",
+ "bounce 1s linear",
+ "linear bounce 1s",
+ "linear 1s bounce",
+ "1s bounce linear",
+ "1s linear bounce",
+ "1s 2s bounce",
+ "1s bounce 2s",
+ "bounce 1s 2s",
+ "1s 2s linear",
+ "1s linear 2s",
+ "linear 1s 2s",
+ "bounce 1s",
+ "1s bounce",
+ "linear 1s",
+ "1s linear",
+ "1s 2s",
+ "2s 1s",
+ "bounce",
+ "linear",
+ "1s",
+ "height",
+ "2s",
+ "ease-in-out",
+ "2s ease-in",
+ "opacity linear",
+ "ease-out 2s",
+ "2s color, 1s bounce, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)",
+ "1s \\32bounce linear 2s",
+ "1s -bounce linear 2s",
+ "1s -\\32bounce linear 2s",
+ "1s \\32 0bounce linear 2s",
+ "1s -\\32 0bounce linear 2s",
+ "1s \\2bounce linear 2s",
+ "1s -\\2bounce linear 2s",
+ "2s, 1s bounce",
+ "1s bounce, 2s",
+ "2s all, 1s bounce",
+ "1s bounce, 2s all",
+ "1s bounce, 2s none",
+ "2s none, 1s bounce",
+ "2s bounce, 1s all",
+ "2s all, 1s bounce",
+ ],
+ invalid_values: [
+ "2s inherit",
+ "inherit 2s",
+ "2s bounce, 1s inherit",
+ "2s inherit, 1s bounce",
+ "2s initial",
+ "2s all,, 1s bounce",
+ "2s all, , 1s bounce",
+ "bounce 1s cubic-bezier(0, rubbish) 2s",
+ "bounce 1s steps(rubbish) 2s",
+ "2s unset",
+ ],
+ },
+ "animation-delay": {
+ domProp: "animationDelay",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["0s", "0ms"],
+ other_values: [
+ "1s",
+ "250ms",
+ "-100ms",
+ "-1s",
+ "1s, 250ms, 2.3s",
+ "calc(1s + 2ms)",
+ ],
+ invalid_values: ["0", "0px"],
+ },
+ "animation-direction": {
+ domProp: "animationDirection",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["normal"],
+ other_values: [
+ "alternate",
+ "normal, alternate",
+ "alternate, normal",
+ "normal, normal",
+ "normal, normal, normal",
+ "reverse",
+ "alternate-reverse",
+ "normal, reverse, alternate-reverse, alternate",
+ ],
+ invalid_values: [
+ "normal normal",
+ "inherit, normal",
+ "reverse-alternate",
+ "normal, unset",
+ "unset, normal",
+ ],
+ },
+ "animation-duration": {
+ domProp: "animationDuration",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0s", "0ms"],
+ applies_to_marker: true,
+ other_values: ["1s", "250ms", "1s, 250ms, 2.3s", "calc(1s + 2ms)"],
+ invalid_values: ["0", "0px", "-1ms", "-2s"],
+ },
+ "animation-fill-mode": {
+ domProp: "animationFillMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["none"],
+ other_values: [
+ "forwards",
+ "backwards",
+ "both",
+ "none, none",
+ "forwards, backwards",
+ "forwards, none",
+ "none, both",
+ ],
+ invalid_values: ["all"],
+ },
+ "animation-iteration-count": {
+ domProp: "animationIterationCount",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["1"],
+ other_values: [
+ "infinite",
+ "0",
+ "0.5",
+ "7.75",
+ "-0.0",
+ "1, 2, 3",
+ "infinite, 2",
+ "1, infinite",
+ "calc(1 + 2.0)",
+ ],
+ // negatives forbidden per
+ // http://lists.w3.org/Archives/Public/www-style/2011Mar/0355.html
+ invalid_values: ["none", "-1", "-0.5", "-1, infinite", "infinite, -3"],
+ },
+ "animation-name": {
+ domProp: "animationName",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["none"],
+ other_values: [
+ "all",
+ "ball",
+ "mall",
+ "color",
+ "bounce, bubble, opacity",
+ "foobar",
+ "auto",
+ "\\32bounce",
+ "-bounce",
+ "-\\32bounce",
+ "\\32 0bounce",
+ "-\\32 0bounce",
+ "\\2bounce",
+ "-\\2bounce",
+ ],
+ invalid_values: [
+ "bounce, initial",
+ "initial, bounce",
+ "bounce, inherit",
+ "inherit, bounce",
+ "bounce, unset",
+ "unset, bounce",
+ ],
+ },
+ "animation-play-state": {
+ domProp: "animationPlayState",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["running"],
+ other_values: [
+ "paused",
+ "running, running",
+ "paused, running",
+ "paused, paused",
+ "running, paused",
+ "paused, running, running, running, paused, running",
+ ],
+ invalid_values: ["0"],
+ },
+ "animation-timing-function": {
+ domProp: "animationTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["ease"],
+ other_values: [
+ "cubic-bezier(0.25, 0.1, 0.25, 1.0)",
+ "linear",
+ "ease-in",
+ "ease-out",
+ "ease-in-out",
+ "linear, ease-in, cubic-bezier(0.1, 0.2, 0.8, 0.9)",
+ "cubic-bezier(0.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(0.25, 1.5, 0.75, -0.5)",
+ "step-start",
+ "step-end",
+ "steps(1)",
+ "steps(2, start)",
+ "steps(386)",
+ "steps(3, end)",
+ "steps(calc(2 + 1))",
+ "steps(1, jump-start)",
+ "steps(1, jump-end)",
+ "steps(2, jump-none)",
+ "steps(1, jump-both)",
+ ],
+ invalid_values: [
+ "none",
+ "auto",
+ "cubic-bezier(0.25, 0.1, 0.25)",
+ "cubic-bezier(0.25, 0.1, 0.25, 0.25, 1.0)",
+ "cubic-bezier(-0.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(1.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(0.5, 0.5, -0.5, 0.5)",
+ "cubic-bezier(0.5, 0.5, 1.5, 0.5)",
+ "steps(2, step-end)",
+ "steps(0)",
+ "steps(-2)",
+ "steps(0, step-end, 1)",
+ "steps(0, jump-start)",
+ "steps(0, jump-end)",
+ "steps(1, jump-none)",
+ "steps(0, jump-both)",
+ ],
+ },
+ appearance: {
+ domProp: "appearance",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["auto", "radio", "menulist"],
+ invalid_values: [],
+ },
+ "-moz-appearance": {
+ domProp: "MozAppearance",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "appearance",
+ subproperties: ["appearance"],
+ },
+ "-webkit-appearance": {
+ domProp: "webkitAppearance",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "appearance",
+ subproperties: ["appearance"],
+ },
+ "aspect-ratio": {
+ domProp: "aspectRatio",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "1",
+ "1.0",
+ "1 / 2",
+ "1/2",
+ "16.2 / 9.5",
+ "1/0",
+ "0/1",
+ "0 / 0",
+ "auto 1",
+ "0 auto",
+ ],
+ invalid_values: ["none", "1 test", "1 / auto", "auto / 1"],
+ },
+ "border-inline": {
+ domProp: "borderInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-inline-start-color",
+ "border-inline-start-style",
+ "border-inline-start-width",
+ "border-inline-end-color",
+ "border-inline-end-style",
+ "border-inline-end-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-inline-end": {
+ domProp: "borderInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-inline-end-color",
+ "border-inline-end-style",
+ "border-inline-end-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 green none"],
+ },
+ "border-inline-color": {
+ domProp: "borderInlineColor",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-inline-start-color", "border-inline-end-color"],
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5) blue", "blue transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-inline-end-color": {
+ domProp: "borderInlineEndColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-inline-style": {
+ domProp: "borderInlineStyle",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-inline-start-style", "border-inline-end-style"],
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed solid",
+ "solid dotted",
+ "double double",
+ "inset outset",
+ "inset double",
+ "none groove",
+ "ridge none",
+ ],
+ invalid_values: [],
+ },
+ "border-inline-end-style": {
+ domProp: "borderInlineEndStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-inline-width": {
+ domProp: "borderInlineWidth",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-inline-start-width", "border-inline-end-width"],
+ prerequisites: { "border-style": "solid" },
+ initial_values: ["medium", "3px", "medium medium"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(2px) thin",
+ "calc(-2px)",
+ "calc(-2px) thick",
+ "calc(0em)",
+ "medium calc(0em)",
+ "calc(0px)",
+ "1px calc(0px)",
+ "calc(5em)",
+ "1em calc(5em)",
+ ],
+ invalid_values: ["5%", "5", "5 thin", "thin 5%", "blue", "solid"],
+ },
+ "border-inline-end-width": {
+ domProp: "borderInlineEndWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { "border-inline-end-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ "border-image": {
+ domProp: "borderImage",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat",
+ ],
+ initial_values: ["none"],
+ other_values: [
+ "url('border.png') 27 27 27 27",
+ "url('border.png') 27",
+ "stretch url('border.png')",
+ "url('border.png') 27 fill",
+ "url('border.png') 27 27 27 27 repeat",
+ "repeat url('border.png') 27 27 27 27",
+ "url('border.png') repeat 27 27 27 27",
+ "url('border.png') fill 27 27 27 27 repeat",
+ "url('border.png') fill 27 27 27 27 repeat space",
+ "url('border.png') 27 27 27 27 / 1em",
+ "27 27 27 27 / 1em url('border.png') ",
+ "url('border.png') 27 27 27 27 / 10 10 10 / 10 10 repeat",
+ "repeat 27 27 27 27 / 10 10 10 / 10 10 url('border.png')",
+ "url('border.png') 27 27 27 27 / / 10 10 1em",
+ "fill 27 27 27 27 / / 10 10 1em url('border.png')",
+ "url('border.png') 27 27 27 27 / 1em 1em 1em 1em repeat",
+ "url('border.png') 27 27 27 27 / 1em 1em 1em 1em stretch round",
+ ],
+ invalid_values: [
+ "url('border.png') 27 27 27 27 27",
+ "url('border.png') 27 27 27 27 / 1em 1em 1em 1em 1em",
+ "url('border.png') 27 27 27 27 /",
+ "url('border.png') fill",
+ "url('border.png') fill repeat",
+ "fill repeat",
+ "url('border.png') fill / 1em",
+ "url('border.png') / repeat",
+ "url('border.png') 1 /",
+ "url('border.png') 1 / /",
+ "1 / url('border.png')",
+ "url('border.png') / 1",
+ "url('border.png') / / 1",
+ ],
+ },
+ "border-image-source": {
+ domProp: "borderImageSource",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["none"],
+ other_values: ["url('border.png')"].concat(validNonUrlImageValues),
+ invalid_values: ["url('border.png') url('border.png')"].concat(
+ invalidNonUrlImageValues
+ ),
+ unbalanced_values: [].concat(unbalancedGradientAndElementValues),
+ },
+ "border-image-slice": {
+ domProp: "borderImageSlice",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["100%", "100% 100% 100% 100%"],
+ other_values: [
+ "0%",
+ "10",
+ "10 100% 0 2",
+ "0 0 0 0",
+ "fill 10 10",
+ "10 10 fill",
+ ],
+ invalid_values: [
+ "-10%",
+ "-10",
+ "10 10 10 10 10",
+ "10 10 10 10 -10",
+ "10px",
+ "-10px",
+ "fill",
+ "fill fill 10px",
+ "10px fill fill",
+ ],
+ },
+ "border-image-width": {
+ domProp: "borderImageWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["1", "1 1 1 1"],
+ other_values: [
+ "0",
+ "0%",
+ "0px",
+ "auto auto auto auto",
+ "10 10% auto 15px",
+ "10px 10px 10px 10px",
+ "10",
+ "10 10",
+ "10 10 10",
+ "calc(10px)",
+ "calc(10px + 5%)",
+ ],
+ invalid_values: [
+ "-10",
+ "-10px",
+ "-10%",
+ "10 10 10 10 10",
+ "10 10 10 10 auto",
+ "auto auto auto auto auto",
+ "10px calc(nonsense)",
+ "1px red",
+ ],
+ unbalanced_values: ["10px calc("],
+ },
+ "border-image-outset": {
+ domProp: "borderImageOutset",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["0", "0 0 0 0"],
+ other_values: [
+ "10px",
+ "10",
+ "10 10",
+ "10 10 10",
+ "10 10 10 10",
+ "10px 10 10 10px",
+ ],
+ invalid_values: [
+ "-10",
+ "-10px",
+ "-10%",
+ "10%",
+ "10 10 10 10 10",
+ "10px calc(nonsense)",
+ "1px red",
+ ],
+ unbalanced_values: ["10px calc("],
+ },
+ "border-image-repeat": {
+ domProp: "borderImageRepeat",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["stretch", "stretch stretch"],
+ other_values: [
+ "round",
+ "repeat",
+ "stretch round",
+ "repeat round",
+ "stretch repeat",
+ "round round",
+ "repeat repeat",
+ "space",
+ "stretch space",
+ "repeat space",
+ "round space",
+ "space space",
+ ],
+ invalid_values: ["none", "stretch stretch stretch", "0", "10", "0%", "0px"],
+ },
+ "border-radius": {
+ domProp: "borderRadius",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ subproperties: [
+ "border-bottom-left-radius",
+ "border-bottom-right-radius",
+ "border-top-left-radius",
+ "border-top-right-radius",
+ ],
+ initial_values: [
+ "0",
+ "0px",
+ "0px 0 0 0px",
+ "calc(-2px)",
+ "calc(0px) calc(0pt)",
+ "calc(0px) calc(0pt) calc(0px) calc(0em)",
+ ],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em",
+ "3em 2px",
+ "2pt 3% 4em",
+ "2px 2px 2px 2px", // circular
+ "3% / 2%",
+ "1px / 4px",
+ "2em / 1em",
+ "3em 2px / 2px 3em",
+ "2pt 3% 4em / 4pt 1% 5em",
+ "2px 2px 2px 2px / 4px 4px 4px 4px",
+ "1pt / 2pt 3pt",
+ "4pt 5pt / 3pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "2px 2px calc(2px + 1%) 2px",
+ "1px 2px 2px 2px / 2px 2px calc(2px + 1%) 2px",
+ ],
+ invalid_values: [
+ "2px -2px",
+ "inherit 2px",
+ "inherit / 2px",
+ "2px inherit",
+ "2px / inherit",
+ "2px 2px 2px 2px 2px",
+ "1px / 2px 2px 2px 2px 2px",
+ "2",
+ "2 2",
+ "2px 2px 2px 2px / 2px 2px 2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "unset / 2px",
+ "2px unset",
+ "2px / unset",
+ ],
+ },
+ "border-bottom-left-radius": {
+ domProp: "borderBottomLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-bottom-right-radius": {
+ domProp: "borderBottomRightRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-top-left-radius": {
+ domProp: "borderTopLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-top-right-radius": {
+ domProp: "borderTopRightRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-start-start-radius": {
+ domProp: "borderStartStartRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-start-end-radius": {
+ domProp: "borderStartEndRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-end-start-radius": {
+ domProp: "borderEndStartRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-end-end-radius": {
+ domProp: "borderEndEndRadius",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { width: "200px", height: "100px", display: "inline-block" },
+ initial_values: ["0", "0px", "calc(-2px)"],
+ other_values: [
+ "0%",
+ "3%",
+ "1px",
+ "2em", // circular
+ "3% 2%",
+ "1px 4px",
+ "2em 2pt", // elliptical
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) 5px",
+ "5px calc(3*25px)",
+ "calc(20%) calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "4px -2px",
+ "inherit 2px",
+ "2px inherit",
+ "2",
+ "2px 2",
+ "2 2px",
+ "2px calc(0px + rubbish)",
+ "unset 2px",
+ "2px unset",
+ ],
+ },
+ "border-inline-start": {
+ domProp: "borderInlineStart",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-inline-start-color",
+ "border-inline-start-style",
+ "border-inline-start-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 green solid"],
+ },
+ "border-inline-start-color": {
+ domProp: "borderInlineStartColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-inline-start-style": {
+ domProp: "borderInlineStartStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-inline-start-width": {
+ domProp: "borderInlineStartWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { "border-inline-start-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ "-moz-box-align": {
+ domProp: "MozBoxAlign",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["stretch"],
+ other_values: ["start", "center", "baseline", "end"],
+ invalid_values: [],
+ },
+ "-moz-box-direction": {
+ domProp: "MozBoxDirection",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["reverse"],
+ invalid_values: [],
+ },
+ "-moz-box-flex": {
+ domProp: "MozBoxFlex",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0", "0.0", "-0.0"],
+ other_values: ["1", "100", "0.1"],
+ invalid_values: ["10px", "-1"],
+ },
+ "-moz-box-ordinal-group": {
+ domProp: "MozBoxOrdinalGroup",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["1"],
+ other_values: ["2", "100", "0"],
+ invalid_values: ["1.0", "-1", "-1000"],
+ },
+ "-moz-box-orient": {
+ domProp: "MozBoxOrient",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["horizontal", "inline-axis"],
+ other_values: ["vertical", "block-axis"],
+ invalid_values: [],
+ },
+ "-moz-box-pack": {
+ domProp: "MozBoxPack",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["start"],
+ other_values: ["center", "end", "justify"],
+ invalid_values: [],
+ },
+ "box-decoration-break": {
+ domProp: "boxDecorationBreak",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["slice"],
+ other_values: ["clone"],
+ invalid_values: ["auto", "none", "1px"],
+ },
+ "box-sizing": {
+ domProp: "boxSizing",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["content-box"],
+ other_values: ["border-box"],
+ invalid_values: [
+ "padding-box",
+ "margin-box",
+ "content",
+ "padding",
+ "border",
+ "margin",
+ ],
+ },
+ "-moz-box-sizing": {
+ domProp: "MozBoxSizing",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "box-sizing",
+ subproperties: ["box-sizing"],
+ },
+ "print-color-adjust": {
+ domProp: "printColorAdjust",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["economy"],
+ other_values: ["exact"],
+ invalid_values: [],
+ },
+ "color-adjust": {
+ domProp: "colorAdjust",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "print-color-adjust",
+ subproperties: ["print-color-adjust"],
+ },
+ "color-scheme": {
+ domProp: "colorScheme",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "light",
+ "dark",
+ "light dark",
+ "light dark purple",
+ "light light dark",
+ "only light",
+ "only light dark",
+ "only light dark purple",
+ "light only",
+ ],
+ invalid_values: ["only normal", "normal only", "only light only"],
+ },
+ columns: {
+ domProp: "columns",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["column-count", "column-width"],
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "3",
+ "20px",
+ "2 10px",
+ "10px 2",
+ "2 auto",
+ "auto 2",
+ "auto 50px",
+ "50px auto",
+ ],
+ invalid_values: [
+ "5%",
+ "-1px",
+ "-1",
+ "3 5",
+ "10px 4px",
+ "10 2px 5in",
+ "30px -1",
+ "auto 3 5px",
+ "5 auto 20px",
+ "auto auto auto",
+ "calc(50px + rubbish) 2",
+ ],
+ },
+ "column-count": {
+ domProp: "columnCount",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["1", "17"],
+ // negative and zero invalid per editor's draft
+ invalid_values: ["-1", "0", "3px"],
+ },
+ "column-fill": {
+ domProp: "columnFill",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["balance"],
+ other_values: ["auto"],
+ invalid_values: ["2px", "dotted", "5em"],
+ },
+ "column-rule": {
+ domProp: "columnRule",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { color: "green" },
+ subproperties: [
+ "column-rule-width",
+ "column-rule-style",
+ "column-rule-color",
+ ],
+ initial_values: [
+ "medium none currentColor",
+ "none",
+ "medium",
+ "currentColor",
+ ],
+ other_values: [
+ "2px blue solid",
+ "red dotted 1px",
+ "ridge 4px orange",
+ "5px solid",
+ ],
+ invalid_values: [
+ "2px 3px 4px red",
+ "dotted dashed",
+ "5px dashed green 3px",
+ "5 solid",
+ "5 green solid",
+ ],
+ },
+ "column-rule-width": {
+ domProp: "columnRuleWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { "column-rule-style": "solid" },
+ initial_values: ["medium", "3px", "calc(3px)", "calc(5em + 3px - 5em)"],
+ other_values: [
+ "thin",
+ "15px",
+ /* valid calc() values */
+ "calc(-2px)",
+ "calc(2px)",
+ "calc(3em)",
+ "calc(3em + 2px)",
+ "calc( 3em + 2px)",
+ "calc(3em + 2px )",
+ "calc( 3em + 2px )",
+ "calc(3*25px)",
+ "calc(3 *25px)",
+ "calc(3 * 25px)",
+ "calc(3* 25px)",
+ "calc(25px*3)",
+ "calc(25px *3)",
+ "calc(25px* 3)",
+ "calc(25px * 3)",
+ "calc(25px * 3 / 4)",
+ "calc((25px * 3) / 4)",
+ "calc(25px * (3 / 4))",
+ "calc(3 * 25px / 4)",
+ "calc((3 * 25px) / 4)",
+ "calc(3 * (25px / 4))",
+ "calc(3em + 25px * 3 / 4)",
+ "calc(3em + (25px * 3) / 4)",
+ "calc(3em + 25px * (3 / 4))",
+ "calc(25px * 3 / 4 + 3em)",
+ "calc((25px * 3) / 4 + 3em)",
+ "calc(25px * (3 / 4) + 3em)",
+ "calc(3em + (25px * 3 / 4))",
+ "calc(3em + ((25px * 3) / 4))",
+ "calc(3em + (25px * (3 / 4)))",
+ "calc((25px * 3 / 4) + 3em)",
+ "calc(((25px * 3) / 4) + 3em)",
+ "calc((25px * (3 / 4)) + 3em)",
+ "calc(3*25px + 1in)",
+ "calc(1in - 3em + 2px)",
+ "calc(1in - (3em + 2px))",
+ "calc((1in - 3em) + 2px)",
+ "calc(50px/2)",
+ "calc(50px/(2 - 1))",
+ "calc(-3px)",
+ /* numeric reduction cases */
+ "calc(5 * 3 * 2em)",
+ "calc(2em * 5 * 3)",
+ "calc((5 * 3) * 2em)",
+ "calc(2em * (5 * 3))",
+ "calc((5 + 3) * 2em)",
+ "calc(2em * (5 + 3))",
+ "calc(2em / (5 + 3))",
+ "calc(2em * (5*2 + 3))",
+ "calc(2em * ((5*2) + 3))",
+ "calc(2em * (5*(2 + 3)))",
+
+ "calc((5 + 7) * 3em)",
+ "calc((5em + 3em) - 2em)",
+ "calc((5em - 3em) + 2em)",
+ "calc(2em - (5em - 3em))",
+ "calc(2em + (5em - 3em))",
+ "calc(2em - (5em + 3em))",
+ "calc(2em + (5em + 3em))",
+ "calc(2em + 5em - 3em)",
+ "calc(2em - 5em - 3em)",
+ "calc(2em + 5em + 3em)",
+ "calc(2em - 5em + 3em)",
+
+ "calc(2em / 4 * 3)",
+ "calc(2em * 4 / 3)",
+ "calc(2em * 4 * 3)",
+ "calc(2em / 4 / 3)",
+ "calc(4 * 2em / 3)",
+ "calc(4 / 3 * 2em)",
+
+ "calc((2em / 4) * 3)",
+ "calc((2em * 4) / 3)",
+ "calc((2em * 4) * 3)",
+ "calc((2em / 4) / 3)",
+ "calc((4 * 2em) / 3)",
+ "calc((4 / 3) * 2em)",
+
+ "calc(2em / (4 * 3))",
+ "calc(2em * (4 / 3))",
+ "calc(2em * (4 * 3))",
+ "calc(2em / (4 / 3))",
+ "calc(4 * (2em / 3))",
+
+ "min(5px)",
+ "min(5px,2em)",
+
+ "max(5px)",
+ "max(5px,2em)",
+
+ "calc(min(5px))",
+ "calc(min(5px,2em))",
+
+ "calc(max(5px))",
+ "calc(max(5px,2em))",
+
+ // Valid cases with unitless zero (which is never
+ // a length).
+ "calc(0 * 2em)",
+ "calc(2em * 0)",
+ "calc(3em + 0 * 2em)",
+ "calc(3em + 2em * 0)",
+ "calc((0 + 2) * 2em)",
+ "calc((2 + 0) * 2em)",
+ // And test zero lengths while we're here.
+ "calc(2 * 0px)",
+ "calc(0 * 0px)",
+ "calc(2 * 0em)",
+ "calc(0 * 0em)",
+ "calc(0px * 0)",
+ "calc(0px * 2)",
+ ],
+ invalid_values: [
+ "20",
+ "-1px",
+ "red",
+ "50%",
+ /* invalid calc() values */
+ "calc(2em+ 2px)",
+ "calc(2em +2px)",
+ "calc(2em+2px)",
+ "calc(2em- 2px)",
+ "calc(2em -2px)",
+ "calc(2em-2px)",
+ "-moz-min()",
+ "calc(min())",
+ "-moz-max()",
+ "calc(max())",
+ "-moz-min(5px)",
+ "-moz-max(5px)",
+ "-moz-min(5px,2em)",
+ "-moz-max(5px,2em)",
+ "calc(5 + 5)",
+ "calc(5 * 5)",
+ "calc(5em * 5em)",
+ "calc(5em / 5em * 5em)",
+
+ "calc(4 * 3 / 2em)",
+ "calc((4 * 3) / 2em)",
+ "calc(4 * (3 / 2em))",
+ "calc(4 / (3 * 2em))",
+
+ // Tests for handling of unitless zero, which cannot
+ // be a length inside calc().
+ "calc(0)",
+ "calc(0 + 2em)",
+ "calc(2em + 0)",
+ "calc(0 * 2)",
+ "calc(2 * 0)",
+ "calc(1 * (2em + 0))",
+ "calc((2em + 0))",
+ "calc((2em + 0) * 1)",
+ "calc(1 * (0 + 2em))",
+ "calc((0 + 2em))",
+ "calc((0 + 2em) * 1)",
+ ],
+ },
+ "column-rule-style": {
+ domProp: "columnRuleStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "hidden",
+ "ridge",
+ "groove",
+ "inset",
+ "outset",
+ "double",
+ "dotted",
+ "dashed",
+ ],
+ invalid_values: ["20", "foo"],
+ },
+ "column-rule-color": {
+ domProp: "columnRuleColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "green" },
+ initial_values: ["currentColor"],
+ other_values: ["red", "blue", "#ffff00"],
+ invalid_values: ["ffff00"],
+ },
+ "column-span": {
+ domProp: "columnSpan",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["all"],
+ invalid_values: ["-1", "0", "auto", "2px"],
+ },
+ "column-width": {
+ domProp: "columnWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "15px",
+ "calc(15px)",
+ "calc(30px - 3em)",
+ "calc(-15px)",
+ "0px",
+ "calc(0px)",
+ ],
+ invalid_values: ["20", "-1px", "50%"],
+ },
+ d: {
+ domProp: "d",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["path('')", "path(' ')"].concat(pathValues.other_values),
+ invalid_values: pathValues.invalid_values,
+ },
+ "-moz-float-edge": {
+ domProp: "MozFloatEdge",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["content-box"],
+ other_values: ["margin-box"],
+ invalid_values: ["content", "padding", "border", "margin"],
+ },
+ "-moz-force-broken-image-icon": {
+ domProp: "MozForceBrokenImageIcon",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["1"],
+ invalid_values: [],
+ },
+ "margin-inline": {
+ domProp: "marginInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["margin-inline-start", "margin-inline-end"],
+ initial_values: ["0", "0px 0em"],
+ other_values: [
+ "1px",
+ "3em 1%",
+ "5%",
+ "calc(2px) 1%",
+ "calc(-2px) 1%",
+ "calc(50%) 1%",
+ "calc(3*25px) calc(2px)",
+ "calc(25px*3) 1em",
+ "calc(3*25px + 50%) calc(3*25px - 50%)",
+ ],
+ invalid_values: [
+ "5",
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "margin-inline-end": {
+ domProp: "marginInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* no subproperties */
+ /* auto may or may not be initial */
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "0em",
+ "0ex",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ ],
+ other_values: [
+ "1px",
+ "3em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "5",
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "margin-inline-start": {
+ domProp: "marginInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* no subproperties */
+ /* auto may or may not be initial */
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "0em",
+ "0ex",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ ],
+ other_values: [
+ "1px",
+ "3em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "5",
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ mask: {
+ domProp: "mask",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ /* FIXME: All mask-border-* should be added when we implement them. */
+ subproperties: [
+ "mask-clip",
+ "mask-image",
+ "mask-mode",
+ "mask-origin",
+ "mask-position-x",
+ "mask-position-y",
+ "mask-repeat",
+ "mask-size",
+ "mask-composite",
+ ],
+ initial_values: [
+ "match-source",
+ "none",
+ "repeat",
+ "add",
+ "0% 0%",
+ "top left",
+ "0% 0% / auto",
+ "top left / auto",
+ "left top / auto",
+ "0% 0% / auto auto",
+ "top left none",
+ "left top none",
+ "none left top",
+ "none top left",
+ "none 0% 0%",
+ "top left / auto none",
+ "left top / auto none",
+ "top left / auto auto none",
+ "match-source none repeat add top left",
+ "top left repeat none add",
+ "none repeat add top left / auto",
+ "top left / auto repeat none add match-source",
+ "none repeat add 0% 0% / auto auto match-source",
+ "border-box",
+ "border-box border-box",
+ ],
+ other_values: [
+ "none alpha repeat add left top",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "no-repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') alpha left top add",
+ "repeat-x",
+ "repeat-y",
+ "no-repeat",
+ "none repeat-y alpha add 0% 0%",
+ "subtract",
+ "0% top subtract alpha repeat none",
+ "top",
+ "left",
+ "50% 50%",
+ "center",
+ "top / 100px",
+ "left / contain",
+ "left / cover",
+ "10px / 10%",
+ "10em / calc(20px)",
+ "top left / 100px 100px",
+ "top left / 100px auto",
+ "top left / 100px 10%",
+ "top left / 100px calc(20px)",
+ "bottom right add none alpha repeat",
+ "50% alpha",
+ "alpha 50%",
+ "50%",
+ "url(#mymask)",
+ "radial-gradient(at 10% bottom, #ffffff, black) add no-repeat",
+ "repeating-radial-gradient(at 10% bottom, #ffffff, black) no-repeat",
+ "-moz-element(#test) alpha",
+ /* multiple mask-image */
+ "url(404.png), url(404.png)",
+ "repeat-x, subtract, none",
+ "0% top url(404.png), url(404.png) 50% top",
+ "subtract repeat-y top left url(404.png), repeat-x alpha",
+ "top left / contain, bottom right / cover",
+ /* test cases with clip+origin in the shorthand */
+ "url(404.png) alpha padding-box",
+ "url(404.png) border-box alpha",
+ "content-box url(404.png)",
+ "url(404.png) alpha padding-box padding-box",
+ "url(404.png) alpha padding-box border-box",
+ "content-box border-box url(404.png)",
+ "alpha padding-box url(404.png) border-box",
+ "alpha padding-box url(404.png) padding-box",
+ ],
+ invalid_values: [
+ /* mixes with keywords have to be in correct order */
+ "50% left",
+ "top 50%",
+ /* no quirks mode colors */
+ "radial-gradient(at 10% bottom, ffffff, black) add no-repeat",
+ /* no quirks mode lengths */
+ "linear-gradient(red -99, yellow, green, blue 120%)",
+ /* bug 258080: don't accept background-position separated */
+ "left url(404.png) top",
+ "top url(404.png) left",
+ "-moz-element(#a rubbish)",
+ "left top / match-source",
+ ],
+ },
+ "mask-clip": {
+ domProp: "maskClip",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["border-box"],
+ other_values: [
+ "content-box",
+ "fill-box",
+ "stroke-box",
+ "view-box",
+ "no-clip",
+ "padding-box",
+ "border-box, padding-box",
+ "padding-box, padding-box, padding-box",
+ "border-box, border-box",
+ ],
+ invalid_values: ["content-box content-box", "margin-box"],
+ },
+ "mask-composite": {
+ domProp: "maskComposite",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["add"],
+ other_values: [
+ "subtract",
+ "intersect",
+ "exclude",
+ "add, add",
+ "subtract, intersect",
+ "subtract, subtract, add",
+ ],
+ invalid_values: ["add subtract", "intersect exclude"],
+ },
+ "mask-image": {
+ domProp: "maskImage",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')",
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ "none, none",
+ "none, none, none, none, none",
+ "url(#mymask)",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none",
+ "none, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ ].concat(validNonUrlImageValues),
+ invalid_values: [].concat(invalidNonUrlImageValues),
+ unbalanced_values: [].concat(unbalancedGradientAndElementValues),
+ },
+ "mask-mode": {
+ domProp: "maskMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["match-source"],
+ other_values: [
+ "alpha",
+ "luminance",
+ "match-source, match-source",
+ "match-source, alpha",
+ "alpha, luminance, match-source",
+ ],
+ invalid_values: ["match-source match-source", "alpha match-source"],
+ },
+ "mask-origin": {
+ domProp: "maskOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["border-box"],
+ other_values: [
+ "padding-box",
+ "content-box",
+ "fill-box",
+ "stroke-box",
+ "view-box",
+ "border-box, padding-box",
+ "padding-box, padding-box, padding-box",
+ "border-box, border-box",
+ ],
+ invalid_values: ["padding-box padding-box", "no-clip", "margin-box"],
+ },
+ "mask-position": {
+ domProp: "maskPosition",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ initial_values: [
+ "top 0% left 0%",
+ "top left",
+ "left top",
+ "0% 0%",
+ "0% top",
+ "left 0%",
+ ],
+ other_values: [
+ "top",
+ "left",
+ "right",
+ "bottom",
+ "center",
+ "center bottom",
+ "bottom center",
+ "center right",
+ "right center",
+ "center top",
+ "top center",
+ "center left",
+ "left center",
+ "right bottom",
+ "bottom right",
+ "50%",
+ "top left, top left",
+ "top left, top right",
+ "top right, top left",
+ "left top, 0% 0%",
+ "10% 20%, 30%, 40%",
+ "top left, bottom right",
+ "right bottom, left top",
+ "0%",
+ "0px",
+ "30px",
+ "0%, 10%, 20%, 30%",
+ "top, top, top, top, top",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ "0px 0px",
+ "right 20px top 60px",
+ "right 20px bottom 60px",
+ "left 20px top 60px",
+ "left 20px bottom 60px",
+ "right -50px top -50px",
+ "left -50px bottom -50px",
+ "right 20px top -50px",
+ "right -20px top 50px",
+ "right 3em bottom 10px",
+ "bottom 3em right 10px",
+ "top 3em right 10px",
+ "left 15px",
+ "10px top",
+ "left 20%",
+ "right 20%",
+ ],
+ subproperties: ["mask-position-x", "mask-position-y"],
+ invalid_values: [
+ "center 10px center 4px",
+ "center 10px center",
+ "top 20%",
+ "bottom 20%",
+ "50% left",
+ "top 50%",
+ "50% bottom 10%",
+ "right 10% 50%",
+ "left right",
+ "top bottom",
+ "left 10% right",
+ "top 20px bottom 20px",
+ "left left",
+ "0px calc(0px + rubbish)",
+ "left top 15px",
+ "left 10px top",
+ ],
+ },
+ "mask-position-x": {
+ domProp: "maskPositionX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["left", "0%"],
+ other_values: [
+ "right",
+ "center",
+ "50%",
+ "center, center",
+ "center, right",
+ "right, center",
+ "center, 50%",
+ "10%, 20%, 40%",
+ "1px",
+ "30px",
+ "50%, 10%, 20%, 30%",
+ "center, center, center, center, center",
+ "calc(20px)",
+ "calc(20px + 1em)",
+ "calc(20px / 2)",
+ "calc(20px + 50%)",
+ "calc(50% - 10px)",
+ "calc(-20px)",
+ "calc(-50%)",
+ "calc(-20%)",
+ "right 20px",
+ "left 20px",
+ "right -50px",
+ "left -50px",
+ "right 20px",
+ "right 3em",
+ ],
+ invalid_values: [
+ "center 10px",
+ "right 10% 50%",
+ "left right",
+ "left left",
+ "bottom 20px",
+ "top 10%",
+ "bottom 3em",
+ "top",
+ "bottom",
+ "top, top",
+ "top, bottom",
+ "bottom, top",
+ "top, 0%",
+ "top, top, top, top, top",
+ "calc(0px + rubbish)",
+ "center 0%",
+ ],
+ },
+ "mask-position-y": {
+ domProp: "maskPositionY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["top", "0%"],
+ other_values: [
+ "bottom",
+ "center",
+ "50%",
+ "center, center",
+ "center, bottom",
+ "bottom, center",
+ "center, 0%",
+ "10%, 20%, 40%",
+ "1px",
+ "30px",
+ "50%, 10%, 20%, 30%",
+ "center, center, center, center, center",
+ "calc(20px)",
+ "calc(20px + 1em)",
+ "calc(20px / 2)",
+ "calc(20px + 50%)",
+ "calc(50% - 10px)",
+ "calc(-20px)",
+ "calc(-50%)",
+ "calc(-20%)",
+ "bottom 20px",
+ "top 20px",
+ "bottom -50px",
+ "top -50px",
+ "bottom 20px",
+ "bottom 3em",
+ ],
+ invalid_values: [
+ "center 10px",
+ "bottom 10% 50%",
+ "top bottom",
+ "top top",
+ "right 20px",
+ "left 10%",
+ "right 3em",
+ "left",
+ "right",
+ "left, left",
+ "left, right",
+ "right, left",
+ "left, 0%",
+ "left, left, left, left, left",
+ "calc(0px + rubbish)",
+ "center 0%",
+ ],
+ },
+ "mask-repeat": {
+ domProp: "maskRepeat",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["repeat", "repeat repeat"],
+ other_values: [
+ "repeat-x",
+ "repeat-y",
+ "no-repeat",
+ "repeat-x, repeat-x",
+ "repeat, no-repeat",
+ "repeat-y, no-repeat, repeat-y",
+ "repeat, repeat, repeat",
+ "repeat no-repeat",
+ "no-repeat repeat",
+ "no-repeat no-repeat",
+ "repeat no-repeat",
+ "no-repeat no-repeat, no-repeat no-repeat",
+ ],
+ invalid_values: [
+ "repeat repeat repeat",
+ "repeat-x repeat-y",
+ "repeat repeat-x",
+ "repeat repeat-y",
+ "repeat-x repeat",
+ "repeat-y repeat",
+ ],
+ },
+ "mask-size": {
+ domProp: "maskSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "contain",
+ "cover",
+ "100px auto",
+ "auto 100px",
+ "100% auto",
+ "auto 100%",
+ "25% 50px",
+ "3em 40%",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ ],
+ invalid_values: [
+ "contain contain",
+ "cover cover",
+ "cover auto",
+ "auto cover",
+ "contain cover",
+ "cover contain",
+ "-5px 3px",
+ "3px -5px",
+ "auto -5px",
+ "-5px auto",
+ "5 3",
+ "10px calc(10px + rubbish)",
+ ],
+ },
+ "mask-type": {
+ domProp: "maskType",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["luminance"],
+ other_values: ["alpha"],
+ invalid_values: [],
+ },
+ "padding-inline-end": {
+ domProp: "paddingInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ logical: true,
+ /* no subproperties */
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "0em",
+ "0ex",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "3em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: ["5"],
+ },
+ "padding-inline-start": {
+ domProp: "paddingInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ logical: true,
+ /* no subproperties */
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "0em",
+ "0ex",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "3em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: ["5"],
+ },
+ resize: {
+ domProp: "resize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: { display: "block", overflow: "auto" },
+ initial_values: ["none"],
+ other_values: ["both", "horizontal", "vertical", "inline", "block"],
+ invalid_values: [],
+ },
+ "tab-size": {
+ domProp: "tabSize",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["8"],
+ other_values: [
+ "0",
+ "2.5",
+ "3",
+ "99",
+ "12000",
+ "0px",
+ "1em",
+ "calc(1px + 1em)",
+ "calc(1px - 2px)",
+ "calc(1 + 1)",
+ "calc(-2.5)",
+ ],
+ invalid_values: [
+ "9%",
+ "calc(9% + 1px)",
+ "calc(1 + 1em)",
+ "-1",
+ "-808",
+ "auto",
+ ],
+ },
+ "-moz-text-size-adjust": {
+ domProp: "MozTextSizeAdjust",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["-5%", "0", "100", "0%", "50%", "100%", "220.3%"],
+ },
+ transform: {
+ domProp: "transform",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { width: "300px", height: "50px" },
+ initial_values: ["none"],
+ other_values: [
+ "translatex(1px)",
+ "translatex(4em)",
+ "translatex(-4px)",
+ "translatex(3px)",
+ "translatex(0px) translatex(1px) translatex(2px) translatex(3px) translatex(4px)",
+ "translatey(4em)",
+ "translate(3px)",
+ "translate(10px, -3px)",
+ "rotate(45deg)",
+ "rotate(45grad)",
+ "rotate(45rad)",
+ "rotate(0.25turn)",
+ "rotate(0)",
+ "scalex(10)",
+ "scalex(10%)",
+ "scalex(-10)",
+ "scalex(-10%)",
+ "scaley(10)",
+ "scaley(10%)",
+ "scaley(-10)",
+ "scaley(-10%)",
+ "scale(10)",
+ "scale(10%)",
+ "scale(10, 20)",
+ "scale(10%, 20%)",
+ "scale(-10)",
+ "scale(-10%)",
+ "scale(-10, 20)",
+ "scale(10%, -20%)",
+ "scale(10, 20%)",
+ "scale(-10, 20%)",
+ "skewx(30deg)",
+ "skewx(0)",
+ "skewy(0)",
+ "skewx(30grad)",
+ "skewx(30rad)",
+ "skewx(0.08turn)",
+ "skewy(30deg)",
+ "skewy(30grad)",
+ "skewy(30rad)",
+ "skewy(0.08turn)",
+ "rotate(45deg) scale(2, 1)",
+ "skewx(45deg) skewx(-50grad)",
+ "translate(0, 0) scale(1, 1) skewx(0) skewy(0) matrix(1, 0, 0, 1, 0, 0)",
+ "translatex(50%)",
+ "translatey(50%)",
+ "translate(50%)",
+ "translate(3%, 5px)",
+ "translate(5px, 3%)",
+ "matrix(1, 2, 3, 4, 5, 6)",
+ /* valid calc() values */
+ "translatex(calc(5px + 10%))",
+ "translatey(calc(0.25 * 5px + 10% / 3))",
+ "translate(calc(5px - 10% * 3))",
+ "translate(calc(5px - 3 * 10%), 50px)",
+ "translate(-50px, calc(5px - 10% * 3))",
+ "translate(10px, calc(min(5px,10%)))",
+ "translate(calc(max(5px,10%)), 10%)",
+ "translate(max(5px,10%), 10%)",
+ "translatez(1px)",
+ "translatez(4em)",
+ "translatez(-4px)",
+ "translatez(0px)",
+ "translatez(2px) translatez(5px)",
+ "translate3d(3px, 4px, 5px)",
+ "translate3d(2em, 3px, 1em)",
+ "translatex(2px) translate3d(4px, 5px, 6px) translatey(1px)",
+ "scale3d(4, 4, 4)",
+ "scale3d(4%, 4%, 4%)",
+ "scale3d(-2, 3, -7)",
+ "scale3d(-2%, 3%, -7%)",
+ "scalez(4)",
+ "scalez(4%)",
+ "scalez(-6)",
+ "scalez(-6%)",
+ "rotate3d(2, 3, 4, 45deg)",
+ "rotate3d(-3, 7, 0, 12rad)",
+ "rotatex(15deg)",
+ "rotatey(-12grad)",
+ "rotatez(72rad)",
+ "rotatex(0.125turn)",
+ "rotate3d(0, 0, 0, 0rad)",
+ "perspective(0px)",
+ "perspective(1000px)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)",
+ ],
+ invalid_values: [
+ "1px",
+ "#0000ff",
+ "red",
+ "auto",
+ "translatex(1)",
+ "translatey(1)",
+ "translate(2)",
+ "translate(-3, -4)",
+ "translatex(1px 1px)",
+ "translatex(translatex(1px))",
+ "translatex(#0000ff)",
+ "translatex(red)",
+ "translatey()",
+ "matrix(1px, 2px, 3px, 4px, 5px, 6px)",
+ "skewx(red)",
+ "matrix(1%, 0, 0, 0, 0px, 0px)",
+ "matrix(0, 1%, 2, 3, 4px,5px)",
+ "matrix(0, 1, 2%, 3, 4px, 5px)",
+ "matrix(0, 1, 2, 3%, 4%, 5%)",
+ "matrix(1, 2, 3, 4, 5px, 6%)",
+ "matrix(1, 2, 3, 4, 5%, 6px)",
+ "matrix(1, 2, 3, 4, 5%, 6%)",
+ "matrix(1, 2, 3, 4, 5px, 6em)",
+ /* invalid calc() values */
+ "translatey(-moz-min(5px,10%))",
+ "translatex(-moz-max(5px,10%))",
+ "matrix(1, 0, 0, 1, max(5px * 3), calc(10% - 3px))",
+ "perspective(-10px)",
+ "matrix3d(dinosaur)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15%, 16)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16px)",
+ "rotatey(words)",
+ "rotatex(7)",
+ "translate3d(3px, 4px, 1px, 7px)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13px, 14em, 15px, 16)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20%, 10%, 15, 16)",
+ ],
+ },
+ "transform-box": {
+ domProp: "transformBox",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["view-box"],
+ other_values: ["fill-box", "border-box"],
+ invalid_values: ["padding-box", "margin-box"],
+ },
+ "transform-origin": {
+ domProp: "transformOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* no subproperties */
+ prerequisites: { width: "10px", height: "10px", display: "block" },
+ initial_values: ["50% 50%", "center", "center center"],
+ other_values: [
+ "25% 25%",
+ "6px 5px",
+ "20% 3em",
+ "0 0",
+ "0in 1in",
+ "top",
+ "bottom",
+ "top left",
+ "top right",
+ "top center",
+ "center left",
+ "center right",
+ "bottom left",
+ "bottom right",
+ "bottom center",
+ "20% center",
+ "6px center",
+ "13in bottom",
+ "left 50px",
+ "right 13%",
+ "center 40px",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ "6px 5px 5px",
+ "top center 10px",
+ ],
+ invalid_values: [
+ "red",
+ "auto",
+ "none",
+ "0.5 0.5",
+ "40px #0000ff",
+ "border",
+ "center red",
+ "right diagonal",
+ "#00ffff bottom",
+ "0px calc(0px + rubbish)",
+ "0px 0px calc(0px + rubbish)",
+ ],
+ },
+ "perspective-origin": {
+ domProp: "perspectiveOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* no subproperties */
+ prerequisites: { width: "10px", height: "10px", display: "block" },
+ initial_values: ["50% 50%", "center", "center center"],
+ other_values: [
+ "25% 25%",
+ "6px 5px",
+ "20% 3em",
+ "0 0",
+ "0in 1in",
+ "top",
+ "bottom",
+ "top left",
+ "top right",
+ "top center",
+ "center left",
+ "center right",
+ "bottom left",
+ "bottom right",
+ "bottom center",
+ "20% center",
+ "6px center",
+ "13in bottom",
+ "left 50px",
+ "right 13%",
+ "center 40px",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ ],
+ invalid_values: [
+ "red",
+ "auto",
+ "none",
+ "0.5 0.5",
+ "40px #0000ff",
+ "border",
+ "center red",
+ "right diagonal",
+ "#00ffff bottom",
+ ],
+ },
+ perspective: {
+ domProp: "perspective",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1000px", "500.2px", "0", "0px"],
+ invalid_values: ["pants", "200", "-100px", "-27.2em"],
+ },
+ "backface-visibility": {
+ domProp: "backfaceVisibility",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["visible"],
+ other_values: ["hidden"],
+ invalid_values: ["collapse"],
+ },
+ "transform-style": {
+ domProp: "transformStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["flat"],
+ other_values: ["preserve-3d"],
+ invalid_values: [],
+ },
+ "-moz-user-input": {
+ domProp: "MozUserInput",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: [],
+ },
+ "-moz-user-modify": {
+ domProp: "MozUserModify",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["read-only"],
+ other_values: ["read-write", "write-only"],
+ invalid_values: [],
+ },
+ "-moz-user-select": {
+ domProp: "MozUserSelect",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "user-select",
+ subproperties: ["user-select"],
+ },
+ "user-select": {
+ domProp: "userSelect",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none", "text", "all", "-moz-none"],
+ invalid_values: [],
+ },
+ background: {
+ domProp: "background",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "background-attachment",
+ "background-color",
+ "background-image",
+ "background-position-x",
+ "background-position-y",
+ "background-repeat",
+ "background-clip",
+ "background-origin",
+ "background-size",
+ ],
+ initial_values: [
+ "transparent",
+ "none",
+ "repeat",
+ "scroll",
+ "0% 0%",
+ "top left",
+ "left top",
+ "0% 0% / auto",
+ "top left / auto",
+ "left top / auto",
+ "0% 0% / auto auto",
+ "transparent none",
+ "top left none",
+ "left top none",
+ "none left top",
+ "none top left",
+ "none 0% 0%",
+ "left top / auto none",
+ "left top / auto auto none",
+ "transparent none repeat scroll top left",
+ "left top repeat none scroll transparent",
+ "transparent none repeat scroll top left / auto",
+ "left top / auto repeat none scroll transparent",
+ "none repeat scroll 0% 0% / auto auto transparent",
+ "padding-box border-box",
+ ],
+ other_values: [
+ /* without multiple backgrounds */
+ "green",
+ "none green repeat scroll left top",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') transparent left top scroll",
+ "repeat-x",
+ "repeat-y",
+ "no-repeat",
+ "none repeat-y transparent scroll 0% 0%",
+ "fixed",
+ "0% top transparent fixed repeat none",
+ "top",
+ "left",
+ "50% 50%",
+ "center",
+ "top / 100px",
+ "left / contain",
+ "left / cover",
+ "10px / 10%",
+ "10em / calc(20px)",
+ "top left / 100px 100px",
+ "top left / 100px auto",
+ "top left / 100px 10%",
+ "top left / 100px calc(20px)",
+ "bottom right 8px scroll none transparent repeat",
+ "50% transparent",
+ "transparent 50%",
+ "50%",
+ "radial-gradient(at 10% bottom, #ffffff, black) scroll no-repeat",
+ "repeating-radial-gradient(at 10% bottom, #ffffff, black) scroll no-repeat",
+ "-moz-element(#test) lime",
+ /* multiple backgrounds */
+ "url(404.png), url(404.png)",
+ "url(404.png), url(404.png) transparent",
+ "url(404.png), url(404.png) red",
+ "repeat-x, fixed, none",
+ "0% top url(404.png), url(404.png) 0% top",
+ "fixed repeat-y top left url(404.png), repeat-x green",
+ "top left / contain, bottom right / cover",
+ /* test cases with clip+origin in the shorthand */
+ "url(404.png) green padding-box",
+ "url(404.png) border-box transparent",
+ "content-box url(404.png) blue",
+ "url(404.png) green padding-box padding-box",
+ "url(404.png) green padding-box border-box",
+ "content-box border-box url(404.png) blue",
+ "url(404.png) green padding-box text",
+ "content-box text url(404.png) blue",
+ /* clip and origin separated in the shorthand */
+ "url(404.png) padding-box green border-box",
+ "url(404.png) padding-box green padding-box",
+ "transparent padding-box url(404.png) border-box",
+ "transparent padding-box url(404.png) padding-box",
+ /* text */
+ "text",
+ "text border-box",
+ ],
+ invalid_values: [
+ /* mixes with keywords have to be in correct order */
+ "50% left",
+ "top 50%",
+ /* no quirks mode colors */
+ "radial-gradient(at 10% bottom, ffffff, black) scroll no-repeat",
+ /* no quirks mode lengths */
+ "linear-gradient(red -99, yellow, green, blue 120%)",
+ /* bug 258080: don't accept background-position separated */
+ "left url(404.png) top",
+ "top url(404.png) left",
+ /* not allowed to have color in non-bottom layer */
+ "url(404.png) transparent, url(404.png)",
+ "url(404.png) red, url(404.png)",
+ "url(404.png) transparent, url(404.png) transparent",
+ "url(404.png) transparent red, url(404.png) transparent red",
+ "url(404.png) red, url(404.png) red",
+ "url(404.png) rgba(0, 0, 0, 0), url(404.png)",
+ "url(404.png) rgb(255, 0, 0), url(404.png)",
+ "url(404.png) rgba(0, 0, 0, 0), url(404.png) rgba(0, 0, 0, 0)",
+ "url(404.png) rgba(0, 0, 0, 0) rgb(255, 0, 0), url(404.png) rgba(0, 0, 0, 0) rgb(255, 0, 0)",
+ "url(404.png) rgb(255, 0, 0), url(404.png) rgb(255, 0, 0)",
+ /* error inside functions */
+ "-moz-element(#a rubbish) black",
+ "content-box text text",
+ "padding-box text url(404.png) text",
+ ],
+ },
+ "background-attachment": {
+ domProp: "backgroundAttachment",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["scroll"],
+ other_values: [
+ "fixed",
+ "local",
+ "scroll,scroll",
+ "fixed, scroll",
+ "scroll, fixed, local, scroll",
+ "fixed, fixed",
+ ],
+ invalid_values: [],
+ },
+ "background-blend-mode": {
+ domProp: "backgroundBlendMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "multiply",
+ "screen",
+ "overlay",
+ "darken",
+ "lighten",
+ "color-dodge",
+ "color-burn",
+ "hard-light",
+ "soft-light",
+ "difference",
+ "exclusion",
+ "hue",
+ "saturation",
+ "color",
+ "luminosity",
+ ],
+ invalid_values: ["none", "10px", "multiply multiply", "plus-lighter"],
+ },
+ "background-clip": {
+ /*
+ * When we rename this to 'background-clip', we also
+ * need to rename the values to match the spec.
+ */
+ domProp: "backgroundClip",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["border-box"],
+ other_values: [
+ "content-box",
+ "padding-box",
+ "border-box, padding-box",
+ "padding-box, padding-box, padding-box",
+ "border-box, border-box",
+ "text",
+ "content-box, text",
+ "text, border-box",
+ "text, text",
+ ],
+ invalid_values: [
+ "margin-box",
+ "border-box border-box",
+ "fill-box",
+ "stroke-box",
+ "view-box",
+ "no-clip",
+ ],
+ },
+ "background-color": {
+ domProp: "backgroundColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["transparent", "rgba(0, 0, 0, 0)"],
+ other_values: [
+ "green",
+ "rgb(255, 0, 128)",
+ "#fc2",
+ "#96ed2a",
+ "black",
+ "rgba(255,255,0,3)",
+ "hsl(240, 50%, 50%)",
+ "rgb(50%, 50%, 50%)",
+ "-moz-default-background-color",
+ "rgb(100, 100.0, 100)",
+ "rgba(255, 127, 15, 0)",
+ "hsla(240, 97%, 50%, 0.0)",
+ "rgba(255,255,255,-3.7)",
+ ],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "rgb(100, 100%, 100)",
+ ],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "background-image": {
+ domProp: "backgroundImage",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["none"],
+ other_values: [
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')",
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ "none, none",
+ "none, none, none, none, none",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none",
+ "none, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none",
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ ].concat(validNonUrlImageValues),
+ invalid_values: [].concat(invalidNonUrlImageValues),
+ unbalanced_values: [].concat(unbalancedGradientAndElementValues),
+ },
+ "background-origin": {
+ domProp: "backgroundOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["padding-box"],
+ other_values: [
+ "border-box",
+ "content-box",
+ "border-box, padding-box",
+ "padding-box, padding-box, padding-box",
+ "border-box, border-box",
+ ],
+ invalid_values: [
+ "margin-box",
+ "padding-box padding-box",
+ "fill-box",
+ "stroke-box",
+ "view-box",
+ "no-clip",
+ ],
+ },
+ "background-position": {
+ domProp: "backgroundPosition",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [
+ "top 0% left 0%",
+ "top 0% left",
+ "top left",
+ "left top",
+ "0% 0%",
+ "0% top",
+ "left 0%",
+ ],
+ other_values: [
+ "top",
+ "left",
+ "right",
+ "bottom",
+ "center",
+ "center bottom",
+ "bottom center",
+ "center right",
+ "right center",
+ "center top",
+ "top center",
+ "center left",
+ "left center",
+ "right bottom",
+ "bottom right",
+ "50%",
+ "top left, top left",
+ "top left, top right",
+ "top right, top left",
+ "left top, 0% 0%",
+ "10% 20%, 30%, 40%",
+ "top left, bottom right",
+ "right bottom, left top",
+ "0%",
+ "0px",
+ "30px",
+ "0%, 10%, 20%, 30%",
+ "top, top, top, top, top",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ "0px 0px",
+ "right 20px top 60px",
+ "right 20px bottom 60px",
+ "left 20px top 60px",
+ "left 20px bottom 60px",
+ "right -50px top -50px",
+ "left -50px bottom -50px",
+ "right 20px top -50px",
+ "right -20px top 50px",
+ "right 3em bottom 10px",
+ "bottom 3em right 10px",
+ "top 3em right 10px",
+ "left 15px",
+ "10px top",
+ "left top 15px",
+ "left 10px top",
+ "left 20%",
+ "right 20%",
+ ],
+ subproperties: ["background-position-x", "background-position-y"],
+ invalid_values: [
+ "center 10px center 4px",
+ "center 10px center",
+ "top 20%",
+ "bottom 20%",
+ "50% left",
+ "top 50%",
+ "50% bottom 10%",
+ "right 10% 50%",
+ "left right",
+ "top bottom",
+ "left 10% right",
+ "top 20px bottom 20px",
+ "left left",
+ "0px calc(0px + rubbish)",
+ ],
+ quirks_values: {
+ "20 20": "20px 20px",
+ "10 5px": "10px 5px",
+ "7px 2": "7px 2px",
+ },
+ },
+ "background-position-x": {
+ domProp: "backgroundPositionX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["left 0%", "left", "0%"],
+ other_values: [
+ "right",
+ "center",
+ "50%",
+ "left, left",
+ "left, right",
+ "right, left",
+ "left, 0%",
+ "10%, 20%, 40%",
+ "0px",
+ "30px",
+ "0%, 10%, 20%, 30%",
+ "left, left, left, left, left",
+ "calc(20px)",
+ "calc(20px + 1em)",
+ "calc(20px / 2)",
+ "calc(20px + 50%)",
+ "calc(50% - 10px)",
+ "calc(-20px)",
+ "calc(-50%)",
+ "calc(-20%)",
+ "right 20px",
+ "left 20px",
+ "right -50px",
+ "left -50px",
+ "right 20px",
+ "right 3em",
+ ],
+ invalid_values: [
+ "center 10px",
+ "right 10% 50%",
+ "left right",
+ "left left",
+ "bottom 20px",
+ "top 10%",
+ "bottom 3em",
+ "top",
+ "bottom",
+ "top, top",
+ "top, bottom",
+ "bottom, top",
+ "top, 0%",
+ "top, top, top, top, top",
+ "calc(0px + rubbish)",
+ ],
+ },
+ "background-position-y": {
+ domProp: "backgroundPositionY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["top 0%", "top", "0%"],
+ other_values: [
+ "bottom",
+ "center",
+ "50%",
+ "top, top",
+ "top, bottom",
+ "bottom, top",
+ "top, 0%",
+ "10%, 20%, 40%",
+ "0px",
+ "30px",
+ "0%, 10%, 20%, 30%",
+ "top, top, top, top, top",
+ "calc(20px)",
+ "calc(20px + 1em)",
+ "calc(20px / 2)",
+ "calc(20px + 50%)",
+ "calc(50% - 10px)",
+ "calc(-20px)",
+ "calc(-50%)",
+ "calc(-20%)",
+ "bottom 20px",
+ "top 20px",
+ "bottom -50px",
+ "top -50px",
+ "bottom 20px",
+ "bottom 3em",
+ ],
+ invalid_values: [
+ "center 10px",
+ "bottom 10% 50%",
+ "top bottom",
+ "top top",
+ "right 20px",
+ "left 10%",
+ "right 3em",
+ "left",
+ "right",
+ "left, left",
+ "left, right",
+ "right, left",
+ "left, 0%",
+ "left, left, left, left, left",
+ "calc(0px + rubbish)",
+ ],
+ },
+ "background-repeat": {
+ domProp: "backgroundRepeat",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["repeat", "repeat repeat"],
+ other_values: [
+ "repeat-x",
+ "repeat-y",
+ "no-repeat",
+ "repeat-x, repeat-x",
+ "repeat, no-repeat",
+ "repeat-y, no-repeat, repeat-y",
+ "repeat, repeat, repeat",
+ "repeat no-repeat",
+ "no-repeat repeat",
+ "no-repeat no-repeat",
+ "repeat repeat, repeat repeat",
+ "round, repeat",
+ "round repeat, repeat-x",
+ "round no-repeat, repeat-y",
+ "round round",
+ "space, repeat",
+ "space repeat, repeat-x",
+ "space no-repeat, repeat-y",
+ "space space",
+ "space round",
+ ],
+ invalid_values: [
+ "repeat repeat repeat",
+ "repeat-x repeat-y",
+ "repeat repeat-x",
+ "repeat repeat-y",
+ "repeat-x repeat",
+ "repeat-y repeat",
+ "round round round",
+ "repeat-x round",
+ "round repeat-x",
+ "repeat-y round",
+ "round repeat-y",
+ "space space space",
+ "repeat-x space",
+ "space repeat-x",
+ "repeat-y space",
+ "space repeat-y",
+ ],
+ },
+ "background-size": {
+ domProp: "backgroundSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "contain",
+ "cover",
+ "100px auto",
+ "auto 100px",
+ "100% auto",
+ "auto 100%",
+ "25% 50px",
+ "3em 40%",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ ],
+ invalid_values: [
+ "contain contain",
+ "cover cover",
+ "cover auto",
+ "auto cover",
+ "contain cover",
+ "cover contain",
+ "-5px 3px",
+ "3px -5px",
+ "auto -5px",
+ "-5px auto",
+ "5 3",
+ "10px calc(10px + rubbish)",
+ ],
+ },
+ border: {
+ domProp: "border",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-bottom-color",
+ "border-bottom-style",
+ "border-bottom-width",
+ "border-left-color",
+ "border-left-style",
+ "border-left-width",
+ "border-right-color",
+ "border-right-style",
+ "border-right-width",
+ "border-top-color",
+ "border-top-style",
+ "border-top-width",
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ "calc(4px - 1px) none",
+ ],
+ other_values: [
+ "solid",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "calc(2px) solid blue",
+ ],
+ invalid_values: ["5%", "medium solid ff00ff", "5 solid green"],
+ },
+ "border-bottom": {
+ domProp: "borderBottom",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-bottom-color",
+ "border-bottom-style",
+ "border-bottom-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-bottom-color": {
+ domProp: "borderBottomColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000"],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-bottom-style": {
+ domProp: "borderBottomStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-bottom-width": {
+ domProp: "borderBottomWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { "border-bottom-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%"],
+ quirks_values: { 5: "5px" },
+ },
+ "border-collapse": {
+ domProp: "borderCollapse",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["separate"],
+ other_values: ["collapse"],
+ invalid_values: [],
+ },
+ "border-color": {
+ domProp: "borderColor",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-top-color",
+ "border-right-color",
+ "border-bottom-color",
+ "border-left-color",
+ ],
+ initial_values: [
+ "currentColor",
+ "currentColor currentColor",
+ "currentColor currentColor currentColor",
+ "currentColor currentColor currentcolor CURRENTcolor",
+ ],
+ other_values: [
+ "green",
+ "currentColor green",
+ "currentColor currentColor green",
+ "currentColor currentColor currentColor green",
+ "rgba(255,128,0,0.5)",
+ "transparent",
+ ],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "red rgb(nonsense)",
+ "red 1px",
+ ],
+ unbalanced_values: ["red rgb("],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-left": {
+ domProp: "borderLeft",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-left-color",
+ "border-left-style",
+ "border-left-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: [
+ "5%",
+ "5",
+ "5 solid green",
+ "calc(5px + rubbish) green solid",
+ "5px rgb(0, rubbish, 0) solid",
+ ],
+ },
+ "border-left-color": {
+ domProp: "borderLeftColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000"],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-left-style": {
+ domProp: "borderLeftStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-left-width": {
+ domProp: "borderLeftWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { "border-left-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%"],
+ quirks_values: { 5: "5px" },
+ },
+ "border-right": {
+ domProp: "borderRight",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-right-color",
+ "border-right-style",
+ "border-right-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-right-color": {
+ domProp: "borderRightColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000"],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-right-style": {
+ domProp: "borderRightStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-right-width": {
+ domProp: "borderRightWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { "border-right-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%"],
+ quirks_values: { 5: "5px" },
+ },
+ "border-spacing": {
+ domProp: "borderSpacing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [
+ "0",
+ "0 0",
+ "0px",
+ "0 0px",
+ "calc(0px)",
+ "calc(0px) calc(0em)",
+ "calc(2em - 2em) calc(3px + 7px - 10px)",
+ "calc(-5px)",
+ "calc(-5px) calc(-5px)",
+ ],
+ other_values: [
+ "3px",
+ "4em 2px",
+ "4em 0",
+ "0px 2px",
+ "calc(7px)",
+ "0 calc(7px)",
+ "calc(7px) 0",
+ "calc(0px) calc(7px)",
+ "calc(7px) calc(0px)",
+ "7px calc(0px)",
+ "calc(0px) 7px",
+ "7px calc(0px)",
+ "3px calc(2em)",
+ ],
+ invalid_values: [
+ "0%",
+ "0 0%",
+ "-5px",
+ "-5px -5px",
+ "0 -5px",
+ "-5px 0",
+ "0 calc(0px + rubbish)",
+ ],
+ quirks_values: {
+ "2px 5": "2px 5px",
+ 7: "7px",
+ "3 4px": "3px 4px",
+ },
+ },
+ "border-style": {
+ domProp: "borderStyle",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-top-style",
+ "border-right-style",
+ "border-bottom-style",
+ "border-left-style",
+ ],
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: [
+ "none",
+ "none none",
+ "none none none",
+ "none none none none",
+ ],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ "none solid",
+ "none none solid",
+ "none none none solid",
+ "groove none none none",
+ "none ridge none none",
+ "none none double none",
+ "none none none dotted",
+ ],
+ invalid_values: [],
+ },
+ "border-top": {
+ domProp: "borderTop",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-top-color", "border-top-style", "border-top-width"],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-top-color": {
+ domProp: "borderTopColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000"],
+ quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" },
+ },
+ "border-top-style": {
+ domProp: "borderTopStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-top-width": {
+ domProp: "borderTopWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ prerequisites: { "border-top-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%"],
+ quirks_values: { 5: "5px" },
+ },
+ "border-width": {
+ domProp: "borderWidth",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-top-width",
+ "border-right-width",
+ "border-bottom-width",
+ "border-left-width",
+ ],
+ prerequisites: { "border-style": "solid" },
+ initial_values: [
+ "medium",
+ "3px",
+ "medium medium",
+ "3px medium medium",
+ "medium 3px medium medium",
+ "calc(3px) 3px calc(5px - 2px) calc(2px - -1px)",
+ ],
+ other_values: ["thin", "thick", "1px", "2em", "2px 0 0px 1em", "calc(2em)"],
+ invalid_values: ["5%", "1px calc(nonsense)", "1px red"],
+ unbalanced_values: ["1px calc("],
+ quirks_values: { 5: "5px" },
+ },
+ bottom: {
+ domProp: "bottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "box-shadow": {
+ domProp: "boxShadow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["none"],
+ prerequisites: { color: "blue" },
+ other_values: [
+ "2px 2px",
+ "2px 2px 1px",
+ "2px 2px 2px 2px",
+ "blue 3px 2px",
+ "2px 2px 1px 5px green",
+ "2px 2px red",
+ "green 2px 2px 1px",
+ "green 2px 2px, blue 1px 3px 4px",
+ "currentColor 3px 3px",
+ "blue 2px 2px, currentColor 1px 2px, 1px 2px 3px 2px orange",
+ "3px 0 0 0",
+ "inset 2px 2px 3px 4px black",
+ "2px -2px green inset, 4px 4px 3px blue, inset 2px 2px",
+ /* calc() values */
+ "2px 2px calc(-5px)" /* clamped */,
+ "calc(3em - 2px) 2px green",
+ "green calc(3em - 2px) 2px",
+ "2px calc(2px + 0.2em)",
+ "blue 2px calc(2px + 0.2em)",
+ "2px calc(2px + 0.2em) blue",
+ "calc(-2px) calc(-2px)",
+ "-2px -2px",
+ "calc(2px) calc(2px)",
+ "calc(2px) calc(2px) calc(2px)",
+ "calc(2px) calc(2px) calc(2px) calc(2px)",
+ ],
+ invalid_values: [
+ "3% 3%",
+ "1px 1px 1px 1px 1px",
+ "2px 2px, none",
+ "red 2px 2px blue",
+ "inherit, 2px 2px",
+ "2px 2px, inherit",
+ "2px 2px -5px",
+ "inset 4px 4px black inset",
+ "inset inherit",
+ "inset none",
+ "3 3",
+ "3px 3",
+ "3 3px",
+ "3px 3px 3",
+ "3px 3px 3px 3",
+ "3px calc(3px + rubbish)",
+ "3px 3px calc(3px + rubbish)",
+ "3px 3px 3px calc(3px + rubbish)",
+ "3px 3px 3px 3px rgb(0, rubbish, 0)",
+ "unset, 2px 2px",
+ "2px 2px, unset",
+ "inset unset",
+ ],
+ },
+ "caption-side": {
+ domProp: "captionSide",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["top"],
+ other_values: ["bottom"],
+ invalid_values: ["right", "left", "top-outside", "bottom-outside"],
+ },
+ "caret-color": {
+ domProp: "caretColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "black" },
+ // Though "auto" is an independent computed-value time keyword value,
+ // it is not distinguishable from currentcolor because getComputedStyle
+ // always returns used value for <color>.
+ initial_values: ["auto", "currentcolor", "black", "rgb(0,0,0)"],
+ other_values: ["green", "transparent", "rgba(128,128,128,.5)", "#123"],
+ invalid_values: ["#0", "#00", "#00000", "cc00ff"],
+ },
+ clear: {
+ domProp: "clear",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["left", "right", "both", "inline-start", "inline-end"],
+ invalid_values: [],
+ },
+ clip: {
+ domProp: "clip",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "rect(0 0 0 0)",
+ "rect(auto,auto,auto,auto)",
+ "rect(3px, 4px, 4em, 0)",
+ "rect(auto, 3em, 4pt, 2px)",
+ "rect(2px 3px 4px 5px)",
+ ],
+ invalid_values: ["rect(auto, 3em, 2%, 5px)"],
+ quirks_values: { "rect(1, 2, 3, 4)": "rect(1px, 2px, 3px, 4px)" },
+ },
+ color: {
+ domProp: "color",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ /* XXX should test currentColor, but may or may not be initial */
+ initial_values: [
+ "black",
+ "#000",
+ "#000f",
+ "#000000ff",
+ "-moz-default-color",
+ "rgb(0, 0, 0)",
+ "rgb(0%, 0%, 0%)",
+ /* css-color-4: */
+ /* rgb() and rgba() are aliases of each other. */
+ "rgb(0, 0, 0)",
+ "rgba(0, 0, 0)",
+ "rgb(0, 0, 0, 1)",
+ "rgba(0, 0, 0, 1)",
+ /* hsl() and hsla() are aliases of each other. */
+ "hsl(0, 0%, 0%)",
+ "hsla(0, 0%, 0%)",
+ "hsl(0, 0%, 0%, 1)",
+ "hsla(0, 0%, 0%, 1)",
+ /* rgb() and rgba() functions now accept <number> rather than <integer>. */
+ "rgb(0.0, 0.0, 0.0)",
+ "rgba(0.0, 0.0, 0.0)",
+ "rgb(0.0, 0.0, 0.0, 1)",
+ "rgba(0.0, 0.0, 0.0, 1)",
+ /* <alpha-value> now accepts <percentage> as well as <number> in rgba() and hsla(). */
+ "rgb(0.0, 0.0, 0.0, 100%)",
+ "hsl(0, 0%, 0%, 100%)",
+ /* rgb() and hsl() now support comma-less expression. */
+ "rgb(0 0 0)",
+ "rgb(0 0 0 / 1)",
+ "rgb(0/* comment */0/* comment */0)",
+ "rgb(0/* comment */0/* comment*/0/1.0)",
+ "hsl(0 0% 0%)",
+ "hsl(0 0% 0% / 1)",
+ "hsl(0/* comment */0%/* comment */0%)",
+ "hsl(0/* comment */0%/* comment */0%/1)",
+ /* Support <angle> for hsl() hue component. */
+ "hsl(0deg, 0%, 0%)",
+ "hsl(360deg, 0%, 0%)",
+ "hsl(0grad, 0%, 0%)",
+ "hsl(400grad, 0%, 0%)",
+ "hsl(0rad, 0%, 0%)",
+ "hsl(0turn, 0%, 0%)",
+ "hsl(1turn, 0%, 0%)",
+ /* CSS4 System Colors */
+ "canvastext",
+ /* Preserve previously available specially prefixed colors */
+ "-moz-default-color",
+ ],
+ other_values: [
+ "green",
+ "#f3c",
+ "#fed292",
+ "rgba(45,300,12,2)",
+ "transparent",
+ "LinkText",
+ "rgba(255,128,0,0.5)",
+ "#e0fc",
+ "#10fcee72",
+ /* css-color-4: */
+ "rgb(100, 100.0, 100)",
+ "rgb(300 300 300 / 200%)",
+ "rgb(300.0 300.0 300.0 / 2.0)",
+ "hsl(720, 200%, 200%, 2.0)",
+ "hsla(720 200% 200% / 200%)",
+ "hsl(480deg, 20%, 30%, 0.3)",
+ "hsl(55grad, 400%, 30%)",
+ "hsl(0.5grad 400% 500% / 9.0)",
+ "hsl(33rad 100% 90% / 4)",
+ "hsl(0.33turn, 40%, 40%, 10%)",
+ "hsl(63e292, 41%, 34%)",
+ /* CSS4 System Colors */
+ "canvas",
+ "linktext",
+ "visitedtext",
+ "activetext",
+ "buttonface",
+ "field",
+ "highlight",
+ "graytext",
+ /* Preserve previously available specially prefixed colors */
+ "-moz-activehyperlinktext",
+ "-moz-default-background-color",
+ "-moz-hyperlinktext",
+ "-moz-visitedhyperlinktext",
+ /* color-mix */
+ "color-mix(in srgb, red, blue)",
+ "color-mix(in srgb, highlight, rgba(0, 0, 0, .5))",
+ "color-mix(in srgb, color-mix(in srgb, red 10%, blue), green)",
+ "color-mix(in srgb, blue, red 80%)",
+ "color-mix(in srgb, rgba(0, 200, 32, .5) 90%, red 50%)",
+ "color-mix(in srgb, currentColor, red)",
+ ],
+ invalid_values: [
+ "#f",
+ "#ff",
+ "#fffff",
+ "#fffffff",
+ "#fffffffff",
+ "rgb(100%, 0, 100%)",
+ "rgba(100, 0, 100%, 30%)",
+ "hsl(0, 0, 0%)",
+ "hsla(0%, 0%, 0%, 0.1)",
+ /* trailing commas */
+ "rgb(0, 0, 0,)",
+ "rgba(0, 0, 0, 0,)",
+ "hsl(0, 0%, 0%,)",
+ "hsla(0, 0%, 0%, 1,)",
+ /* css-color-4: */
+ /* comma and comma-less expressions should not mix together. */
+ "rgb(0, 0, 0 / 1)",
+ "rgb(0 0 0, 1)",
+ "rgb(0, 0 0, 1)",
+ "rgb(0 0, 0 / 1)",
+ "hsl(0, 0%, 0% / 1)",
+ "hsl(0 0% 0%, 1)",
+ "hsl(0 0% 0%, 1)",
+ "hsl(0 0%, 0% / 1)",
+ /* trailing slash */
+ "rgb(0 0 0 /)",
+ "rgb(0, 0, 0 /)",
+ "hsl(0 0% 0% /)",
+ "hsl(0, 0%, 0% /)",
+ /* color-mix */
+ "color-mix(red, blue)",
+ "color-mix(red blue)",
+ "color-mix(in srgb, red blue)",
+ "color-mix(in srgb, red 10% blue)",
+ ],
+ quirks_values: {
+ "000000": "#000000",
+ "96ed2a": "#96ed2a",
+ fff: "#ffffff",
+ ffffff: "#ffffff",
+ },
+ },
+ content: {
+ domProp: "content",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ // XXX This really depends on pseudo-element-ness.
+ initial_values: ["normal", "none"],
+ other_values: [
+ '""',
+ "''",
+ '"hello"',
+ "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
+ "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')",
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ "counter(foo)",
+ "counter(bar, upper-roman)",
+ 'counters(foo, ".")',
+ "counters(bar, '-', lower-greek)",
+ "'-' counter(foo) '.'",
+ "attr(title)",
+ "open-quote",
+ "close-quote",
+ "no-open-quote",
+ "no-close-quote",
+ "close-quote attr(title) counters(foo, '.', upper-alpha)",
+ "attr(\\32)",
+ "attr(\\2)",
+ "attr(-\\2)",
+ "attr(-\\32)",
+ 'attr(title, "fallback")',
+ 'attr(\\32, "fallback")',
+ 'attr(-\\32, "fallback")',
+ "counter(\\2)",
+ "counters(\\32, '.')",
+ "counter(-\\32, upper-roman)",
+ "counters(-\\2, '-', lower-greek)",
+ "counter(\\()",
+ "counters(a\\+b, '.')",
+ "counter(\\}, upper-alpha)",
+ "-moz-alt-content",
+ "counter(foo, symbols('*'))",
+ "counter(foo, symbols(numeric '0' '1'))",
+ "counters(foo, '.', symbols('*'))",
+ "counters(foo, '.', symbols(numeric '0' '1'))",
+ "image-set(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==))",
+ ].concat(validNonUrlImageValues),
+ invalid_values: [
+ "counter(foo, none)",
+ "counters(bar, '.', none)",
+ "counters(foo)",
+ 'counter(foo, ".")',
+ 'attr("title")',
+ "attr('title')",
+ "attr(2)",
+ "attr(-2)",
+ "counter(2)",
+ "counters(-2, '.')",
+ "-moz-alt-content 'foo'",
+ "'foo' -moz-alt-content",
+ "counter(one, two, three) 'foo'",
+ ].concat(invalidNonUrlImageValues),
+ },
+ "counter-increment": {
+ domProp: "counterIncrement",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "foo 1",
+ "bar",
+ "foo 3 bar baz 2",
+ "\\32 1",
+ "-\\32 1",
+ "-c 1",
+ "\\32 1",
+ "-\\32 1",
+ "\\2 1",
+ "-\\2 1",
+ "-c 1",
+ "\\2 1",
+ "-\\2 1",
+ "-\\7f \\9e 1",
+ ],
+ invalid_values: ["none foo", "none foo 3", "foo none", "foo 3 none"],
+ unbalanced_values: ["foo 1 ("],
+ },
+ "counter-reset": {
+ domProp: "counterReset",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "foo 1",
+ "bar",
+ "foo 3 bar baz 2",
+ "\\32 1",
+ "-\\32 1",
+ "-c 1",
+ "\\32 1",
+ "-\\32 1",
+ "\\2 1",
+ "-\\2 1",
+ "-c 1",
+ "\\2 1",
+ "-\\2 1",
+ "-\\7f \\9e 1",
+ ],
+ invalid_values: ["none foo", "none foo 3", "foo none", "foo 3 none"],
+ },
+ "counter-set": {
+ domProp: "counterSet",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "foo 1",
+ "bar",
+ "foo 3 bar baz 2",
+ "\\32 1",
+ "-\\32 1",
+ "-c 1",
+ "\\32 1",
+ "-\\32 1",
+ "\\2 1",
+ "-\\2 1",
+ "-c 1",
+ "\\2 1",
+ "-\\2 1",
+ "-\\7f \\9e 1",
+ ],
+ invalid_values: ["none foo", "none foo 3", "foo none", "foo 3 none"],
+ },
+ cursor: {
+ domProp: "cursor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "crosshair",
+ "default",
+ "pointer",
+ "move",
+ "e-resize",
+ "ne-resize",
+ "nw-resize",
+ "n-resize",
+ "se-resize",
+ "sw-resize",
+ "s-resize",
+ "w-resize",
+ "text",
+ "wait",
+ "help",
+ "progress",
+ "copy",
+ "alias",
+ "context-menu",
+ "cell",
+ "not-allowed",
+ "col-resize",
+ "row-resize",
+ "no-drop",
+ "vertical-text",
+ "all-scroll",
+ "nesw-resize",
+ "nwse-resize",
+ "ns-resize",
+ "ew-resize",
+ "none",
+ "grab",
+ "grabbing",
+ "zoom-in",
+ "zoom-out",
+ "-moz-grab",
+ "-moz-grabbing",
+ "-moz-zoom-in",
+ "-moz-zoom-out",
+ "url(foo.png), move",
+ "url(foo.png) 5 7, move",
+ "url(foo.png) 12 3, url(bar.png), no-drop",
+ "url(foo.png), url(bar.png) 7 2, wait",
+ "url(foo.png) 3 2, url(bar.png) 7 9, pointer",
+ "url(foo.png) calc(1 + 2) calc(3), pointer",
+ "image-set(url(foo.png)), auto",
+ ],
+ invalid_values: [
+ "url(foo.png)",
+ "url(foo.png) 5 5",
+ "image-set(linear-gradient(red, blue)), auto",
+ // Gradients are supported per spec, but we don't have support for it yet
+ "linear-gradient(red, blue), auto",
+ ],
+ },
+ direction: {
+ domProp: "direction",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["ltr"],
+ other_values: ["rtl"],
+ invalid_values: [],
+ },
+ display: {
+ domProp: "display",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: ["inline"],
+ /* XXX none will really mess with other properties */
+ prerequisites: { float: "none", position: "static", contain: "none" },
+ other_values: [
+ "block",
+ "flex",
+ "inline-flex",
+ "list-item",
+ "inline list-item",
+ "inline flow-root list-item",
+ "inline-block",
+ "table",
+ "inline-table",
+ "table-row-group",
+ "table-header-group",
+ "table-footer-group",
+ "table-row",
+ "table-column-group",
+ "table-column",
+ "table-cell",
+ "table-caption",
+ "block ruby",
+ "ruby",
+ "ruby-base",
+ "ruby-base-container",
+ "ruby-text",
+ "ruby-text-container",
+ "contents",
+ "none",
+ ],
+ invalid_values: [],
+ },
+ "empty-cells": {
+ domProp: "emptyCells",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["show"],
+ other_values: ["hide"],
+ invalid_values: [],
+ },
+ float: {
+ domProp: "cssFloat",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["none"],
+ other_values: ["left", "right", "inline-start", "inline-end"],
+ invalid_values: [],
+ },
+ font: {
+ domProp: "font",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { "writing-mode": "initial" },
+ subproperties: [
+ "font-style",
+ "font-variant",
+ "font-weight",
+ "font-size",
+ "line-height",
+ "font-family",
+ "font-stretch",
+ "font-size-adjust",
+ "font-feature-settings",
+ "font-language-override",
+ "font-kerning",
+ "font-variant-alternates",
+ "font-variant-caps",
+ "font-variant-east-asian",
+ "font-variant-ligatures",
+ "font-variant-numeric",
+ "font-variant-position",
+ ],
+ initial_values: [
+ gInitialFontFamilyIsSansSerif ? "medium sans-serif" : "medium serif",
+ ],
+ other_values: [
+ "large serif",
+ "9px fantasy",
+ "condensed bold italic small-caps 24px/1.4 Times New Roman, serif",
+ "small inherit roman",
+ "small roman inherit",
+ // system fonts
+ "caption",
+ "icon",
+ "menu",
+ "message-box",
+ "small-caption",
+ "status-bar",
+ // line-height with calc()
+ "condensed bold italic small-caps 24px/calc(2px) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(50%) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(3*25px) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(25px*3) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(3*25px + 50%) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(1 + 2*3/4) Times New Roman, serif",
+ ],
+ invalid_values: [
+ "9 fantasy",
+ "-2px fantasy",
+ // line-height with calc()
+ "condensed bold italic small-caps 24px/calc(1 + 2px) Times New Roman, serif",
+ "condensed bold italic small-caps 24px/calc(100% + 0.1) Times New Roman, serif",
+ ],
+ },
+ "font-family": {
+ domProp: "fontFamily",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [gInitialFontFamilyIsSansSerif ? "sans-serif" : "serif"],
+ other_values: [
+ gInitialFontFamilyIsSansSerif ? "serif" : "sans-serif",
+ "Times New Roman, serif",
+ "'Times New Roman', serif",
+ "cursive",
+ "fantasy",
+ '\\"Times New Roman',
+ '"Times New Roman"',
+ 'Times, \\"Times New Roman',
+ 'Times, "Times New Roman"',
+ "-no-such-font-installed",
+ "inherit roman",
+ "roman inherit",
+ "Times, inherit roman",
+ "inherit roman, Times",
+ "roman inherit, Times",
+ "Times, roman inherit",
+ ],
+ invalid_values: [
+ '"Times New" Roman',
+ '"Times New Roman\n',
+ 'Times, "Times New Roman\n',
+ ],
+ },
+ "font-feature-settings": {
+ domProp: "fontFeatureSettings",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "'liga' on",
+ "'liga'",
+ '"liga" 1',
+ "'liga', 'clig' 1",
+ '"liga" off',
+ '"liga" 0',
+ '"cv01" 3, "cv02" 4',
+ '"cswh", "smcp" off, "salt" 4',
+ '"cswh" 1, "smcp" off, "salt" 4',
+ '"cswh" 0, \'blah\', "liga", "smcp" off, "salt" 4',
+ '"liga" ,"smcp" 0 , "blah"',
+ '"ab\\"c"',
+ '"ab\\\\c"',
+ "'vert' calc(2)",
+ ],
+ invalid_values: [
+ "liga",
+ "liga 1",
+ "liga normal",
+ '"liga" normal',
+ "normal liga",
+ 'normal "liga"',
+ 'normal, "liga"',
+ '"liga=1"',
+ "'foobar' on",
+ '"blahblah" 0',
+ '"liga" 3.14',
+ '"liga" 1 3.14',
+ '"liga" 1 normal',
+ '"liga" 1 off',
+ '"liga" on off',
+ '"liga" , 0 "smcp"',
+ '"liga" "smcp"',
+ ],
+ },
+ "font-kerning": {
+ domProp: "fontKerning",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["normal", "none"],
+ invalid_values: ["on"],
+ },
+ "font-language-override": {
+ domProp: "fontLanguageOverride",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: ["'ENG'", "'TRK'", '"TRK"', "'N\\'Ko'"],
+ invalid_values: ["TRK", "ja"],
+ },
+ "font-size": {
+ domProp: "fontSize",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [
+ "medium",
+ "1rem",
+ "calc(1rem)",
+ "calc(0.75rem + 200% - 125% + 0.25rem - 75%)",
+ ],
+ other_values: [
+ "large",
+ "2em",
+ "50%",
+ "xx-small",
+ "xxx-large",
+ "36pt",
+ "8px",
+ "larger",
+ "smaller",
+ "0px",
+ "0%",
+ "calc(2em)",
+ "calc(36pt + 75% + (30% + 2em + 2px))",
+ "calc(-2em)",
+ "calc(-50%)",
+ "calc(-1px)",
+ ],
+ invalid_values: ["-2em", "-50%", "-1px"],
+ quirks_values: { 5: "5px" },
+ },
+ "font-size-adjust": {
+ domProp: "fontSizeAdjust",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["none"],
+ other_values: [
+ "0.7",
+ "0.0",
+ "0",
+ "3",
+ "from-font",
+ "cap-height 0.8",
+ "ch-width 0.4",
+ "ic-width 0.4",
+ "ic-height 0.9",
+ "ch-width from-font",
+ ],
+ invalid_values: [
+ "-0.3",
+ "-1",
+ "normal",
+ "none none",
+ "cap-height none",
+ "none from-font",
+ "from-font none",
+ "0.5 from-font",
+ "0.5 cap-height",
+ "cap-height, 0.8",
+ ],
+ },
+ "font-stretch": {
+ domProp: "fontStretch",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "ultra-condensed",
+ "extra-condensed",
+ "condensed",
+ "semi-condensed",
+ "semi-expanded",
+ "expanded",
+ "extra-expanded",
+ "ultra-expanded",
+ ],
+ invalid_values: ["narrower", "wider"],
+ },
+ "font-style": {
+ domProp: "fontStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: ["italic", "oblique"],
+ invalid_values: [],
+ },
+ "font-synthesis": {
+ domProp: "fontSynthesis",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ subproperties: [
+ "font-synthesis-weight",
+ "font-synthesis-style",
+ "font-synthesis-small-caps",
+ "font-synthesis-position",
+ ],
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [
+ "weight style small-caps position",
+ "weight small-caps style position",
+ "small-caps weight position style",
+ "small-caps style position weight",
+ "style position weight small-caps",
+ "style position small-caps weight",
+ ],
+ other_values: [
+ "none",
+ "weight",
+ "style",
+ "small-caps",
+ "position",
+ "weight style",
+ "style weight",
+ "weight small-caps",
+ "small-caps weight",
+ "weight position",
+ "position weight",
+ "style small-caps",
+ "small-caps style",
+ "style position",
+ "position style",
+ "small-caps position",
+ "position small-caps",
+ "weight style small-caps",
+ "small-caps weight style",
+ "weight style position",
+ "position weight style",
+ "weight small-caps position",
+ "position weight small-caps",
+ ],
+ invalid_values: [
+ "10px",
+ "weight none",
+ "style none",
+ "none style",
+ "none 10px",
+ "weight 10px",
+ "weight weight",
+ "style style",
+ "small-caps none",
+ "small-caps small-caps",
+ "position none",
+ "position position",
+ ],
+ },
+ "font-synthesis-weight": {
+ domProp: "fontSynthesisWeight",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["auto none", "weight", "normal", "0"],
+ },
+ "font-synthesis-style": {
+ domProp: "fontSynthesisStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["auto none", "style", "normal", "0"],
+ },
+ "font-synthesis-small-caps": {
+ domProp: "fontSynthesisSmallCaps",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["auto none", "small-caps", "normal", "0"],
+ },
+ "font-synthesis-position": {
+ domProp: "fontSynthesisPosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["auto none", "position", "normal", "0"],
+ },
+ "font-variant": {
+ domProp: "fontVariant",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "font-variant-alternates",
+ "font-variant-caps",
+ "font-variant-east-asian",
+ "font-variant-ligatures",
+ "font-variant-numeric",
+ "font-variant-position",
+ ],
+ initial_values: ["normal"],
+ other_values: [
+ "small-caps",
+ "none",
+ "traditional oldstyle-nums",
+ "all-small-caps",
+ "common-ligatures no-discretionary-ligatures",
+ "proportional-nums oldstyle-nums",
+ "proportional-nums slashed-zero diagonal-fractions oldstyle-nums ordinal",
+ "traditional historical-forms styleset(ok-alt-a, ok-alt-b)",
+ "styleset(potato)",
+ ],
+ invalid_values: [
+ "small-caps normal",
+ "small-caps small-caps",
+ "none common-ligatures",
+ "common-ligatures none",
+ "small-caps potato",
+ "small-caps jis83 all-small-caps",
+ "super historical-ligatures sub",
+ "stacked-fractions diagonal-fractions historical-ligatures",
+ "common-ligatures traditional common-ligatures",
+ "lining-nums traditional slashed-zero ordinal normal",
+ "traditional historical-forms styleset(ok-alt-a, ok-alt-b) historical-forms",
+ "historical-forms styleset(ok-alt-a, ok-alt-b) traditional styleset(potato)",
+ "annotation(a,b,c)",
+ ],
+ },
+ "font-variant-alternates": {
+ domProp: "fontVariantAlternates",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "historical-forms",
+ "styleset(alt-a, alt-b)",
+ "character-variant(a, b, c)",
+ "annotation(circled)",
+ "swash(squishy)",
+ "styleset(complex\\ blob, a)",
+ "annotation(\\62 lah)",
+ ],
+ invalid_values: [
+ "historical-forms normal",
+ "historical-forms historical-forms",
+ "swash",
+ "swash(3)",
+ "annotation(a, b)",
+ "ornaments(a,b)",
+ "styleset(1234blah)",
+ "annotation(a), annotation(b)",
+ "annotation(a) normal",
+ ],
+ },
+ "font-variant-caps": {
+ domProp: "fontVariantCaps",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "small-caps",
+ "all-small-caps",
+ "petite-caps",
+ "all-petite-caps",
+ "titling-caps",
+ "unicase",
+ ],
+ invalid_values: [
+ "normal small-caps",
+ "petite-caps normal",
+ "unicase unicase",
+ ],
+ },
+ "font-variant-east-asian": {
+ domProp: "fontVariantEastAsian",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "jis78",
+ "jis83",
+ "jis90",
+ "jis04",
+ "simplified",
+ "traditional",
+ "full-width",
+ "proportional-width",
+ "ruby",
+ "jis78 full-width",
+ "jis78 full-width ruby",
+ "simplified proportional-width",
+ "ruby simplified",
+ ],
+ invalid_values: [
+ "jis78 normal",
+ "jis90 jis04",
+ "simplified traditional",
+ "full-width proportional-width",
+ "ruby simplified ruby",
+ "jis78 ruby simplified",
+ ],
+ },
+ "font-variant-ligatures": {
+ domProp: "fontVariantLigatures",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "none",
+ "common-ligatures",
+ "no-common-ligatures",
+ "discretionary-ligatures",
+ "no-discretionary-ligatures",
+ "historical-ligatures",
+ "no-historical-ligatures",
+ "contextual",
+ "no-contextual",
+ "common-ligatures no-discretionary-ligatures",
+ "contextual no-discretionary-ligatures",
+ "historical-ligatures no-common-ligatures",
+ "no-historical-ligatures discretionary-ligatures",
+ "common-ligatures no-discretionary-ligatures historical-ligatures no-contextual",
+ ],
+ invalid_values: [
+ "common-ligatures normal",
+ "common-ligatures no-common-ligatures",
+ "common-ligatures common-ligatures",
+ "no-historical-ligatures historical-ligatures",
+ "no-discretionary-ligatures discretionary-ligatures",
+ "no-contextual contextual",
+ "common-ligatures no-discretionary-ligatures no-common-ligatures",
+ "common-ligatures none",
+ "no-discretionary-ligatures none",
+ "none common-ligatures",
+ ],
+ },
+ "font-variant-numeric": {
+ domProp: "fontVariantNumeric",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "lining-nums",
+ "oldstyle-nums",
+ "proportional-nums",
+ "tabular-nums",
+ "diagonal-fractions",
+ "stacked-fractions",
+ "slashed-zero",
+ "ordinal",
+ "lining-nums diagonal-fractions",
+ "tabular-nums stacked-fractions",
+ "tabular-nums slashed-zero stacked-fractions",
+ "proportional-nums slashed-zero diagonal-fractions oldstyle-nums ordinal",
+ ],
+ invalid_values: [
+ "lining-nums normal",
+ "lining-nums oldstyle-nums",
+ "lining-nums normal slashed-zero ordinal",
+ "proportional-nums tabular-nums",
+ "diagonal-fractions stacked-fractions",
+ "slashed-zero diagonal-fractions slashed-zero",
+ "lining-nums slashed-zero diagonal-fractions oldstyle-nums",
+ "diagonal-fractions diagonal-fractions",
+ ],
+ },
+ "font-variant-position": {
+ domProp: "fontVariantPosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: ["super", "sub"],
+ invalid_values: ["normal sub", "super sub"],
+ },
+ "font-weight": {
+ domProp: "fontWeight",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal", "400"],
+ other_values: [
+ "bold",
+ "100",
+ "200",
+ "300",
+ "500",
+ "600",
+ "700",
+ "800",
+ "900",
+ "bolder",
+ "lighter",
+ "10.5",
+ "calc(10 + 10)",
+ "calc(10 - 99)",
+ "100.0",
+ "107",
+ "399",
+ "401",
+ "699",
+ "710",
+ "1000",
+ ],
+ invalid_values: ["0", "1001", "calc(10%)"],
+ },
+ height: {
+ domProp: "height",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: test zero, and test calc clamping */
+ initial_values: [" auto"],
+ /* computed value tests for height test more with display:block */
+ prerequisites: { display: "block" },
+ other_values: [
+ "15px",
+ "3em",
+ "15%",
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ quirks_values: { 5: "5px" },
+ },
+ "ime-mode": {
+ domProp: "imeMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["normal", "disabled", "active", "inactive"],
+ invalid_values: ["none", "enabled", "1px"],
+ },
+ left: {
+ domProp: "left",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "letter-spacing": {
+ domProp: "letterSpacing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["normal", "0", "0px", "calc(0px)"],
+ other_values: [
+ "1em",
+ "2px",
+ "-3px",
+ "calc(1em)",
+ "calc(1em + 3px)",
+ "calc(15px / 2)",
+ "calc(15px/2)",
+ "calc(-3px)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "line-break": {
+ domProp: "lineBreak",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["loose", "normal", "strict", "anywhere"],
+ invalid_values: [],
+ },
+ "line-height": {
+ domProp: "lineHeight",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ /*
+ * Inheritance tests require consistent font size, since
+ * getComputedStyle (which uses the CSS2 computed value, or
+ * CSS2.1 used value) doesn't match what the CSS2.1 computed
+ * value is. And they even require consistent font metrics for
+ * computation of 'normal'.
+ */
+ prerequisites: {
+ "font-size": "19px",
+ "font-size-adjust": "none",
+ "font-family": "serif",
+ "font-weight": "normal",
+ "font-style": "normal",
+ height: "18px",
+ display: "block",
+ "writing-mode": "initial",
+ },
+
+ initial_values: ["normal"],
+ other_values: [
+ "1.0",
+ "1",
+ "1em",
+ "47px",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "calc(1 + 2*3/4)",
+ ],
+ invalid_values: ["calc(1 + 2px)", "calc(100% + 0.1)"],
+ },
+ "list-style": {
+ domProp: "listStyle",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "list-style-type",
+ "list-style-position",
+ "list-style-image",
+ ],
+ initial_values: [
+ "outside",
+ "disc",
+ "disc outside",
+ "outside disc",
+ "disc none",
+ "none disc",
+ "none disc outside",
+ "none outside disc",
+ "disc none outside",
+ "disc outside none",
+ "outside none disc",
+ "outside disc none",
+ ],
+ other_values: [
+ "inside none",
+ "none inside",
+ "none none inside",
+ "square",
+ "none",
+ "none none",
+ "outside none none",
+ "none outside none",
+ "none none outside",
+ "none outside",
+ "outside none",
+ "outside outside",
+ "outside inside",
+ "\\32 style",
+ "\\32 style inside",
+ '"-"',
+ "'-'",
+ "inside '-'",
+ "'-' outside",
+ "none '-'",
+ "inside none '-'",
+ 'symbols("*" "\\2020" "\\2021" "\\A7")',
+ 'symbols(cyclic "*" "\\2020" "\\2021" "\\A7")',
+ 'inside symbols("*" "\\2020" "\\2021" "\\A7")',
+ 'symbols("*" "\\2020" "\\2021" "\\A7") outside',
+ 'none symbols("*" "\\2020" "\\2021" "\\A7")',
+ 'inside none symbols("*" "\\2020" "\\2021" "\\A7")',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside',
+ 'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'outside none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none',
+ 'none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside',
+ 'none outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside none',
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none outside',
+ ],
+ invalid_values: [
+ "disc disc",
+ "unknown value",
+ "none none none",
+ "none disc url(404.png)",
+ "none url(404.png) disc",
+ "disc none url(404.png)",
+ "disc url(404.png) none",
+ "url(404.png) none disc",
+ "url(404.png) disc none",
+ "none disc outside url(404.png)",
+ ],
+ },
+ "list-style-image": {
+ domProp: "listStyleImage",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
+ // Add some tests for interesting url() values here to test serialization, etc.
+ "url('data:text/plain,\"')",
+ 'url("data:text/plain,\'")',
+ "url('data:text/plain,\\'')",
+ 'url("data:text/plain,\\"")',
+ "url('data:text/plain,\\\"')",
+ 'url("data:text/plain,\\\'")',
+ "url(data:text/plain,\\\\)",
+ ].concat(validNonUrlImageValues),
+ invalid_values: ["url('border.png') url('border.png')"].concat(
+ invalidNonUrlImageValues
+ ),
+ unbalanced_values: [].concat(unbalancedGradientAndElementValues),
+ },
+ "list-style-position": {
+ domProp: "listStylePosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["outside"],
+ other_values: ["inside"],
+ invalid_values: [],
+ },
+ "list-style-type": {
+ domProp: "listStyleType",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["disc"],
+ other_values: [
+ "none",
+ "circle",
+ "square",
+ "disclosure-closed",
+ "disclosure-open",
+ "decimal",
+ "decimal-leading-zero",
+ "lower-roman",
+ "upper-roman",
+ "lower-greek",
+ "lower-alpha",
+ "lower-latin",
+ "upper-alpha",
+ "upper-latin",
+ "hebrew",
+ "armenian",
+ "georgian",
+ "cjk-decimal",
+ "cjk-ideographic",
+ "hiragana",
+ "katakana",
+ "hiragana-iroha",
+ "katakana-iroha",
+ "japanese-informal",
+ "japanese-formal",
+ "korean-hangul-formal",
+ "korean-hanja-informal",
+ "korean-hanja-formal",
+ "simp-chinese-informal",
+ "simp-chinese-formal",
+ "trad-chinese-informal",
+ "trad-chinese-formal",
+ "ethiopic-numeric",
+ "-moz-cjk-heavenly-stem",
+ "-moz-cjk-earthly-branch",
+ "-moz-trad-chinese-informal",
+ "-moz-trad-chinese-formal",
+ "-moz-simp-chinese-informal",
+ "-moz-simp-chinese-formal",
+ "-moz-japanese-informal",
+ "-moz-japanese-formal",
+ "-moz-arabic-indic",
+ "-moz-persian",
+ "-moz-urdu",
+ "-moz-devanagari",
+ "-moz-gurmukhi",
+ "-moz-gujarati",
+ "-moz-oriya",
+ "-moz-kannada",
+ "-moz-malayalam",
+ "-moz-bengali",
+ "-moz-tamil",
+ "-moz-telugu",
+ "-moz-thai",
+ "-moz-lao",
+ "-moz-myanmar",
+ "-moz-khmer",
+ "-moz-hangul",
+ "-moz-hangul-consonant",
+ "-moz-ethiopic-halehame",
+ "-moz-ethiopic-numeric",
+ "-moz-ethiopic-halehame-am",
+ "-moz-ethiopic-halehame-ti-er",
+ "-moz-ethiopic-halehame-ti-et",
+ "other-style",
+ "inside",
+ "outside",
+ "\\32 style",
+ '"-"',
+ "'-'",
+ 'symbols("*" "\\2020" "\\2021" "\\A7")',
+ "symbols(cyclic '*' '\\2020' '\\2021' '\\A7')",
+ ],
+ invalid_values: [],
+ },
+ margin: {
+ domProp: "margin",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left",
+ ],
+ initial_values: ["0", "0px 0 0em", "0% 0px 0em 0pt"],
+ other_values: [
+ "3px 0",
+ "2em 4px 2pt",
+ "1em 2em 3px 4px",
+ "1em calc(2em + 3px) 4ex 5cm",
+ ],
+ invalid_values: ["1px calc(nonsense)", "1px red"],
+ unbalanced_values: ["1px calc("],
+ quirks_values: { 5: "5px", "3px 6px 2 5px": "3px 6px 2px 5px" },
+ },
+ "margin-bottom": {
+ domProp: "marginBottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "margin-left": {
+ domProp: "marginLeft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ ".5px",
+ "+32px",
+ "+.789px",
+ "-.328px",
+ "+0.56px",
+ "-0.974px",
+ "237px",
+ "-289px",
+ "-056px",
+ "1987.45px",
+ "-84.32px",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ quirks_values: { 5: "5px" },
+ },
+ "margin-right": {
+ domProp: "marginRight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "margin-top": {
+ domProp: "marginTop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "max-height": {
+ domProp: "maxHeight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "30px",
+ "50%",
+ "0",
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["auto"],
+ quirks_values: { 5: "5px" },
+ },
+ "max-width": {
+ domProp: "maxWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "30px",
+ "50%",
+ "0",
+ // these four keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["auto"],
+ quirks_values: { 5: "5px" },
+ },
+ "min-height": {
+ domProp: "minHeight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { display: "block" },
+ initial_values: ["auto", "0", "calc(0em)", "calc(-2px)"],
+ other_values: [
+ "30px",
+ "50%",
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ quirks_values: { 5: "5px" },
+ },
+ "min-width": {
+ domProp: "minWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { display: "block" },
+ initial_values: ["auto", "0", "calc(0em)", "calc(-2px)"],
+ other_values: [
+ "30px",
+ "50%",
+ // these four keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ quirks_values: { 5: "5px" },
+ },
+ "object-fit": {
+ domProp: "objectFit",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["fill"],
+ other_values: ["contain", "cover", "none", "scale-down"],
+ invalid_values: ["auto", "5px", "100%"],
+ },
+ "object-position": {
+ domProp: "objectPosition",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["50% 50%", "50%", "center", "center center"],
+ other_values: [
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ "0px 0px",
+ "right 20px top 60px",
+ "right 20px bottom 60px",
+ "left 20px top 60px",
+ "left 20px bottom 60px",
+ "right -50px top -50px",
+ "left -50px bottom -50px",
+ "right 20px top -50px",
+ "right -20px top 50px",
+ "right 3em bottom 10px",
+ "bottom 3em right 10px",
+ "top 3em right 10px",
+ "left 15px",
+ "10px top",
+ "left 20%",
+ "right 20%",
+ ],
+ invalid_values: [
+ "center 10px center 4px",
+ "center 10px center",
+ "top 20%",
+ "bottom 20%",
+ "50% left",
+ "top 50%",
+ "50% bottom 10%",
+ "right 10% 50%",
+ "left right",
+ "top bottom",
+ "left 10% right",
+ "top 20px bottom 20px",
+ "left left",
+ "20 20",
+ "left top 15px",
+ "left 10px top",
+ ],
+ },
+ opacity: {
+ domProp: "opacity",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: [
+ "1",
+ "17",
+ "397.376",
+ "3e1",
+ "3e+1",
+ "3e0",
+ "3e+0",
+ "3e-0",
+ "300%",
+ ],
+ other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%"],
+ invalid_values: ["0px", "1px"],
+ },
+ "-moz-orient": {
+ domProp: "MozOrient",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["inline"],
+ other_values: ["horizontal", "vertical", "block"],
+ invalid_values: ["none"],
+ },
+ outline: {
+ domProp: "outline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["outline-color", "outline-style", "outline-width"],
+ initial_values: [
+ "none",
+ "medium",
+ "thin",
+ // XXX Should be invert, but currently currentcolor.
+ //"invert", "none medium invert"
+ "currentColor",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "outline-color": {
+ domProp: "outlineColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"], // XXX should be invert
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "cc00ff",
+ ],
+ },
+ "outline-offset": {
+ domProp: "outlineOffset",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [
+ "0",
+ "0px",
+ "-0",
+ "calc(0px)",
+ "calc(3em + 2px - 2px - 3em)",
+ "calc(-0em)",
+ ],
+ other_values: [
+ "-3px",
+ "1em",
+ "calc(3em)",
+ "calc(7pt + 3 * 2em)",
+ "calc(-3px)",
+ ],
+ invalid_values: ["5%"],
+ },
+ "outline-style": {
+ domProp: "outlineStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ // XXX Should 'hidden' be the same as initial?
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ "auto",
+ ],
+ invalid_values: [],
+ },
+ "outline-width": {
+ domProp: "outlineWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ prerequisites: { "outline-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0px)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ overflow: {
+ domProp: "overflow",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ prerequisites: { display: "block", contain: "none" },
+ subproperties: ["overflow-x", "overflow-y"],
+ initial_values: ["visible"],
+ other_values: [
+ "auto",
+ "scroll",
+ "hidden",
+ "clip",
+ "auto auto",
+ "auto scroll",
+ "hidden scroll",
+ "auto hidden",
+ "clip clip",
+ "overlay",
+ "overlay overlay",
+ ],
+ invalid_values: [
+ "clip -moz-scrollbars-none",
+ "-moz-scrollbars-none",
+ "-moz-scrollbars-horizontal",
+ "-moz-scrollbars-vertical",
+ ],
+ },
+ "overflow-x": {
+ domProp: "overflowX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: {
+ display: "block",
+ "overflow-y": "visible",
+ contain: "none",
+ },
+ initial_values: ["visible"],
+ other_values: ["auto", "scroll", "hidden", "clip", "overlay"],
+ invalid_values: [],
+ },
+ "overflow-y": {
+ domProp: "overflowY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: {
+ display: "block",
+ "overflow-x": "visible",
+ contain: "none",
+ },
+ initial_values: ["visible"],
+ other_values: ["auto", "scroll", "hidden", "clip", "overlay"],
+ invalid_values: [],
+ },
+ "overflow-inline": {
+ domProp: "overflowInline",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: {
+ display: "block",
+ "overflow-block": "visible",
+ contain: "none",
+ },
+ initial_values: ["visible"],
+ other_values: ["auto", "scroll", "hidden", "clip"],
+ invalid_values: [],
+ },
+ "overflow-block": {
+ domProp: "overflowBlock",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ prerequisites: {
+ display: "block",
+ "overflow-inline": "visible",
+ contain: "none",
+ },
+ initial_values: ["visible"],
+ other_values: ["auto", "scroll", "hidden", "clip"],
+ invalid_values: [],
+ },
+ "overflow-clip-margin": {
+ domProp: "overflowClipMargin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["1px", "2em", "calc(10px + 1vh)"],
+ invalid_values: ["-10px"],
+ },
+ padding: {
+ domProp: "padding",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left",
+ ],
+ initial_values: [
+ "0",
+ "0px 0 0em",
+ "0% 0px 0em 0pt",
+ "calc(0px) calc(0em) calc(-2px) calc(-1%)",
+ ],
+ other_values: ["3px 0", "2em 4px 2pt", "1em 2em 3px 4px"],
+ invalid_values: ["1px calc(nonsense)", "1px red", "-1px"],
+ unbalanced_values: ["1px calc("],
+ quirks_values: { 5: "5px", "3px 6px 2 5px": "3px 6px 2px 5px" },
+ },
+ "padding-block": {
+ domProp: "paddingBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["padding-block-start", "padding-block-end"],
+ initial_values: ["0", "0px 0em"],
+ other_values: ["3px 0", "2% 4px", "1em", "calc(1px) calc(-1%)"],
+ invalid_values: ["1px calc(nonsense)", "1px red", "-1px", "auto", "none"],
+ unbalanced_values: ["1px calc("],
+ },
+ "padding-inline": {
+ domProp: "paddingInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["padding-inline-start", "padding-inline-end"],
+ initial_values: ["0", "0px 0em"],
+ other_values: ["3px 0", "2% 4px", "1em", "calc(1px) calc(-1%)"],
+ invalid_values: ["1px calc(nonsense)", "1px red", "-1px", "auto", "none"],
+ unbalanced_values: ["1px calc("],
+ },
+ "padding-bottom": {
+ domProp: "paddingBottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "padding-left": {
+ domProp: "paddingLeft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "padding-right": {
+ domProp: "paddingRight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "padding-top": {
+ domProp: "paddingTop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "page-break-after": {
+ domProp: "pageBreakAfter",
+ inherited: false,
+ type: CSS_TYPE_LEGACY_SHORTHAND,
+ alias_for: "break-after",
+ subproperties: ["break-after"],
+ initial_values: ["auto"],
+ other_values: ["always", "avoid", "left", "right"],
+ legacy_mapping: {
+ always: "page",
+ },
+ invalid_values: ["page", "column"],
+ },
+ "page-break-before": {
+ domProp: "pageBreakBefore",
+ inherited: false,
+ type: CSS_TYPE_LEGACY_SHORTHAND,
+ alias_for: "break-before",
+ subproperties: ["break-before"],
+ initial_values: ["auto"],
+ other_values: ["always", "avoid", "left", "right"],
+ legacy_mapping: {
+ always: "page",
+ },
+ invalid_values: ["page", "column"],
+ },
+ "break-after": {
+ domProp: "breakAfter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["always", "page", "avoid", "left", "right"],
+ invalid_values: [],
+ },
+ "break-before": {
+ domProp: "breakBefore",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["always", "page", "avoid", "left", "right"],
+ invalid_values: [],
+ },
+ "break-inside": {
+ domProp: "breakInside",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["avoid", "avoid-page", "avoid-column"],
+ invalid_values: ["left", "right", "always"],
+ },
+ "page-break-inside": {
+ domProp: "pageBreakInside",
+ inherited: false,
+ type: CSS_TYPE_LEGACY_SHORTHAND,
+ alias_for: "break-inside",
+ subproperties: ["break-inside"],
+ initial_values: ["auto"],
+ other_values: ["avoid"],
+ invalid_values: ["avoid-page", "avoid-column"],
+ },
+ "paint-order": {
+ domProp: "paintOrder",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["normal"],
+ other_values: [
+ "fill",
+ "fill stroke",
+ "fill stroke markers",
+ "stroke markers fill",
+ ],
+ invalid_values: ["fill stroke markers fill", "fill normal"],
+ },
+ "pointer-events": {
+ domProp: "pointerEvents",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ initial_values: ["auto"],
+ other_values: [
+ "visiblePainted",
+ "visibleFill",
+ "visibleStroke",
+ "visible",
+ "painted",
+ "fill",
+ "stroke",
+ "all",
+ "none",
+ ],
+ invalid_values: [],
+ },
+ position: {
+ domProp: "position",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["static"],
+ other_values: ["relative", "absolute", "fixed", "sticky"],
+ invalid_values: [],
+ },
+ quotes: {
+ domProp: "quotes",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "none",
+ "'\"' '\"'",
+ "'' ''",
+ '"\u201C" "\u201D" "\u2018" "\u2019"',
+ '"\\201C" "\\201D" "\\2018" "\\2019"',
+ ],
+ invalid_values: ["'\"'", '"" "" ""'],
+ },
+ right: {
+ domProp: "right",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "ruby-align": {
+ domProp: "rubyAlign",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["space-around"],
+ other_values: ["start", "center", "space-between"],
+ invalid_values: ["end", "1", "10px", "50%", "start center"],
+ },
+ "ruby-position": {
+ domProp: "rubyPosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ initial_values: ["alternate", "alternate over", "over alternate"],
+ other_values: ["over", "under", "alternate under", "under alternate"],
+ invalid_values: [
+ "left",
+ "right",
+ "auto",
+ "none",
+ "not_a_position",
+ "over left",
+ "right under",
+ "over under",
+ "alternate alternate",
+ "0",
+ "100px",
+ "50%",
+ ],
+ },
+ "scroll-behavior": {
+ domProp: "scrollBehavior",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["smooth"],
+ invalid_values: ["none", "1px"],
+ },
+ "scroll-snap-stop": {
+ domProp: "scrollSnapStop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["always"],
+ invalid_values: ["auto", "none", "1px"],
+ },
+ "scroll-snap-type": {
+ domProp: "scrollSnapType",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "both mandatory",
+ "y mandatory",
+ "inline proximity",
+ "both",
+ "x",
+ "y",
+ "block",
+ "inline",
+ ],
+ invalid_values: [
+ "auto",
+ "1px",
+ "x y",
+ "block mandatory inline",
+ "mandatory",
+ "proximity",
+ "mandatory inline",
+ "proximity both",
+ "mandatory x",
+ "proximity y",
+ "mandatory block",
+ "proximity mandatory",
+ ],
+ },
+ "scroll-snap-align": {
+ domProp: "scrollSnapAlign",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "start",
+ "end",
+ "center",
+ "start none",
+ "center end",
+ "start start",
+ ],
+ invalid_values: ["auto", "start invalid", "start end center"],
+ },
+ "scroll-margin": {
+ domProp: "scrollMargin",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "scroll-margin-top",
+ "scroll-margin-right",
+ "scroll-margin-bottom",
+ "scroll-margin-left",
+ ],
+ initial_values: ["0"],
+ other_values: [
+ "-10px",
+ "calc(2em + 3ex)",
+ "1px 2px",
+ "1px 2px 3px",
+ "1px 2px 3px 4px",
+ ],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px 3px 4px 5px"],
+ },
+ "scroll-margin-top": {
+ domProp: "scrollMarginTop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-right": {
+ domProp: "scrollMarginRight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-bottom": {
+ domProp: "scrollMarginBottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-left": {
+ domProp: "scrollMarginLeft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-inline": {
+ domProp: "scrollMarginInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-margin-inline-start", "scroll-margin-inline-end"],
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)", "1px 2px"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px 3px"],
+ },
+ "scroll-margin-inline-start": {
+ domProp: "scrollMarginInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-inline-end": {
+ domProp: "scrollMarginInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-block": {
+ domProp: "scrollMarginBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-margin-block-start", "scroll-margin-block-end"],
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)", "1px 2px"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px 3px"],
+ },
+ "scroll-margin-block-start": {
+ domProp: "scrollMarginBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-margin-block-end": {
+ domProp: "scrollMarginBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["0"],
+ other_values: ["-10px", "calc(2em + 3ex)"],
+ invalid_values: ["auto", "20%", "-30%", "1px 2px"],
+ },
+ "scroll-padding": {
+ domProp: "scrollPadding",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "scroll-padding-top",
+ "scroll-padding-right",
+ "scroll-padding-bottom",
+ "scroll-padding-left",
+ ],
+ initial_values: ["auto"],
+ other_values: [
+ "10px",
+ "0",
+ "20%",
+ "calc(2em + 3ex)",
+ "1px 2px",
+ "1px 2px 3%",
+ "1px 2px 3% 4px",
+ "1px auto",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-top": {
+ domProp: "scrollPaddingTop",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-right": {
+ domProp: "scrollPaddingRight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-bottom": {
+ domProp: "scrollPaddingBottom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-left": {
+ domProp: "scrollPaddingLeft",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-inline": {
+ domProp: "scrollPaddingInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-padding-inline-start", "scroll-padding-inline-end"],
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "10px",
+ "0",
+ "20%",
+ "calc(2em + 3ex)",
+ "1px 2px",
+ "1px auto",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-inline-start": {
+ domProp: "scrollPaddingInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-inline-end": {
+ domProp: "scrollPaddingInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-block": {
+ domProp: "scrollPaddingBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-padding-block-start", "scroll-padding-block-end"],
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "10px",
+ "0",
+ "20%",
+ "calc(2em + 3ex)",
+ "1px 2px",
+ "1px auto",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-block-start": {
+ domProp: "scrollPaddingBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "scroll-padding-block-end": {
+ domProp: "scrollPaddingBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ initial_values: ["auto"],
+ other_values: [
+ "0",
+ "10px",
+ "20%",
+ "calc(2em + 3ex)",
+ "calc(50% + 60px)",
+ "calc(-50px)",
+ ],
+ invalid_values: ["20", "-20px"],
+ },
+ "table-layout": {
+ domProp: "tableLayout",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["fixed"],
+ invalid_values: [],
+ },
+ "text-align": {
+ domProp: "textAlign",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ // don't know whether left and right are same as start
+ initial_values: ["start"],
+ other_values: ["center", "justify", "end", "match-parent"],
+ invalid_values: [
+ "true",
+ "true true",
+ "char",
+ "-moz-center-or-inherit",
+ "true left",
+ "unsafe left",
+ ],
+ },
+ "text-align-last": {
+ domProp: "textAlignLast",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["center", "justify", "start", "end", "left", "right"],
+ invalid_values: [],
+ },
+ "text-combine-upright": {
+ domProp: "textCombineUpright",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["none"],
+ other_values: ["all"],
+ invalid_values: [
+ "auto",
+ "all 2",
+ "none all",
+ "digits -3",
+ "digits 0",
+ "digits 12",
+ "none 3",
+ "digits 3.1415",
+ "digits3",
+ "digits 1",
+ "digits 3 all",
+ "digits foo",
+ "digits all",
+ "digits 3.0",
+ ],
+ },
+ "text-decoration": {
+ domProp: "textDecoration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ subproperties: [
+ "text-decoration-color",
+ "text-decoration-line",
+ "text-decoration-style",
+ "text-decoration-thickness",
+ ],
+ initial_values: ["none"],
+ other_values: [
+ "underline",
+ "overline",
+ "line-through",
+ "blink",
+ "blink line-through underline",
+ "underline overline line-through blink",
+ "underline red solid",
+ "underline #ff0000",
+ "solid underline",
+ "red underline",
+ "#ff0000 underline",
+ "dotted underline",
+ "solid underline 50px",
+ "underline 50px blue",
+ "50px dotted line-through purple",
+ "overline 2em",
+ "underline from-font",
+ "red from-font overline",
+ "5% underline blue",
+ "dotted line-through 25%",
+ ],
+ invalid_values: [
+ "none none",
+ "underline none",
+ "none underline",
+ "blink none",
+ "none blink",
+ "line-through blink line-through",
+ "underline overline line-through blink none",
+ "underline overline line-throuh blink blink",
+ "rgb(0, rubbish, 0) underline",
+ "from font blue underline",
+ ],
+ },
+ "text-decoration-color": {
+ domProp: "textDecorationColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "ff00ff",
+ ],
+ },
+ "text-decoration-line": {
+ domProp: "textDecorationLine",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["none"],
+ other_values: [
+ "underline",
+ "overline",
+ "line-through",
+ "blink",
+ "blink line-through underline",
+ "underline overline line-through blink",
+ ],
+ invalid_values: [
+ "none none",
+ "underline none",
+ "none underline",
+ "line-through blink line-through",
+ "underline overline line-through blink none",
+ "underline overline line-throuh blink blink",
+ ],
+ },
+ "text-decoration-style": {
+ domProp: "textDecorationStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["solid"],
+ other_values: ["double", "dotted", "dashed", "wavy", "-moz-none"],
+ invalid_values: [
+ "none",
+ "groove",
+ "ridge",
+ "inset",
+ "outset",
+ "solid dashed",
+ "wave",
+ ],
+ },
+ "text-decoration-thickness": {
+ domProp: "textDecorationThickness",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: [
+ "from-font",
+ "0",
+ "-14px",
+ "25px",
+ "100em",
+ "-45em",
+ "43%",
+ "-10%",
+ ],
+ invalid_values: ["13", "-25", "rubbish", ",./!@#$", "from font"],
+ },
+ "text-decoration-skip-ink": {
+ domProp: "textDecorationSkipInk",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ["none", "all"],
+ invalid_values: [
+ "13",
+ "15%",
+ "-1",
+ "0",
+ "otto",
+ "trash",
+ "non",
+ "nada",
+ "!@#$%^",
+ "none auto",
+ "auto none",
+ ],
+ },
+ "text-underline-offset": {
+ domProp: "textUnderlineOffset",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ["0", "-14px", "25px", "100em", "-45em", "43%", "-10%"],
+ invalid_values: [
+ "13",
+ "-25",
+ "rubbish",
+ ",./!@#$",
+ "from-font",
+ "from font",
+ ],
+ },
+ "text-underline-position": {
+ domProp: "textUnderlinePosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: [
+ "under",
+ "left",
+ "right",
+ "left under",
+ "under left",
+ "right under",
+ "under right",
+ "from-font",
+ "from-font left",
+ "from-font right",
+ "left from-font",
+ "right from-font",
+ ],
+ invalid_values: [
+ "none",
+ "auto from-font",
+ "auto under",
+ "under from-font",
+ "left right",
+ "right auto",
+ "0",
+ "1px",
+ "10%",
+ "from font",
+ ],
+ },
+ "text-emphasis": {
+ domProp: "textEmphasis",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { color: "black" },
+ subproperties: ["text-emphasis-style", "text-emphasis-color"],
+ initial_values: [
+ "none currentColor",
+ "currentColor none",
+ "none",
+ "currentColor",
+ "none black",
+ ],
+ other_values: [
+ "filled dot black",
+ "#f00 circle open",
+ "sesame filled rgba(0,0,255,0.5)",
+ "red",
+ "green none",
+ "currentColor filled",
+ "currentColor open",
+ ],
+ invalid_values: [
+ "filled black dot",
+ "filled filled red",
+ "open open circle #000",
+ "circle dot #f00",
+ "rubbish",
+ ],
+ },
+ "text-emphasis-color": {
+ domProp: "textEmphasisColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor", "black", "rgb(0,0,0)"],
+ other_values: ["red", "rgba(255,255,255,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "ff00ff",
+ "rgb(255,xxx,255)",
+ ],
+ },
+ "text-emphasis-position": {
+ domProp: "textEmphasisPosition",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["over right", "right over", "over"],
+ other_values: [
+ "over left",
+ "left over",
+ "under left",
+ "left under",
+ "under right",
+ "right under",
+ "under",
+ ],
+ invalid_values: [
+ "over over",
+ "left left",
+ "over right left",
+ "rubbish left",
+ "over rubbish",
+ ],
+ },
+ "text-emphasis-style": {
+ domProp: "textEmphasisStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "filled",
+ "open",
+ "dot",
+ "circle",
+ "double-circle",
+ "triangle",
+ "sesame",
+ "'#'",
+ "filled dot",
+ "filled circle",
+ "filled double-circle",
+ "filled triangle",
+ "filled sesame",
+ "dot filled",
+ "circle filled",
+ "double-circle filled",
+ "triangle filled",
+ "sesame filled",
+ "dot open",
+ "circle open",
+ "double-circle open",
+ "triangle open",
+ "sesame open",
+ ],
+ invalid_values: [
+ "rubbish",
+ "dot rubbish",
+ "rubbish dot",
+ "open rubbish",
+ "rubbish open",
+ "open filled",
+ "dot circle",
+ "open '#'",
+ "'#' filled",
+ "dot '#'",
+ "'#' circle",
+ "1",
+ "1 open",
+ "open 1",
+ ],
+ },
+ "text-indent": {
+ domProp: "textIndent",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0", "calc(3em - 5em + 2px + 2em - 2px)"],
+ other_values: [
+ "2em",
+ "5%",
+ "-10px",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "text-overflow": {
+ domProp: "textOverflow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ initial_values: ["clip"],
+ other_values: [
+ "ellipsis",
+ '""',
+ "''",
+ '"hello"',
+ "clip clip",
+ "ellipsis ellipsis",
+ "clip ellipsis",
+ 'clip ""',
+ '"hello" ""',
+ '"" ellipsis',
+ ],
+ invalid_values: [
+ "none",
+ "auto",
+ '"hello" inherit',
+ 'inherit "hello"',
+ "clip initial",
+ "initial clip",
+ "initial inherit",
+ "inherit initial",
+ "inherit none",
+ '"hello" unset',
+ 'unset "hello"',
+ "clip unset",
+ "unset clip",
+ "unset inherit",
+ "unset none",
+ "initial unset",
+ ],
+ },
+ "text-shadow": {
+ domProp: "textShadow",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ prerequisites: { color: "blue" },
+ initial_values: ["none"],
+ other_values: [
+ "2px 2px",
+ "2px 2px 1px",
+ "2px 2px green",
+ "2px 2px 1px green",
+ "green 2px 2px",
+ "green 2px 2px 1px",
+ "green 2px 2px, blue 1px 3px 4px",
+ "currentColor 3px 3px",
+ "blue 2px 2px, currentColor 1px 2px",
+ /* calc() values */
+ "2px 2px calc(-5px)" /* clamped */,
+ "calc(3em - 2px) 2px green",
+ "green calc(3em - 2px) 2px",
+ "2px calc(2px + 0.2em)",
+ "blue 2px calc(2px + 0.2em)",
+ "2px calc(2px + 0.2em) blue",
+ "calc(-2px) calc(-2px)",
+ "-2px -2px",
+ "calc(2px) calc(2px)",
+ "calc(2px) calc(2px) calc(2px)",
+ ],
+ invalid_values: [
+ "3% 3%",
+ "2px 2px -5px",
+ "2px 2px 2px 2px",
+ "2px 2px, none",
+ "none, 2px 2px",
+ "inherit, 2px 2px",
+ "2px 2px, inherit",
+ "2 2px",
+ "2px 2",
+ "2px 2px 2",
+ "2px 2px 2px 2",
+ "calc(2px) calc(2px) calc(2px) calc(2px)",
+ "3px 3px calc(3px + rubbish)",
+ "unset, 2px 2px",
+ "2px 2px, unset",
+ ],
+ },
+ "text-transform": {
+ domProp: "textTransform",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_marker: true,
+ initial_values: ["none"],
+ other_values: [
+ "capitalize",
+ "uppercase",
+ "lowercase",
+ "full-width",
+ "full-size-kana",
+ "uppercase full-width",
+ "full-size-kana capitalize",
+ "full-width lowercase full-size-kana",
+ ],
+ invalid_values: [
+ "none none",
+ "none uppercase",
+ "full-width none",
+ "uppercase lowercase",
+ "full-width capitalize full-width",
+ "uppercase full-width lowercase",
+ ],
+ },
+ "text-wrap": {
+ domProp: "textWrap",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["text-wrap-mode"],
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["wrap"],
+ other_values: ["nowrap"],
+ invalid_values: [],
+ },
+ "text-wrap-mode": {
+ domProp: "textWrapMode",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ applies_to_placeholder: true,
+ applies_to_marker: true,
+ initial_values: ["wrap"],
+ other_values: ["nowrap"],
+ invalid_values: ["none", "normal", "on", "off", "wrap nowrap"],
+ },
+ top: {
+ domProp: "top",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ transition: {
+ domProp: "transition",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ subproperties: [
+ "transition-property",
+ "transition-duration",
+ "transition-timing-function",
+ "transition-delay",
+ ],
+ initial_values: ["all 0s ease 0s", "all", "0s", "0s 0s", "ease"],
+ other_values: [
+ "all 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) 0s",
+ "width 1s linear 2s",
+ "width 1s 2s linear",
+ "width linear 1s 2s",
+ "linear width 1s 2s",
+ "linear 1s width 2s",
+ "linear 1s 2s width",
+ "1s width linear 2s",
+ "1s width 2s linear",
+ "1s 2s width linear",
+ "1s linear width 2s",
+ "1s linear 2s width",
+ "1s 2s linear width",
+ "width linear 1s",
+ "width 1s linear",
+ "linear width 1s",
+ "linear 1s width",
+ "1s width linear",
+ "1s linear width",
+ "1s 2s width",
+ "1s width 2s",
+ "width 1s 2s",
+ "1s 2s linear",
+ "1s linear 2s",
+ "linear 1s 2s",
+ "width 1s",
+ "1s width",
+ "linear 1s",
+ "1s linear",
+ "1s 2s",
+ "2s 1s",
+ "width",
+ "linear",
+ "1s",
+ "height",
+ "2s",
+ "ease-in-out",
+ "2s ease-in",
+ "opacity linear",
+ "ease-out 2s",
+ "2s color, 1s width, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)",
+ "1s \\32width linear 2s",
+ "1s -width linear 2s",
+ "1s -\\32width linear 2s",
+ "1s \\32 0width linear 2s",
+ "1s -\\32 0width linear 2s",
+ "1s \\2width linear 2s",
+ "1s -\\2width linear 2s",
+ "2s, 1s width",
+ "1s width, 2s",
+ "2s all, 1s width",
+ "1s width, 2s all",
+ "2s all, 1s width",
+ "2s width, 1s all",
+ "3s --my-color",
+ "none",
+ "none 2s linear 2s",
+ ],
+ invalid_values: [
+ "1s width, 2s none",
+ "2s none, 1s width",
+ "2s inherit",
+ "inherit 2s",
+ "2s width, 1s inherit",
+ "2s inherit, 1s width",
+ "2s initial",
+ "1s width,,2s color",
+ "1s width, ,2s color",
+ "bounce 1s cubic-bezier(0, rubbish) 2s",
+ "bounce 1s steps(rubbish) 2s",
+ "2s unset",
+ ],
+ },
+ "transition-delay": {
+ domProp: "transitionDelay",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["0s", "0ms"],
+ other_values: ["1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s"],
+ invalid_values: ["0", "0px"],
+ },
+ "transition-duration": {
+ domProp: "transitionDuration",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["0s", "0ms"],
+ other_values: ["1s", "250ms", "1s, 250ms, 2.3s"],
+ invalid_values: ["0", "0px", "-1ms", "-2s"],
+ },
+ "transition-property": {
+ domProp: "transitionProperty",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["all"],
+ other_values: [
+ "none",
+ "left",
+ "top",
+ "color",
+ "width, height, opacity",
+ "foobar",
+ "auto",
+ "\\32width",
+ "-width",
+ "-\\32width",
+ "\\32 0width",
+ "-\\32 0width",
+ "\\2width",
+ "-\\2width",
+ "all, all",
+ "all, color",
+ "color, all",
+ "--my-color",
+ ],
+ invalid_values: [
+ "none, none",
+ "color, none",
+ "none, color",
+ "inherit, color",
+ "color, inherit",
+ "initial, color",
+ "color, initial",
+ "none, color",
+ "color, none",
+ "unset, color",
+ "color, unset",
+ ],
+ },
+ "transition-timing-function": {
+ domProp: "transitionTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["ease"],
+ other_values: [
+ "cubic-bezier(0.25, 0.1, 0.25, 1.0)",
+ "linear",
+ "ease-in",
+ "ease-out",
+ "ease-in-out",
+ "linear, ease-in, cubic-bezier(0.1, 0.2, 0.8, 0.9)",
+ "cubic-bezier(0.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(0.25, 1.5, 0.75, -0.5)",
+ "step-start",
+ "step-end",
+ "steps(1)",
+ "steps(2, start)",
+ "steps(386)",
+ "steps(3, end)",
+ "steps(1, jump-start)",
+ "steps(1, jump-end)",
+ "steps(2, jump-none)",
+ "steps(1, jump-both)",
+ ],
+ invalid_values: [
+ "none",
+ "auto",
+ "cubic-bezier(0.25, 0.1, 0.25)",
+ "cubic-bezier(0.25, 0.1, 0.25, 0.25, 1.0)",
+ "cubic-bezier(-0.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(1.5, 0.5, 0.5, 0.5)",
+ "cubic-bezier(0.5, 0.5, -0.5, 0.5)",
+ "cubic-bezier(0.5, 0.5, 1.5, 0.5)",
+ "steps(2, step-end)",
+ "steps(0)",
+ "steps(-2)",
+ "steps(0, step-end, 1)",
+ "steps(0, jump-start)",
+ "steps(0, jump-end)",
+ "steps(1, jump-none)",
+ "steps(0, jump-both)",
+ ],
+ },
+ "unicode-bidi": {
+ domProp: "unicodeBidi",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["normal"],
+ other_values: [
+ "embed",
+ "bidi-override",
+ "isolate",
+ "plaintext",
+ "isolate-override",
+ ],
+ invalid_values: [
+ "auto",
+ "none",
+ "-moz-isolate",
+ "-moz-plaintext",
+ "-moz-isolate-override",
+ ],
+ },
+ "vertical-align": {
+ domProp: "verticalAlign",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["baseline"],
+ other_values: [
+ "sub",
+ "super",
+ "top",
+ "text-top",
+ "middle",
+ "bottom",
+ "text-bottom",
+ "-moz-middle-with-baseline",
+ "15%",
+ "3px",
+ "0.2em",
+ "-5px",
+ "-3%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "baseline-source": {
+ domProp: "baselineSource",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ["first", "last"],
+ invalid_values: [],
+ },
+ visibility: {
+ domProp: "visibility",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_cue: true,
+ initial_values: ["visible"],
+ other_values: ["hidden", "collapse"],
+ invalid_values: [],
+ },
+ "white-space": {
+ domProp: "whiteSpace",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["white-space-collapse", "text-wrap-mode"],
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["normal"],
+ other_values: [
+ "pre",
+ "nowrap",
+ "pre-wrap",
+ "pre-line",
+ "-moz-pre-space",
+ "break-spaces",
+ ],
+ invalid_values: [],
+ },
+ "white-space-collapse": {
+ domProp: "whiteSpaceCollapse",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["collapse"],
+ other_values: [
+ "preserve",
+ "preserve-breaks",
+ "preserve-spaces",
+ "break-spaces",
+ ],
+ invalid_values: ["normal", "auto"],
+ },
+ width: {
+ domProp: "width",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: {
+ // computed value tests for width test more with display:block
+ display: "block",
+ // add some margin to avoid the initial "auto" value getting
+ // resolved to the same length as the parent element.
+ "margin-left": "5px",
+ },
+ initial_values: [" auto"],
+ /* XXX these have prerequisites */
+ other_values: [
+ "15px",
+ "3em",
+ "15%",
+ // these three keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ // whether -moz-available computes to the initial value depends on
+ // the container size, and for the container size we're testing
+ // with, it does
+ // "-moz-available",
+ "3e1px",
+ "3e+1px",
+ "3e0px",
+ "3e+0px",
+ "3e-0px",
+ "3e-1px",
+ "3.2e1px",
+ "3.2e+1px",
+ "3.2e0px",
+ "3.2e+0px",
+ "3.2e-0px",
+ "3.2e-1px",
+ "3e1%",
+ "3e+1%",
+ "3e0%",
+ "3e+0%",
+ "3e-0%",
+ "3e-1%",
+ "3.2e1%",
+ "3.2e+1%",
+ "3.2e0%",
+ "3.2e+0%",
+ "3.2e-0%",
+ "3.2e-1%",
+ /* valid calc() values */
+ "calc(-2px)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(50% + 2px)",
+ "calc( 50% + 2px)",
+ "calc(50% + 2px )",
+ "calc( 50% + 2px )",
+ "calc(50% - -2px)",
+ "calc(2px - -50%)",
+ "calc(3*25px)",
+ "calc(3 *25px)",
+ "calc(3 * 25px)",
+ "calc(3* 25px)",
+ "calc(25px*3)",
+ "calc(25px *3)",
+ "calc(25px* 3)",
+ "calc(25px * 3)",
+ "calc(3*25px + 50%)",
+ "calc(50% - 3em + 2px)",
+ "calc(50% - (3em + 2px))",
+ "calc((50% - 3em) + 2px)",
+ "calc(2em)",
+ "calc(50%)",
+ "calc(50px/2)",
+ "calc(50px/(2 - 1))",
+ "calc(min(5px))",
+ "calc(min(5px,2em))",
+ "calc(max(5px))",
+ "calc(max(5px,2em))",
+ "min(5px)",
+ "min(5px,2em)",
+ "max(5px)",
+ "max(5px,2em)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: [
+ "none",
+ "-2px",
+ "content" /* (valid for 'flex-basis' but not 'width') */,
+ /* invalid calc() values */
+ "calc(50%+ 2px)",
+ "calc(50% +2px)",
+ "calc(50%+2px)",
+ "-moz-min()",
+ "calc(min())",
+ "-moz-max()",
+ "calc(max())",
+ "-moz-min(5px)",
+ "-moz-max(5px)",
+ "-moz-min(5px,2em)",
+ "-moz-max(5px,2em)",
+ /* If we ever support division by values, which is
+ * complicated for the reasons described in
+ * http://lists.w3.org/Archives/Public/www-style/2010Jan/0007.html
+ * , we should support all 4 of these as described in
+ * http://lists.w3.org/Archives/Public/www-style/2009Dec/0296.html
+ */
+ "calc((3em / 100%) * 3em)",
+ "calc(3em / 100% * 3em)",
+ "calc(3em * (3em / 100%))",
+ "calc(3em * 3em / 100%)",
+ ],
+ quirks_values: { 5: "5px" },
+ },
+ "will-change": {
+ domProp: "willChange",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "scroll-position",
+ "contents",
+ "transform",
+ "opacity",
+ "scroll-position, transform",
+ "transform, opacity",
+ "contents, transform",
+ "property-that-doesnt-exist-yet",
+ ],
+ invalid_values: [
+ "none",
+ "all",
+ "default",
+ "auto, scroll-position",
+ "scroll-position, auto",
+ "transform scroll-position",
+ ",",
+ "trailing,",
+ "will-change",
+ "transform, will-change",
+ ],
+ },
+ "word-break": {
+ domProp: "wordBreak",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["break-all", "keep-all"],
+ invalid_values: [],
+ },
+ "word-spacing": {
+ domProp: "wordSpacing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["normal", "0", "0px", "-0em", "calc(-0px)", "calc(0em)"],
+ other_values: [
+ "1em",
+ "2px",
+ "-3px",
+ "0%",
+ "50%",
+ "-120%",
+ "calc(1em)",
+ "calc(1em + 3px)",
+ "calc(15px / 2)",
+ "calc(15px/2)",
+ "calc(-2em)",
+ "calc(0% + 0px)",
+ "calc(-10%/2 - 1em)",
+ ],
+ invalid_values: [],
+ quirks_values: { 5: "5px" },
+ },
+ "overflow-wrap": {
+ domProp: "overflowWrap",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["break-word"],
+ invalid_values: [],
+ },
+ hyphens: {
+ domProp: "hyphens",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["manual"],
+ other_values: ["none", "auto"],
+ invalid_values: [],
+ },
+ "z-index": {
+ domProp: "zIndex",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* XXX requires position */
+ initial_values: ["auto"],
+ other_values: ["0", "3", "-7000", "12000"],
+ invalid_values: ["3.0", "17.5", "3e1"],
+ },
+ "clip-path": {
+ domProp: "clipPath",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "path(nonzero, 'M 10 10 h 100 v 100 h-100 v-100 z')",
+ "path(evenodd, 'M 10 10 h 100 v 100 h-100 v-100 z')",
+ "path('M10,30A20,20 0,0,1 50,30A20,20 0,0,1 90,30Q90,60 50,90Q10,60 10,30z')",
+ "url(#mypath)",
+ "url('404.svg#mypath')",
+ "url(#my-clip-path)",
+ "margin-box",
+ ]
+ .concat(basicShapeSVGBoxValues)
+ .concat(basicShapeOtherValues)
+ .concat(basicShapeOtherValuesWithFillRule)
+ .concat(basicShapeXywhRectValues),
+ invalid_values: [
+ "path(nonzero)",
+ "path(abs, 'M 10 10 L 10 10 z')",
+ "path(evenodd, '')",
+ "path('')",
+ ].concat(basicShapeInvalidValues),
+ unbalanced_values: basicShapeUnbalancedValues,
+ },
+ "clip-rule": {
+ domProp: "clipRule",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["nonzero"],
+ other_values: ["evenodd"],
+ invalid_values: [],
+ },
+ "color-interpolation": {
+ domProp: "colorInterpolation",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["sRGB"],
+ other_values: ["auto", "linearRGB"],
+ invalid_values: [],
+ },
+ "color-interpolation-filters": {
+ domProp: "colorInterpolationFilters",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["linearRGB"],
+ other_values: ["sRGB", "auto"],
+ invalid_values: [],
+ },
+ "dominant-baseline": {
+ domProp: "dominantBaseline",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "ideographic",
+ "alphabetic",
+ "hanging",
+ "mathematical",
+ "central",
+ "middle",
+ "text-after-edge",
+ "text-before-edge",
+ ],
+ invalid_values: [],
+ },
+ fill: {
+ domProp: "fill",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ prerequisites: { color: "blue" },
+ initial_values: ["black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)"],
+ other_values: [
+ "green",
+ "#fc3",
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "none",
+ "currentColor",
+ "context-fill",
+ "context-stroke",
+ ],
+ invalid_values: ["000000", "ff00ff", "url('#myserver') rgb(0, rubbish, 0)"],
+ },
+ "fill-opacity": {
+ domProp: "fillOpacity",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["1", "2.8", "1.000", "300%"],
+ other_values: [
+ "0",
+ "0.3",
+ "-7.3",
+ "-100%",
+ "50%",
+ "context-fill-opacity",
+ "context-stroke-opacity",
+ ],
+ invalid_values: [],
+ },
+ "fill-rule": {
+ domProp: "fillRule",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["nonzero"],
+ other_values: ["evenodd"],
+ invalid_values: [],
+ },
+ filter: {
+ domProp: "filter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ // SVG reference filters
+ "url(#my-filter)",
+ "url(#my-filter-1) url(#my-filter-2)",
+
+ // Filter functions
+ "opacity(50%) saturate(1.0)",
+ "invert(50%) sepia(0.1) brightness(90%)",
+
+ // Mixed SVG reference filters and filter functions
+ "grayscale(1) url(#my-filter-1)",
+ "url(#my-filter-1) brightness(50%) contrast(0.9)",
+
+ // Bad URLs
+ "url('badscheme:badurl')",
+ "blur(3px) url('badscheme:badurl') grayscale(50%)",
+
+ "blur()",
+ "blur(0)",
+ "blur(0px)",
+ "blur(0.5px)",
+ "blur(3px)",
+ "blur(100px)",
+ "blur(0.1em)",
+ "blur(calc(-1px))", // Parses and becomes blur(0px).
+ "blur(calc(0px))",
+ "blur(calc(5px))",
+ "blur(calc(2 * 5px))",
+
+ "brightness()",
+ "brightness(0)",
+ "brightness(50%)",
+ "brightness(1)",
+ "brightness(1.0)",
+ "brightness(2)",
+ "brightness(350%)",
+ "brightness(4.567)",
+
+ "contrast()",
+ "contrast(0)",
+ "contrast(50%)",
+ "contrast(1)",
+ "contrast(1.0)",
+ "contrast(2)",
+ "contrast(350%)",
+ "contrast(4.567)",
+
+ "drop-shadow(2px 2px)",
+ "drop-shadow(2px 2px 1px)",
+ "drop-shadow(2px 2px green)",
+ "drop-shadow(2px 2px 1px green)",
+ "drop-shadow(green 2px 2px)",
+ "drop-shadow(green 2px 2px 1px)",
+ "drop-shadow(currentColor 3px 3px)",
+ "drop-shadow(2px 2px calc(-5px))" /* clamped */,
+ "drop-shadow(calc(3em - 2px) 2px green)",
+ "drop-shadow(green calc(3em - 2px) 2px)",
+ "drop-shadow(2px calc(2px + 0.2em))",
+ "drop-shadow(blue 2px calc(2px + 0.2em))",
+ "drop-shadow(2px calc(2px + 0.2em) blue)",
+ "drop-shadow(calc(-2px) calc(-2px))",
+ "drop-shadow(-2px -2px)",
+ "drop-shadow(calc(2px) calc(2px))",
+ "drop-shadow(calc(2px) calc(2px) calc(2px))",
+
+ "grayscale()",
+ "grayscale(0)",
+ "grayscale(50%)",
+ "grayscale(1)",
+ "grayscale(1.0)",
+ "grayscale(2)",
+ "grayscale(350%)",
+ "grayscale(4.567)",
+
+ "hue-rotate()",
+ "hue-rotate(0)",
+ "hue-rotate(0deg)",
+ "hue-rotate(90deg)",
+ "hue-rotate(540deg)",
+ "hue-rotate(-90deg)",
+ "hue-rotate(10grad)",
+ "hue-rotate(1.6rad)",
+ "hue-rotate(-1.6rad)",
+ "hue-rotate(0.5turn)",
+ "hue-rotate(-2turn)",
+
+ "invert()",
+ "invert(0)",
+ "invert(50%)",
+ "invert(1)",
+ "invert(1.0)",
+ "invert(2)",
+ "invert(350%)",
+ "invert(4.567)",
+
+ "opacity()",
+ "opacity(0)",
+ "opacity(50%)",
+ "opacity(1)",
+ "opacity(1.0)",
+ "opacity(2)",
+ "opacity(350%)",
+ "opacity(4.567)",
+
+ "saturate()",
+ "saturate(0)",
+ "saturate(50%)",
+ "saturate(1)",
+ "saturate(1.0)",
+ "saturate(2)",
+ "saturate(350%)",
+ "saturate(4.567)",
+
+ "sepia()",
+ "sepia(0)",
+ "sepia(50%)",
+ "sepia(1)",
+ "sepia(1.0)",
+ "sepia(2)",
+ "sepia(350%)",
+ "sepia(4.567)",
+ ],
+ invalid_values: [
+ // none
+ "none none",
+ "url(#my-filter) none",
+ "none url(#my-filter)",
+ "blur(2px) none url(#my-filter)",
+
+ // Nested filters
+ "grayscale(invert(1.0))",
+
+ // Comma delimited filters
+ "url(#my-filter),",
+ "invert(50%), url(#my-filter), brightness(90%)",
+
+ // Test the following situations for each filter function:
+ // - Invalid number of arguments
+ // - Comma delimited arguments
+ // - Wrong argument type
+ // - Argument value out of range
+ "blur(3px 5px)",
+ "blur(3px,)",
+ "blur(3px, 5px)",
+ "blur(#my-filter)",
+ "blur(0.5)",
+ "blur(50%)",
+ "blur(calc(0))", // Unitless zero in calc is not a valid length.
+ "blur(calc(0.1))",
+ "blur(calc(10%))",
+ "blur(calc(20px - 5%))",
+ "blur(-3px)",
+
+ "brightness(0.5 0.5)",
+ "brightness(0.5,)",
+ "brightness(0.5, 0.5)",
+ "brightness(#my-filter)",
+ "brightness(10px)",
+ "brightness(-1)",
+
+ "contrast(0.5 0.5)",
+ "contrast(0.5,)",
+ "contrast(0.5, 0.5)",
+ "contrast(#my-filter)",
+ "contrast(10px)",
+ "contrast(-1)",
+
+ "drop-shadow()",
+ "drop-shadow(3% 3%)",
+ "drop-shadow(2px 2px -5px)",
+ "drop-shadow(2px 2px 2px 2px)",
+ "drop-shadow(2px 2px, none)",
+ "drop-shadow(none, 2px 2px)",
+ "drop-shadow(inherit, 2px 2px)",
+ "drop-shadow(2px 2px, inherit)",
+ "drop-shadow(2 2px)",
+ "drop-shadow(2px 2)",
+ "drop-shadow(2px 2px 2)",
+ "drop-shadow(2px 2px 2px 2)",
+ "drop-shadow(calc(2px) calc(2px) calc(2px) calc(2px))",
+ "drop-shadow(green 2px 2px, blue 1px 3px 4px)",
+ "drop-shadow(blue 2px 2px, currentColor 1px 2px)",
+ "drop-shadow(unset, 2px 2px)",
+ "drop-shadow(2px 2px, unset)",
+
+ "grayscale(0.5 0.5)",
+ "grayscale(0.5,)",
+ "grayscale(0.5, 0.5)",
+ "grayscale(#my-filter)",
+ "grayscale(10px)",
+ "grayscale(-1)",
+
+ "hue-rotate(0.5 0.5)",
+ "hue-rotate(0.5,)",
+ "hue-rotate(0.5, 0.5)",
+ "hue-rotate(#my-filter)",
+ "hue-rotate(10px)",
+ "hue-rotate(-1)",
+ "hue-rotate(45deg,)",
+
+ "invert(0.5 0.5)",
+ "invert(0.5,)",
+ "invert(0.5, 0.5)",
+ "invert(#my-filter)",
+ "invert(10px)",
+ "invert(-1)",
+
+ "opacity(0.5 0.5)",
+ "opacity(0.5,)",
+ "opacity(0.5, 0.5)",
+ "opacity(#my-filter)",
+ "opacity(10px)",
+ "opacity(-1)",
+
+ "saturate(0.5 0.5)",
+ "saturate(0.5,)",
+ "saturate(0.5, 0.5)",
+ "saturate(#my-filter)",
+ "saturate(10px)",
+ "saturate(-1)",
+
+ "sepia(0.5 0.5)",
+ "sepia(0.5,)",
+ "sepia(0.5, 0.5)",
+ "sepia(#my-filter)",
+ "sepia(10px)",
+ "sepia(-1)",
+ ],
+ },
+ "flood-color": {
+ domProp: "floodColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "blue" },
+ initial_values: ["black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)"],
+ other_values: ["green", "#fc3", "currentColor"],
+ invalid_values: [
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "000000",
+ "ff00ff",
+ ],
+ },
+ "flood-opacity": {
+ domProp: "floodOpacity",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["1", "2.8", "1.000", "300%"],
+ other_values: ["0", "0.3", "-7.3", "-100%", "50%"],
+ invalid_values: [],
+ },
+ "image-orientation": {
+ domProp: "imageOrientation",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["from-image"],
+ other_values: ["none"],
+ invalid_values: ["0", "0deg"],
+ },
+ "image-rendering": {
+ domProp: "imageRendering",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "optimizeSpeed",
+ "optimizeQuality",
+ "-moz-crisp-edges",
+ "crisp-edges",
+ "smooth",
+ "pixelated",
+ ],
+ invalid_values: [],
+ },
+ isolation: {
+ domProp: "isolation",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["isolate"],
+ invalid_values: [],
+ },
+ "lighting-color": {
+ domProp: "lightingColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "blue" },
+ initial_values: [
+ "white",
+ "#fff",
+ "#ffffff",
+ "rgb(255,255,255)",
+ "rgba(255,255,255,1.0)",
+ "rgba(255,255,255,42.0)",
+ ],
+ other_values: ["green", "#fc3", "currentColor"],
+ invalid_values: [
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "000000",
+ "ff00ff",
+ ],
+ },
+ marker: {
+ domProp: "marker",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["marker-start", "marker-mid", "marker-end"],
+ initial_values: ["none"],
+ other_values: ["url(#mysym)"],
+ invalid_values: [
+ "none none",
+ "url(#mysym) url(#mysym)",
+ "none url(#mysym)",
+ "url(#mysym) none",
+ ],
+ },
+ "marker-end": {
+ domProp: "markerEnd",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["url(#mysym)"],
+ invalid_values: [],
+ },
+ "marker-mid": {
+ domProp: "markerMid",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["url(#mysym)"],
+ invalid_values: [],
+ },
+ "marker-start": {
+ domProp: "markerStart",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["url(#mysym)"],
+ invalid_values: [],
+ },
+ "mix-blend-mode": {
+ domProp: "mixBlendMode",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "multiply",
+ "screen",
+ "overlay",
+ "darken",
+ "lighten",
+ "color-dodge",
+ "color-burn",
+ "hard-light",
+ "soft-light",
+ "difference",
+ "exclusion",
+ "hue",
+ "saturation",
+ "color",
+ "luminosity",
+ "plus-lighter",
+ ],
+ invalid_values: [],
+ },
+ "shape-image-threshold": {
+ domProp: "shapeImageThreshold",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["0", "0.0000", "-3", "0%", "-100%"],
+ other_values: [
+ "0.4",
+ "1",
+ "17",
+ "397.376",
+ "3e1",
+ "3e+1",
+ "3e-1",
+ "3e0",
+ "3e+0",
+ "3e-0",
+ "50%",
+ "300%",
+ ],
+ invalid_values: ["0px", "1px", "default", "auto"],
+ },
+ "shape-margin": {
+ domProp: "shapeMargin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["0"],
+ other_values: ["2px", "2%", "1em", "calc(1px + 1em)", "calc(1%)"],
+ invalid_values: ["-1px", "auto", "none", "1px 1px", "-1%"],
+ },
+ "shape-outside": {
+ domProp: "shapeOutside",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["none"],
+ other_values: ["url(#my-shape-outside)", "margin-box"].concat(
+ basicShapeOtherValues,
+ basicShapeOtherValuesWithFillRule,
+ validNonUrlImageValues
+ ),
+ invalid_values: [].concat(
+ basicShapeSVGBoxValues,
+ basicShapeInvalidValues,
+ invalidNonUrlImageValues
+ ),
+ unbalanced_values: [].concat(
+ basicShapeUnbalancedValues,
+ unbalancedGradientAndElementValues
+ ),
+ },
+ "shape-rendering": {
+ domProp: "shapeRendering",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["optimizeSpeed", "crispEdges", "geometricPrecision"],
+ invalid_values: [],
+ },
+ "stop-color": {
+ domProp: "stopColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "blue" },
+ initial_values: ["black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)"],
+ other_values: ["green", "#fc3", "currentColor"],
+ invalid_values: [
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "000000",
+ "ff00ff",
+ ],
+ },
+ "stop-opacity": {
+ domProp: "stopOpacity",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["1", "2.8", "1.000", "300%"],
+ other_values: ["0", "0.3", "-7.3", "-100%", "50%"],
+ invalid_values: [],
+ },
+ stroke: {
+ domProp: "stroke",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["none"],
+ other_values: [
+ "black",
+ "#000",
+ "#000000",
+ "rgb(0,0,0)",
+ "rgba(0,0,0,1)",
+ "green",
+ "#fc3",
+ "url('#myserver')",
+ "url(foo.svg#myserver)",
+ 'url("#myserver") green',
+ "currentColor",
+ "context-fill",
+ "context-stroke",
+ ],
+ invalid_values: ["000000", "ff00ff"],
+ },
+ "stroke-dasharray": {
+ domProp: "strokeDasharray",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["none"],
+ other_values: [
+ "5px,3px,2px",
+ "5px 3px 2px",
+ " 5px ,3px\t, 2px ",
+ "1px",
+ "5%",
+ "3em",
+ "0.0002",
+ "context-value",
+ ],
+ invalid_values: ["-5px,3px,2px", "5px,3px,-2px"],
+ },
+ "stroke-dashoffset": {
+ domProp: "strokeDashoffset",
+ inherited: true,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0", "-0px", "0em"],
+ other_values: ["3px", "3%", "1em", "0.0002", "context-value"],
+ invalid_values: [],
+ },
+ "stroke-linecap": {
+ domProp: "strokeLinecap",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["butt"],
+ other_values: ["round", "square"],
+ invalid_values: [],
+ },
+ "stroke-linejoin": {
+ domProp: "strokeLinejoin",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["miter"],
+ other_values: ["round", "bevel"],
+ invalid_values: [],
+ },
+ "stroke-miterlimit": {
+ domProp: "strokeMiterlimit",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["4"],
+ other_values: ["0", "0.9", "1", "7", "5000", "1.1"],
+ invalid_values: ["-1", "3px", "-0.3"],
+ },
+ "stroke-opacity": {
+ domProp: "strokeOpacity",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["1", "2.8", "1.000", "300%"],
+ other_values: [
+ "0",
+ "0.3",
+ "-7.3",
+ "-100%",
+ "50%",
+ "context-fill-opacity",
+ "context-stroke-opacity",
+ ],
+ invalid_values: [],
+ },
+ "stroke-width": {
+ domProp: "strokeWidth",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["1px"],
+ other_values: [
+ "0",
+ "0px",
+ "-0em",
+ "17px",
+ "0.2em",
+ "0.0002",
+ "context-value",
+ ],
+ invalid_values: ["-0.1px", "-3px"],
+ },
+ x: {
+ domProp: "x",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["-1em", "17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "context-value", "0.0002"],
+ },
+ y: {
+ domProp: "y",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["-1em", "17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "context-value", "0.0002"],
+ },
+ cx: {
+ domProp: "cx",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["-1em", "17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "context-value", "0.0002"],
+ },
+ cy: {
+ domProp: "cy",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["-1em", "17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "context-value", "0.0002"],
+ },
+ r: {
+ domProp: "r",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0px"],
+ other_values: ["17px", "0.2em", "23.4%"],
+ invalid_values: ["auto", "-1", "-1.5px", "0.0002"],
+ },
+ rx: {
+ domProp: "rx",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["17px", "0.2em", "23.4%"],
+ invalid_values: ["hello", "-12px", "0.0002"],
+ },
+ ry: {
+ domProp: "ry",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["17px", "0.2em", "23.4%"],
+ invalid_values: ["hello", "-1.3px", "0.0002"],
+ },
+ "text-anchor": {
+ domProp: "textAnchor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["start"],
+ other_values: ["middle", "end"],
+ invalid_values: [],
+ },
+ "text-rendering": {
+ domProp: "textRendering",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["auto"],
+ other_values: ["optimizeSpeed", "optimizeLegibility", "geometricPrecision"],
+ invalid_values: [],
+ },
+ "vector-effect": {
+ domProp: "vectorEffect",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ initial_values: ["none"],
+ other_values: ["non-scaling-stroke"],
+ invalid_values: [],
+ },
+ "-moz-window-dragging": {
+ domProp: "MozWindowDragging",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["default"],
+ other_values: ["drag", "no-drag"],
+ invalid_values: ["none"],
+ },
+ "accent-color": {
+ domProp: "accentColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { color: "black" },
+ initial_values: ["auto"],
+ other_values: [
+ "currentcolor",
+ "black",
+ "green",
+ "transparent",
+ "rgba(128,128,128,.5)",
+ "#123",
+ ],
+ invalid_values: ["#0", "#00", "#00000", "cc00ff"],
+ },
+ "align-content": {
+ domProp: "alignContent",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "start",
+ "end",
+ "flex-start",
+ "flex-end",
+ "center",
+ "space-between",
+ "space-around",
+ "space-evenly",
+ "first baseline",
+ "last baseline",
+ "baseline",
+ "stretch",
+ "safe start",
+ "unsafe end",
+ "safe end",
+ ],
+ invalid_values: [
+ "none",
+ "5",
+ "self-end",
+ "safe",
+ "normal unsafe",
+ "unsafe safe",
+ "safe baseline",
+ "baseline unsafe",
+ "baseline end",
+ "end normal",
+ "safe end unsafe start",
+ "safe end unsafe",
+ "normal safe start",
+ "unsafe end start",
+ "end start safe",
+ "space-between unsafe",
+ "stretch safe",
+ "auto",
+ "first",
+ "last",
+ "left",
+ "right",
+ ],
+ },
+ "align-items": {
+ domProp: "alignItems",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "end",
+ "flex-start",
+ "flex-end",
+ "self-start",
+ "self-end",
+ "center",
+ "stretch",
+ "first baseline",
+ "last baseline",
+ "baseline",
+ "start",
+ "unsafe center",
+ "safe center",
+ ],
+ invalid_values: [
+ "space-between",
+ "abc",
+ "5%",
+ "legacy",
+ "legacy end",
+ "end legacy",
+ "unsafe",
+ "unsafe baseline",
+ "normal unsafe",
+ "safe left unsafe",
+ "safe stretch",
+ "end end",
+ "auto",
+ "left",
+ "right",
+ ],
+ },
+ "align-self": {
+ domProp: "alignSelf",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "normal",
+ "start",
+ "flex-start",
+ "flex-end",
+ "center",
+ "stretch",
+ "first baseline",
+ "last baseline",
+ "baseline",
+ "unsafe center",
+ "self-start",
+ "safe self-end",
+ ],
+ invalid_values: [
+ "space-between",
+ "abc",
+ "30px",
+ "stretch safe",
+ "safe",
+ "left",
+ "right",
+ ],
+ },
+ "justify-content": {
+ domProp: "justifyContent",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "start",
+ "end",
+ "flex-start",
+ "flex-end",
+ "center",
+ "left",
+ "right",
+ "space-between",
+ "space-around",
+ "space-evenly",
+ "stretch",
+ "safe start",
+ "unsafe end",
+ "safe end",
+ ],
+ invalid_values: [
+ "30px",
+ "5%",
+ "self-end",
+ "safe",
+ "normal unsafe",
+ "unsafe safe",
+ "safe baseline",
+ "baseline unsafe",
+ "baseline end",
+ "normal end",
+ "safe end unsafe start",
+ "safe end unsafe",
+ "normal safe start",
+ "unsafe end start",
+ "end start safe",
+ "space-around unsafe",
+ "safe stretch",
+ "auto",
+ "first",
+ "last",
+ ],
+ },
+ "justify-items": {
+ domProp: "justifyItems",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["legacy", "normal"],
+ other_values: [
+ "end",
+ "flex-start",
+ "flex-end",
+ "self-start",
+ "self-end",
+ "center",
+ "left",
+ "right",
+ "stretch",
+ "start",
+ "legacy left",
+ "right legacy",
+ "legacy center",
+ "unsafe right",
+ "unsafe left",
+ "safe right",
+ "safe center",
+ ],
+ invalid_values: [
+ "auto",
+ "space-between",
+ "abc",
+ "30px",
+ "legacy start",
+ "end legacy",
+ "legacy baseline",
+ "legacy legacy",
+ "unsafe",
+ "safe legacy left",
+ "legacy left safe",
+ "legacy safe left",
+ "safe left legacy",
+ "legacy left legacy",
+ "baseline unsafe",
+ "safe unsafe",
+ "safe left unsafe",
+ "safe stretch",
+ "last",
+ ],
+ },
+ "justify-self": {
+ domProp: "justifySelf",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "normal",
+ "start",
+ "end",
+ "flex-start",
+ "flex-end",
+ "self-start",
+ "self-end",
+ "center",
+ "left",
+ "right",
+ "stretch",
+ "unsafe left",
+ "baseline",
+ "last baseline",
+ "first baseline",
+ "unsafe right",
+ "safe right",
+ "safe center",
+ ],
+ invalid_values: [
+ "space-between",
+ "abc",
+ "30px",
+ "none",
+ "first",
+ "last",
+ "legacy left",
+ "right legacy",
+ "baseline first",
+ "baseline last",
+ ],
+ },
+ "place-content": {
+ domProp: "placeContent",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["align-content", "justify-content"],
+ initial_values: ["normal"],
+ other_values: [
+ "normal start",
+ "baseline end",
+ "end end",
+ "space-between flex-end",
+ "last baseline start",
+ "space-evenly",
+ "flex-start",
+ "end",
+ "unsafe start",
+ "safe center",
+ "baseline",
+ "last baseline",
+ ],
+ invalid_values: [
+ "none",
+ "center safe",
+ "right / end",
+ "left",
+ "right",
+ "left left",
+ "right right",
+ ],
+ },
+ "place-items": {
+ domProp: "placeItems",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["align-items", "justify-items"],
+ initial_values: ["normal"],
+ other_values: [
+ "normal center",
+ "baseline end",
+ "end legacy",
+ "end",
+ "flex-end left",
+ "last baseline start",
+ "stretch",
+ "safe center",
+ "end legacy left",
+ ],
+ invalid_values: [
+ "space-between",
+ "start space-evenly",
+ "none",
+ "end/end",
+ "center safe",
+ "auto start",
+ "left",
+ "right",
+ "left left",
+ "right right",
+ ],
+ },
+ "place-self": {
+ domProp: "placeSelf",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["align-self", "justify-self"],
+ initial_values: ["auto"],
+ other_values: [
+ "normal start",
+ "first baseline end",
+ "end auto",
+ "end",
+ "normal",
+ "baseline start",
+ "baseline",
+ "start baseline",
+ "self-end left",
+ "last baseline start",
+ "stretch",
+ ],
+ invalid_values: [
+ "space-between",
+ "start space-evenly",
+ "none",
+ "end safe",
+ "auto legacy left",
+ "legacy left",
+ "auto/auto",
+ "left",
+ "right",
+ "left left",
+ "right right",
+ ],
+ },
+ flex: {
+ domProp: "flex",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["flex-grow", "flex-shrink", "flex-basis"],
+ initial_values: ["0 1 auto", "auto 0 1", "0 auto", "auto 0"],
+ other_values: [
+ "none",
+ "1",
+ "0",
+ "0 1",
+ "0.5",
+ "1.2 3.4",
+ "0 0 0",
+ "0 0 0px",
+ "0px 0 0",
+ "5px 0 0",
+ "2 auto",
+ "auto 4",
+ "auto 5.6 7.8",
+ "max-content",
+ "1 max-content",
+ "1 2 max-content",
+ "max-content 1",
+ "max-content 1 2",
+ "-0",
+ ],
+ invalid_values: [
+ "1 2px 3",
+ "1 auto 3",
+ "1px 2 3px",
+ "1px 2 3 4px",
+ "-1",
+ "1 -1",
+ "0 1 calc(0px + rubbish)",
+ ],
+ },
+ "flex-basis": {
+ domProp: "flexBasis",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [" auto"],
+ // NOTE: Besides "content", this is cribbed directly from the "width"
+ // chunk, since this property takes the exact same values as width
+ // (plus 'content' & with different semantics on 'auto').
+ // XXXdholbert (Maybe these should get separated out into
+ // a reusable array defined at the top of this file?)
+ other_values: [
+ "content",
+ "15px",
+ "3em",
+ "15%",
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // valid calc() values
+ "calc(-2px)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(50% + 2px)",
+ "calc( 50% + 2px)",
+ "calc(50% + 2px )",
+ "calc( 50% + 2px )",
+ "calc(50% - -2px)",
+ "calc(2px - -50%)",
+ "calc(3*25px)",
+ "calc(3 *25px)",
+ "calc(3 * 25px)",
+ "calc(3* 25px)",
+ "calc(25px*3)",
+ "calc(25px *3)",
+ "calc(25px* 3)",
+ "calc(25px * 3)",
+ "calc(3*25px + 50%)",
+ "calc(50% - 3em + 2px)",
+ "calc(50% - (3em + 2px))",
+ "calc((50% - 3em) + 2px)",
+ "calc(2em)",
+ "calc(50%)",
+ "calc(50px/2)",
+ "calc(50px/(2 - 1))",
+ "calc(min(5px))",
+ "calc(min(5px,2em))",
+ "calc(max(5px))",
+ "calc(max(5px,2em))",
+ "min(5px)",
+ "min(5px,2em)",
+ "max(5px)",
+ "max(5px,2em)",
+ ],
+ invalid_values: [
+ "none",
+ "-2px",
+ // invalid calc() values
+ "calc(50%+ 2px)",
+ "calc(50% +2px)",
+ "calc(50%+2px)",
+ "-moz-min()",
+ "calc(min())",
+ "-moz-max()",
+ "calc(max())",
+ "-moz-min(5px)",
+ "-moz-max(5px)",
+ "-moz-min(5px,2em)",
+ "-moz-max(5px,2em)",
+ // If we ever support division by values, which is
+ // complicated for the reasons described in
+ // http://lists.w3.org/Archives/Public/www-style/2010Jan/0007.html
+ // , we should support all 4 of these as described in
+ // http://lists.w3.org/Archives/Public/www-style/2009Dec/0296.html
+ "calc((3em / 100%) * 3em)",
+ "calc(3em / 100% * 3em)",
+ "calc(3em * (3em / 100%))",
+ "calc(3em * 3em / 100%)",
+ ],
+ },
+ "flex-direction": {
+ domProp: "flexDirection",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["row"],
+ other_values: ["row-reverse", "column", "column-reverse"],
+ invalid_values: ["10px", "30%", "justify", "column wrap"],
+ },
+ "flex-flow": {
+ domProp: "flexFlow",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["flex-direction", "flex-wrap"],
+ initial_values: ["row nowrap", "nowrap row", "row", "nowrap"],
+ other_values: [
+ // only specifying one property:
+ "column",
+ "wrap",
+ "wrap-reverse",
+ // specifying both properties, 'flex-direction' first:
+ "row wrap",
+ "row wrap-reverse",
+ "column wrap",
+ "column wrap-reverse",
+ // specifying both properties, 'flex-wrap' first:
+ "wrap row",
+ "wrap column",
+ "wrap-reverse row",
+ "wrap-reverse column",
+ ],
+ invalid_values: [
+ // specifying flex-direction twice (invalid):
+ "row column",
+ "row column nowrap",
+ "row nowrap column",
+ "nowrap row column",
+ // specifying flex-wrap twice (invalid):
+ "nowrap wrap-reverse",
+ "nowrap wrap-reverse row",
+ "nowrap row wrap-reverse",
+ "row nowrap wrap-reverse",
+ // Invalid data-type / invalid keyword type:
+ "1px",
+ "5%",
+ "justify",
+ "none",
+ ],
+ },
+ "flex-grow": {
+ domProp: "flexGrow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["3", "1", "1.0", "2.5", "123"],
+ invalid_values: ["0px", "-5", "1%", "3em", "stretch", "auto"],
+ },
+ "flex-shrink": {
+ domProp: "flexShrink",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["1"],
+ other_values: ["3", "0", "0.0", "2.5", "123"],
+ invalid_values: ["0px", "-5", "1%", "3em", "stretch", "auto"],
+ },
+ "flex-wrap": {
+ domProp: "flexWrap",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["nowrap"],
+ other_values: ["wrap", "wrap-reverse"],
+ invalid_values: ["10px", "30%", "justify", "column wrap", "auto"],
+ },
+ order: {
+ domProp: "order",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["1", "99999", "-1", "-50"],
+ invalid_values: ["0px", "1.0", "1.", "1%", "0.2", "3em", "stretch"],
+ },
+
+ // Aliases
+ "word-wrap": {
+ domProp: "wordWrap",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "overflow-wrap",
+ subproperties: ["overflow-wrap"],
+ },
+ "-moz-tab-size": {
+ domProp: "MozTabSize",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "tab-size",
+ subproperties: ["tab-size"],
+ },
+ "-moz-border-image": {
+ domProp: "MozBorderImage",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-image",
+ subproperties: [
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat",
+ ],
+ },
+ "-moz-font-feature-settings": {
+ domProp: "MozFontFeatureSettings",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "font-feature-settings",
+ subproperties: ["font-feature-settings"],
+ },
+ "-moz-font-language-override": {
+ domProp: "MozFontLanguageOverride",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "font-language-override",
+ subproperties: ["font-language-override"],
+ },
+ "-moz-hyphens": {
+ domProp: "MozHyphens",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "hyphens",
+ subproperties: ["hyphens"],
+ },
+ // vertical text properties
+ "writing-mode": {
+ domProp: "writingMode",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["horizontal-tb", "lr", "lr-tb", "rl", "rl-tb"],
+ other_values: [
+ "vertical-lr",
+ "vertical-rl",
+ "sideways-rl",
+ "sideways-lr",
+ "tb",
+ "tb-rl",
+ ],
+ invalid_values: ["10px", "30%", "justify", "auto", "1em"],
+ },
+ "text-orientation": {
+ domProp: "textOrientation",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["mixed"],
+ other_values: [
+ "upright",
+ "sideways",
+ "sideways-right",
+ ] /* sideways-right alias for backward compatibility */,
+ invalid_values: [
+ "none",
+ "3em",
+ "sideways-left",
+ ] /* sideways-left removed from CSS Writing Modes */,
+ },
+ "block-size": {
+ domProp: "blockSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["auto"],
+ prerequisites: { display: "block" },
+ other_values: [
+ "15px",
+ "3em",
+ "15%",
+ // These keywords are treated as initial value.
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ },
+ "border-block": {
+ domProp: "borderBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-block-start-color",
+ "border-block-start-style",
+ "border-block-start-width",
+ "border-block-end-color",
+ "border-block-end-style",
+ "border-block-end-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-block-end": {
+ domProp: "borderBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-block-end-color",
+ "border-block-end-style",
+ "border-block-end-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-block-color": {
+ domProp: "borderBlockColor",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-block-start-color", "border-block-end-color"],
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5) blue", "blue transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-block-end-color": {
+ domProp: "borderBlockEndColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-block-style": {
+ domProp: "borderBlockStyle",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-block-start-style", "border-block-end-style"],
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed solid",
+ "solid dotted",
+ "double double",
+ "inset outset",
+ "inset double",
+ "none groove",
+ "ridge none",
+ ],
+ invalid_values: [],
+ },
+ "border-block-end-style": {
+ domProp: "borderBlockEndStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-block-width": {
+ domProp: "borderBlockWidth",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["border-block-start-width", "border-block-end-width"],
+ prerequisites: { "border-style": "solid" },
+ initial_values: ["medium", "3px", "medium medium"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(2px) thin",
+ "calc(-2px)",
+ "calc(-2px) thick",
+ "calc(0em)",
+ "medium calc(0em)",
+ "calc(0px)",
+ "1px calc(0px)",
+ "calc(5em)",
+ "1em calc(5em)",
+ ],
+ invalid_values: ["5%", "5", "5 thin", "thin 5%", "blue", "solid"],
+ },
+ "border-block-end-width": {
+ domProp: "borderBlockEndWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { "border-block-end-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ "border-block-start": {
+ domProp: "borderBlockStart",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "border-block-start-color",
+ "border-block-start-style",
+ "border-block-start-width",
+ ],
+ initial_values: [
+ "none",
+ "medium",
+ "currentColor",
+ "thin",
+ "none medium currentcolor",
+ ],
+ other_values: [
+ "solid",
+ "green",
+ "medium solid",
+ "green solid",
+ "10px solid",
+ "thick solid",
+ "5px green none",
+ ],
+ invalid_values: ["5%", "5", "5 solid green"],
+ },
+ "border-block-start-color": {
+ domProp: "borderBlockStartColor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ initial_values: ["currentColor"],
+ other_values: ["green", "rgba(255,128,0,0.5)", "transparent"],
+ invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"],
+ },
+ "border-block-start-style": {
+ domProp: "borderBlockStartStyle",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX hidden is sometimes the same as initial */
+ initial_values: ["none"],
+ other_values: [
+ "solid",
+ "dashed",
+ "dotted",
+ "double",
+ "outset",
+ "inset",
+ "groove",
+ "ridge",
+ ],
+ invalid_values: [],
+ },
+ "border-block-start-width": {
+ domProp: "borderBlockStartWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ prerequisites: { "border-block-start-style": "solid" },
+ initial_values: ["medium", "3px", "calc(4px - 1px)"],
+ other_values: [
+ "thin",
+ "thick",
+ "1px",
+ "2em",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(0em)",
+ "calc(0px)",
+ "calc(5em)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 5em)",
+ ],
+ invalid_values: ["5%", "5"],
+ },
+ "-moz-border-end": {
+ domProp: "MozBorderEnd",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-inline-end",
+ subproperties: [
+ "-moz-border-end-color",
+ "-moz-border-end-style",
+ "-moz-border-end-width",
+ ],
+ },
+ "-moz-border-end-color": {
+ domProp: "MozBorderEndColor",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-end-color",
+ subproperties: ["border-inline-end-color"],
+ },
+ "-moz-border-end-style": {
+ domProp: "MozBorderEndStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-end-style",
+ subproperties: ["border-inline-end-style"],
+ },
+ "-moz-border-end-width": {
+ domProp: "MozBorderEndWidth",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-end-width",
+ subproperties: ["border-inline-end-width"],
+ },
+ "-moz-border-start": {
+ domProp: "MozBorderStart",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-inline-start",
+ subproperties: [
+ "-moz-border-start-color",
+ "-moz-border-start-style",
+ "-moz-border-start-width",
+ ],
+ },
+ "-moz-border-start-color": {
+ domProp: "MozBorderStartColor",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-start-color",
+ subproperties: ["border-inline-start-color"],
+ },
+ "-moz-border-start-style": {
+ domProp: "MozBorderStartStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-start-style",
+ subproperties: ["border-inline-start-style"],
+ },
+ "-moz-border-start-width": {
+ domProp: "MozBorderStartWidth",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-inline-start-width",
+ subproperties: ["border-inline-start-width"],
+ },
+ "inline-size": {
+ domProp: "inlineSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["auto"],
+ prerequisites: {
+ display: "block",
+ // add some margin to avoid the initial "auto" value getting
+ // resolved to the same length as the parent element.
+ "margin-left": "5px",
+ },
+ other_values: [
+ "15px",
+ "3em",
+ "15%",
+ // these three keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ // whether -moz-available computes to the initial value depends on
+ // the container size, and for the container size we're testing
+ // with, it does
+ // "-moz-available",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none"],
+ },
+ "margin-block": {
+ domProp: "marginBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["margin-block-start", "margin-block-end"],
+ initial_values: ["0", "0px 0em"],
+ other_values: [
+ "1px",
+ "3em 1%",
+ "5%",
+ "calc(2px) 1%",
+ "calc(-2px) 1%",
+ "calc(50%) 1%",
+ "calc(3*25px) calc(2px)",
+ "calc(25px*3) 1em",
+ "calc(3*25px + 50%) calc(3*25px - 50%)",
+ ],
+ invalid_values: [
+ "5",
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "margin-block-end": {
+ domProp: "marginBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "margin-block-start": {
+ domProp: "marginBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ logical: true,
+ /* XXX testing auto has prerequisites */
+ initial_values: ["0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)"],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [
+ "..25px",
+ ".+5px",
+ ".px",
+ "-.px",
+ "++5px",
+ "-+4px",
+ "+-3px",
+ "--7px",
+ "+-.6px",
+ "-+.5px",
+ "++.7px",
+ "--.4px",
+ ],
+ },
+ "-moz-margin-end": {
+ domProp: "MozMarginEnd",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "margin-inline-end",
+ subproperties: ["margin-inline-end"],
+ },
+ "-moz-margin-start": {
+ domProp: "MozMarginStart",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "margin-inline-start",
+ subproperties: ["margin-inline-start"],
+ },
+ "max-block-size": {
+ domProp: "maxBlockSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ prerequisites: { display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "30px",
+ "50%",
+ // These keywords are treated as initial value.
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["auto", "5"],
+ },
+ "max-inline-size": {
+ domProp: "maxInlineSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ prerequisites: { display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "30px",
+ "50%",
+ // these four keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["auto", "5"],
+ },
+ "min-block-size": {
+ domProp: "minBlockSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ prerequisites: { display: "block" },
+ initial_values: ["auto", "0", "calc(0em)", "calc(-2px)"],
+ other_values: [
+ "30px",
+ "50%",
+ // These keywords are treated as initial value.
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none", "5"],
+ },
+ "min-inline-size": {
+ domProp: "minInlineSize",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ axis: true,
+ prerequisites: { display: "block" },
+ initial_values: ["auto", "0", "calc(0em)", "calc(-2px)"],
+ other_values: [
+ "30px",
+ "50%",
+ // these four keywords compute to the initial value only when the
+ // writing mode is vertical, and we're testing with a horizontal
+ // writing mode
+ "max-content",
+ "min-content",
+ "fit-content",
+ "-moz-fit-content",
+ "-moz-available",
+ // these two keywords are the aliases of above first two.
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc(-1%)",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "fit-content(100px)",
+ "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))",
+ ],
+ invalid_values: ["none", "5"],
+ },
+ inset: {
+ domProp: "inset",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["top", "right", "bottom", "left"],
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ initial_values: ["auto"],
+ other_values: [
+ "3px 0",
+ "2em 4px 2pt",
+ "1em 2em 3px 4px",
+ "1em calc(2em + 3px) 4ex 5cm",
+ ],
+ invalid_values: ["1px calc(nonsense)", "1px red", "3"],
+ unbalanced_values: ["1px calc("],
+ },
+ "inset-block": {
+ domProp: "insetBlock",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["inset-block-start", "inset-block-end"],
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "32px auto",
+ "auto -3em",
+ "12% auto",
+ "calc(2px)",
+ "calc(2px) auto",
+ "calc(-2px)",
+ "auto calc(-2px)",
+ "calc(50%)",
+ "auto calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) auto",
+ "calc(25px*3)",
+ "auto calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "auto calc(3*25px + 50%)",
+ ],
+ invalid_values: ["none"],
+ },
+ "inset-block-end": {
+ domProp: "insetBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "inset-block-start": {
+ domProp: "insetBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "inset-inline": {
+ domProp: "insetInline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["inset-inline-start", "inset-inline-end"],
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ initial_values: ["auto", "auto auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "32px auto",
+ "auto -3em",
+ "12% auto",
+ "calc(2px)",
+ "calc(2px) auto",
+ "calc(-2px)",
+ "auto calc(-2px)",
+ "calc(50%)",
+ "auto calc(50%)",
+ "calc(3*25px)",
+ "calc(3*25px) auto",
+ "calc(25px*3)",
+ "auto calc(25px*3)",
+ "calc(3*25px + 50%)",
+ "auto calc(3*25px + 50%)",
+ ],
+ invalid_values: ["none"],
+ },
+ "inset-inline-end": {
+ domProp: "insetInlineEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "inset-inline-start": {
+ domProp: "insetInlineStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ logical: true,
+ /* FIXME: run tests with multiple prerequisites */
+ prerequisites: { position: "relative" },
+ /* XXX 0 may or may not be equal to auto */
+ initial_values: ["auto"],
+ other_values: [
+ "32px",
+ "-3em",
+ "12%",
+ "calc(2px)",
+ "calc(-2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "padding-block-end": {
+ domProp: "paddingBlockEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ logical: true,
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "padding-block-start": {
+ domProp: "paddingBlockStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ // No applies_to_placeholder because we have a !important rule in forms.css.
+ logical: true,
+ initial_values: [
+ "0",
+ "0px",
+ "0%",
+ "calc(0pt)",
+ "calc(0% + 0px)",
+ "calc(-3px)",
+ "calc(-1%)",
+ ],
+ other_values: [
+ "1px",
+ "2em",
+ "5%",
+ "calc(2px)",
+ "calc(50%)",
+ "calc(3*25px)",
+ "calc(25px*3)",
+ "calc(3*25px + 50%)",
+ ],
+ invalid_values: [],
+ },
+ "-moz-padding-end": {
+ domProp: "MozPaddingEnd",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "padding-inline-end",
+ subproperties: ["padding-inline-end"],
+ },
+ "-moz-padding-start": {
+ domProp: "MozPaddingStart",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "padding-inline-start",
+ subproperties: ["padding-inline-start"],
+ },
+ "-webkit-animation": {
+ domProp: "webkitAnimation",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ alias_for: "animation",
+ subproperties: [
+ "animation-name",
+ "animation-duration",
+ "animation-timing-function",
+ "animation-delay",
+ "animation-direction",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-play-state",
+ ],
+ },
+ "-webkit-animation-delay": {
+ domProp: "webkitAnimationDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-delay",
+ subproperties: ["animation-delay"],
+ },
+ "-webkit-animation-direction": {
+ domProp: "webkitAnimationDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-direction",
+ subproperties: ["animation-direction"],
+ },
+ "-webkit-animation-duration": {
+ domProp: "webkitAnimationDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-duration",
+ subproperties: ["animation-duration"],
+ },
+ "-webkit-animation-fill-mode": {
+ domProp: "webkitAnimationFillMode",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-fill-mode",
+ subproperties: ["animation-fill-mode"],
+ },
+ "-webkit-animation-iteration-count": {
+ domProp: "webkitAnimationIterationCount",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-iteration-count",
+ subproperties: ["animation-iteration-count"],
+ },
+ "-webkit-animation-name": {
+ domProp: "webkitAnimationName",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-name",
+ subproperties: ["animation-name"],
+ },
+ "-webkit-animation-play-state": {
+ domProp: "webkitAnimationPlayState",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-play-state",
+ subproperties: ["animation-play-state"],
+ },
+ "-webkit-animation-timing-function": {
+ domProp: "webkitAnimationTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-timing-function",
+ subproperties: ["animation-timing-function"],
+ },
+ "-webkit-clip-path": {
+ domProp: "webkitClipPath",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "clip-path",
+ subproperties: ["clip-path"],
+ },
+ "-webkit-filter": {
+ domProp: "webkitFilter",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "filter",
+ subproperties: ["filter"],
+ },
+ "-webkit-text-security": {
+ domProp: "webkitTextSecurity",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["none"],
+ other_values: ["circle", "disc", "square"],
+ invalid_values: ["0", "auto", "true", "'*'"],
+ },
+ "-webkit-text-fill-color": {
+ domProp: "webkitTextFillColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor", "black", "#000", "#000000", "rgb(0,0,0)"],
+ other_values: ["red", "rgba(255,255,255,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "ff00ff",
+ "rgb(255,xxx,255)",
+ ],
+ },
+ "-webkit-text-stroke": {
+ domProp: "webkitTextStroke",
+ inherited: true,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ prerequisites: { color: "black" },
+ subproperties: ["-webkit-text-stroke-width", "-webkit-text-stroke-color"],
+ initial_values: [
+ "0 currentColor",
+ "currentColor 0px",
+ "0",
+ "currentColor",
+ "0px black",
+ ],
+ other_values: [
+ "thin black",
+ "#f00 medium",
+ "thick rgba(0,0,255,0.5)",
+ "calc(4px - 8px) green",
+ "2px",
+ "green 0",
+ "currentColor 4em",
+ "currentColor calc(5px - 1px)",
+ ],
+ invalid_values: ["-3px black", "calc(50%+ 2px) #000", "30% #f00"],
+ },
+ "-webkit-text-stroke-color": {
+ domProp: "webkitTextStrokeColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ prerequisites: { color: "black" },
+ initial_values: ["currentColor", "black", "#000", "#000000", "rgb(0,0,0)"],
+ other_values: ["red", "rgba(255,255,255,0.5)", "transparent"],
+ invalid_values: [
+ "#0",
+ "#00",
+ "#00000",
+ "#0000000",
+ "#000000000",
+ "000000",
+ "ff00ff",
+ "rgb(255,xxx,255)",
+ ],
+ },
+ "-webkit-text-stroke-width": {
+ domProp: "webkitTextStrokeWidth",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["0", "0px", "0em", "0ex", "calc(0pt)", "calc(4px - 8px)"],
+ other_values: [
+ "thin",
+ "medium",
+ "thick",
+ "17px",
+ "0.2em",
+ "calc(3*25px + 5em)",
+ "calc(5px - 1px)",
+ ],
+ invalid_values: [
+ "5%",
+ "1px calc(nonsense)",
+ "1px red",
+ "-0.1px",
+ "-3px",
+ "30%",
+ ],
+ },
+ "-webkit-text-size-adjust": {
+ domProp: "webkitTextSizeAdjust",
+ inherited: true,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-text-size-adjust",
+ subproperties: ["-moz-text-size-adjust"],
+ },
+ "-webkit-transform": {
+ domProp: "webkitTransform",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform",
+ subproperties: ["transform"],
+ },
+ "-webkit-transform-origin": {
+ domProp: "webkitTransformOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-origin",
+ subproperties: ["transform-origin"],
+ },
+ "-webkit-transform-style": {
+ domProp: "webkitTransformStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-style",
+ subproperties: ["transform-style"],
+ },
+ "-webkit-backface-visibility": {
+ domProp: "webkitBackfaceVisibility",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "backface-visibility",
+ subproperties: ["backface-visibility"],
+ },
+ "-webkit-perspective": {
+ domProp: "webkitPerspective",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective",
+ subproperties: ["perspective"],
+ },
+ "-webkit-perspective-origin": {
+ domProp: "webkitPerspectiveOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective-origin",
+ subproperties: ["perspective-origin"],
+ },
+ "-webkit-transition": {
+ domProp: "webkitTransition",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ alias_for: "transition",
+ subproperties: [
+ "transition-property",
+ "transition-duration",
+ "transition-timing-function",
+ "transition-delay",
+ ],
+ },
+ "-webkit-transition-delay": {
+ domProp: "webkitTransitionDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-delay",
+ subproperties: ["transition-delay"],
+ },
+ "-webkit-transition-duration": {
+ domProp: "webkitTransitionDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-duration",
+ subproperties: ["transition-duration"],
+ },
+ "-webkit-transition-property": {
+ domProp: "webkitTransitionProperty",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-property",
+ subproperties: ["transition-property"],
+ },
+ "-webkit-transition-timing-function": {
+ domProp: "webkitTransitionTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-timing-function",
+ subproperties: ["transition-timing-function"],
+ },
+ "-webkit-border-radius": {
+ domProp: "webkitBorderRadius",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-radius",
+ subproperties: [
+ "border-bottom-left-radius",
+ "border-bottom-right-radius",
+ "border-top-left-radius",
+ "border-top-right-radius",
+ ],
+ },
+ "-webkit-border-top-left-radius": {
+ domProp: "webkitBorderTopLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-top-left-radius",
+ subproperties: ["border-top-left-radius"],
+ },
+ "-webkit-border-top-right-radius": {
+ domProp: "webkitBorderTopRightRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-top-right-radius",
+ subproperties: ["border-top-right-radius"],
+ },
+ "-webkit-border-bottom-left-radius": {
+ domProp: "webkitBorderBottomLeftRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-bottom-left-radius",
+ subproperties: ["border-bottom-left-radius"],
+ },
+ "-webkit-border-bottom-right-radius": {
+ domProp: "webkitBorderBottomRightRadius",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "border-bottom-right-radius",
+ subproperties: ["border-bottom-right-radius"],
+ },
+ "-webkit-background-clip": {
+ domProp: "webkitBackgroundClip",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "background-clip",
+ subproperties: ["background-clip"],
+ },
+ "-webkit-background-origin": {
+ domProp: "webkitBackgroundOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "background-origin",
+ subproperties: ["background-origin"],
+ },
+ "-webkit-background-size": {
+ domProp: "webkitBackgroundSize",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ alias_for: "background-size",
+ subproperties: ["background-size"],
+ },
+ "-webkit-border-image": {
+ domProp: "webkitBorderImage",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "border-image",
+ subproperties: [
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat",
+ ],
+ },
+ "-webkit-box-shadow": {
+ domProp: "webkitBoxShadow",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_first_letter: true,
+ alias_for: "box-shadow",
+ subproperties: ["box-shadow"],
+ },
+ "-webkit-box-sizing": {
+ domProp: "webkitBoxSizing",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "box-sizing",
+ subproperties: ["box-sizing"],
+ },
+ "-webkit-box-flex": {
+ domProp: "webkitBoxFlex",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-flex",
+ subproperties: ["-moz-box-flex"],
+ },
+ "-webkit-box-ordinal-group": {
+ domProp: "webkitBoxOrdinalGroup",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-ordinal-group",
+ subproperties: ["-moz-box-ordinal-group"],
+ },
+ "-webkit-box-orient": {
+ domProp: "webkitBoxOrient",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-orient",
+ subproperties: ["-moz-box-orient"],
+ },
+ "-webkit-box-direction": {
+ domProp: "webkitBoxDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-direction",
+ subproperties: ["-moz-box-direction"],
+ },
+ "-webkit-box-align": {
+ domProp: "webkitBoxAlign",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-align",
+ subproperties: ["-moz-box-align"],
+ },
+ "-webkit-box-pack": {
+ domProp: "webkitBoxPack",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "-moz-box-pack",
+ subproperties: ["-moz-box-pack"],
+ },
+ "-webkit-flex-direction": {
+ domProp: "webkitFlexDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-direction",
+ subproperties: ["flex-direction"],
+ },
+ "-webkit-flex-wrap": {
+ domProp: "webkitFlexWrap",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-wrap",
+ subproperties: ["flex-wrap"],
+ },
+ "-webkit-flex-flow": {
+ domProp: "webkitFlexFlow",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "flex-flow",
+ subproperties: ["flex-direction", "flex-wrap"],
+ },
+ "-webkit-line-clamp": {
+ domProp: "webkitLineClamp",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1", "2"],
+ invalid_values: ["auto", "0", "-1"],
+ },
+ "-webkit-order": {
+ domProp: "webkitOrder",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "order",
+ subproperties: ["order"],
+ },
+ "-webkit-flex": {
+ domProp: "webkitFlex",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "flex",
+ subproperties: ["flex-grow", "flex-shrink", "flex-basis"],
+ },
+ "-webkit-flex-grow": {
+ domProp: "webkitFlexGrow",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-grow",
+ subproperties: ["flex-grow"],
+ },
+ "-webkit-flex-shrink": {
+ domProp: "webkitFlexShrink",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-shrink",
+ subproperties: ["flex-shrink"],
+ },
+ "-webkit-flex-basis": {
+ domProp: "webkitFlexBasis",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "flex-basis",
+ subproperties: ["flex-basis"],
+ },
+ "-webkit-justify-content": {
+ domProp: "webkitJustifyContent",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "justify-content",
+ subproperties: ["justify-content"],
+ },
+ "-webkit-align-items": {
+ domProp: "webkitAlignItems",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "align-items",
+ subproperties: ["align-items"],
+ },
+ "-webkit-align-self": {
+ domProp: "webkitAlignSelf",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "align-self",
+ subproperties: ["align-self"],
+ },
+ "-webkit-align-content": {
+ domProp: "webkitAlignContent",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "align-content",
+ subproperties: ["align-content"],
+ },
+ "-webkit-user-select": {
+ domProp: "webkitUserSelect",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "user-select",
+ subproperties: ["user-select"],
+ },
+ "-webkit-mask": {
+ domProp: "webkitMask",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "mask",
+ subproperties: [
+ "mask-clip",
+ "mask-image",
+ "mask-mode",
+ "mask-origin",
+ "mask-position",
+ "mask-repeat",
+ "mask-size",
+ "mask-composite",
+ ],
+ },
+ "-webkit-mask-clip": {
+ domProp: "webkitMaskClip",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-clip",
+ subproperties: ["mask-clip"],
+ },
+
+ "-webkit-mask-composite": {
+ domProp: "webkitMaskComposite",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-composite",
+ subproperties: ["mask-composite"],
+ },
+
+ "-webkit-mask-image": {
+ domProp: "webkitMaskImage",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-image",
+ subproperties: ["mask-image"],
+ },
+ "-webkit-mask-origin": {
+ domProp: "webkitMaskOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-origin",
+ subproperties: ["mask-origin"],
+ },
+ "-webkit-mask-position": {
+ domProp: "webkitMaskPosition",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-position",
+ subproperties: ["mask-position"],
+ },
+ "-webkit-mask-position-x": {
+ domProp: "webkitMaskPositionX",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-position-x",
+ subproperties: ["mask-position-x"],
+ },
+ "-webkit-mask-position-y": {
+ domProp: "webkitMaskPositionY",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-position-y",
+ subproperties: ["mask-position-y"],
+ },
+ "-webkit-mask-repeat": {
+ domProp: "webkitMaskRepeat",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-repeat",
+ subproperties: ["mask-repeat"],
+ },
+ "-webkit-mask-size": {
+ domProp: "webkitMaskSize",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "mask-size",
+ subproperties: ["mask-size"],
+ },
+}; // end of gCSSProperties
+
+// Get the computed value for a property. For shorthands, return the
+// computed values of all the subproperties, delimited by " ; ".
+function get_computed_value(cs, property) {
+ var info = gCSSProperties[property];
+ if (
+ info.type == CSS_TYPE_TRUE_SHORTHAND ||
+ info.type == CSS_TYPE_LEGACY_SHORTHAND ||
+ (info.type == CSS_TYPE_SHORTHAND_AND_LONGHAND &&
+ (property == "text-decoration" || property == "mask"))
+ ) {
+ var results = [];
+ for (var idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ results.push(get_computed_value(cs, subprop));
+ }
+ return results.join(" ; ");
+ }
+ return cs.getPropertyValue(property);
+}
+
+{
+ const mozHiddenUnscrollableEnabled = IsCSSPropertyPrefEnabled(
+ "layout.css.overflow-moz-hidden-unscrollable.enabled"
+ );
+ for (let p of ["overflow", "overflow-x", "overflow-y"]) {
+ let prop = gCSSProperties[p];
+ let mozHiddenUnscrollableValues = mozHiddenUnscrollableEnabled
+ ? prop.other_values
+ : prop.invalid_values;
+ mozHiddenUnscrollableValues.push("-moz-hidden-unscrollable");
+ if (p == "overflow") {
+ mozHiddenUnscrollableValues.push(
+ "-moz-hidden-unscrollable -moz-hidden-unscrollable"
+ );
+ }
+ }
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.individual-transform.enabled")) {
+ gCSSProperties.rotate = {
+ domProp: "rotate",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "45deg",
+ "45grad",
+ "72rad",
+ "0.25turn",
+ ".57rad",
+ "0 0 0 0rad",
+ "0 0 1 45deg",
+ "0 0 1 0rad",
+ "0rad 0 0 1",
+ "10rad 10 20 30",
+ "x 10rad",
+ "y 10rad",
+ "z 10rad",
+ "10rad x",
+ "10rad y",
+ "10rad z",
+ /* valid calc() values */
+ "calc(1) 0 0 calc(45deg + 5rad)",
+ "0 1 0 calc(400grad + 1rad)",
+ "calc(0.5turn + 10deg)",
+ ],
+ invalid_values: [
+ "0",
+ "7",
+ "0, 0, 1, 45deg",
+ "0 0 45deg",
+ "0 0 20rad",
+ "0 0 0 0",
+ "x x 10rad",
+ "x y 10rad",
+ "0 0 1 10rad z",
+ "0 0 1 z 10rad",
+ "z 0 0 1 10rad",
+ "0 0 z 1 10rad",
+ /* invalid calc() values */
+ "0.5 1 0 calc(45deg + 10)",
+ "calc(0.5turn + 10%)",
+ ],
+ };
+
+ gCSSProperties.translate = {
+ domProp: "translate",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { width: "10px", height: "10px", display: "block" },
+ initial_values: ["none"],
+ other_values: [
+ "-4px",
+ "3px",
+ "4em",
+ "50%",
+ "4px 5px 6px",
+ "4px 5px",
+ "50% 5px 6px",
+ "50% 10% 6em",
+ /* valid calc() values */
+ "calc(5px + 10%)",
+ "calc(0.25 * 5px + 10% / 3)",
+ "calc(5px - 10% * 3)",
+ "calc(5px - 3 * 10%) 50px",
+ "-50px calc(5px - 10% * 3)",
+ "10px calc(min(5px,10%))",
+ ],
+ invalid_values: [
+ "1",
+ "-moz-min(5px,10%)",
+ "4px, 5px, 6px",
+ "3px 4px 1px 7px",
+ "4px 5px 10%",
+ /* invalid calc() values */
+ "calc(max(5px,10%) 10%)",
+ "calc(nonsense)",
+ ],
+ };
+ gCSSProperties.scale = {
+ domProp: "scale",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "10",
+ "10%",
+ "10 20",
+ "10% 20%",
+ "10 20 30",
+ "10% 20% 30%",
+ "10 20% 30",
+ "-10",
+ "-10%",
+ "-10 20",
+ "-10% 20%",
+ "-10 20 -30",
+ "-10% 20% -30%",
+ "-10 20% -30",
+ "0 2.0",
+ /* valid calc() values */
+ "calc(1 + 2)",
+ "calc(10) calc(20) 30",
+ ],
+ invalid_values: [
+ "10px",
+ "10deg",
+ "10, 20, 30",
+ /* invalid calc() values */
+ "calc(1 + 20%)",
+ "10 calc(1 + 10px)",
+ ],
+ };
+}
+
+if (
+ IsCSSPropertyPrefEnabled("layout.css.transform-box-content-stroke.enabled")
+) {
+ gCSSProperties["transform-box"]["other_values"].push(
+ "content-box",
+ "stroke-box"
+ );
+}
+
+gCSSProperties["touch-action"] = {
+ domProp: "touchAction",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "none",
+ "pan-x",
+ "pan-y",
+ "pinch-zoom",
+ "pan-x pan-y",
+ "pan-y pan-x",
+ "pinch-zoom pan-x",
+ "pinch-zoom pan-y",
+ "pan-x pinch-zoom",
+ "pan-y pinch-zoom",
+ "pinch-zoom pan-x pan-y",
+ "pinch-zoom pan-y pan-x",
+ "pan-x pinch-zoom pan-y",
+ "pan-y pinch-zoom pan-x",
+ "pan-x pan-y pinch-zoom",
+ "pan-y pan-x pinch-zoom",
+ "manipulation",
+ ],
+ invalid_values: [
+ "zoom",
+ "pinch",
+ "tap",
+ "10px",
+ "2",
+ "auto pan-x",
+ "pan-x auto",
+ "none pan-x",
+ "pan-x none",
+ "auto pan-y",
+ "pan-y auto",
+ "none pan-y",
+ "pan-y none",
+ "pan-x pan-x",
+ "pan-y pan-y",
+ "auto pinch-zoom",
+ "pinch-zoom auto",
+ "none pinch-zoom",
+ "pinch-zoom none",
+ "pinch-zoom pinch-zoom",
+ "pan-x pan-y none",
+ "pan-x none pan-y",
+ "none pan-x pan-y",
+ "pan-y pan-x none",
+ "pan-y none pan-x",
+ "none pan-y pan-x",
+ "pan-x pinch-zoom none",
+ "pan-x none pinch-zoom",
+ "none pan-x pinch-zoom",
+ "pinch-zoom pan-x none",
+ "pinch-zoom none pan-x",
+ "none pinch-zoom pan-x",
+ "pinch-zoom pan-y none",
+ "pinch-zoom none pan-y",
+ "none pinch-zoom pan-y",
+ "pan-y pinch-zoom none",
+ "pan-y none pinch-zoom",
+ "none pan-y pinch-zoom",
+ "pan-x pan-y auto",
+ "pan-x auto pan-y",
+ "auto pan-x pan-y",
+ "pan-y pan-x auto",
+ "pan-y auto pan-x",
+ "auto pan-y pan-x",
+ "pan-x pinch-zoom auto",
+ "pan-x auto pinch-zoom",
+ "auto pan-x pinch-zoom",
+ "pinch-zoom pan-x auto",
+ "pinch-zoom auto pan-x",
+ "auto pinch-zoom pan-x",
+ "pinch-zoom pan-y auto",
+ "pinch-zoom auto pan-y",
+ "auto pinch-zoom pan-y",
+ "pan-y pinch-zoom auto",
+ "pan-y auto pinch-zoom",
+ "auto pan-y pinch-zoom",
+ "pan-x pan-y zoom",
+ "pan-x zoom pan-y",
+ "zoom pan-x pan-y",
+ "pan-y pan-x zoom",
+ "pan-y zoom pan-x",
+ "zoom pan-y pan-x",
+ "pinch-zoom pan-y zoom",
+ "pinch-zoom zoom pan-y",
+ "zoom pinch-zoom pan-y",
+ "pan-y pinch-zoom zoom",
+ "pan-y zoom pinch-zoom",
+ "zoom pan-y pinch-zoom",
+ "pan-x pinch-zoom zoom",
+ "pan-x zoom pinch-zoom",
+ "zoom pan-x pinch-zoom",
+ "pinch-zoom pan-x zoom",
+ "pinch-zoom zoom pan-x",
+ "zoom pinch-zoom pan-x",
+ "pan-x pan-y pan-x",
+ "pan-x pan-x pan-y",
+ "pan-y pan-x pan-x",
+ "pan-y pan-x pan-y",
+ "pan-y pan-y pan-x",
+ "pan-x pan-y pan-y",
+ "pan-x pinch-zoom pan-x",
+ "pan-x pan-x pinch-zoom",
+ "pinch-zoom pan-x pan-x",
+ "pinch-zoom pan-x pinch-zoom",
+ "pinch-zoom pinch-zoom pan-x",
+ "pan-x pinch-zoom pinch-zoom",
+ "pinch-zoom pan-y pinch-zoom",
+ "pinch-zoom pinch-zoom pan-y",
+ "pan-y pinch-zoom pinch-zoom",
+ "pan-y pinch-zoom pan-y",
+ "pan-y pan-y pinch-zoom",
+ "pinch-zoom pan-y pan-y",
+ "manipulation none",
+ "none manipulation",
+ "manipulation auto",
+ "auto manipulation",
+ "manipulation zoom",
+ "zoom manipulation",
+ "manipulation manipulation",
+ "manipulation pan-x",
+ "pan-x manipulation",
+ "manipulation pan-y",
+ "pan-y manipulation",
+ "manipulation pinch-zoom",
+ "pinch-zoom manipulation",
+ "manipulation pan-x pan-y",
+ "pan-x manipulation pan-y",
+ "pan-x pan-y manipulation",
+ "manipulation pan-y pan-x",
+ "pan-y manipulation pan-x",
+ "pan-y pan-x manipulation",
+ "manipulation pinch-zoom pan-y",
+ "pinch-zoom manipulation pan-y",
+ "pinch-zoom pan-y manipulation",
+ "manipulation pan-y pinch-zoom",
+ "pan-y manipulation pinch-zoom",
+ "pan-y pinch-zoom manipulation",
+ "manipulation pan-x pinch-zoom",
+ "pan-x manipulation pinch-zoom",
+ "pan-x pinch-zoom manipulation",
+ "manipulation pinch-zoom pan-x",
+ "pinch-zoom manipulation pan-x",
+ "pinch-zoom pan-x manipulation",
+ ],
+};
+
+gCSSProperties["page"] = {
+ domProp: "page",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["page", "small_page", "large_page", "A4"],
+ invalid_values: ["page1 page2", "auto page", "1cm"],
+};
+
+gCSSProperties["text-justify"] = {
+ domProp: "textJustify",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ["none", "inter-word", "inter-character", "distribute"],
+ invalid_values: [],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.text-indent-keywords.enabled")) {
+ gCSSProperties["text-indent"].other_values.push(
+ "2em hanging",
+ "5% each-line",
+ "-10px hanging each-line",
+ "hanging calc(2px)",
+ "each-line calc(-2px)",
+ "each-line calc(50%) hanging",
+ "hanging calc(3*25px) each-line",
+ "each-line hanging calc(25px*3)"
+ );
+ gCSSProperties["text-indent"].invalid_values.push(
+ "hanging",
+ "each-line",
+ "-10px hanging hanging",
+ "each-line calc(2px) each-line"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) {
+ gCSSProperties["font-variation-settings"] = {
+ domProp: "fontVariationSettings",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_marker: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: [
+ "'wdth' 0",
+ "'wdth' -.1",
+ '"wdth" 1',
+ "'wdth' 2, 'wght' 3",
+ '"XXXX" 0',
+ ],
+ invalid_values: [
+ "wdth",
+ "wdth 1", // unquoted tags
+ "'wdth'",
+ "'wdth' 'wght'",
+ "'wdth', 'wght'", // missing values
+ "'' 1",
+ "'wid' 1",
+ "'width' 1", // incorrect tag lengths
+ "'wd\th' 1", // non-graphic character in tag
+ "'wdth' 1 'wght' 2", // missing comma between pairs
+ "'wdth' 1,", // trailing comma
+ "'wdth' 1 , , 'wght' 2", // extra comma
+ "'wdth', 1", // comma within pair
+ ],
+ unbalanced_values: [
+ "'wdth\" 1",
+ "\"wdth' 1", // mismatched quotes
+ ],
+ };
+ gCSSProperties["font"].subproperties.push("font-variation-settings");
+ gCSSProperties["font-optical-sizing"] = {
+ domProp: "fontOpticalSizing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_marker: true,
+ applies_to_cue: true,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: ["on"],
+ };
+ gCSSProperties["font"].subproperties.push("font-optical-sizing");
+ gCSSProperties["font-variation-settings"].other_values.push(
+ "'vert' calc(2.5)"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.font-palette.enabled")) {
+ gCSSProperties["font-palette"] = {
+ domProp: "fontPalette",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ initial_values: ["normal"],
+ other_values: ["light", "dark", "--custom"],
+ invalid_values: ["custom"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.font-variant-emoji.enabled")) {
+ gCSSProperties["font"].subproperties.push("font-variant-emoji");
+ gCSSProperties["font-variant"].subproperties.push("font-variant-emoji");
+ gCSSProperties["font-variant-emoji"] = {
+ domProp: "fontVariantEmoji",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_marker: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ initial_values: ["normal"],
+ other_values: ["text", "emoji", "unicode"],
+ invalid_values: [
+ "none",
+ "auto",
+ "text emoji",
+ "auto text",
+ "normal, unicode",
+ ],
+ };
+}
+
+var isGridTemplateMasonryValueEnabled = IsCSSPropertyPrefEnabled(
+ "layout.css.grid-template-masonry-value.enabled"
+);
+
+if (isGridTemplateMasonryValueEnabled) {
+ gCSSProperties["masonry-auto-flow"] = {
+ domProp: "masonryAutoFlow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["pack"],
+ other_values: ["pack ordered", "ordered next", "next definite-first"],
+ invalid_values: ["auto", "none", "10px", "row", "dense"],
+ };
+
+ let alignTracks = { ...gCSSProperties["align-content"] };
+ alignTracks.domProp = "alignTracks";
+ gCSSProperties["align-tracks"] = alignTracks;
+
+ let justifyTracks = { ...gCSSProperties["justify-content"] };
+ justifyTracks.domProp = "justifyTracks";
+ gCSSProperties["justify-tracks"] = justifyTracks;
+}
+
+gCSSProperties["display"].other_values.push("grid", "inline-grid");
+gCSSProperties["grid-auto-flow"] = {
+ domProp: "gridAutoFlow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["row"],
+ other_values: [
+ "column",
+ "column dense",
+ "row dense",
+ "dense column",
+ "dense row",
+ "dense",
+ ],
+ invalid_values: ["", "auto", "none", "10px", "column row", "dense row dense"],
+};
+
+gCSSProperties["grid-auto-columns"] = {
+ domProp: "gridAutoColumns",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "40px",
+ "2em",
+ "2.5fr",
+ "12%",
+ "min-content",
+ "max-content",
+ "calc(2px - 99%)",
+ "minmax(20px, max-content)",
+ "minmax(min-content, auto)",
+ "minmax(auto, max-content)",
+ "m\\69nmax(20px, 4Fr)",
+ "MinMax(min-content, calc(20px + 10%))",
+ "fit-content(1px)",
+ "fit-content(calc(1px - 99%))",
+ "fit-content(10%)",
+ "40px 12%",
+ "2.5fr min-content fit-content(1px)",
+ ],
+ invalid_values: [
+ "",
+ "normal",
+ "40ms",
+ "-40px",
+ "-12%",
+ "-2em",
+ "-2.5fr",
+ "minmax()",
+ "minmax(20px)",
+ "mİnmax(20px, 100px)",
+ "minmax(20px, 100px, 200px)",
+ "maxmin(100px, 20px)",
+ "minmax(min-content, minmax(30px, max-content))",
+ "fit-content(-1px)",
+ "fit-content(auto)",
+ "fit-content(min-content)",
+ "1px [a] 1px",
+ ],
+};
+gCSSProperties["grid-auto-rows"] = {
+ domProp: "gridAutoRows",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: gCSSProperties["grid-auto-columns"].initial_values,
+ other_values: gCSSProperties["grid-auto-columns"].other_values,
+ invalid_values: gCSSProperties["grid-auto-columns"].invalid_values,
+};
+
+gCSSProperties["grid-template-columns"] = {
+ domProp: "gridTemplateColumns",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "auto",
+ "40px",
+ "2.5fr",
+ "[normal] 40px [] auto [ ] 12%",
+ "[foo] 40px min-content [ bar ] calc(2px - 99%) max-content",
+ "40px min-content calc(20px + 10%) max-content",
+ "minmax(min-content, auto)",
+ "minmax(auto, max-content)",
+ "m\\69nmax(20px, 4Fr)",
+ "40px MinMax(min-content, calc(20px + 10%)) max-content",
+ "40px 2em",
+ "[] 40px [-foo] 2em [bar baz This is one ident]",
+ // TODO bug 978478: "[a] repeat(3, [b] 20px [c] 40px [d]) [e]",
+ "repeat(1, 20px)",
+ "repeat(1, [a] 20px)",
+ "[a] Repeat(4, [a] 20px [] auto [b c]) [d]",
+ "[a] 2.5fr Repeat(4, [a] 20px [] auto [b c]) [d]",
+ "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto [b c]) [d]",
+ "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto) [d]",
+ "[a] 2.5fr [z] Repeat(4, 20px [b c] auto [b c]) [d]",
+ "[a] 2.5fr [z] Repeat(4, 20px auto) [d]",
+ "repeat(auto-fill, 0)",
+ "[a] repeat( Auto-fill,1%)",
+ "minmax(auto,0) [a] repeat(Auto-fit, 0) minmax(0,auto)",
+ "minmax(calc(1% + 1px),auto) repeat(Auto-fit,[] 1%) minmax(auto,1%)",
+ "[a] repeat( auto-fit,[a b] minmax(0,0) )",
+ "[a] 40px repeat(auto-fit,[a b] minmax(1px, 0) [])",
+ "[a] calc(1px - 99%) [b] repeat(auto-fit,[a b] minmax(1mm, 1%) [c]) [c]",
+ "repeat(auto-fill, 0 0)",
+ "repeat(auto-fill, 0 [] 0)",
+ "repeat(auto-fill,minmax(1%,auto))",
+ "repeat(auto-fill,minmax(1em,min-content)) minmax(min-content,0)",
+ "repeat(auto-fill,minmax(max-content,1mm))",
+ "repeat(2, fit-content(1px))",
+ "fit-content(1px) 1fr",
+ "[a] fit-content(calc(1px - 99%)) [b]",
+ "[a] fit-content(10%) [b c] fit-content(1em)",
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=981300
+ "[none subgrid min-content max-content foo] 40px",
+ "subgrid",
+ "subgrid [] [foo bar]",
+ "subgrid repeat(1, [])",
+ "subgrid Repeat(4, [a] [b c] [] [d])",
+ "subgrid repeat(auto-fill, [])",
+ "subgrid repeat(Auto-fill, [a b c]) [a] []",
+ "subgrid [x] repeat( Auto-fill, [a b c]) []",
+ "subgrid [x] repeat( auto-fill , [a b] [c]) [y]",
+ "subgrid repeat(auto-fill, [a] [b] [c]) [d]",
+ "subgrid repeat(Auto-fill, [a] [b c] [] [d])",
+ "subgrid [x y] [x] repeat(auto-fill, [a b] [c] [d] [d]) [x] [x]",
+ "subgrid [x] repeat(auto-fill, []) [y z]",
+ "subgrid [x] repeat(auto-fill, [y]) [z] [] repeat(2, [a] [b]) [y] []",
+ "subgrid [x] repeat(auto-fill, []) [x y] [z] [] []",
+ ],
+ invalid_values: [
+ "",
+ "normal",
+ "40ms",
+ "-40px",
+ "-12%",
+ "-2fr",
+ "[foo]",
+ "[inherit] 40px",
+ "[initial] 40px",
+ "[unset] 40px",
+ "[default] 40px",
+ "[span] 40px",
+ "[6%] 40px",
+ "[5th] 40px",
+ "[foo[] bar] 40px",
+ "[foo]] 40px",
+ "(foo) 40px",
+ "[foo] [bar] 40px",
+ "40px [foo] [bar]",
+ "minmax()",
+ "minmax(20px)",
+ "mİnmax(20px, 100px)",
+ "minmax(20px, 100px, 200px)",
+ "maxmin(100px, 20px)",
+ "minmax(min-content, minmax(30px, max-content))",
+ "repeat(0, 20px)",
+ "repeat(-3, 20px)",
+ "rêpeat(1, 20px)",
+ "repeat(1)",
+ "repeat(1, )",
+ "repeat(3px, 20px)",
+ "repeat(2.0, 20px)",
+ "repeat(2.5, 20px)",
+ "repeat(2, (foo))",
+ "repeat(2, foo)",
+ "40px calc(0px + rubbish)",
+ "repeat(1, repeat(1, 20px))",
+ "repeat(auto-fill, auto)",
+ "repeat(auto-fit,auto)",
+ "repeat(auto-fill, fit-content(1px))",
+ "repeat(auto-fit, fit-content(1px))",
+ "repeat(auto-fit,[])",
+ "repeat(auto-fill, 0) repeat(auto-fit, 0) ",
+ "repeat(auto-fit, 0) repeat(auto-fill, 0) ",
+ "[a] repeat(auto-fit, 0) repeat(auto-fit, 0) ",
+ "[a] repeat(auto-fill, 0) [a] repeat(auto-fill, 0) ",
+ "repeat(auto-fill, min-content)",
+ "repeat(auto-fit,max-content)",
+ "repeat(auto-fit,1fr)",
+ "repeat(auto-fit,minmax(auto,auto))",
+ "repeat(auto-fit,minmax(min-content,1fr))",
+ "repeat(auto-fit,minmax(1fr,auto))",
+ "repeat(auto-fill,minmax(1fr,1em))",
+ "repeat(auto-fill, 10px) auto",
+ "auto repeat(auto-fit, 10px)",
+ "minmax(min-content,max-content) repeat(auto-fit, 0)",
+ "10px [a] 10px [b a] 1fr [b] repeat(auto-fill, 0)",
+ "fit-content(-1px)",
+ "fit-content(auto)",
+ "fit-content(min-content)",
+ "fit-content(1px) repeat(auto-fit, 1px)",
+ "fit-content(1px) repeat(auto-fill, 1px)",
+ "subgrid [inherit]",
+ "subgrid [initial]",
+ "subgrid [unset]",
+ "subgrid [default]",
+ "subgrid [span]",
+ "subgrid [foo] 40px",
+ "subgrid [foo 40px]",
+ "[foo] subgrid",
+ "subgrid rêpeat(1, [])",
+ "subgrid repeat(0, [])",
+ "subgrid repeat(-3, [])",
+ "subgrid repeat(2.0, [])",
+ "subgrid repeat(2.5, [])",
+ "subgrid repeat(3px, [])",
+ "subgrid repeat(1)",
+ "subgrid repeat(1, )",
+ "subgrid repeat(2, [40px])",
+ "subgrid repeat(2, foo)",
+ "subgrid repeat(1, repeat(1, []))",
+ "subgrid repeat(auto-fill)",
+ "subgrid repeat(auto-fill) [a]",
+ "subgrid repeat(auto-fill) []",
+ "subgrid [a] repeat(auto-fill)",
+ "subgrid repeat(auto-fill,)",
+ "subgrid repeat(auto-fill,)",
+ "subgrid repeat(auto-fill,) [a]",
+ "subgrid repeat(auto-fill,) []",
+ "subgrid [a] repeat(auto-fill,)",
+ "subgrid repeat(auto-fit,[])",
+ "subgrid [] repeat(auto-fit,[])",
+ "subgrid [a] repeat(auto-fit,[])",
+ "subgrid repeat(auto-fill, 1px)",
+ "subgrid repeat(auto-fill, 1px [])",
+ "subgrid repeat(auto-fill, []) repeat(auto-fill, [])",
+ ],
+ unbalanced_values: ["(foo] 40px"],
+};
+if (isGridTemplateMasonryValueEnabled) {
+ gCSSProperties["grid-template-columns"].other_values.push("masonry");
+ gCSSProperties["grid-template-columns"].invalid_values.push(
+ "masonry []",
+ "masonry [foo] 40px",
+ "masonry 40px",
+ "[foo] masonry",
+ "0px masonry",
+ "masonry masonry",
+ "subgrid masonry",
+ "masonry subgrid",
+ "masonry repeat(1, [])"
+ );
+}
+gCSSProperties["grid-template-rows"] = {
+ domProp: "gridTemplateRows",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: gCSSProperties["grid-template-columns"].initial_values,
+ other_values: gCSSProperties["grid-template-columns"].other_values,
+ invalid_values: gCSSProperties["grid-template-columns"].invalid_values,
+};
+gCSSProperties["grid-template-areas"] = {
+ domProp: "gridTemplateAreas",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "'1a-é_ .' \"b .\"",
+ "' Z\t\\aZ' 'Z Z'",
+ " '. . a b' '. .a b' ",
+ "'a.b' '. . .'",
+ "'.' '..'",
+ "'...' '.'",
+ "'...-blah' '. .'",
+ "'.. ..' '.. ...'",
+ ],
+ invalid_values: [
+ "''",
+ "' '",
+ "'' ''",
+ "'a b' 'a/b'",
+ "'a . a'",
+ "'. a a' 'a a a'",
+ "'a a .' 'a a a'",
+ "'a a' 'a .'",
+ "'a a'\n'..'\n'a a'",
+ ],
+};
+
+gCSSProperties["grid-template"] = {
+ domProp: "gridTemplate",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "grid-template-areas",
+ "grid-template-rows",
+ "grid-template-columns",
+ ],
+ initial_values: ["none", "none / none"],
+ other_values: [
+ // <'grid-template-rows'> / <'grid-template-columns'>
+ "40px / 100px",
+ "[foo] 40px [bar] / [baz] repeat(auto-fill,100px) [fizz]",
+ " none/100px",
+ "40px/none",
+ // [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]?
+ "'fizz'",
+ "[bar] 'fizz'",
+ "'fizz' / [foo] 40px",
+ "[bar] 'fizz' / [foo] 40px",
+ "'fizz' 100px / [foo] 40px",
+ "[bar] 'fizz' 100px / [foo] 40px",
+ "[bar] 'fizz' 100px [buzz] / [foo] 40px",
+ "[bar] 'fizz' 100px [buzz] \n [a] '.' 200px [b] / [foo] 40px",
+ "subgrid / subgrid",
+ "subgrid/40px 20px",
+ "subgrid [foo] [] [bar baz] / 40px 20px",
+ "40px 20px/subgrid",
+ "40px 20px/subgrid [foo] [] repeat(3, [a] [b]) [bar baz]",
+ "subgrid/subgrid",
+ "subgrid [foo] [] [bar baz]/subgrid [foo] [] [bar baz]",
+ ],
+ invalid_values: [
+ "'fizz' / repeat(1, 100px)",
+ "'fizz' repeat(1, 100px) / 0px",
+ "[foo] [bar] 40px / 100px",
+ "[fizz] [buzz] 100px / 40px",
+ "[fizz] [buzz] 'foo' / 40px",
+ "'foo' / none",
+ "subgrid",
+ "subgrid []",
+ "subgrid [] / 'fizz'",
+ "subgrid / 'fizz'",
+ ],
+};
+if (isGridTemplateMasonryValueEnabled) {
+ gCSSProperties["grid-template"].other_values.push(
+ "masonry / subgrid",
+ "subgrid / masonry",
+ "masonry / masonry" /* valid but behaves as 'masonry / none' */,
+ "masonry/40px 20px",
+ "subgrid [foo] [] [bar baz] / masonry",
+ "40px 20px/masonry",
+ "masonry/subgrid [foo] [] repeat(3, [a] [b]) [bar baz]",
+ "subgrid [foo] [] [bar baz]/masonry"
+ );
+ gCSSProperties["grid-template"].invalid_values.push(
+ "masonry",
+ "masonry / 'fizz'"
+ );
+}
+
+gCSSProperties["grid"] = {
+ domProp: "grid",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "grid-template-areas",
+ "grid-template-rows",
+ "grid-template-columns",
+ "grid-auto-flow",
+ "grid-auto-rows",
+ "grid-auto-columns",
+ ],
+ initial_values: ["none", "none / none"],
+ other_values: [
+ "auto-flow 40px / none",
+ "auto-flow 40px 100px / 0",
+ "auto-flow / 40px",
+ "auto-flow dense auto / auto",
+ "dense auto-flow minmax(min-content, 2fr) / auto",
+ "dense auto-flow / 100px",
+ "none / auto-flow 40px",
+ "40px / auto-flow",
+ "none / dense auto-flow auto",
+ ].concat(gCSSProperties["grid-template"].other_values),
+ invalid_values: [
+ "auto-flow",
+ " / auto-flow",
+ "dense 0 / 0",
+ "dense dense 40px / 0",
+ "auto-flow / auto-flow",
+ "auto-flow / dense",
+ "auto-flow [a] 0 / 0",
+ "0 / auto-flow [a] 0",
+ "auto-flow -20px / 0",
+ "auto-flow 200ms / 0",
+ "auto-flow 1px [a] 1px / 0",
+ ].concat(
+ gCSSProperties["grid-template"].invalid_values,
+ gCSSProperties["grid-auto-flow"].other_values,
+ gCSSProperties["grid-auto-flow"].invalid_values.filter(v => v != "none")
+ ),
+};
+
+var gridLineOtherValues = [
+ "foo",
+ "2",
+ "2 foo",
+ "foo 2",
+ "-3",
+ "-3 bar",
+ "bar -3",
+ "span 2",
+ "2 span",
+ "span foo",
+ "foo span",
+ "span 2 foo",
+ "span foo 2",
+ "2 foo span",
+ "foo 2 span",
+];
+var gridLineInvalidValues = [
+ "",
+ "4th",
+ "span",
+ "inherit 2",
+ "2 inherit",
+ "20px",
+ "2 3",
+ "2.5",
+ "2.0",
+ "0",
+ "0 foo",
+ "span 0",
+ "2 foo 3",
+ "foo 2 foo",
+ "2 span foo",
+ "foo span 2",
+ "span -3",
+ "span -3 bar",
+ "span 2 span",
+ "span foo span",
+ "span 2 foo span",
+];
+
+gCSSProperties["grid-column-start"] = {
+ domProp: "gridColumnStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: gridLineOtherValues,
+ invalid_values: gridLineInvalidValues,
+};
+gCSSProperties["grid-column-end"] = {
+ domProp: "gridColumnEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: gridLineOtherValues,
+ invalid_values: gridLineInvalidValues,
+};
+gCSSProperties["grid-row-start"] = {
+ domProp: "gridRowStart",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: gridLineOtherValues,
+ invalid_values: gridLineInvalidValues,
+};
+gCSSProperties["grid-row-end"] = {
+ domProp: "gridRowEnd",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: gridLineOtherValues,
+ invalid_values: gridLineInvalidValues,
+};
+
+// The grid-column and grid-row shorthands take values of the form
+// <grid-line> [ / <grid-line> ]?
+var gridColumnRowOtherValues = [].concat(gridLineOtherValues);
+gridLineOtherValues.concat(["auto"]).forEach(function (val) {
+ gridColumnRowOtherValues.push(" foo / " + val);
+ gridColumnRowOtherValues.push(val + "/2");
+});
+var gridColumnRowInvalidValues = ["foo, bar", "foo / bar / baz"].concat(
+ gridLineInvalidValues
+);
+gridLineInvalidValues.forEach(function (val) {
+ gridColumnRowInvalidValues.push("span 3 / " + val);
+ gridColumnRowInvalidValues.push(val + " / foo");
+});
+gCSSProperties["grid-column"] = {
+ domProp: "gridColumn",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["grid-column-start", "grid-column-end"],
+ initial_values: ["auto", "auto / auto"],
+ other_values: gridColumnRowOtherValues,
+ invalid_values: gridColumnRowInvalidValues,
+};
+gCSSProperties["grid-row"] = {
+ domProp: "gridRow",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["grid-row-start", "grid-row-end"],
+ initial_values: ["auto", "auto / auto"],
+ other_values: gridColumnRowOtherValues,
+ invalid_values: gridColumnRowInvalidValues,
+};
+
+var gridAreaOtherValues = gridLineOtherValues.slice();
+gridLineOtherValues.forEach(function (val) {
+ gridAreaOtherValues.push("foo / " + val);
+ gridAreaOtherValues.push(val + "/2/3");
+ gridAreaOtherValues.push("foo / bar / " + val + " / baz");
+});
+var gridAreaInvalidValues = [
+ "foo, bar",
+ "foo / bar / baz / fizz / buzz",
+ "default / foo / bar / baz",
+ "foo / initial / bar / baz",
+ "foo / bar / inherit / baz",
+ "foo / bar / baz / unset",
+].concat(gridLineInvalidValues);
+gridLineInvalidValues.forEach(function (val) {
+ gridAreaInvalidValues.push("foo / " + val);
+ gridAreaInvalidValues.push("foo / bar / " + val);
+ gridAreaInvalidValues.push("foo / 4 / bar / " + val);
+});
+
+gCSSProperties["grid-area"] = {
+ domProp: "gridArea",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "grid-row-start",
+ "grid-column-start",
+ "grid-row-end",
+ "grid-column-end",
+ ],
+ initial_values: [
+ "auto",
+ "auto / auto",
+ "auto / auto / auto",
+ "auto / auto / auto / auto",
+ ],
+ other_values: gridAreaOtherValues,
+ invalid_values: gridAreaInvalidValues,
+};
+
+gCSSProperties["column-gap"] = {
+ domProp: "columnGap",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "2px",
+ "2%",
+ "1em",
+ "calc(1px + 1em)",
+ "calc(1%)",
+ "calc(1% + 1ch)",
+ "calc(1px - 99%)",
+ ],
+ invalid_values: [
+ "-1px",
+ "auto",
+ "none",
+ "1px 1px",
+ "-1%",
+ "fit-content(1px)",
+ ],
+};
+gCSSProperties["grid-column-gap"] = {
+ domProp: "gridColumnGap",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "column-gap",
+ subproperties: ["column-gap"],
+};
+gCSSProperties["row-gap"] = {
+ domProp: "rowGap",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "2px",
+ "2%",
+ "1em",
+ "calc(1px + 1em)",
+ "calc(1%)",
+ "calc(1% + 1ch)",
+ "calc(1px - 99%)",
+ ],
+ invalid_values: ["-1px", "auto", "none", "1px 1px", "-1%", "min-content"],
+};
+gCSSProperties["grid-row-gap"] = {
+ domProp: "gridRowGap",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "row-gap",
+ subproperties: ["row-gap"],
+};
+gCSSProperties["gap"] = {
+ domProp: "gap",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["column-gap", "row-gap"],
+ initial_values: ["normal", "normal normal"],
+ other_values: [
+ "1ch 0",
+ "1px 1%",
+ "1em 1px",
+ "calc(1px) calc(1%)",
+ "normal 0",
+ "1% normal",
+ ],
+ invalid_values: [
+ "-1px",
+ "1px -1px",
+ "1px 1px 1px",
+ "inherit 1px",
+ "1px auto",
+ ],
+};
+gCSSProperties["grid-gap"] = {
+ domProp: "gridGap",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ alias_for: "gap",
+ subproperties: ["column-gap", "row-gap"],
+};
+
+gCSSProperties["contain"] = {
+ domProp: "contain",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "strict",
+ "layout",
+ "size",
+ "content",
+ "paint",
+ "layout paint",
+ "paint layout",
+ "size layout",
+ "paint size",
+ "layout size paint",
+ "layout paint size",
+ "size paint layout",
+ "paint size layout",
+ ],
+ invalid_values: [
+ "none strict",
+ "none size",
+ "strict layout",
+ "strict layout size",
+ "layout strict",
+ "layout content",
+ "strict content",
+ "layout size strict",
+ "layout size paint strict",
+ "paint strict",
+ "size strict",
+ "paint paint",
+ "content content",
+ "size content",
+ "content strict size",
+ "paint layout content",
+ "layout size content",
+ "size size",
+ "strict strict",
+ "auto",
+ "10px",
+ "0",
+ ],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.initial-letter.enabled")) {
+ gCSSProperties["initial-letter"] = {
+ domProp: "initialLetter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ initial_values: ["normal"],
+ other_values: ["2", "2.5", "3.7 2", "4 3"],
+ invalid_values: ["-3", "3.7 -2", "25%", "16px", "1 0", "0", "0 1"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.osx-font-smoothing.enabled")) {
+ gCSSProperties["-moz-osx-font-smoothing"] = {
+ domProp: "MozOsxFontSmoothing",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["auto"],
+ other_values: ["grayscale"],
+ invalid_values: ["none", "subpixel-antialiased", "antialiased"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.scroll-anchoring.enabled")) {
+ gCSSProperties["overflow-anchor"] = {
+ domProp: "overflowAnchor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: [],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.overflow-clip-box.enabled")) {
+ gCSSProperties["overflow-clip-box-block"] = {
+ domProp: "overflowClipBoxBlock",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ initial_values: ["padding-box"],
+ other_values: ["content-box"],
+ invalid_values: ["auto", "border-box", "0", "padding-box padding-box"],
+ };
+ gCSSProperties["overflow-clip-box-inline"] = {
+ domProp: "overflowClipBoxInline",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ initial_values: ["padding-box"],
+ other_values: ["content-box"],
+ invalid_values: ["none", "border-box", "0", "content-box content-box"],
+ };
+ gCSSProperties["overflow-clip-box"] = {
+ domProp: "overflowClipBox",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["overflow-clip-box-block", "overflow-clip-box-inline"],
+ initial_values: ["padding-box"],
+ other_values: [
+ "content-box",
+ "padding-box content-box",
+ "content-box padding-box",
+ "content-box content-box",
+ ],
+ invalid_values: [
+ "none",
+ "auto",
+ "content-box none",
+ "border-box",
+ "0",
+ "content-box, content-box",
+ ],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.overscroll-behavior.enabled")) {
+ gCSSProperties["overscroll-behavior-x"] = {
+ domProp: "overscrollBehaviorX",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["contain", "none"],
+ invalid_values: ["left", "1px"],
+ };
+ gCSSProperties["overscroll-behavior-y"] = {
+ domProp: "overscrollBehaviorY",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["contain", "none"],
+ invalid_values: ["left", "1px"],
+ };
+ gCSSProperties["overscroll-behavior-inline"] = {
+ domProp: "overscrollBehaviorInline",
+ inherited: false,
+ logical: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["contain", "none"],
+ invalid_values: ["left", "1px"],
+ };
+ gCSSProperties["overscroll-behavior-block"] = {
+ domProp: "overscrollBehaviorBlock",
+ inherited: false,
+ logical: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["contain", "none"],
+ invalid_values: ["left", "1px"],
+ };
+ gCSSProperties["overscroll-behavior"] = {
+ domProp: "overscrollBehavior",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["overscroll-behavior-x", "overscroll-behavior-y"],
+ initial_values: ["auto"],
+ other_values: [
+ "contain",
+ "none",
+ "contain contain",
+ "contain auto",
+ "none contain",
+ ],
+ invalid_values: ["left", "1px", "contain auto none", "contain nonsense"],
+ };
+}
+
+{
+ const patterns = {
+ background: [
+ "{} scroll no-repeat",
+ "{} repeat",
+ "url(404.png), {}, -moz-element(#a) black",
+ ],
+ mask: [
+ "{} add no-repeat",
+ "{} repeat",
+ "url(404.png), {}, -moz-element(#a) alpha",
+ ],
+ };
+
+ for (const prop of ["background", "mask"]) {
+ let i = 0;
+ const p = patterns[prop];
+ for (const v of invalidNonUrlImageValues) {
+ gCSSProperties[prop].invalid_values.push(
+ p[i++ % p.length].replace("{}", v)
+ );
+ }
+ for (const v of validNonUrlImageValues) {
+ gCSSProperties[prop].other_values.push(
+ p[i++ % p.length].replace("{}", v)
+ );
+ }
+ }
+}
+
+gCSSProperties["display"].other_values.push("flow-root");
+
+gCSSProperties["hyphenate-character"] = {
+ domProp: "hyphenateCharacter",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_first_letter: true,
+ applies_to_first_line: true,
+ applies_to_placeholder: true,
+ initial_values: ["auto"],
+ other_values: ['"="', '"/-/"', '"\1400"', '""'],
+ invalid_values: ["none", "auto auto", "1400", "U+1400"],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.content-visibility.enabled")) {
+ gCSSProperties["content-visibility"] = {
+ domProp: "contentVisibility",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["visible"],
+ other_values: ["auto", "hidden"],
+ invalid_values: [
+ "invisible",
+ "partially-visible",
+ "auto auto",
+ "visible hidden",
+ ],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) {
+ gCSSProperties["contain-intrinsic-width"] = {
+ domProp: "containIntrinsicWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1em", "1px", "auto 1px", "auto none"],
+ invalid_values: ["auto auto", "auto", "-1px"],
+ };
+ gCSSProperties["contain-intrinsic-height"] = {
+ domProp: "containIntrinsicHeight",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1em", "1px", "auto 1px", "auto none"],
+ invalid_values: ["auto auto", "auto", "-1px"],
+ };
+ gCSSProperties["contain-intrinsic-block-size"] = {
+ domProp: "containIntrinsicBlockSize",
+ inherited: false,
+ logical: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1em", "1px", "auto 1px", "auto none"],
+ invalid_values: ["auto auto", "auto", "-1px"],
+ };
+ gCSSProperties["contain-intrinsic-inline-size"] = {
+ domProp: "containIntrinsicInlineSize",
+ inherited: false,
+ logical: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["1em", "1px", "auto 1px", "auto none"],
+ invalid_values: ["auto auto", "auto", "-1px"],
+ };
+
+ gCSSProperties["contain-intrinsic-size"] = {
+ domProp: "containIntrinsicSize",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["contain-intrinsic-width", "contain-intrinsic-height"],
+ initial_values: ["none"],
+ other_values: ["1em 1em", "1px 1px", "auto 1px auto 1px", "1px auto 1px"],
+ invalid_values: ["auto auto", "-1px -1px", "1px, auto none"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.container-queries.enabled")) {
+ gCSSProperties["container-type"] = {
+ domProp: "containerType",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["inline-size", "size"],
+ invalid_values: [
+ "none style",
+ "none inline-size",
+ "inline-size none",
+ "style none",
+ "style style",
+ "inline-size style inline-size",
+ "inline-size block-size",
+ "block-size",
+ "block-size style",
+ "size inline-size",
+ "size block-size",
+ ],
+ };
+ gCSSProperties["container-name"] = {
+ domProp: "containerName",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: ["foo bar", "foo", "baz bazz", "foo foo"],
+ invalid_values: ["foo unset", "none bar", "foo initial", "initial foo"],
+ };
+ gCSSProperties["container"] = {
+ domProp: "container",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["container-type", "container-name"],
+ initial_values: ["none"],
+ other_values: ["foo / size", "foo bar / size", "foo / inline-size", "foo"],
+ invalid_values: ["size / foo", "size / foo bar"],
+ };
+}
+
+if (false) {
+ // TODO These properties are chrome-only, and are not exposed via CSSOM.
+ // We may still want to find a way to test them. See bug 1206999.
+ gCSSProperties["-moz-window-shadow"] = {
+ //domProp: "MozWindowShadow",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["default"],
+ other_values: ["none", "menu", "tooltip", "sheet", "cliprounded"],
+ invalid_values: [],
+ };
+
+ gCSSProperties["-moz-window-opacity"] = {
+ // domProp: "MozWindowOpacity",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [
+ "1",
+ "17",
+ "397.376",
+ "3e1",
+ "3e+1",
+ "3e0",
+ "3e+0",
+ "3e-0",
+ "300%",
+ ],
+ other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%"],
+ invalid_values: ["0px", "1px", "20%", "default", "auto"],
+ };
+
+ gCSSProperties["-moz-window-transform"] = {
+ // domProp: "MozWindowTransform",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ prerequisites: { width: "300px", height: "50px" },
+ initial_values: ["none"],
+ other_values: [
+ "translatex(1px)",
+ "translatex(4em)",
+ "translatex(-4px)",
+ "translatex(3px)",
+ "translatex(0px) translatex(1px) translatex(2px) translatex(3px) translatex(4px)",
+ "translatey(4em)",
+ "translate(3px)",
+ "translate(10px, -3px)",
+ "rotate(45deg)",
+ "rotate(45grad)",
+ "rotate(45rad)",
+ "rotate(0.25turn)",
+ "rotate(0)",
+ "scalex(10)",
+ "scalex(10%)",
+ "scalex(-10)",
+ "scalex(-10%)",
+ "scaley(10)",
+ "scaley(10%)",
+ "scaley(-10)",
+ "scaley(-10%)",
+ "scale(10)",
+ "scale(10%)",
+ "scale(10, 20)",
+ "scale(10%, 20%)",
+ "scale(-10)",
+ "scale(-10%)",
+ "scale(-10, 20)",
+ "scale(10%, -20%)",
+ "scale(10, 20%)",
+ "scale(-10, 20%)",
+ "skewx(30deg)",
+ "skewx(0)",
+ "skewy(0)",
+ "skewx(30grad)",
+ "skewx(30rad)",
+ "skewx(0.08turn)",
+ "skewy(30deg)",
+ "skewy(30grad)",
+ "skewy(30rad)",
+ "skewy(0.08turn)",
+ "rotate(45deg) scale(2, 1)",
+ "skewx(45deg) skewx(-50grad)",
+ "translate(0, 0) scale(1, 1) skewx(0) skewy(0) matrix(1, 0, 0, 1, 0, 0)",
+ "translatex(50%)",
+ "translatey(50%)",
+ "translate(50%)",
+ "translate(3%, 5px)",
+ "translate(5px, 3%)",
+ "matrix(1, 2, 3, 4, 5, 6)",
+ /* valid calc() values */
+ "translatex(calc(5px + 10%))",
+ "translatey(calc(0.25 * 5px + 10% / 3))",
+ "translate(calc(5px - 10% * 3))",
+ "translate(calc(5px - 3 * 10%), 50px)",
+ "translate(-50px, calc(5px - 10% * 3))",
+ "translatez(1px)",
+ "translatez(4em)",
+ "translatez(-4px)",
+ "translatez(0px)",
+ "translatez(2px) translatez(5px)",
+ "translate3d(3px, 4px, 5px)",
+ "translate3d(2em, 3px, 1em)",
+ "translatex(2px) translate3d(4px, 5px, 6px) translatey(1px)",
+ "scale3d(4, 4, 4)",
+ "scale3d(4%, 4%, 4%)",
+ "scale3d(-2, 3, -7)",
+ "scale3d(-2%, 3%, -7%)",
+ "scalez(4)",
+ "scalez(4%)",
+ "scalez(-6)",
+ "scalez(-6%)",
+ "rotate3d(2, 3, 4, 45deg)",
+ "rotate3d(-3, 7, 0, 12rad)",
+ "rotatex(15deg)",
+ "rotatey(-12grad)",
+ "rotatez(72rad)",
+ "rotatex(0.125turn)",
+ "perspective(0px)",
+ "perspective(1000px)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)",
+ "translate(10px, calc(min(5px,10%)))",
+ "translate(calc(max(5px,10%)), 10%)",
+ "translate(max(5px,10%), 10%)",
+ ],
+ invalid_values: [
+ "1px",
+ "#0000ff",
+ "red",
+ "auto",
+ "translatex(1)",
+ "translatey(1)",
+ "translate(2)",
+ "translate(-3, -4)",
+ "translatex(1px 1px)",
+ "translatex(translatex(1px))",
+ "translatex(#0000ff)",
+ "translatex(red)",
+ "translatey()",
+ "matrix(1px, 2px, 3px, 4px, 5px, 6px)",
+ "skewx(red)",
+ "matrix(1%, 0, 0, 0, 0px, 0px)",
+ "matrix(0, 1%, 2, 3, 4px,5px)",
+ "matrix(0, 1, 2%, 3, 4px, 5px)",
+ "matrix(0, 1, 2, 3%, 4%, 5%)",
+ "matrix(1, 2, 3, 4, 5px, 6%)",
+ "matrix(1, 2, 3, 4, 5%, 6px)",
+ "matrix(1, 2, 3, 4, 5%, 6%)",
+ "matrix(1, 2, 3, 4, 5px, 6em)",
+ /* invalid calc() values */
+ "translatey(-moz-min(5px,10%))",
+ "translatex(-moz-max(5px,10%))",
+ "matrix(1, 0, 0, 1, max(5px * 3), calc(10% - 3px))",
+ "perspective(-10px)",
+ "matrix3d(dinosaur)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15%, 16)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16px)",
+ "rotatey(words)",
+ "rotatex(7)",
+ "translate3d(3px, 4px, 1px, 7px)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13px, 14em, 15px, 16)",
+ "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20%, 10%, 15, 16)",
+ ],
+ };
+
+ gCSSProperties["-moz-window-transform-origin"] = {
+ // domProp: "MozWindowTransformOrigin",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ /* no subproperties */
+ prerequisites: { width: "10px", height: "10px", display: "block" },
+ initial_values: ["50% 50%", "center", "center center"],
+ other_values: [
+ "25% 25%",
+ "6px 5px",
+ "20% 3em",
+ "0 0",
+ "0in 1in",
+ "top",
+ "bottom",
+ "top left",
+ "top right",
+ "top center",
+ "center left",
+ "center right",
+ "bottom left",
+ "bottom right",
+ "bottom center",
+ "20% center",
+ "6px center",
+ "13in bottom",
+ "left 50px",
+ "right 13%",
+ "center 40px",
+ "calc(20px)",
+ "calc(20px) 10px",
+ "10px calc(20px)",
+ "calc(20px) 25%",
+ "25% calc(20px)",
+ "calc(20px) calc(20px)",
+ "calc(20px + 1em) calc(20px / 2)",
+ "calc(20px + 50%) calc(50% - 10px)",
+ "calc(-20px) calc(-50%)",
+ "calc(-20%) calc(-50%)",
+ ],
+ invalid_values: [
+ "red",
+ "auto",
+ "none",
+ "0.5 0.5",
+ "40px #0000ff",
+ "border",
+ "center red",
+ "right diagonal",
+ "#00ffff bottom",
+ "0px calc(0px + rubbish)",
+ "0px 0px calc(0px + rubbish)",
+ "6px 5px 5px",
+ "top center 10px",
+ ],
+ };
+
+ gCSSProperties["-moz-context-properties"] = {
+ //domProp: "MozContextProperties",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "fill",
+ "stroke",
+ "fill, stroke",
+ "fill, stroke, fill",
+ "fill, foo",
+ "foo",
+ ],
+ invalid_values: [
+ "default",
+ "fill, auto",
+ "all, stroke",
+ "none, fill",
+ "fill, none",
+ "fill, default",
+ "2px",
+ ],
+ };
+}
+
+gCSSProperties["scrollbar-color"] = {
+ domProp: "scrollbarColor",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["red green", "blue yellow", "#ffff00 white"],
+ invalid_values: ["ffff00 red", "auto red", "red auto", "green"],
+};
+
+gCSSProperties["scrollbar-width"] = {
+ domProp: "scrollbarWidth",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none", "thin"],
+ invalid_values: ["1px"],
+};
+
+gCSSProperties["offset"] = {
+ domProp: "offset",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: [
+ "offset-path",
+ "offset-distance",
+ "offset-rotate",
+ "offset-anchor",
+ ],
+ initial_values: ["none"],
+ other_values: [
+ "none 30deg reverse",
+ "none 50px reverse 30deg",
+ "none calc(10px + 20%) auto",
+ "none reverse",
+ "none / left center",
+ "path('M 0 0 H 1') -200% auto",
+ "path('M 0 0 H 1') -200%",
+ "path('M 0 0 H 1') 50px",
+ "path('M 0 0 H 1') auto",
+ "path('M 0 0 H 1') reverse 30deg 50px",
+ "path('M 0 0 H 1')",
+ "path('m 20 0 h 100') -7rad 8px / auto",
+ "path('m 0 30 v 100') -7rad 8px / left top",
+ "path('m 0 0 h 100') -7rad 8px",
+ "path('M 0 0 H 100') 100px 0deg",
+ ],
+ invalid_values: [
+ "100px 0deg path('m 0 0 h 100')",
+ "30deg",
+ "auto 30deg 100px",
+ "auto / none",
+ "none /",
+ "none / 100px 20px 30deg",
+ "path('M 20 30 A 60 70 80') bottom",
+ "path('M 20 30 A 60 70 80') bottom top",
+ "path('M 20 30 A 60 70 80') 100px 200px",
+ "path('M 20 30 A 60 70 80') reverse auto",
+ "path('M 20 30 A 60 70 80') reverse 10px 30deg",
+ "path('M 20 30 A 60 70 80') /",
+ ],
+};
+
+gCSSProperties["offset-path"] = {
+ domProp: "offsetPath",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [...pathValues.other_values],
+ invalid_values: ["path('')"].concat(pathValues.invalid_values),
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.motion-path-ray.enabled")) {
+ gCSSProperties["offset-path"]["other_values"].push(
+ "ray(0deg)",
+ "ray(45deg closest-side)",
+ "ray(0rad farthest-side)",
+ "ray(0.5turn closest-corner contain)",
+ "ray(200grad farthest-corner)",
+ "ray(sides 180deg)",
+ "ray(contain farthest-side 180deg)",
+ "ray(calc(180deg - 45deg) farthest-side)",
+ "ray(0deg at center center)",
+ "ray(at 10% 10% 1rad)"
+ );
+
+ gCSSProperties["offset-path"]["invalid_values"].push(
+ "ray(closest-side)",
+ "ray(0deg, closest-side)",
+ "ray(contain 0deg closest-side contain)"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.motion-path-basic-shapes.enabled")) {
+ gCSSProperties["offset-path"]["other_values"].push(
+ ...basicShapeOtherValues,
+ ...basicShapeXywhRectValues
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.motion-path-url.enabled")) {
+ gCSSProperties["offset-path"]["other_values"].push("url(#svgPath)");
+}
+
+gCSSProperties["offset-distance"] = {
+ domProp: "offsetDistance",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: ["10px", "10%", "190%", "-280%", "calc(30px + 40%)"],
+ invalid_values: ["none", "45deg"],
+};
+
+gCSSProperties["offset-rotate"] = {
+ domProp: "offsetRotate",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["reverse", "0deg", "0rad reverse", "-45deg", "5turn auto"],
+ invalid_values: ["none", "10px", "reverse 0deg reverse", "reverse auto"],
+};
+
+gCSSProperties["offset-anchor"] = {
+ domProp: "offsetAnchor",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: [
+ "left bottom",
+ "center center",
+ "calc(20% + 10px) center",
+ "right 30em",
+ "10px 20%",
+ "left -10px top -20%",
+ "right 10% bottom 20em",
+ ],
+ invalid_values: ["none", "10deg", "left 10% top"],
+};
+
+if (
+ IsCSSPropertyPrefEnabled("layout.css.motion-path-offset-position.enabled")
+) {
+ gCSSProperties["offset"]["subproperties"].push("offset-position");
+ gCSSProperties["offset"]["other_values"].push("top right / top left");
+
+ if (IsCSSPropertyPrefEnabled("layout.css.motion-path-ray.enabled")) {
+ gCSSProperties["offset"]["other_values"].push(
+ "top right ray(45deg closest-side)",
+ "50% 50% ray(0rad farthest-side)"
+ );
+ }
+
+ gCSSProperties["offset-position"] = {
+ domProp: "offsetPosition",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: [
+ "auto",
+ "left bottom",
+ "center center",
+ "calc(20% + 10px) center",
+ "right 30em",
+ "10px 20%",
+ "left -10px top -20%",
+ "right 10% bottom 20em",
+ ],
+ invalid_values: ["none", "10deg", "left 10% top"],
+ };
+}
+
+{
+ let linear_function_other_values = [
+ "linear(0, 1)",
+ "linear(0 0% 50%, 1 50% 100%)",
+ ];
+
+ let linear_function_invalid_values = [
+ "linear()",
+ "linear(0.5)",
+ "linear(0% 0 100%)",
+ "linear(0,)",
+ ];
+ gCSSProperties["animation-timing-function"].other_values.push(
+ ...linear_function_other_values
+ );
+ gCSSProperties["animation-timing-function"].invalid_values.push(
+ ...linear_function_invalid_values
+ );
+
+ gCSSProperties["transition-timing-function"].other_values.push(
+ ...linear_function_other_values
+ );
+ gCSSProperties["transition-timing-function"].invalid_values.push(
+ ...linear_function_invalid_values
+ );
+
+ gCSSProperties["animation"].other_values.push(
+ "1s 2s linear(0, 1) bounce",
+ "4s linear(0, 0.5 25% 75%, 1 100% 100%)"
+ );
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.backdrop-filter.enabled")) {
+ gCSSProperties["backdrop-filter"] = {
+ domProp: "backdropFilter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: gCSSProperties["filter"].other_values,
+ invalid_values: gCSSProperties["filter"].invalid_values,
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.math-depth.enabled")) {
+ gCSSProperties["math-depth"] = {
+ domProp: "mathDepth",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["0"],
+ other_values: [
+ // auto-add cannot be tested here because it has no effect when the
+ // inherited math-style is equal to the default (normal).
+ "123",
+ "-123",
+ "add(123)",
+ "add(-123)",
+ "calc(1 + 2*3)",
+ "add(calc(4 - 2/3))",
+ ],
+ invalid_values: ["auto", "1,23", "1.23", "add(1,23)", "add(1.23)"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.math-style.enabled")) {
+ gCSSProperties["math-style"] = {
+ domProp: "mathStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal"],
+ other_values: ["compact"],
+ invalid_values: [],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.forced-color-adjust.enabled")) {
+ gCSSProperties["forced-color-adjust"] = {
+ domProp: "forcedColorAdjust",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["none"],
+ invalid_values: [],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.animation-composition.enabled")) {
+ gCSSProperties["animation-composition"] = {
+ domProp: "animationComposition",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["replace"],
+ other_values: [
+ "add",
+ "accumulate",
+ "replace, add",
+ "add, accumulate",
+ "replace, add, accumulate",
+ ],
+ invalid_values: ["all", "none"],
+ };
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.animations")) {
+ Object.assign(gCSSProperties, {
+ "-moz-animation": {
+ domProp: "MozAnimation",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ alias_for: "animation",
+ subproperties: [
+ "animation-name",
+ "animation-duration",
+ "animation-timing-function",
+ "animation-delay",
+ "animation-direction",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-play-state",
+ ],
+ },
+ "-moz-animation-delay": {
+ domProp: "MozAnimationDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-delay",
+ subproperties: ["animation-delay"],
+ },
+ "-moz-animation-direction": {
+ domProp: "MozAnimationDirection",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-direction",
+ subproperties: ["animation-direction"],
+ },
+ "-moz-animation-duration": {
+ domProp: "MozAnimationDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-duration",
+ subproperties: ["animation-duration"],
+ },
+ "-moz-animation-fill-mode": {
+ domProp: "MozAnimationFillMode",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-fill-mode",
+ subproperties: ["animation-fill-mode"],
+ },
+ "-moz-animation-iteration-count": {
+ domProp: "MozAnimationIterationCount",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-iteration-count",
+ subproperties: ["animation-iteration-count"],
+ },
+ "-moz-animation-name": {
+ domProp: "MozAnimationName",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-name",
+ subproperties: ["animation-name"],
+ },
+ "-moz-animation-play-state": {
+ domProp: "MozAnimationPlayState",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-play-state",
+ subproperties: ["animation-play-state"],
+ },
+ "-moz-animation-timing-function": {
+ domProp: "MozAnimationTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "animation-timing-function",
+ subproperties: ["animation-timing-function"],
+ },
+ });
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.scroll-driven-animations.enabled")) {
+ // Basically, web-platform-tests should cover most cases, so here we only
+ // put some basic test cases.
+ gCSSProperties["animation"].subproperties.push("animation-timeline");
+ gCSSProperties["animation"].initial_values.push(
+ "none none 0s 0s ease normal running 1.0 auto",
+ "none none auto"
+ );
+ gCSSProperties["animation"].other_values.push(
+ "none none 0s 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) normal running 1.0 auto",
+ "bounce 1s linear 2s timeline",
+ "bounce 1s 2s linear none",
+ "bounce timeline",
+ "2s, 1s bounce timeline",
+ "1s bounce timeline, 2s",
+ "1s bounce none, 2s none auto"
+ );
+
+ gCSSProperties["-moz-animation"].subproperties.push("animation-timeline");
+ gCSSProperties["-webkit-animation"].subproperties.push("animation-timeline");
+
+ gCSSProperties["animation-timeline"] = {
+ domProp: "animationTimeline",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_marker: true,
+ initial_values: ["auto"],
+ other_values: [
+ "none",
+ "all",
+ "ball",
+ "mall",
+ "color",
+ "bounce, bubble, opacity",
+ "foobar",
+ "\\32bounce",
+ "-bounce",
+ "-\\32bounce",
+ "\\32 0bounce",
+ "-\\32 0bounce",
+ "\\2bounce",
+ "-\\2bounce",
+ "scroll()",
+ "scroll(block)",
+ "scroll(inline)",
+ "scroll(horizontal)",
+ "scroll(vertical)",
+ "scroll(root)",
+ "scroll(nearest)",
+ "scroll(inline nearest)",
+ "scroll(vertical root)",
+ "scroll(root horizontal)",
+ "view()",
+ "view(inline)",
+ "view(auto)",
+ "view(auto 1px)",
+ "view(inline auto)",
+ "view(vertical auto auto)",
+ "view(horizontal 1px 1%)",
+ "view(1px 1% block)",
+ ],
+ invalid_values: [
+ "bounce, initial",
+ "initial, bounce",
+ "bounce, inherit",
+ "inherit, bounce",
+ "bounce, unset",
+ "unset, bounce",
+ ],
+ };
+
+ gCSSProperties["scroll-timeline-name"] = {
+ domProp: "scrollTimelineName",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "all",
+ "auto",
+ "ball",
+ "mall",
+ "color",
+ "foobar",
+ "\\32bounce",
+ "-bounce",
+ "-\\32bounce",
+ "\\32 0bounce",
+ "-\\32 0bounce",
+ "\\2bounce",
+ "-\\2bounce",
+ ],
+ invalid_values: ["abc bounce", "10px", "rgb(1, 2, 3)"],
+ };
+
+ gCSSProperties["scroll-timeline-axis"] = {
+ domProp: "scrollTimelineAxis",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["block"],
+ other_values: ["inline", "vertical", "horizontal"],
+ invalid_values: ["auto", "none", "abc"],
+ };
+
+ gCSSProperties["scroll-timeline"] = {
+ domProp: "scrollTimeline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["scroll-timeline-name", "scroll-timeline-axis"],
+ initial_values: ["none block", "none"],
+ other_values: [
+ "auto inline",
+ "bounce inline",
+ "bounce vertical",
+ "\\32bounce inline",
+ "-bounce block",
+ "\\32 0bounce vertical",
+ "-\\32 0bounce horizontal",
+ "a, b, c",
+ "a block, b inline, c vertical",
+ ],
+ invalid_values: ["", "bounce bounce", "horizontal a", "block abc"],
+ };
+
+ gCSSProperties["view-timeline-name"] = {
+ domProp: "viewTimelineName",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["none"],
+ other_values: [
+ "all",
+ "auto",
+ "ball",
+ "mall",
+ "color",
+ "foobar",
+ "\\32bounce",
+ "-bounce",
+ "-\\32bounce",
+ "\\32 0bounce",
+ "-\\32 0bounce",
+ "\\2bounce",
+ "-\\2bounce",
+ "bounce, abc",
+ "none, none",
+ ],
+ invalid_values: ["abc bounce", "10px", "rgb(1, 2, 3)"],
+ };
+
+ gCSSProperties["view-timeline-axis"] = {
+ domProp: "viewTimelineAxis",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["block"],
+ other_values: ["inline", "vertical", "horizontal", "inline, block"],
+ invalid_values: ["auto", "none", "abc", "inline block"],
+ };
+
+ gCSSProperties["view-timeline-inset"] = {
+ domProp: "viewTimelineInset",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["0px", "1%", "1px 1%", "0px 0%", "calc(0px) auto"],
+ invalid_values: ["none", "rgb(1, 2, 3)", "foo bar", "1px 2px 3px"],
+ };
+
+ gCSSProperties["view-timeline"] = {
+ domProp: "viewTimeline",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ subproperties: ["view-timeline-name", "view-timeline-axis"],
+ initial_values: ["none block", "none"],
+ other_values: [
+ "auto inline",
+ "bounce inline",
+ "bounce vertical",
+ "\\32bounce inline",
+ "-bounce block",
+ "\\32 0bounce vertical",
+ "-\\32 0bounce horizontal",
+ "a, b, c",
+ "a block, b inline, c vertical",
+ ],
+ invalid_values: ["", ",", "abc abc", "horizontal a", "block abc"],
+ };
+}
+
+gCSSProperties["scrollbar-gutter"] = {
+ domProp: "scrollbarGutter",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["auto"],
+ other_values: ["stable", "stable both-edges", "both-edges stable"],
+ invalid_values: [
+ "auto stable",
+ "auto both-edges",
+ "both-edges",
+ "stable mirror",
+ // The following values are from scrollbar-gutter extension in CSS
+ // Overflow 4 https://drafts.csswg.org/css-overflow-4/#sbg-ext.
+ "always",
+ "always both-edges",
+ "always force",
+ "always both-edges force",
+ "stable both-edges force",
+ "match-parent",
+ ],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.text-wrap-balance.enabled")) {
+ gCSSProperties["text-wrap-style"] = {
+ domProp: "textWrapStyle",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ applies_to_placeholder: true,
+ applies_to_cue: true,
+ applies_to_marker: true,
+ initial_values: ["auto"],
+ other_values: ["stable", "balance"],
+ invalid_values: ["wrap", "nowrap", "normal"],
+ };
+ gCSSProperties["text-wrap"].subproperties.push("text-wrap-style");
+ gCSSProperties["text-wrap"].other_values.push("stable");
+ gCSSProperties["text-wrap"].other_values.push("balance");
+ gCSSProperties["text-wrap"].other_values.push("wrap stable");
+ gCSSProperties["text-wrap"].other_values.push("nowrap balance");
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.transforms")) {
+ Object.assign(gCSSProperties, {
+ "-moz-transform": {
+ domProp: "MozTransform",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform",
+ subproperties: ["transform"],
+ },
+ "-moz-transform-origin": {
+ domProp: "MozTransformOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-origin",
+ subproperties: ["transform-origin"],
+ },
+ "-moz-perspective-origin": {
+ domProp: "MozPerspectiveOrigin",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective-origin",
+ subproperties: ["perspective-origin"],
+ },
+ "-moz-perspective": {
+ domProp: "MozPerspective",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "perspective",
+ subproperties: ["perspective"],
+ },
+ "-moz-backface-visibility": {
+ domProp: "MozBackfaceVisibility",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "backface-visibility",
+ subproperties: ["backface-visibility"],
+ },
+ "-moz-transform-style": {
+ domProp: "MozTransformStyle",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ alias_for: "transform-style",
+ subproperties: ["transform-style"],
+ },
+ });
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.zoom.enabled")) {
+ Object.assign(gCSSProperties, {
+ zoom: {
+ domProp: "zoom",
+ inherited: false,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: ["normal", "1", "100%", "0", "0%"],
+ other_values: ["1.5", "2", "150%", "200%"],
+ invalid_values: ["-1", "-40%"],
+ },
+ });
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.transitions")) {
+ Object.assign(gCSSProperties, {
+ "-moz-transition": {
+ domProp: "MozTransition",
+ inherited: false,
+ type: CSS_TYPE_TRUE_SHORTHAND,
+ applies_to_marker: true,
+ alias_for: "transition",
+ subproperties: [
+ "transition-property",
+ "transition-duration",
+ "transition-timing-function",
+ "transition-delay",
+ ],
+ },
+ "-moz-transition-delay": {
+ domProp: "MozTransitionDelay",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-delay",
+ subproperties: ["transition-delay"],
+ },
+ "-moz-transition-duration": {
+ domProp: "MozTransitionDuration",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-duration",
+ subproperties: ["transition-duration"],
+ },
+ "-moz-transition-property": {
+ domProp: "MozTransitionProperty",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-property",
+ subproperties: ["transition-property"],
+ },
+ "-moz-transition-timing-function": {
+ domProp: "MozTransitionTimingFunction",
+ inherited: false,
+ type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ applies_to_marker: true,
+ alias_for: "transition-timing-function",
+ subproperties: ["transition-timing-function"],
+ },
+ });
+}
+
+// Copy aliased properties' fields from their alias targets. Keep this logic
+// at the bottom of this file to ensure all the aliased properties are
+// processed.
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ if (entry.alias_for) {
+ var aliasTargetEntry = gCSSProperties[entry.alias_for];
+ if (!aliasTargetEntry) {
+ ok(
+ false,
+ "Alias '" +
+ prop +
+ "' alias_for field, '" +
+ entry.alias_for +
+ "', " +
+ "must be set to a recognized CSS property in gCSSProperties"
+ );
+ } else {
+ // Copy 'values' fields & 'prerequisites' field from aliasTargetEntry:
+ var fieldsToCopy = [
+ "initial_values",
+ "other_values",
+ "invalid_values",
+ "quirks_values",
+ "unbalanced_values",
+ "prerequisites",
+ ];
+
+ fieldsToCopy.forEach(function (fieldName) {
+ // (Don't copy the field if the alias already has something there,
+ // or if the aliased property doesn't have anything to copy.)
+ if (!(fieldName in entry) && fieldName in aliasTargetEntry) {
+ entry[fieldName] = aliasTargetEntry[fieldName];
+ }
+ });
+ }
+ }
+}
diff --git a/layout/style/test/redirect.sjs b/layout/style/test/redirect.sjs
new file mode 100644
index 0000000000..43fec90b5a
--- /dev/null
+++ b/layout/style/test/redirect.sjs
@@ -0,0 +1,4 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", request.queryString, false);
+}
diff --git a/layout/style/test/redundant_font_download.sjs b/layout/style/test/redundant_font_download.sjs
new file mode 100644
index 0000000000..09236563de
--- /dev/null
+++ b/layout/style/test/redundant_font_download.sjs
@@ -0,0 +1,63 @@
+"use strict";
+
+const BinaryOutputStream = Components.Constructor(
+ "@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream"
+);
+
+// this is simply a hex dump of a red square .PNG image
+// prettier-ignore
+const RED_SQUARE =
+ [
+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00,
+ 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x20, 0x08, 0x02, 0x00, 0x00, 0x00, 0xFC,
+ 0x18, 0xED, 0xA3, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
+ 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x28,
+ 0x49, 0x44, 0x41, 0x54, 0x48, 0xC7, 0xED, 0xCD, 0x41, 0x0D,
+ 0x00, 0x00, 0x08, 0x04, 0xA0, 0xD3, 0xFE, 0x9D, 0x35, 0x85,
+ 0x0F, 0x37, 0x28, 0x40, 0x4D, 0x6E, 0x75, 0x04, 0x02, 0x81,
+ 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0xC1, 0x93, 0x60, 0x01,
+ 0xA3, 0xC4, 0x01, 0x3F, 0x58, 0x1D, 0xEF, 0x27, 0x00, 0x00,
+ 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
+ ];
+
+function handleRequest(request, response) {
+ let query = {};
+ request.queryString.split("&").forEach(function (val) {
+ let [name, value] = val.split("=");
+ query[name] = unescape(value);
+ });
+
+ response.setHeader("Cache-Control", "no-cache");
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ let log = getState("bug-879963-request-log") || "";
+
+ let stream = new BinaryOutputStream(response.bodyOutputStream);
+
+ if (query.q == "init") {
+ log = "init"; // initialize the log, and return a PNG image
+ response.setHeader("Content-Type", "image/png", false);
+ stream.writeByteArray(RED_SQUARE);
+ } else if (query.q == "image") {
+ log = log + ";" + query.q;
+ response.setHeader("Content-Type", "image/png", false);
+ stream.writeByteArray(RED_SQUARE);
+ } else if (query.q == "font") {
+ log = log + ";" + query.q;
+ // we don't provide a real font; that's ok, OTS will just reject it
+ response.write("Junk");
+ } else if (query.q == "report") {
+ // don't include the actual "report" request in the log we return
+ response.write(log);
+ } else {
+ log = log + ";" + query.q;
+ response.setStatusLine(request.httpVersion, 404, "Not Found");
+ }
+
+ setState("bug-879963-request-log", log);
+}
diff --git a/layout/style/test/slow_broken_sheet.sjs b/layout/style/test/slow_broken_sheet.sjs
new file mode 100644
index 0000000000..6af03ee4c6
--- /dev/null
+++ b/layout/style/test/slow_broken_sheet.sjs
@@ -0,0 +1,19 @@
+// Make sure our timer stays alive.
+let gTimer;
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine("1.1", 404, "Not Found");
+ response.processAsync();
+
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ // Wait for 1s before responding; this should usually make sure this load comes in last.
+ gTimer.init(
+ () => {
+ response.write("<h1>Hello</h1>");
+ response.finish();
+ },
+ 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/layout/style/test/slow_load.sjs b/layout/style/test/slow_load.sjs
new file mode 100644
index 0000000000..3873f466ce
--- /dev/null
+++ b/layout/style/test/slow_load.sjs
@@ -0,0 +1,29 @@
+// Make sure our timer stays alive.
+let gTimer;
+
+function handleRequest(request, response) {
+ let isCss = request.queryString.indexOf("css") != -1;
+
+ response.setHeader("Content-Type", isCss ? "text/css" : "text/plain", false);
+ response.setStatusLine("1.1", 200, "OK");
+ response.processAsync();
+
+ let time = Date.now();
+
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ // Wait for 1s before responding; this should usually make sure this load comes in last.
+ gTimer.init(
+ () => {
+ if (isCss) {
+ // FIXME(emilio): This clamps the date to the 32-bit integer range which
+ // is what we use to store the z-index... We don't seem to store f64s
+ // anywhere in the specified values...
+ time = time % (Math.pow(2, 31) - 1);
+ response.write(":root { z-index: " + time + "}");
+ }
+ response.finish();
+ },
+ 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/layout/style/test/slow_ok_sheet.sjs b/layout/style/test/slow_ok_sheet.sjs
new file mode 100644
index 0000000000..5673bb2be8
--- /dev/null
+++ b/layout/style/test/slow_ok_sheet.sjs
@@ -0,0 +1,21 @@
+// Make sure our timer stays alive.
+let gTimer;
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/css", false);
+ response.setStatusLine("1.1", 200, "OK");
+ response.processAsync();
+
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ // Wait for 1s before responding; this should usually make sure this load comes in last.
+ gTimer.init(
+ () => {
+ // This sheet _does_ still get applied even though its importing link
+ // overall reports failure...
+ response.write("nosuchelement { background: red }");
+ response.finish();
+ },
+ 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/layout/style/test/sourcemap_css.html b/layout/style/test/sourcemap_css.html
new file mode 100644
index 0000000000..1f29a38d5f
--- /dev/null
+++ b/layout/style/test/sourcemap_css.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for bug 1306887</title>
+ <link rel="stylesheet" type="text/css" href="mapped.css"/>
+ <link rel="stylesheet" type="text/css" href="mapped2.css"/>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1306887">Mozilla Bug 1306887</a>
+ </body>
+</html>
diff --git a/layout/style/test/style_attribute_tests.js b/layout/style/test/style_attribute_tests.js
new file mode 100644
index 0000000000..243ec3380d
--- /dev/null
+++ b/layout/style/test/style_attribute_tests.js
@@ -0,0 +1,25 @@
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("load", runTests);
+
+function runTests(event) {
+ if (event.target != document) {
+ return;
+ }
+
+ var elt = document.getElementById("content");
+
+ elt.setAttribute("style", "color: blue; background-color: fuchsia");
+ is(elt.style.color, "blue", "setting correct style attribute (color)");
+ is(
+ elt.style.backgroundColor,
+ "fuchsia",
+ "setting correct style attribute (color)"
+ );
+
+ elt.setAttribute("style", "{color: blue; background-color: fuchsia}");
+ is(elt.style.color, "", "setting braced style attribute (color)");
+ is(elt.style.backgroundColor, "", "setting braced style attribute (color)");
+
+ SimpleTest.finish();
+}
diff --git a/layout/style/test/support/1x1-transparent.png b/layout/style/test/support/1x1-transparent.png
new file mode 100644
index 0000000000..56cd9eb930
--- /dev/null
+++ b/layout/style/test/support/1x1-transparent.png
Binary files differ
diff --git a/layout/style/test/support/blue-100x100.png b/layout/style/test/support/blue-100x100.png
new file mode 100644
index 0000000000..3b72d5ce53
--- /dev/null
+++ b/layout/style/test/support/blue-100x100.png
Binary files differ
diff --git a/layout/style/test/support/external-variable-url.css b/layout/style/test/support/external-variable-url.css
new file mode 100644
index 0000000000..d730ac0cea
--- /dev/null
+++ b/layout/style/test/support/external-variable-url.css
@@ -0,0 +1,3 @@
+#t4 {
+ --a: url('image.png');
+}
diff --git a/layout/style/test/test_acid3_test46.html b/layout/style/test/test_acid3_test46.html
new file mode 100644
index 0000000000..4ec50cfddc
--- /dev/null
+++ b/layout/style/test/test_acid3_test46.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=156716
+-->
+<!--
+
+This is test 46 from the Acid3 test, http://acid3.acidtests.org/
+extracted from the test framework there and put into Mochitest.
+
+(from irc.mozilla.org, developers)
+[2008-05-14 18:07:38] <Hixie> dbaron: I hereby grant all files available from the server http://acid3.acidtests.org/ under the following license: (c) copyright 2008 Ian Hickson. These documents may be used under the terms of any of the following licenses: MPL. GPL. LGPL. BSD.
+
+-->
+<head>
+ <title>Test for Bug 156716</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style type="text/css">
+ iframe#selectors { width: 0; height: 0; }
+ </style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=156716">Mozilla Bug 156716</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 156716 **/
+SimpleTest.waitForExplicitFinish();
+function runTest() {
+
+ function getTestDocument() {
+ var iframe = document.getElementById("selectors");
+ var doc = iframe.contentDocument;
+ for (var i = doc.documentElement.childNodes.length-1; i >= 0; i -= 1)
+ doc.documentElement.removeChild(doc.documentElement.childNodes[i]);
+ doc.documentElement.appendChild(doc.createElement('head'));
+ doc.documentElement.firstChild.appendChild(doc.createElement('title'));
+ doc.documentElement.appendChild(doc.createElement('body'));
+ return doc;
+ }
+
+ // test 46: media queries
+ var doc = getTestDocument();
+ var style = doc.createElement('style');
+ style.setAttribute('type', 'text/css');
+ style.appendChild(doc.createTextNode('@media all and (min-color: 0) { #a { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media not all and (min-color: 0) { #b { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media only all and (min-color: 0) { #c { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media (bogus) { #d { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and (bogus) { #e { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media not all and (bogus) { #f { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media only all and (bogus) { #g { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media (bogus), all { #h { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all and (bogus), all { #i { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media not all and (bogus), all { #j { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media only all and (bogus), all { #k { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all, (bogus) { #l { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all, all and (bogus) { #m { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all, not all and (bogus) { #n { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all, only all and (bogus) { #o { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all and color { #p { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and min-color: 0 { #q { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all, all and color { #r { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all, all and min-color: 0 { #s { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and min-color: 0, all { #t { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media (max-color: 0) and (max-monochrome: 0) { #u { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media (min-color: 1), (min-monochrome: 1) { #v { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all and (min-color: 0) and (min-monochrome: 0) { #w { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media not all and (min-color: 1), not all and (min-monochrome: 1) { #x { text-transform: uppercase; } }')); // matches
+ style.appendChild(doc.createTextNode('@media all and (min-height: 1em) and (min-width: 1em) { #y1 { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and (max-height: 1em) and (min-width: 1em) { #y2 { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and (min-height: 1em) and (max-width: 1em) { #y3 { text-transform: uppercase; } }'));
+ style.appendChild(doc.createTextNode('@media all and (max-height: 1em) and (max-width: 1em) { #y4 { text-transform: uppercase; } }')); // matches
+ doc.getElementsByTagName('head')[0].appendChild(style);
+ var names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y1', 'y2', 'y3', 'y4'];
+ for (var i in names) {
+ let p = doc.createElement('p');
+ p.id = names[i];
+ doc.body.appendChild(p);
+ }
+ var count = 0;
+ var check = function (c, e) {
+ count += 1;
+ let p = doc.getElementById(c);
+ is(doc.defaultView.getComputedStyle(p).textTransform, e ? 'uppercase' : 'none', "case " + c + " failed (index " + count + ")");
+ }
+ check('a', true); // 1
+ check('b', false);
+ check('c', true);
+ check('d', false);
+ check('e', false);
+ check('f', false); // true in old spec; commented out in real Acid3
+ check('g', false);
+ check('h', true);
+ check('i', true);
+ check('j', true); // 10
+ check('k', true);
+ check('l', true);
+ check('m', true);
+ check('n', true);
+ check('o', true);
+ check('p', false);
+ check('q', false);
+ check('r', true); // false in old spec
+ check('s', true); // false in old spec
+ check('t', true); // 20 - false in old spec
+ check('u', false);
+ check('v', true);
+ check('w', true);
+ check('x', true);
+ // here the viewport is 0x0
+ check('y1', false); // 25
+ check('y2', false);
+ check('y3', false);
+ check('y4', true);
+ document.getElementById("selectors").setAttribute("style", "height: 100px; width: 100px");
+ // now the viewport is more than 1em by 1em
+ check('y1', true); // 29
+ check('y2', false);
+ check('y3', false);
+ check('y4', false);
+ document.getElementById("selectors").removeAttribute("style");
+ // here the viewport is 0x0 again
+ check('y1', false); // 33
+ check('y2', false);
+ check('y3', false);
+ check('y4', true);
+ SimpleTest.finish();
+}
+</script>
+</pre>
+<p id="display">
+ <iframe src="empty.html" id="selectors" onload="runTest()"></iframe>
+</p>
+</body>
+</html>
diff --git a/layout/style/test/test_addSheet.html b/layout/style/test/test_addSheet.html
new file mode 100644
index 0000000000..06f9f93fc3
--- /dev/null
+++ b/layout/style/test/test_addSheet.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for addSheet</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1024707">Mozilla Bug 1024707</a>
+
+<iframe id="iframe1" src="additional_sheets_helper.html"></iframe>
+<iframe id="iframe2" src="additional_sheets_helper.html"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+
+let gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+
+let gSSService = SpecialPowers.Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(SpecialPowers.Ci.nsIStyleSheetService);
+
+function test(win, sheet) {
+ let cs = win.getComputedStyle(win.document.body);
+ is(cs.getPropertyValue('color'), "rgb(0, 0, 0)", "should have default color");
+ var windowUtils = SpecialPowers.getDOMWindowUtils(win);
+ windowUtils.addSheet(sheet, SpecialPowers.Ci.nsIDOMWindowUtils.USER_SHEET);
+ is(cs.getPropertyValue('color'), "rgb(255, 0, 0)", "should have changed color to red");
+}
+
+function run() {
+ var uri = gIOService.newURI("data:text/css,body{color:red;}");
+ let sheet = gSSService.preloadSheet(uri, SpecialPowers.Ci.nsIStyleSheetService.USER_SHEET);
+
+ test(document.getElementById("iframe1").contentWindow, sheet);
+ test(document.getElementById("iframe2").contentWindow, sheet);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_additional_sheets.html b/layout/style/test/test_additional_sheets.html
new file mode 100644
index 0000000000..8cd8ffd93a
--- /dev/null
+++ b/layout/style/test/test_additional_sheets.html
@@ -0,0 +1,310 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for additional sheets</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=737003">Mozilla Bug 737003</a>
+<iframe id="iframe" src="additional_sheets_helper.html"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService)
+
+var gSSService = SpecialPowers.Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(SpecialPowers.Ci.nsIStyleSheetService);
+
+function getUri(style)
+{
+ return "data:text/css," + style;
+}
+
+function getStyle(color, swapped)
+{
+ return "body {color: " + color + (swapped ? " !important" : "") +
+ "; background-color: " + color + (swapped ? "" : " !important;") + ";}";
+}
+
+function loadUserSheet(win, style)
+{
+ loadSheet(win, style, "USER_SHEET");
+}
+
+function loadAgentSheet(win, style)
+{
+ loadSheet(win, style, "AGENT_SHEET");
+}
+
+function loadAuthorSheet(win, style)
+{
+ loadSheet(win, style, "AUTHOR_SHEET");
+}
+
+function removeUserSheet(win, style)
+{
+ removeSheet(win, style, "USER_SHEET");
+}
+
+function removeAgentSheet(win, style)
+{
+ removeSheet(win, style, "AGENT_SHEET");
+}
+
+function removeAuthorSheet(win, style)
+{
+ removeSheet(win, style, "AUTHOR_SHEET");
+}
+
+function loadSheet(win, style, type)
+{
+ var uri = gIOService.newURI(getUri(style));
+ var windowUtils = SpecialPowers.getDOMWindowUtils(win);
+ windowUtils.loadSheet(uri, windowUtils[type]);
+}
+
+function removeSheet(win, style, type)
+{
+ var uri = gIOService.newURI(getUri(style));
+ var windowUtils = SpecialPowers.getDOMWindowUtils(win);
+ windowUtils.removeSheet(uri, windowUtils[type]);
+}
+
+function loadAndRegisterUserSheet(win, style)
+{
+ loadAndRegisterSheet(win, style, "USER_SHEET");
+}
+
+function loadAndRegisterAgentSheet(win, style)
+{
+ loadAndRegisterSheet(win, style, "AGENT_SHEET");
+}
+
+function loadAndRegisterAuthorSheet(win, style)
+{
+ loadAndRegisterSheet(win, style, "AUTHOR_SHEET");
+}
+
+function unregisterUserSheet(win, style)
+{
+ unregisterSheet(win, style, "USER_SHEET");
+}
+
+function unregisterAgentSheet(win, style)
+{
+ unregisterSheet(win, style, "AGENT_SHEET");
+}
+
+function unregisterAuthorSheet(win, style)
+{
+ unregisterSheet(win, style, "AUTHOR_SHEET");
+}
+
+function loadAndRegisterSheet(win, style, type)
+{
+ uri = gIOService.newURI(getUri(style));
+ gSSService.loadAndRegisterSheet(uri, gSSService[type]);
+ is(gSSService.sheetRegistered(uri, gSSService[type]), true);
+}
+
+function unregisterSheet(win, style, type)
+{
+ var uri = gIOService.newURI(getUri(style));
+ gSSService.unregisterSheet(uri, gSSService[type]);
+ is(gSSService.sheetRegistered(uri, gSSService[type]), false);
+}
+
+function setDocSheet(win, style)
+{
+ var subdoc = win.document;
+ var headID = subdoc.getElementsByTagName("head")[0];
+ var cssNode = subdoc.createElement('style');
+ cssNode.type = 'text/css';
+ cssNode.innerHTML = style;
+ cssNode.id = 'docsheet';
+ headID.appendChild(cssNode);
+}
+
+function removeDocSheet(win)
+{
+ var subdoc = win.document;
+ var node = subdoc.getElementById('docsheet');
+ node.remove();
+}
+
+var agent = {
+ type: 'agent',
+ color: 'rgb(255, 0, 0)',
+ addRules: loadAndRegisterAgentSheet,
+ removeRules: unregisterAgentSheet
+};
+
+var user = {
+ type: 'user',
+ color: 'rgb(0, 255, 0)',
+ addRules: loadAndRegisterUserSheet,
+ removeRules: unregisterUserSheet
+};
+
+var additionalAgent = {
+ type: 'additionalAgent',
+ color: 'rgb(0, 0, 255)',
+ addRules: loadAgentSheet,
+ removeRules: removeAgentSheet
+};
+
+var additionalUser = {
+ type: 'additionalUser',
+ color: 'rgb(255, 255, 0)',
+ addRules: loadUserSheet,
+ removeRules: removeUserSheet
+};
+
+var additionalAuthor = {
+ type: 'additionalAuthor',
+ color: 'rgb(255, 255, 0)',
+ addRules: loadAuthorSheet,
+ removeRules: removeAuthorSheet
+};
+
+var doc = {
+ type: 'doc',
+ color: 'rgb(0, 255, 255)',
+ addRules: setDocSheet,
+ removeRules: removeDocSheet
+};
+
+var author = {
+ type: 'author',
+ color: 'rgb(255, 0, 255)',
+ addRules: loadAndRegisterAuthorSheet,
+ removeRules: unregisterAuthorSheet
+};
+
+function loadAndCheck(win, firstType, secondType, swap, result1, result2)
+{
+ var firstStyle = getStyle(firstType.color, false);
+ var secondStyle = getStyle(secondType.color, swap);
+
+ firstType.addRules(win, firstStyle);
+ secondType.addRules(win, secondStyle);
+
+ var cs = win.getComputedStyle(win.document.body);
+ is(cs.getPropertyValue('color'), result1,
+ firstType.type + "(normal)" + " vs " + secondType.type + (swap ? "(important)" : "(normal)" ) + " 1");
+ is(cs.getPropertyValue('background-color'), result2,
+ firstType.type + "(important)" + " vs " + secondType.type + (swap ? "(normal)" : "(important)" ) + " 2");
+
+ firstType.removeRules(win, firstStyle);
+ secondType.removeRules(win, secondStyle);
+
+ is(cs.getPropertyValue('color'), 'rgb(0, 0, 0)', firstType.type + " vs " + secondType.type + " 3");
+ is(cs.getPropertyValue('background-color'), 'rgba(0, 0, 0, 0)', firstType.type + " vs " + secondType.type + " 4");
+}
+
+// There are 8 cases. Regular against regular, regular against important, important
+// against regular, important against important. We can load style from typeA first
+// then typeB or the other way around so that's 4*2=8 cases.
+
+function testStyleVsStyle(win, typeA, typeB, results)
+{
+ function color(res)
+ {
+ return res ? typeB.color : typeA.color;
+ }
+
+ loadAndCheck(win, typeA, typeB, false, color(results.AB.rr), color(results.AB.ii));
+ loadAndCheck(win, typeB, typeA, false, color(results.BA.rr), color(results.BA.ii));
+
+ loadAndCheck(win, typeA, typeB, true, color(results.AB.ri), color(results.AB.ir));
+ loadAndCheck(win, typeB, typeA, true, color(results.BA.ir), color(results.BA.ri));
+}
+
+// 5 user agent normal declarations
+// 4 user normal declarations
+// 3 author normal declarations
+// 2 author important declarations
+// 1 user important declarations
+// 0 user agent important declarations
+
+function run()
+{
+ var iframe = document.getElementById("iframe");
+ var win = iframe.contentWindow;
+
+// Some explanation how to interpret this result table...
+// in case of loading the agent style first and the user style later (AB)
+// if there is an important rule in both for let's say color (ii)
+// the rule specified in the agent style will lead (AB.ii == 0)
+// If both rules would be just regular rules the one specified in the user style
+// would lead. (AB.rr == 1). If we would load/add the rules in reverse order that
+// would not change that (BA.rr == 1)
+ testStyleVsStyle(win, agent, user,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, agent, doc,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+
+ testStyleVsStyle(win, additionalUser, agent,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalUser, doc,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAgent, user,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAgent, doc,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+
+ testStyleVsStyle(win, additionalAgent, additionalUser,
+ {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, author, doc,
+ {AB:{rr:0, ii:0, ri:1, ir:0}, BA:{rr:0, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, author, user,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, author, agent,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, author, additionalUser,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, doc,
+ {AB:{rr:0, ii:0, ri:1, ir:0}, BA:{rr:0, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, author,
+ {AB:{rr:0, ii:0, ri:1, ir:0}, BA:{rr:0, ii:0, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, user,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, agent,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ testStyleVsStyle(win, additionalAuthor, additionalUser,
+ {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}});
+
+ // Bug 1228542
+ var url = getStyle('rgb(255, 0, 0)');
+ loadAndRegisterAuthorSheet(win, url);
+ // Avoiding security exception...
+ (new win.Function("document.open()"))();
+ (new win.Function("document.close()"))();
+ unregisterAuthorSheet(win, url);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_align_justify_computed_values.html b/layout/style/test/test_align_justify_computed_values.html
new file mode 100644
index 0000000000..aa13762cb7
--- /dev/null
+++ b/layout/style/test/test_align_justify_computed_values.html
@@ -0,0 +1,484 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696253
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test align/justify-items/self/content computed values</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="position:relative">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696253">Mozilla Bug 696253</a>
+<style>
+#flexContainer, #flexContainerGrid { display: flex; position:relative; }
+#gridContainer, #gridContainerFlex { display: grid; position:relative; }
+#display b, #absChild { position:absolute; }
+</style>
+<div id="display">
+ <div id="myDiv"></div>
+ <div id="flexContainer"><a></a><b></b></div>
+ <div id="gridContainer"><a></a><b></b></div>
+ <div id="flexContainerGrid"><a style="diplay:grid"></a><b style="diplay:grid"></b></div>
+ <div id="gridContainerFlex"><a style="diplay:flex"></a><b style="diplay:flex"></b></div>
+</div>
+<div id="absChild"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+/*
+ * Utility function for getting computed style of "align-self":
+ */
+function getComputedAlignSelf(elem) {
+ return window.getComputedStyle(elem).alignSelf;
+}
+function getComputedAlignItems(elem) {
+ return window.getComputedStyle(elem).alignItems;
+}
+function getComputedAlignContent(elem) {
+ return window.getComputedStyle(elem).alignContent;
+}
+function getComputedJustifySelf(elem) {
+ return window.getComputedStyle(elem).justifySelf;
+}
+function getComputedJustifyItems(elem) {
+ return window.getComputedStyle(elem).justifyItems;
+}
+function getComputedJustifyContent(elem) {
+ return window.getComputedStyle(elem).justifyContent;
+}
+
+/**
+ * Test behavior of 'align-self:auto' (Bug 696253 and Bug 1304012)
+ * ===============================================
+ *
+ * In a previous revision of the CSS Alignment spec, align-self:auto
+ * was required to actually *compute* to the parent's align-items value --
+ * but now, the spec says it simply computes to itself, and it should
+ * only get converted into the parent's align-items value when it's used
+ * in layout. This test verifies that we do indeed have it compute to
+ * itself, regardless of the parent's align-items value.
+ */
+
+/*
+ * Tests for a block node with a parent node:
+ */
+function testGeneralNode(elem) {
+ // Test initial computed style
+ // (Initial value should be 'auto', which should compute to itself)
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "initial computed value of 'align-self' should be 'auto'");
+
+ // Test value after setting align-self explicitly to "auto"
+ elem.style.alignSelf = "auto";
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "computed value of 'align-self: auto' should be 'auto'");
+ elem.style.alignSelf = ""; // clean up
+
+ // Test value after setting align-self explicitly to "inherit"
+ elem.style.alignSelf = "inherit";
+ if (elem.parentNode && elem.parentNode.style) {
+ is(getComputedAlignSelf(elem), getComputedAlignSelf(elem.parentNode),
+ elem.tagName + ": computed value of 'align-self: inherit' " +
+ "should match the value on the parent");
+ } else {
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "computed value of 'align-self: inherit' should be 'auto', " +
+ "when there is no parent");
+ }
+ elem.style.alignSelf = ""; // clean up
+}
+
+/*
+ * Tests that depend on us having a parent node:
+ */
+function testNodeThatHasParent(elem) {
+ // Sanity-check that we actually do have a styleable parent:
+ ok(elem.parentNode && elem.parentNode.style, elem.tagName + ": " +
+ "bug in test -- expecting caller to pass us a node with a parent");
+
+ // Test initial computed style when "align-items" has been set on our parent.
+ // (elem's initial "align-self" value should be "auto", which should compute
+ // to its parent's "align-items" value, which in this case is "center".)
+ elem.parentNode.style.alignItems = "center";
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "initial computed value of 'align-self' should be 'auto', even " +
+ "after changing parent's 'align-items' value");
+
+ // ...and now test computed style after setting "align-self" explicitly to
+ // "auto" (with parent "align-items" still at "center")
+ elem.style.alignSelf = "auto";
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "computed value of 'align-self: auto' should remain 'auto', after " +
+ "being explicitly set");
+
+ elem.style.alignSelf = ""; // clean up
+ elem.parentNode.style.alignItems = ""; // clean up
+
+ // Finally: test computed style after setting "align-self" to "inherit"
+ // and leaving parent at its initial value which should be "auto".
+ elem.style.alignSelf = "inherit";
+ is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " +
+ "computed value of 'align-self: inherit' should take parent's " +
+ "computed 'align-self' value (which should be 'auto', " +
+ "if we haven't explicitly set any other style");
+ elem.style.alignSelf = ""; // clean up
+ }
+
+/*
+ * Main test function
+ */
+function main() {
+ // Test the root node
+ // ==================
+ // (It's special because it has no parent ComputedStyle.)
+
+ var rootNode = document.documentElement;
+
+ // Sanity-check that we actually have the root node, as far as CSS is concerned.
+ // (Note: rootNode.parentNode is a HTMLDocument object -- not an element that
+ // we inherit style from.)
+ ok(!rootNode.parentNode.style,
+ "expecting root node to have no node to inherit style from");
+
+ testGeneralNode(rootNode);
+
+ // Test the body node
+ // ==================
+ // (It's special because it has no grandparent ComputedStyle.)
+
+ var body = document.getElementsByTagName("body")[0];
+ is(body.parentNode, document.documentElement,
+ "expecting body element's parent to be the root node");
+
+ testGeneralNode(body);
+ testNodeThatHasParent(body);
+
+ //
+ // align-items/self tests:
+ //
+ //// Block tests
+ var element = document.body;
+ var child = document.getElementById("display");
+ var absChild = document.getElementById("absChild");
+ is(getComputedAlignItems(element), 'normal', "default align-items value for block container");
+ is(getComputedAlignSelf(child), 'auto', "default align-self value for block child");
+ is(getComputedAlignSelf(absChild), 'auto', "default align-self value for block container abs.pos. child");
+ element.style.alignItems = "end";
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child");
+ is(getComputedAlignSelf(absChild), 'auto', "align-self:auto value persists for block container abs.pos. child");
+ element.style.alignItems = "left";
+ is(getComputedAlignItems(element), 'end', "align-items:left is an invalid declaration");
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto persists for block child");
+ is(getComputedAlignSelf(absChild), 'auto', "align-self:auto value persists for block container abs.pos. child");
+ element.style.alignItems = "right";
+ is(getComputedAlignItems(element), 'end', "align-items:right is an invalid declaration");
+ is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child");
+ is(getComputedAlignSelf(absChild), 'auto', "align-self:auto value persists for block container abs.pos. child");
+
+ //// Flexbox tests
+ function testFlexAlignItemsSelf(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedAlignItems(elem), 'normal', "default align-items value for flex container");
+ is(getComputedAlignSelf(item), 'auto', "default align-self value for flex item");
+ is(getComputedAlignSelf(abs), 'auto', "default align-self value for flex container abs.pos. child");
+ elem.style.alignItems = "flex-end";
+ is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for flex container child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for flex container abs.pos. child");
+ elem.style.alignItems = "left";
+ is(getComputedAlignItems(elem), 'flex-end', "align-items:left is an invalid declaration");
+ elem.style.alignItems = "";
+ }
+ testFlexAlignItemsSelf(document.getElementById("flexContainer"));
+ testFlexAlignItemsSelf(document.getElementById("flexContainerGrid"));
+
+ //// Grid tests
+ function testGridAlignItemsSelf(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedAlignItems(elem), 'normal', "default align-items value for grid container");
+ is(getComputedAlignSelf(item), 'auto', "default align-self value for grid item");
+ is(getComputedAlignSelf(abs), 'auto', "default align-self value for grid container abs.pos. child");
+ elem.style.alignItems = "end";
+ is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for grid container abs.pos. child");
+
+ elem.style.alignItems = "left";
+ is(getComputedAlignItems(elem), 'end', "align-items:left is an invalid declaration");
+ is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for grid container abs.pos. child");
+ elem.style.alignItems = "right";
+ is(getComputedAlignItems(elem), 'end', "align-items:right is an invalid declaration");
+ is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child");
+ is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for grid container abs.pos. child");
+
+ item.style.alignSelf = "";
+ abs.style.alignSelf = "";
+ elem.style.alignItems = "";
+ item.style.alignSelf = "";
+ }
+ testGridAlignItemsSelf(document.getElementById("gridContainer"));
+ testGridAlignItemsSelf(document.getElementById("gridContainerFlex"));
+
+ //
+ // justify-items/self tests:
+ //
+ //// Block tests
+ element = document.body;
+ child = document.getElementById("display");
+ absChild = document.getElementById("absChild");
+ is(getComputedJustifyItems(element), 'normal', "default justify-items value for block container");
+ is(getComputedJustifySelf(child), 'auto', "default justify-self value for block container child");
+ is(getComputedJustifySelf(absChild), 'auto', "default justify-self value for block container abs.pos. child");
+ element.style.justifyItems = "end";
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ is(getComputedJustifySelf(absChild), 'auto', "justify-self:auto value persists for block container abs.pos. child");
+ element.style.justifyItems = "left";
+ is(getComputedJustifyItems(element), 'left', "justify-items:left computes to itself on a block");
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ is(getComputedJustifySelf(absChild), 'auto', "justify-self:auto value persists for block container abs.pos. child");
+ element.style.justifyItems = "right";
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ is(getComputedJustifySelf(absChild), 'auto', "justify-self:auto value persists for block container abs.pos. child");
+ element.style.justifyItems = "safe right";
+ is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child");
+ element.style.justifyItems = "";
+ child.style.justifySelf = "left";
+ is(getComputedJustifySelf(child), 'left', "justify-self:left computes to left on block child");
+ child.style.justifySelf = "right";
+ is(getComputedJustifySelf(child), 'right', "justify-self:right computes to right on block child");
+ child.style.justifySelf = "";
+ absChild.style.justifySelf = "right";
+ is(getComputedJustifySelf(absChild), 'right', "justify-self:right computes to right on block container abs.pos. child");
+
+ //// Flexbox tests
+ function testFlexJustifyItemsSelf(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedJustifyItems(elem), 'normal', "default justify-items value for flex container");
+ is(getComputedJustifySelf(item), 'auto', "default justify-self value for flex item");
+ is(getComputedJustifySelf(abs), 'auto', "default justify-self value for flex container abs.pos. child");
+ elem.style.justifyItems = "flex-end";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for flex container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for flex container abs.pos. child");
+ elem.style.justifyItems = "left";
+ is(getComputedJustifyItems(elem), 'left', "justify-items:left computes to itself for flex container");
+ elem.style.justifyItems = "safe right";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for flex container child");
+ // XXX TODO: add left/right tests (bug 1221565)
+ elem.style.justifyItems = "";
+ }
+ testFlexJustifyItemsSelf(document.getElementById("flexContainer"));
+ testFlexJustifyItemsSelf(document.getElementById("flexContainerGrid"));
+
+ //// Grid tests
+ function testGridJustifyItemsSelf(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedJustifyItems(elem), 'normal', "default justify-items value for grid container");
+ is(getComputedJustifySelf(item), 'auto', "default justify-self value for grid item");
+ is(getComputedJustifySelf(abs), 'auto', "default justify-self value for grid container abs.pos. child");
+ elem.style.justifyItems = "end";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "left";
+ is(getComputedJustifyItems(elem), 'left', "justify-items:left computes to itself for grid container");
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "legacy left";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "right";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "safe right";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ elem.style.justifyItems = "legacy right";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ elem.style.justifyItems = "legacy center";
+ item.style.justifyItems = "inherit";
+ abs.style.justifyItems = "inherit";
+ is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child");
+ is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child");
+ is(getComputedJustifyItems(elem), 'legacy center', "justify-items computes to itself grid container");
+ is(getComputedJustifyItems(item), 'legacy center', "justify-items inherits including legacy keyword to grid item");
+ is(getComputedJustifyItems(abs), 'legacy center', "justify-items inherits including legacy keyword to grid container abs.pos. child");
+ elem.style.justifyItems = "";
+ item.style.justifySelf = "left";
+ is(getComputedJustifySelf(item), 'left', "justify-self:left computes to left on grid item");
+ item.style.justifySelf = "right";
+ is(getComputedJustifySelf(item), 'right', "justify-self:right computes to right on grid item");
+ item.style.justifySelf = "safe right";
+ is(getComputedJustifySelf(item), 'safe right', "justify-self:'safe right' computes to 'safe right' on grid item");
+ item.style.justifySelf = "";
+ abs.style.justifySelf = "right";
+ is(getComputedJustifySelf(abs), 'right', "justify-self:right computes to right on grid container abs.pos. child");
+ abs.style.justifySelf = "";
+ elem.style.justifyItems = "";
+ item.style.justifySelf = "";
+ }
+ testGridJustifyItemsSelf(document.getElementById("gridContainer"));
+ testGridJustifyItemsSelf(document.getElementById("gridContainerFlex"));
+
+ //
+ // align-content tests:
+ //
+ //// Block tests
+ element = document.body;
+ child = document.getElementById("display");
+ absChild = document.getElementById("absChild");
+ is(getComputedAlignContent(element), 'normal', "default align-content value for block container");
+ is(getComputedAlignContent(child), 'normal', "default align-content value for block child");
+ is(getComputedAlignContent(absChild), 'normal', "default align-content value for block container abs.pos. child");
+ element.style.alignContent = "end";
+ is(getComputedAlignContent(child), 'normal', "default align-content isn't affected by parent align-content value for in-flow child");
+ is(getComputedAlignContent(absChild), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child");
+ element.style.alignContent = "left";
+ is(getComputedAlignContent(element), 'end', "align-content:left isn't a valid declaration");
+ is(getComputedAlignContent(absChild), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child");
+ element.style.alignContent = "right";
+ is(getComputedAlignContent(element), 'end', "align-content:right isn't a valid declaration");
+ is(getComputedAlignContent(absChild), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child");
+ element.style.alignContent = "";
+
+ //// Flexbox tests
+ function testFlexAlignContent(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedAlignContent(elem), 'normal', "default align-content value for flex container");
+ is(getComputedAlignContent(item), 'normal', "default align-content value for flex item");
+ is(getComputedAlignContent(abs), 'normal', "default align-content value for flex container abs.pos. child");
+ elem.style.alignContent = "safe end";
+ is(getComputedAlignContent(elem), 'safe end', "align-content:'safe end' computes to itself for flex container");
+ is(getComputedAlignContent(item), 'normal', "default align-content isn't affected by parent align-content value for flex item");
+ is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for flex container abs.pos. child");
+ elem.style.alignContent = "";
+ }
+ testFlexAlignContent(document.getElementById("flexContainer"));
+ testFlexAlignContent(document.getElementById("flexContainerGrid"));
+
+ //// Grid tests
+ function testGridAlignContent(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedAlignContent(elem), 'normal', "default align-content value for grid container");
+ is(getComputedAlignContent(item), 'normal', "default align-content value for grid item");
+ is(getComputedAlignContent(abs), 'normal', "default align-content value for grid container abs.pos. child");
+ elem.style.alignContent = "safe end";
+ is(getComputedAlignContent(elem), 'safe end', "align-content:'safe end' computes to itself on grid container");
+ is(getComputedAlignContent(item), 'normal', "default align-content isn't affected by parent align-content value for grid item");
+ is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for grid container abs.pos. child");
+ elem.style.alignContent = "safe end";
+ item.style.alignContent = "inherit";
+ abs.style.alignContent = "inherit";
+ is(getComputedAlignContent(elem), 'safe end', "align-content:'safe end' computes to 'align-content:safe end' on grid container");
+ is(getComputedAlignContent(item), 'safe end', "align-content:'safe end' inherits as 'align-content:safe end' to grid item");
+ is(getComputedAlignContent(abs), 'safe end', "align-content:'safe end' inherits as 'align-content:safe end' to grid container abs.pos. child");
+ item.style.alignContent = "";
+ abs.style.alignContent = "";
+ elem.style.alignContent = "";
+ item.style.alignContent = "";
+ }
+ testGridAlignContent(document.getElementById("gridContainer"));
+ testGridAlignContent(document.getElementById("gridContainerFlex"));
+
+
+ //
+ // justify-content tests:
+ //
+ //// Block tests
+ element = document.body;
+ child = document.getElementById("display");
+ absChild = document.getElementById("absChild");
+ is(getComputedJustifyContent(element), 'normal', "default justify-content value for block container");
+ is(getComputedJustifyContent(child), 'normal', "default justify-content value for block child");
+ is(getComputedJustifyContent(absChild), 'normal', "default justify-content value for block container abs.pos. child");
+ element.style.justifyContent = "end";
+ is(getComputedJustifyContent(child), 'normal', "default justify-content isn't affected by parent justify-content value for in-flow child");
+ is(getComputedJustifyContent(absChild), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child");
+ element.style.justifyContent = "left";
+ is(getComputedJustifyContent(element), 'left', "justify-content:left computes to left on block child");
+ is(getComputedJustifyContent(absChild), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child");
+ element.style.justifyContent = "right";
+ is(getComputedJustifyContent(element), 'right', "justify-content:right computes to right on block child");
+ is(getComputedJustifyContent(absChild), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child");
+ element.style.justifyContent = "safe right";
+ is(getComputedJustifyContent(element), 'safe right', "justify-content:'safe right' computes to 'justify-content:safe right'");
+ element.style.justifyContent = "";
+ child.style.justifyContent = "left";
+ is(getComputedJustifyContent(child), 'left', "justify-content:left computes to left on block child");
+ child.style.justifyContent = "right";
+ is(getComputedJustifyContent(child), 'right', "justify-content:right computes to right on block child");
+ child.style.justifyContent = "safe left";
+ is(getComputedJustifyContent(child), 'safe left', "justify-content:safe left computes to 'safe left' on block child");
+ child.style.justifyContent = "";
+ absChild.style.justifyContent = "right";
+ is(getComputedJustifyContent(absChild), 'right', "justify-content:right computes to right on block container abs.pos. child");
+ absChild.style.justifyContent = "";
+
+ //// Flexbox tests
+ function testFlexJustifyContent(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedJustifyContent(elem), 'normal', "default justify-content value for flex container");
+ is(getComputedJustifyContent(item), 'normal', "default justify-content value for flex item");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content value for flex container abs.pos. child");
+ elem.style.justifyContent = "safe end";
+ is(getComputedJustifyContent(elem), 'safe end', "justify-content:'safe end' computes to itself for flex container");
+ is(getComputedJustifyContent(item), 'normal', "default justify-content isn't affected by parent justify-content value for flex item");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for flex container abs.pos. child");
+ // XXX TODO: add left/right tests (bug 1221565)
+ elem.style.justifyContent = "";
+ }
+ testFlexJustifyContent(document.getElementById("flexContainer"));
+ testFlexJustifyContent(document.getElementById("flexContainerGrid"));
+
+ //// Grid tests
+ function testGridJustifyContent(elem) {
+ var item = elem.firstChild;
+ var abs = elem.children[1];
+ is(getComputedJustifyContent(elem), 'normal', "default justify-content value for grid container");
+ is(getComputedJustifyContent(item), 'normal', "default justify-content value for grid item");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content value for grid container abs.pos. child");
+ elem.style.justifyContent = "safe end";
+ is(getComputedJustifyContent(elem), 'safe end', "justify-content:'safe end' computes to itself on grid container");
+ is(getComputedJustifyContent(item), 'normal', "default justify-content isn't affected by parent justify-content value for grid item");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for grid container abs.pos. child");
+ elem.style.justifyContent = "left";
+ is(getComputedJustifyContent(elem), 'left', "justify-content:left computes to left on grid container");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for grid container abs.pos. child");
+ elem.style.justifyContent = "right";
+ is(getComputedJustifyContent(elem), 'right', "justify-content:right computes to right on grid container");
+ is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for grid container abs.pos. child");
+ elem.style.justifyContent = "safe right";
+ item.style.justifyContent = "inherit";
+ abs.style.justifyContent = "inherit";
+ is(getComputedJustifyContent(elem), 'safe right', "justify-content:'safe right' computes to 'justify-content:safe right' on grid container");
+ is(getComputedJustifyContent(item), 'safe right', "justify-content:'safe right' inherits as 'justify-content:safe right' to grid item");
+ is(getComputedJustifyContent(abs), 'safe right', "justify-content:'safe right' inherits as 'justify-content:safe right' to grid container abs.pos. child");
+ item.style.justifyContent = "left";
+ is(getComputedJustifyContent(item), 'left', "justify-content:left computes to left on grid item");
+ item.style.justifyContent = "right";
+ is(getComputedJustifyContent(item), 'right', "justify-content:right computes to right on grid item");
+ item.style.justifyContent = "safe right";
+ is(getComputedJustifyContent(item), 'safe right', "justify-content:'safe right' computes to 'safe right' on grid item");
+ item.style.justifyContent = "";
+ abs.style.justifyContent = "right";
+ is(getComputedJustifyContent(abs), 'right', "justify-content:right computes to right on grid container abs.pos. child");
+ abs.style.justifyContent = "";
+ elem.style.justifyContent = "";
+ item.style.justifyContent = "";
+ }
+ testGridJustifyContent(document.getElementById("gridContainer"));
+ testGridJustifyContent(document.getElementById("gridContainerFlex"));
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_all_shorthand.html b/layout/style/test/test_all_shorthand.html
new file mode 100644
index 0000000000..0a3bfd29fd
--- /dev/null
+++ b/layout/style/test/test_all_shorthand.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<title>Test the 'all' shorthand property</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="property_database.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<body onload="runTest()">
+
+<style id="stylesheet">
+#parent { }
+#child { }
+#child { }
+</style>
+
+<div style="display: none">
+ <div id="parent">
+ <div id="child"></div>
+ </div>
+</div>
+
+<script>
+function runTest() {
+ var sheet = document.getElementById("stylesheet").sheet;
+ var parentRule = sheet.cssRules[0];
+ var childRule1 = sheet.cssRules[1];
+ var childRule2 = sheet.cssRules[2];
+ var parent = document.getElementById("parent");
+ var child = document.getElementById("child");
+
+ // Longhand properties that are NOT considered to be subproperties of the 'all'
+ // shorthand.
+ var excludedSubproperties = ["direction", "unicode-bidi"];
+ var excludedSubpropertiesSet = new Set(excludedSubproperties);
+
+ // Longhand properties that are considered to be subproperties of the 'all'
+ // shorthand.
+ var includedSubproperties = Object.keys(gCSSProperties).filter(function(prop) {
+ var info = gCSSProperties[prop];
+ return info.type == CSS_TYPE_LONGHAND &&
+ !excludedSubpropertiesSet.has(prop);
+ });
+
+ // All longhand properties to be tested.
+ var allSubproperties = includedSubproperties.concat(excludedSubproperties);
+
+
+ // First, get the computed value for the initial value and one other value of
+ // each property.
+ var initialComputedValues = new Map();
+ var otherComputedValues = new Map();
+
+ allSubproperties.forEach(function(prop) {
+ parentRule.style.setProperty(prop, "initial", "");
+ initialComputedValues.set(prop, getComputedStyle(parent, "").getPropertyValue(prop));
+ parentRule.style.cssText = "";
+ });
+
+ allSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ otherComputedValues.set(prop, getComputedStyle(parent, "").getPropertyValue(prop));
+ parentRule.style.cssText = "";
+ });
+
+
+ // Test setting all:inherit through setProperty.
+ includedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "initial");
+ childRule2.style.setProperty("all", "inherit");
+ is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop),
+ "computed value for " + prop + " when 'all:inherit' set with setProperty");
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+ excludedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "initial");
+ childRule2.style.setProperty("all", "inherit");
+ is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop),
+ "computed value for excluded subproperty " + prop + " when 'all:inherit' set with setProperty");
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+
+ // Test setting all:initial through setProperty.
+ includedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "inherit");
+ childRule2.style.setProperty("all", "initial");
+ is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop),
+ "computed value for " + prop + " when 'all:initial' set with setProperty");
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+ excludedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, info.other_values[0], "");
+ childRule2.style.setProperty("all", "initial");
+ is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop),
+ "computed value for excluded subproperty " + prop + " when 'all:initial' set with setProperty");
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+
+ // Test setting all:unset through setProperty.
+ includedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ if (info.inherited) {
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "initial", "");
+ childRule2.style.setProperty("all", "unset");
+ is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop),
+ "computed value for " + prop + " when 'all:unset' set with setProperty");
+ } else {
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, info.other_values[0], "");
+ childRule2.style.setProperty("all", "unset");
+ is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop),
+ "computed value for " + prop + " when 'all:unset' set with setProperty");
+ }
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+ excludedSubproperties.forEach(function(prop) {
+ var info = gCSSProperties[prop];
+ if (info.inherited) {
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, "initial", "");
+ childRule2.style.setProperty("all", "unset");
+ is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop),
+ "computed value for excluded subproperty " + prop + " when 'all:unset' set with setProperty");
+ } else {
+ parentRule.style.setProperty(prop, info.other_values[0], "");
+ childRule1.style.setProperty(prop, info.other_values[0], "");
+ childRule2.style.setProperty("all", "unset");
+ is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop),
+ "computed value for excluded subproperty " + prop + " when 'all:unset' set with setProperty");
+ }
+ parentRule.style.cssText = "";
+ childRule1.style.cssText = "";
+ childRule2.style.cssText = "";
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
diff --git a/layout/style/test/test_animations.html b/layout/style/test/test_animations.html
new file mode 100644
index 0000000000..846eb1d2a0
--- /dev/null
+++ b/layout/style/test/test_animations.html
@@ -0,0 +1,2107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435442
+-->
+<!--
+
+ ====== PLEASE KEEP THIS IN SYNC WITH test_animations_omta.html =======
+
+ test_animations_omta.html mimicks the content of this file but with
+ extra machinery for testing animation values on the compositor thread.
+
+ If you are making changes to this file or to test_animations_omta.html, please
+ try to keep them consistent where appropriate.
+
+-->
+<head>
+ <title>Test for css3-animations (Bug 435442)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ @keyframes anim1 {
+ 0% { margin-left: 0px }
+ 50% { margin-left: 80px }
+ 100% { margin-left: 100px }
+ }
+ @keyframes anim2 {
+ from { margin-right: 0 } to { margin-right: 100px }
+ }
+ @keyframes anim3 {
+ from { margin-top: 0 } to { margin-top: 100px }
+ }
+ @keyframes anim4 {
+ from { margin-bottom: 0 } to { margin-bottom: 100px }
+ }
+ @keyframes anim5 {
+ from { margin-left: 0 } to { margin-left: 100px }
+ }
+
+ @keyframes kf1 {
+ 50% { margin-top: 50px }
+ to { margin-top: 150px }
+ }
+ @keyframes kf2 {
+ from { margin-top: 150px }
+ 50% { margin-top: 50px }
+ }
+ @keyframes kf3 {
+ 25% { margin-top: 100px }
+ }
+ @keyframes kf4 {
+ to, from { display: none; margin-top: 37px }
+ }
+ @keyframes kf_cascade1 {
+ from { padding-top: 50px }
+ 50%, from { padding-top: 30px } /* wins: 0% */
+ 75%, 85%, 50% { padding-top: 20px } /* wins: 75%, 50% */
+ 100%, 85% { padding-top: 70px } /* wins: 100% */
+ 85.1% { padding-top: 60px } /* wins: 85.1% */
+ 85% { padding-top: 30px } /* wins: 85% */
+ }
+ @keyframes kf_cascade2 { from, to { margin-top: 100px } }
+ @keyframes kf_cascade2 { from, to { margin-left: 200px } }
+ @keyframes kf_cascade2 { from, to { margin-left: 300px } }
+ @keyframes kf_tf1 {
+ 0% { padding-bottom: 20px; animation-timing-function: ease }
+ 25% { padding-bottom: 60px; }
+ 50% { padding-bottom: 160px; animation-timing-function: steps(5) }
+ 75% { padding-bottom: 120px; animation-timing-function: linear }
+ 100% { padding-bottom: 20px; animation-timing-function: ease-out }
+ }
+
+ @keyframes always_fifty {
+ from, to { margin-left: 50px }
+ }
+
+ #withbefore::before, #withafter::after {
+ content: "";
+ animation: anim2 1s linear alternate 3;
+ }
+
+ @keyframes multiprop {
+ 0% {
+ padding-top: 10px; padding-left: 30px;
+ animation-timing-function: ease;
+ }
+ 25% {
+ padding-left: 50px;
+ animation-timing-function: ease-out;
+ }
+ 50% {
+ padding-top: 40px;
+ }
+ 75% {
+ padding-top: 80px; padding-left: 60px;
+ animation-timing-function: ease-in;
+ }
+ }
+
+ @keyframes uaoverride {
+ 0%, 100% { white-space: pre; margin-top: 20px }
+ 50% { margin-top: 120px }
+ }
+
+ @keyframes cascade {
+ 0%, 25%, 100% { top: 0 }
+ 50%, 75% { top: 100px }
+ 0%, 75%, 100% { left: 0 }
+ 25%, 50% { left: 100px }
+ }
+ @keyframes cascade2 {
+ 0% { text-indent: 0 }
+ 25% { text-indent: 30px; animation-timing-function: ease-in } /* beaten by rule below */
+ 50% { text-indent: 0 }
+ 25% { text-indent: 50px }
+ 100% { text-indent: 100px }
+ }
+
+ @keyframes primitives1 {
+ from { transform: rotate(0deg) translateX(0px) scaleX(1)
+ translate(0px) scale3d(1, 1, 1); }
+ to { transform: rotate(270deg) translate3d(0px, 0px, 0px) scale(1)
+ translateY(0px) scaleY(1); }
+ }
+
+ @keyframes important1 {
+ from { margin-top: 50px; }
+ 50% { margin-top: 150px !important; } /* ignored */
+ to { margin-top: 100px; }
+ }
+
+ @keyframes important2 {
+ from { margin-top: 50px;
+ margin-bottom: 100px; }
+ to { margin-top: 150px !important; /* ignored */
+ margin-bottom: 50px; }
+ }
+
+ @keyframes empty { }
+ @keyframes nearlyempty { to { margin-left: 100px; } }
+
+ @keyframes lowerpriority {
+ 0% {
+ top: 0px;
+ left: 0px;
+ }
+ 100% {
+ top: 100px;
+ left: 100px;
+ }
+ }
+
+ @keyframes overrideleft {
+ 0%, 100% { left: 0px }
+ }
+
+ @keyframes overridetop {
+ 0%, 100% { top: 0px }
+ }
+
+ @keyframes opacitymid {
+ 0% { opacity: 0.2 }
+ 100% { opacity: 0.8 }
+ }
+
+ @keyframes "string name 1" { /* using string for keyframes name */
+ 0%, 100% { left: 1px }
+ }
+
+ @keyframes "string name 2" {
+ 0%, 100% { left: 2px }
+ }
+
+ @keyframes custom\ ident\ 1 {
+ 0%, 100% { left: 3px }
+ }
+
+ @keyframes custom\ ident\ 2 {
+ 0%, 100% { left: 4px }
+ }
+
+ @keyframes "initial" {
+ 0%, 100% { left: 5px }
+ }
+
+ @keyframes initial { /* illegal as an identifier, should be dropped */
+ 0%, 100% { left: 6px }
+ }
+
+ @keyframes "none" {
+ 0%, 100% { left: 7px }
+ }
+
+ @keyframes none { /* illegal as an identifier, should be dropped */
+ 0%, 100% { left: 8px }
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435442">Mozilla Bug 435442</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for css3-animations (Bug 435442) **/
+
+var e = new AnimationEvent("foo",
+ {
+ bubbles: true,
+ cancelable: true,
+ animationName: "name",
+ elapsedTime: 0.5,
+ pseudoElement: "pseudo"
+ });
+is(e.bubbles, true);
+is(e.cancelable, true);
+is(e.animationName, "name");
+is(e.elapsedTime, 0.5);
+is(e.pseudoElement, "pseudo");
+is(e.isTrusted, false)
+
+// Shortcut new_div to update div, cs
+var div, cs;
+var originalNewDiv = window.new_div;
+window.new_div = function(style) {
+ [ div, cs ] = originalNewDiv(style);
+};
+
+// take over the refresh driver right from the start.
+advance_clock(0);
+
+SimpleTest.registerCleanupFunction(() => {
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+});
+
+/*
+ * css3-animations: 2. Animations
+ * http://dev.w3.org/csswg/css3-animations/#animations
+ */
+
+// Test that animations don't affect the computed value before the
+// start of the animation or after its end. Test without
+// animation-fill-mode, but then repeat the test with all the values of
+// animation-fill-mode.
+function test_fill_mode(fill_mode, fills_backwards, fills_forwards)
+{
+ var style = "margin-left: 30px; animation: 10s 3s anim1 linear";
+ var desc;
+ if (fill_mode.length > 0) {
+ style += " " + fill_mode;
+ desc = "fill mode " + fill_mode + ": ";
+ } else {
+ desc = "default fill mode: ";
+ }
+ new_div(style);
+ listen();
+ if (fills_backwards)
+ is(cs.marginLeft, "0px", desc + "does affect value during delay (0s)");
+ else
+ is(cs.marginLeft, "30px", desc + "doesn't affect value during delay (0s)");
+ advance_clock(2000);
+ if (fills_backwards)
+ is(cs.marginLeft, "0px", desc + "does affect value during delay (2s)");
+ else
+ is(cs.marginLeft, "30px", desc + "doesn't affect value during delay (2s)");
+ check_events([], "before start in test_fill_mode");
+ advance_clock(1000);
+ check_events([{ type: 'animationstart', target: div,
+ bubbles: true, cancelable: false,
+ animationName: 'anim1', elapsedTime: 0.0,
+ pseudoElement: "" }],
+ "right after start in test_fill_mode");
+ if (fills_backwards)
+ is(cs.marginLeft, "0px", desc + "affects value at start of animation");
+ advance_clock(125);
+ is(cs.marginLeft, "2px", desc + "affects value during animation");
+ advance_clock(2375);
+ is(cs.marginLeft, "40px", desc + "affects value during animation");
+ advance_clock(2500);
+ is(cs.marginLeft, "80px", desc + "affects value during animation");
+ advance_clock(2500);
+ is(cs.marginLeft, "90px", desc + "affects value during animation");
+ advance_clock(2375);
+ is(cs.marginLeft, "99.5px", desc + "affects value during animation");
+ check_events([], "before end in test_fill_mode");
+ advance_clock(125);
+ check_events([{ type: 'animationend', target: div,
+ bubbles: true, cancelable: false,
+ animationName: 'anim1', elapsedTime: 10.0,
+ pseudoElement: "" }],
+ "right after end in test_fill_mode");
+ if (fills_forwards)
+ is(cs.marginLeft, "100px", desc + "affects value at end of animation");
+ advance_clock(10);
+ if (fills_forwards)
+ is(cs.marginLeft, "100px", desc + "does affect value after animation");
+ else
+ is(cs.marginLeft, "30px", desc + "does not affect value after animation");
+ done_div();
+}
+test_fill_mode("", false, false);
+test_fill_mode("none", false, false);
+test_fill_mode("forwards", false, true);
+test_fill_mode("backwards", true, false);
+test_fill_mode("both", true, true);
+
+// Test that animations continue running when the animation name
+// list is changed.
+new_div("animation: anim1 linear 10s");
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "just anim1, margin-top at start");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "just anim1, margin-right at start");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "just anim1, margin-bottom at start");
+ is(cs.getPropertyValue("margin-left"), "0px",
+ "just anim1, margin-left at start");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "just anim1, margin-top at 1s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "just anim1, margin-right at 1s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "just anim1, margin-bottom at 1s");
+ is(cs.getPropertyValue("margin-left"), "16px",
+ "just anim1, margin-left at 1s");
+// append anim2
+div.style.animation = "anim1 linear 10s, anim2 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim1 + anim2, margin-top at 1s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim1 + anim2, margin-right at 1s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim2, margin-bottom at 1s");
+ is(cs.getPropertyValue("margin-left"), "16px",
+ "anim1 + anim2, margin-left at 1s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim1 + anim2, margin-top at 2s");
+ is(cs.getPropertyValue("margin-right"), "10px",
+ "anim1 + anim2, margin-right at 2s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim2, margin-bottom at 2s");
+ is(cs.getPropertyValue("margin-left"), "32px",
+ "anim1 + anim2, margin-left at 2s");
+// prepend anim3
+div.style.animation = "anim3 linear 10s, anim1 linear 10s, anim2 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim3 + anim1 + anim2, margin-top at 2s");
+ is(cs.getPropertyValue("margin-right"), "10px",
+ "anim3 + anim1 + anim2, margin-right at 2s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim2, margin-bottom at 2s");
+ is(cs.getPropertyValue("margin-left"), "32px",
+ "anim3 + anim1 + anim2, margin-left at 2s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "10px",
+ "anim3 + anim1 + anim2, margin-top at 3s");
+ is(cs.getPropertyValue("margin-right"), "20px",
+ "anim3 + anim1 + anim2, margin-right at 3s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim2, margin-bottom at 3s");
+ is(cs.getPropertyValue("margin-left"), "48px",
+ "anim3 + anim1 + anim2, margin-left at 3s");
+// remove anim2 from end
+div.style.animation = "anim3 linear 10s, anim1 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "10px",
+ "anim3 + anim1, margin-top at 3s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1, margin-right at 3s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1, margin-bottom at 3s");
+ is(cs.getPropertyValue("margin-left"), "48px",
+ "anim3 + anim1, margin-left at 3s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "20px",
+ "anim3 + anim1, margin-top at 4s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1, margin-right at 4s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1, margin-bottom at 4s");
+ is(cs.getPropertyValue("margin-left"), "64px",
+ "anim3 + anim1, margin-left at 4s");
+// swap anim1 and anim3, change duration of anim3
+div.style.animation = "anim1 linear 10s, anim3 linear 5s";
+ is(cs.getPropertyValue("margin-top"), "40px",
+ "anim1 + anim3, margin-top at 4s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim1 + anim3, margin-right at 4s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim3, margin-bottom at 4s");
+ is(cs.getPropertyValue("margin-left"), "64px",
+ "anim1 + anim3, margin-left at 4s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "60px",
+ "anim1 + anim3, margin-top at 5s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim1 + anim3, margin-right at 5s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim3, margin-bottom at 5s");
+ is(cs.getPropertyValue("margin-left"), "80px",
+ "anim1 + anim3, margin-left at 5s");
+// list anim1 twice, last duration wins, original start time still applies
+div.style.animation = "anim1 linear 10s, anim3 linear 5s, anim1 linear 20s";
+ is(cs.getPropertyValue("margin-top"), "60px",
+ "anim1 + anim3 + anim1, margin-top at 5s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim1 + anim3 + anim1, margin-right at 5s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim1 + anim3 + anim1, margin-bottom at 5s");
+ is(cs.getPropertyValue("margin-left"), "40px",
+ "anim1 + anim3 + anim1, margin-left at 5s");
+// drop one of the anim1, and list anim5 as well, which animates
+// the same property as anim1
+div.style.animation = "anim3 linear 5s, anim1 linear 20s, anim5 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "60px",
+ "anim3 + anim1 + anim5, margin-top at 5s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 5s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 5s");
+ is(cs.getPropertyValue("margin-left"), "0px",
+ "anim3 + anim1 + anim5, margin-left at 5s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "80px",
+ "anim3 + anim1 + anim5, margin-top at 6s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 6s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 6s");
+ is(cs.getPropertyValue("margin-left"), "10px",
+ "anim3 + anim1 + anim5, margin-left at 6s");
+// now swap the anim5 and anim1 order
+div.style.animation = "anim3 linear 5s, anim5 linear 10s, anim1 linear 20s";
+ is(cs.getPropertyValue("margin-top"), "80px",
+ "anim3 + anim1 + anim5, margin-top at 6s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 6s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 6s");
+ is(cs.getPropertyValue("margin-left"), "48px",
+ "anim3 + anim1 + anim5, margin-left at 6s");
+advance_clock(1000);
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim3 + anim1 + anim5, margin-top at 7s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 7s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 7s");
+ is(cs.getPropertyValue("margin-left"), "56px",
+ "anim3 + anim1 + anim5, margin-left at 7s");
+// swap anim1 and anim5 back
+div.style.animation = "anim3 linear 5s, anim1 linear 20s, anim5 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim3 + anim1 + anim5, margin-top at 7s");
+ is(cs.getPropertyValue("margin-right"), "0px",
+ "anim3 + anim1 + anim5, margin-right at 7s");
+ is(cs.getPropertyValue("margin-bottom"), "0px",
+ "anim3 + anim1 + anim5, margin-bottom at 7s");
+ is(cs.getPropertyValue("margin-left"), "20px",
+ "anim3 + anim1 + anim5, margin-left at 7s");
+advance_clock(100);
+ is(cs.getPropertyValue("margin-top"), "0px",
+ "anim3 + anim1 + anim5, margin-top at 7.1s");
+// Change the animation fill mode on the completed animation.
+div.style.animation = "anim3 linear 5s forwards, anim1 linear 20s, anim5 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "100px",
+ "anim3 + anim1 + anim5, margin-top at 7.1s, with fill mode");
+advance_clock(900);
+ is(cs.getPropertyValue("margin-top"), "100px",
+ "anim3 + anim1 + anim5, margin-top at 8s, with fill mode");
+// Change the animation duration on the completed animation, so it is
+// no longer completed.
+div.style.animation = "anim3 linear 10s, anim1 linear 20s, anim5 linear 10s";
+ is(cs.getPropertyValue("margin-top"), "60px",
+ "anim3 + anim1 + anim5, margin-top at 8s, with fill mode");
+ is(cs.getPropertyValue("margin-left"), "30px",
+ "anim3 + anim1 + anim5, margin-left at 8s");
+done_div();
+
+/*
+ * css3-animations: 3. Keyframes
+ * http://dev.w3.org/csswg/css3-animations/#keyframes
+ *
+ * Also see test_keyframes_rules.html .
+ */
+
+// Test the rules on keyframes that lack a 0% or 100% rule:
+// (simultaneously, test that reverse animations have their keyframes
+// run backwards)
+
+// 100px at 0%, 50px at 50%, 150px at 100%
+new_div("margin-top: 100px; animation: kf1 ease 1s alternate infinite");
+is(cs.marginTop, "100px", "no-0% at 0.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.2), 0.01,
+ "no-0% at 0.1s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.6), 0.01,
+ "no-0% at 0.3s");
+advance_clock(200);
+is(cs.marginTop, "50px", "no-0% at 0.5s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.4), 0.01,
+ "no-0% at 0.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.8), 0.01,
+ "no-0% at 0.9s");
+advance_clock(100);
+is(cs.marginTop, "150px", "no-0% at 1.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.8), 0.01,
+ "no-0% at 1.1s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.2), 0.01,
+ "no-0% at 1.4s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.6), 0.01,
+ "no-0% at 1.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.2), 0.01,
+ "no-0% at 1.9s");
+advance_clock(100);
+is(cs.marginTop, "100px", "no-0% at 2.0s");
+done_div();
+
+// 150px at 0%, 50px at 50%, 100px at 100%
+new_div("margin-top: 100px; animation: kf2 ease-in 1s alternate infinite");
+is(cs.marginTop, "150px", "no-100% at 0.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.2), 0.01,
+ "no-100% at 0.1s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.6), 0.01,
+ "no-100% at 0.3s");
+advance_clock(200);
+is(cs.marginTop, "50px", "no-100% at 0.5s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.4), 0.01,
+ "no-100% at 0.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.8), 0.01,
+ "no-100% at 0.9s");
+advance_clock(100);
+is(cs.marginTop, "100px", "no-100% at 1.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.8), 0.01,
+ "no-100% at 1.1s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.2), 0.01,
+ "no-100% at 1.4s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.6), 0.01,
+ "no-100% at 1.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.2), 0.01,
+ "no-100% at 1.9s");
+advance_clock(100);
+is(cs.marginTop, "150px", "no-100% at 2.0s");
+done_div();
+
+
+// 50px at 0%, 100px at 25%, 50px at 100%
+new_div("margin-top: 50px; animation: kf3 ease-out 1s alternate infinite");
+is(cs.marginTop, "50px", "no-0%-no-100% at 0.0s");
+advance_clock(50);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.2), 0.01,
+ "no-0%-no-100% at 0.05s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.6), 0.01,
+ "no-0%-no-100% at 0.15s");
+advance_clock(100);
+is(cs.marginTop, "100px", "no-0%-no-100% at 0.25s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.4), 0.01,
+ "no-0%-no-100% at 0.55s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.8), 0.01,
+ "no-0%-no-100% at 0.85s");
+advance_clock(150);
+is(cs.marginTop, "50px", "no-0%-no-100% at 1.0s");
+advance_clock(150);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.8), 0.01,
+ "no-0%-no-100% at 1.15s");
+advance_clock(450);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.2), 0.01,
+ "no-0%-no-100% at 1.6s");
+advance_clock(250);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.6), 0.01,
+ "no-0%-no-100% at 1.85s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.2), 0.01,
+ "no-0%-no-100% at 1.95s");
+advance_clock(50);
+is(cs.marginTop, "50px", "no-0%-no-100% at 2.0s");
+done_div();
+
+// Test that non-animatable properties are ignored.
+// Simultaneously, test that the block is still honored, and that
+// we still override the value when two consecutive keyframes have
+// the same value.
+new_div("animation: kf4 ease 10s");
+is(cs.display, "block",
+ "non-animatable properties should be ignored (linear, 0s)");
+is(cs.marginTop, "37px",
+ "animatable properties should still apply (linear, 0s)");
+advance_clock(1000);
+is(cs.display, "block",
+ "non-animatable properties should be ignored (linear, 1s)");
+is(cs.marginTop, "37px",
+ "animatable properties should still apply (linear, 1s)");
+done_div();
+new_div("animation: kf4 step-start 10s");
+is(cs.display, "block",
+ "non-animatable properties should be ignored (step-start, 0s)");
+is(cs.marginTop, "37px",
+ "animatable properties should still apply (step-start, 0s)");
+advance_clock(1000);
+is(cs.display, "block",
+ "non-animatable properties should be ignored (step-start, 1s)");
+is(cs.marginTop, "37px",
+ "animatable properties should still apply (step-start, 1s)");
+done_div();
+
+// Test cascading of the keyframes within an @keyframes rule.
+new_div("animation: kf_cascade1 linear 10s");
+// 0%: 30px
+// 50%: 20px
+// 75%: 20px
+// 85%: 30px
+// 85.1%: 60px
+// 100%: 70px
+is(cs.paddingTop, "30px", "kf_cascade1 at 0s");
+advance_clock(2500);
+is(cs.paddingTop, "25px", "kf_cascade1 at 2.5s");
+advance_clock(2500);
+is(cs.paddingTop, "20px", "kf_cascade1 at 5s");
+advance_clock(2000);
+is(cs.paddingTop, "20px", "kf_cascade1 at 7s");
+advance_clock(500);
+is(cs.paddingTop, "20px", "kf_cascade1 at 7.5s");
+advance_clock(500);
+is(cs.paddingTop, "25px", "kf_cascade1 at 8s");
+advance_clock(500);
+is(cs.paddingTop, "30px", "kf_cascade1 at 8.5s");
+advance_clock(10);
+is_approx(px_to_num(cs.paddingTop), 60, 0.001, "kf_cascade1 at 8.51s");
+advance_clock(745);
+is(cs.paddingTop, "65px", "kf_cascade1 at 9.2505s");
+done_div();
+
+// Test cascading of the @keyframes rules themselves.
+new_div("animation: kf_cascade2 linear 10s");
+is(cs.marginTop, "0px", "@keyframes rule with margin-top should be ignored");
+is(cs.marginLeft, "300px", "last @keyframes rule with margin-left should win");
+done_div();
+
+/*
+ * css3-animations: 3.1. Timing functions for keyframes
+ * http://dev.w3.org/csswg/css3-animations/#timing-functions-for-keyframes-
+ */
+new_div("animation: kf_tf1 ease-in 10s alternate infinite");
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 0s (test needed for flush)");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
+ "keyframe timing functions test at 1s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.8), 0.01,
+ "keyframe timing functions test at 2s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01,
+ "keyframe timing functions test at 3s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
+ "keyframe timing functions test at 4s");
+advance_clock(1000);
+is(cs.paddingBottom, "160px",
+ "keyframe timing functions test at 5s");
+advance_clock(1001); // avoid floating-point error
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
+ "keyframe timing functions test at 6s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01,
+ "keyframe timing functions test at 7s");
+advance_clock(999);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
+ "keyframe timing functions test at 8s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.6), 0.01,
+ "keyframe timing functions test at 9s");
+advance_clock(1000);
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 10s");
+advance_clock(20000);
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 30s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.6), 0.01,
+ "keyframe timing functions test at 31s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
+ "keyframe timing functions test at 32s");
+advance_clock(999); // avoid floating-point error
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01,
+ "keyframe timing functions test at 33s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
+ "keyframe timing functions test at 34s");
+advance_clock(1001);
+is(cs.paddingBottom, "160px",
+ "keyframe timing functions test at 35s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
+ "keyframe timing functions test at 36s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01,
+ "keyframe timing functions test at 37s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.8), 0.01,
+ "keyframe timing functions test at 38s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
+ "keyframe timing functions test at 39s");
+advance_clock(1000);
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 40s");
+done_div();
+
+// spot-check the same thing without alternate
+new_div("animation: kf_tf1 ease-in 10s infinite");
+is(cs.paddingBottom, "20px",
+ "keyframe timing functions test at 0s (test needed for flush)");
+advance_clock(11000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
+ "keyframe timing functions test at 11s");
+advance_clock(3000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
+ "keyframe timing functions test at 14s");
+advance_clock(2001); // avoid floating-point error
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
+ "keyframe timing functions test at 16s");
+advance_clock(1999);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
+ "keyframe timing functions test at 18s");
+done_div();
+
+/*
+ * css3-animations: 3.2. The 'animation-name' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-name-property-
+ */
+
+// animation-name is reasonably well-tested up in the tests for Section
+// 2, particularly the tests that "Test that animations continue running
+// when the animation name list is changed."
+
+// Test that 'animation-name: none' steps the animation, and setting
+// it again starts a new one.
+
+new_div("");
+div.style.animation = "anim2 ease-in-out 10s";
+is(cs.marginRight, "0px", "after setting animation-name to anim2");
+advance_clock(1000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in_out(0.1), 0.01,
+ "before changing animation-name to none");
+div.style.animationName = "none";
+is(cs.marginRight, "0px", "after changing animation-name to none");
+advance_clock(1000);
+is(cs.marginRight, "0px", "after changing animation-name to none plus 1s");
+div.style.animationName = "anim2";
+is(cs.marginRight, "0px", "after changing animation-name to anim2");
+advance_clock(1000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in_out(0.1), 0.01,
+ "at 1s in animation when animation-name no longer none again");
+div.style.animationName = "none";
+is(cs.marginRight, "0px", "after changing animation-name to none");
+advance_clock(1000);
+is(cs.marginRight, "0px", "after changing animation-name to none plus 1s");
+done_div();
+
+/*
+ * css3-animations: 3.3. The 'animation-duration' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-duration-property-
+ */
+
+// FIXME: test animation-duration of 0 (quite a bit, including interaction
+// with fill-mode, count, and reversing), once I know what the right
+// behavior is.
+
+/*
+ * css3-animations: 3.4. The 'animation-timing-function' Property
+ * http://dev.w3.org/csswg/css3-animations/#animation-timing-function_tag
+ */
+
+// tested in tests for section 3.1
+
+/*
+ * css3-animations: 3.5. The 'animation-iteration-count' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-iteration-count-property-
+ */
+new_div("animation: anim2 ease-in 10s 0.3 forwards");
+is(cs.marginRight, "0px", "animation-iteration-count test 1 at 0s");
+advance_clock(2000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-iteration-count test 1 at 2s");
+advance_clock(900);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.29), 0.01,
+ "animation-iteration-count test 1 at 2.9s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-iteration-count test 1 at 3s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-iteration-count test 1 at 3.1s");
+advance_clock(5000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-iteration-count test 1 at 8.1s");
+done_div();
+
+new_div("animation: anim2 ease-in 10s 0.3"
+ + ", anim3 ease-out 20s 1.2 alternate forwards"
+ + ", anim4 ease-in-out 5s 1.6 forwards");
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 0s");
+is(cs.marginTop, "0px", "animation-iteration-count test 3 at 0s");
+is(cs.marginBottom, "0px", "animation-iteration-count test 4 at 0s");
+advance_clock(2000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-iteration-count test 2 at 2s");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.1), 0.01,
+ "animation-iteration-count test 3 at 2s");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.4), 0.01,
+ "animation-iteration-count test 4 at 2s");
+advance_clock(900);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.29), 0.01,
+ "animation-iteration-count test 2 at 2.9s");
+advance_clock(200);
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 3.1s");
+advance_clock(1800);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.98), 0.01,
+ "animation-iteration-count test 4 at 4.9s");
+advance_clock(200);
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 5.1s");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.02), 0.01,
+ "animation-iteration-count test 4 at 5.1s");
+advance_clock(2800);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.58), 0.01,
+ "animation-iteration-count test 4 at 7.9s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+ "animation-iteration-count test 4 at 8s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+ "animation-iteration-count test 4 at 8.1s");
+advance_clock(11700);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.99), 0.01,
+ "animation-iteration-count test 3 at 19.8s");
+advance_clock(200);
+is(cs.marginTop, "100px", "animation-iteration-count test 3 at 20s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.99), 0.01,
+ "animation-iteration-count test 3 at 20.2s");
+advance_clock(3600);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.81), 0.01,
+ "animation-iteration-count test 3 at 23.8s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.8), 0.01,
+ "animation-iteration-count test 3 at 24s");
+advance_clock(200);
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 25s");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.8), 0.01,
+ "animation-iteration-count test 3 at 25s");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+ "animation-iteration-count test 4 at 25s");
+done_div();
+
+/*
+ * css3-animations: 3.6. The 'animation-direction' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-direction-property-
+ */
+
+// Tested in tests for sections 3.1 and 3.5.
+
+new_div("animation: anim2 ease-in 10s infinite");
+div.style.animationDirection = "normal";
+is(cs.marginRight, "0px", "animation-direction test 1 (normal) at 0s");
+div.style.animationDirection = "reverse";
+is(cs.marginRight, "100px", "animation-direction test 1 (reverse) at 0s");
+div.style.animationDirection = "alternate";
+is(cs.marginRight, "0px", "animation-direction test 1 (alternate) at 0s");
+div.style.animationDirection = "alternate-reverse";
+is(cs.marginRight, "100px", "animation-direction test 1 (alternate-reverse) at 0s");
+advance_clock(2000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (normal) at 2s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (reverse) at 2s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (alternate) at 2s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 2s");
+advance_clock(5000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.7), 0.01,
+ "animation-direction test 1 (normal) at 7s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-direction test 1 (reverse) at 7s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.7), 0.01,
+ "animation-direction test 1 (alternate) at 7s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 7s");
+advance_clock(5000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (normal) at 12s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (reverse) at 12s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (alternate) at 12s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 12s");
+advance_clock(10000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (normal) at 22s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (reverse) at 22s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (alternate) at 22s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 22s");
+advance_clock(30000);
+div.style.animationDirection = "normal";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (normal) at 52s");
+div.style.animationDirection = "reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (reverse) at 52s");
+div.style.animationDirection = "alternate";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "animation-direction test 1 (alternate) at 52s");
+div.style.animationDirection = "alternate-reverse";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+ "animation-direction test 1 (alternate-reverse) at 52s");
+done_div();
+
+/*
+ * css3-animations: 3.7. The 'animation-play-state' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-play-state-property-
+ */
+
+// simple test with just one animation
+new_div("");
+div.style.animationTimingFunction = "ease";
+div.style.animationName = "anim1";
+div.style.animationDuration = "1s";
+div.style.animationDirection = "alternate";
+div.style.animationIterationCount = "2";
+is(cs.marginLeft, "0px", "animation-play-state test 1, at 0s");
+advance_clock(250);
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 250ms");
+div.style.animationPlayState = "paused";
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 250ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 still at 500ms");
+div.style.animationPlayState = "running";
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 still at 500ms");
+advance_clock(500);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 1000ms");
+advance_clock(250);
+is(cs.marginLeft, "100px", "animation-play-state test 1 at 1250ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 1500ms");
+div.style.animationPlayState = "paused";
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 1500ms");
+advance_clock(2000);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 3500ms");
+advance_clock(500);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 4000ms");
+div.style.animationPlayState = "";
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 4000ms");
+advance_clock(500);
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+ "animation-play-state test 1 at 4500ms");
+advance_clock(250);
+is(cs.marginLeft, "0px", "animation-play-state test 1, at 4750ms");
+advance_clock(250);
+is(cs.marginLeft, "0px", "animation-play-state test 1, at 5000ms");
+done_div();
+
+// more complicated test with multiple animations (and different directions
+// and iteration counts)
+new_div("");
+div.style.animationTimingFunction = "ease-out, ease-in, ease-in-out";
+div.style.animationName = "anim2, anim3, anim4";
+div.style.animationDuration = "1s, 2s, 1s";
+div.style.animationDirection = "alternate, normal, normal";
+div.style.animationIterationCount = "4, 2, infinite";
+is(cs.marginRight, "0px", "animation-play-state test 2, at 0s");
+is(cs.marginTop, "0px", "animation-play-state test 3, at 0s");
+is(cs.marginBottom, "0px", "animation-play-state test 4, at 0s");
+advance_clock(250);
+div.style.animationPlayState = "paused, running"; // pause 1 and 3
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+ "animation-play-state test 2 at 250ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.125), 0.01,
+ "animation-play-state test 3 at 250ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01,
+ "animation-play-state test 4 at 250ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+ "animation-play-state test 2 at 500ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.25), 0.01,
+ "animation-play-state test 3 at 500ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01,
+ "animation-play-state test 4 at 500ms");
+div.style.animationPlayState = "paused, running, running"; // unpause 3
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+ "animation-play-state test 2 at 500ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.25), 0.01,
+ "animation-play-state test 3 at 500ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01,
+ "animation-play-state test 4 at 500ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+ "animation-play-state test 2 at 750ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 750ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.5), 0.01,
+ "animation-play-state test 4 at 750ms");
+div.style.animationPlayState = "running, paused"; // unpause 1, pause 2
+advance_clock(0); // notify refresh observers
+advance_clock(250);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.5), 0.01,
+ "animation-play-state test 2 at 1000ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 1000ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.75), 0.01,
+ "animation-play-state test 4 at 1000ms");
+div.style.animationPlayState = "paused"; // pause all
+advance_clock(0); // notify refresh observers
+advance_clock(3000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.5), 0.01,
+ "animation-play-state test 2 at 4000ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 4000ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.75), 0.01,
+ "animation-play-state test 4 at 4000ms");
+div.style.animationPlayState = "running, paused"; // pause 2
+advance_clock(0); // notify refresh observers
+advance_clock(850);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.65), 0.01,
+ "animation-play-state test 2 at 4850ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 4850ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+ "animation-play-state test 4 at 4850ms");
+advance_clock(300);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.35), 0.01,
+ "animation-play-state test 2 at 5150ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 5150ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.9), 0.01,
+ "animation-play-state test 4 at 5150ms");
+advance_clock(2300);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.05), 0.01,
+ "animation-play-state test 2 at 7450ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 7450ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.2), 0.01,
+ "animation-play-state test 4 at 7450ms");
+advance_clock(100);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 7550ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+ "animation-play-state test 3 at 7550ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01,
+ "animation-play-state test 4 at 7550ms");
+div.style.animationPlayState = "running"; // unpause 2
+advance_clock(0); // notify refresh observers
+advance_clock(1000);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 7550ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.875), 0.01,
+ "animation-play-state test 3 at 7550ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01,
+ "animation-play-state test 4 at 7550ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 8050ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.125), 0.01,
+ "animation-play-state test 3 at 8050ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01,
+ "animation-play-state test 4 at 8050ms");
+advance_clock(1000);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 9050ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.625), 0.01,
+ "animation-play-state test 3 at 9050ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01,
+ "animation-play-state test 4 at 9050ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 9550ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.875), 0.01,
+ "animation-play-state test 3 at 9550ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01,
+ "animation-play-state test 4 at 9550ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 10050ms");
+is(cs.marginTop, "0px", "animation-play-state test 3 at 10050ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01,
+ "animation-play-state test 4 at 10050ms");
+done_div();
+
+// an initially paused animation (bug 1063992)
+new_div("animation: anim1 1s paused both");
+is(cs.marginLeft, "0px", "animation-play-state test 5, at 0s");
+advance_clock(500);
+is(cs.marginLeft, "0px", "animation-play-state test 5, at 0.5s");
+div.style.animationPlayState = "running";
+is(cs.marginLeft, "0px",
+ "animation-play-state test 5, at 0.5s after unpausing");
+advance_clock(500);
+is(cs.marginLeft, "80px",
+ "animation-play-state test 5, at 1s after unpaused");
+done_div();
+
+/*
+ * css3-animations: 3.8. The 'animation-delay' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-delay-property-
+ */
+
+// test positive delay
+new_div("animation: anim2 1s 0.5s ease-out");
+is(cs.marginRight, "0px", "positive delay test at 0ms");
+advance_clock(400);
+is(cs.marginRight, "0px", "positive delay test at 400ms");
+advance_clock(100);
+is(cs.marginRight, "0px", "positive delay test at 500ms");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+ "positive delay test at 500ms");
+done_div();
+
+// test dynamic changes to delay (i.e., that we preserve the start time
+// that's before the delay)
+new_div("animation: anim2 1s 0.5s ease-out both");
+is(cs.marginRight, "0px", "dynamic delay delay test at 0ms");
+advance_clock(400);
+is(cs.marginRight, "0px", "dynamic delay delay test at 400ms (1)");
+div.style.animationDelay = "0.2s";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.2), 0.01,
+ "dynamic delay delay test at 400ms (2)");
+div.style.animationDelay = "0.6s";
+advance_clock(0);
+advance_clock(200);
+is(cs.marginRight, "0px", "dynamic delay delay test at 600ms");
+advance_clock(200);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.2), 0.01,
+ "dynamic delay delay test at 800ms");
+advance_clock(1000);
+is(cs.marginRight, "100px", "dynamic delay delay test at 1800ms (1)");
+div.style.animationDelay = "1.5s";
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.3), 0.01,
+ "dynamic delay delay test at 1800ms (2)");
+div.style.animationDelay = "2s";
+is(cs.marginRight, "0px", "dynamic delay delay test at 1800ms (3)");
+done_div();
+
+// test delay and play-state interaction
+new_div("animation: anim2 1s 0.5s ease-out");
+is(cs.marginRight, "0px", "delay and play-state delay test at 0ms");
+advance_clock(400);
+is(cs.marginRight, "0px", "delay and play-state delay test at 400ms");
+div.style.animationPlayState = "paused";
+advance_clock(0);
+advance_clock(100);
+is(cs.marginRight, "0px", "delay and play-state delay test at 500ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "delay and play-state delay test at 1000ms");
+div.style.animationPlayState = "running";
+advance_clock(0);
+advance_clock(100);
+is(cs.marginRight, "0px", "delay and play-state delay test at 1100ms");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+ "delay and play-state delay test at 1200ms");
+div.style.animationPlayState = "paused";
+advance_clock(0);
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+ "delay and play-state delay test at 1300ms");
+done_div();
+
+// test negative delay and implicit starting values
+new_div("margin-top: 1000px");
+advance_clock(300);
+div.style.marginTop = "100px";
+div.style.animation = "kf1 1s -0.1s ease-in";
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_in(0.2), 0.01,
+ "delay and implicit starting values test");
+done_div();
+
+// test large negative delay that causes the animation to start
+// in the fourth iteration
+new_div("animation: anim2 1s -3.6s ease-in 5 alternate forwards");
+listen(); // rely on no flush having happened yet
+cs.animationName; // flush styles so animation is created
+advance_clock(0); // complete pending animation start
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.4), 0.01,
+ "large negative delay test at 0ms");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 3.6,
+ pseudoElement: "" }],
+ "right after start in large negative delay test");
+advance_clock(380);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.02), 0.01,
+ "large negative delay test at 380ms");
+check_events([]);
+advance_clock(20);
+is(cs.marginRight, "0px", "large negative delay test at 400ms");
+check_events([{ type: 'animationiteration', target: div,
+ animationName: 'anim2', elapsedTime: 4.0,
+ pseudoElement: "" }],
+ "large negative delay test at 400ms");
+advance_clock(800);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
+ "large negative delay test at 1200ms");
+check_events([]);
+advance_clock(200);
+is(cs.marginRight, "100px", "large negative delay test at 1400ms");
+check_events([{ type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 5.0,
+ pseudoElement: "" }],
+ "large negative delay test at 1400ms");
+done_div();
+
+/*
+ * css3-animations: 3.9. The 'animation-fill-mode' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-fill-mode-property-
+ */
+
+// animation-fill-mode is tested in the tests for section (2).
+
+/*
+ * css3-animations: 3.10. The 'animation' Shorthand Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-shorthand-property-
+ */
+
+// shorthand vs. longhand is adequately tested by the
+// property_database.js-based tests.
+
+/**
+ * Basic tests of animations on pseudo-elements
+ */
+new_div("");
+listen();
+div.id = "withbefore";
+var cs_before = getComputedStyle(div, ":before");
+is(cs_before.marginRight, "0px", ":before test at 0ms");
+advance_clock(400);
+is(cs_before.marginRight, "40px", ":before test at 400ms");
+advance_clock(800);
+is(cs_before.marginRight, "80px", ":before test at 1200ms");
+is(cs.marginRight, "0px", ":before animation should not affect element");
+advance_clock(800);
+is(cs_before.marginRight, "0px", ":before test at 2000ms");
+advance_clock(300);
+is(cs_before.marginRight, "30px", ":before test at 2300ms");
+advance_clock(700);
+check_events([ { type: "animationstart", animationName: "anim2", elapsedTime: 0, pseudoElement: "::before" },
+ { type: "animationiteration", animationName: "anim2", elapsedTime: 1, pseudoElement: "::before" },
+ { type: "animationiteration", animationName: "anim2", elapsedTime: 2, pseudoElement: "::before" },
+ { type: "animationend", animationName: "anim2", elapsedTime: 3, pseudoElement: "::before" }]);
+done_div();
+
+new_div("");
+listen();
+div.id = "withafter";
+var cs_after = getComputedStyle(div, ":after");
+is(cs_after.marginRight, "0px", ":after test at 0ms");
+advance_clock(400);
+is(cs_after.marginRight, "40px", ":after test at 400ms");
+advance_clock(800);
+is(cs_after.marginRight, "80px", ":after test at 1200ms");
+is(cs.marginRight, "0px", ":after animation should not affect element");
+advance_clock(800);
+is(cs_after.marginRight, "0px", ":after test at 2000ms");
+advance_clock(300);
+is(cs_after.marginRight, "30px", ":after test at 2300ms");
+advance_clock(700);
+check_events([ { type: "animationstart", animationName: "anim2", elapsedTime: 0, pseudoElement: "::after" },
+ { type: "animationiteration", animationName: "anim2", elapsedTime: 1, pseudoElement: "::after" },
+ { type: "animationiteration", animationName: "anim2", elapsedTime: 2, pseudoElement: "::after" },
+ { type: "animationend", animationName: "anim2", elapsedTime: 3, pseudoElement: "::after" }]);
+done_div();
+
+/**
+ * Test handling of properties that are present in only some of the
+ * keyframes.
+ */
+new_div("animation: multiprop 1s ease-in-out alternate infinite");
+is(cs.paddingTop, "10px", "multiprop top at 0ms");
+is(cs.paddingLeft, "30px", "multiprop top at 0ms");
+advance_clock(100);
+is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.2), 0.01,
+ "multiprop top at 100ms");
+is_approx(px_to_num(cs.paddingLeft), 30 + 20 * gTF.ease(0.4), 0.01,
+ "multiprop left at 100ms");
+advance_clock(200);
+is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.6), 0.01,
+ "multiprop top at 300ms");
+is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.1), 0.01,
+ "multiprop left at 300ms");
+advance_clock(300);
+is_approx(px_to_num(cs.paddingTop), 40 + 40 * gTF.ease_in_out(0.4), 0.01,
+ "multiprop top at 600ms");
+is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.7), 0.01,
+ "multiprop left at 600ms");
+advance_clock(200);
+is_approx(px_to_num(cs.paddingTop), 80 - 80 * gTF.ease_in(0.2), 0.01,
+ "multiprop top at 800ms");
+is_approx(px_to_num(cs.paddingLeft), 60 - 60 * gTF.ease_in(0.2), 0.01,
+ "multiprop left at 800ms");
+advance_clock(400);
+is_approx(px_to_num(cs.paddingTop), 80 - 80 * gTF.ease_in(0.2), 0.01,
+ "multiprop top at 1200ms");
+is_approx(px_to_num(cs.paddingLeft), 60 - 60 * gTF.ease_in(0.2), 0.01,
+ "multiprop left at 1200ms");
+advance_clock(200);
+is_approx(px_to_num(cs.paddingTop), 40 + 40 * gTF.ease_in_out(0.4), 0.01,
+ "multiprop top at 1400ms");
+is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.7), 0.01,
+ "multiprop left at 1400ms");
+advance_clock(300);
+is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.6), 0.01,
+ "multiprop top at 1700ms");
+is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.1), 0.01,
+ "multiprop left at 1700ms");
+advance_clock(200);
+is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.2), 0.01,
+ "multiprop top at 1900ms");
+is_approx(px_to_num(cs.paddingLeft), 30 + 20 * gTF.ease(0.4), 0.01,
+ "multiprop left at 1900ms");
+done_div();
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=651456 -- make
+// sure that refreshing of animations doesn't break when we get two
+// refreshes with the same timestamp.
+new_div("animation: anim2 1s linear");
+is(cs.marginRight, "0px", "bug 651456 at 0ms");
+advance_clock(100);
+is(cs.marginRight, "10px", "bug 651456 at 100ms (1)");
+advance_clock(0); // still forces a refresh
+is(cs.marginRight, "10px", "bug 651456 at 100ms (2)");
+advance_clock(100);
+is(cs.marginRight, "20px", "bug 651456 at 200ms");
+done_div();
+
+// Test that UA !important rules override animations.
+// This test depends on forms.css having a rule
+// option { white-space: !important }
+// If that rule changes, we should rewrite it to depend on a different rule.
+var option;
+[ option, cs ] = new_element("option", "");
+var default_white_space = cs.whiteSpace;
+isnot(default_white_space, "pre",
+ "default style should not be the same as animation style");
+done_element();
+[ option, cs ] = new_element("option",
+ "animation: uaoverride 2s linear infinite");
+is(cs.whiteSpace, default_white_space,
+ "animations should not override UA !important at 0ms");
+is(cs.marginTop, "20px",
+ "rest of animation should still work when UA !important present at 0ms");
+advance_clock(200);
+is(cs.whiteSpace, default_white_space,
+ "animations should not override UA !important at 200ms");
+is(cs.marginTop, "40px",
+ "rest of animation should still work when UA !important present at 200ms");
+done_element();
+
+// Test that author !important rules override animations, but
+// that animations override regular author rules.
+new_div("animation: always_fifty 1s linear infinite; margin-left: 200px");
+is(cs.marginLeft, "50px", "animations override regular author rules");
+done_div();
+new_div("animation: always_fifty 1s linear infinite;"
+ + " margin-left: 200px ! important;");
+is(cs.marginLeft, "200px", "important author rules override animations");
+done_div();
+
+// Test interaction of animations and restyling (Bug 686656).
+// This test depends on kf3 getting its 0% and 100% values from the
+// rules below it in the cascade; we're checking that the animation
+// isn't rebuilt when the restyles happen.
+new_div("animation: kf3 1s linear forwards");
+is(cs.marginTop, "0px", "bug 686656 test 1 at 0ms");
+advance_clock(250);
+display.style.color = "blue";
+is(cs.marginTop, "100px", "bug 686656 test 1 at 250ms");
+advance_clock(375);
+is(cs.marginTop, "50px", "bug 686656 test 1 at 625ms");
+advance_clock(375);
+is(cs.marginTop, "0px", "bug 686656 test 1 at 1000ms");
+done_div();
+display.style.color = "";
+
+// Test interaction of animations and restyling (Bug 686656),
+// with reframing.
+// This test depends on kf3 getting its 0% and 100% values from the
+// rules below it in the cascade; we're checking that the animation
+// isn't rebuilt when the restyles happen.
+new_div("animation: kf3 1s linear forwards");
+is(cs.marginTop, "0px", "bug 686656 test 2 at 0ms");
+advance_clock(250);
+display.style.overflow = "scroll";
+is(cs.marginTop, "100px", "bug 686656 test 2 at 250ms");
+advance_clock(375);
+is(cs.marginTop, "50px", "bug 686656 test 2 at 625ms");
+advance_clock(375);
+is(cs.marginTop, "0px", "bug 686656 test 2 at 1000ms");
+done_div();
+display.style.overflow = "";
+
+// Test that cascading between keyframes rules is per-property rather
+// than per-rule (bug ), and that the timing function isn't taken from a
+// rule that's skipped. (Bug 738003)
+new_div("animation: cascade 1s linear forwards; position: relative");
+is(cs.top, "0px", "cascade test (top) at 0ms");
+is(cs.left, "0px", "cascade test (top) at 0ms");
+advance_clock(125);
+is(cs.top, "0px", "cascade test (top) at 125ms");
+is(cs.left, "50px", "cascade test (top) at 125ms");
+advance_clock(125);
+is(cs.top, "0px", "cascade test (top) at 250ms");
+is(cs.left, "100px", "cascade test (top) at 250ms");
+advance_clock(125);
+is(cs.top, "50px", "cascade test (top) at 375ms");
+is(cs.left, "100px", "cascade test (top) at 375ms");
+advance_clock(125);
+is(cs.top, "100px", "cascade test (top) at 500ms");
+is(cs.left, "100px", "cascade test (top) at 500ms");
+advance_clock(125);
+is(cs.top, "100px", "cascade test (top) at 625ms");
+is(cs.left, "50px", "cascade test (top) at 625ms");
+advance_clock(125);
+is(cs.top, "100px", "cascade test (top) at 750ms");
+is(cs.left, "0px", "cascade test (top) at 750ms");
+advance_clock(125);
+is(cs.top, "50px", "cascade test (top) at 875ms");
+is(cs.left, "0px", "cascade test (top) at 875ms");
+advance_clock(125);
+is(cs.top, "0px", "cascade test (top) at 1000ms");
+is(cs.left, "0px", "cascade test (top) at 1000ms");
+done_div();
+
+new_div("animation: cascade2 8s linear forwards");
+is(cs.textIndent, "0px", "cascade2 test at 0s");
+advance_clock(1000);
+is(cs.textIndent, "25px", "cascade2 test at 1s");
+advance_clock(1000);
+is(cs.textIndent, "50px", "cascade2 test at 2s");
+advance_clock(1000);
+is(cs.textIndent, "25px", "cascade2 test at 3s");
+advance_clock(1000);
+is(cs.textIndent, "0px", "cascade2 test at 4s");
+advance_clock(3000);
+is(cs.textIndent, "75px", "cascade2 test at 7s");
+advance_clock(1000);
+is(cs.textIndent, "100px", "cascade2 test at 8s");
+done_div();
+
+new_div("animation: primitives1 2s linear forwards");
+is(cs.getPropertyValue("transform"), "matrix(1, 0, 0, 1, 0, 0)",
+ "primitives1 at 0s");
+advance_clock(1000);
+is(cs.getPropertyValue("transform"),
+ "matrix(-0.707107, 0.707107, -0.707107, -0.707107, 0, 0)",
+ "primitives1 at 1s");
+advance_clock(1000);
+is(cs.getPropertyValue("transform"), "matrix(0, -1, 1, 0, 0, 0)",
+ "primitives1 at 0s");
+done_div();
+
+new_div("animation: important1 1s linear forwards");
+is(cs.marginTop, "50px", "important1 test at 0s");
+advance_clock(500);
+is(cs.marginTop, "75px", "important1 test at 0.5s");
+advance_clock(500);
+is(cs.marginTop, "100px", "important1 test at 1s");
+done_div();
+
+new_div("animation: important2 1s linear forwards");
+is(cs.marginTop, "50px", "important2 (margin-top) test at 0s");
+is(cs.marginBottom, "100px", "important2 (margin-bottom) test at 0s");
+advance_clock(1000);
+is(cs.marginTop, "0px", "important2 (margin-top) test at 1s");
+is(cs.marginBottom, "50px", "important2 (margin-bottom) test at 1s");
+done_div();
+
+// Test that it's the length of the 'animation-name' list that's used to
+// start animations.
+// note: anim2 animates margin-right from 0 to 100px
+// note: anim3 animates margin-top from 0 to 100px
+new_div("animation-name: anim2, anim3;"
+ + " animation-duration: 1s;"
+ + " animation-timing-function: linear;"
+ + " animation-delay: -250ms, -250ms, -750ms, -500ms;");
+is(cs.marginRight, "25px", "animation-name list length is the length that matters");
+is(cs.marginTop, "25px", "animation-name list length is the length that matters");
+done_div();
+new_div("animation-name: anim2, anim3, anim2;"
+ + " animation-duration: 1s;"
+ + " animation-timing-function: linear;"
+ + " animation-delay: -250ms, -250ms, -750ms, -500ms;");
+is(cs.marginRight, "75px", "animation-name list length is the length that matters, and the last occurrence of a name wins");
+is(cs.marginTop, "25px", "animation-name list length is the length that matters");
+done_div();
+
+var dyn_sheet_elt = document.createElement("style");
+document.head.appendChild(dyn_sheet_elt);
+var dyn_sheet = dyn_sheet_elt.sheet;
+dyn_sheet.insertRule("@keyframes dyn1 { from { margin-left: 0 } 50% { margin-left: 50px } to { margin-left: 100px } }", 0);
+dyn_sheet.insertRule("@keyframes dyn2 { from { margin-left: 100px } to { margin-left: 200px } }", 1);
+var dyn1 = dyn_sheet.cssRules[0];
+var dyn2 = dyn_sheet.cssRules[1];
+new_div("animation: dyn1 1s linear");
+is(cs.marginLeft, "0px", "dynamic rule change test, initial state");
+advance_clock(250);
+is(cs.marginLeft, "25px", "dynamic rule change test, 250ms");
+dyn2.name = "dyn1";
+is(cs.marginLeft, "125px", "dynamic rule change test, change in @keyframes name applies");
+dyn2.appendRule("50% { margin-left: 0px }");
+is(cs.marginLeft, "50px", "dynamic rule change test, @keyframes appendRule");
+var dyn2_kf1 = dyn2.cssRules[0]; // currently 0% { margin-left: 100px }
+dyn2_kf1.style.marginLeft = "-100px";
+is(cs.marginLeft, "-50px", "dynamic rule change test, keyframe style set");
+dyn2.name = "dyn2";
+is(cs.marginLeft, "25px", "dynamic rule change test, change in @keyframes name applies (second time)");
+var dyn1_kf2 = dyn1.cssRules[1]; // currently 50% { margin-left: 50px }
+dyn1_kf2.keyText = "25%";
+is(cs.marginLeft, "50px", "dynamic rule change test, change in keyframe keyText");
+dyn1.deleteRule("25%");
+is(cs.marginLeft, "25px", "dynamic rule change test, @keyframes deleteRule");
+done_div();
+dyn_sheet_elt.remove();
+dyn_sheet_elt = null;
+dyn_sheet = null;
+
+/*
+ * Bug 1004361 - CSS animations with short duration sometimes don't dispatch
+ * a start event
+ */
+new_div("animation: anim2 1s 0.1s");
+listen();
+advance_clock(0); // Trigger animation
+advance_clock(1200); // Skip past end of animation's entire active duration
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events after skipping over animation interval");
+done_div();
+
+/*
+ * Bug 1007513 - AnimationEvent.elapsedTime should be animation time
+ */
+new_div("animation: anim2 1s 2");
+listen();
+advance_clock(0); // Trigger animation
+advance_clock(500); // Jump to middle of first interval
+advance_clock(1000); // Jump to middle of second interval
+advance_clock(1000); // Jump past end of last interval
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationiteration', target: div,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 2,
+ pseudoElement: "" }],
+ "events after skipping past event moments");
+done_div();
+
+new_div("animation: anim2 1s -2s");
+listen();
+cs.animationName; // build animation
+advance_clock(0); // finish pending
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events after skipping over animation with negative delay");
+done_div();
+
+/*
+ * Bug 1004365 - zero-duration animations
+ */
+
+new_div("margin-right: 200px; animation: anim2 0s 1s both");
+listen();
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of zero-duration animation");
+advance_clock(2000); // Skip over animation
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during forwards fill of zero-duration animation");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration animation");
+done_div();
+
+new_div("margin-right: 200px; animation: anim2 0s 1s both");
+listen();
+advance_clock(0);
+// Seek to just before the animation starts and stops
+advance_clock(999);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right at exact end of zero-duration animation");
+check_events([]);
+// Seek to exactly the point where the animation starts and stops
+advance_clock(1);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right at exact end of zero-duration animation");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of zero-duration animation");
+// Check no further events are dispatched
+advance_clock(0);
+advance_clock(100);
+check_events([]);
+done_div();
+
+// Test with animation-direction reverse
+new_div("margin-right: 200px;"
+ + " animation: anim2 0s 1s both reverse");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during backwards fill of reversed zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during forwards fill of reversed zero-duration animation");
+done_div();
+
+// Test with animation-direction alternate
+new_div("margin-right: 200px; animation: anim2 0s 1s both alternate 2");
+listen();
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of alternating zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during forwards fill of alternating zero-duration animation");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of zero-duration animation"
+ + " that repeats twice");
+done_div();
+
+// Test with animation-direction alternate and odd number of iterations
+new_div("margin-right: 200px; animation: anim2 0s 1s both alternate 3");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of alternating zero-duration " +
+ "animation with odd number of iterations");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during forwards fill of alternating zero-duration " +
+ "animation with odd number of iterations");
+done_div();
+
+// Test with animation-direction alternate and non-integral number of iterations
+new_div("margin-right: 200px;"
+ + " animation: anim2 0s 1s both alternate 7.3 linear");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of alternating zero-duration " +
+ "animation with non-integral number of iterations");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "70px",
+ "margin-right during forwards fill of alternating zero-duration " +
+ "animation with non-integral number of iterations");
+done_div();
+
+// Test with infinite iteration count
+// CSS Animations doesn't actually define what the behavior is in this case
+// (and many many other similar cases) so we follow the behavior defined in Web
+// Animations which is that the zero-duration "wins".
+new_div("margin-right: 200px; animation: anim2 0s 1s both infinite");
+listen();
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of infinitely repeating " +
+ "zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during forwards fill of infinitely repeating " +
+ "zero-duration animation");
+// Check we don't get infinite iteration events :)
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of infinitely repeating " +
+ "zero-duration animation");
+done_div();
+
+// Test with infinite iteration count and alternating direction
+new_div("margin-right: 200px; animation: anim2 0s 1s alternate both infinite");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of infinitely repeating and " +
+ "alternating zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during forwards fill of infinitely repeating and " +
+ "alternating zero-duration animation");
+done_div();
+
+// Test with infinite iteration count and alternate-reverse direction
+new_div("margin-right: 200px;"
+ + " animation: anim2 0s 1s alternate-reverse infinite both");
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during backwards fill of infinitely repeating and " +
+ "alternate-reverse zero-duration animation");
+advance_clock(2000);
+is(cs.getPropertyValue("margin-right"), "100px",
+ "margin-right during forwards fill of infinitely repeating and " +
+ "alternate-reverse zero-duration animation");
+done_div();
+
+// Test with negative delay
+new_div("margin-right: 200px;"
+ + " animation: anim2 0s -1s both reverse 12.7 linear");
+listen();
+cs.animationName; // build animation
+advance_clock(0); // finish pending
+is(cs.getPropertyValue("margin-right"), "30px",
+ "margin-right during forwards fill of reversed and repeated " +
+ "zero-duration animation with negative delay");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration animation " +
+ "with negative delay");
+done_div();
+
+// Test zero duration with zero iteration count
+new_div("margin-right: 200px; animation: anim2 0s 1s both 0");
+listen();
+advance_clock(0);
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during backwards fill of zero-duration animation");
+advance_clock(2000); // Skip over animation
+is(cs.getPropertyValue("margin-right"), "0px",
+ "margin-right during forwards fill of zero-duration animation");
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration, zero iteration count"
+ + " animation");
+done_div();
+
+/*
+ * Bug 1004377 - Animations with empty keyframes rule
+ */
+
+new_div("margin-right: 200px; animation: empty 2s 1s both");
+listen();
+advance_clock(0);
+check_events([], "events during delay");
+advance_clock(2000); // Skip to middle of animation
+div.clientTop; // Trigger events
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "middle of animation with empty keyframes rule");
+advance_clock(1000); // Skip to end of animation
+div.clientTop; // Trigger events
+check_events([{ type: 'animationend', target: div,
+ animationName: 'empty', elapsedTime: 2,
+ pseudoElement: "" }],
+ "end of animation with empty keyframes rule");
+done_div();
+
+// Test with a zero-duration animation and empty @keyframes rule
+new_div("margin-right: 200px; animation: empty 0s 1s both");
+listen();
+advance_clock(0);
+advance_clock(1000);
+div.clientTop; // Trigger events
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: div,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "end of zero-duration animation with empty keyframes rule");
+done_div();
+
+// Test with a keyframes rule that becomes empty
+new_div("animation: nearlyempty 1s both linear");
+advance_clock(0);
+advance_clock(500);
+is(cs.getPropertyValue("margin-left"), "50px",
+ "margin-left for animation that is about to be emptied");
+listen();
+findKeyframesRule("nearlyempty").deleteRule("to");
+is(cs.getPropertyValue("margin-left"), "0px",
+ "margin-left for animation with (now) empty keyframes rule");
+check_events([], "events after emptying keyframes rule");
+advance_clock(500);
+div.clientTop; // Trigger events
+check_events([{ type: 'animationend', target: div,
+ animationName: 'nearlyempty', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events at end of animation with newly " +
+ "empty keyframes rule");
+done_div();
+
+// Test when we update to point to an empty animation
+new_div("animation: always_fifty 1s both linear");
+advance_clock(0);
+advance_clock(500);
+is(cs.getPropertyValue("margin-left"), "50px",
+ "margin-left for animation that will soon point to an empty keyframes rule");
+listen();
+div.style.animationName = "empty";
+is(cs.getPropertyValue("margin-left"), "0px",
+ "margin-left for animation now points to empty keyframes rule");
+advance_clock(500);
+div.clientTop; // Trigger events
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events at start of animation updated to use " +
+ "empty keyframes rule");
+done_div();
+
+/*
+ * Bug 1031319 - 'none' animations
+ *
+ * The code under test here is run entirely on the main thread so there is no
+ * OMTA version of these tests in test_animations_omta.html.
+ */
+
+// Setting "animation: none" after animations have finished should not trigger
+// animation events
+new_div("animation: always_fifty 1s");
+listen();
+advance_clock(0);
+advance_clock(1000);
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' }],
+ "events after running initial animation");
+div.style.animation = "none";
+div.clientTop; // Trigger events
+check_events([], "events after setting animation to 'none'");
+done_div();
+
+// Setting "animation: " after animations have finished should not trigger
+// animation events
+new_div("animation: always_fifty 1s");
+listen();
+advance_clock(0);
+advance_clock(1000);
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' }],
+ "events after running initial animation");
+div.style.animation = "";
+div.clientTop; // Trigger events
+check_events([], "events after setting animation to ''");
+done_div();
+
+// Setting "animation: none 1s" should not trigger events
+new_div("animation: none 1s");
+listen();
+advance_clock(0);
+advance_clock(1000);
+check_events([], "events after setting animation to 'none 1s'");
+done_div();
+
+// Setting "animation: 1s" should not trigger events
+new_div("animation: 1s");
+listen();
+advance_clock(0);
+advance_clock(1000);
+check_events([], "events after setting animation to '1s'");
+done_div();
+
+// Setting animation-name: none among other animations should cause only that
+// animation to be skipped
+new_div("animation-name: always_fifty, none, always_fifty;"
+ + " animation-duration: 1s");
+listen();
+advance_clock(0);
+advance_clock(500);
+advance_clock(500);
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' }],
+ "events for animation-name: a, none, a");
+done_div();
+
+/*
+ * Bug 1033881 - Non-matching animation-name
+ *
+ * The code under test here is run entirely on the main thread so there is no
+ * OMTA version of these tests in test_animations_omta.html.
+ */
+
+new_div("animation-name: non_existent, always_fifty; animation-duration: 1s");
+listen();
+advance_clock(0);
+advance_clock(500);
+advance_clock(500);
+check_events([{ type: 'animationstart', target: div,
+ animationName: 'always_fifty', elapsedTime: 0,
+ pseudoElement: '' },
+ { type: 'animationend', target: div,
+ animationName: 'always_fifty', elapsedTime: 1,
+ pseudoElement: '' }],
+ "events for animation-name: non_existent, always_fifty");
+done_div();
+
+/*
+ * Bug 1038032 - Infinite repetition and delay causes overflow
+ */
+new_div("animation: always_fifty 10s 1s infinite");
+advance_clock(0);
+advance_clock(2000);
+is(cs.marginLeft, "50px",
+ "infinitely repeating animation with positive delay takes effect"
+ + " (does not overflow)");
+done_div();
+
+/*
+ * Bug 1140134 - A property in a CSS animation being overridden by later
+ * animation causes later properties in that animation to be skipped
+ */
+new_div("position: relative; animation: lowerpriority 1s linear infinite alternate, overridetop 1s linear infinite alternate");
+advance_clock(0);
+advance_clock(500);
+is(cs.getPropertyValue("left"), "50px", "left is animating");
+is(cs.getPropertyValue("top"), "0px", "top is not animating");
+done_div();
+
+new_div("position: relative; animation: lowerpriority 1s linear infinite alternate, overrideleft 1s linear infinite alternate");
+advance_clock(0);
+advance_clock(500);
+is(cs.getPropertyValue("left"), "0px", "left is not animating");
+is(cs.getPropertyValue("top"), "50px", "top is animating");
+done_div();
+
+/*
+ * Bug 962594 - Turn off CSS animations when the element is display:none, or
+ * is in a display:none subtree.
+ */
+
+// Helper function for the two tests below
+function testDisplayNoneTurnsOffAnimations(aTestName, aElementToDisplayNone) {
+ is(cs.getPropertyValue("margin-right"), "0px",
+ aTestName + "margin-right at 0s");
+ advance_clock(1000);
+ is(cs.getPropertyValue("margin-right"), "10px",
+ aTestName + "margin-right at 1s");
+ aElementToDisplayNone.style.display = "none";
+ is(cs.getPropertyValue("margin-right"), "0px",
+ aTestName + "margin-right after display:none");
+ advance_clock(1000);
+ is(cs.getPropertyValue("margin-right"), "0px",
+ aTestName + "margin-right 1s after display:none");
+ aElementToDisplayNone.style.display = "";
+ is(cs.getPropertyValue("margin-right"), "0px",
+ aTestName + "margin-right after display:block");
+ advance_clock(1000);
+ is(cs.getPropertyValue("margin-right"), "10px",
+ aTestName + "margin-right 1s after display:block");
+}
+
+// Check that it works if the animated element itself becomes display:none
+new_div("animation: anim2 linear 10s");
+testDisplayNoneTurnsOffAnimations("AnimatedElement ", div);
+done_div();
+
+// Check that it works if an ancestor of the animated element becomes display:none
+new_div("animation: anim2 linear 10s");
+var ancestor = document.createElement("div");
+div.parentNode.insertBefore(ancestor, div);
+ancestor.appendChild(div);
+testDisplayNoneTurnsOffAnimations("AncestorElement ", ancestor);
+ancestor.parentNode.insertBefore(div, ancestor);
+ancestor.remove();
+done_div();
+
+
+/*
+ * Bug 1125455 - Transitions should not run when animations are running.
+ */
+new_div("transition: opacity 2s linear; opacity: 0.8");
+advance_clock(0);
+is(cs.getPropertyValue("opacity"), "0.8", "initial opacity");
+div.style.opacity = "0.2";
+is(cs.getPropertyValue("opacity"), "0.8", "opacity transition at 0s");
+advance_clock(500);
+is(cs.getPropertyValue("opacity"), "0.65", "opacity transition at 0.5s");
+div.style.animation = "opacitymid 2s linear";
+is(cs.getPropertyValue("opacity"), "0.2", "opacity animation overriding transition at 0s");
+advance_clock(500);
+is(cs.getPropertyValue("opacity"), "0.35", "opacity animation overriding transition at 0.5s");
+done_div();
+
+
+/*
+ * Bug 1320474 - keyframes-name may be a string, allows names that would otherwise be excluded
+ */
+new_div("position: relative; animation: \"string name 1\" 1s linear");
+advance_clock(0);
+is(cs.getPropertyValue("left"), "1px", "animation name as a string");
+div.style.animation = "string\\ name\\ 2 1s linear";
+is(cs.getPropertyValue("left"), "2px", "animation name specified as string, referenced using custom ident");
+div.style.animation = "custom\\ ident\\ 1 1s linear";
+is(cs.getPropertyValue("left"), "3px", "animation name specified as custom-ident");
+div.style.animation = "\"custom ident 2\" 1s linear";
+is(cs.getPropertyValue("left"), "4px", "animation name specified as custom-ident, referenced using string");
+div.style.animation = "unset";
+div.style.animation = "initial 1s linear";
+is(cs.getPropertyValue("left"), "0px", "animation name 'initial' as identifier is ignored");
+div.style.animation = "unset";
+div.style.animation = "\"initial\" 1s linear";
+is(cs.getPropertyValue("left"), "5px", "animation name 'initial' as string is accepted");
+div.style.animation = "unset";
+div.style.animation = "none 1s linear";
+is(cs.getPropertyValue("left"), "0px", "animation name 'none' as identifier is ignored");
+div.style.animation = "unset";
+div.style.animation = "\"none\" 1s linear";
+is(cs.getPropertyValue("left"), "7px", "animation name 'none' as string is accepted");
+done_div();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_async_tests.html b/layout/style/test/test_animations_async_tests.html
new file mode 100644
index 0000000000..7ad1d0d598
--- /dev/null
+++ b/layout/style/test/test_animations_async_tests.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1086937</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ window.open("file_animations_async_tests.html");
+ }
+ </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086937">Mozilla Bug 1086937</a>
+<div id="display"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_dynamic_changes.html b/layout/style/test/test_animations_dynamic_changes.html
new file mode 100644
index 0000000000..1863d08829
--- /dev/null
+++ b/layout/style/test/test_animations_dynamic_changes.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=978833
+-->
+<head>
+ <title>Test for Bug 978833</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+ @keyframes a {
+ from, to {
+ /* a non-inherited property, so it's cached in the rule tree */
+ margin-left: 50px;
+ }
+ }
+ .alwaysa {
+ animation: a linear 1s infinite;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978833">Mozilla Bug 978833</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+var style = document.getElementById("style").sheet;
+
+/** Test for Bug 978833 **/
+function test_bug978833() {
+ var kfs = style.cssRules[0];
+ var kf = kfs.cssRules[0];
+ is(kf.style.marginLeft, "50px", "we found the right keyframe rule");
+
+ p.classList.add("alwaysa");
+ is(cs.marginLeft, "50px", "p margin-left should be 50px");
+
+ // Temporarily remove the animation style, since we resolve keyframes
+ // on top of current animation styles (although maybe we shouldn't),
+ // so we need to remove those styles to hit the rule tree cache.
+ p.classList.remove("alwaysa");
+ is(cs.marginLeft, "0px", "p margin-left should be 0px without animation");
+
+ p.classList.add("alwaysa");
+ kf.style.marginLeft = "100px";
+ is(cs.marginLeft, "100px", "p margin-left should be 100px after change");
+
+ // Try the same thing a second time, just to make sure it works again.
+ p.classList.remove("alwaysa");
+ is(cs.marginLeft, "0px", "p margin-left should be 0px without animation");
+ p.classList.add("alwaysa");
+ kf.style.marginLeft = "150px";
+ is(cs.marginLeft, "150px", "p margin-left should be 150px after second change");
+
+ p.style.animation = "";
+}
+test_bug978833();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_effect_timing_duration.html b/layout/style/test/test_animations_effect_timing_duration.html
new file mode 100644
index 0000000000..7b3c443669
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_duration.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for animation.effect.updateTiming({ duration }) on compositor
+ animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)', easing: "steps(2, start)" },
+ { transform: 'translate(100px)' } ], 4000);
+ await waitForPaints();
+
+ advance_clock(500);
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ animation.effect.updateTiming({ duration: 2000 });
+ // Setter of timing option should set up the changes to animations for the
+ // next layer transaction but it won't schedule a paint immediately so we need
+ // to tick the refresh driver before we can wait on the next paint.
+ advance_clock(0);
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation remains on compositor");
+
+ advance_clock(1000);
+ omta_is(div, "transform", { tx: 100 }, RunningOn.Compositor,
+ "Animation is updated on compositor");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)', easing: "steps(2, end)" },
+ { transform: 'translate(100px)' } ], 4000);
+ await waitForPaints();
+
+ advance_clock(1000);
+ animation.effect.updateTiming({ duration: 2000 });
+ advance_clock(0);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ done_div();
+})
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_effect_timing_enddelay.html b/layout/style/test/test_animations_effect_timing_enddelay.html
new file mode 100644
index 0000000000..ad018f6373
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_enddelay.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for animation.effect.updateTiming({ endDelay }) on compositor
+ animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, fill: 'none' });
+ await waitForPaints();
+
+ advance_clock(100);
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ animation.effect.updateTiming({ endDelay: 1000 });
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "Animation remains on compositor when endDelay is changed");
+
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is updated on main thread");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, endDelay: -500, fill: 'none' });
+ await waitForPaints();
+
+ advance_clock(400);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 40 }, RunningOn.Compositor,
+ "Animation is updated on compositor " +
+ "duration 1000, endDelay -500, fill none, current time 400");
+
+ advance_clock(100);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill none, current time 500");
+
+ advance_clock(400);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill none, current time 900");
+
+ advance_clock(100);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill none, current time 1000");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, endDelay: 1000, fill: 'forwards' });
+ await waitForPaints();
+
+ advance_clock(1500);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 100 }, RunningOn.Compositor,
+ "The end delay is performed on the compositor thread");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
+ { duration: 1000, endDelay: -500, fill: 'forwards' });
+ await waitForPaints();
+
+ advance_clock(400);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 40 }, RunningOn.Compositor,
+ "Animation is updated on compositor " +
+ "duration 1000, endDelay -500, fill forwards, current time 400");
+
+ advance_clock(100);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill forwards, current time 500");
+
+ advance_clock(400);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill forwards, current time 900");
+
+ advance_clock(100);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread,
+ "Animation is updated on main thread " +
+ "duration 1000, endDelay -500, fill forwards, current time 1000");
+
+ done_div();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_effect_timing_iterations.html b/layout/style/test/test_animations_effect_timing_iterations.html
new file mode 100644
index 0000000000..380cab1763
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_iterations.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for Animation.effect.updateTiming({ iterations }) on compositor
+ animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("");
+ var animation = div.animate(
+ [ { transform: 'translate(0px)' },
+ { transform: 'translate(100px)' } ],
+ { duration: 4000,
+ iterations: 2
+ });
+ await waitForPaints();
+
+ advance_clock(6000);
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running on compositor");
+ animation.effect.updateTiming({ iterations: 1 });
+ advance_clock(0);
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+ "Animation is on MainThread");
+
+ animation.effect.updateTiming({ iterations: 3 });
+
+ advance_clock(0);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is running again on compositor");
+
+ done_div();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_event_handler_attribute.html b/layout/style/test/test_animations_event_handler_attribute.html
new file mode 100644
index 0000000000..7a9da1809c
--- /dev/null
+++ b/layout/style/test/test_animations_event_handler_attribute.html
@@ -0,0 +1,204 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=911987
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for CSS Animation and Transition event handler
+ attributes. (Bug 911987)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @keyframes anim { to { margin-left: 100px } }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=911987">Mozilla Bug
+ 911987</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+'use strict';
+
+// Create the div element with event handlers.
+// We need two elements: one with the content attribute speficied and one
+// with the IDL attribute specified since we can't set these independently.
+function createAndRegisterTargets(eventAttributes) {
+ var displayElement = document.getElementById('display');
+ var contentAttributeElement = document.createElement("div");
+ var idlAttributeElement = document.createElement("div");
+ displayElement.appendChild(contentAttributeElement);
+ displayElement.appendChild(idlAttributeElement);
+
+ // Add handlers
+ eventAttributes.forEach(event => {
+ contentAttributeElement.setAttribute(event, 'handleEvent(event);');
+ contentAttributeElement.handlerType = 'content attribute';
+ idlAttributeElement[event] = handleEvent;
+ idlAttributeElement.handlerType = 'IDL attribute';
+ });
+
+ return [contentAttributeElement, idlAttributeElement];
+}
+
+function handleEvent(event) {
+ if (event.target.receivedEventType) {
+ ok(false, `Received ${event.type} event, but this element have previous `
+ `received event '${event.target.receivedEventType}'.`);
+ return;
+ }
+ event.target.receivedEventType = event.type;
+}
+
+function checkReceivedEvents(eventType, elements) {
+ elements.forEach(element => {
+ is(element.receivedEventType, eventType,
+ `Expected to receive '${eventType}', got
+ '${element.receivedEventType}', for event handler registered
+ using ${element.handlerType}`);
+ element.receivedEventType = undefined;
+ });
+}
+
+// Take over the refresh driver right from the start.
+advance_clock(0);
+
+// 1a. Test CSS Animation event handlers (without animationcancel)
+
+var targets = createAndRegisterTargets([ 'onanimationstart',
+ 'onanimationiteration',
+ 'onanimationend',
+ 'onanimationcancel']);
+targets.forEach(div => {
+ div.setAttribute('style', 'animation: anim 100ms 2');
+ getComputedStyle(div).animationName; // flush
+});
+
+advance_clock(0);
+checkReceivedEvents("animationstart", targets);
+
+advance_clock(100);
+checkReceivedEvents("animationiteration", targets);
+
+advance_clock(200);
+checkReceivedEvents("animationend", targets);
+
+targets.forEach(div => { div.remove(); });
+
+// 1b. Test CSS Animation cancel event handler
+var targets = createAndRegisterTargets([ 'onanimationcancel' ]);
+
+targets.forEach(div => {
+ div.setAttribute('style', 'animation: anim 100ms 2 200ms');
+ getComputedStyle(div).animationName; // flush
+});
+
+advance_clock(200);
+
+targets.forEach(div => {
+ div.style.display = "none"
+ getComputedStyle(div).display; // flush
+});
+
+advance_clock(0);
+checkReceivedEvents("animationcancel", targets);
+
+advance_clock(200);
+
+targets.forEach(div => { div.remove(); });
+
+
+// 2a. Test CSS Transition event handlers (without transitioncancel)
+
+var targets = createAndRegisterTargets([ 'ontransitionrun',
+ 'ontransitionstart',
+ 'ontransitionend',
+ 'ontransitioncancel' ]);
+targets.forEach(div => {
+ div.style.transition = 'margin-left 100ms 200ms';
+ getComputedStyle(div).marginLeft; // flush
+ div.style.marginLeft = "200px";
+ getComputedStyle(div).marginLeft; // flush
+});
+
+advance_clock(0);
+checkReceivedEvents("transitionrun", targets);
+
+advance_clock(200);
+checkReceivedEvents("transitionstart", targets);
+
+advance_clock(100);
+checkReceivedEvents("transitionend", targets);
+
+targets.forEach(div => { div.remove(); });
+
+// 2b. Test CSS Transition cancel event handler.
+
+var targets = createAndRegisterTargets([ 'ontransitioncancel' ]);
+targets.forEach(div => {
+ div.style.transition = 'margin-left 100ms 200ms';
+ getComputedStyle(div).marginLeft; // flush
+ div.style.marginLeft = "200px";
+ getComputedStyle(div).marginLeft; // flush
+});
+
+advance_clock(200);
+
+targets.forEach(div => {
+ div.style.display = "none"
+});
+getComputedStyle(targets[0]).display; // flush
+
+advance_clock(0);
+checkReceivedEvents("transitioncancel", targets);
+
+advance_clock(100);
+targets.forEach( div => { is(div.receivedEventType, undefined); });
+
+targets.forEach(div => { div.remove(); });
+
+// 3. Test prefixed CSS Animation event handlers.
+
+var targets = createAndRegisterTargets([ 'onwebkitanimationstart',
+ 'onwebkitanimationiteration',
+ 'onwebkitanimationend' ]);
+targets.forEach(div => {
+ div.setAttribute('style', 'animation: anim 100ms 2');
+ getComputedStyle(div).animationName; // flush
+});
+
+advance_clock(0);
+checkReceivedEvents("webkitAnimationStart", targets);
+
+advance_clock(100);
+checkReceivedEvents("webkitAnimationIteration", targets);
+
+advance_clock(200);
+checkReceivedEvents("webkitAnimationEnd", targets);
+
+targets.forEach(div => { div.remove(); });
+
+// 4. Test prefixed CSS Transition event handlers.
+
+advance_clock(0);
+var targets = createAndRegisterTargets([ 'onwebkittransitionend' ]);
+targets.forEach(div => {
+ div.style.transition = 'margin-left 100ms';
+ getComputedStyle(div).marginLeft; // flush
+ div.style.marginLeft = "200px";
+ getComputedStyle(div).marginLeft; // flush
+});
+
+advance_clock(100);
+checkReceivedEvents("webkitTransitionEnd", targets);
+
+targets.forEach(div => { div.remove(); });
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_event_order.html b/layout/style/test/test_animations_event_order.html
new file mode 100644
index 0000000000..7caee2bdcb
--- /dev/null
+++ b/layout/style/test/test_animations_event_order.html
@@ -0,0 +1,710 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1183461
+-->
+<!--
+ This test is similar to those in test_animations.html with the exception
+ that those tests interact with a single element at a time. The tests in this
+ file are specifically concerned with testing the ordering of events between
+ elements for which most of the utilities in animation_utils.js are not
+ suited.
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for CSS Animation and Transition event ordering
+ (Bug 1183461)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <!-- We still need animation_utils.js for advance_clock -->
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @keyframes anim { to { margin-left: 100px } }
+ @keyframes animA { to { margin-left: 100px } }
+ @keyframes animB { to { margin-left: 100px } }
+ @keyframes animC { to { margin-left: 100px } }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1183461">Mozilla Bug
+ 1183461</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+'use strict';
+
+/* eslint-disable no-shadow */
+
+// Take over the refresh driver right from the start.
+advance_clock(0);
+
+// Common test scaffolding
+
+var gEventsReceived = [];
+var gDisplay = document.getElementById('display');
+
+[ 'animationstart',
+ 'animationiteration',
+ 'animationend',
+ 'animationcancel',
+ 'transitionrun',
+ 'transitionstart',
+ 'transitionend',
+ 'transitioncancel' ]
+ .forEach(event =>
+ gDisplay.addEventListener(event,
+ event => gEventsReceived.push(event)));
+
+function checkEventOrder(...args) {
+ // Argument format:
+ // Arguments = ExpectedEvent*, desc
+ // ExpectedEvent =
+ // [ target|animationName|transitionProperty, (pseudo,) message ]
+ var expectedEvents = args.slice(0, -1);
+ var desc = args[args.length - 1];
+ var isTestingNameOrProperty = expectedEvents.length &&
+ typeof expectedEvents[0][0] == 'string';
+
+ var formatEvent = (target, nameOrProperty, pseudo, message) =>
+ isTestingNameOrProperty ?
+ `${nameOrProperty}${pseudo}:${message}` :
+ `${target.id}${pseudo}:${message}`;
+
+ var actual = gEventsReceived.map(
+ event => formatEvent(event.target,
+ event.animationName || event.propertyName,
+ event.pseudoElement, event.type)
+ ).join(';');
+ var expected = expectedEvents.map(
+ event => event.length == 3 ?
+ formatEvent(event[0], event[0], event[1], event[2]) :
+ formatEvent(event[0], event[0], '', event[1])
+ ).join(';');
+ is(actual, expected, desc);
+ gEventsReceived = [];
+}
+
+// 1. TESTS FOR SORTING BY TREE ORDER
+
+// 1a. Test that simultaneous events are sorted by tree order (siblings)
+
+var divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+ getComputedStyle(div).animationName; // trigger building of animation
+});
+
+advance_clock(0);
+checkEventOrder([ divs[0], 'animationstart' ],
+ [ divs[1], 'animationstart' ],
+ [ divs[2], 'animationstart' ],
+ 'Simultaneous start on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 1b. Test that simultaneous events are sorted by tree order (children)
+
+divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// / \
+// div[0] div[1]
+// /
+// div[2]
+
+gDisplay.appendChild(divs[0]);
+gDisplay.appendChild(divs[1]);
+divs[0].appendChild(divs[2]);
+
+divs.forEach((div, i) => {
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+ getComputedStyle(div).animationName; // trigger building of animation
+});
+
+advance_clock(0);
+checkEventOrder([ divs[0], 'animationstart' ],
+ [ divs[2], 'animationstart' ],
+ [ divs[1], 'animationstart' ],
+ 'Simultaneous start on children');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 1c. Test that simultaneous events are sorted by tree order (pseudos)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// |
+// div[0]
+// ::before,
+// ::after
+// |
+// div[1]
+
+gDisplay.appendChild(divs[0]);
+divs[0].appendChild(divs[1]);
+
+divs.forEach((div, i) => {
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+});
+
+var extraStyle = document.createElement('style');
+document.head.appendChild(extraStyle);
+var sheet = extraStyle.sheet;
+sheet.insertRule('#div0::after { animation: anim 10s }', 0);
+sheet.insertRule('#div0::before { animation: anim 10s }', 1);
+sheet.insertRule('#div0::after, #div0::before { ' +
+ ' content: " " }', 2);
+getComputedStyle(divs[0]).animationName; // build animation
+getComputedStyle(divs[1]).animationName; // build animation
+
+advance_clock(0);
+checkEventOrder([ divs[0], 'animationstart' ],
+ [ divs[0], '::before', 'animationstart' ],
+ [ divs[0], '::after', 'animationstart' ],
+ [ divs[1], 'animationstart' ],
+ 'Simultaneous start on pseudo-elements');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+sheet = undefined;
+extraStyle.remove();
+extraStyle = undefined;
+
+// 2. TESTS FOR SORTING BY TIME
+
+// 2a. Test that events are sorted by time
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.animationDelay = '5s';
+
+advance_clock(0);
+advance_clock(20000);
+
+checkEventOrder([ divs[1], 'animationstart' ],
+ [ divs[0], 'animationstart' ],
+ [ divs[1], 'animationend' ],
+ [ divs[0], 'animationend' ],
+ 'Sorting of start and end events by time');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 2b. Test different events within the one element
+
+var div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.animation = 'anim 4s 2, ' + // Repeat at t=4s
+ 'anim 10s 5s, ' + // Start at t=5s
+ 'anim 3s'; // End at t=3s
+div.setAttribute('id', 'div');
+getComputedStyle(div).animationName; // build animation
+
+advance_clock(0);
+advance_clock(5000);
+
+checkEventOrder([ div, 'animationstart' ],
+ [ div, 'animationstart' ],
+ [ div, 'animationend' ],
+ [ div, 'animationiteration' ],
+ [ div, 'animationstart' ],
+ 'Sorting of different events by time within an element');
+
+div.remove();
+div = undefined;
+
+// 2c. Test negative delay is sorted equal to zero delay but before
+// positive delay
+
+divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.animation = 'anim 20s 5s'; // Positive delay, sorts last
+divs[1].style.animation = 'anim 20s'; // 0s delay
+divs[2].style.animation = 'anim 20s -5s'; // Negative delay, sorts same as
+ // 0s delay, i.e. use document
+ // position
+
+advance_clock(0);
+advance_clock(5000);
+checkEventOrder([ divs[1], 'animationstart' ],
+ [ divs[2], 'animationstart' ],
+ [ divs[0], 'animationstart' ],
+ 'Sorting of events including negative delay');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 3. TESTS FOR SORTING BY animation-name POSITION
+
+// 3a. Test animation-name position
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.animation = 'animA 10s, animB 5s, animC 5s 2';
+div.setAttribute('id', 'div');
+getComputedStyle(div).animationName; // build animation
+
+advance_clock(0);
+
+checkEventOrder([ 'animA', 'animationstart' ],
+ [ 'animB', 'animationstart' ],
+ [ 'animC', 'animationstart' ],
+ 'Sorting of simultaneous animationstart events by ' +
+ 'animation-name');
+
+advance_clock(5000);
+
+checkEventOrder([ 'animB', 'animationend' ],
+ [ 'animC', 'animationiteration' ],
+ 'Sorting of different types of events by animation-name');
+
+div.remove();
+div = undefined;
+
+// 3b. Test time trumps animation-name position
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.animation = 'animA 10s 2s, animB 10s 1s';
+div.setAttribute('id', 'div');
+
+advance_clock(0);
+advance_clock(3000);
+
+checkEventOrder([ 'animB', 'animationstart' ],
+ [ 'animA', 'animationstart' ],
+ 'Events are sorted by time first, before animation-position');
+
+div.remove();
+div = undefined;
+
+// 4. TESTS FOR TRANSITIONS
+
+// 4a. Test sorting transitions by document position
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.style.transition = 'margin-left 10s';
+ div.setAttribute('id', 'div' + i);
+});
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4b. Test sorting transitions by document position (children)
+
+divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// / \
+// div[0] div[1]
+// /
+// div[2]
+
+gDisplay.appendChild(divs[0]);
+gDisplay.appendChild(divs[1]);
+divs[0].appendChild(divs[2]);
+
+divs.forEach((div, i) => {
+ div.style.marginLeft = '0px';
+ div.style.transition = 'margin-left 10s';
+ div.setAttribute('id', 'div' + i);
+});
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[2], 'transitionrun' ],
+ [ divs[2], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[2], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on children');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4c. Test sorting transitions by document position (pseudos)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// |
+// div[0]
+// ::before,
+// ::after
+// |
+// div[1]
+
+gDisplay.appendChild(divs[0]);
+divs[0].appendChild(divs[1]);
+
+divs.forEach((div, i) => {
+ div.setAttribute('id', 'div' + i);
+});
+
+extraStyle = document.createElement('style');
+document.head.appendChild(extraStyle);
+sheet = extraStyle.sheet;
+sheet.insertRule('div, #div0::after, #div0::before { ' +
+ ' transition: margin-left 10s; ' +
+ ' margin-left: 0px }', 0);
+sheet.insertRule('div.active, #div0.active::after, #div0.active::before { ' +
+ ' margin-left: 100px }', 1);
+sheet.insertRule('#div0::after, #div0::before { ' +
+ ' content: " " }', 2);
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.classList.add('active'));
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[0], '::before', 'transitionrun' ],
+ [ divs[0], '::before', 'transitionstart' ],
+ [ divs[0], '::after', 'transitionrun' ],
+ [ divs[0], '::after', 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[0], '::before', 'transitionend' ],
+ [ divs[0], '::after', 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on pseudo-elements');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+sheet = undefined;
+extraStyle.remove();
+extraStyle = undefined;
+
+// 4d. Test sorting transitions by time
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s';
+divs[1].style.transition = 'margin-left 5s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Sorting of transitionrun/start/end events by time');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4e. Test sorting transitions by time (with delay)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 5s 5s';
+divs[1].style.transition = 'margin-left 5s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Sorting of transitionrun/start/end events by time' +
+ '(including delay)');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4f. Test sorting transitions by transition-property
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.opacity = '0';
+div.style.marginLeft = '0px';
+div.style.transition = 'all 5s';
+
+getComputedStyle(div).marginLeft;
+div.style.opacity = '1';
+div.style.marginLeft = '100px';
+getComputedStyle(div).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'margin-left', 'transitionend' ],
+ [ 'opacity', 'transitionend' ],
+ 'Sorting of transitionrun/start/end events by ' +
+ 'transition-property')
+
+div.remove();
+div = undefined;
+
+// 4g. Test document position beats transition-property
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.style.opacity = '0';
+ div.style.transition = 'all 10s';
+ div.setAttribute('id', 'div' + i);
+});
+
+getComputedStyle(divs[0]).marginLeft;
+divs[0].style.opacity = '1';
+divs[1].style.marginLeft = '100px';
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Transition events are sorted by document position first, ' +
+ 'before transition-property');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4h. Test time beats transition-property
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.opacity = '0';
+div.style.marginLeft = '0px';
+div.style.transition = 'margin-left 10s, opacity 5s';
+
+getComputedStyle(div).marginLeft;
+div.style.opacity = '1';
+div.style.marginLeft = '100px';
+getComputedStyle(div).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'opacity', 'transitionend' ],
+ [ 'margin-left', 'transitionend' ],
+ 'Transition events are sorted by time first, before ' +
+ 'transition-property');
+
+div.remove();
+div = undefined;
+
+// 4i. Test sorting transitions by document position (negative delay)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s 5s';
+divs[1].style.transition = 'margin-left 10s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(15 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4j. Test sorting transitions with cancel
+// The order of transitioncancel is based on StyleManager.
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s 5s';
+divs[1].style.transition = 'margin-left 10s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(5 * 1000);
+divs.forEach(div => {
+ div.style.display = 'none';
+ // The transitioncancel event order is not absolute when firing siblings
+ // transitioncancel on same elapsed time.
+ // Force to flush style for the element so that the transition on the element
+ // iscancelled and corresponding cancel event is queued respectively.
+ getComputedStyle(div).display;
+});
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[0], 'transitioncancel' ],
+ [ divs[1], 'transitioncancel' ],
+ 'Simultaneous transitionrun/start/cancel on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+
+// 4k. Test sorting animations with cancel
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.animation = 'anim 10s 5s';
+divs[1].style.animation = 'anim 10s';
+
+getComputedStyle(divs[0]).animation; // flush
+
+advance_clock(0); // divs[1]'s animation start
+advance_clock(5 * 1000); // divs[0]'s animation start
+divs.forEach(div => {
+ div.style.display = 'none';
+ // The animationcancel event order is not absolute when firing siblings
+ // animationcancel on same elapsed time.
+ // Force to flush style for the element so that the transition on the element
+ // iscancelled and corresponding cancel event is queued respectively.
+ getComputedStyle(div).display;
+});
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[1], 'animationstart' ],
+ [ divs[0], 'animationstart' ],
+ [ divs[0], 'animationcancel' ],
+ [ divs[1], 'animationcancel' ],
+ 'Simultaneous animationcancel on siblings');
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_iterationstart.html b/layout/style/test/test_animations_iterationstart.html
new file mode 100644
index 0000000000..dbca9490e4
--- /dev/null
+++ b/layout/style/test/test_animations_iterationstart.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for Animation.effect.timing.iterationStart on compositor animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div ] = new_div("test");
+ var animation = div.animate(
+ { transform: ["translate(0px)", "translate(100px)"] },
+ { iterationStart: 0.5, duration: 10000, fill: "both"}
+ );
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, "Start of Animation");
+
+ advance_clock(4000);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 90 }, RunningOn.Compositor, "40% of Animation");
+
+ advance_clock(6000);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread, "End of Animation");
+
+ done_div();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_omta.html b/layout/style/test/test_animations_omta.html
new file mode 100644
index 0000000000..06a409b490
--- /dev/null
+++ b/layout/style/test/test_animations_omta.html
@@ -0,0 +1,2969 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=964646
+-->
+<!--
+
+ ========= PLEASE KEEP THIS IN SYNC WITH test_animations.html =========
+
+ This test mimicks the content of test_animations.html but performs tests
+ specific to animations that run on the compositor thread since they require
+ special (asynchronous) handling. Furthermore, these tests check that
+ animations that are expected to run on the compositor thread, are actually
+ doing so.
+
+ If you are making changes to this file or to test_animations.html, please
+ try to keep them consistent where appropriate.
+
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for css3-animations running on the compositor thread (Bug
+ 964646)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ @keyframes transform-anim {
+ to {
+ transform: translate(100px);
+ }
+ }
+ @keyframes anim1 {
+ 0% { transform: translate(0px) }
+ 50% { transform: translate(80px) }
+ 100% { transform: translate(100px) }
+ }
+ @keyframes anim2 {
+ from { opacity: 0 } to { opacity: 1 }
+ }
+ @keyframes anim3 {
+ from { opacity: 0 } to { opacity: 1 }
+ }
+ @keyframes anim4 {
+ from { transform: translate(0px, 0px) }
+ to { transform: translate(0px, 100px) }
+ }
+
+ @keyframes kf1 {
+ 50% { transform: translate(50px) }
+ to { transform: translate(150px) }
+ }
+ @keyframes kf2 {
+ from { transform: translate(150px) }
+ 50% { transform: translate(50px) }
+ }
+ @keyframes kf3 {
+ 25% { transform: translate(100px) }
+ }
+ @keyframes kf4 {
+ to, from { display: none; transform: translate(37px) }
+ }
+ @keyframes kf_cascade1 {
+ from { transform: translate(50px) }
+ 50%, from { transform: translate(30px) } /* wins: 0% */
+ 75%, 85%, 50% { transform: translate(20px) } /* wins: 75%, 50% */
+ 100%, 85% { transform: translate(70px) } /* wins: 100% */
+ 85.1% { transform: translate(60px) } /* wins: 85.1% */
+ 85% { transform: translate(30px) } /* wins: 85% */
+ }
+ @keyframes kf_cascade2 { from, to { opacity: 0.3 } }
+ @keyframes kf_cascade2 { from, to { transform: translate(50px) } }
+ @keyframes kf_cascade2 { from, to { transform: translate(100px) } }
+ @keyframes kf_tf1 {
+ 0% { transform: translate(20px); animation-timing-function: ease }
+ 25% { transform: translate(60px); }
+ 50% { transform: translate(160px); animation-timing-function: steps(5) }
+ 75% { transform: translate(120px); animation-timing-function: linear }
+ 100% { transform: translate(20px); animation-timing-function: ease-out }
+ }
+ @keyframes kf_scale {
+ to { scale: 2.25 2.25; }
+ }
+
+ @keyframes always_fifty {
+ from, to { transform: translate(50px) }
+ }
+
+ #withbefore::before, #withafter::after {
+ content: "test";
+ animation: anim4 1s linear alternate 3;
+ display:block;
+ }
+
+ @keyframes multiprop {
+ 0% {
+ transform: translate(10px); opacity: 0.3;
+ animation-timing-function: ease;
+ }
+ 25% {
+ opacity: 0.5;
+ animation-timing-function: ease-out;
+ }
+ 50% {
+ transform: translate(40px);
+ }
+ 75% {
+ transform: translate(80px); opacity: 0.6;
+ animation-timing-function: ease-in;
+ }
+ }
+
+ @keyframes cascade {
+ 0%, 25%, 100% { transform: translate(0px) }
+ 50%, 75% { transform: translate(100px) }
+ 0%, 75%, 100% { opacity: 0 }
+ 25%, 50% { opacity: 1 }
+ }
+ @keyframes cascade2 {
+ 0% { transform: translate(0px) }
+ 25% { transform: translate(30px);
+ animation-timing-function: ease-in } /* beaten by rule below */
+ 50% { transform: translate(0px) }
+ 25% { transform: translate(50px) }
+ 100% { transform: translate(100px) }
+ }
+
+ @keyframes primitives1 {
+ from { transform: rotate(0deg) translateX(0px) scaleX(1)
+ translate(0px) scale3d(1, 1, 1); }
+ to { transform: rotate(270deg) translate3d(0px, 0px, 0px) scale(1)
+ translateY(0px) scaleY(1); }
+ }
+
+ @keyframes important1 {
+ from { opacity: 0.5; }
+ 50% { opacity: 1 !important; } /* ignored */
+ to { opacity: 0.8; }
+ }
+ @keyframes important2 {
+ from { opacity: 0.5;
+ transform: translate(100px); }
+ to { opacity: 0.2 !important; /* ignored */
+ transform: translate(50px); }
+ }
+
+ @keyframes empty { }
+ @keyframes nearlyempty {
+ to {
+ transform: translate(100px);
+ }
+ }
+
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+
+ .visitedLink:link { background-color: yellow }
+ .visitedLink:visited { background-color: blue }
+
+ @keyframes opacitymid {
+ 0% { opacity: 0.2 }
+ 100% { opacity: 0.8 }
+ }
+
+ @keyframes transformnone {
+ 0%, 100% { transform: translateX(50px) }
+ 25%, 75% { transform: none }
+ }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=964646">Mozilla Bug
+ 964646</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+/** Test for css3-animations running on the compositor thread (Bug 964646) **/
+
+// Global state
+var gDisplay = document.getElementById("display")
+ , gDiv = null;
+
+// Shortcut omta_is and friends by filling in the initial 'elem' argument
+// with gDiv.
+[ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) {
+ var origFn = window[fn];
+ window[fn] = function() {
+ var args = Array.from(arguments);
+ if (!(args[0] instanceof Element)) {
+ args.unshift(gDiv);
+ }
+ return origFn.apply(window, args);
+ };
+});
+
+// Shortcut new_div and done_div to update gDiv
+var originalNewDiv = window.new_div;
+window.new_div = function(style) {
+ [ gDiv ] = originalNewDiv(style);
+};
+var originalDoneDiv = window.done_div;
+window.done_div = function() {
+ originalDoneDiv();
+ gDiv = null;
+};
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+runOMTATest(function() {
+ var onAbort = function() {
+ if (gDiv) {
+ done_div();
+ }
+ };
+ runAllAsyncAnimTests(onAbort).then(function() {
+ SimpleTest.finish();
+ });
+}, SimpleTest.finish);
+
+//----------------------------------------------------------------------
+//
+// Test cases
+//
+//----------------------------------------------------------------------
+
+// This test is not in test_animations.html but is here to test that
+// transform animations are actually run on the compositor thread as expected.
+addAsyncAnimTest(async function() {
+ new_div("animation: transform-anim linear 300s");
+
+ await waitForPaintsFlushed();
+
+ advance_clock(200000);
+ omta_is("transform", { tx: 100 * 2 / 3 }, RunningOn.Compositor,
+ "OMTA animation is animating as expected");
+ done_div();
+});
+
+async function testFillMode(fillMode, fillsBackwards, fillsForwards)
+{
+ var style = "transform: translate(30px); animation: 10s 3s anim1 linear";
+ var desc;
+ if (fillMode.length > 0) {
+ style += " " + fillMode;
+ desc = "fill mode " + fillMode + ": ";
+ } else {
+ desc = "default fill mode: ";
+ }
+ new_div(style);
+ listen();
+
+ await waitForPaintsFlushed();
+
+ if (fillsBackwards)
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ desc + "does affect value during delay (0s)");
+ else
+ omta_is("transform", { tx: 30 }, RunningOn.MainThread,
+ desc + "doesn't affect value during delay (0s)");
+
+ advance_clock(2000);
+ if (fillsBackwards)
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ desc + "does affect value during delay (0s)");
+ else
+ omta_is("transform", { tx: 30 }, RunningOn.MainThread,
+ desc + "does affect value during delay (0s)");
+
+ check_events([], "before start in testFillMode");
+ advance_clock(1000);
+ check_events([{ type: "animationstart", target: gDiv,
+ bubbles: true, cancelable: false,
+ animationName: "anim1", elapsedTime: 0.0,
+ pseudoElement: "" }],
+ "right after start in testFillMode");
+
+ // If we have a backwards fill then at the start of the animation we will end
+ // up applying the same value as the fill value. Various optimizations in
+ // RestyleManager may filter out this meaning that the animation doesn't get
+ // added to the compositor thread until the first time the value changes.
+ //
+ // As a result we look for this first sample on either the compositor or the
+ // computed style
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.Either,
+ desc + "affects value at start of animation");
+ advance_clock(125);
+ // We might not add the animation to compositor until the second sample (due
+ // to the optimizations mentioned above) so we should wait for paints before
+ // proceeding
+ await waitForPaints();
+ omta_is("transform", { tx: 2 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ advance_clock(2375);
+ omta_is("transform", { tx: 40 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ advance_clock(2500);
+ omta_is("transform", { tx: 80 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ advance_clock(2500);
+ omta_is("transform", { tx: 90 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ advance_clock(2375);
+ omta_is("transform", { tx: 99.5 }, RunningOn.Compositor,
+ desc + "affects value during animation");
+ check_events([], "before end in testFillMode");
+ advance_clock(125);
+ check_events([{ type: "animationend", target: gDiv,
+ bubbles: true, cancelable: false,
+ animationName: "anim1", elapsedTime: 10.0,
+ pseudoElement: "" }],
+ "right after end in testFillMode");
+
+ // Currently the compositor will apply a forwards fill until it gets told by
+ // the main thread to clear the animation. As a result we should wait for
+ // paints to be flushed before checking that the animated value does *not*
+ // appear on the compositor thread.
+ await waitForPaints();
+ if (fillsForwards)
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ desc + "affects value at end of animation");
+ advance_clock(10);
+ if (fillsForwards)
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ desc + "affects value after animation");
+ else
+ omta_is("transform", { tx: 30 }, RunningOn.MainThread,
+ desc + "does not affect value after animation");
+
+ done_div();
+}
+
+addAsyncAnimTest(function() { return testFillMode("", false, false); });
+addAsyncAnimTest(function() { return testFillMode("none", false, false); });
+addAsyncAnimTest(function() { return testFillMode("forwards", false, true); });
+addAsyncAnimTest(function() { return testFillMode("backwards", true, false); });
+addAsyncAnimTest(function() { return testFillMode("both", true, true); });
+
+// Test that animations continue running when the animation name
+// list is changed.
+//
+// test_animations.html combines all these tests into one block but this is
+// difficult for OMTA because currently there are only two properties to which
+// we apply OMTA. Instead we break the test down into a few independent pieces
+// in order to exercise the same functionality.
+
+// Append to list
+addAsyncAnimTest(async function() {
+ new_div("animation: anim1 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Either,
+ "just anim1, translate at start");
+ advance_clock(1000);
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "just anim1, translate at 1s");
+ // append anim2
+ gDiv.style.animation = "anim1 linear 10s, anim2 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "anim1 + anim2, translate at 1s");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim1 + anim2, opacity at 1s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim1 + anim2, translate at 2s");
+ omta_is("opacity", 0.1, RunningOn.Compositor,
+ "anim1 + anim2, opacity at 2s");
+ done_div();
+});
+
+// Prepend to list; delete from list
+addAsyncAnimTest(async function() {
+ new_div("animation: anim1 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Either,
+ "just anim1, translate at start");
+ advance_clock(1000);
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "just anim1, translate at 1s");
+ // prepend anim2
+ gDiv.style.animation = "anim2 linear 10s, anim1 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "anim2 + anim1, translate at 1s");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim2 + anim1, opacity at 1s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim2 + anim1, translate at 2s");
+ omta_is("opacity", 0.1, RunningOn.Compositor,
+ "anim2 + anim1, opacity at 2s");
+ // remove anim2 from list
+ gDiv.style.animation = "anim1 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "just anim1, translate at 2s");
+ omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 2s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 48 }, RunningOn.Compositor,
+ "just anim1, translate at 3s");
+ omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 3s");
+ done_div();
+});
+
+// Swap elements
+addAsyncAnimTest(async function() {
+ new_div("animation: anim1 linear 10s, anim2 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Either,
+ "anim1 + anim2, translate at start");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim1 + anim2, opacity at start");
+ advance_clock(1000);
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "anim1 + anim2, translate at 1s");
+ omta_is("opacity", 0.1, RunningOn.Compositor,
+ "anim1 + anim2, opacity at 1s");
+ // swap anim1 and anim2, change duration of anim2
+ gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 16 }, RunningOn.Compositor,
+ "anim2 + anim1, translate at 1s");
+ omta_is("opacity", 0.2, RunningOn.Compositor,
+ "anim2 + anim1, opacity at 1s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim2 + anim1, translate at 2s");
+ omta_is("opacity", 0.4, RunningOn.Compositor,
+ "anim2 + anim1, opacity at 2s");
+ // list anim2 twice, last duration wins, original start time still applies
+ gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s, anim2 linear 20s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim2 + anim1 + anim2, translate at 2s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1",
+ "anim2 + anim1 + anim2, opacity at 2s");
+ // drop one of the anim2, and list anim3 as well, which animates
+ // the same property as anim2
+ gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 32 }, RunningOn.Compositor,
+ "anim1 + anim2 + anim3, translate at 2s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0",
+ "anim1 + anim2 + anim3, opacity at 2s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 48 }, RunningOn.Compositor,
+ "anim1 + anim2 + anim3, translate at 3s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1",
+ "anim1 + anim2 + anim3, opacity at 3s");
+ // now swap the anim3 and anim2 order
+ gDiv.style.animation = "anim1 linear 10s, anim3 linear 10s, anim2 linear 20s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 48 }, RunningOn.Compositor,
+ "anim1 + anim3 + anim2, translate at 3s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.15",
+ "anim1 + anim3 + anim2, opacity at 3s");
+ advance_clock(2000); // (unlike test_animations.html, we seek 2s forwards here
+ // since at 4s anim2 and anim3 produce the same result so
+ // we can't tell which won.)
+ omta_is("transform", { tx: 80 }, RunningOn.Compositor,
+ "anim1 + anim3 + anim2, translate at 5s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.25",
+ "anim1 + anim3 + anim2, opacity at 5s");
+ // swap anim3 and anim2 back
+ gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 80 }, RunningOn.Compositor,
+ "anim1 + anim2 + anim3, translate at 5s");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.3",
+ "anim1 + anim2 + anim3, opacity at 5s");
+ // seek past end of anim1
+ advance_clock(5100);
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "anim1 + anim2 + anim3, translate at 10.1s");
+ // Change the animation fill mode on the completed animation.
+ gDiv.style.animation =
+ "anim1 linear 10s forwards, anim2 linear 20s, anim3 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ "anim1 + anim2 + anim3, translate at 10.1s with fill mode");
+ advance_clock(900);
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ "anim1 + anim2 + anim3, translate at 11s with fill mode");
+ // Change the animation duration on the completed animation, so it is
+ // no longer completed.
+ // XXX Not sure about this---there seems to be a bug in test_animations.html
+ // in that it drops the fill mode but the test comment says it has a fill mode
+ gDiv.style.animation = "anim1 linear 20s, anim2 linear 20s, anim3 linear 10s";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 82 }, RunningOn.Compositor,
+ "anim1 + anim2 + anim3, translate at 11s with fill mode");
+ is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.9",
+ "anim1 + anim2 + anim3, opacity at 11s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3. Keyframes
+ * http://dev.w3.org/csswg/css3-animations/#keyframes
+ */
+
+// Test the rules on keyframes that lack a 0% or 100% rule:
+// (simultaneously, test that reverse animations have their keyframes
+// run backwards)
+
+addAsyncAnimTest(async function() {
+ // 100px at 0%, 50px at 50%, 150px at 100%
+ new_div("transform: translate(100px); " +
+ "animation: kf1 ease 1s alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 0.0s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0% at 0.1s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 0.01,
+ RunningOn.Compositor, "no-0% at 0.3s");
+ advance_clock(200);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-0% at 0.5s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.4) }, 0.01,
+ RunningOn.Compositor, "no-0% at 0.7s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 0.01,
+ RunningOn.Compositor, "no-0% at 0.9s");
+ advance_clock(100);
+ omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-0% at 1.0s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 0.01,
+ RunningOn.Compositor, "no-0% at 1.1s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0% at 1.4s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 0.01,
+ RunningOn.Compositor, "no-0% at 1.7s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0% at 1.9s");
+ advance_clock(100);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 2.0s");
+ done_div();
+
+ // 150px at 0%, 50px at 50%, 100px at 100%
+ new_div("transform: translate(100px); " +
+ "animation: kf2 ease-in 1s alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 0.0s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "no-100% at 0.1s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor, "no-100% at 0.3s");
+ advance_clock(200);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-100% at 0.5s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.4) }, 0.01,
+ RunningOn.Compositor, "no-100% at 0.7s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 0.01,
+ RunningOn.Compositor, "no-100% at 0.9s");
+ advance_clock(100);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-100% at 1.0s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 0.01,
+ RunningOn.Compositor, "no-100% at 1.1s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "no-100% at 1.4s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor, "no-100% at 1.7s");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "no-100% at 1.9s");
+ advance_clock(100);
+ omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 2.0s");
+ done_div();
+
+ // 50px at 0%, 100px at 25%, 50px at 100%
+ new_div("transform: translate(50px); " +
+ "animation: kf3 ease-out 1s alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "no-0%-no-100% at 0.0s");
+ advance_clock(50);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 0.05s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 0.15s");
+ advance_clock(100);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "no-0%-no-100% at 0.25s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.4) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 0.55s");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 0.85s");
+ advance_clock(150);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "no-0%-no-100% at 1.0s");
+ advance_clock(150);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 1.15s");
+ advance_clock(450);
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 1.6s");
+ advance_clock(250);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 1.85s");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 0.01,
+ RunningOn.Compositor, "no-0%-no-100% at 1.95s");
+ advance_clock(50);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "no-0%-no-100% at 2.0s");
+ done_div();
+
+ // Test that non-animatable properties are ignored.
+ // Simultaneously, test that the block is still honored, and that
+ // we still override the value when two consecutive keyframes have
+ // the same value.
+ new_div("animation: kf4 ease 10s");
+ await waitForPaintsFlushed();
+ var cs = window.getComputedStyle(gDiv);
+ is(cs.display, "block",
+ "non-animatable properties should be ignored (linear, 0s)");
+ omta_is("transform", { tx: 37 }, RunningOn.Compositor,
+ "animatable properties should still apply (linear, 0s)");
+ advance_clock(1000);
+ is(cs.display, "block",
+ "non-animatable properties should be ignored (linear, 1s)");
+ omta_is("transform", { tx: 37 }, RunningOn.Compositor,
+ "animatable properties should still apply (linear, 1s)");
+ done_div();
+ new_div("animation: kf4 step-start 10s");
+ await waitForPaintsFlushed();
+ cs = window.getComputedStyle(gDiv);
+ is(cs.display, "block",
+ "non-animatable properties should be ignored (step-start, 0s)");
+ omta_is("transform", { tx: 37 }, RunningOn.Compositor,
+ "animatable properties should still apply (step-start, 0s)");
+ advance_clock(1000);
+ is(cs.display, "block",
+ "non-animatable properties should be ignored (step-start, 1s)");
+ omta_is("transform", { tx: 37 }, RunningOn.Compositor,
+ "animatable properties should still apply (step-start, 1s)");
+ done_div();
+
+ // Test cascading of the keyframes within an @keyframes rule.
+ new_div("animation: kf_cascade1 linear 10s");
+ await waitForPaintsFlushed();
+ // 0%: 30px
+ // 50%: 20px
+ // 75%: 20px
+ // 85%: 30px
+ // 85.1%: 60px
+ // 100%: 70px
+ omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 0s");
+ advance_clock(2500);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 2.5s");
+ advance_clock(2500);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 5s");
+ advance_clock(2000);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7s");
+ advance_clock(500);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7.5s");
+ advance_clock(500);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 8s");
+ advance_clock(500);
+ omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 8.5s");
+ advance_clock(10);
+ // For some reason we get an error of 0.0003 for this test only
+ omta_is_approx("transform", { tx: 60 }, 0.001, RunningOn.Compositor,
+ "kf_cascade1 at 8.51s");
+ advance_clock(745);
+ omta_is("transform", { tx: 65 }, RunningOn.Compositor,
+ "kf_cascade1 at 9.2505s");
+ done_div();
+
+ // Test cascading of the @keyframes rules themselves.
+ new_div("animation: kf_cascade2 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "last @keyframes rule with transform should win");
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "last @keyframes rule with transform should win");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.1. Timing functions for keyframes
+ * http://dev.w3.org/csswg/css3-animations/#timing-functions-for-keyframes-
+ */
+
+addAsyncAnimTest(async function() {
+ new_div("animation: kf_tf1 ease-in 10s alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 0s (test needed for flush)");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 1s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.8) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 2s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 3s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 4s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 160 }, RunningOn.Compositor,
+ "keyframe timing functions test at 5s");
+ advance_clock(1010); // avoid floating-point error
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 6s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 7s");
+ advance_clock(990);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 8s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.6) }, 0.01,
+ RunningOn.Compositor, "keyframe timing functions test at 9s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 10s");
+ advance_clock(20000);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 30s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.6) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 31s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 32s");
+ advance_clock(990); // avoid floating-point error
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 33s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 34s");
+ advance_clock(1010);
+ omta_is("transform", { tx: 160 }, RunningOn.Compositor,
+ "keyframe timing functions test at 35s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 36s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 37s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.8) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 38s");
+ advance_clock(1000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 39s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 40s");
+ done_div();
+
+ // spot-check the same thing without alternate
+ new_div("animation: kf_tf1 ease-in 10s infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 20 }, RunningOn.Compositor,
+ "keyframe timing functions test at 0s (test needed for flush)");
+ advance_clock(11000);
+ omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 11s");
+ advance_clock(3000);
+ omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 14s");
+ advance_clock(2010); // avoid floating-point error
+ omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 16s");
+ advance_clock(1990);
+ omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01,
+ RunningOn.Compositor,
+ "keyframe timing functions test at 18s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.2. The 'animation-name' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-name-property-
+ */
+
+// animation-name is reasonably well-tested up in the tests for Section
+// 2, particularly the tests that "Test that animations continue running
+// when the animation name list is changed."
+
+// Test that 'animation-name: none' stops the animation, and setting
+// it again starts a new one.
+
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 ease-in-out 10s");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "after setting animation-name to anim2");
+ advance_clock(1000);
+ omta_is_approx("opacity", gTF.ease_in_out(0.1), 0.01, RunningOn.Compositor,
+ "before changing animation-name to none");
+ gDiv.style.animationName = "none";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "after changing animation-name to none");
+ advance_clock(1000);
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "after changing animation-name to none plus 1s");
+ gDiv.style.animationName = "anim2";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "after changing animation-name to anim2");
+ advance_clock(1000);
+ omta_is_approx("opacity", gTF.ease_in_out(0.1), 0.01, RunningOn.Compositor,
+ "at 1s in animation when animation-name no longer none again");
+ gDiv.style.animationName = "none";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "after changing animation-name to none");
+ advance_clock(1000);
+ omta_is("opacity", 1, RunningOn.MainThread,
+ "after changing animation-name to none plus 1s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.3. The 'animation-duration' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-duration-property-
+ */
+
+// FIXME: test animation-duration of 0 (quite a bit, including interaction
+// with fill-mode, count, and reversing), once I know what the right
+// behavior is.
+
+/*
+ * css3-animations: 3.4. The 'animation-timing-function' Property
+ * http://dev.w3.org/csswg/css3-animations/#animation-timing-function_tag
+ */
+
+// tested in tests for section 3.1
+
+/*
+ * css3-animations: 3.5. The 'animation-iteration-count' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-iteration-count-property-
+ */
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 ease-in 10s 0.3 forwards");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-iteration-count test 1 at 0s");
+ advance_clock(2000);
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-iteration-count test 1 at 2s");
+ advance_clock(900);
+ omta_is_approx("opacity", gTF.ease_in(0.29), 0.01, RunningOn.Compositor,
+ "animation-iteration-count test 1 at 2.9s");
+ advance_clock(100);
+ // Animation has reached the end so allow it to be cleared from the compositor
+ await waitForPaints();
+ // For transform animations we can tell whether a transform on the compositor
+ // thread was set by animation or not since there is a special flag for it.
+ //
+ // For opacity animations, however, there is no such flag so we'll get an
+ // "OMTA" opacity even when it wasn't set by animation. When we pause an
+ // opacity animation we don't worry about where it is reported to be running
+ // (main thread or compositor) so long as the result is correct, hence we
+ // check for "either" below.
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either,
+ "animation-iteration-count test 1 at 3s");
+ advance_clock(100);
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either,
+ "animation-iteration-count test 1 at 3.1s");
+ advance_clock(5000);
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either,
+ "animation-iteration-count test 1 at 8.1s");
+ done_div();
+
+ // The corresponding test in test_animations.html runs three animations in
+ // parallel but since we only have two properties that are OMTA-enabled at
+ // this time and no additive animation we split this test into two parts.
+ new_div("animation: anim2 ease-in 10s 0.3, " +
+ "anim4 ease-out 20s 1.2 alternate forwards");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-iteration-count test 2 at 0s");
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "animation-iteration-count test 3 at 0s");
+ advance_clock(2000);
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-iteration-count test 2 at 2s");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.1) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 2s");
+ advance_clock(900);
+ omta_is_approx("opacity", gTF.ease_in(0.29), 0.01, RunningOn.Compositor,
+ "animation-iteration-count test 2 at 2.9s");
+ advance_clock(200);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-iteration-count test 2 at 3.1s");
+ advance_clock(2000);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-iteration-count test 2 at 5.1s");
+ advance_clock(14700);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.99) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 19.8s");
+ advance_clock(200);
+ omta_is("transform", { ty: 100 }, RunningOn.Compositor,
+ "animation-iteration-count test 3 at 20s");
+ advance_clock(200);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.99) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 20.2s");
+ advance_clock(3600);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.81) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 23.8s");
+ advance_clock(200);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.8) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 3 at 24s");
+ advance_clock(200);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-iteration-count test 2 at 25s");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.8) }, 0.01,
+ RunningOn.MainThread,
+ "animation-iteration-count test 3 at 25s");
+ done_div();
+
+ new_div("animation: anim4 ease-in-out 5s 1.6 forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "animation-iteration-count test 4 at 0s");
+ advance_clock(2000);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.4) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 2s");
+ advance_clock(2900);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.98) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 4.9s");
+ advance_clock(200);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.02) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 5.1s");
+ advance_clock(2800);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.58) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 7.9s");
+ advance_clock(100);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01,
+ RunningOn.Compositor,
+ "animation-iteration-count test 4 at 8s");
+ advance_clock(100);
+ await waitForPaints();
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01,
+ RunningOn.Either,
+ "animation-iteration-count test 4 at 8.1s");
+ advance_clock(16100);
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01,
+ RunningOn.Either,
+ "animation-iteration-count test 4 at 25s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.6. The 'animation-direction' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-direction-property-
+ */
+
+// Tested in tests for sections 3.1 and 3.5.
+
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 ease-in 10s infinite");
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 0s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 0s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 0s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 0s");
+ advance_clock(2000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 2s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 2s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 2s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 2s");
+ advance_clock(5000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.7), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 7s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 7s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.7), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 7s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 7s");
+ advance_clock(5000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 12s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 12s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 12s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 12s");
+ advance_clock(10000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 22s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 22s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 22s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 22s");
+ advance_clock(30000);
+ gDiv.style.animationDirection = "normal";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (normal) at 52s");
+ gDiv.style.animationDirection = "reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (reverse) at 52s");
+ gDiv.style.animationDirection = "alternate";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate) at 52s");
+ gDiv.style.animationDirection = "alternate-reverse";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor,
+ "animation-direction test 1 (alternate-reverse) at 52s");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.7. The 'animation-play-state' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-play-state-property-
+ */
+
+addAsyncAnimTest(async function() {
+ // simple test with just one animation
+ new_div("");
+ gDiv.style.animationTimingFunction = "ease";
+ gDiv.style.animationName = "anim1";
+ gDiv.style.animationDuration = "1s";
+ gDiv.style.animationDirection = "alternate";
+ gDiv.style.animationIterationCount = "2";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "animation-play-state test 1, at 0s");
+ advance_clock(250);
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 250ms");
+ gDiv.style.animationPlayState = "paused";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 at 250ms");
+ advance_clock(250);
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 still at 500ms");
+ gDiv.style.animationPlayState = "running";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 still at 500ms");
+ advance_clock(500);
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 1000ms");
+ advance_clock(250);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "animation-play-state test 1 at 1250ms");
+ advance_clock(250);
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 1500ms");
+ gDiv.style.animationPlayState = "paused";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 at 1500ms");
+ advance_clock(2000);
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 at 3500ms");
+ advance_clock(500);
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 1 at 4000ms");
+ gDiv.style.animationPlayState = "";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 4000ms");
+ advance_clock(500);
+ omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 1 at 4500ms");
+ advance_clock(250);
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "animation-play-state test 1, at 4750ms");
+ advance_clock(250);
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "animation-play-state test 1, at 5000ms");
+ done_div();
+
+ // The corresponding test in test_animations.html tests various cases of
+ // pausing individual animations in a list of three different animations
+ // but since there are only two OMTA properties we can animate
+ // independently this test is substantially simpler.
+ new_div("");
+ gDiv.style.animationTimingFunction = "ease-out, ease-in";
+ gDiv.style.animationName = "anim2, anim4";
+ gDiv.style.animationDuration = "1s, 2s";
+ gDiv.style.animationDirection = "alternate, normal";
+ gDiv.style.animationIterationCount = "4, 2";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "animation-play-state test 2, at 0s");
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "animation-play-state test 3, at 0s");
+ advance_clock(250);
+ gDiv.style.animationPlayState = "paused, running"; // pause 1
+ await waitForPaintsFlushed();
+ // As noted with the tests for animation-iteration-count, for opacity
+ // animations we don't strictly check the finished animation is being animated
+ // on the main thread, but simply that it is producing the correct result.
+ omta_is_approx("opacity", gTF.ease_out(0.25), 0.01, RunningOn.MainThread,
+ "animation-play-state test 2 at 250ms"); // paused
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.125) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 250ms");
+ advance_clock(250);
+ omta_is_approx("opacity", gTF.ease_out(0.25), 0.01, RunningOn.MainThread,
+ "animation-play-state test 2 at 500ms"); // paused
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.25) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 500ms");
+ advance_clock(250);
+ gDiv.style.animationPlayState = "running, paused"; // unpause 1, pause 2
+ await waitForPaintsFlushed();
+ advance_clock(250);
+ omta_is_approx("opacity", gTF.ease_out(0.5), 0.01, RunningOn.Compositor,
+ "animation-play-state test 2 at 1000ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 1000ms"); // paused
+ gDiv.style.animationPlayState = "paused"; // pause all
+ await waitForPaintsFlushed();
+ advance_clock(3000);
+ omta_is_approx("opacity", gTF.ease_out(0.5), 0.01, RunningOn.MainThread,
+ "animation-play-state test 2 at 4000ms"); // paused
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 4000ms"); // paused
+ gDiv.style.animationPlayState = "running, paused"; // pause 2
+ await waitForPaintsFlushed();
+ advance_clock(850);
+ omta_is_approx("opacity", gTF.ease_out(0.65), 0.01, RunningOn.Compositor,
+ "animation-play-state test 2 at 4850ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 4850ms");
+ advance_clock(300);
+ omta_is_approx("opacity", gTF.ease_out(0.35), 0.01, RunningOn.Compositor,
+ "animation-play-state test 2 at 5150ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 5150ms");
+ advance_clock(2300);
+ omta_is_approx("opacity", gTF.ease_out(0.05), 0.01, RunningOn.Compositor,
+ "animation-play-state test 2 at 7450ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 7450ms");
+ advance_clock(100);
+ // test 2 has finished so wait for it to be removed from the
+ // compositor (otherwise it will fill forwards)
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 7550ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01,
+ RunningOn.MainThread,
+ "animation-play-state test 3 at 7550ms");
+ gDiv.style.animationPlayState = "running"; // unpause 2
+ await waitForPaintsFlushed();
+ advance_clock(1000);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 7550ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.875) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 7550ms");
+ advance_clock(500);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 8050ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.125) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 8050ms");
+ advance_clock(1000);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 9050ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.625) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 9050ms");
+ advance_clock(500);
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 9550ms");
+ omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.875) }, 0.01,
+ RunningOn.Compositor,
+ "animation-play-state test 3 at 9550ms");
+ advance_clock(500);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "animation-play-state test 2 at 10050ms");
+ omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+ "animation-play-state test 3 at 10050ms");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.8. The 'animation-delay' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-delay-property-
+ */
+
+addAsyncAnimTest(async function() {
+ // test positive delay
+ new_div("animation: anim2 1s 0.5s ease-out");
+ await waitForPaintsFlushed();
+ // NOTE: getOMTAStyle() can't detect the animation is running on the
+ // compositor or not during the delay phase, since no opacity style is
+ // applied during the delay phase.
+ omta_is("opacity", 1, RunningOn.Either, "positive delay test at 0ms");
+ advance_clock(400);
+ omta_is("opacity", 1, RunningOn.Either, "positive delay test at 400ms");
+ advance_clock(100);
+ await waitForPaints();
+ omta_is("opacity", 0, RunningOn.Compositor, "positive delay test at 500ms");
+ advance_clock(100);
+ omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Compositor,
+ "positive delay test at 500ms");
+ done_div();
+
+ // test dynamic changes to delay (i.e., that we preserve the start time
+ // that's before the delay)
+ new_div("animation: anim2 1s 0.5s ease-out both");
+ await waitForPaintsFlushed();
+ // NOTE: As noted above, getOMTAStyle() can't detect the animation is running
+ // on the compositor during the delay phase.
+ omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 0ms");
+ advance_clock(400);
+ omta_is("opacity", 0, RunningOn.Either,
+ "dynamic delay delay test at 400ms (1)");
+ gDiv.style.animationDelay = "0.2s";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor,
+ "dynamic delay delay test at 400ms (2)");
+ gDiv.style.animationDelay = "0.6s";
+ await waitForPaintsFlushed();
+ advance_clock(200);
+ omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 600ms");
+ advance_clock(200);
+ await waitForPaints();
+ omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor,
+ "dynamic delay delay test at 800ms");
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "dynamic delay delay test at 1800ms (1)");
+ gDiv.style.animationDelay = "1.5s";
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_out(0.3), 0.01, RunningOn.Compositor,
+ "dynamic delay delay test at 1800ms (2)");
+ gDiv.style.animationDelay = "2s";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Either,
+ "dynamic delay delay test at 1800ms (3)");
+ done_div();
+
+ // test delay and play-state interaction
+ new_div("animation: anim2 1s 0.5s ease-out");
+ await waitForPaintsFlushed();
+ // NOTE: As noted above, getOMTAStyle() can't detect the animation is running
+ // on the compositor during the delay phase.
+ omta_is("opacity", 1, RunningOn.Either,
+ "delay and play-state delay test at 0ms");
+ advance_clock(400);
+ omta_is("opacity", 1, RunningOn.Either,
+ "delay and play-state delay test at 400ms");
+ gDiv.style.animationPlayState = "paused";
+ await waitForPaintsFlushed();
+ advance_clock(100);
+ omta_is("opacity", 1, RunningOn.MainThread, // paused
+ "delay and play-state delay test at 500ms");
+ advance_clock(500);
+ omta_is("opacity", 1, RunningOn.MainThread, // paused
+ "delay and play-state delay test at 1000ms");
+ gDiv.style.animationPlayState = "running";
+ await waitForPaintsFlushed();
+ advance_clock(100);
+ await waitForPaints();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "delay and play-state delay test at 1100ms");
+ advance_clock(100);
+ omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Compositor,
+ "delay and play-state delay test at 1200ms");
+ gDiv.style.animationPlayState = "paused";
+ await waitForPaintsFlushed();
+ advance_clock(100);
+ omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Either,
+ "delay and play-state delay test at 1300ms");
+ done_div();
+
+ // test negative delay and implicit starting values
+ new_div("transform: translate(1000px)");
+ await waitForPaintsFlushed();
+ advance_clock(300);
+ gDiv.style.transform = "translate(100px)";
+ gDiv.style.animation = "kf1 1s -0.1s ease-in";
+ await waitForPaintsFlushed();
+ omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_in(0.2) },
+ 0.01, RunningOn.Compositor,
+ "delay and implicit starting values test");
+ done_div();
+
+ // test large negative delay that causes the animation to start
+ // in the fourth iteration
+ new_div("animation: anim2 1s -3.6s ease-in 5 alternate forwards");
+ listen();
+ await waitForPaintsFlushed();
+ omta_is_approx("opacity", gTF.ease_in(0.4), 0.01, RunningOn.Compositor,
+ "large negative delay test at 0ms");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim2', elapsedTime: 3.6,
+ pseudoElement: "" }],
+ "right after start in large negative delay test");
+ advance_clock(380);
+ omta_is_approx("opacity", gTF.ease_in(0.02), 0.01, RunningOn.Compositor,
+ "large negative delay test at 380ms");
+ check_events([]);
+ advance_clock(20);
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "large negative delay test at 400ms");
+ check_events([{ type: 'animationiteration', target: gDiv,
+ animationName: 'anim2', elapsedTime: 4.0,
+ pseudoElement: "" }],
+ "right after start in large negative delay test");
+ advance_clock(800);
+ omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor,
+ "large negative delay test at 1200ms");
+ check_events([]);
+ advance_clock(200);
+ omta_is("opacity", 1, RunningOn.Either,
+ "large negative delay test at 1400ms");
+ check_events([{ type: 'animationend', target: gDiv,
+ animationName: 'anim2', elapsedTime: 5.0,
+ pseudoElement: "" }],
+ "right after start in large negative delay test");
+ done_div();
+});
+
+/*
+ * css3-animations: 3.9. The 'animation-fill-mode' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-fill-mode-property-
+ */
+
+// animation-fill-mode is tested in the tests for section (2).
+
+/*
+ * css3-animations: 3.10. The 'animation' Shorthand Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-shorthand-property-
+ */
+
+/**
+ * Basic tests of animations on pseudo-elements
+ */
+addAsyncAnimTest(async function() {
+ new_div("");
+ listen();
+ gDiv.id = "withbefore";
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ ":before test at 0ms", "::before");
+ advance_clock(400);
+ omta_is("transform", { ty: 40 }, RunningOn.Compositor,
+ ":before test at 400ms", "::before");
+ advance_clock(800);
+ omta_is("transform", { ty: 80 }, RunningOn.Compositor,
+ ":before test at 1200ms", "::before");
+ omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+ ":before animation should not affect element");
+ advance_clock(800);
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ ":before test at 2000ms", "::before");
+ advance_clock(300);
+ omta_is("transform", { ty: 30 }, RunningOn.Compositor,
+ ":before test at 2300ms", "::before");
+ advance_clock(700);
+ check_events([ { type: "animationstart", animationName: "anim4",
+ elapsedTime: 0, pseudoElement: "::before" },
+ { type: "animationiteration", animationName: "anim4",
+ elapsedTime: 1, pseudoElement: "::before" },
+ { type: "animationiteration", animationName: "anim4",
+ elapsedTime: 2, pseudoElement: "::before" },
+ { type: "animationend", animationName: "anim4",
+ elapsedTime: 3, pseudoElement: "::before" }]);
+ done_div();
+
+ new_div("");
+ listen();
+ gDiv.id = "withafter";
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ ":after test at 0ms", "::after");
+ advance_clock(400);
+ omta_is("transform", { ty: 40 }, RunningOn.Compositor,
+ ":after test at 400ms", "::after");
+ advance_clock(800);
+ omta_is("transform", { ty: 80 }, RunningOn.Compositor,
+ ":after test at 1200ms", "::after");
+ omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+ ":before animation should not affect element");
+ advance_clock(800);
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ ":after test at 2000ms", "::after");
+ advance_clock(300);
+ omta_is("transform", { ty: 30 }, RunningOn.Compositor,
+ ":after test at 2300ms", "::after");
+ advance_clock(700);
+ check_events([ { type: "animationstart", animationName: "anim4",
+ elapsedTime: 0, pseudoElement: "::after" },
+ { type: "animationiteration", animationName: "anim4",
+ elapsedTime: 1, pseudoElement: "::after" },
+ { type: "animationiteration", animationName: "anim4",
+ elapsedTime: 2, pseudoElement: "::after" },
+ { type: "animationend", animationName: "anim4",
+ elapsedTime: 3, pseudoElement: "::after" }]);
+ done_div();
+});
+
+/**
+ * Test handling of properties that are present in only some of the
+ * keyframes.
+ */
+addAsyncAnimTest(async function() {
+ new_div("animation: multiprop 1s ease-in-out alternate infinite");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 10 }, RunningOn.Compositor,
+ "multiprop transform at 0ms");
+ omta_is("opacity", 0.3, RunningOn.Compositor, "multiprop opacity at 0ms");
+ advance_clock(100);
+ omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 100ms");
+ omta_is_approx("opacity", 0.3 + 0.2 * gTF.ease(0.4), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 100ms");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.6) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 300ms");
+ omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.1), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 300ms");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 40 + 40 * gTF.ease_in_out(0.4) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 600ms");
+ omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.7), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 600ms");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 80 - 80 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 800ms");
+ omta_is_approx("opacity", 0.6 + 0.4 * gTF.ease_in(0.2), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 800ms");
+ advance_clock(400);
+ omta_is_approx("transform", { tx: 80 - 80 * gTF.ease_in(0.2) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 1200ms");
+ omta_is_approx("opacity", 0.6 + 0.4 * gTF.ease_in(0.2), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 1200ms");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 40 + 40 * gTF.ease_in_out(0.4) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 1400ms");
+ omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.7), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 1400ms");
+ advance_clock(300);
+ omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.6) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 1700ms");
+ omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.1), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 1700ms");
+ advance_clock(200);
+ omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.2) }, 0.01,
+ RunningOn.Compositor, "multiprop transform at 1900ms");
+ omta_is_approx("opacity", 0.3 + 0.2 * gTF.ease(0.4), 0.01,
+ RunningOn.Compositor, "multiprop opacity at 1900ms");
+ done_div();
+});
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=651456 -- make
+// sure that refreshing of animations doesn't break when we get two
+// refreshes with the same timestamp.
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 1s linear");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor, "bug 651456 at 0ms");
+ advance_clock(100);
+ omta_is("opacity", 0.1, RunningOn.Compositor, "bug 651456 at 100ms (1)");
+ advance_clock(0); // still forces a refresh
+ omta_is("opacity", 0.1, RunningOn.Compositor, "bug 651456 at 100ms (2)");
+ advance_clock(100);
+ omta_is("opacity", 0.2, RunningOn.Compositor, "bug 651456 at 200ms");
+ done_div();
+});
+
+// test_animations.html includes a test that UA !important rules override
+// animations. Unfortunately, there do not appear to be any UA !important rules
+// for opacity or transform except for one targetting a pseudo-element and
+// pseudo elements are not animated on the compositor. As a result we cannot
+// currently test this behavior.
+
+// Test that author !important rules override animations, but
+// that animations override regular author rules.
+addAsyncAnimTest(async function() {
+ new_div("animation: always_fifty 1s linear infinite; " +
+ "transform: translate(200px)");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "animations override regular author rules");
+ done_div();
+ new_div("animation: always_fifty 1s linear infinite; " +
+ "transform: translate(200px) ! important;");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 200 }, RunningOn.MainThread,
+ "important author rules override animations");
+ done_div();
+});
+
+// Test interaction of animations and restyling (Bug 686656).
+// This test depends on kf3 getting its 0% and 100% values from the
+// rules below it in the cascade; we're checking that the animation
+// isn't rebuilt when the restyles happen.
+addAsyncAnimTest(async function() {
+ new_div("animation: kf3 1s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "bug 686656 test 1 at 0ms");
+ advance_clock(250);
+ gDisplay.style.color = "blue";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "bug 686656 test 1 at 250ms");
+ advance_clock(375);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "bug 686656 test 1 at 625ms");
+ advance_clock(375);
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "bug 686656 test 1 at 1000ms");
+ done_div();
+ gDisplay.style.color = "";
+});
+
+// Test interaction of animations and restyling (Bug 686656),
+// with reframing.
+// This test depends on kf3 getting its 0% and 100% values from the
+// rules below it in the cascade; we're checking that the animation
+// isn't rebuilt when the restyles happen.
+addAsyncAnimTest(async function() {
+ new_div("animation: kf3 1s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "bug 686656 test 2 at 0ms");
+ advance_clock(250);
+ gDisplay.style.overflow = "scroll";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "bug 686656 test 2 at 250ms");
+ advance_clock(375);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "bug 686656 test 2 at 625ms");
+ advance_clock(375);
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "bug 686656 test 2 at 1000ms");
+ done_div();
+ gDisplay.style.overflow = "";
+});
+
+// Test that cascading between keyframes rules is per-property rather
+// than per-rule (bug ), and that the timing function isn't taken from a
+// rule that's skipped. (Bug 738003)
+addAsyncAnimTest(async function() {
+ new_div("animation: cascade 1s linear forwards; position: relative");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "cascade test (transform) at 0ms");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "cascade test (opacity) at 0ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "cascade test (transform) at 125ms");
+ omta_is("opacity", 0.5, RunningOn.Compositor,
+ "cascade test (opacity) at 125ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "cascade test (transform) at 250ms");
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "cascade test (opacity) at 250ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "cascade test (transform) at 375ms");
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "cascade test (opacity) at 375ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "cascade test (transform) at 500ms");
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "cascade test (opacity) at 500ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "cascade test (transform) at 625ms");
+ omta_is("opacity", 0.5, RunningOn.Compositor,
+ "cascade test (opacity) at 625ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "cascade test (transform) at 750ms");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "cascade test (opacity) at 750ms");
+ advance_clock(125);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "cascade test (transform) at 875ms");
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "cascade test (opacity) at 875ms");
+ advance_clock(125);
+ await waitForPaints();
+ omta_is("transform", { tx: 0 }, RunningOn.MainThread,
+ "cascade test (transform) at 1000ms");
+ omta_is("opacity", 0, RunningOn.Either,
+ "cascade test (opacity) at 1000ms");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("animation: cascade2 8s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor, "cascade2 test at 0s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor, "cascade2 test at 1s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor, "cascade2 test at 2s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor, "cascade2 test at 3s");
+ advance_clock(1000);
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor, "cascade2 test at 4s");
+ advance_clock(3000);
+ omta_is("transform", { tx: 75 }, RunningOn.Compositor, "cascade2 test at 7s");
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("transform", { tx: 100 }, RunningOn.MainThread,
+ "cascade2 test at 8s");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("animation: primitives1 2s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("transform", { }, RunningOn.Compositor, "primitives1 at 0s");
+ advance_clock(1000);
+ omta_is("transform", [ -0.707107, 0.707107, -0.707107, -0.707107, 0, 0 ],
+ RunningOn.Compositor, "primitives1 at 1s");
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("transform", [ 0, -1, 1, 0, 0, 0 ], RunningOn.MainThread,
+ "primitives1 at 0s");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("animation: important1 1s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.5, RunningOn.Compositor, "important1 test at 0s");
+ advance_clock(500);
+ omta_is("opacity", 0.65, RunningOn.Compositor, "important1 test at 0.5s");
+ advance_clock(500);
+ await waitForPaints();
+ omta_is("opacity", 0.8, RunningOn.Either, "important1 test at 1s");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("animation: important2 1s linear forwards");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.5, RunningOn.Compositor,
+ "important2 (opacity) test at 0s");
+ omta_is("transform", { tx: 100 }, RunningOn.Compositor,
+ "important2 (transform) test at 0s");
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("opacity", 1, RunningOn.Either,
+ "important2 (opacity) test at 1s");
+ omta_is("transform", { tx: 50 }, RunningOn.MainThread,
+ "important2 (transform) test at 1s");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ // Test that it's the length of the 'animation-name' list that's used to
+ // start animations.
+ // note: anim2 animates opacity from 0 to 1
+ // note: anim4 animates transform's y translation component from 0 to 100px
+ new_div("animation-name: anim2, anim4; " +
+ "animation-duration: 1s; " +
+ "animation-timing-function: linear; " +
+ "animation-delay: -250ms, -250ms, -750ms, -500ms;");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.25, RunningOn.Compositor,
+ "animation-name list length is the length that matters");
+ omta_is("transform", { ty: 25 }, RunningOn.Compositor,
+ "animation-name list length is the length that matters");
+ done_div();
+ new_div("animation-name: anim2, anim4, anim2; " +
+ "animation-duration: 1s; " +
+ "animation-timing-function: linear; " +
+ "animation-delay: -250ms, -250ms, -750ms, -500ms;");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "animation-name list length is the length that matters, " +
+ "and the last occurrence of a name wins");
+ omta_is("transform", { ty: 25 }, RunningOn.Compositor,
+ "animation-name list length is the length that matters");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var dyn_sheet_elt = document.createElement("style");
+ document.head.appendChild(dyn_sheet_elt);
+ var dyn_sheet = dyn_sheet_elt.sheet;
+ dyn_sheet.insertRule(
+ "@keyframes dyn1 { from { transform: translate(0px) } " +
+ "50% { transform: translate(50px) } " +
+ "to { transform: translate(100px) } }", 0);
+ dyn_sheet.insertRule(
+ "@keyframes dyn2 { from { transform: translate(100px) } " +
+ "to { transform: translate(200px) } }", 1);
+ var dyn1 = dyn_sheet.cssRules[0];
+ var dyn2 = dyn_sheet.cssRules[1];
+ new_div("animation: dyn1 1s linear");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "dynamic rule change test, initial state");
+ advance_clock(250);
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor,
+ "dynamic rule change test, 250ms");
+ dyn2.name = "dyn1";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 125 }, RunningOn.Compositor,
+ "dynamic rule change test, change in @keyframes name applies");
+ dyn2.appendRule("50% { transform: translate(0px) }");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "dynamic rule change test, @keyframes appendRule");
+ // currently 0% { transform: translate(100px) }
+ var dyn2_kf1 = dyn2.cssRules[0];
+ dyn2_kf1.style.transform = "translate(-100px)";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: -50 }, RunningOn.Compositor,
+ "dynamic rule change test, keyframe style set");
+ dyn2.name = "dyn2";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor,
+ "dynamic rule change test, " +
+ "change in @keyframes name applies (second time)");
+ // currently 50% { transform: translate(50px) }
+ var dyn1_kf2 = dyn1.cssRules[1];
+ dyn1_kf2.keyText = "25%";
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "dynamic rule change test, change in keyframe keyText");
+ dyn1.deleteRule("25%");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 25 }, RunningOn.Compositor,
+ "dynamic rule change test, @keyframes deleteRule");
+ done_div();
+ dyn_sheet_elt.remove();
+ dyn_sheet_elt = null;
+ dyn_sheet = null;
+});
+
+/*
+ * Bug 1004361 - CSS animations with short duration sometimes don't dispatch
+ * a start event
+ */
+addAsyncAnimTest(async function() {
+ new_div("animation: anim2 1s 0.1s");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(1200); // Skip past end of animation's entire active duration
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim2', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim2', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events after skipping over animation interval");
+ done_div();
+});
+
+/*
+ * Bug 1007513 - AnimationEvent.elapsedTime should be animation time
+ *
+ * There is no OMTA-version of this test since it is specific to the
+ * contents of animation events which are dispatched on the main thread.
+ *
+ * We *do* provide an OMTA-version of some tests regarding the *dispatch* of
+ * events to catch possible regressions if in future event dispatch is tied
+ * to animation throttling.
+ */
+
+/*
+ * Bug 1004365 - zero-duration animations
+ */
+
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(0, 200px); animation: anim4 0s 1s both");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(0);
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform during backwards fill of zero-duration animation");
+ advance_clock(2000); // Skip over animation
+ await waitForPaints();
+ omta_is("transform", { ty: 100 }, RunningOn.MainThread,
+ "transform during backwards fill of zero-duration animation");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration animation");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(0, 200px); animation: anim4 0s 1s both");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(0);
+ // Seek to exactly the point where the animation starts and stops
+ advance_clock(1000);
+ await waitForPaints();
+ omta_is("transform", { ty: 100 }, RunningOn.MainThread,
+ "transform during backwards fill of zero-duration animation");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of zero-duration animation");
+ // Check no further events are dispatched
+ advance_clock(0);
+ advance_clock(100);
+ check_events([]);
+ done_div();
+});
+
+// We don't need to include all the animation-direction related tests
+// found in test_animations.html. We have already asserted above that
+// these zero-length animations do in fact run on the main thread and
+// we have checked that they dispatch events correctly.
+// The actual calculation of values on the main thread is covered by
+// test_animations.html
+
+// We do however still want to test with an infinite repeat count and zero
+// duration to ensure this does not confuse the screening of OMTA animations.
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(0, 200px); " +
+ "animation: anim4 0s 1s both infinite");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(0);
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform during backwards fill of infinitely repeating " +
+ "zero-duration animation");
+ advance_clock(2000);
+ await waitForPaints();
+ omta_is("transform", { ty: 100 }, RunningOn.MainThread,
+ "transform during forwards fill of infinitely repeating " +
+ "zero-duration animation");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after seeking to end of infinitely repeating " +
+ "zero-duration animation");
+ done_div();
+});
+
+// Test with negative delay
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(0, 200px); " +
+ "animation: anim4 0s -1s both reverse 12.7 linear");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(0);
+ omta_is("transform", { ty: 30 }, RunningOn.MainThread,
+ "transform during forwards fill of reversed and repeated " +
+ "zero-duration animation with negative delay");
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'anim4', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events after skipping over zero-duration animation " +
+ "with negative delay");
+ done_div();
+});
+
+/*
+ * Bug 1004377 - Animations with empty keyframes rule
+ */
+
+addAsyncAnimTest(async function() {
+ new_div("margin-right: 200px; animation: empty 2s 1s both");
+ listen();
+ advance_clock(0);
+ await waitForPaintsFlushed();
+ check_events([], "events during delay");
+ advance_clock(2000); // Skip to middle of animation
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events during middle of animation with empty keyframes rule");
+ advance_clock(1000); // Skip to end of animation
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationend', target: gDiv,
+ animationName: 'empty', elapsedTime: 2,
+ pseudoElement: "" }],
+ "events at end of animation with empty keyframes rule");
+ done_div();
+});
+
+// Test with a zero-duration animation and empty @keyframes rule
+addAsyncAnimTest(async function() {
+ new_div("margin-right: 200px; animation: empty 0s 1s both");
+ listen();
+ await waitForPaintsFlushed();
+ advance_clock(1000);
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" },
+ { type: 'animationend', target: gDiv,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events at end of zero-duration animation with " +
+ "empty keyframes rule");
+ done_div();
+});
+
+// Test with a keyframes rule that becomes empty
+addAsyncAnimTest(async function() {
+ new_div("animation: nearlyempty 1s both linear");
+ await waitForPaintsFlushed();
+ advance_clock(500);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is animating on compositor");
+
+ // Update keyframes rule and check the result gets removed
+ listen();
+ findKeyframesRule("nearlyempty").deleteRule("to");
+ await waitForPaintsFlushed();
+ omta_is("transform", { }, RunningOn.MainThread,
+ "Animation with (now) empty keyframes rule is cleared " +
+ "from compositor");
+
+ // Check we still dispatch the end event however
+ advance_clock(500);
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationend', target: gDiv,
+ animationName: 'nearlyempty', elapsedTime: 1,
+ pseudoElement: "" }],
+ "events at end of animation with newly " +
+ "empty keyframes rule");
+
+ done_div();
+});
+
+// Test when we update to point to an empty animation
+addAsyncAnimTest(async function() {
+ new_div("animation: always_fifty 1s both linear");
+ await waitForPaintsFlushed();
+ advance_clock(500);
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "Animation is animating on compositor");
+
+ // Update animation name
+ listen();
+ gDiv.style.animationName = "empty";
+ await waitForPaintsFlushed();
+ omta_is("transform", { }, RunningOn.MainThread,
+ "Animation updated to use empty keyframes rule is cleared " +
+ "from compositor");
+
+ // Check events
+ advance_clock(500);
+ gDiv.clientTop; // Trigger events
+ check_events([{ type: 'animationstart', target: gDiv,
+ animationName: 'empty', elapsedTime: 0,
+ pseudoElement: "" }],
+ "events at start of animation updated to use " +
+ "empty keyframes rule");
+
+ done_div();
+});
+
+// Bug 996796 patch 12 - test for correct visited styles during
+// animation-only style flush.
+addAsyncAnimTest(async function() {
+ if (AppConstants.platform === "android") {
+ todo(false, "no global history on GeckoView; can't run test");
+ return;
+ }
+
+ var div1 = document.createElement("div");
+ div1.classList.add("target");
+ div1.style.height = "10px";
+ div1.style.animation = "anim2 linear 1s";
+
+ const topLocation =
+ await SpecialPowers.spawnChrome([], () => browsingContext.top.currentURI.spec);
+
+ var visitedLink = document.createElement("a");
+ visitedLink.setAttribute("href", topLocation);
+ visitedLink.classList.add("visitedLink");
+ visitedLink.classList.add("target");
+ visitedLink.style.display = "block";
+ visitedLink.style.height = "10px";
+ visitedLink.style.animation = "anim2 linear 1s";
+
+ var refVisitedLink = document.createElement("a");
+ refVisitedLink.setAttribute("href", topLocation);
+ refVisitedLink.classList.add("visitedLink");
+
+ gDisplay.appendChild(div1);
+ gDisplay.appendChild(visitedLink);
+ gDisplay.appendChild(refVisitedLink);
+
+ // Wait for visited link coloring.
+ await waitForVisitedLinkColoring(refVisitedLink,
+ "background-color", "rgb(0, 0, 255)");
+
+ // Wait for animations to start.
+ await waitForPaintsFlushed();
+
+ var bgColor = SpecialPowers.DOMWindowUtils
+ .getVisitedDependentComputedStyle(visitedLink, "", "background-color");
+ is(bgColor, "rgb(0, 0, 255)", "initial visited link background color");
+
+ advance_clock(250);
+
+ // Trigger a style change on div1 that will force us to do a miniflush,
+ // but which will not trigger a style change on visitedLink.
+ div1.style.color = "blue";
+ advance_clock(250);
+
+ bgColor = SpecialPowers.DOMWindowUtils
+ .getVisitedDependentComputedStyle(visitedLink, "", "background-color");
+
+ is(bgColor, "rgb(0, 0, 255)",
+ "visited link background color after animation-only flush");
+
+ div1.remove();
+ visitedLink.remove();
+ refVisitedLink.remove();
+});
+
+/*
+ * Bug 962594 - Turn off CSS animations when the element is display:none, or
+ * is in a display:none subtree.
+ */
+
+// Check that it works if the animated element itself becomes display:none
+addAsyncAnimTest(async function() {
+ new_div("animation: anim4 linear 10s");
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform animation is running on compositor");
+ advance_clock(1000);
+ omta_is("transform", { ty: 10 }, RunningOn.Compositor,
+ "transform animation is at 1s on compositor");
+ gDiv.style.display = "none";
+ await waitForPaintsFlushed();
+ omta_is("transform", "none", RunningOn.MainThread,
+ "transform animation stopped on compositor");
+ advance_clock(1000);
+ omta_is("transform", "none", RunningOn.MainThread,
+ "transform animation 1s after display:none");
+ gDiv.style.display = "";
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform animation after display:block");
+ advance_clock(1000);
+ omta_is("transform", { ty: 10 }, RunningOn.Compositor,
+ "transform animation 1s after display:block");
+ done_div();
+});
+
+// Check that it works if an ancestor of the animated element becomes display:none
+addAsyncAnimTest(async function() {
+ new_div("animation: anim4 linear 10s");
+ var ancestor = document.createElement("div");
+ gDiv.parentNode.insertBefore(ancestor, gDiv);
+ ancestor.appendChild(gDiv);
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform animation is running on compositor");
+ advance_clock(1000);
+ omta_is("transform", { ty: 10 }, RunningOn.Compositor,
+ "transform animation is at 1s on compositor");
+ gDiv.style.display = "none";
+ await waitForPaintsFlushed();
+ omta_is("transform", "none", RunningOn.MainThread,
+ "transform animation stopped on compositor");
+ advance_clock(1000);
+ omta_is("transform", "none", RunningOn.MainThread,
+ "transform animation 1s after display:none");
+ gDiv.style.display = "";
+ await waitForPaintsFlushed();
+ omta_is("transform", { ty: 0 }, RunningOn.Compositor,
+ "transform animation after display:block");
+ advance_clock(1000);
+ omta_is("transform", { ty: 10 }, RunningOn.Compositor,
+ "transform animation 1s after display:block");
+ ancestor.parentNode.insertBefore(gDiv, ancestor);
+ ancestor.remove();
+ done_div();
+});
+
+// Bug 1125455 - Transitions should not run when animations are running.
+addAsyncAnimTest(async function() {
+ new_div("transition: opacity 2s linear; opacity: 0.8");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.8, RunningOn.MainThread,
+ "initial opacity");
+ gDiv.style.opacity = "0.2";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.8, RunningOn.Compositor,
+ "opacity transition at 0s");
+ advance_clock(500);
+ omta_is("opacity", 0.65, RunningOn.Compositor,
+ "opacity transition at 0.5s");
+ gDiv.style.animation = "opacitymid 2s linear";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.2, RunningOn.Compositor,
+ "opacity animation overriding transition at 0s");
+ advance_clock(500);
+ omta_is("opacity", 0.35, RunningOn.Compositor,
+ "opacity animation overriding transition at 0.5s");
+ done_div();
+});
+
+// Bug 1320474 - keyframes-name may be a string, allows names that would
+// otherwise be excluded.
+// These tests don't need to be duplicated here as they relate purely to
+// the animation setup which is common to both main-thread and compositor
+// animations.
+
+// Bug 847287 - Test that changes of when an animation is dynamically
+// overridden work correctly.
+addAsyncAnimTest(async function() {
+ // anim2 and anim3 are both animations from opacity 0 to 1
+
+ new_div("animation: anim2 1s linear forwards; opacity: 0.5 ! important");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation at start (0s)");
+ advance_clock(750);
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation while running (750ms)");
+ advance_clock(1000);
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation while filling (1750ms)");
+ done_div();
+
+ new_div("animation: anim2 1s linear; opacity: 0.5 ! important");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation at start (0s)");
+ advance_clock(750);
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation while running (750ms)");
+ advance_clock(1000);
+ omta_is("opacity", 0.5, RunningOn.MainThread,
+ "opacity overriding animation after complete (1750ms)");
+ done_div();
+
+ // One animation overriding another, and then not.
+ new_div("animation: anim2 1s linear, anim3 500ms linear reverse");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 1, RunningOn.Compositor,
+ "anim3 overriding anim2 at start (0s)");
+ advance_clock(400);
+ omta_is("opacity", 0.2, RunningOn.Compositor,
+ "anim3 overriding anim2 at 400ms");
+ advance_clock(200);
+ // Wait for paints because we're resending animations to the
+ // compositor via an UpdateOpacityLayer hint, which does the resending
+ // via painting.
+ await waitForPaints();
+ omta_is("opacity", 0.6, RunningOn.Compositor,
+ "anim2 at 600ms");
+ done_div();
+
+ // One animation overriding another, and then not, but without a
+ // restyle when the overriding one ends.
+ new_div("animation: anim2 1s steps(8, end)");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim2 at start (0s)");
+ advance_clock(300);
+ omta_is("opacity", 0.25, RunningOn.Compositor,
+ "anim2 at 300ms");
+ gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim3 overriding anim2 at 300ms");
+ advance_clock(475);
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "anim3 the same as anim2 at 775ms");
+ advance_clock(50);
+ // Wait for paints because we're resending animations to the
+ // compositor via an UpdateOpacityLayer hint, which does the resending
+ // via painting.
+ await waitForPaints();
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "anim2 at 825ms");
+ advance_clock(75);
+ omta_is("opacity", 0.875, RunningOn.Compositor,
+ "anim2 at 900ms");
+ done_div();
+
+ // Exactly the same as the previous test, except with an extra
+ // waitForPaintsFlushed(), since that extra one exposes other bugs.
+ new_div("animation: anim2 1s steps(8, end)");
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim2 at start (0s)");
+ advance_clock(300);
+ omta_is("opacity", 0.25, RunningOn.Compositor,
+ "anim2 at 300ms");
+ gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)";
+ await waitForPaintsFlushed();
+ omta_is("opacity", 0, RunningOn.Compositor,
+ "anim3 overriding anim2 at 300ms");
+ advance_clock(475);
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "anim3 the same as anim2 at 775ms");
+ // Extra waitForPaintsFlushed to expose bugs.
+ await waitForPaintsFlushed();
+ advance_clock(50);
+ // Wait for paints because we're resending animations to the
+ // compositor via an UpdateOpacityLayer hint, which does the resending
+ // via painting.
+ await waitForPaints();
+ omta_is("opacity", 0.75, RunningOn.Compositor,
+ "anim2 at 825ms");
+ advance_clock(75);
+ omta_is("opacity", 0.875, RunningOn.Compositor,
+ "anim2 at 900ms");
+ done_div();
+
+ // Test that an interpolation that produces transform: none doesn't
+ // crash.
+ new_div("animation: transformnone 1s linear");
+ await waitForPaintsFlushed();
+ omta_is("transform", { tx: 50 }, RunningOn.Compositor,
+ "transformnone animation at 0ms");
+ advance_clock(500);
+ omta_is("transform", { tx: 0 }, RunningOn.Compositor,
+ "transformnone animation at 500ms, interpolating none values");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ new_div("transform: translate(100px); transition: transform 10s 5s linear");
+ await waitForPaintsFlushed();
+ gDiv.style.transform = "translate(200px)";
+ await waitForPaintsFlushed();
+ // NOTE: As noted above, getOMTAStyle() can't detect the animation is running
+ // on the compositor during the delay phase.
+ omta_is("transform", { tx: 100 }, RunningOn.Either,
+ "transition runs on compositor thread during delay");
+ // At the *very* start of the transition the start value of the transition
+ // will match the underlying transform value. Various optimizations in
+ // RestyleManager may recognize this a "no change" and filter out the
+ // transition meaning that the animation doesn't get added to the compositor
+ // thread until the first time the value changes. As a result, we fast-forward
+ // a little past the beginning and then wait for the animation to be sent
+ // to the compositor.
+ advance_clock(5100);
+ await waitForPaints();
+ omta_is("transform", { tx: 101 }, RunningOn.Compositor,
+ "transition runs on compositor at start of active interval");
+ advance_clock(4900);
+ omta_is("transform", { tx: 150 }, RunningOn.Compositor,
+ "transition runs on compositor at during active interval");
+ advance_clock(5000);
+ // Currently the compositor will apply a forwards fill until it gets told by
+ // the main thread to clear the animation. As a result we should wait for
+ // paints before checking that the animated value does *not* appear on the
+ // compositor thread.
+ await waitForPaints();
+ omta_is("transform", { tx: 200 }, RunningOn.MainThread,
+ "transition runs on main thread at end of active interval");
+
+ done_div();
+});
+
+// Normal background-color animation.
+addAsyncAnimTest(async function() {
+ new_div("background-color: rgb(255, 0, 0); " +
+ "transition: background-color 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.backgroundColor = "rgb(0, 255, 0)";
+ await waitForPaintsFlushed();
+
+ omta_is("background-color", "rgb(255, 0, 0)", RunningOn.Compositor,
+ "background-color transition runs on compositor thread");
+
+ advance_clock(5000);
+ omta_is("background-color", "rgb(128, 128, 0)", RunningOn.Compositor,
+ "background-color on compositor at 5s");
+
+ done_div();
+});
+
+// background-color animation with currentColor.
+addAsyncAnimTest(async function() {
+ new_div("color: rgb(255, 0, 0); " +
+ "background-color: currentColor; " +
+ "transition: background-color 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.backgroundColor = "rgb(0, 255, 0)";
+ await waitForPaintsFlushed();
+
+ omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor,
+ "background-color transition starting with current-color runs on " +
+ "compositor thread");
+
+ advance_clock(5000);
+ omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor,
+ "background-color on compositor at 5s");
+
+ done_div();
+});
+
+// Tests that a background-color animation from inherited currentColor to
+// a normal color on the compositor is updated when the parent color is
+// changed.
+addAsyncAnimTest(async function() {
+ new_div("");
+ const parent = document.createElement("div");
+ gDiv.parentNode.insertBefore(parent, gDiv);
+ parent.style.color = "rgb(255, 0, 0)";
+ parent.appendChild(gDiv);
+
+ gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000);
+
+ await waitForPaintsFlushed();
+
+ omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor,
+ "background-color animation starting with current-color runs on " +
+ "compositor thread");
+
+ advance_clock(500);
+
+ omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor,
+ "background-color on compositor at 5s");
+
+ // Change the parent's color in the middle of the animation.
+ parent.style.color = "rgb(0, 0, 255)";
+ await waitForPaintsFlushed();
+
+ omta_todo_is("background-color", "rgb(0, 128, 128)", RunningOn.TodoCompositor,
+ "background-color on compositor is reflected by the parent's " +
+ "color change");
+
+ done_div();
+ parent.remove();
+});
+
+// Tests that a background-color animation from currentColor to a normal color
+// on <a> element is updated when the link is visited.
+addAsyncAnimTest(async function() {
+ if (AppConstants.platform === "android") {
+ todo(false, "no global history on GeckoView; can't run test");
+ return;
+ }
+
+ [ gDiv ] = new_element("a", "display: block");
+ gDiv.setAttribute("href", "not-exist.html");
+ gDiv.classList.add("visited");
+
+ const extraStyle = document.createElement('style');
+ document.head.appendChild(extraStyle);
+ extraStyle.sheet.insertRule(".visited:visited { color: rgb(0, 0, 255); }", 0);
+ extraStyle.sheet.insertRule(".visited:link { color: rgb(255, 0, 0); }", 1);
+
+ gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000);
+ await waitForPaintsFlushed();
+
+ omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor,
+ "background-color animation starting with current-color runs on " +
+ "compositor thread");
+
+ advance_clock(500);
+
+ omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor,
+ "background-color on compositor at 5s");
+
+ const topLocation =
+ await SpecialPowers.spawnChrome([], () => browsingContext.top.currentURI.spec);
+ gDiv.setAttribute("href", topLocation);
+ await waitForVisitedLinkColoring(gDiv, "color", "rgb(0, 0, 255)");
+ await waitForPaintsFlushed();
+
+ // `omta_is` checks that the result on the compositor equals to the value by
+ // getComputedValue() but getComputedValue lies for visited link values so
+ // we use getOMTAStyle directly instead.
+ todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "background-color"),
+ "rgb(0, 128, 128)",
+ "background-color on <a> element after the link is visited");
+
+ extraStyle.remove();
+ done_element();
+ gDiv = null;
+});
+
+// Normal translate animation.
+addAsyncAnimTest(async function() {
+ new_div("translate: 100px; " +
+ "transition: translate 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.translate = "200px";
+ await waitForPaintsFlushed();
+
+ omta_is("translate", { compositorValue: { tx: 100 }, computed: "100px" },
+ RunningOn.Compositor,
+ "translate transition runs on compositor thread");
+
+ advance_clock(5000);
+ omta_is("translate", { compositorValue: { tx: 150 }, computed: "150px" },
+ RunningOn.Compositor, "translate on compositor at 5s");
+
+ done_div();
+});
+
+// Normal rotate animation.
+addAsyncAnimTest(async function() {
+ new_div("rotate: 0deg; " +
+ "transition: rotate 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.rotate = "90deg";
+ await waitForPaintsFlushed();
+
+ omta_is("rotate", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "0deg"},
+ RunningOn.Compositor, "rotate transition runs on compositor thread");
+
+ advance_clock(5000);
+ omta_is("rotate",
+ { compositorValue: [ Math.cos(Math.PI / 4), Math.sin(Math.PI / 4),
+ -Math.sin(Math.PI / 4), Math.cos(Math.PI / 4),
+ 0, 0 ],
+ computed: "45deg" }, RunningOn.Compositor,
+ "rotate on compositor at 5s");
+
+ done_div();
+});
+
+// Normal scale animation.
+addAsyncAnimTest(async function() {
+ new_div("scale: 1 1; " +
+ "transition: scale 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.scale = "2 2";
+ await waitForPaintsFlushed();
+
+ omta_is("scale", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "1" },
+ RunningOn.Compositor, "scale transition runs on compositor thread");
+
+ advance_clock(5000);
+ omta_is("scale",
+ { compositorValue: [ 1.5, 0, 0, 1.5, 0, 0 ], computed: "1.5" },
+ RunningOn.Compositor, "scale on compositor at 5s");
+
+ done_div();
+});
+
+// Normal multiple transform-like properties animation.
+addAsyncAnimTest(async function() {
+ new_div("translate: 100px; " +
+ "scale: 1 1; " +
+ "transform: translate(200px); " +
+ "transition: all 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.translate = "200px";
+ gDiv.style.scale = "2 2";
+ gDiv.style.transform = "translate(100px)";
+ await waitForPaintsFlushed();
+
+ omta_is("transform", { compositorValue: { tx: 300 },
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("transform",
+ // The order is: translate, scale, transform.
+ // So the translate() in transform should be multiplied by 1.5.
+ { compositorValue: [ 1.5, 0, 0, 1.5, (150 + 150*1.5), 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties on compositor at 5s");
+
+ done_div();
+});
+
+// Multiple transform-like properties animation. The non-animating properties
+// shouldn't be overridden by animating ones.
+addAsyncAnimTest(async function() {
+ new_div("translate: 100px; " +
+ "scale: 1 1; " +
+ "transform: translate(200px); " +
+ "transition: all 10s linear");
+ await waitForPaintsFlushed();
+
+ // No transition on transform property.
+ gDiv.style.translate = "200px";
+ gDiv.style.scale = "2 2";
+ await waitForPaintsFlushed();
+
+ omta_is("transform", { compositorValue: { tx: 300 },
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("transform",
+ // The order is: translate, scale, transform.
+ // So the translate() in transform should be multiplied by 1.5.
+ { compositorValue: [ 1.5, 0, 0, 1.5, (150 + 200*1.5), 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties on compositor at 5s");
+
+ done_div();
+});
+
+// Multiple transform-like properties animation with delay. The delayed
+// animating properties shouldn't be overridden.
+//
+// Note:
+// In delay phase, the SampleResult should be None, even though there is
+// an non-animating property which is also sent to the compositor.
+// If the SampleResult is Sampled in this case, we may get an incorrect result
+// ("scale" would be "1" because the final matrix is calculated by a default
+// scale value which overrides the scale css style).
+// That's why we shouldn't take non-animating properties into account on
+// SampleResult.
+addAsyncAnimTest(async function() {
+ new_div("translate: 100px; " +
+ "scale: 1.25 1.25; " +
+ "animation: kf_scale 10s 2.5s linear");
+ await waitForPaintsFlushed();
+
+ // All transform-like properties use DisplayItemType::TYPE_TRANSFORM,
+ // so using "transform" is enough.
+ var compositorStr =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
+ ok(compositorStr === "",
+ "transform-like properties should not run on the compositor at 0s " +
+ "(in delay phase)");
+ var computedStr = window.getComputedStyle(gDiv).translate;
+ ok(computedStr === "100px",
+ "The computed value of translate property should be equal to 100px " +
+ "in delay phase, got " + computedStr);
+ computedStr = window.getComputedStyle(gDiv).scale;
+ ok(computedStr === "1.25",
+ "The computed value of scale property should be equal to 1.25 " +
+ "in delay phase, got " + computedStr);
+
+ advance_clock(2500);
+
+ omta_is("transform", { compositorValue: [ 1.25, 0, 0, 1.25, 100, 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties on compositor at 2.5s");
+
+ advance_clock(5000);
+
+ omta_is("transform",
+ { compositorValue: [ 1.75, 0, 0, 1.75, 100, 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties on compositor at 7.5s");
+
+ done_div();
+});
+
+// Normal offset-path animation with path().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: path('M50 50L100 100'); " +
+ "offset-distance: 50%; " +
+ "offset-rotate: 0deg; " +
+ "transition: offset-path 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetPath = "path('M50 50L200 200')";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-path",
+ { compositorValue: { tx: 25, ty: 25 },
+ computed: 'path("M 50 50 L 100 100")' },
+ RunningOn.Compositor,
+ "offset-path transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-path",
+ { compositorValue: { tx: 50, ty: 50 },
+ computed: 'path("M 50 50 L 150 150")' },
+ RunningOn.Compositor,
+ "offset-path on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-path animation with ray().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: ray(90deg); " +
+ "offset-distance: 0%; " +
+ "offset-position: auto; " +
+ "transition: offset-path 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetPath = "ray(180deg)";
+ await waitForPaintsFlushed();
+
+ // At 0%, it's ray(90deg), so there is no rotation and only movement because
+ // the default offset-anchor is 50%.
+ omta_is("offset-path",
+ { compositorValue: { tx: -50, ty: -50 },
+ computed: 'ray(90deg)' },
+ RunningOn.Compositor,
+ "offset-path transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ // At 50%, ray() is 135deg. so the matrix is kind of rotate -45 deg:
+ // [cos(-1/4 * pi), -sin(-1/4 * pi), sin(-1/4 * pi), cos(-1/4 * pi), -50, -50]
+ // Note: the movement of (-50 -50) is from the default offset-anchor.
+ omta_is("offset-path",
+ {
+ compositorValue: [
+ Math.cos(-Math.PI * 1/4), -Math.sin(-Math.PI * 1/4),
+ Math.sin(-Math.PI * 1/4), Math.cos(-Math.PI * 1/4),
+ -50, -50
+ ],
+ computed: 'ray(135deg)'
+ },
+ RunningOn.Compositor,
+ "offset-path on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-path animation with polygon().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: polygon(0px 0px, 100px 0px, 50px 100px); " +
+ "offset-distance: 0%; " +
+ "offset-rotate: 0deg; " +
+ "offset-anchor: left top; " +
+ "transition: offset-path 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetPath = "polygon(50px 0px, 100px 0px, 50px 100px)";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-path",
+ { compositorValue: { tx: 0 },
+ computed: 'polygon(0px 0px, 100px 0px, 50px 100px)' },
+ RunningOn.Compositor,
+ "offset-path transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-path",
+ { compositorValue: { tx: 25 },
+ computed: 'polygon(25px 0px, 100px 0px, 50px 100px)' },
+ RunningOn.Compositor,
+ "offset-path on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-distance animation with path().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: path('M50 50v100'); " +
+ "offset-distance: 0%; " +
+ "offset-rotate: 0deg; " +
+ "transition: offset-distance 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetDistance = "100%";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-distance",
+ { compositorValue: { ty: 0 }, computed: '0%' },
+ RunningOn.Compositor,
+ "offset-distance transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-distance",
+ { compositorValue: { ty: 50 }, computed: '50%' },
+ RunningOn.Compositor,
+ "offset-distance on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-distance animation with polygon().
+addAsyncAnimTest(async function() {
+ new_div("offset-path: polygon(0px 0px, 100px 0px, 100px 50px, 0px 50px); " +
+ "offset-distance: 0%; " +
+ "offset-rotate: 0deg; " +
+ "offset-anchor: left top; " +
+ "transition: offset-distance 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetDistance = "100%";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-distance",
+ { compositorValue: { tx: 0 }, computed: '0%' },
+ RunningOn.Compositor,
+ "offset-distance transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-distance",
+ { compositorValue: { tx: 100, ty: 50 }, computed: '50%' },
+ RunningOn.Compositor,
+ "offset-distance on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-rotate animation.
+addAsyncAnimTest(async function() {
+ new_div("offset-path: path('M50 50v100'); " +
+ "offset-rotate: auto; " +
+ "transition: offset-rotate 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetRotate = "auto 90deg";
+ await waitForPaintsFlushed();
+
+ // The direction vector is 90deg (because the path go from top to bottom), and
+ // offset-rotate is auto 0deg, so the entire rotation is 90deg.
+ // The matrix is [cos(pi/2), sin(pi/2), -sin(pi/2), cos(pi/2), 0, 0].
+ omta_is("offset-rotate",
+ { compositorValue: [ 0, 1, -1, 0, 0, 0 ], computed: 'auto' },
+ RunningOn.Compositor,
+ "offset-rotate transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ // At 50%, offset-rotate is auto 45deg, so the entire rotation is 135deg
+ // (= 90deg + 45deg = pi * 3/4), so the matrix is
+ // [cos(pi * 3/4), sin(pi * 3/4), -sin(pi * 3/4), cos(pi * 3/4), 0, 0]
+ omta_is("offset-rotate",
+ {
+ compositorValue: [
+ Math.cos(Math.PI * 3/4), Math.sin(Math.PI * 3/4),
+ -Math.sin(Math.PI * 3/4), Math.cos(Math.PI * 3/4),
+ 0, 0
+ ],
+ computed: 'auto 45deg',
+ },
+ RunningOn.Compositor,
+ "offset-rotate on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-anchor animation.
+addAsyncAnimTest(async function() {
+ new_div("offset-path: path('M50 50v100'); " +
+ "offset-rotate: 0deg; " +
+ "offset-anchor: 0% 0%; " +
+ "transition: offset-anchor 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetAnchor = "100% 100%";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-anchor",
+ { compositorValue: { tx: 50, ty: 50 }, computed: '0% 0%' },
+ RunningOn.Compositor,
+ "offset-anchor transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-anchor",
+ { compositorValue: { tx: 0, ty: 0 }, computed: '50% 50%' },
+ RunningOn.Compositor,
+ "offset-anchor on compositor at 5s");
+
+ done_div();
+});
+
+// Normal offset-position animation.
+addAsyncAnimTest(async function() {
+ new_div("offset-path: ray(0deg sides); " +
+ "offset-rotate: 0deg; " +
+ "offset-anchor: 0% 0%; " +
+ "offset-position: 0px 0px; " +
+ "transition: offset-position 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.offsetPosition = "100px 100px";
+ await waitForPaintsFlushed();
+
+ omta_is("offset-position",
+ { compositorValue: { tx: 0, ty: 0 }, computed: '0px 0px' },
+ RunningOn.Compositor,
+ "offset-position transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("offset-position",
+ { compositorValue: { tx: 50, ty: 50 }, computed: '50px 50px' },
+ RunningOn.Compositor,
+ "offset-position on compositor at 5s");
+
+ done_div();
+});
+
+// Normal multiple transform-like properties animation (including motion-path).
+addAsyncAnimTest(async function() {
+ new_div("translate: 0px; " +
+ "offset-path: path('M50 50v100');" +
+ "offset-distance: 0%;" +
+ "transform: translateX(0px); " +
+ "transition: all 10s linear");
+ await waitForPaintsFlushed();
+
+ gDiv.style.translate = "0px 100px";
+ gDiv.style.transform = "translateX(-100px)";
+ gDiv.style.offsetDistance = "100%";
+ await waitForPaintsFlushed();
+
+ omta_is("transform", { compositorValue: [ 0, 1, -1, 0, 0, 0 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties transition runs on compositor thread");
+
+ advance_clock(5000);
+
+ omta_is("transform", { compositorValue: [ 0, 1, -1, 0, 0, 50 ],
+ usesMultipleProperties: true },
+ RunningOn.Compositor,
+ "transform-like properties transition runs on compositor thread");
+
+ done_div();
+});
+
+</script>
+</html>
diff --git a/layout/style/test/test_animations_omta_scroll.html b/layout/style/test/test_animations_omta_scroll.html
new file mode 100644
index 0000000000..324264c3e7
--- /dev/null
+++ b/layout/style/test/test_animations_omta_scroll.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test for css-animations running on the compositor thread with
+ scroll-timeline</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1676780">Scroll-driven
+ animations generated by CSS</a>
+<pre id="test"></pre>
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+// Open a new window to make sure we test this with scrolling attribute.
+window.open('file_animations_omta_scroll.html');
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_omta_scroll_rtl.html b/layout/style/test/test_animations_omta_scroll_rtl.html
new file mode 100644
index 0000000000..73339211c5
--- /dev/null
+++ b/layout/style/test/test_animations_omta_scroll_rtl.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test for css-animations running on the compositor thread with
+ scroll-timeline with right to left writing mode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1676780">Scroll-driven
+ animations generated by CSS</a>
+<pre id="test"></pre>
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+// Open a new window to make sure we test this with scrolling attribute.
+window.open('file_animations_omta_scroll_rtl.html');
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_omta_start.html b/layout/style/test/test_animations_omta_start.html
new file mode 100644
index 0000000000..92423bf67d
--- /dev/null
+++ b/layout/style/test/test_animations_omta_start.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=975261
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test OMTA animations start correctly (Bug 975261)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ @keyframes anim-opacity {
+ 0% { opacity: 0.5 }
+ 100% { opacity: 0.5 }
+ }
+ @keyframes anim-opacity-2 {
+ 0% { opacity: 0.0 }
+ 100% { opacity: 1.0 }
+ }
+ @keyframes anim-transform {
+ 0% { transform: translate(50px); }
+ 100% { transform: translate(50px); }
+ }
+ @keyframes anim-transform-2 {
+ 0% { transform: translate(0px); }
+ 100% { transform: translate(100px); }
+ }
+ .target {
+ /* These two lines are needed so that an opacity/transform layer
+ * already exists when the animation is applied. */
+ opacity: 0.99;
+ transform: translate(99px);
+
+ /* Element needs geometry in order to be animated on the
+ * compositor. */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=975261">Mozilla Bug
+ 975261</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+var gUtils = SpecialPowers.DOMWindowUtils;
+
+SimpleTest.waitForExplicitFinish();
+runOMTATest(testDelay, SimpleTest.finish);
+
+function newTarget() {
+ var target = document.createElement("div");
+ target.classList.add("target");
+ document.getElementById("display").appendChild(target);
+ return target;
+}
+
+function testDelay() {
+ gUtils.advanceTimeAndRefresh(0);
+
+ var target = newTarget();
+ target.setAttribute("style", "animation: 10s 10s anim-opacity linear");
+ gUtils.advanceTimeAndRefresh(0);
+
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(10100);
+ waitForAllPaints(function() {
+ var opacity = gUtils.getOMTAStyle(target, "opacity");
+ is(opacity, "0.5",
+ "opacity is set on compositor thread after delayed start");
+ target.removeAttribute("style");
+ gUtils.restoreNormalRefresh();
+ testTransform();
+ });
+ });
+}
+
+function testTransform() {
+ gUtils.advanceTimeAndRefresh(0);
+
+ var target = newTarget();
+ target.setAttribute("style", "animation: 10s 10s anim-transform linear");
+ gUtils.advanceTimeAndRefresh(0);
+
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(10100);
+ waitForAllPaints(function() {
+ var transform = gUtils.getOMTAStyle(target, "transform");
+ ok(matricesRoughlyEqual(convertTo3dMatrix(transform),
+ convertTo3dMatrix("matrix(1, 0, 0, 1, 50, 0)")),
+ "transform is set on compositor thread after delayed start");
+ target.remove();
+ gUtils.restoreNormalRefresh();
+ testBackwardsFill();
+ });
+ });
+}
+
+function testBackwardsFill() {
+ gUtils.advanceTimeAndRefresh(0);
+
+ var target = newTarget();
+ target.setAttribute("style",
+ "transform: translate(30px); " +
+ "animation: 10s 10s anim-transform-2 linear backwards");
+
+ gUtils.advanceTimeAndRefresh(0);
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(10000);
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(100);
+ waitForAllPaints(function() {
+ var transform = gUtils.getOMTAStyle(target, "transform");
+ ok(matricesRoughlyEqual(convertTo3dMatrix(transform),
+ convertTo3dMatrix("matrix(1, 0, 0, 1, 1, 0)")),
+ "transform is set on compositor thread after delayed start " +
+ "with backwards fill");
+ target.remove();
+ gUtils.restoreNormalRefresh();
+ testTransitionTakingOver();
+ });
+ });
+ });
+}
+
+function testTransitionTakingOver() {
+ gUtils.advanceTimeAndRefresh(0);
+
+ var parent = newTarget();
+ var child = newTarget();
+ parent.appendChild(child);
+ parent.style.opacity = "0.0";
+ parent.style.animation = "10s anim-opacity-2 linear";
+ child.style.opacity = "inherit";
+ child.style.transition = "10s opacity linear";
+
+ var childCS = getComputedStyle(child, "");
+
+ gUtils.advanceTimeAndRefresh(0);
+ waitForAllPaints(function() {
+ gUtils.advanceTimeAndRefresh(4000);
+ waitForAllPaints(function() {
+ child.style.opacity = "1.0";
+ var opacity = gUtils.getOMTAStyle(child, "opacity");
+ // FIXME Bug 1039799 (or lower priority followup): Animations
+ // inherited from an animating parent element don't get shipped to
+ // the compositor thread.
+ todo_is(opacity, "0.4",
+ "transition that interrupted animation is correct");
+
+ // Trigger to start the transition, without this the transition will
+ // be pending in advanceTimeAndRefresh(0) so the transition will not
+ // be sent to the compositor until we call advanceTimeAndRefresh with
+ // a positive time value.
+ getComputedStyle(child).opacity;
+ gUtils.advanceTimeAndRefresh(0);
+ waitForAllPaints(function() {
+ opacity = gUtils.getOMTAStyle(child, "opacity");
+ is(opacity, "0.4",
+ "transition that interrupted animation is correct");
+ gUtils.advanceTimeAndRefresh(5000);
+ waitForAllPaints(function() {
+ opacity = gUtils.getOMTAStyle(child, "opacity");
+ is(opacity, "0.7",
+ "transition that interrupted animation is correct");
+ is(childCS.opacity, "0.7",
+ "transition that interrupted animation is correct");
+ parent.remove();
+ gUtils.restoreNormalRefresh();
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_pausing.html b/layout/style/test/test_animations_pausing.html
new file mode 100644
index 0000000000..8aba46b5e8
--- /dev/null
+++ b/layout/style/test/test_animations_pausing.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test for Animation.play() and Animation.pause() on compositor animations
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 2 linear alternate");
+
+ // Animation is initially running on compositor
+ await waitForPaintsFlushed();
+ advance_clock(1000);
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "Animation is initally animating on compositor");
+
+ // pause() means it is no longer on the compositor
+ var animation = div.getAnimations()[0];
+ animation.pause();
+ // pause() should set up the changes to animations for the next layer
+ // transaction but it won't schedule a paint immediately so we need to tick
+ // the refresh driver before we can wait on the next paint.
+ advance_clock(0);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 10 }, RunningOn.MainThread,
+ "After pausing, animation is removed from compositor");
+
+ // Animation remains paused
+ advance_clock(1000);
+ omta_is(div, "transform", { tx: 10 }, RunningOn.MainThread,
+ "Animation remains paused");
+
+ // play() puts the animation back on the compositor
+ animation.play();
+ // As with pause(), play() will set up pending animations for the next layer
+ // transaction but won't schedule a paint so we need to tick the refresh
+ // driver before waiting on the next paint.
+ advance_clock(0);
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor,
+ "After playing, animation is sent to compositor");
+
+ // Where it continues to run
+ advance_clock(1000);
+ omta_is(div, "transform", { tx: 20 }, RunningOn.Compositor,
+ "Animation continues playing on compositor");
+
+ done_div();
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_playbackrate.html b/layout/style/test/test_animations_playbackrate.html
new file mode 100644
index 0000000000..8ecfdb6f28
--- /dev/null
+++ b/layout/style/test/test_animations_playbackrate.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for Animation.playbackRate on compositor animations</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards");
+ var animation = div.getAnimations()[0];
+ animation.playbackRate = 10;
+
+ advance_clock(300);
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 30 }, RunningOn.Compositor,
+ "at 300ms");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards");
+ var animation = div.getAnimations()[0];
+ advance_clock(300);
+ await waitForPaints();
+
+ animation.playbackRate = 0;
+
+ await waitForPaintsFlushed();
+
+ omta_is(div, "transform", { tx: 3 }, RunningOn.MainThread,
+ "animation with zero playback rate should stay in the " +
+ "same position and be running on the main thread");
+
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 1s");
+ var animation = div.getAnimations()[0];
+ animation.playbackRate = 0.5;
+
+ advance_clock(2000); // 1s * (1 / playbackRate)
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.Compositor,
+ "animation with positive delay and playbackRate > 1 should " +
+ "start from the initial position at the beginning of the " +
+ "active duration");
+ done_div();
+});
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s 1s");
+ var animation = div.getAnimations()[0];
+ animation.playbackRate = 2.0;
+
+ advance_clock(500); // 1s * (1 / playbackRate)
+
+ await waitForPaints();
+ omta_is(div, "transform", { tx: 0 }, RunningOn.Compositor,
+ "animation with positive delay and playbackRate < 1 should " +
+ "start from the initial position at the beginning of the " +
+ "active duration");
+ done_div();
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_reverse.html b/layout/style/test/test_animations_reverse.html
new file mode 100644
index 0000000000..93ff8c37a9
--- /dev/null
+++ b/layout/style/test/test_animations_reverse.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test for Animation.reverse() on compositor animations</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translate(0px) }
+ 100% { transform: translate(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+runOMTATest(function() {
+ runAllAsyncAnimTests().then(SimpleTest.finish);
+}, SimpleTest.finish, SpecialPowers);
+
+addAsyncAnimTest(async function() {
+ var [ div, cs ] = new_div("animation: anim 10s linear");
+ const animation = div.getAnimations()[0];
+
+ // Animation is initially running on compositor
+ await waitForPaintsFlushed();
+ advance_clock(5000);
+ omta_is(div, 'transform', { tx: 50 }, RunningOn.Compositor,
+ 'Animation is initally animating on compositor');
+
+ // Reverse animation
+ animation.reverse();
+
+ // At this point the playbackRate has changed but the transform will
+ // not have changed.
+ await waitForPaints();
+ omta_is(div, 'transform', { tx: 50 }, RunningOn.Compositor,
+ 'Animation value does not change after being reversed');
+
+ // However, we should still have sent a layer transaction to update the
+ // playbackRate on the compositor so that on the next tick we advance
+ // in the right direction.
+ advance_clock(1000);
+ omta_is(div, 'transform', { tx: 40 }, RunningOn.Compositor,
+ 'Animation proceeds in reverse direction');
+
+ done_div();
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_styles_on_event.html b/layout/style/test/test_animations_styles_on_event.html
new file mode 100644
index 0000000000..0e96711530
--- /dev/null
+++ b/layout/style/test/test_animations_styles_on_event.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Test that mouse movement immediately after finish() should involve
+ restyling for finished state (Bug 1228137)
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript"
+ src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ @keyframes anim {
+ 0% { transform: translateX(0px) }
+ 100% { transform: translateX(100px) }
+ }
+ .target {
+ /* The animation target needs geometry in order to qualify for OMTA */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+ }
+ </style>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function () {
+ // To avoid the effect that newly created element's styles are
+ // not updated immediately, we need to add an element without
+ // animation properties first.
+ var [ div ] = new_div("");
+ div.setAttribute("id", "bug1228137");
+
+ waitForPaints().then(function() {
+ var initialRect = div.getBoundingClientRect();
+
+ // Now we can set animation properties.
+ div.style.animation = "anim 100s linear forwards";
+
+ div.addEventListener("mousemove", function(event) {
+ is(event.target.id, "bug1228137",
+ "The target of the animation should receive the mouse move event " +
+ "on the position of the animation's effect end.");
+ done_div();
+ SimpleTest.finish();
+ });
+
+ var animation = div.getAnimations()[0];
+ animation.finish();
+
+ // Mouse over where the animation is positioned at finished state.
+ // We can't use synthesizeMouse here since synthesizeMouse causes
+ // layout flush. We need to check the position without explicit flushes.
+ synthesizeMouseAtPoint(initialRect.left + initialRect.width / 2 + 100,
+ initialRect.top + initialRect.height / 2,
+ { type: "mousemove" }, window);
+ });
+};
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_animations_variable_changes.html b/layout/style/test/test_animations_variable_changes.html
new file mode 100644
index 0000000000..ac254e1136
--- /dev/null
+++ b/layout/style/test/test_animations_variable_changes.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Tests that animations respond to changes to variables</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<style>
+:root {
+ --width: 100px;
+}
+.wider {
+ --width: 200px;
+}
+@keyframes widen {
+ to { margin-left: var(--width) }
+}
+</style>
+<body>
+<div id="log"></div>
+<script>
+
+test(() => {
+ const div = document.createElement('div');
+ document.body.append(div);
+
+ div.style.animation = 'widen step-start 100s';
+ assert_equals(getComputedStyle(div).marginLeft, '100px',
+ 'Animation value before updating CSS variable');
+
+ div.classList.add('wider');
+
+ assert_equals(getComputedStyle(div).marginLeft, '200px',
+ 'Animation value after updating CSS variable');
+
+ div.remove();
+}, 'Animation reflects changes to custom properties');
+
+test(() => {
+ const parent = document.createElement('div');
+ const child = document.createElement('div');
+ parent.append(child);
+ document.body.append(parent);
+
+ child.style.animation = 'widen step-start 100s';
+ assert_equals(getComputedStyle(child).marginLeft, '100px',
+ 'Animation value before updating CSS variable');
+
+ parent.classList.add('wider');
+
+ assert_equals(getComputedStyle(child).marginLeft, '200px',
+ 'Animation value after updating CSS variable');
+
+ parent.remove();
+ child.remove();
+}, 'Animation reflect changes to custom properties on parent');
+
+</script>
+</body>
diff --git a/layout/style/test/test_animations_with_disabled_properties.html b/layout/style/test/test_animations_with_disabled_properties.html
new file mode 100644
index 0000000000..9eb395003f
--- /dev/null
+++ b/layout/style/test/test_animations_with_disabled_properties.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265611
+-->
+<head>
+ <title>Test CSS animations ignore disabled properties (Bug 1265611)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265611">Mozilla Bug
+ 1265611</a>
+<pre id="test">
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+/*
+ * This test relies on the fact that the overflow-clip-box property
+ * is disabled by the layout.css.overflow-clip-box.enabled pref. If we ever
+ * remove that pref we will need to substitute some other pref:property
+ * combination.
+ */
+SpecialPowers.pushPrefEnv(
+ { 'set': [[ 'layout.css.overflow-clip-box.enabled', false ]] },
+ () => window.open('file_animations_with_disabled_properties.html'));
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_any_dynamic.html b/layout/style/test/test_any_dynamic.html
new file mode 100644
index 0000000000..9c9a9e2a3f
--- /dev/null
+++ b/layout/style/test/test_any_dynamic.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=544834
+-->
+<head>
+ <title>Test for Bug 544834</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ :-moz-any(#display, #display2) { text-decoration-line: underline }
+ p:-moz-any([foo], [bar]) { z-index: 17 }
+
+ </style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=544834">Mozilla Bug 544834</a>
+<p id="display" style="position:absolute"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 544834
+ *
+ * In particular, test that we go through :-moz-any() in AddRule.
+ */
+
+function run()
+{
+ var p = document.getElementById("display");
+ var cs = getComputedStyle(p, "");
+ is(cs.textDecorationLine, "underline", "should match first rule");
+ is(cs.zIndex, "auto", "should not match second rule");
+ p.removeAttribute("id");
+ is(cs.textDecorationLine, "none", "should not match first rule");
+ is(cs.zIndex, "auto", "should not match second rule");
+ p.setAttribute("foo", "v");
+ is(cs.textDecorationLine, "none", "should not match first rule");
+ is(cs.zIndex, "17", "should match second rule");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_area_url_cursor.html b/layout/style/test/test_area_url_cursor.html
new file mode 100644
index 0000000000..cbc5efed74
--- /dev/null
+++ b/layout/style/test/test_area_url_cursor.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<title>cursor: url() doesn't assert for area elements</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+area {
+ /* Doesn't matter to trigger the assert */
+ cursor: url(invalid.cur), auto;
+}
+</style>
+<img width="300" height="98" usemap="#map">
+<map name="map" id="map">
+ <area class="url" shape="rect" coords="0,0,300,98" href="https://mozilla.org"></area>
+</map>
+<div></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(() => {
+
+ let checked = false;
+ document.querySelector("area").addEventListener("mousemove", function() {
+ setTimeout(() => {
+ if (checked) {
+ return;
+ }
+ checked = true;
+ ok(true, "Didn't assert");
+ SimpleTest.finish()
+ }, 0);
+ });
+
+ synthesizeMouseAtCenter(document.querySelector("img"), { type: "mousemove" });
+});
+</script>
diff --git a/layout/style/test/test_asyncopen.html b/layout/style/test/test_asyncopen.html
new file mode 100644
index 0000000000..9a52ea37eb
--- /dev/null
+++ b/layout/style/test/test_asyncopen.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1195173
+-->
+<head>
+ <title>Bug 1195173 - Test asyncOpen security exception</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!-- Note: the following stylesheet does not exist -->
+ <link rel="stylesheet" id="myCSS" type="text/css" href="file:///home/foo/bar.css">
+
+</head>
+<body onload="checkCSS()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1195173">Mozilla Bug 1195173</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+
+<script type="application/javascript">
+/*
+ * Description of the test:
+ * Accessing a stylesheet that got blocked by asyncOpen should
+ * throw an exception.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+function checkCSS()
+{
+ try {
+ // accessing tests/SimpleTest/test.css should not throw
+ var goodCSS = document.styleSheets[0].cssRules
+ ok(true, "accessing test.css should be allowed");
+ }
+ catch(e) {
+ ok(false, "accessing test.css should be allowed");
+ }
+
+ try {
+ // accessing file:///home/foo/bar.css should throw
+ var badCSS = document.styleSheets[1].cssRules
+ ok(false, "accessing bar.css should throw");
+ }
+ catch(e) {
+ ok(true, "accessing bar.css should throw");
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_at_rule_parse_serialize.html b/layout/style/test/test_at_rule_parse_serialize.html
new file mode 100644
index 0000000000..2c6f2e2d5c
--- /dev/null
+++ b/layout/style/test/test_at_rule_parse_serialize.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=478160
+-->
+<head>
+ <title>Test for Bug 478160</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style" type="text/css"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478160">Mozilla Bug 478160</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 478160 **/
+
+var style_element = document.getElementById("style");
+var style_text = document.createTextNode("");
+style_element.appendChild(style_text);
+
+function test_at_rule(str) {
+ style_text.data = str;
+ is(style_element.sheet.cssRules.length, 1,
+ "should have one rule from " + str);
+ var ser1 = style_element.sheet.cssRules[0].cssText;
+ isnot(ser1, "", "should have non-empty rule from " + str);
+ style_text.data = ser1;
+ var ser2 = style_element.sheet.cssRules[0].cssText;
+ is(ser2, ser1, "parse+serialize should be idempotent for " + str);
+}
+
+test_at_rule("@namespace 'a b'");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_attribute_selector_eof_behavior.html b/layout/style/test/test_attribute_selector_eof_behavior.html
new file mode 100644
index 0000000000..76635f9ed0
--- /dev/null
+++ b/layout/style/test/test_attribute_selector_eof_behavior.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for EOF behavior of attribute selectors in selectors API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ assert_equals(document.querySelector("[id"),
+ document.getElementById("log"),
+ "We only have one element with an id");
+}, "']' should be implied if EOF after attribute name");
+test(function() {
+ assert_equals(document.querySelector('[id="log"'),
+ document.getElementById("log"),
+ "We should find the element with id=log");
+}, "']' should be implied if EOF after attribute value");
+</script>
diff --git a/layout/style/test/test_backdrop_filter_enabled_state.html b/layout/style/test/test_backdrop_filter_enabled_state.html
new file mode 100644
index 0000000000..f8f2995b28
--- /dev/null
+++ b/layout/style/test/test_backdrop_filter_enabled_state.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<head>
+ <meta charset=utf-8>
+ <title>Test Backdrop-Filter Enabled State</title>
+ <link rel="author" title="Erik Nordin" href="mailto:nordzilla@mozilla.com">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+(async function() {
+ for (let enabled of [true, false]) {
+ await SpecialPowers.pushPrefEnv({"set": [["layout.css.backdrop-filter.enabled", enabled]]});
+ is(enabled, CSS.supports("backdrop-filter: initial"),
+ "backdrop-filter is available only if backdrop-filter pref is set");
+ }
+ SimpleTest.finish();
+}());
+
+</script>
+
diff --git a/layout/style/test/test_background_blend_mode.html b/layout/style/test/test_background_blend_mode.html
new file mode 100644
index 0000000000..23551ebda9
--- /dev/null
+++ b/layout/style/test/test_background_blend_mode.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for miscellaneous computed style issues</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for miscellaneous computed style issues **/
+
+var frame_container = document.getElementById("display");
+var noframe_container = document.getElementById("content");
+
+function test_bug_841601() {
+ // Test handling of background-blend-mode
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+
+ frame_container.appendChild(p);
+ is(cs.backgroundBlendMode, "normal",
+ "default value of background-blend-mode");
+
+ p.setAttribute("style", "background-blend-mode: normal, invalid");
+ cs = getComputedStyle(p, "");
+ is(cs.backgroundBlendMode, "normal",
+ "set invalid blendmode");
+
+ p.setAttribute("style", "background-blend-mode: normal, normal");
+ cs = getComputedStyle(p, "");
+ is(cs.backgroundBlendMode, "normal, normal",
+ "set normal blendmode twice");
+
+ p.setAttribute("style", "background-blend-mode: normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity");
+ cs = getComputedStyle(p, "");
+ is(cs.backgroundBlendMode, "normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity",
+ "set all blendmodes");
+
+ p.remove();
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+test_bug_841601();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_border_device_pixel_rounding_initial_style.html b/layout/style/test/test_border_device_pixel_rounding_initial_style.html
new file mode 100644
index 0000000000..a8b5b0546d
--- /dev/null
+++ b/layout/style/test/test_border_device_pixel_rounding_initial_style.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<iframe id="frame"></iframe>
+<script>
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ "set": [["layout.css.devPixelsPerPx", "1.25"]] },
+ function() {
+ is(window.devicePixelRatio, 1.25, "devPixelsPerPx should work");
+ const frame = document.getElementById("frame");
+ frame.addEventListener("load", function() {
+ let doc = frame.contentDocument;
+ let win = frame.contentWindow;
+ is(win.devicePixelRatio, 1.25, "devPixelsPerPx should work inside the frame");
+ is(win.getComputedStyle(doc.querySelector("div")).borderTopWidth, "0.8px", "Shouldn't incorrectly round with 60 app units after getting the initial style");
+ SimpleTest.finish();
+ });
+ frame.srcdoc = "<div style='border: 1px solid; display: none;'></div>";
+ });
+</script>
diff --git a/layout/style/test/test_box_size_keywords.html b/layout/style/test/test_box_size_keywords.html
new file mode 100644
index 0000000000..c4074c7d8f
--- /dev/null
+++ b/layout/style/test/test_box_size_keywords.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for keywords on box sizing properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1122253">Mozilla Bug 1122253</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1496558">Mozilla Bug 1496558</a>
+
+<style>
+#outer {
+ position: absolute;
+ width: 200px;
+ height: 200px;
+}
+#horizontal, #vertical {
+ background-color: #ccc;
+ line-height: 1px;
+}
+#vertical {
+ writing-mode: vertical-rl;
+ position: relative;
+ top: 10px;
+}
+.small, .big {
+ display: inline-block;
+ block-size: 10px;
+}
+.small {
+ background-image: linear-gradient(to bottom right, black, fuchsia);
+ inline-size: 10px;
+}
+.big {
+ background-image: linear-gradient(to bottom right, black, cyan);
+ inline-size: 90px;
+}
+</style>
+
+<div id=outer>
+ <div id=horizontal><span class=small></span><span class=big></span><span class=big></span><span class=big></span></div>
+ <div id=vertical><span class=small></span><span class=big></span><span class=big></span><span class=big></span></div>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1122253 and Bug 1496558 **/
+
+// Test that the -moz-available, min-content, max-content, and
+// fit-content keywords are usable only on width, when the writing
+// mode is horizontal, or height, when the writing mode is vertical,
+// and that they are always available on inline-size and never on
+// block-size. When used on the wrong properties, they should be
+// equivalent to unset.
+//
+// Also test the corresponding min-* and max-* properties.
+
+var gTests = [
+ { orientation: "horizontal", property: "width", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "horizontal", property: "width", specified_value: "min-content", computed_value: "90px", },
+ { orientation: "horizontal", property: "width", specified_value: "max-content", computed_value: "280px", },
+ { orientation: "horizontal", property: "width", specified_value: "fit-content", computed_value: "200px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "min-content", computed_value: "90px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "max-content", computed_value: "280px", },
+ { orientation: "horizontal", property: "inline-size", specified_value: "fit-content", computed_value: "200px", },
+ { orientation: "horizontal", property: "min-width", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "min-width", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", property: "min-width", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", property: "min-width", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", property: "min-inline-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", property: "max-width", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "max-width", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", property: "max-width", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", property: "max-width", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", property: "max-inline-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", property: "height", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "vertical", property: "height", specified_value: "min-content", computed_value: "90px", },
+ { orientation: "vertical", property: "height", specified_value: "max-content", computed_value: "280px", },
+ { orientation: "vertical", property: "height", specified_value: "fit-content", computed_value: "200px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "min-content", computed_value: "90px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "max-content", computed_value: "280px", },
+ { orientation: "vertical", property: "inline-size", specified_value: "fit-content", computed_value: "200px", },
+ { orientation: "vertical", property: "min-height", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "min-height", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", property: "min-height", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", property: "min-height", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", property: "min-inline-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", property: "max-height", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "max-height", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", property: "max-height", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", property: "max-height", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", property: "max-inline-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "-moz-available", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "min-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "max-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "fit-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "min-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "max-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "fit-content", computed_value: "20px", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-available", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "min-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "max-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "fit-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "min-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "max-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "fit-content", computed_value: "20px", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "fit-content", computed_value: "fit-content", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "-moz-available", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "min-content", computed_value: "min-content", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "max-content", computed_value: "max-content", },
+ { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "fit-content", computed_value: "fit-content", },
+];
+
+gTests.forEach(function(t) {
+ var e = document.getElementById(t.orientation);
+ e.style = (t.prerequisites || "") + t.property + ": " + t.specified_value;
+ is(get_computed_value(getComputedStyle(e), t.property), t.computed_value,
+ `${t.orientation} ${t.property}:${t.specified_value}`);
+ e.style = "";
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1055933.html b/layout/style/test/test_bug1055933.html
new file mode 100644
index 0000000000..de96d1d441
--- /dev/null
+++ b/layout/style/test/test_bug1055933.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1055933
+-->
+<head>
+ <title>Test for Bug 1055933</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1055933">Mozilla Bug 1055933</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 1055933 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var element = document.getElementById('area');
+ is(getComputedStyle(element).cursor, "pointer", "Does the <area> element return the correct cursor?");
+ //Force mPrimaryFrame to be set
+ requestAnimationFrame( function() {
+ synthesizeMouseAtCenter(document.getElementById('image'), {}, window);
+ is(getComputedStyle(element).cursor, "pointer", "Does the <area> element still return the correct cursor after mPrimaryFrame is set?");
+ SimpleTest.finish();
+ });
+});
+</script>
+</pre>
+ <div style="cursor: crosshair">
+ <img usemap="#map" border ="0" id="image" src="file_bug1055933_circle-xxl.png">
+ <map id="map" name="map">
+ <area id="area" onmousedown="this.setCapture();" onmouseup="this.releaseCapture();" shape="circle" coords="128,129,103" style="cursor: pointer">
+ </map>
+ </div>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1089417.html b/layout/style/test/test_bug1089417.html
new file mode 100644
index 0000000000..d4b09beffd
--- /dev/null
+++ b/layout/style/test/test_bug1089417.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1089417
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1089417</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1089417 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ var f = document.getElementById("f");
+ var fwin = f.contentWindow;
+ var fdoc = f.contentDocument;
+
+ f.height = "400";
+ fdoc.getElementById("s").disabled = false;
+ is(fwin.getComputedStyle(fdoc.documentElement).backgroundColor,
+ "rgb(0, 128, 0)",
+ "media query change should have restyled");
+
+ f.height = "200";
+ fdoc.getElementById("s").disabled = true;
+ fdoc.getElementById("s").disabled = false;
+ is(fwin.getComputedStyle(fdoc.documentElement).backgroundColor,
+ "rgb(255, 0, 0)",
+ "media query change should have restyled");
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1089417">Mozilla Bug 1089417</a>
+<div id="display">
+ <iframe id="f" src="file_bug1089417_iframe.html" width="300" height="200"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1112014.html b/layout/style/test/test_bug1112014.html
new file mode 100644
index 0000000000..cd4330e50f
--- /dev/null
+++ b/layout/style/test/test_bug1112014.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1112014
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1112014</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="property_database.js"></script>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+async function test() {
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ // This holds a canonical test value for each TYPE_ constant.
+ let testValues = {
+ "color": "rgb(3,3,3)",
+ "gradient": "linear-gradient( 45deg, blue, red )",
+ "timing-function": "cubic-bezier(0.1, 0.7, 1.0, 0.1)",
+ };
+
+ // The canonical test values don't work for all properties, in
+ // particular some shorthand properties. For these cases we have
+ // override values.
+ let overrideValues = {
+ "box-shadow": {
+ "color": testValues.color + " 2px 2px"
+ },
+ "-webkit-box-shadow": {
+ "color": testValues.color + " 2px 2px"
+ },
+ "scrollbar-color": {
+ "color": testValues.color + " " + testValues.color,
+ },
+ "text-shadow": {
+ "color": testValues.color + " 2px 2px"
+ },
+ };
+
+
+ // Ensure that all the TYPE_ constants have a representative
+ // test value, to try to ensure that this test is updated
+ // whenever a new type is added.
+ let reps = await SpecialPowers.spawn(window, [], () => {
+ return Object.getOwnPropertyNames(InspectorUtils).filter(tc => /TYPE_/.test(tc));
+ }).then(v => v.filter(tc => !(tc in testValues)));
+ is(reps.join(","), "", "all types have representative test value");
+
+ for (let propertyName in gCSSProperties) {
+ let prop = gCSSProperties[propertyName];
+
+ for (let iter in testValues) {
+ let testValue = testValues[iter];
+ if (propertyName in overrideValues &&
+ iter in overrideValues[propertyName]) {
+ testValue = overrideValues[propertyName][iter];
+ }
+
+ let supported =
+ InspectorUtils.cssPropertySupportsType(propertyName, iter);
+ let parsed = CSS.supports(propertyName, testValue);
+ is(supported, parsed, propertyName + " supports " + iter);
+ }
+ }
+
+ // Regression test for an assertion failure in an earlier version of
+ // the code. Note that cssPropertySupportsType returns false for
+ // all types for a variable.
+ ok(!InspectorUtils.cssPropertySupportsType("--variable", "color"),
+ "cssPropertySupportsType returns false for variable");
+
+ SimpleTest.finish();
+}
+ </script>
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1112014">Mozilla Bug 1112014</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1203766.html b/layout/style/test/test_bug1203766.html
new file mode 100644
index 0000000000..42da6520ac
--- /dev/null
+++ b/layout/style/test/test_bug1203766.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for bug 1203766</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<style>
+.x { color: red; }
+body > .x { color: green; }
+.y { color: green; }
+body > .y { display: none; color: red; }
+div > .z { color: red; }
+.z { color: green; }
+.a { color: red; }
+body > .a { display: none; color: green; }
+.b { display: none; }
+.c { color: red; }
+.b > .c { color: green; }
+.e { color: red; }
+.d > .e { color: green; }
+.f { color: red; }
+.g { color: green; }
+.h > .i { color: red; }
+.j > .i { color: green; }
+</style>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1203766">Mozilla Bug 1203766</a>
+<p id="display"></p>
+<div class=y></div>
+<div class=b></div>
+<pre id="test">
+<script class="testbody">
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+
+ // Element that goes from being out of the document to in the document.
+ var e = document.createElement("div");
+ e.className = "x";
+ var cs = getComputedStyle(e);
+ is(cs.color, "");
+ document.body.appendChild(e);
+ is(cs.color, "rgb(0, 128, 0)");
+
+ // Element that goes from in the document (and display:none) to out of
+ // the document.
+ e = document.querySelector(".y");
+ cs = getComputedStyle(e);
+ is(cs.color, "rgb(255, 0, 0)");
+ e.remove();
+ is(cs.color, "");
+
+ // Element that is removed from an out-of-document tree.
+ e = document.createElement("div");
+ f = document.createElement("span");
+ f.className = "z";
+ e.appendChild(f);
+ cs = getComputedStyle(f);
+ is(cs.color, "");
+ f.remove();
+ is(cs.color, "");
+
+ // Element going from not in document to in document and display:none.
+ e = document.createElement("div");
+ e.className = "a";
+ cs = getComputedStyle(e);
+ is(cs.color, "");
+ document.body.appendChild(e);
+ is(cs.color, "rgb(0, 128, 0)");
+
+ // Element going from not in document to in document and child of
+ // display:none element.
+ e = document.createElement("div");
+ e.className = "c";
+ cs = getComputedStyle(e);
+ is(cs.color, "");
+ document.querySelector(".b").appendChild(e);
+ is(cs.color, "rgb(0, 128, 0)");
+
+ // Element that is added to an out-of-document tree.
+ e = document.createElement("div");
+ e.className = "d";
+ f = document.createElement("span");
+ f.className = "e";
+ cs = getComputedStyle(f);
+ is(cs.color, "");
+ e.appendChild(f);
+ is(cs.color, "");
+
+ // Element that is outside the document when an attribute is modified to
+ // cause a different rule to match.
+ e = document.createElement("div");
+ e.className = "f";
+ cs = getComputedStyle(e);
+ is(cs.color, "");
+ e.className = "g";
+ is(cs.color, "");
+
+ // Element that is outside the document when an ancestor is modified to
+ // cause a different rule to match.
+ e = document.createElement("div");
+ e.className = "h";
+ f = document.createElement("span");
+ f.className = "i";
+ e.appendChild(f);
+ cs = getComputedStyle(f);
+ is(cs.color, "");
+ e.className = "j";
+ is(cs.color, "");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
diff --git a/layout/style/test/test_bug1232829.html b/layout/style/test/test_bug1232829.html
new file mode 100644
index 0000000000..65bea2014d
--- /dev/null
+++ b/layout/style/test/test_bug1232829.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1232829
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 1232829</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+
+/** Test for Bug 1232829 **/
+
+// This should be a crashtest but it relies on using a pop-up window which
+// isn't supported in crashtests.
+function boom() {
+ var popup = window.open("data:text/html,2");
+ setTimeout(function() {
+ var frameDoc = document.querySelector("iframe").contentDocument;
+ frameDoc.write("3");
+ requestAnimationFrame(function() {
+ popup.close();
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+ });
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</head>
+<body onload="boom()">
+ <iframe srcdoc="<style>@keyframes a { to { opacity: 0.5 } }</style>
+ <div style='animation: a 1ms'></div>"></iframe>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1292447.html b/layout/style/test/test_bug1292447.html
new file mode 100644
index 0000000000..6937b648c5
--- /dev/null
+++ b/layout/style/test/test_bug1292447.html
@@ -0,0 +1,350 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Was for: https://bugzilla.mozilla.org/show_bug.cgi?id=365932
+ Updated for: https://bugzilla.mozilla.org/show_bug.cgi?id=1292447
+-->
+<head>
+ <title>Test for Bug 1292447</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ #content {
+ width: 800px;
+ height: 800px;
+ padding: 0 200px;
+ border-width: 0 200px;
+ border-style: solid;
+ border-color: transparent
+ }
+ #content2 {
+ display: none;
+ }
+ #content > div, #content2 > div {
+ width: 400px;
+ height: 400px;
+ padding: 0 100px;
+ border-width: 0 100px;
+ border-style: solid;
+ border-color: transparent
+ }
+ #content > div.auto, #content2 > div.auto {
+ width: auto; height: auto;
+ padding: 0 100px;
+ border-width: 0 80px;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1292447">Mozilla Bug 1292447</a>
+<p id="display"></p>
+<div id="content">
+ <div id="indent1" style="text-indent: 400px"></div>
+ <div id="indent2" style="text-indent: 50%"></div>
+
+ <div id="widthheight-1" class="auto"></div>
+
+ <div id="minwidth1-1" style="min-width: 200px"></div>
+ <div id="minwidth1-2" style="min-width: 25%"></div>
+ <div id="minwidth2-1" style="min-width: 600px"></div>
+ <div id="minwidth2-2" style="min-width: 75%"></div>
+ <div id="minwidth3-1" class="auto" style="min-width: 200px"></div>
+ <div id="minwidth3-2" class="auto" style="min-width: 25%"></div>
+ <div id="minwidth4-1" class="auto" style="min-width: 600px"></div>
+ <div id="minwidth4-2" class="auto" style="min-width: 75%"></div>
+
+ <div id="maxwidth1-1" style="max-width: 320px"></div>
+ <div id="maxwidth1-2" style="max-width: 40%"></div>
+ <div id="maxwidth2-1" style="max-width: 480px"></div>
+ <div id="maxwidth2-2" style="max-width: 60%"></div>
+ <div id="maxwidth3-1" class="auto" style="max-width: 320px"></div>
+ <div id="maxwidth3-2" class="auto" style="max-width: 40%"></div>
+ <div id="maxwidth4-1" class="auto" style="max-width: 480px"></div>
+ <div id="maxwidth4-2" class="auto" style="max-width: 60%"></div>
+
+ <div id="minmaxwidth1-1" style="min-width: 200px; max-width: 320px"></div>
+ <div id="minmaxwidth1-2" style="min-width: 200px; max-width: 40%"></div>
+ <div id="minmaxwidth2-1" style="min-width: 25%; max-width: 320px"></div>
+ <div id="minmaxwidth2-2" style="min-width: 25%; max-width: 40%"></div>
+ <div id="minmaxwidth3-1" style="min-width: 600px; max-width: 320px"></div>
+ <div id="minmaxwidth3-2" style="min-width: 600px; max-width: 40%"></div>
+ <div id="minmaxwidth4-1" style="min-width: 75%; max-width: 320px"></div>
+ <div id="minmaxwidth4-2" style="min-width: 75%; max-width: 40%"></div>
+ <div id="minmaxwidth5-1"
+ style="display:none; min-width: 200px; max-width: 320px"></div>
+ <div id="minmaxwidth6-1"
+ style="display: none; min-width: 25%; max-width: 320px"></div>
+ <div id="minmaxwidth7-1"
+ style="display: none; min-width: 600px; max-width: 320px"></div>
+ <div id="minmaxwidth7-2"
+ style="display: none; min-width: 600px; max-width: 40%"></div>
+ <div id="minmaxwidth8-1" class="auto"
+ style="min-width: 200px; max-width: 320px"></div>
+ <div id="minmaxwidth8-2" class="auto"
+ style="min-width: 200px; max-width: 40%"></div>
+ <div id="minmaxwidth9-1" class="auto"
+ style="min-width: 25%; max-width: 320px"></div>
+ <div id="minmaxwidth9-2" class="auto"
+ style="min-width: 25%; max-width: 40%"></div>
+ <div id="minmaxwidth10-1" class="auto"
+ style="min-width: 600px; max-width: 320px"></div>
+ <div id="minmaxwidth10-2" class="auto"
+ style="min-width: 600px; max-width: 40%"></div>
+ <div id="minmaxwidth11-1" class="auto"
+ style="min-width: 75%; max-width: 320px"></div>
+ <div id="minmaxwidth11-2" class="auto"
+ style="min-width: 75%; max-width: 40%"></div>
+
+ <div id="minheight1-1" style="min-height: 200px"></div>
+ <div id="minheight1-2" style="min-height: 25%"></div>
+ <div id="minheight2-1" style="min-height: 600px"></div>
+ <div id="minheight2-2" style="min-height: 75%"></div>
+ <div id="minheight3-1" class="auto" style="min-height: 200px"></div>
+ <div id="minheight3-2" class="auto" style="min-height: 25%"></div>
+ <div id="minheight4-1" class="auto" style="min-height: 600px"></div>
+ <div id="minheight4-2" class="auto" style="min-height: 75%"></div>
+
+ <div id="maxheight1-1" style="max-height: 320px"></div>
+ <div id="maxheight1-2" style="max-height: 40%"></div>
+ <div id="maxheight2-1" style="max-height: 480px"></div>
+ <div id="maxheight2-2" style="max-height: 60%"></div>
+ <div id="maxheight3-1" class="auto" style="max-height: 320px"></div>
+ <div id="maxheight3-2" class="auto" style="max-height: 40%"></div>
+ <div id="maxheight4-1" class="auto" style="max-height: 480px"></div>
+ <div id="maxheight4-2" class="auto" style="max-height: 60%"></div>
+
+ <div id="minmaxheight1-1" style="min-height: 200px; max-height: 320px"></div>
+ <div id="minmaxheight1-2" style="min-height: 200px; max-height: 40%"></div>
+ <div id="minmaxheight2-1" style="min-height: 25%; max-height: 320px"></div>
+ <div id="minmaxheight2-2" style="min-height: 25%; max-height: 40%"></div>
+ <div id="minmaxheight3-1" style="min-height: 600px; max-height: 320px"></div>
+ <div id="minmaxheight3-2" style="min-height: 600px; max-height: 40%"></div>
+ <div id="minmaxheight4-1" style="min-height: 75%; max-height: 320px"></div>
+ <div id="minmaxheight4-2" style="min-height: 75%; max-height: 40%"></div>
+ <div id="minmaxheight5-1"
+ style="display:none; min-height: 200px; max-height: 320px"></div>
+ <div id="minmaxheight6-1"
+ style="display: none; min-height: 25%; max-height: 320px"></div>
+ <div id="minmaxheight7-1"
+ style="display: none; min-height: 600px; max-height: 320px"></div>
+ <div id="minmaxheight7-2"
+ style="display: none; min-height: 600px; max-height: 40%"></div>
+ <div id="minmaxheight8-1" class="auto"
+ style="min-height: 200px; max-height: 320px"></div>
+ <div id="minmaxheight8-2" class="auto"
+ style="min-height: 200px; max-height: 40%"></div>
+ <div id="minmaxheight9-1" class="auto"
+ style="min-height: 25%; max-height: 320px"></div>
+ <div id="minmaxheight9-2" class="auto"
+ style="min-height: 25%; max-height: 40%"></div>
+ <div id="minmaxheight10-1" class="auto"
+ style="min-height: 600px; max-height: 320px"></div>
+ <div id="minmaxheight10-2" class="auto"
+ style="min-height: 600px; max-height: 40%"></div>
+ <div id="minmaxheight11-1" class="auto"
+ style="min-height: 75%; max-height: 320px"></div>
+ <div id="minmaxheight11-2" class="auto"
+ style="min-height: 75%; max-height: 40%"></div>
+
+ <div id="radius1" style="border-radius: 80px"></div>
+ <div id="radius2" style="border-radius: 20% / 20%"></div>
+</div>
+<div id="content2" style="display: none">
+ <div id="indent3" style="text-indent: 400px"></div>
+ <div id="indent4" style="text-indent: 50%"></div>
+
+ <div id="minwidth1-3" style="min-width: 200px"></div>
+ <div id="minwidth1-4" style="min-width: 25%"></div>
+ <div id="minwidth2-3" style="min-width: 600px"></div>
+ <div id="minwidth2-4" style="min-width: 75%"></div>
+
+ <div id="maxwidth1-3" style="max-width: 320px"></div>
+ <div id="maxwidth1-4" style="max-width: 40%"></div>
+ <div id="maxwidth2-3" style="max-width: 480px"></div>
+ <div id="maxwidth2-4" style="max-width: 60%"></div>
+
+ <div id="minmaxwidth1-3" style="min-width: 200px; max-width: 320px"></div>
+ <div id="minmaxwidth1-4" style="min-width: 200px; max-width: 40%"></div>
+ <div id="minmaxwidth2-3" style="min-width: 25%; max-width: 320px"></div>
+ <div id="minmaxwidth2-4" style="min-width: 25%; max-width: 40%"></div>
+ <div id="minmaxwidth3-3" style="min-width: 600px; max-width: 320px"></div>
+ <div id="minmaxwidth3-4" style="min-width: 600px; max-width: 40%"></div>
+ <div id="minmaxwidth4-3" style="min-width: 75%; max-width: 320px"></div>
+ <div id="minmaxwidth4-4" style="min-width: 75%; max-width: 40%"></div>
+
+ <div id="minheight1-3" style="min-height: 200px"></div>
+ <div id="minheight1-4" style="min-height: 25%"></div>
+ <div id="minheight2-3" style="min-height: 600px"></div>
+ <div id="minheight2-4" style="min-height: 75%"></div>
+
+ <div id="maxheight1-3" style="max-height: 320px"></div>
+ <div id="maxheight1-4" style="max-height: 40%"></div>
+ <div id="maxheight2-3" style="max-height: 480px"></div>
+ <div id="maxheight2-4" style="max-height: 60%"></div>
+
+ <div id="minmaxheight1-3" style="min-height: 200px; max-height: 320px"></div>
+ <div id="minmaxheight1-4" style="min-height: 200px; max-height: 40%"></div>
+ <div id="minmaxheight2-3" style="min-height: 25%; max-height: 320px"></div>
+ <div id="minmaxheight2-4" style="min-height: 25%; max-height: 40%"></div>
+ <div id="minmaxheight3-3" style="min-height: 600px; max-height: 320px"></div>
+ <div id="minmaxheight3-4" style="min-height: 600px; max-height: 40%"></div>
+ <div id="minmaxheight4-3" style="min-height: 75%; max-height: 320px"></div>
+ <div id="minmaxheight4-4" style="min-height: 75%; max-height: 40%"></div>
+
+ <div id="radius3" style="border-radius: 80px"></div>
+ <div id="radius4" style="border-radius: 20%"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1292447 **/
+
+document.body.offsetWidth;
+
+doATest("text-indent", "indent", 400, 50);
+doATest("border-top-left-radius", "radius", 80, 20);
+
+doATest("width", "widthheight-", 440, 0, true);
+doATest("height", "widthheight-", 0, 0, true);
+
+doATest("min-width", "minwidth1-", 200, 25);
+doATest("min-width", "minwidth2-", 600, 75);
+doATest("max-width", "maxwidth1-", 320, 40);
+doATest("max-width", "maxwidth2-", 480, 60);
+
+// Test that min-width doesn't affect computed max-width
+doATest("max-width", "minmaxwidth1-", 320, 40);
+doATest("max-width", "minmaxwidth2-", 320, 40);
+doATest("max-width", "minmaxwidth3-", 320, 40);
+doATest("max-width", "minmaxwidth4-", 320, 40);
+
+// Test that max and min-width affect computed width correctly
+doATest("width", "minwidth1-", 400, 0, true);
+doATest("width", "minwidth2-", 600, 0, true);
+doATest("width", "minwidth3-", 440, 0, true);
+doATest("width", "minwidth4-", 600, 0, true);
+doATest("width", "maxwidth1-", 320, 0, true);
+doATest("width", "maxwidth2-", 400, 0, true);
+doATest("width", "maxwidth3-", 320, 0, true);
+doATest("width", "maxwidth4-", 440, 0, true);
+doATest("width", "minmaxwidth1-", 320, 0, true);
+doATest("width", "minmaxwidth2-", 320, 0, true);
+doATest("width", "minmaxwidth3-", 600, 0, true);
+doATest("width", "minmaxwidth4-", 600, 0, true);
+doATest("width", "minmaxwidth5-", 400, 0, true);
+doATest("width", "minmaxwidth6-", 400, 0, true);
+doATest("width", "minmaxwidth7-", 400, 0, true);
+doATest("width", "minmaxwidth8-", 320, 0, true);
+doATest("width", "minmaxwidth9-", 320, 0, true);
+doATest("width", "minmaxwidth10-", 600, 0, true);
+doATest("width", "minmaxwidth11-", 600, 0, true);
+
+doATest("min-height", "minheight1-", 200, 25);
+doATest("min-height", "minheight2-", 600, 75);
+doATest("max-height", "maxheight1-", 320, 40);
+doATest("max-height", "maxheight2-", 480, 60);
+
+// Test that min-height doesn't affect computed max-height
+doATest("max-height", "minmaxheight1-", 320, 40);
+doATest("max-height", "minmaxheight2-", 320, 40);
+doATest("max-height", "minmaxheight3-", 320, 40);
+doATest("max-height", "minmaxheight4-", 320, 40);
+
+// Test that max and min-height affect computed height correctly
+doATest("height", "minheight1-", 400, 0, true);
+doATest("height", "minheight2-", 600, 0, true);
+doATest("height", "minheight3-", 200, 0, true);
+doATest("height", "minheight4-", 600, 0, true);
+doATest("height", "maxheight1-", 320, 0, true);
+doATest("height", "maxheight2-", 400, 0, true);
+doATest("height", "maxheight3-", 0, 0, true);
+doATest("height", "maxheight4-", 0, 0, true);
+doATest("height", "minmaxheight1-", 320, 0, true);
+doATest("height", "minmaxheight2-", 320, 0, true);
+doATest("height", "minmaxheight3-", 600, 0, true);
+doATest("height", "minmaxheight4-", 600, 0, true);
+doATest("height", "minmaxheight5-", 400, 0, true);
+doATest("height", "minmaxheight6-", 400, 0, true);
+doATest("height", "minmaxheight7-", 400, 0, true);
+doATest("height", "minmaxheight8-", 200, 0, true);
+doATest("height", "minmaxheight9-", 200, 0, true);
+doATest("height", "minmaxheight10-", 600, 0, true);
+doATest("height", "minmaxheight11-", 600, 0, true);
+
+function style(id) {
+ return document.defaultView.getComputedStyle($(id));
+}
+
+function round(num, decimals) {
+ return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals);
+}
+
+function coordValueTest(camelProp, decl, coordVal, prettyName) {
+ is(decl[camelProp], coordVal + "px", prettyName);
+}
+
+function percentValueTest(camelProp, decl, percentVal, prettyName) {
+ is(decl[camelProp], percentVal + "%", prettyName);
+}
+
+function doATest(propName, idBase, coordVal, percentVal, resolveToUsedVal = false) {
+ var cssCamelPropName = "";
+ var parts = propName.split("-");
+ ok(parts.length > 0, "CSS prop name should not be empty");
+ var i;
+ if (parts[0]) {
+ i = 0;
+ } else {
+ is(parts[1], "moz", "Testing an extension property that's not -moz");
+ ok(parts.length > 2, "-moz prop name should not have more than 2 parts");
+ cssCamelPropName = "Moz";
+ i = 2;
+ }
+ for (; i < parts.length; ++i) {
+ var part = parts[i];
+ isnot(part, "", "Must have a nonempty part");
+ if (cssCamelPropName) {
+ cssCamelPropName += part.charAt(0).toUpperCase() +
+ part.substring(1, part.length);
+ } else {
+ cssCamelPropName += part;
+ }
+ }
+
+ /* Test $(id)-1 */
+ coordValueTest(cssCamelPropName,
+ style(idBase + "1"), coordVal,
+ propName + " of " + idBase + "1");
+
+ if (!$(idBase + "2")) {
+ // Nothing else to do here
+ return
+ }
+
+ /* Test $(id)-2 */
+ if (resolveToUsedVal) {
+ coordValueTest(cssCamelPropName,
+ style(idBase + "2"), coordVal,
+ propName + " of " + idBase + "2");
+ } else {
+ percentValueTest(cssCamelPropName,
+ style(idBase + "2"), percentVal,
+ propName + " of " + idBase + "2");
+ }
+
+ if (percentVal) {
+ /* Test $(id)-3 */
+ coordValueTest(cssCamelPropName,
+ style(idBase + "3"), coordVal,
+ propName + " of " + idBase + "3");
+
+ /* Test $(id)-4 */
+ percentValueTest(cssCamelPropName,
+ style(idBase + "4"), percentVal,
+ propName + " of " + idBase + "4");
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1330375.html b/layout/style/test/test_bug1330375.html
new file mode 100644
index 0000000000..c9bc0f6715
--- /dev/null
+++ b/layout/style/test/test_bug1330375.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<!-- https://bugzil.la/1330375 -->
+<meta charset="utf-8">
+<title>Test for Bug 1330375</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest.css"/>
+<body>
+ <div id="content">
+ <table>
+ <tbody>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr>
+ </tbody>
+ </table>
+ </div>
+</body>
+<script>
+"use strict";
+
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function flush_layout(element) {
+ (element || document.documentElement).offsetHeight;
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function() {
+ flush_layout(document.getElementById("content"));
+
+ let before = {
+ framesConstructed: gUtils.framesConstructed,
+ framesReflowed: gUtils.framesReflowed,
+ };
+
+ // Begin test
+ let rows = document.getElementsByTagName("tr");
+ for (var r = 0; r < rows.length; r++) {
+ let row = rows[r];
+ row.innerText;
+ // Cause potential invalidation of layout:
+ row.style.display = "none";
+ }
+
+ is(gUtils.framesConstructed, before.framesConstructed, "Frames constructed should be 0");
+ is(gUtils.framesReflowed, before.framesReflowed, "Frames reflowed should be 0");
+
+ SimpleTest.finish();
+}
+</script>
+
diff --git a/layout/style/test/test_bug1371488.html b/layout/style/test/test_bug1371488.html
new file mode 100644
index 0000000000..7e32fa0031
--- /dev/null
+++ b/layout/style/test/test_bug1371488.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for bug 1371488</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style id="test">
+ @import url(non-exist-file.css);
+ </style>
+</head>
+<body>
+<pre id="log">
+<script>
+ let sheet = document.getElementById("test").sheet;
+ let rule = sheet.cssRules[0];
+ ok(rule, "The import rule should not be null even if the file fails to load");
+ is(rule.type, CSSRule.IMPORT_RULE, "It is the import rule");
+ ok(rule.styleSheet, "The associated stylesheet should exists as well");
+ is(rule.styleSheet.cssRules.length, 0, "The stylesheet should be empty");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1375944.html b/layout/style/test/test_bug1375944.html
new file mode 100644
index 0000000000..c265691170
--- /dev/null
+++ b/layout/style/test/test_bug1375944.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for bug 1375944</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<iframe id="subframe"></iframe>
+<pre id="log">
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+async function runTest() {
+ let f = new FontFace("Ahem", "url(Ahem.ttf)", {});
+ await f.load();
+ is(f.status, "loaded", "Loaded Ahem font");
+
+ let subframe = document.getElementById("subframe");
+ subframe.src = "file_bug1375944.html";
+ await new Promise(resolve => subframe.onload = resolve);
+ let elem = subframe.contentDocument.getElementById("test");
+ is(elem.getBoundingClientRect().width, 64,
+ "The font should be loaded properly");
+
+ SimpleTest.finish();
+}
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1382568.html b/layout/style/test/test_bug1382568.html
new file mode 100644
index 0000000000..23d4dfe5b6
--- /dev/null
+++ b/layout/style/test/test_bug1382568.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for bug 1382568: calling innerText on an uninitialized presshell doesn't crash</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ window.onmessage = function(e) {
+ is(e.data.result, "ok", "Child frame should load properly");
+ SimpleTest.finish();
+ };
+</script>
+<iframe src="https://example.com/tests/layout/style/test/bug1382568-iframe.html"></iframe>
+<script>
+ SimpleTest.waitForExplicitFinish();
+</script>
diff --git a/layout/style/test/test_bug1394302.html b/layout/style/test/test_bug1394302.html
new file mode 100644
index 0000000000..e21bcc4ea1
--- /dev/null
+++ b/layout/style/test/test_bug1394302.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1394302
+-->
+<head>
+ <title>Test for Bug 1394302</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ #inner {
+ animation: setFontSize 0s forwards;
+ }
+ @keyframes setFontSize {
+ to { font-size: calc(110% + 0.1em); }
+ }
+ </style>
+</head>
+<body>
+<div id=outer>
+ <div id=inner></div>
+</div>
+<script>
+var outer = document.getElementById("outer");
+outer.style.fontSize = '10px';
+is(getComputedStyle(inner).fontSize, "12px");
+
+outer.style.fontSize = '20px';
+is(getComputedStyle(inner).fontSize, "24px");
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1443344-1.html b/layout/style/test/test_bug1443344-1.html
new file mode 100644
index 0000000000..fbbcb1ecbb
--- /dev/null
+++ b/layout/style/test/test_bug1443344-1.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1443344
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1443344</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1443344 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var sheetURL = new URL("file_bug1443344.css", location.href);
+ sheetURL.protocol = "http";
+ var link = document.createElement("link");
+ link.href = `data:text/css,@import url("${sheetURL}");`
+ link.rel = "stylesheet";
+ var loadFired = false, errorFired = false;
+ link.onload = () => loadFired = true;
+ link.onerror = () => errorFired = true;
+ document.head.appendChild(link);
+
+ addLoadEvent(() => {
+ is(loadFired, false, "Should not fire onload for erroring @import");
+ is(errorFired, true, "Should fire onerror for erroring @import");
+ is(getComputedStyle($("importTarget")).color, "rgb(0, 255, 0)",
+ "Erroring sheet should not load");
+ SimpleTest.finish();
+ });
+
+ </script>
+ <style>
+ #importTarget { color: rgb(0, 255, 0); }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1443344">Mozilla Bug 1443344</a>
+<p id="display"><div id="importTarget"></div></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1443344-2.html b/layout/style/test/test_bug1443344-2.html
new file mode 100644
index 0000000000..224f575f36
--- /dev/null
+++ b/layout/style/test/test_bug1443344-2.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1443344
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1443344</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1443344 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var sheetURL = new URL("file_bug1443344.css", location.href);
+ sheetURL.protocol = "http";
+ var link = document.createElement("link");
+ link.href = `data:text/css,@import url("data:text/css,@import url('${sheetURL}');");`
+ link.rel = "stylesheet";
+ var loadFired = false, errorFired = false;
+ link.onload = () => loadFired = true;
+ link.onerror = () => errorFired = true;
+ document.head.appendChild(link);
+
+ addLoadEvent(() => {
+ is(loadFired, false, "Should not fire onload for erroring @import");
+ is(errorFired, true, "Should fire onerror for erroring @import");
+ is(getComputedStyle($("importTarget")).color, "rgb(0, 255, 0)",
+ "Erroring sheet should not load");
+ SimpleTest.finish();
+ });
+
+ </script>
+ <style>
+ #importTarget { color: rgb(0, 255, 0); }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1443344">Mozilla Bug 1443344</a>
+<p id="display"><div id="importTarget"></div></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1451199-1.html b/layout/style/test/test_bug1451199-1.html
new file mode 100644
index 0000000000..4deb6aac1b
--- /dev/null
+++ b/layout/style/test/test_bug1451199-1.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1451199
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1451199</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1451199 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ var iframe = document.querySelector("iframe");
+ iframe.width = "50";
+ iframe.contentDocument.documentElement.offsetWidth; // Flush layout
+
+ // We have to be careful to not check l.matches until the very end
+ // of the test.
+ var l = frames[0].matchMedia("(orientation: portrait)");
+ l.onchange = function() {
+ is(l.matches, false,
+ "Should not match portrait by the time we get notified");
+ SimpleTest.finish();
+ };
+ iframe.width = "200";
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1451199">Mozilla Bug 1451199</a>
+<p id="display"><iframe height="100" width="200"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1451199-2.html b/layout/style/test/test_bug1451199-2.html
new file mode 100644
index 0000000000..d29d644c0b
--- /dev/null
+++ b/layout/style/test/test_bug1451199-2.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1451199
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1451199</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1451199 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(async function() {
+ // We have to be careful to not check l.matches until the very end
+ // of the test.
+ const l = frames[0].matchMedia("(orientation: portrait)");
+ const iframe = document.querySelector("iframe");
+ iframe.width = "50";
+
+ await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
+
+ l.addEventListener("change", function() {
+ is(l.matches, false,
+ "Should not match portrait by the time we get notified");
+ SimpleTest.finish();
+ }, { once: true });
+ iframe.width = "200";
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1451199">Mozilla Bug 1451199</a>
+<p id="display"><iframe height="100" width="200"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1490890.html b/layout/style/test/test_bug1490890.html
new file mode 100644
index 0000000000..00a8176ed6
--- /dev/null
+++ b/layout/style/test/test_bug1490890.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1490890
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1490890</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #flex {
+ display: flex;
+ flex-direction: column;
+ height: 100px;
+ max-height: 100px;
+ overflow: hidden;
+ border: 1px solid black;
+ }
+ #overflowAuto {
+ overflow: auto;
+ white-space: pre-wrap;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1490890">Mozilla Bug 1490890</a>
+<div id="display">
+ <div id="content">
+ <div id="flex">
+ <div id="overflowAuto">
+ <!-- Populated by test JS below: -->
+ <div id="tall"></div>
+ </div>
+ <div id="testNode">abc</div>
+ </div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 1490890 **/
+
+/**
+ * This test checks how many reflows are required, when we make a change inside
+ * a flex item, with a tall scrollable sibling flex item.
+ */
+
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+// The elements that we will modify here:
+const gTall = document.getElementById("tall");
+const gTestNode = document.getElementById("testNode");
+
+// Helper function to undo our modifications:
+function cleanup()
+{
+ gTall.firstChild.remove();
+ gTestNode.style = "";
+}
+
+// Flush layout & return the global frame-reflow-count
+function getReflowCount()
+{
+ let unusedVal = document.getElementById("flex").offsetHeight; // flush layout
+ return gUtils.framesReflowed;
+}
+
+// This function changes gTestNode to "display:none", and returns the number
+// of frames that need to be reflowed as a result of that tweak.
+function makeTweakAndCountReflows()
+{
+ let beforeCount = getReflowCount();
+ gTestNode.style.display = "none";
+ let afterCount = getReflowCount();
+
+ let numReflows = afterCount - beforeCount;
+ if (numReflows <= 0) {
+ ok(false, "something's wrong -- we should've reflowed *something*");
+ }
+ return numReflows;
+}
+
+// ACTUAL TEST LOGIC STARTS HERE
+// -----------------------------
+const testLineCount = 100;
+const refLineCount = 5000;
+
+// "Reference" measurement: put enough lines of text into gTall to trigger
+// a vertical scrollbar, and then see how many frames need to be reflowed
+// in response to a tweak in gTestNode:
+let text = document.createTextNode("a\n".repeat(testLineCount));
+gTall.appendChild(text);
+let numReferenceReflows = makeTweakAndCountReflows();
+cleanup();
+
+// "Test" measurement: put many more lines of text into gTall (many more than
+// for reference case), and then see how many frames need to be reflowed
+// in response to a tweak in gTestNode:
+text = document.createTextNode("a\n".repeat(refLineCount));
+gTall.appendChild(text);
+let numTestReflows = makeTweakAndCountReflows();
+cleanup();
+
+is(numTestReflows, numReferenceReflows,
+ "Tweak should trigger the same number of reflows regardless of " +
+ "how much content is present in descendant of sibling");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1505254.html b/layout/style/test/test_bug1505254.html
new file mode 100644
index 0000000000..9cb3d1e316
--- /dev/null
+++ b/layout/style/test/test_bug1505254.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1505254
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1505254</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ /* Note: this CSS/DOM structure is loosely based on WhatsApp Web. */
+ #outerFlex {
+ display: flex;
+ height: 200px;
+ border: 3px solid purple;
+ overflow: hidden;
+ position: relative;
+ }
+ #outerItem {
+ flex: 0 0 60%;
+ overflow: hidden;
+ position: relative;
+ }
+ #abspos {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+ }
+ #insideAbspos {
+ position: relative;
+ flex: 1 1 0;
+ width: 100%;
+ height: 100%;
+ }
+ #scroller {
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ height: 100%;
+ width: 100%;
+ }
+ #initiallyHidden {
+ display:none;
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1505254">Mozilla Bug 1505254</a>
+<div id="display">
+ <div id="content">
+ <div id="outerFlex">
+ <div id="outerItem">
+ <div id="abspos">
+ <div id="insideAbspos">
+ <div>
+ <div id="scroller">
+ <div style="min-height: 600px">abc</div>
+ <div id="initiallyHidden">def</div>
+ </div>
+ </div>
+ </div>
+ <div id="testNode"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 1505254 **/
+
+/**
+ * This test checks how many reflows are required when we make a change inside
+ * of an abpsos element, which itself is inside of a flex item with cached
+ * block-size measurements. This test is checking that this sort of change
+ * doesn't invalidate those cached block-size measurements on the flex item
+ * ancestor. (We're testing that indirectly by seeing how many frames are
+ * reflowed.)
+ */
+
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+// The elements that we will modify here:
+const gInitiallyHidden = document.getElementById("initiallyHidden");
+const gTestNode = document.getElementById("testNode");
+
+// Helper function to undo our modifications:
+function cleanup()
+{
+ gTestNode.textContent = "";
+ gInitiallyHidden.style = "";
+}
+
+// Helper function to flush layout & return the global frame-reflow-count:
+function getReflowCount()
+{
+ let unusedVal = document.getElementById("scroller").offsetHeight; // flush layout
+ return gUtils.framesReflowed;
+}
+
+// This function adds some text in gTestNode and returns the number of frames
+// that need to be reflowed as a result of that tweak:
+function makeTweakAndCountReflows()
+{
+ let beforeCount = getReflowCount();
+ gTestNode.textContent = "def";
+ let afterCount = getReflowCount();
+
+ let numReflows = afterCount - beforeCount;
+ if (numReflows <= 0) {
+ ok(false, "something's wrong -- we should've reflowed *something*");
+ }
+ return numReflows;
+}
+
+// ACTUAL TEST LOGIC STARTS HERE
+// -----------------------------
+
+// "Reference" measurement: see how many frames need to be reflowed
+// in response to a tweak in gTestNode, before we've shown
+// #initiallyHidden:
+let numReferenceReflows = makeTweakAndCountReflows();
+cleanup();
+
+// "Test" measurement: see how many frames need to be reflowed
+// in response to a tweak in gTestNode, after we've shown #initiallyHidden:
+gInitiallyHidden.style.display = "block";
+let numTestReflows = makeTweakAndCountReflows();
+cleanup();
+
+// Any difference between our measurements is an indication that we're reflowing
+// frames in a non-"dirty" subtree. (The gTestNode tweak has no reason to cause
+// #initiallyHidden to be dirty -- and therefore, the presence/absence of
+// #initiallyHidden shouldn't affect the number of frames that get reflowed in
+// response to the gTestNode tweak).
+is(numTestReflows, numReferenceReflows,
+ "Tweak should trigger the same number of reflows regardless of " +
+ "content in unmodified sibling");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug160403.html b/layout/style/test/test_bug160403.html
new file mode 100644
index 0000000000..79b10462d8
--- /dev/null
+++ b/layout/style/test/test_bug160403.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=160403
+-->
+<head>
+ <title>Test for Bug 160403</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=160403">Mozilla Bug 160403</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 160403 **/
+
+var element = document.getElementById("content");
+var style = element.style;
+
+element.setAttribute("style", "border-top-style: dotted");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "");
+is(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "");
+
+element.setAttribute("style", "border-top-style: dotted ! important");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "important");
+is(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "");
+
+element.setAttribute("style", "border-top-style: dotted ! important; border-bottom-style: dotted ! important; border-left-style: dotted ! important");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "important");
+is(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "");
+
+element.setAttribute("style", "border-top-style: dotted ! important; border-right-style: dotted; border-bottom-style: dotted ! important; border-left-style: dotted ! important");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "important");
+is(style.getPropertyValue("border-right-style"), "dotted");
+is(style.getPropertyPriority("border-right-style"), "");
+is(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "");
+
+element.setAttribute("style", "border-top-style: dotted ! important; border-right-style: dotted ! important; border-bottom-style: dotted ! important; border-left-style: dotted ! important");
+is(style.getPropertyValue("border-top-style"), "dotted");
+is(style.getPropertyPriority("border-top-style"), "important");
+is(style.getPropertyValue("border-right-style"), "dotted");
+is(style.getPropertyPriority("border-right-style"), "important");
+isnot(style.getPropertyValue("border-style"), "");
+is(style.getPropertyPriority("border-style"), "important");
+
+// Also test that we check consistency of inherit and initial.
+element.setAttribute("style", "border-top-style: dotted; border-right-style: dotted; border-bottom-style: dotted; border-left-style: dotted");
+isnot(style.getPropertyValue("border-style"), "", "serialize shorthand when all values not inherit/initial");
+element.setAttribute("style", "border-top-style: inherit; border-right-style: inherit; border-bottom-style: inherit; border-left-style: inherit");
+is(style.getPropertyValue("border-style"), "inherit", "serialize shorthand as inherit");
+element.setAttribute("style", "border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial");
+is(style.getPropertyValue("border-style"), "initial", "serialize shorthand as initial");
+element.setAttribute("style", "border-top-style: dotted; border-right-style: dotted; border-bottom-style: dotted; border-left-style: inherit");
+is(style.getPropertyValue("border-style"), "", "don't serialize shorthand when partly inherit");
+element.setAttribute("style", "border-top-style: initial; border-right-style: dotted; border-bottom-style: initial; border-left-style: initial");
+is(style.getPropertyValue("border-style"), "", "don't serialize shorthand when partly initial");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1729861.html b/layout/style/test/test_bug1729861.html
new file mode 100644
index 0000000000..247b7c2644
--- /dev/null
+++ b/layout/style/test/test_bug1729861.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1729861
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test that toggling the resistFingerprinting pref re-evaluates device media queries</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="test-css"></style>
+ <script src="bug1729861.js"></script>
+ <script>
+ // Run all tests now.
+ window.onload = function () {
+ add_task(async function() {
+ await test();
+ });
+ };
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1729861">Bug 1729861</a>
+<p id="display">TEST</p>
+</body>
+</html>
diff --git a/layout/style/test/test_bug200089.html b/layout/style/test/test_bug200089.html
new file mode 100644
index 0000000000..496de50e98
--- /dev/null
+++ b/layout/style/test/test_bug200089.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=200089
+-->
+<head>
+ <title>Test for Bug 200089</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=200089">Mozilla Bug 200089</a>
+<div id="display" style="width: 600px">
+ <table border="0" id="t" style="width: 300px; margin-left: auto; margin-right: auto">
+ <tr><td>Cell</td></tr>
+ </table>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 200089 **/
+is(getComputedStyle($("t"), "").width, "300px",
+ "Used width should match specified width in this case");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug221428.html b/layout/style/test/test_bug221428.html
new file mode 100644
index 0000000000..7e84319462
--- /dev/null
+++ b/layout/style/test/test_bug221428.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=221428
+-->
+<head>
+ <title>Test for Bug 221428</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <link rel="stylesheet" href="data:text/css,body { color: green; }">
+ <style>
+ @import url("data:text/css,body { border: 1px solid transparent; }");
+ body { color: black; }
+ </style>
+ <script>
+ var executed = false;
+ </script>
+ <link rel="stylesheet" href="javascript:executed = true;">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=221428">Mozilla Bug 221428</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 221428 **/
+
+var exceptionThrown = false;
+try {
+ is(document.styleSheets[1].cssRules[0].cssText, "body { color: green; }",
+ "Should get the color: green rule back");
+} catch (e) {
+ exceptionThrown = true;
+}
+
+ok(!exceptionThrown, "Should be able to access data: <link> stylesheet");
+
+exceptionThrown = false;
+try {
+ is(document.styleSheets[2].cssRules[1].cssText, "body { color: black; }",
+ "Should get the color: black rule back");
+} catch (e) {
+ exceptionThrown = true;
+}
+ok(!exceptionThrown, "Should be able to access <style> stylesheet");
+
+exceptionThrown = false;
+try {
+ is(document.styleSheets[2].cssRules[0].styleSheet.cssRules[0].cssText,
+ "body { border: 1px solid transparent; }",
+ "Should get the 'border: 1px solid transparent' rule back");
+} catch (e) {
+ exceptionThrown = true;
+}
+ok(!exceptionThrown, "Should be able to access data: @import stylesheet");
+
+ok(!executed,
+ "Shouldn't be executing stylesheet-link javascript: URIs against " +
+ "the page context");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug229915.html b/layout/style/test/test_bug229915.html
new file mode 100644
index 0000000000..0a23d8b799
--- /dev/null
+++ b/layout/style/test/test_bug229915.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=229915
+-->
+<head>
+ <title>Test for Bug 229915</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ p { color: black; background: transparent; }
+ p.prev + p { color: green; }
+ p.prev ~ p { background: white; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=229915">Mozilla Bug 229915</a>
+<div id="display">
+
+<div>
+ <p id="toinsertbefore">After testing, this should turn green.</p>
+</div>
+
+<div>
+ <p id="toreplace">To be replaced.</p>
+ <p id="replacecolor">After testing, this should turn green.</p>
+</div>
+
+<div>
+ <p class="prev">Previous paragraph.</p>
+ <p id="toremove">To be removed.</p>
+ <p id="removecolor">After testing, this should turn green.</p>
+</div>
+
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 229915 **/
+
+const GREEN = "rgb(0, 128, 0)";
+const BLACK = "rgb(0, 0, 0)";
+const TRANSPARENT = "rgba(0, 0, 0, 0)";
+const WHITE = "rgb(255, 255, 255)";
+
+function make_prev() {
+ var result = document.createElement("p");
+ result.setAttribute("class", "prev");
+ var t = document.createTextNode("Dynamically created previous paragraph.");
+ result.appendChild(t);
+ return result;
+}
+
+function color(id) {
+ return getComputedStyle(document.getElementById(id), "").color;
+}
+function bg(id) {
+ return getComputedStyle(document.getElementById(id), "").backgroundColor;
+}
+
+var node;
+
+// test insert
+is(color("toinsertbefore"), BLACK, "initial state (insertion test)");
+is(bg("toinsertbefore"), TRANSPARENT, "initial state (insertion test)");
+node = document.getElementById("toinsertbefore");
+node.parentNode.insertBefore(make_prev(), node);
+is(color("toinsertbefore"), GREEN, "inserting should turn node green");
+is(bg("toinsertbefore"), WHITE, "inserting should turn background white");
+
+// test replace
+is(color("replacecolor"), BLACK, "initial state (replacement test)");
+is(bg("replacecolor"), TRANSPARENT, "initial state (replacement test)");
+node = document.getElementById("toreplace");
+node.parentNode.replaceChild(make_prev(), node);
+is(color("replacecolor"), GREEN, "replacing should turn node green");
+is(bg("replacecolor"), WHITE, "replacing should turn background white");
+
+// test remove
+is(color("removecolor"), BLACK, "initial state (removal test)");
+is(bg("removecolor"), WHITE, "initial state (removal test; no change)");
+node = document.getElementById("toremove");
+node.remove();
+is(color("removecolor"), GREEN, "removing should turn node green");
+is(bg("removecolor"), WHITE, "removing should leave background");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug302186.html b/layout/style/test/test_bug302186.html
new file mode 100644
index 0000000000..28ff676ff0
--- /dev/null
+++ b/layout/style/test/test_bug302186.html
@@ -0,0 +1,508 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=302186
+-->
+<head>
+ <title>Test for Bug 302186</title>
+ <script type="text/javascript" src="/MochiKit/Base.js"></script>
+ <script type="text/javascript" src="/MochiKit/DOM.js"></script>
+ <script type="text/javascript" src="/MochiKit/Style.js"></script>
+ <script type="text/javascript" src="/MochiKit/Color.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<style>
+
+
+span { color: red }
+:default + span { color: green }
+
+span.reverse { color: green }
+:default + span.reverse { color: red }
+
+button { display: none }
+input { display: none }
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=302186">Mozilla Bug 302186</a>
+<p id="display"></p>
+<div id="content" style="display: block">
+
+ <!-- static default 1 -->
+ <form>
+ <div>
+ <input type="submit" checked="checked"><span id="s1a">There should be no red.</span>
+ </div>
+ <div>
+ <input type="submit"><span id="s1b" class="reverse">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- static default 2 -->
+ <form>
+ <div>
+ <button type="submit" checked="checked" id="foo"></button>
+ <span id="s2a">There should be no red.</span>
+ </div>
+ <div>
+ <button type="submit"></button>
+ <span class="reverse" id="s2b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- static default 3 -->
+ <form>
+ <div>
+ <input type="checkbox" checked="checked" id="foo">
+ <span id="s3a">There should be no red.</span>
+ </div>
+ <div>
+ <input checked="checked">
+ <span class="reverse" id="s3b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- static default 3 -->
+ <form>
+ <div>
+ <input type="radio" checked="checked" id="foo">
+ <span id="s4a">There should be no red.</span>
+ </div>
+ <div>
+ <input checked="checked">
+ <span class="reverse" id="s4b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- static default 5 -->
+ <form>
+ <div>
+ <input type="image"><span id="s5a">There should be no red.</span>
+ </div>
+ <div>
+ <input type="image"><span id="s5b" class="reverse">There should be no red.</span>
+
+ </div>
+ </form>
+
+ <!-- dynamic default 1 -->
+ <form>
+ <div>
+ <input type="submit" checked="checked" id="foo1">
+ <span class="reverse" id="1a">There should be no red.</span>
+ </div>
+ <div>
+ <input type="submit">
+ <span id="1b">There should be no red.</span>
+
+ </div>
+ </form>
+
+ <!-- dynamic default 2 -->
+ <form>
+ <div>
+ <button type="submit" checked="checked" id="foo2"></button>
+ <span class="reverse" id="2a">There should be no red.</span>
+ </div>
+ <div>
+ <button type="submit"></button>
+ <span id="2b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- dynamic default 3 -->
+ <form>
+ <div>
+ <input type="checkbox" checked="checked" id="foo3">
+ <span class="reverse" id="3a">There should be no red.</span>
+ </div>
+ <div>
+ <input checked="checked" id="bar3">
+ <span id="3b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- dynamic default 4 -->
+ <form>
+ <div>
+ <input type="radio" checked="checked" id="foo4">
+ <span class="reverse" id="4a" >There should be no red.</span>
+ </div>
+ <div>
+ <input checked="checked" id="bar4">
+ <span id="4b">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- dynamic default 5 -->
+ <form>
+ <div>
+ <input type="submit">
+ <input type="radio" checked="checked" id="foo5">
+ <span id="5" class="reverse">There should be no red.</span>
+ </div>
+ </form>
+
+ <!-- dynamic default 6 -->
+ <form>
+ <div id="div6">
+ <span id="6a">There should be no red.</span>
+</div>
+<div>
+ <input type="submit"><span id="6b" class="reverse">There should be no red.</span>
+</div>
+ </form>
+
+ <!-- dynamic default 7 -->
+ <form>
+<div>
+ <input type="submit"><span id="7a">There should be no red.</span>
+</div>
+<div id="div7">
+ <span class="reverse" id="7b">There should be no red.</span>
+
+</div>
+</form>
+
+ <!-- dynamic default 8 -->
+<form>
+<div id="div8"><span id="8a">There should be no red.</span>
+</div>
+<div>
+ <input type="image" id="foo"><span class="reverse" id="8b">There should be no red.</span>
+
+</div>
+</form>
+
+ <!-- dynamic default 9 -->
+<form>
+<div>
+ <input type="image"><span id="9a">There should be no red.</span>
+</div>
+<div id="div9">
+ <span class="reverse" id="9b">There should be no red.</span>
+
+</div>
+</form>
+
+ <!-- dynamic default 10 -->
+<form>
+<div id="div10">
+ <input type="submit"><span id="10a" class="reverse">There should be no red.</span>
+</div>
+<div>
+ <input type="submit"><span id="10b" >There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 11 -->
+<form>
+<div id="div11a">
+ <input type="submit"><span id="11a">There should be no red.</span>
+</div>
+<div id="div11">
+ <input type="submit"><span id="11b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 12 -->
+<form>
+<div id="div12">
+ <input type="image"><span id="12a" class="reverse">There should be no red.</span>
+</div>
+<div>
+ <input type="image"><span id="12b">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 13 -->
+<form>
+<div id="div13a">
+ <input type="image"><span id="13a">There should be no red.</span>
+</div>
+<div id="div13">
+ <input type="image"><span id="13b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 14 -->
+<form>
+<div id="div14a">
+ <input type="submit" id="foo14"><span id="14a">There should be no red.</span>
+</div>
+<div id="div14b">
+ <input type="submit" id="foo14b"><span id="14b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 15 -->
+<form>
+<div id="div15a">
+ <input type="image" id="foo15a"><span id="15a">There should be no red.</span>
+</div>
+<div id="div15b">
+ <input type="image" id="foo15b"><span id="15b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 16 -->
+<form>
+<div>
+ <input type="image" checked="checked" id="foo16"></button>
+ <span class="reverse" id="16a">There should be no red.</span>
+</div>
+<div>
+ <input type="image"></button><span id="16b">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 17 -->
+<form>
+<div>
+ <button type="button" id="foo17"></button>
+ <span id="17a">There should be no red.</span>
+</div>
+<div>
+ <button type="submit"></button><span class="reverse" id="17b">There should be no red.</span>
+</div>
+</form>
+
+<!-- dynamic default 18 -->
+<form>
+<div>
+ <input type="button" id="foo18"></button>
+ <span id="18a">There should be no red.</span>
+</div>
+<div>
+ <input type="submit"></button><span id="18b" class="reverse">There should be no red.</span>
+
+</div>
+</form>
+
+<!-- dynamic default 19 -->
+<form>
+<div id="div19">
+ <span id="19a">There should be no red.</span>
+</div>
+</form>
+
+<!-- dynamic default 20 -->
+<form>
+<div id="div20">
+ <span id="20a">There should be no red.</span>
+</div>
+</form>
+
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 302186 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function idColor(anId) {
+ var color = Color.fromComputedStyle(anId, "color");
+ return color.toRGBString();
+}
+
+is(idColor("s1a"),"rgb(0,128,0)", "CSS static-default 1a");
+is(idColor("s1b"),"rgb(0,128,0)", "CSS static-default 1b");
+is(idColor("s2a"),"rgb(0,128,0)", "CSS static-default 2a");
+is(idColor("s2b"),"rgb(0,128,0)", "CSS static-default 2b");
+is(idColor("s3a"),"rgb(0,128,0)", "CSS static-default 3a");
+is(idColor("s3b"),"rgb(0,128,0)", "CSS static-default 3b");
+is(idColor("s4a"),"rgb(0,128,0)", "CSS static-default 4a");
+is(idColor("s4b"),"rgb(0,128,0)", "CSS static-default 4b");
+is(idColor("s5a"),"rgb(0,128,0)", "CSS static-default 5a");
+is(idColor("s5b"),"rgb(0,128,0)", "CSS static-default 5b");
+
+function dynamicDefault1() {
+ $('foo1').removeAttribute("type");
+ is(idColor("1a"),"rgb(0,128,0)", "CSS dynamic-default 1a");
+ is(idColor("1b"),"rgb(0,128,0)", "CSS dynamic-default 1b");
+}
+
+function dynamicDefault2() {
+ $('foo2').setAttribute("type", "button");
+ is(idColor("2a"),"rgb(0,128,0)", "CSS dynamic-default 2a");
+ is(idColor("2b"),"rgb(0,128,0)", "CSS dynamic-default 2b");
+}
+
+function dynamicDefault3() {
+ $('foo3').removeAttribute("type");
+ $('bar3').setAttribute("type", "checkbox");
+ is(idColor("3a"),"rgb(0,128,0)", "CSS dynamic-default 3a");
+ is(idColor("3b"),"rgb(0,128,0)", "CSS dynamic-default 3b");
+}
+
+function dynamicDefault4() {
+ $('foo4').removeAttribute("type");
+ $('bar4').setAttribute("type", "radio");
+ is(idColor("4a"),"rgb(0,128,0)", "CSS dynamic-default 4a");
+ is(idColor("4b"),"rgb(0,128,0)", "CSS dynamic-default 4b");
+}
+
+function dynamicDefault5() {
+ $('foo5').setAttribute("type", "submit")
+ is(idColor("5"),"rgb(0,128,0)", "CSS dynamic-default 5");
+}
+
+function dynamicDefault6() {
+ var but = document.createElement("input");
+ but.setAttribute("type", "submit");
+ $('div6').insertBefore(but, $('div6').firstChild);
+ is(idColor("6a"),"rgb(0,128,0)", "CSS dynamic-default 6a");
+ is(idColor("6b"),"rgb(0,128,0)", "CSS dynamic-default 6b");
+}
+
+function dynamicDefault7() {
+ var but = document.createElement("input");
+ but.setAttribute("type", "submit");
+ $('div7').insertBefore(but, $('div7').firstChild);
+ is(idColor("7a"),"rgb(0,128,0)", "CSS dynamic-default 7a");
+ is(idColor("7b"),"rgb(0,128,0)", "CSS dynamic-default 7b");
+}
+
+function dynamicDefault8() {
+ var but = document.createElement("input");
+ but.setAttribute("type", "image");
+ $('div8').insertBefore(but, $('div8').firstChild);
+ is(idColor("8a"),"rgb(0,128,0)", "CSS dynamic-default 8a");
+ is(idColor("8b"),"rgb(0,128,0)", "CSS dynamic-default 8b");
+}
+
+function dynamicDefault9() {
+ var but = document.createElement("input");
+ but.setAttribute("type", "image");
+ $('div9').insertBefore(but, $('div9').firstChild);
+ is(idColor("9a"),"rgb(0,128,0)", "CSS dynamic-default 9a");
+ is(idColor("9b"),"rgb(0,128,0)", "CSS dynamic-default 9b");
+}
+
+function dynamicDefault10() {
+ var inputs = $('div10').getElementsByTagName("input");
+ $('div10').removeChild(inputs[0]);
+ is(idColor("10a"),"rgb(0,128,0)", "CSS dynamic-default 10a");
+ is(idColor("10b"),"rgb(0,128,0)", "CSS dynamic-default 10b");
+}
+
+function dynamicDefault11() {
+ var inputs = $('div11').getElementsByTagName("input");
+ $('div11').removeChild(inputs[0]);
+ is(idColor("11a"),"rgb(0,128,0)", "CSS dynamic-default 11a");
+ is(idColor("11b"),"rgb(0,128,0)", "CSS dynamic-default 11b");
+}
+
+function dynamicDefault12() {
+ var inputs = $('div12').getElementsByTagName("input");
+ $('div12').removeChild(inputs[0]);
+ is(idColor("12a"),"rgb(0,128,0)", "CSS dynamic-default 12a");
+ is(idColor("12b"),"rgb(0,128,0)", "CSS dynamic-default 12b");
+}
+
+function dynamicDefault13() {
+ var inputs = $('div13').getElementsByTagName("input");
+ $('div13').removeChild(inputs[0]);
+ is(idColor("13a"),"rgb(0,128,0)", "CSS dynamic-default 13a");
+ is(idColor("13b"),"rgb(0,128,0)", "CSS dynamic-default 13b");
+}
+
+function dynamicDefault14() {
+ var div1 = document.getElementById("div14a");
+ var inputs = div1.getElementsByTagName("input");
+ var firstElement = div1.removeChild(inputs[0]);
+ var div2 = document.getElementById("div14b");
+ inputs = div2.getElementsByTagName("input");
+ var secondElement = div2.removeChild(inputs[0]);
+ div1.insertBefore(secondElement, div1.firstChild);
+ div2.insertBefore(firstElement, div2.firstChild);
+ is(idColor("14a"),"rgb(0,128,0)", "CSS dynamic-default 14a");
+ is(idColor("14b"),"rgb(0,128,0)", "CSS dynamic-default 14b");
+}
+
+function dynamicDefault15() {
+ var div1 = document.getElementById("div15a");
+ var inputs = div1.getElementsByTagName("input");
+ var firstElement = div1.removeChild(inputs[0]);
+ var div2 = document.getElementById("div15b");
+ inputs = div2.getElementsByTagName("input");
+ var secondElement = div2.removeChild(inputs[0]);
+ div1.insertBefore(secondElement, div1.firstChild);
+ div2.insertBefore(firstElement, div2.firstChild);
+ is(idColor("15a"),"rgb(0,128,0)", "CSS dynamic-default 15a");
+ is(idColor("15b"),"rgb(0,128,0)", "CSS dynamic-default 15b");
+}
+
+function dynamicDefault16() {
+ $("foo16").setAttribute("type", "button");
+ is(idColor("16a"),"rgb(0,128,0)", "CSS dynamic-default 16a");
+ is(idColor("16b"),"rgb(0,128,0)", "CSS dynamic-default 16b");
+}
+
+function dynamicDefault17() {
+ $("foo17").setAttribute("type", "submit");
+ is(idColor("17a"),"rgb(0,128,0)", "CSS dynamic-default 17a");
+ is(idColor("17b"),"rgb(0,128,0)", "CSS dynamic-default 17b");
+}
+
+function dynamicDefault18() {
+ $("foo18").setAttribute("type", "submit");
+ is(idColor("18a"),"rgb(0,128,0)", "CSS dynamic-default 18a");
+ is(idColor("18b"),"rgb(0,128,0)", "CSS dynamic-default 18b");
+}
+
+function dynamicDefault19() {
+ var newSubmit = document.createElement("input");
+ newSubmit.setAttribute("type", "submit");
+ var div1 = document.getElementById("div19");
+ div1.insertBefore(newSubmit, div1.firstChild);
+ is(idColor("19a"),"rgb(0,128,0)", "CSS dynamic-default 19a");
+}
+
+function dynamicDefault20() {
+ var newSubmit = document.createElement("input");
+ newSubmit.setAttribute("type", "image");
+ var div1 = document.getElementById("div20");
+ div1.insertBefore(newSubmit, div1.firstChild);
+ is(idColor("20a"),"rgb(0,128,0)", "CSS dynamic-default 20a");
+}
+
+addLoadEvent(dynamicDefault1);
+addLoadEvent(dynamicDefault2);
+addLoadEvent(dynamicDefault3);
+addLoadEvent(dynamicDefault4);
+addLoadEvent(dynamicDefault5);
+addLoadEvent(dynamicDefault6);
+addLoadEvent(dynamicDefault7);
+addLoadEvent(dynamicDefault8);
+addLoadEvent(dynamicDefault9);
+addLoadEvent(dynamicDefault10);
+addLoadEvent(dynamicDefault11);
+addLoadEvent(dynamicDefault12);
+addLoadEvent(dynamicDefault13);
+addLoadEvent(dynamicDefault14);
+addLoadEvent(dynamicDefault15);
+addLoadEvent(dynamicDefault16);
+addLoadEvent(dynamicDefault17);
+addLoadEvent(dynamicDefault18);
+addLoadEvent(dynamicDefault19);
+addLoadEvent(dynamicDefault20);
+
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug319381.html b/layout/style/test/test_bug319381.html
new file mode 100644
index 0000000000..d29f1bbd39
--- /dev/null
+++ b/layout/style/test/test_bug319381.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=319381
+-->
+<head>
+ <title>Test for Bug 319381</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=319381">Mozilla Bug 319381</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="t"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 319381 **/
+
+function c() {
+ return document.defaultView.getComputedStyle($('t')).
+ getPropertyValue("overflow");
+}
+
+var vals = ["visible", "hidden", "auto", "scroll"];
+var mozVals = ["-moz-scrollbars-vertical", "-moz-scrollbars-horizontal"];
+var i, j;
+
+for (i = 0; i < vals.length; ++i) {
+ $('t').style.overflow = vals[i];
+ is($('t').style.overflow, vals[i], "Roundtrip");
+ is(c(), vals[i], "Simple property set");
+}
+
+for (i = 0; i < vals.length; ++i) {
+ for (j = 0; j < vals.length; ++j) {
+ $('t').setAttribute("style",
+ "overflow-x: " + vals[i] + "; overflow-y: " + vals[j]);
+ is($('t').style.getPropertyValue("overflow-x"), vals[i], "Roundtrip");
+ is($('t').style.getPropertyValue("overflow-y"), vals[j], "Roundtrip");
+
+ if (i == j) {
+ is($('t').style.overflow, vals[i], "Shorthand serialization");
+ } else {
+ is($('t').style.overflow, vals[i] + " " + vals[j], "Shorthand serialization");
+ }
+
+ // "visible" overflow-x and overflow-y become "auto" in computed style if
+ // the other direction is not also "visible".
+ if (i == j || (vals[i] == "visible" && vals[j] == "auto")) {
+ is(c(), vals[j], "Shorthand computation");
+ } else if (vals[j] == "visible" && vals[i] == "auto") {
+ is(c(), vals[i], "Shorthand computation");
+ } else {
+ let x = vals[i] == "visible" ? "auto" : vals[i];
+ let y = vals[j] == "visible" ? "auto" : vals[j];
+ is(c(), x + " " + y, "Shorthand computation");
+ }
+ }
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug357614.html b/layout/style/test/test_bug357614.html
new file mode 100644
index 0000000000..37475512d4
--- /dev/null
+++ b/layout/style/test/test_bug357614.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=357614
+-->
+<head>
+ <title>Test for Bug 357614</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style type="text/css" id="style">
+ a { color: red; }
+ a { color: green; }
+ </style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=357614">Mozilla Bug 357614</a>
+<p id="display"><a href="http://www.FOO.com/" rel="next" rev="PREV" foo="bar">a link</a></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 357614 **/
+
+var sheet = document.getElementById("style").sheet;
+var rule1 = sheet.cssRules[0];
+var rule2 = sheet.cssRules[1];
+
+var a = document.getElementById("display").firstChild;
+var cs = getComputedStyle(a, "");
+
+function change_selector_text(selector) {
+ // rule2.selectorText = selector; // NOT IMPLEMENTED
+
+ sheet.deleteRule(1);
+ sheet.insertRule(selector + " { color: green; }", 1);
+}
+
+var cs_green = cs.getPropertyValue("color");
+change_selector_text('p');
+var cs_red = cs.getPropertyValue("color");
+isnot(cs_green, cs_red, "computed values for green and red are different");
+
+change_selector_text('a[href="http://www.FOO.com/"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on href value matches case-sensitively");
+
+change_selector_text('a[href="http://www.foo.com/"]');
+is(cs.getPropertyValue("color"), cs_red, "selector on href value does not match case-insensitively");
+
+change_selector_text('a[rel="next"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on rel value matches case-sensitively");
+
+change_selector_text('a[rel="NEXT"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on rel value matches case-insensitively");
+
+change_selector_text('a[rev="PREV"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on rev value matches case-sensitively");
+
+change_selector_text('a[rev="prev"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on rev value matches case-insensitively");
+
+change_selector_text('a[foo="bar"]');
+is(cs.getPropertyValue("color"), cs_green, "selector on foo value matches case-sensitively");
+
+change_selector_text('a[foo="Bar"]');
+is(cs.getPropertyValue("color"), cs_red, "selector on foo value does not match case-insensitively");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug363146.html b/layout/style/test/test_bug363146.html
new file mode 100644
index 0000000000..09ed45a8e2
--- /dev/null
+++ b/layout/style/test/test_bug363146.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=363146
+-->
+<head>
+ <title>Test for Bug 363146</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=363146">Mozilla Bug 363146</a>
+<div style="width:100px; height:400px; position:relative;">
+ <table id="t1" border="0" cellspacing="0" cellpadding="0"
+ style="border:10px solid black; margin:20px; position:absolute; left:50px; top:35px;">
+ <caption align="top" style="height:100px;">Caption</caption>
+ <tr>
+ <td><div style="width:400px; height:100px;">Cell</div></td>
+ </tr>
+ </table>
+</div>
+<div style="width:100px; height:400px; position:relative;">
+ <table id="t2" border="0" cellspacing="0" cellpadding="0"
+ style="margin:20%;">
+ <caption align="top" style="height:100px;">Caption</caption>
+ <tr>
+ <td><div style="width:400px; height:100px;">Cell</div></td>
+ </tr>
+ </table>
+</div>
+<p id="display"></p>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var c = window.getComputedStyle(document.getElementById("t1"));
+is(c.width, "420px");
+is(c.height, "120px");
+is(c.left, "50px");
+is(c.top, "35px");
+is(c.borderLeftWidth, "10px");
+is(c.borderRightWidth, "10px");
+is(c.borderTopWidth, "10px");
+is(c.borderBottomWidth, "10px");
+is(c.marginLeft, "20px");
+is(c.marginRight, "20px");
+is(c.marginTop, "20px");
+is(c.marginBottom, "20px");
+
+var c2 = window.getComputedStyle(document.getElementById("t2"));
+is(c2.marginLeft, "20px");
+is(c2.marginRight, "20px");
+is(c2.marginTop, "20px");
+is(c2.marginBottom, "20px");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug372770.html b/layout/style/test/test_bug372770.html
new file mode 100644
index 0000000000..ac3879c9d4
--- /dev/null
+++ b/layout/style/test/test_bug372770.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372770
+-->
+<head>
+ <title>Test for Bug 372770</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style id="testStyle">
+ #content {}
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372770">Mozilla Bug 372770</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 372770 **/
+var style1 = $("content").style;
+var style2 = $("testStyle").sheet.cssRules[0].style;
+
+var colors = [ "rgb(128, 128, 128)", "transparent" ]
+var i;
+
+for (i = 0; i < colors.length; ++i) {
+ var color = colors[i];
+ style1.color = color;
+ style2.color = color;
+ is(style1.color, color, "Inline style color roundtripping failed at color " + i);
+ is(style2.color, color, "Rule style color roundtripping failed at color " + i);
+}
+
+style1.color = "rgba(0, 0, 0, 0)";
+style2.color = "rgba(0, 0, 0, 0)";
+is(style1.color, "rgba(0, 0, 0, 0)",
+ "Inline style should round-trip black transparent color correctly");
+is(style2.color, "rgba(0, 0, 0, 0)",
+ "Rule style should round-trip black transparent color correctly");
+
+for (var i = 0; i <= 100; ++i) {
+ if (i == 70 || i == 90) {
+ // Tinderbox unhappy for some reason... just skip these for now?
+ continue;
+ }
+ var color1 = "rgba(128, 128, 128, " + i/100 + ")";
+ var color2 = "rgba(175, 63, 27, " + i/100 + ")";
+ style1.color = color1;
+ style1.backgroundColor = color2;
+ style2.color = color2;
+ style2.background = color1;
+
+ if (i == 100) {
+ // Bug 372783 means this doesn't round-trip quite right
+ todo(style1.color == color1,
+ "Inline style color roundtripping failed at opacity " + i);
+ todo(style1.backgroundColor == color2,
+ "Inline style background roundtripping failed at opacity " + i);
+ todo(style2.color == color2,
+ "Rule style color roundtripping failed at opacity " + i);
+ todo(style2.backgroundColor == color1,
+ "Rule style background roundtripping failed at opacity " + i);
+ color1 = "rgb(128, 128, 128)";
+ color2 = "rgb(175, 63, 27)";
+ }
+
+ is(style1.color, color1,
+ "Inline style color roundtripping failed at opacity " + i);
+ is(style1.backgroundColor, color2,
+ "Inline style background roundtripping failed at opacity " + i);
+ is(style2.color, color2,
+ "Rule style color roundtripping failed at opacity " + i);
+ is(style2.backgroundColor, color1,
+ "Rule style background roundtripping failed at opacity " + i);
+
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug373293.html b/layout/style/test/test_bug373293.html
new file mode 100644
index 0000000000..d23c865b67
--- /dev/null
+++ b/layout/style/test/test_bug373293.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=373293
+-->
+<head>
+ <title>Test for Bug 373293</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=373293">Mozilla Bug 373293</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="t" style="color: transparent;"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 373293**/
+
+var actual = $("t").getAttribute("style");
+var expected = "color: transparent;";
+is(actual, expected, "Expected style content did not match the actual style content");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug377947.html b/layout/style/test/test_bug377947.html
new file mode 100644
index 0000000000..88fccd0dc2
--- /dev/null
+++ b/layout/style/test/test_bug377947.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377947
+-->
+<head>
+ <title>Test for Bug 377947</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=377947">Mozilla Bug 377947</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 377947 **/
+
+/*
+ * In particular, test that CSSStyleDeclaration.getPropertyValue doesn't
+ * return values for shorthands when some of the subproperties are not
+ * specified (a change that wasn't all that related to the main point of
+ * the bug). And also test that the internal system-font property added
+ * in bug 377947 doesn't interfere with that.
+ */
+
+var s = document.getElementById("display").style;
+
+is(s.getPropertyValue("list-style"), "",
+ "list-style shorthand should start off empty");
+s.listStyleType="disc";
+s.listStyleImage="none";
+is(s.getPropertyValue("list-style"), "",
+ "list-style shorthand should be empty when some subproperties specified");
+s.listStylePosition="inside";
+isnot(s.getPropertyValue("list-style"), "",
+ "list-style shorthand should produce value when all subproperties set");
+s.removeProperty("list-style");
+is(s.getPropertyValue("list-style"), "",
+ "list-style shorthand be empty after removal");
+s.listStyle="none";
+isnot(s.getPropertyValue("list-style"), "",
+ "list-style shorthand should produce value when shorthand set");
+s.removeProperty("list-style");
+is(s.getPropertyValue("list-style"), "",
+ "list-style shorthand be empty after removal");
+
+is(s.getPropertyValue("font"), "",
+ "font shorthand should start off empty");
+var all_but_one = {
+ "font-family": "serif",
+ "font-style": "normal",
+ "font-variant": "normal",
+ "font-weight": "bold",
+ "font-size": "small",
+ "font-stretch": "normal",
+ "font-size-adjust": "none", // has to be default value
+ "font-feature-settings": "normal", // has to be default value
+ "font-variation-settings": "normal", // has to be default value
+ "font-language-override": "normal", // has to be default value
+ "font-kerning": "auto", // has to be default value
+ "font-optical-sizing": "auto", // has to be default value
+ "font-synthesis": "weight style", // has to be default value
+ "font-variant-alternates": "normal", // has to be default value
+ "font-variant-caps": "normal", // has to be default value
+ "font-variant-east-asian": "normal", // has to be default value
+ "font-variant-emoji": "auto", // has to be default value
+ "font-variant-ligatures": "normal", // has to be default value
+ "font-variant-numeric": "normal", // has to be default value
+ "font-variant-position": "normal" // has to be default value
+};
+
+for (var prop in all_but_one) {
+ s.setProperty(prop, all_but_one[prop], "");
+}
+is(s.getPropertyValue("font"), "",
+ "font shorthand should be empty when some subproperties specified");
+s.setProperty("line-height", "1.5", "");
+isnot(s.getPropertyValue("font"), "",
+ "font shorthand should produce value when all subproperties set");
+s.setProperty("font-size-adjust", "0.5", "");
+is(s.getPropertyValue("font"), "",
+ "font shorthand should be empty when font-size-adjust is non-default");
+s.setProperty("font-size-adjust", "none", "");
+isnot(s.getPropertyValue("font"), "",
+ "font shorthand should produce value when all subproperties set");
+s.removeProperty("font");
+is(s.getPropertyValue("font"), "",
+ "font shorthand be empty after removal");
+s.font="medium serif";
+isnot(s.getPropertyValue("font"), "",
+ "font shorthand should produce value when shorthand set");
+s.removeProperty("font");
+is(s.getPropertyValue("font"), "",
+ "font shorthand be empty after removal");
+s.font="menu";
+isnot(s.getPropertyValue("font"), "",
+ "font shorthand should produce value when shorthand (system font) set");
+s.removeProperty("font");
+is(s.getPropertyValue("font"), "",
+ "font shorthand be empty after removal");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug379440.html b/layout/style/test/test_bug379440.html
new file mode 100644
index 0000000000..56644f85cb
--- /dev/null
+++ b/layout/style/test/test_bug379440.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=379440
+-->
+<head>
+ <title>Test for Bug 379440</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ #display > * { cursor: auto }
+ #t1 {
+ cursor: url(file:///tmp/foo), url(file:///c|/),
+ url(http://example.com/), crosshair;
+ }
+ #t2 {
+ cursor: url(file:///tmp/foo), url(file:///c|/), crosshair;
+ }
+ #t3 {
+ cursor: url(http://example.com/), crosshair;
+ }
+ #t4 {
+ cursor: url(http://example.com/);
+ }
+ #t5 {
+ cursor: url(http://example.com/), no-such-cursor-exists;
+ }
+ #t6 {
+ cursor: crosshair;
+ }
+ #t7 {
+ cursor: no-such-cursor-exists;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=379440">Mozilla Bug 379440</a>
+<p id="display">
+ <div id="t1"> </div>
+ <div id="t2"></div>
+ <div id="t3"></div>
+ <div id="t4"></div>
+ <div id="t5"></div>
+ <div id="t6"></div>
+ <div id="t7"></div>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 379440 **/
+
+function cur(id) {
+ return document.defaultView.getComputedStyle($(id)).cursor;
+}
+
+is(cur("t1"), 'url("file:///tmp/foo"), url("file:///c:/"), ' +
+ 'url("http://example.com/"), crosshair',
+ "Serialize unloadable URLs using their specified value");
+is(cur("t2"), 'url("file:///tmp/foo"), url("file:///c:/"), crosshair',
+ "Serialize unloadable URLs using their specified value");
+is(cur("t3"), 'url("http://example.com/"), crosshair', "URI + fallback");
+is(cur("t4"), "auto", "Must have a fallback");
+is(cur("t5"), "auto", "Fallback must be recognized");
+is(cur("t6"), "crosshair", "Just a fallback");
+is(cur("t7"), "auto", "Invalid fallback means ignore");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug379741.html b/layout/style/test/test_bug379741.html
new file mode 100644
index 0000000000..7bb2463ce7
--- /dev/null
+++ b/layout/style/test/test_bug379741.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=379741
+-->
+<head>
+ <title>Test for Bug 379741</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=379741">Mozilla Bug 379741</a>
+<div id="content" style="display: none">
+<div id="noframe"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 379741 **/
+
+var cs = getComputedStyle(document.getElementById("noframe"), "");
+ok(cs.marginTop == "0" || cs.marginTop == "0px",
+ "computed margin-top is not none");
+ok(cs.marginRight == "0" || cs.marginRight == "0px",
+ "computed margin-right is not none");
+ok(cs.marginBottom == "0" || cs.marginBottom == "0px",
+ "computed margin-bottom is not none");
+ok(cs.marginLeft == "0" || cs.marginLeft == "0px",
+ "computed margin-left is not none");
+ok(cs.paddingTop == "0" || cs.paddingTop == "0px",
+ "computed padding-top is not none");
+ok(cs.paddingRight == "0" || cs.paddingRight == "0px",
+ "computed padding-right is not none");
+ok(cs.paddingBottom == "0" || cs.paddingBottom == "0px",
+ "computed padding-bottom is not none");
+ok(cs.paddingLeft == "0" || cs.paddingLeft == "0px",
+ "computed padding-left is not none");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug382027.html b/layout/style/test/test_bug382027.html
new file mode 100644
index 0000000000..795a17048a
--- /dev/null
+++ b/layout/style/test/test_bug382027.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=382027
+-->
+<head>
+ <title>Test for Bug 382027</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=382027">Mozilla Bug 382027</a>
+<div id="content" style="display: none;"
+ ><div style="border-style: groove none none;"
+ ><div style="border-style: none none double"
+ ></div
+ ></div
+></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/*
+ * Test that the regression in the original patch checked in by bug
+ * 382027 is caught by something other than an unexpected pass.
+ */
+
+var e = document.createElement("div");
+e.style.setProperty("border-style", "groove none none none", "");
+is(e.getAttribute("style"), "border-style: groove none none;");
+e.style.setProperty("border-style", "none none double none", "");
+is(e.getAttribute("style"), "border-style: none none double;");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug383075.html b/layout/style/test/test_bug383075.html
new file mode 100644
index 0000000000..8d902103e9
--- /dev/null
+++ b/layout/style/test/test_bug383075.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=383075
+-->
+<head>
+ <title>Test for bug 383075</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style type="text/css">
+
+ html,body {
+ color:black; background-color:white; font-size:16px; font-family: Arial;
+ }
+
+
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=383075">Mozilla bug 383075</a>
+<p id="display">
+
+The X'es below should have the same size:<br>
+
+<span id="a1" style="font-size:72px;">X</span>
+<span id="a2" style="font-size:72px;">X</span>
+<span id="a3" style="font-size:72px;">X</span>
+<span id="a4" style="font-size:72px;">X</span>
+<span id="a5" style="font-size:72px;">X</span>
+<span id="a6" style="font-size:72px;">X</span>
+<span id="a7" style="font-size:24px;">X</span>
+<span id="a8" style="font-size:72px;">X</span>
+<span id="a9" style="font:24px Arial;">X</span>
+
+<br>
+
+<span id="b1" style="font-size:72px;">X</span>
+<span id="b2" style="font-size:72px;">X</span>
+<span id="b3" style="font-size:72px;">X</span>
+<span id="b4" style="font-size:72px;">X</span>
+<span id="b5" style="font-size:72px;">X</span>
+<span id="b6" style="font-size:72px;">X</span>
+<span id="b7" style="font-size:24px;">X</span>
+<span id="b8" style="font-size:72px;">X</span>
+<span id="b9" style="font:24px Arial;">X</span>
+</p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ document.getElementById("a1").style.fontSize = "illegal";
+ document.getElementById("a2").style.fontSize = "24px;";
+ document.getElementById("a3").style.fontSize = "24px; font-size-adjust:2";
+ document.getElementById("a4").style.fontSize = ";";
+ document.getElementById("a5").style.font = "24px Arial;";
+ document.getElementById("a6").style.font = "24px;";
+ document.getElementById("a7").style.fontSize = " 72px "; // correct
+ document.getElementById("a8").style.font = ";";
+ document.getElementById("a9").style.font = " 72px Arial "; // correct
+
+ document.getElementById("b1").style.setProperty("font-size", "illegal", null);
+ document.getElementById("b2").style.setProperty("font-size", "24px;", null);
+ document.getElementById("b3").style.setProperty("font-size", "24px; font-size-adjust:2", null);
+ document.getElementById("b4").style.setProperty("font-size", ";", null);
+ document.getElementById("b5").style.setProperty("font", "24px Arial;", null);
+ document.getElementById("b6").style.setProperty("font", "24px;", null);
+ document.getElementById("b7").style.setProperty("font-size", " 72px ", null); // correct
+ document.getElementById("b8").style.setProperty("font", ";", null);
+ document.getElementById("b9").style.setProperty("font", " 72px Arial ", null); // correct
+
+
+for (i=1; i <= 9; ++i)
+ is($('a'+i).style.fontSize, '72px', "font size");
+
+for (i=1; i <= 9; ++i)
+ is($('b'+i).style.fontSize, '72px', "font size");
+
+
+</script>
+</pre>
+
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug387615.html b/layout/style/test/test_bug387615.html
new file mode 100644
index 0000000000..eec7109289
--- /dev/null
+++ b/layout/style/test/test_bug387615.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=387615
+-->
+<head>
+ <title>Test for Bug 387615</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ @namespace html url(http://www.w3.org/1999/xhtml);
+ a { color: red; }
+ a[rel="next"] { color: green; }
+ a[html|rel="next"] { color: green; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=387615">Mozilla Bug 387615</a>
+<p id="display"><a>link</a></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 387615 **/
+
+var htmlns = "http://www.w3.org/1999/xhtml";
+
+var a = document.getElementById("display").firstChild;
+
+function col(elt) { return getComputedStyle(elt, "").color; }
+
+var red_cs = col(a);
+a.setAttribute("rel", "next");
+var green_cs = col(a);
+isnot(green_cs, red_cs, "computed values for red and green are different");
+
+a.setAttribute("rel", "NEXT");
+is(col(a), green_cs, "rel attribute should match case insensitively");
+
+a.removeAttribute("rel");
+a.setAttributeNS(htmlns, "html:rel", "next");
+is(col(a), green_cs, "html:rel attribute should match case-sensitively");
+
+a.setAttributeNS(htmlns, "html:rel", "NEXT");
+is(col(a), red_cs, "html:rel attribute should not match case-insensitively");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug389464.html b/layout/style/test/test_bug389464.html
new file mode 100644
index 0000000000..2e05ed848f
--- /dev/null
+++ b/layout/style/test/test_bug389464.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <!-- above is to force x-western language group -->
+ <title>Test for preference not to use document colors</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=58048">Mozilla Bug 58048</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=255411">Mozilla Bug 255411</a>
+<div id="display">
+
+<pre><font id="one" size="-1">text</font></pre>
+<p><font id="two" size="-1">text</font></p>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var cs1 = getComputedStyle(document.getElementById("one"), "");
+var cs2 = getComputedStyle(document.getElementById("two"), "");
+
+SpecialPowers.pushPrefEnv({'set': [['variable.x-western', 25], ['fixed.x-western', 20]]}, part1);
+
+function part1()
+{
+ var fs1 = cs1.fontSize.match(/(.*)px/)[1];
+ var fs2 = cs2.fontSize.match(/(.*)px/)[1];
+ ok(fs1 < fs2, "<font size=-1> shrinks relative to font-family: -moz-fixed");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug391034.html b/layout/style/test/test_bug391034.html
new file mode 100644
index 0000000000..f9544035b1
--- /dev/null
+++ b/layout/style/test/test_bug391034.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=391034
+-->
+<head>
+ <title>Test for Bug 391034</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391034">Mozilla Bug 391034</a>
+<div id="display" style="width: 90px; height: 80px">
+ <div id="width-ref" style="width: 2ch"></div>
+ <div id="width-ref2" style="width: 5ch"></div>
+ <div id="one" style="position: relative; left: 2ch; bottom: 5ch"></div>
+ <div id="two" style="position: relative; left: 10%; bottom: 20%"></div>
+ <div id="three" style="position: relative; left: 10px; bottom: 6px"></div>
+</div>
+<div id="content" style="display: none">
+ <div id="four" style="position: relative; left: 10%; bottom: 20%"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 391034 **/
+function getComp(id) {
+ return document.defaultView.getComputedStyle($(id));
+}
+
+is(getComp("one").top, "-" + getComp("width-ref2").width,
+ "Incorrect computed top offset if specified in ch")
+is(getComp("one").right, "-" + getComp("width-ref").width,
+ "Incorrect computed right offset if specified in ch")
+is(getComp("one").bottom, getComp("width-ref2").width,
+ "Incorrect computed bottom offset if specified in ch")
+is(getComp("one").left, getComp("width-ref").width,
+ "Incorrect computed left offset if specified in ch")
+
+is(getComp("two").top, "-16px",
+ "Incorrect computed top offset if specified in %")
+is(getComp("two").right, "-9px",
+ "Incorrect computed right offset if specified in %")
+is(getComp("two").bottom, "16px",
+ "Incorrect computed bottom offset if specified in %")
+is(getComp("two").left, "9px",
+ "Incorrect computed left offset if specified in %")
+
+is(getComp("three").top, "-6px",
+ "Incorrect computed top offset if specified in %")
+is(getComp("three").right, "-10px",
+ "Incorrect computed right offset if specified in %")
+is(getComp("three").bottom, "6px",
+ "Incorrect computed bottom offset if specified in %")
+is(getComp("three").left, "10px",
+ "Incorrect computed left offset if specified in %")
+
+is(getComp("four").top, "auto",
+ "Incorrect undisplayed computed top offset if specified in %")
+is(getComp("four").right, "auto",
+ "Incorrect undisplayed computed right offset if specified in %")
+is(getComp("four").bottom, "20%",
+ "Incorrect undisplayed computed bottom offset if specified in %")
+is(getComp("four").left, "10%",
+ "Incorrect undisplayed computed left offset if specified in %")
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug391221.html b/layout/style/test/test_bug391221.html
new file mode 100644
index 0000000000..bf78711197
--- /dev/null
+++ b/layout/style/test/test_bug391221.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=391221
+-->
+<head>
+ <title>Test for Bug 391221</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391221">Mozilla Bug 391221</a>
+<p id="display">
+ <div id="width-ref" style="width: 2ch"></div>
+</p>
+<div id="content">
+
+<div id="one" style="width: 1000px; max-width: 2ch"></div>
+<div id="two" style="width: 0px; min-width: 2ch"></div>
+<div id="three" style="width: 1000ch; max-width: 2px"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 391221 **/
+function getComp(id) {
+ return document.defaultView.getComputedStyle($(id));
+}
+
+is(getComp("one").width, getComp("width-ref").width,
+ "max-width in ch units not working?");
+
+is(getComp("two").width, getComp("width-ref").width,
+ "min-width in ch units not working?");
+
+is(getComp("three").width, "2px", "max-width not applied to width in chars?");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug397427.html b/layout/style/test/test_bug397427.html
new file mode 100644
index 0000000000..ff0e71f238
--- /dev/null
+++ b/layout/style/test/test_bug397427.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=397427
+-->
+<head>
+ <title>Test for Bug 397427</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style id="a">
+ @import url("redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-1.css");
+ @import url("redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-2.css");
+ .test { color: red }
+ </style>
+ <link id="b" rel="stylesheet" href="http://example.com">
+ <link id="c" rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-2.css">
+ <link id="d" rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-3.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=397427">Mozilla Bug 397427</a>
+<p id="display">
+<span id="one" class="test"></span>
+<span id="two" class="test"></span>
+<span id="three" class="test"></span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 397427 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is($("a").sheet.href, null, "href should be null");
+ is(typeof($("a").sheet.href), "object", "should be actual null");
+
+ // Make sure the redirected sheets are loaded and have the right base URI
+ is(document.defaultView.getComputedStyle($("one")).color,
+ "rgb(0, 128, 0)", "Redirect 1 did not work");
+ is(document.defaultView.getComputedStyle($("one")).backgroundImage,
+ "url(\"http://example.org/tests/layout/style/test/post-redirect-1.css?1\")",
+ "Redirect 1 did not get right base URI");
+ is(document.defaultView.getComputedStyle($("two")).color,
+ "rgb(0, 128, 0)", "Redirect 2 did not work");
+ is(document.defaultView.getComputedStyle($("two")).backgroundImage,
+ "url(\"http://example.org/tests/layout/style/test/post-redirect-2.css?1\")",
+ "Redirect 2 did not get right base URI");
+ is(document.defaultView.getComputedStyle($("three")).color,
+ "rgb(0, 128, 0)", "Redirect 3 did not work");
+ is(document.defaultView.getComputedStyle($("three")).backgroundImage,
+ "url(\"http://example.org/tests/layout/style/test/post-redirect-3.css?1\")",
+ "Redirect 3 did not get right base URI");
+
+ var ruleList = $("a").sheet.cssRules;
+
+ var redirHrefBase =
+ window.location.href.replace(/test_bug397427.html$/,
+ "redirect.sjs?http://example.org/tests/layout/style/test/post-");
+
+ is(ruleList[0].styleSheet.href, redirHrefBase + "redirect-1.css",
+ "Unexpected href for imported sheet");
+ todo_is(ruleList[0].href, redirHrefBase + "redirect-1.css",
+ "Rule href should be absolute");
+ is(ruleList[1].styleSheet.href, redirHrefBase + "redirect-2.css",
+ "Unexpected href for imported sheet");
+ todo_is(ruleList[1].href, redirHrefBase + "redirect-2.css",
+ "Rule href should be absolute");
+
+ is($("b").href, "http://example.com/", "Unexpected href one");
+ is($("b").href, $("b").sheet.href,
+ "Should have the same href when not redirecting");
+
+ is($("c").href, redirHrefBase + "redirect-2.css",
+ "Unexpected href two");
+ is($("c").href, $("c").sheet.href,
+ "Should have the same href when redirecting");
+
+ is($("d").href, redirHrefBase + "redirect-3.css",
+ "Unexpected href three");
+ is($("d").href, $("d").sheet.href,
+ "Should have the same href when redirecting again");
+})
+
+addLoadEvent(SimpleTest.finish);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug399349.html b/layout/style/test/test_bug399349.html
new file mode 100644
index 0000000000..a36451927f
--- /dev/null
+++ b/layout/style/test/test_bug399349.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=363146
+-->
+<head>
+ <title>Test for Bug 363146</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=399349">Mozilla Bug 399349</a>
+
+<!-- Test parsing of integer numbers -->
+<div id="Aone" style="width:100px; height:400px; top:-100px; left: -200px;position:relative;"></div>
+
+<!-- Test parsing of float numbers -->
+<div id="Atwo" style="width:150.2px; height:450.25px; top:-150.2px; left: -450.25px;position:relative;"></div>
+<div id="Athree" style="width:.1px; height:0.3px; top:-0.1px; left:-0.3px;position:relative;"></div>
+<div id="Afour" style="width:+100.017px; height:+400.017px; top:-.117px; left: -.217px;position:relative;"></div>
+
+<!-- Test parsing of long fractions -->
+<div id="Afive" style="width:+2345.0000000000000000000000000000000000001px; height:+456.000000000000000000000000000001px;
+ top:-2123.000000000000000000000000000000000001px; left:-6543.99999999999999999999999999999999px;
+ position:relative;"></div>
+
+<!-- Force parsing of long numbers (>9 digits), if they are zero's. Note css itself can't handle large numers -->
+<div id="Asix" style="width:+000000000012px; height:+000000000037.456788px;
+ top:-000000000023px; left:-000000000044.456788px;
+ position:relative;"></div>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var a1 = window.getComputedStyle(document.getElementById("Aone"));
+is(a1.width, "100px");
+is(a1.height, "400px");
+is(a1.top, "-100px");
+is(a1.left, "-200px");
+
+var a2 = window.getComputedStyle(document.getElementById("Atwo"));
+is(a2.width, "150.2px");
+is(a2.height, "450.25px");
+is(a2.top, "-150.2px");
+is(a2.left, "-450.25px");
+
+var a3 = window.getComputedStyle(document.getElementById("Athree"));
+is(a3.width, "0.1px");
+is(a3.height, "0.3px");
+is(a3.top, "-0.1px");
+is(a3.left, "-0.3px");
+
+var a4 = window.getComputedStyle(document.getElementById("Afour"));
+is(a4.width, "100.017px");
+is(a4.height, "400.017px");
+is(a4.top, "-0.117px");
+is(a4.left, "-0.217px");
+
+var a5 = window.getComputedStyle(document.getElementById("Afive"));
+is(a5.width, "2345px");
+is(a5.height, "456px");
+is(a5.top, "-2123px");
+is(a5.left, "-6544px");
+
+var a6 = window.getComputedStyle(document.getElementById("Asix"));
+is(a6.width, "12px");
+is(a6.height, "37.45px");
+is(a6.top, "-23px");
+is(a6.left, "-44.4568px");
+
+</script>
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug401046.html b/layout/style/test/test_bug401046.html
new file mode 100644
index 0000000000..e4492a5ca8
--- /dev/null
+++ b/layout/style/test/test_bug401046.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=401046
+-->
+<head>
+ <title>Test for Bug 401046</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ #display span { margin-bottom: 1em; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=401046">Mozilla Bug 401046</a>
+<p id="display" lang="zh-Hans">
+ <span id="s0" style="font-size: 0">汉字</span>
+ <span id="s4" style="font-size: 4px">汉字</span>
+ <span id="s12" style="font-size: 12px">汉字</span>
+ <span id="s28" style="font-size: 28px">汉字</span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 401046 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var elts = [
+ document.getElementById("s0"),
+ document.getElementById("s4"),
+ document.getElementById("s12"),
+ document.getElementById("s28")
+];
+
+function fs(idx) {
+ // The computed font size actually *doesn't* currently reflect the
+ // minimum font size preference, but things in em units do. Not sure
+ // if this is how it ought to be...
+ return getComputedStyle(elts[idx], "").marginBottom;
+}
+
+SpecialPowers.pushPrefEnv({'clear': [['font.minimum-size.zh-CN']]}, step1);
+
+function step1() {
+ is(fs(0), "0px", "at min font size 0, 0px should compute to 0px");
+ is(fs(1), "4px", "at min font size 0, 4px should compute to 4px");
+ is(fs(2), "12px", "at min font size 0, 12px should compute to 12px");
+ is(fs(3), "28px", "at min font size 0, 28px should compute to 28px");
+
+ SpecialPowers.pushPrefEnv({'set': [['font.minimum-size.zh-CN', 7]]}, step2);
+}
+
+function step2() {
+ is(fs(0), "0px", "at min font size 7, 0px should compute to 0px");
+ is(fs(1), "7px", "at min font size 7, 4px should compute to 7px");
+ is(fs(2), "12px", "at min font size 7, 12px should compute to 12px");
+ is(fs(3), "28px", "at min font size 7, 28px should compute to 28px");
+
+ SpecialPowers.pushPrefEnv({'set': [['font.minimum-size.zh-CN', 18]]}, step3);
+}
+
+function step3() {
+ is(fs(0), "0px", "at min font size 18, 0px should compute to 0px");
+ is(fs(1), "18px", "at min font size 18, 4px should compute to 18px");
+ is(fs(2), "18px", "at min font size 18, 12px should compute to 18px");
+ is(fs(3), "28px", "at min font size 18, 28px should compute to 28px");
+
+ SpecialPowers.pushPrefEnv({'clear': [['font.minimum-size.zh-CN']]}, SimpleTest.finish);
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug405818.html b/layout/style/test/test_bug405818.html
new file mode 100644
index 0000000000..eff4da449b
--- /dev/null
+++ b/layout/style/test/test_bug405818.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=405818
+-->
+<head>
+ <title>Test for Bug 405818</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <link rel="stylesheet" type="text/css" href="data:text/css,%23myDiv{color:green;}">
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin/popup.css">
+ <!-- Script to make sure sheets gets a chance to load fully in Gecko 1.8 and earlier -->
+ <script type="text/javascript" src="data:text/javascript,"></script>
+ <link rel="stylesheet" type="text/css" href="data:text/css,%23myDiv{color:green;}">
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin/popup.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=405818">Mozilla Bug 405818</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="myDiv"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 405818 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is(document.styleSheets[1].href,
+ "data:text/css,%23myDiv{color:green;}",
+ "Unexpected href for linked sheet before cloning");
+ is(document.styleSheets[3].href,
+ "data:text/css,%23myDiv{color:green;}",
+ "Unexpected href for later linked sheet before cloning");
+
+ is(document.styleSheets[2].href,
+ "chrome://global/skin/popup.css",
+ "Unexpected href for linked chrome sheet before cloning");
+ is(document.styleSheets[4].href,
+ "chrome://global/skin/popup.css",
+ "Unexpected href for later linked chrome sheet before cloning");
+
+ // Force cloning of inners
+ document.styleSheets[1].cssRules[0];
+ SpecialPowers.wrap(document.styleSheets[2]).cssRules[0];
+
+ is(document.styleSheets[1].href,
+ "data:text/css,%23myDiv{color:green;}",
+ "Unexpected href for linked sheet after cloning");
+ is(document.styleSheets[3].href,
+ "data:text/css,%23myDiv{color:green;}",
+ "Unexpected href for later linked sheet after cloning");
+
+ is(document.styleSheets[2].href,
+ "chrome://global/skin/popup.css",
+ "Unexpected href for linked chrome sheet after cloning");
+ is(document.styleSheets[4].href,
+ "chrome://global/skin/popup.css",
+ "Unexpected href for later linked chrome sheet after cloning");
+
+ var myDiv = document.getElementById("myDiv");
+ is(getComputedStyle(myDiv, "").color, "rgb(0, 128, 0)",
+ "Unexpected color for div (data URI stylesheet not being honored?)");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug412901.html b/layout/style/test/test_bug412901.html
new file mode 100644
index 0000000000..fb37be57f7
--- /dev/null
+++ b/layout/style/test/test_bug412901.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=412901
+-->
+<head>
+ <title>Test for Bug 412901</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=412901">Mozilla Bug 412901</a>
+<div id="testDiv" style="width:20px; height:20px; border:solid silver; border-left-width:0.2px; border-right-width:0px; border-top-width:2.3px; border-bottom-width:5.5px;">
+<div id="testDiv2" style="width:20px; height:20px; border:hidden solid silver; border-left-width:0.2px; border-right-width:0px; border-top-width:2.3px; border-bottom-width:5.5px;">
+<p id="display"></p>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 412901 **/
+
+var div = document.getElementById("testDiv");
+var computedStyle = document.defaultView.getComputedStyle(div);
+// we never round down to 0px, very small widths are rounded up to 1px
+is(computedStyle.borderLeftWidth, "1px");
+is(computedStyle.borderRightWidth, "0px");
+is(computedStyle.borderTopWidth, "2px");
+is(computedStyle.borderBottomWidth, "5px");
+
+var div2 = document.getElementById("testDiv2");
+var computedStyle2 = document.defaultView.getComputedStyle(div2);
+is(computedStyle2.borderLeftWidth, "0px");
+is(computedStyle2.borderRightWidth, "0px");
+is(computedStyle2.borderTopWidth, "0px");
+is(computedStyle2.borderBottomWidth, "0px");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug413958.html b/layout/style/test/test_bug413958.html
new file mode 100644
index 0000000000..0b48e3aa68
--- /dev/null
+++ b/layout/style/test/test_bug413958.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=413958
+-->
+<head>
+ <title>Test for Bug 413958</title>
+ <meta charset="UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<style>span { color: red }</style><!-- backstop -->
+<p><a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=413958"
+ >Mozilla Bug 413958</a>. All text below should be black on white.</p>
+<p>Sheet: <span id="s1">1</span>
+ <span id="s2">2</span>
+ <span id="s3">3</span>.
+ Style attr: <span id="setStyle">4</span>.
+ Properties: <span id="setStyleProp" style="">5</span>.</p>
+<script>
+SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true;
+
+var tests = [
+ function() {
+ var s = document.createTextNode(
+"#s1{nosuchprop:auto; color:black}\n"+
+"#s2{nosuchprop:auto; color:black}invalid?sel{}#s3{color:black}"),
+ e = document.createElement("style");
+ e.appendChild(s);
+ document.body.appendChild(e);
+ },
+ function() {
+ document.getElementById("setStyle")
+ .setAttribute("style", "width:200;color:black");
+ },
+ function() {
+ var s = document.getElementById("setStyleProp").style;
+ s.width = "200";
+ s.color = "black";
+ },
+];
+var results = [
+ [ { errorMessage: /Unknown property \u2018nosuchprop\u2019/,
+ lineNumber: 1, columnNumber: 16, sourceLine: "", cssSelectors: "#s1" },
+ { errorMessage: /Unknown property \u2018nosuchprop\u2019/,
+ lineNumber: 2, columnNumber: 16, sourceLine: "", cssSelectors: "#s2" },
+ { errorMessage: /Ruleset ignored due to bad selector/,
+ lineNumber: 2, columnNumber: 41, sourceLine: "", cssSelectors: "" } ],
+ [ { errorMessage: /parsing value for \u2018width\u2019/,
+ lineNumber: 1, columnNumber: 7, sourceLine: "", cssSelectors: "" } ],
+ [ { errorMessage: /parsing value for \u2018width\u2019/,
+ lineNumber: 1, columnNumber: 1, sourceLine: "", cssSelectors: "" } ],
+];
+var curTest = -1;
+
+function doTest() {
+ if (++curTest == tests.length) {
+ var ss = document.getElementsByTagName("span");
+ for (var i = 0; i < ss.length; i++) {
+ is(window.getComputedStyle(ss[i]).color, "rgb(0, 0, 0)",
+ "recovery | " + ss[i].id);
+ }
+ SimpleTest.finish();
+ } else {
+ SimpleTest.expectConsoleMessages(tests[curTest], results[curTest], doTest);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+doTest();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_bug418986-2.html b/layout/style/test/test_bug418986-2.html
new file mode 100644
index 0000000000..04443a3553
--- /dev/null
+++ b/layout/style/test/test_bug418986-2.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418986
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2/3 for Bug #418986: Resist fingerprinting by preventing exposure of screen and system info</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="test-css"></style>
+ <script type="text/javascript" src="chrome/bug418986-2.js"></script>
+ <script type="text/javascript">
+ // Run all tests now.
+ window.onload = function () {
+ add_task(async function() {
+ await test(true);
+ });
+ };
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
+<p id="display">TEST</p>
+<div id="content" style="display: none">
+
+</div>
+<p id="pictures"></p>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug437915.html b/layout/style/test/test_bug437915.html
new file mode 100644
index 0000000000..fb6830dd57
--- /dev/null
+++ b/layout/style/test/test_bug437915.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=437915
+-->
+<head>
+ <title>Test for Bug 437915</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ div.classvalue { text-decoration: underline; }
+ div[title~="titlevalue"] { visibility: hidden; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437915">Mozilla Bug 437915</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 437915 **/
+
+var div = document.getElementById("content");
+var cs = document.defaultView.getComputedStyle(div);
+
+var chars = {
+ 0x09: true, // tab
+ 0x0a: true, // newline
+ 0x0b: false, // vertical tab (MAY CHANGE IN FUTURE!)
+ 0x0c: true, // form feed
+ 0x0d: true, // carriage return
+ 0x0e: false,
+ 0x20: true, // space
+ 0x2003: false,
+ 0x200b: false,
+ 0x2028: false,
+ 0x2029: false,
+ 0x3000: false
+};
+
+var wsmap = {
+ false: { str: " NOT", "text-decoration-line": "none", "visibility": "visible" },
+ true: { str: "", "text-decoration-line": "underline", "visibility": "hidden" }
+};
+
+for (var char in chars) {
+ var is_whitespace = chars[char];
+ var mapent = wsmap[is_whitespace];
+ div.setAttribute("class", "classvalue" + String.fromCharCode(char) + "b")
+ div.setAttribute("title", "a" + String.fromCharCode(char) + "titlevalue")
+ for (var prop of ["text-decoration-line", "visibility"]) {
+ is(cs.getPropertyValue(prop), mapent[prop],
+ "Character " + char + " should" + mapent.str +
+ " be treated as whitespace ("
+ + prop + " should be " + mapent[prop] + ")");
+ }
+}
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug450191.html b/layout/style/test/test_bug450191.html
new file mode 100644
index 0000000000..91488e9496
--- /dev/null
+++ b/layout/style/test/test_bug450191.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=450191
+-->
+<head>
+ <title>Test for Bug 450191</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450191">Mozilla Bug 450191</a>
+<iframe id="display" src="about:blank"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 450191 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var iframe = document.getElementById("display");
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+
+ var doctext = "<div style='font-size: 2em'>div text <table><tr><td id='t'>table text</td></tr></table></div>";
+
+ function subdoc_body_font() {
+ return subwin.getComputedStyle(subdoc.body).fontSize;
+ }
+
+ function subdoc_cell_font() {
+ return subwin.getComputedStyle(subdoc.getElementById("t")).fontSize;
+ }
+
+ subdoc.open();
+ subdoc.write(doctext);
+ subdoc.close();
+
+ is(subdoc_cell_font(), subdoc_body_font(),
+ "Quirks style sheet should be applied.");
+
+ subdoc.open();
+ subdoc.write("<!DOCTYPE HTML>" + doctext);
+ subdoc.close();
+
+ isnot(subdoc_cell_font(), subdoc_body_font(),
+ "Quirks style sheet should NOT be applied.");
+
+ subdoc.open();
+ subdoc.write(doctext);
+ subdoc.close();
+
+ is(subdoc_cell_font(), subdoc_body_font(),
+ "Quirks style sheet should be applied.");
+
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug470769.html b/layout/style/test/test_bug470769.html
new file mode 100644
index 0000000000..589cf790b0
--- /dev/null
+++ b/layout/style/test/test_bug470769.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=470769
+-->
+<head>
+ <title>Test for Bug 470769</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=470769">Mozilla Bug 470769</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 470769 **/
+
+var e = document.getElementById("display");
+e.setAttribute("style", "z-index: 2147483647"); // maximum signed 32-bit
+is(e.style.zIndex, "2147483647", "element.style should roundtrip correctly");
+is(window.getComputedStyle(e).zIndex, "2147483647",
+ "getComputedStyle should roundtrip correctly");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug499655.html b/layout/style/test/test_bug499655.html
new file mode 100644
index 0000000000..37ad553206
--- /dev/null
+++ b/layout/style/test/test_bug499655.html
@@ -0,0 +1,45 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=499655
+-->
+<head>
+ <title>Test for Bug 499655</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499655">Mozilla Bug 499655</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 499655 **/
+
+test1 = document.createElementNS("http://www.w3.org/1999/xhtml","test");
+test2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst");
+test3 = document.createElementNS("test","test");
+test4 = document.createElementNS("test","TEst");
+
+content = document.getElementById("content");
+
+content.appendChild(test1);
+content.appendChild(test2);
+content.appendChild(test3);
+content.appendChild(test4);
+
+list = document.querySelectorAll('test');
+is(list.length, 2, "Number of elements found");
+is(list[0], test1, "First element didn't match");
+is(list[1], test3, "Third element didn't match");
+
+list = document.querySelectorAll('TEst');
+is(list.length, 2, "Wrong number of elements found");
+is(list[0], test1, "First element didn't match");
+is(list[1], test4, "Fourth element didn't match");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug499655.xhtml b/layout/style/test/test_bug499655.xhtml
new file mode 100644
index 0000000000..b398d33967
--- /dev/null
+++ b/layout/style/test/test_bug499655.xhtml
@@ -0,0 +1,48 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=499655
+-->
+<head>
+ <title>Test for Bug 499655</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499655">Mozilla Bug 499655</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+test1 = document.createElementNS("http://www.w3.org/1999/xhtml","test");
+test2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst");
+test3 = document.createElementNS("test","test");
+test4 = document.createElementNS("test","TEst");
+
+content = document.getElementById("content");
+
+content.appendChild(test1);
+content.appendChild(test2);
+content.appendChild(test3);
+content.appendChild(test4);
+
+
+list = document.querySelectorAll('test');
+is(list.length, 2, "Number of elements found");
+is(list[0], test1, "First element didn't match");
+is(list[1] , test3, "Third element didn't match");
+
+list = document.querySelectorAll('TEst');
+is(list.length, 2, "Number of elements found");
+is(list[0], test2, "Second element didn't match");
+is(list[1], test4, "Fourth element didn't match");
+
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug517224.html b/layout/style/test/test_bug517224.html
new file mode 100644
index 0000000000..83d2bb8d80
--- /dev/null
+++ b/layout/style/test/test_bug517224.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=517224
+-->
+<head>
+ <title>Test for Bug 517224</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/ecmascript" src="bug517224.sjs?reset"></script>
+ <style type="text/css">
+
+ p#display { background: url(bug517224.sjs?image); }
+ p#display { background-image: none; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=517224">Mozilla Bug 517224</a>
+<p id="display">Element with overridden background</p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 517224 **/
+
+// Test that we don't load background images for style rules that are
+// always overridden.
+
+// Make sure the style has been computed
+var bi =
+ getComputedStyle(document.getElementById('display'), "").backgroundImage;
+SimpleTest.waitForExplicitFinish();
+window.onload = run;
+
+function run()
+{
+ var script = document.createElement("script");
+ script.setAttribute("src", "bug517224.sjs?result");
+ document.body.appendChild(script);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug524175.html b/layout/style/test/test_bug524175.html
new file mode 100644
index 0000000000..5a57b61ba2
--- /dev/null
+++ b/layout/style/test/test_bug524175.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=524175
+-->
+<head>
+ <title>Test for Bug 524175</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ span::before ::before {}
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=524175">Mozilla Bug 524175</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 524175 **/
+is(document.styleSheets[1].cssRules.length, 0, "Shouldn't have parsed that rule");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug525952.html b/layout/style/test/test_bug525952.html
new file mode 100644
index 0000000000..d3ad02419d
--- /dev/null
+++ b/layout/style/test/test_bug525952.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=525952
+-->
+<head>
+ <title>Test for Bug 525952</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525952">Mozilla Bug 525952</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 525952 **/
+var bodies = document.querySelectorAll("::before, div::before, body");
+is(bodies.length, 1, "Unexpected length");
+is(bodies[0], document.body, "Unexpected element");
+
+is(document.querySelector("div > ::after, body"), document.body,
+ "Unexpected return value");
+
+var emptyList = document.querySelectorAll("::before, div::before");
+is(emptyList.length, 0, "Unexpected empty list length");
+
+is(document.querySelectorAll("div > ::after").length, 0,
+ "Pseudo-element matched something?");
+
+is(document.body.matches("::first-line"), false,
+ "body shouldn't match ::first-line");
+
+is(document.body.matches("::first-line, body"), true,
+ "body should match 'body'");
+
+is(document.body.matches("::first-line, body, ::first-letter"), true,
+ "body should match 'body' here too");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug534804.html b/layout/style/test/test_bug534804.html
new file mode 100644
index 0000000000..0b60e6d89c
--- /dev/null
+++ b/layout/style/test/test_bug534804.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=534804
+-->
+<head>
+ <title>Test for Bug 534804</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css" id="styleone"> </style>
+ <style type="text/css" id="styletwo"> </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=534804">Mozilla Bug 534804</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 534804 **/
+
+var styleone = document.getElementById("styleone");
+var styletwo = document.getElementById("styletwo");
+var display = document.getElementById("display");
+
+run1();
+styletwo.firstChild.data = "#e > span:nth-child(2n+1) { color: green }";
+run1();
+styletwo.firstChild.data = "#e > span:first-child { color: green }";
+run1();
+styletwo.firstChild.data = "#e > span:nth-last-child(2n+1) { color: green }";
+run1();
+styletwo.firstChild.data = "#e > span:last-child { color: green }";
+run1();
+
+function run1()
+{
+ function identity(bool) { return bool; }
+ function inverse(bool) { return !bool; }
+ function always_false(bool) { return false; }
+ run2("#e:empty + span", identity, always_false);
+ run2("#e:empty ~ span", identity, identity);
+ run2("#e:not(:empty) + span", inverse, always_false);
+ run2("#e:not(:empty) ~ span", inverse, inverse);
+}
+
+function run2(sel, next_sibling_rule, later_sibling_rule)
+{
+ styleone.firstChild.data = sel + " { text-decoration: underline }";
+
+ // Rebuild the subtree every time.
+ var span1 = document.createElement("span");
+ span1.id = "e";
+ var span2 = document.createElement("span");
+ var span3 = document.createElement("span");
+ display.appendChild(span1);
+ display.appendChild(span2);
+ display.appendChild(span3);
+
+ function td(e) { return getComputedStyle(e, "").textDecorationLine; }
+
+ function check(desc, isempty) {
+ is(td(span2), next_sibling_rule(isempty) ? "underline" : "none",
+ "match of next sibling in state " + desc);
+ is(td(span3), later_sibling_rule(isempty) ? "underline" : "none",
+ "match of next sibling in state " + desc);
+ }
+
+ check("initially empty", true);
+ var kid = document.createElement("span");
+ span1.appendChild(kid);
+ check("after append", false);
+ span1.removeChild(kid);
+ check("after remove", true);
+ span1.appendChild(document.createTextNode(""));
+ span1.appendChild(document.createComment("a comment"));
+ span1.appendChild(document.createTextNode(""));
+ check("after append of insignificant children", true);
+ span1.insertBefore(kid, span1.childNodes[1]);
+ check("after insert", false);
+
+ display.removeChild(span1);
+ display.removeChild(span2);
+ display.removeChild(span3);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug573255.html b/layout/style/test/test_bug573255.html
new file mode 100644
index 0000000000..182087e1ec
--- /dev/null
+++ b/layout/style/test/test_bug573255.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=573255
+-->
+<head>
+ <title>Test for Bug 573255</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display:not(.hasSummary) { visibility: hidden }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=573255">Mozilla Bug 573255</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+
+is(cs.visibility, "hidden", "should be visibility:none since it has no class");
+p.className = "hasSummary";
+is(cs.visibility, "visible", "changing class attribute should remove visibility:hidden");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug580685.html b/layout/style/test/test_bug580685.html
new file mode 100644
index 0000000000..e54dfc2d7f
--- /dev/null
+++ b/layout/style/test/test_bug580685.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=580685
+-->
+<head>
+ <title>Test for Bug 580685</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=580685">Mozilla Bug 580685</a>
+<p id="display">
+ <iframe id="f" srcdoc="<body style='outline-offset: 1rem'>">
+ </iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 580685 **/
+SimpleTest.waitForExplicitFinish()
+addLoadEvent(function() {
+ var doc = $("f").contentDocument;
+ var b = doc.body;
+ doc.removeChild(doc.documentElement);
+ is(doc.defaultView.getComputedStyle(b).outlineOffset,
+ doc.defaultView.getComputedStyle(b).fontSize,
+ "1rem did not compute correctly");
+ SimpleTest.finish();
+});
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug621351.html b/layout/style/test/test_bug621351.html
new file mode 100644
index 0000000000..6a6d373afc
--- /dev/null
+++ b/layout/style/test/test_bug621351.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang=en>
+<title>Test for Bug 160403</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<span></span>
+<style>
+span {
+ border-inline-start: 0px solid rgb(0, 0, 0);
+ border-inline-end: 0px solid rgb(0, 0, 0);
+ transition: border 100s linear -50s;
+}
+span.transitioned {
+ border-inline-start: 100px solid rgb(100, 100, 100);
+ border-inline-end: 10px solid rgb(10, 10, 10);
+}
+</style>
+<script>
+// Test that transitioning each of border-{left,right}-{color,width}
+// works when the values are set through the -moz-border-{start,end}
+// shorthands.
+
+var e = document.querySelector("span");
+var cs = getComputedStyle(e);
+is(cs.borderLeftColor, "rgb(0, 0, 0)", "value of border-left-color before transition");
+is(cs.borderLeftWidth, "0px", "value of border-left-width before transition");
+is(cs.borderRightColor, "rgb(0, 0, 0)", "value of border-right-color before transition");
+is(cs.borderRightWidth, "0px", "value of border-right-width before transition");
+e.className = "transitioned";
+is(cs.borderLeftWidth, "50px", "value of border-left-width during transition");
+is(cs.borderLeftColor, "rgb(50, 50, 50)", "value of border-left-color during transition");
+is(cs.borderRightWidth, "5px", "value of border-right-width during transition");
+is(cs.borderRightColor, "rgb(5, 5, 5)", "value of border-right-color during transition");
+e.remove();
+</script>
diff --git a/layout/style/test/test_bug635286.html b/layout/style/test/test_bug635286.html
new file mode 100644
index 0000000000..80ea5a98ef
--- /dev/null
+++ b/layout/style/test/test_bug635286.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635286
+-->
+<head>
+ <title>Test for Bug 635286</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ div { background: transparent; }
+ :-moz-any(#case1.before) { background: gray; }
+ #case2:not(.after) { background: gray; }
+ :-moz-any(#case3:not(.after)) { background: gray; }
+ #case4:not(:-moz-any(.after)) { background: gray; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=635286">Mozilla Bug 635286</a>
+<div id="case1" class="before">case1, :-moz-any()</div>
+<div id="case2" class="before">case2, :not()</div>
+<div id="case3" class="before">case3, :not() in :-moz-any()</div>
+<div id="case4" class="before">case4, :-moz-any() in :not()</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 635286 **/
+
+window.addEventListener("load", function() {
+ var cases = Array.from(document.getElementsByTagName("div"));
+ cases.forEach(function(aCase, aIndex) {
+ aCase.className = "after";
+ });
+ window.setTimeout(function() {
+ cases.forEach(function(aCase, aIndex) {
+ is(window.getComputedStyle(aCase)
+ .getPropertyValue("background-color"),
+ "rgba(0, 0, 0, 0)",
+ aCase.textContent);
+ });
+ SimpleTest.finish();
+ }, 1);
+});
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug645998.html b/layout/style/test/test_bug645998.html
new file mode 100644
index 0000000000..ed8feccf7d
--- /dev/null
+++ b/layout/style/test/test_bug645998.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=645998
+-->
+<head>
+ <title>Test for Bug 645998</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <!-- This is the real test: will these stylesheets ever finish loading? -->
+ <link rel="stylesheet" href="file_bug645998-1.css">
+ <link rel="stylesheet" href="file_bug645998-2.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=645998">Mozilla Bug 645998</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 645998 **/
+ok(true, "Hey, we got here! That's a good sign");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug652486.html b/layout/style/test/test_bug652486.html
new file mode 100644
index 0000000000..cdee3f33a7
--- /dev/null
+++ b/layout/style/test/test_bug652486.html
@@ -0,0 +1,192 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=652486
+https://bugzilla.mozilla.org/show_bug.cgi?id=1039488
+-->
+<head>
+ <title>Test for Bug 652486, Bug 1039488 and Bug 1574222</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=652486">Mozilla Bug 652486</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1039488">Mozilla Bug 1039488</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1574222">Mozilla Bug 1574222</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="t"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 652486, Bug 1039488 and Bug 1574222 **/
+
+function c() {
+ return document.defaultView.getComputedStyle($('t')).
+ getPropertyValue("text-decoration");
+}
+
+// The default value of the 'color' property, which in turn establishes the
+// default value of 'text-decoration-color' (via the 'currentColor' keyword).
+var defaultLineColor = "rgb(0, 0, 0)";
+
+var tests = [
+ // When only text-decoration was specified, text-decoration should look like
+ // a longhand property. However, as of Bug 1574222, the getComputedStyle()
+ // serialization for "text-decoration" will always include the color,
+ // because we can't tell whether the resolved color value came from the
+ // initial "currentColor" value (and could safely be omitted) vs. whether it
+ // came from a custom specified value (and cannot be omitted).
+ { decoration: "none",
+ line: null, color: null, style: null,
+ expectedValue: defaultLineColor },
+ { decoration: "underline",
+ line: null, color: null, style: null,
+ expectedValue: "underline " + defaultLineColor },
+ { decoration: "overline",
+ line: null, color: null, style: null,
+ expectedValue: "overline " + defaultLineColor },
+ { decoration: "line-through",
+ line: null, color: null, style: null,
+ expectedValue: "line-through " + defaultLineColor },
+ { decoration: "blink",
+ line: null, color: null, style: null,
+ expectedValue: "blink " + defaultLineColor },
+ { decoration: "underline overline",
+ line: null, color: null, style: null,
+ expectedValue: "underline overline " + defaultLineColor },
+ { decoration: "underline line-through",
+ line: null, color: null, style: null,
+ expectedValue: "underline line-through " + defaultLineColor },
+ { decoration: "blink underline",
+ line: null, color: null, style: null,
+ expectedValue: "underline blink " + defaultLineColor },
+ { decoration: "underline blink",
+ line: null, color: null, style: null,
+ expectedValue: "underline blink " + defaultLineColor },
+
+ // When only text-decoration-line or text-blink was specified,
+ // text-decoration should look like a longhand property.
+ // However, as of Bug 1574222, the getComputedStyle() serialization for
+ // "text-decoration" will always include the color, because we can't tell
+ // whether the resolved color value came from the initial "currentColor"
+ // value (and could safely be omitted) vs. whether it came from a custom
+ // specified value (and cannot be omitted).
+ { decoration: null,
+ line: "blink", color: null, style: null,
+ expectedValue: "blink " + defaultLineColor },
+ { decoration: null,
+ line: "underline", color: null, style: null,
+ expectedValue: "underline " + defaultLineColor },
+ { decoration: null,
+ line: "overline", color: null, style: null,
+ expectedValue: "overline " + defaultLineColor },
+ { decoration: null,
+ line: "line-through", color: null, style: null,
+ expectedValue: "line-through " + defaultLineColor },
+ { decoration: null,
+ line: "blink underline", color: null, style: null,
+ expectedValue: "underline blink " + defaultLineColor },
+
+ // When text-decoration-color isn't its initial value,
+ // text-decoration should be a shorthand property.
+ { decoration: "blink",
+ line: null, color: "rgb(0, 0, 0)", style: null,
+ expectedValue: "blink rgb(0, 0, 0)" },
+ { decoration: "underline",
+ line: null, color: "black", style: null,
+ expectedValue: "underline rgb(0, 0, 0)" },
+ { decoration: "overline",
+ line: null, color: "#ff0000", style: null,
+ expectedValue: "overline rgb(255, 0, 0)" },
+ { decoration: "line-through",
+ line: null, color: "initial", style: null,
+ expectedValue: "line-through " + defaultLineColor },
+ { decoration: "blink underline",
+ line: null, color: "currentColor", style: null,
+ expectedValue: "underline blink " + defaultLineColor },
+ { decoration: "underline line-through",
+ line: null, color: "currentcolor", style: null,
+ expectedValue: "underline line-through " + defaultLineColor },
+
+ // When text-decoration-style isn't its initial value,
+ // text-decoration should be a shorthand property.
+ { decoration: "blink",
+ line: null, color: null, style: "-moz-none",
+ expectedValue: "blink -moz-none " + defaultLineColor },
+ { decoration: "underline",
+ line: null, color: null, style: "dotted",
+ expectedValue: "underline dotted " + defaultLineColor },
+ { decoration: "overline",
+ line: null, color: null, style: "dashed",
+ expectedValue: "overline dashed " + defaultLineColor },
+ { decoration: "line-through",
+ line: null, color: null, style: "double",
+ expectedValue: "line-through double " + defaultLineColor },
+ { decoration: "blink underline",
+ line: null, color: null, style: "wavy",
+ expectedValue: "underline blink wavy " + defaultLineColor },
+ { decoration: "underline blink overline line-through",
+ line: null, color: null, style: "solid",
+ expectedValue: "underline overline line-through blink " + defaultLineColor },
+ { decoration: "line-through overline underline",
+ line: null, color: null, style: "initial",
+ expectedValue: "underline overline line-through " + defaultLineColor }
+];
+
+function makeDeclaration(aTest)
+{
+ var str = "";
+ if (aTest.decoration) {
+ str += "text-decoration: " + aTest.decoration + "; ";
+ }
+ if (aTest.color) {
+ str += "text-decoration-color: " + aTest.color + "; ";
+ }
+ if (aTest.line) {
+ str += "text-decoration-line: " + aTest.line + "; ";
+ }
+ if (aTest.style) {
+ str += "text-decoration-style: " + aTest.style + "; ";
+ }
+ return str;
+}
+
+function clearStyleObject()
+{
+ $('t').style.textDecoration = null;
+}
+
+for (var i = 0; i < tests.length; ++i) {
+ var test = tests[i];
+ if (test.decoration) {
+ $('t').style.textDecoration = test.decoration;
+ }
+ if (test.color) {
+ $('t').style.textDecorationColor = test.color;
+ }
+ if (test.line) {
+ $('t').style.textDecorationLine = test.line;
+ }
+ if (test.style) {
+ $('t').style.textDecorationStyle = test.style;
+ }
+
+ var dec = makeDeclaration(test);
+ is(c(), test.expectedValue, "Test1 (computed value): " + dec);
+
+ clearStyleObject();
+
+ $('t').setAttribute("style", dec);
+
+ is(c(), test.expectedValue, "Test2 (computed value): " + dec);
+
+ $('t').removeAttribute("style");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug657143.html b/layout/style/test/test_bug657143.html
new file mode 100644
index 0000000000..de8a1961d4
--- /dev/null
+++ b/layout/style/test/test_bug657143.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=657143
+-->
+<head>
+ <title>Test for Bug 657143</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657143">Mozilla Bug 657143</a>
+<p id="display"></p>
+<style>
+/* Ensure that there is at least one custom property on the root element's
+ computed style */
+:root { --test: some value; }
+</style>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 657143 **/
+
+// Check ordering of CSS properties in nsComputedDOMStylePropertyList.h
+// by splitting the getComputedStyle() into five sublists
+// then cloning and sort()ing the lists and comparing with originals.
+
+function isMozPrefixed(aProp) {
+ return aProp.startsWith("-moz");
+}
+
+function isNotMozPrefixed(aProp) {
+ return !isMozPrefixed(aProp);
+}
+
+function isWebkitPrefixed(aProp) {
+ return aProp.startsWith("-webkit");
+}
+
+function isNotWebkitPrefixed(aProp) {
+ return !isWebkitPrefixed(aProp);
+}
+
+function isCustom(aProp) {
+ return aProp.startsWith("--");
+}
+
+function isNotCustom(aProp) {
+ return !isCustom(aProp);
+}
+
+var styleList = window.getComputedStyle(document.documentElement);
+ cssA = [], mozA = [], webkitA = [], customA = [];
+
+// Partition the list of property names into four lists:
+//
+// cssA: regular properties
+// mozA: '-moz-' prefixed properties
+// customA: '--' prefixed custom properties
+
+var list = cssA;
+for (var i = 0, j = styleList.length; i < j; i++) {
+ var prop = styleList.item(i);
+
+ switch (list) {
+ case cssA:
+ if (isMozPrefixed(prop)) {
+ list = mozA;
+ }
+ // fall through
+ case mozA:
+ if (isWebkitPrefixed(prop)) {
+ list = webkitA;
+ }
+ // fall through
+ case webkitA:
+ if (isCustom(prop)) {
+ list = customA;
+ }
+ // fall through
+ }
+
+ list.push(prop);
+}
+
+var cssB = cssA.slice(0).sort(),
+ mozB = mozA.slice(0).sort(),
+ webkitB = webkitA.slice(0).sort();
+
+is(cssA.toString(), cssB.toString(), 'CSS property list should be alphabetical');
+is(mozA.toString(), mozB.toString(), 'Experimental -moz- CSS property list should be alphabetical');
+is(webkitA.toString(), webkitB.toString(), 'Compatible -webkit- CSS property list should be alphabetical');
+
+// We don't test that the custom property list is sorted, as the CSSOM
+// specification does not yet require it, and we don't return them
+// in sorted order.
+
+ok(!cssA.find(isWebkitPrefixed), 'Compatible -webkit- CSS properties should not be in the mature CSS property list');
+ok(!cssA.find(isMozPrefixed), 'Experimental -moz- CSS properties should not be in the mature CSS property list');
+ok(!cssA.find(isCustom), 'Custom CSS properties should not be in the mature CSS property list');
+ok(!mozA.find(isWebkitPrefixed), 'Compatible -webkit- CSS properties should not be in the experimental -moz- CSS '
+ + 'property list');
+ok(!mozA.find(isNotMozPrefixed), 'Experimental -moz- CSS property list should not contain non -moz- prefixed '
+ + 'CSS properties');
+ok(!mozA.find(isCustom), 'Custom CSS properties should not be in the experimental -moz- CSS property list');
+ok(!webkitA.find(isNotWebkitPrefixed), 'Compatible -webkit- CSS properties should not contain non -webkit- prefixed '
+ + 'CSS properties');
+ok(!webkitA.find(isMozPrefixed), 'Experimental -moz- CSS properties should not be in the compatible -webkit- CSS '
+ + 'property list');
+ok(!webkitA.find(isCustom), 'Custom CSS properties should not be in the compatible -webkit- CSS property list');
+ok(!customA.find(isNotCustom), 'Non-custom CSS properties should not be in the custom property list');
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug667520.html b/layout/style/test/test_bug667520.html
new file mode 100644
index 0000000000..fb46943e8e
--- /dev/null
+++ b/layout/style/test/test_bug667520.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=667520
+-->
+<head>
+ <title>Test for Bug 667520</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=667520">Mozilla Bug 667520</a>
+<p id="display">
+ <!-- important: we need a <span> as the second element child and then
+ non-span elements between that and the next </span> -->
+ <i></i>
+ <span id="2"></span>
+ <i></i>
+ <i></i>
+ <i></i>
+ <i></i>
+ <span id="7"></span>
+ <span id="8"></span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 667520 **/
+var spans = $("display").querySelectorAll("span");
+is(spans.length, 3, "Should have 3 span kids");
+
+is($("display").querySelector("span:nth-child(3)"), null, "Third child is not span");
+is($("display").querySelector("span:nth-child(4)"), null, "Fourth child is not span");
+
+for (var i = 0; i < spans.length; ++i) {
+ var id = spans[i].id;
+ /* Important: need to include 'span' in that selector so we only match
+ nth-child against spans. */
+ var target = $("display").querySelector("span:nth-child("+id+")");
+ is(target, spans[i], "Unexpected element");
+ is(target.id, spans[i].id, "Unexpected id");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug716226.html b/layout/style/test/test_bug716226.html
new file mode 100644
index 0000000000..ed538cd822
--- /dev/null
+++ b/layout/style/test/test_bug716226.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=716226
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 716226</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="s">
+ @keyframes foo { }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=716226">Mozilla Bug 716226</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 716226 **/
+var sheet = $("s").sheet;
+var rules = sheet.cssRules;
+is(rules.length, 1, "Should have one keyframes rule");
+var keyframesRule = rules[0];
+var keyframeRules = keyframesRule.cssRules;
+is(keyframeRules.length, 0, "Should have no keyframe rules yet");
+
+keyframesRule.appendRule('0% { }');
+is(keyframeRules.length, 1, "Should have a keyframe rule now");
+var keyframeRule = keyframeRules[0];
+is(keyframeRule.parentRule, keyframesRule,
+ "Parent of keyframe should be keyframes");
+is(keyframeRule.parentStyleSheet, sheet,
+ "Parent stylesheet of keyframe should be our sheet");
+
+is(keyframeRule.style.cssText, "", "Should have no declarations yet");
+// Note: purposefully non-canonical cssText string so we can make sure we
+// really invoked the CSS parser and serializer.
+keyframeRule.style.cssText = "color:green";
+is(keyframeRule.style.cssText, "color: green;",
+ "Should have the declarations we set now");
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug732153.html b/layout/style/test/test_bug732153.html
new file mode 100644
index 0000000000..03591e3dc6
--- /dev/null
+++ b/layout/style/test/test_bug732153.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=732153
+-->
+<title>Test for Bug 732153</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=732153">Mozilla Bug 732153</a>
+<div></div>
+<script>
+var div = document.querySelector("div");
+[
+ "",
+ "backface-visibility: hidden",
+ "transform-style: preserve-3d",
+ "backface-visibility: hidden; transform-style: preserve-3d",
+].forEach(function(style) {
+ div.setAttribute("style", style);
+ is(getComputedStyle(div).transform, "none",
+ "Computed 'transform' with style=\"" + style + '"');
+});
+</script>
diff --git a/layout/style/test/test_bug732209.html b/layout/style/test/test_bug732209.html
new file mode 100644
index 0000000000..1bf5825000
--- /dev/null
+++ b/layout/style/test/test_bug732209.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=732209
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 732209</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #content span { color: red; }
+ #content span.reverse { color: green; }
+ #content { display: block !important; }
+ #content span::before { content: attr(id); }
+ </style>
+ <link rel="stylesheet" href="bug732209-css.sjs?one">
+ <link rel="stylesheet" href="bug732209-css.sjs?two" crossorigin>
+ <link rel="stylesheet" href="bug732209-css.sjs?three" crossorigin="use-credentials">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?four">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?five"
+ crossorigin>
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?six"
+ crossorigin="use-credentials">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?seven&cors-anonymous">
+ <link rel="stylesheet" id="cross-origin-sheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?eight&cors-anonymous"
+ crossorigin>
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?nine&cors-anonymous"
+ crossorigin="use-credentials">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?ten&cors-credentials">
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?eleven&cors-credentials"
+ crossorigin>
+ <link rel="stylesheet"
+ href="http://example.com/tests/layout/style/test/bug732209-css.sjs?twelve&cors-credentials"
+ crossorigin="use-credentials">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=732209">Mozilla Bug 732209</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <span id="one"></span>
+ <span id="two"></span>
+ <span id="three"></span>
+ <span id="four"></span>
+ <span id="five" class="reverse"></span>
+ <span id="six" class="reverse"></span>
+ <span id="seven"></span>
+ <span id="eight"></span>
+ <span id="nine" class="reverse"></span>
+ <span id="ten"></span>
+ <span id="eleven"></span>
+ <span id="twelve"></span>
+</div>
+<pre id="test" style="color: red">
+<script type="application/javascript">
+
+/** Test for Bug 732209 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var spans = $("content").querySelectorAll("span");
+ for (var i = 0; i < spans.length; ++i) {
+ is(getComputedStyle(spans[i], "").color, "rgb(0, 128, 0)",
+ "Span " + spans[i].id + " should be green");
+ }
+
+ try {
+ var sheet = $("cross-origin-sheet").sheet;
+ dump('aaa\n');
+ is(sheet.cssRules.length, 2,
+ "Should be able to get length of list of rules");
+ is(sheet.cssRules[0].style.color, "green",
+ "Should be able to read individual rules");
+ } catch (e) {
+ ok(false,
+ "Should be allowed to access cross-origin sheet that opted in with CORS: " + e);
+ }
+
+ SimpleTest.finish();
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug73586.html b/layout/style/test/test_bug73586.html
new file mode 100644
index 0000000000..6a95703cd7
--- /dev/null
+++ b/layout/style/test/test_bug73586.html
@@ -0,0 +1,189 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=73586
+-->
+<head>
+ <title>Test for Bug 73586</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ span { background: white; color: black; border: medium solid black; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=73586">Mozilla Bug 73586</a>
+<div id="display"></div>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 73586 **/
+
+const GREEN = "rgb(0, 128, 0)";
+const LIME = "rgb(0, 255, 0)";
+const BLACK = "rgb(0, 0, 0)";
+const WHITE = "rgb(255, 255, 255)";
+
+function cs(elt) { return getComputedStyle(elt, ""); }
+
+function check_children(p, check_cb) {
+ var len = p.childNodes.length;
+ var elts = 0;
+ var i, elt, child;
+ for (i = 0; i < len; ++i) {
+ if (p.childNodes[i].nodeType == Node.ELEMENT_NODE)
+ ++elts;
+ }
+
+ elt = 0;
+ for (i = 0; i < len; ++i) {
+ child = p.childNodes[i];
+ if (child.nodeType != Node.ELEMENT_NODE)
+ continue;
+ check_cb(child, elt, elts, i, len);
+ ++elt;
+ }
+}
+
+function run_series(check_cb) {
+ var display = document.getElementById("display");
+ // Use a new parent node every time since the optimizations cause
+ // bits to be set (permanently) on the parent.
+ var p = document.createElement("p");
+ display.appendChild(p);
+ p.innerHTML = "x<span></span><span></span>";
+
+ check_children(p, check_cb);
+ var text = p.removeChild(p.childNodes[0]);
+ check_children(p, check_cb);
+ var span = p.removeChild(p.childNodes[0]);
+ check_children(p, check_cb);
+ p.appendChild(span);
+ check_children(p, check_cb);
+ p.removeChild(span);
+ check_children(p, check_cb);
+ p.insertBefore(span, p.childNodes[0]);
+ check_children(p, check_cb);
+ p.removeChild(span);
+ check_children(p, check_cb);
+ p.insertBefore(span, null);
+ check_children(p, check_cb);
+ p.appendChild(document.createElement("span"));
+ check_children(p, check_cb);
+ p.insertBefore(document.createElement("span"), p.childNodes[2]);
+ check_children(p, check_cb);
+ p.appendChild(text);
+ check_children(p, check_cb);
+
+ display.removeChild(p);
+}
+
+var style = document.createElement("style");
+style.setAttribute("type", "text/css");
+var styleText = document.createTextNode("");
+style.appendChild(styleText);
+document.getElementsByTagName("head")[0].appendChild(style);
+
+styleText.data = "span:first-child { background: lime; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).backgroundColor, (elt == 0) ? LIME : WHITE,
+ "child " + node + " should " + ((elt == 0) ? "" : "NOT ") +
+ " match :first-child");
+ });
+
+styleText.data = "span:last-child { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).color, (elt == elts - 1) ? GREEN : BLACK,
+ "child " + node + " should " + ((elt == elts - 1) ? "" : "NOT ") +
+ " match :last-child");
+ });
+
+styleText.data = "span:only-child { border: medium solid green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).borderTopColor, (elts == 1) ? GREEN : BLACK,
+ "child " + node + " should " + ((elts == 1) ? "" : "NOT ") +
+ " match :only-child");
+ });
+
+styleText.data = "span:-moz-first-node { text-decoration-line: underline; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).textDecorationLine, (node == 0) ? "underline" : "none",
+ "child " + node + " should " + ((node == 0) ? "" : "NOT ") +
+ " match :-moz-first-node");
+ });
+
+styleText.data = "span:-moz-last-node { visibility: hidden; }";
+run_series(function(child, elt, elts, node, nodes) {
+ is(cs(child).visibility, (node == nodes - 1) ? "hidden" : "visible",
+ "child " + node + " should " + ((node == nodes - 1) ? "" : "NOT ") +
+ " match :-moz-last-node");
+ });
+
+styleText.data = "span:nth-child(1) { background: lime; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = elt == 0;
+ is(cs(child).backgroundColor, matches ? LIME : WHITE,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:nth-last-child(0n+2) { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = (elt == elts - 2);
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:nth-of-type(2n+3) { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var nidx = elt + 1;
+ var matches = nidx % 2 == 1 && nidx >= 3;
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:nth-last-of-type(-2n+5) { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var nlidx = elts - elt;
+ var matches = nlidx % 2 == 1 && nlidx <= 5;
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:first-of-type { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = (elt == 0);
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:last-of-type { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = (elt == elts - 1);
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+styleText.data = "span:only-of-type { color: green; }";
+run_series(function(child, elt, elts, node, nodes) {
+ var matches = elts == 1;
+ is(cs(child).color, matches ? GREEN : BLACK,
+ "child " + node + " should " + (matches ? "" : "NOT ") +
+ " match " + styleText.data);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug74880.html b/layout/style/test/test_bug74880.html
new file mode 100644
index 0000000000..054189056b
--- /dev/null
+++ b/layout/style/test/test_bug74880.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=74880
+-->
+<head>
+ <title>Test for Bug 74880</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ /* so that computed values for other border properties work right */
+ #display { border-style: solid; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=74880">Mozilla Bug 74880</a>
+<div style="margin: 1px 2px 3px 4px; border-width: 5px 6px 7px 8px; border-style: dotted dashed solid double; border-color: blue fuchsia green orange; padding: 9px 10px 11px 12px">
+<p id="display"></p>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 74880 **/
+
+var gProps = [
+ [ "margin-left", "margin-right", "margin-inline-start", "margin-inline-end" ],
+ [ "padding-left", "padding-right", "padding-inline-start", "padding-inline-end" ],
+ [ "border-left-color", "border-right-color", "border-inline-start-color", "border-inline-end-color" ],
+ [ "border-left-style", "border-right-style", "border-inline-start-style", "border-inline-end-style" ],
+ [ "border-left-width", "border-right-width", "border-inline-start-width", "border-inline-end-width" ],
+];
+
+var gLengthValues = [ "inherit", "initial", "2px", "1em", "unset" ];
+var gColorValues = [ "inherit", "initial", "currentColor", "green", "unset" ];
+var gStyleValues = [ "inherit", "initial", "double", "dashed", "unset" ];
+
+function values_for(set) {
+ var values;
+ if (set[0].match(/style$/))
+ values = gStyleValues;
+ else if (set[0].match(/color$/))
+ values = gColorValues;
+ else
+ values = gLengthValues;
+ return values;
+}
+
+var e = document.getElementById("display");
+var s = e.style;
+var c = window.getComputedStyle(e);
+
+for (var set of gProps) {
+ var values = values_for(set);
+ for (var val of values) {
+ function check(dir, plogical, pvisual) {
+ var v0 = c.getPropertyValue(pvisual);
+ s.setProperty("direction", dir, "");
+ s.setProperty(pvisual, val, "");
+ var v1 = c.getPropertyValue(pvisual);
+ if (val != "initial" && val != "unset" && val != "currentColor")
+ isnot(v1, v0, "setProperty set the property " + pvisual + ": " + val);
+ s.removeProperty(pvisual);
+ is(c.getPropertyValue(pvisual), v0, "removeProperty worked for " + pvisual);
+ s.setProperty(plogical, val, "")
+ var v2 = c.getPropertyValue(pvisual);
+ is(v2, v1, "the logical property " + plogical + ": " + val + " showed up in the right place");
+ s.removeProperty(plogical);
+ is(c.getPropertyValue(pvisual), v0, "removeProperty worked for " + plogical);
+
+ s.removeProperty("direction");
+ }
+
+ check("ltr", set[2], set[0]);
+ check("ltr", set[3], set[1]);
+ check("rtl", set[2], set[1]);
+ check("rtl", set[3], set[0]);
+ }
+
+ function check_cascading(dir, plogical, pvisual) {
+ var dirstr = "direction: " + dir + ";";
+ e.setAttribute("style", dirstr + pvisual + ":" + values[2]);
+ var v2 = c.getPropertyValue(pvisual);
+ e.setAttribute("style", dirstr + pvisual + ":" + values[3]);
+ var v3 = c.getPropertyValue(pvisual);
+ isnot(v2, v3, "values should produce different computed values");
+
+ var desc = ["cascading for", pvisual, "and", plogical, "with direction", dir].join(" ");
+ e.setAttribute("style", dirstr + pvisual + ":" + values[3] + ";" +
+ plogical + ":" + values[2]);
+ is(c.getPropertyValue(pvisual), v2, desc);
+ e.setAttribute("style", dirstr + plogical + ":" + values[3] + ";" +
+ pvisual + ":" + values[2]);
+ is(c.getPropertyValue(pvisual), v2, desc);
+ e.setAttribute("style", dirstr + pvisual + ":" + values[2] + ";" +
+ plogical + ":" + values[3]);
+ is(c.getPropertyValue(pvisual), v3, desc);
+ e.setAttribute("style", dirstr + plogical + ":" + values[2] + ";" +
+ pvisual + ":" + values[3]);
+ is(c.getPropertyValue(pvisual), v3, desc);
+ e.removeAttribute("style");
+ }
+
+ check_cascading("ltr", set[2], set[0]);
+ check_cascading("ltr", set[3], set[1]);
+ check_cascading("rtl", set[2], set[1]);
+ check_cascading("rtl", set[3], set[0]);
+}
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_bug765590.html b/layout/style/test/test_bug765590.html
new file mode 100644
index 0000000000..acc9f5b5c0
--- /dev/null
+++ b/layout/style/test/test_bug765590.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=765590
+-->
+<head>
+ <title>Test for Bug 765590</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @namespace svg "http://www.w3.org/2000/svg";
+ </style>
+</head>
+<body>
+ <script>
+ var styleElement = document.getElementsByTagName("style")[0]
+ var rule = styleElement.sheet.cssRules[0];
+ is(rule.type, 10, "rule type should be equal 10")
+ is(CSSRule.NAMESPACE_RULE, 10, "NAMESPACE_RULE should be equal to 10")
+ </script>
+</body>
diff --git a/layout/style/test/test_bug771043.html b/layout/style/test/test_bug771043.html
new file mode 100644
index 0000000000..a5073d681e
--- /dev/null
+++ b/layout/style/test/test_bug771043.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=771043
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 771043</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 771043 **/
+ var expectedValue;
+ var callCount = 0;
+ var storedHeight;
+ function callback(arg) {
+ ++callCount;
+ is(arg.matches, expectedValue,
+ "Should have the right value on call #" + callCount + " to the callback");
+ SimpleTest.executeSoon(tests.shift());
+ }
+
+ function flushLayout() {
+ storedHeight = document.querySelector("iframe").offsetHeight;
+ }
+
+ function setHeight(height) {
+ var ifr = document.querySelector("iframe");
+ ifr.style.height = height + "px";
+ flushLayout();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ var tests = [
+ () => { expectedValue = true; setHeight(50); },
+ () => { expectedValue = false; setHeight(200); },
+ () => {
+ var ifr = document.querySelector("iframe");
+ ifr.style.display = "none";
+ flushLayout();
+ ifr.style.display = "";
+ expectedValue = true;
+ setHeight(50);
+ },
+ () => { expectedValue = false; setHeight(200); },
+ SimpleTest.finish.bind(SimpleTest)
+ ];
+
+ addLoadEvent(function() {
+ var mql = frames[0].matchMedia("(orientation: landscape)");
+ mql.addListener(callback);
+
+ tests.shift()();
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=771043">Mozilla Bug 771043</a>
+<!-- Important: the iframe needs to be displayed -->
+<p id="display"><iframe style="width: 100px; height: 200px"</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug795520.html b/layout/style/test/test_bug795520.html
new file mode 100644
index 0000000000..66726c2b8e
--- /dev/null
+++ b/layout/style/test/test_bug795520.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795520
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 795520</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795520">Mozilla Bug 795520</a>
+<p id="display">
+ <iframe id="f" style="display:none"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 795520 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ doc = $("f").contentDocument;
+ $("f").style.display = "";
+ isnot(doc.defaultView.getComputedStyle(doc.body), null,
+ "Should have computed style here");
+ SimpleTest.finish();
+});
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug798843_pref.html b/layout/style/test/test_bug798843_pref.html
new file mode 100644
index 0000000000..aea12ccc35
--- /dev/null
+++ b/layout/style/test/test_bug798843_pref.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Make sure that the SVG glyph context-* values are not considered real values
+ when gfx.font_rendering.opentype_svg.enabled is pref'ed off.
+-->
+<head>
+ <title>Test that SVG glyph context-* values can be pref'ed off</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+
+<script>
+
+var props = {
+ "strokeDasharray" : "context-value",
+ "strokeDashoffset" : "context-value",
+ "strokeWidth" : "context-value"
+};
+
+function testDisabled() {
+ for (var p in props) {
+ document.body.style[p] = props[p];
+ is(document.body.style[p], "", p + " not settable to " + props[p]);
+ document.body.style[p] = "";
+ }
+ SimpleTest.finish();
+}
+
+function testEnabled() {
+ for (var p in props) {
+ document.body.style[p] = props[p];
+ is(document.body.style[p], props[p], p + " settable to " + props[p]);
+ document.body.style[p] = "";
+ }
+
+ SpecialPowers.pushPrefEnv(
+ {'set': [['gfx.font_rendering.opentype_svg.enabled', false]]},
+ testDisabled
+ );
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ {'set': [['gfx.font_rendering.opentype_svg.enabled', true]]},
+ testEnabled
+);
+
+</script>
+
+</body>
+</html>
diff --git a/layout/style/test/test_bug829816.html b/layout/style/test/test_bug829816.html
new file mode 100644
index 0000000000..a4614cf6eb
--- /dev/null
+++ b/layout/style/test/test_bug829816.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=829816
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 829816</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <style type="text/css">
+ b { content: "\0"; counter-reset: \0 }
+ b { content: "\00"; counter-reset: \00 }
+ b { content: "\000"; counter-reset: \000 }
+ b { content: "\0000"; counter-reset: \0000 }
+ b { content: "\00000"; counter-reset: \00000 }
+ b { content: "\000000"; counter-reset: \000000 }
+ </style>
+
+ <!-- U+0000 characters in <style> would be replaced by the HTML parser -->
+ <link rel="stylesheet" type="text/css" href="file_bug829816.css"/>
+
+ <script type="application/javascript">
+
+ /** Test for Bug 829816 **/
+ var ss = document.styleSheets[1];
+
+ for (var i = 0; i < 6; i++) {
+ is(ss.cssRules[i].style.content, "\"\uFFFD\"",
+ "\\0 in strings should be converted to U+FFFD");
+ is(ss.cssRules[i].style.counterReset, "\uFFFD 0",
+ "\\0 in identifiers should be converted to U+FFFD");
+ }
+
+ is(document.styleSheets[2].cssRules[0].style.content, "\"\uFFFD\"",
+ "U+0000 in strings should be converted to U+FFFD");
+ is(document.styleSheets[2].cssRules[0].style.counterReset, "\uFFFD 0",
+ "U+0000 in identifiers should be converted to U+FFFD");
+ is(document.styleSheets[2].cssRules[1].style.content, "\"\uFFFD\"",
+ "U+0000 in strings should be converted to U+FFFD");
+ is(document.styleSheets[2].cssRules[1].style.counterReset, "\uFFFD 0",
+ "U+0000 in identifiers should be converted to U+FFFD");
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=829816">Mozilla Bug 829816</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug874919.html b/layout/style/test/test_bug874919.html
new file mode 100644
index 0000000000..be480600b9
--- /dev/null
+++ b/layout/style/test/test_bug874919.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=874919
+-->
+<head>
+ <title>Test for Bug 874919</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=874919">Mozilla Bug 874919</a>
+<p id="display"></p>
+<div id="content" style="width: 150px">
+ <svg id="outer_SVG" style="display: inline; width: 100%">
+ <circle cx="120" cy="120" r="120" fill="blue"></circle>
+ <svg id="inner_SVG">
+ <circle id="circle" cx="120" cy="120" r="120" fill="red"></circle>
+ </svg>
+ </svg>
+</div>
+<pre id="test">
+
+<script type="text/javascript">
+
+ var shouldUseComputed = ["inner_SVG"]
+ var shouldUseUsed = ["outer_SVG"]
+
+ shouldUseUsed.forEach(function(elemId) {
+
+ var style = window.getComputedStyle(document.getElementById(elemId));
+
+ ok(style.width.match(/^\d+px$/),
+ "Inline Outer SVG element's getComputedStyle.width should be used value. ");
+
+ ok(style.height.match(/^\d+px$/),
+ "Inline Outer SVG element's getComputedStyle.height should be used value.");
+ });
+
+ shouldUseComputed.forEach(function(elemId) {
+ var style = window.getComputedStyle(document.getElementById(elemId));
+
+ // Computed value should match either the percentage used, or "auto" in the case of the inner SVG element.
+ ok(style.width.match(/^\d+%$|^auto$/),
+ "Inline inner SVG element's getComputedStyle.width should be computed value. " + style.width);
+
+ ok(style.height.match(/^\d+%$|^auto$/),
+ "Inline inner SVG element's getComputedStyle.height should be computed value. " + style.height);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug887741_at-rules_in_declaration_lists.html b/layout/style/test/test_bug887741_at-rules_in_declaration_lists.html
new file mode 100644
index 0000000000..739ace0f04
--- /dev/null
+++ b/layout/style/test/test_bug887741_at-rules_in_declaration_lists.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=887741
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 887741: at-rules in declaration lists</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style>
+ #foo {
+ color: red;
+ @invalid-rule {
+ ignored: ignored;
+ }
+ /* No semicolon */
+ color: green;
+ }
+ @page {
+ margin-top: 0;
+ @bottom-center {
+ content: counter(page);
+ }
+ /* No semicolon */
+ margin-top: 5cm;
+ }
+ @keyframes dummy-animation {
+ 12% {
+ color: red;
+ @invalid-rule {}
+ /* No semicolon */
+ color: green;
+ }
+ }
+ /* TODO: other at-rules that use declaration syntax? */
+ </style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887741">Mozilla Bug 887741</a>
+<p id="display"></p>
+<div id="content" style="display: none; color: red;
+ @invalid-rule{} /* No semicolon */ color: green;">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 887741 **/
+
+ var style = document.getElementById('content').style;
+ is(style.display, 'none', 'Sanity check: we have the right element');
+ is(style.color, 'green', 'Support at-rules in style attributes');
+
+ style.cssText = 'display: none; color: red; @invalid-rule{} /* No semicolon */ color: lime;';
+ is(style.color, 'lime', 'Support at-rules in CSSStyleDeclaration.cssText');
+
+ var rules = document.styleSheets[0].cssRules;
+ var style_rule = rules[0];
+ is(style_rule.selectorText, '#foo', 'Sanity check: we have the right style rule');
+ is(style_rule.style.color, 'green', 'Support at-rules in style rules');
+
+ var page_rule = rules[1];
+ is(page_rule.type, page_rule.PAGE_RULE, 'Sanity check: we have the right style rule');
+ is(page_rule.style.marginTop, '5cm', 'Support at-rules in @page rules');
+
+ var keyframe_rule = rules[2].cssRules[0];
+ is(keyframe_rule.keyText, '12%', 'Sanity check: we have the right keyframe rule');
+ is(keyframe_rule.style.color, 'green', 'Support at-rules in keyframe rules')
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug892929.html b/layout/style/test/test_bug892929.html
new file mode 100644
index 0000000000..a67db56ee3
--- /dev/null
+++ b/layout/style/test/test_bug892929.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Bug 892929 test</title>
+ <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com">
+ <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#om-fontfeaturevalues" />
+ <meta name="assert" content="window.CSSFontFeatureValuesRule should appear by default" />
+ <script type="text/javascript" src="/resources/testharness.js"></script>
+ <script type="text/javascript" src="/resources/testharnessreport.js"></script>
+ <style type="text/css">
+ </style>
+</head>
+<body>
+<div id="log"></div>
+<pre id="display"></pre>
+
+<script type="text/javascript">
+
+function testCSSFontFeatureValuesRuleOM() {
+ var s = document.documentElement.style;
+ var cs = window.getComputedStyle(document.documentElement);
+
+ var hasFFVRule = "CSSFontFeatureValuesRule" in window;
+ var hasStyleAlternates = "fontVariantAlternates" in s;
+ var hasCompStyleAlternates = "fontVariantAlternates" in cs;
+
+ test(function() {
+ assert_equals(hasFFVRule,
+ hasStyleAlternates,
+ "style.fontVariantAlternates " +
+ (hasStyleAlternates ? "available" : "not available") +
+ " but " +
+ "window.CSSFontFeatureValuesRule " +
+ (hasFFVRule ? "available" : "not available") +
+ " - ");
+ }, "style.fontVariantAlternates availability matches window.CSSFontFeatureValuesRule availability");
+
+ test(function() {
+ assert_equals(hasFFVRule,
+ hasCompStyleAlternates,
+ "computedStyle.fontVariantAlternates " +
+ (hasCompStyleAlternates ? "available" : "not available") +
+ " but " +
+ "window.CSSFontFeatureValuesRule " +
+ (hasFFVRule ? "available" : "not available") +
+ " - ");
+ }, "computedStyle.fontVariantAlternates availability matches window.CSSFontFeatureValuesRule availability");
+
+ // if window.CSSFontFeatureValuesRule isn't around, neither should any of the font feature props
+ fontFeatureProps = [ "fontKerning", "fontVariantAlternates", "fontVariantCaps", "fontVariantEastAsian",
+ "fontVariantLigatures", "fontVariantNumeric", "fontVariantPosition", "fontSynthesis",
+ "fontFeatureSettings", "fontLanguageOverride" ];
+
+ if (!hasFFVRule) {
+ var i;
+ for (i = 0; i < fontFeatureProps.length; i++) {
+ var prop = fontFeatureProps[i];
+ test(function() {
+ assert_true(!(prop in s), "window.CSSFontFeatureValuesRule not available but style." + prop + " is available - ");
+ }, "style." + prop + " availability");
+ test(function() {
+ assert_true(!(prop in cs), "window.CSSFontFeatureValuesRule not available but computedStyle." + prop + " is available - ");
+ }, "computedStyle." + prop + " availability");
+ }
+ }
+
+}
+
+testCSSFontFeatureValuesRuleOM();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_bug98997.html b/layout/style/test/test_bug98997.html
new file mode 100644
index 0000000000..5dc325f9ef
--- /dev/null
+++ b/layout/style/test/test_bug98997.html
@@ -0,0 +1,144 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=98997
+-->
+<head>
+ <title>Test for Bug 98997</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ /*
+ * This test does NOT test any of the cases where :empty and
+ * :-moz-only-whitespace differ. We should probably have some tests
+ * for that as well.
+ */
+ div.test { width: 200px; height: 30px; margin: 5px 0; }
+ div.test.to, div.test.from:empty { background: orange; }
+ div.test.to:empty, div.test.from { background: green; }
+ div.test.to, div.test.from:-moz-only-whitespace { color: maroon; }
+ div.test.to:-moz-only-whitespace, div.test.from { color: navy; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=98997">Mozilla Bug 98997</a>
+<div id="display">
+<div class="test to" onclick="testReplaceChild(this, '')">x</div>
+<div class="test to" onclick="testReplaceChild(this, '')"><span>x</span></div>
+<div class="test to" onclick="testReplaceChild(this, '')">x<!-- comment --></div>
+<div class="test to" onclick="testRemoveChild(this)">x</div>
+<div class="test to" onclick="testRemoveChild(this)"><span>x</span></div>
+<div class="test to" onclick="testRemoveChild(this)">x<!-- comment --></div>
+<div class="test to" onclick="testChangeData(this, '')">x</div>
+<div class="test to" onclick="testChangeData(this, '')">x<!-- comment --></div>
+<div class="test to" onclick="testDeleteData(this)">x</div>
+<div class="test to" onclick="testDeleteData(this)">x<!-- comment --></div>
+<div class="test to" onclick="testReplaceData(this, '')">x</div>
+<div class="test to" onclick="testReplaceData(this, '')">x<!-- comment --></div>
+
+<div class="test from makeemptytext" onclick="testReplaceChild(this, 'x')"></div>
+<div class="test from makeemptytext" onclick="testReplaceChild(this, 'x')"><!-- comment --></div>
+<div class="test from" onclick="testReplaceChild(this, 'x')"><!-- comment --></div>
+<div class="test from" onclick="testInsertBefore(this, 'x')"></div>
+<div class="test from" onclick="testInsertBefore(this, 'x')"><!-- comment --></div>
+<div class="test from" onclick="testAppendChild(this, 'x')"></div>
+<div class="test from" onclick="testAppendChild(this, 'x')"><!-- comment --></div>
+<div class="test from makeemptytext" onclick="testChangeData(this, 'x')"></div>
+<div class="test from makeemptytext" onclick="testChangeData(this, 'x')"><!-- comment --></div>
+<div class="test from makeemptytext" onclick="testAppendData(this, 'x')"></div>
+<div class="test from makeemptytext" onclick="testAppendData(this, 'x')"><!-- comment --></div>
+<div class="test from makeemptytext" onclick="testReplaceData(this, 'x')"></div>
+<div class="test from makeemptytext" onclick="testReplaceData(this, 'x')"><!-- comment --></div>
+</div>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 98997 **/
+
+function testInsertBefore(elt, text) {
+ elt.insertBefore(document.createTextNode(text), elt.firstChild);
+}
+
+function testAppendChild(elt, text) {
+ elt.appendChild(document.createTextNode(text));
+}
+
+function testReplaceChild(elt, text) {
+ elt.replaceChild(document.createTextNode(text), elt.firstChild);
+}
+
+function testRemoveChild(elt) {
+ elt.firstChild.remove();
+}
+
+function testChangeData(elt, text) {
+ elt.firstChild.data = text;
+}
+
+function testAppendData(elt, text) {
+ elt.firstChild.appendData(text);
+}
+
+function testDeleteData(elt) {
+ elt.firstChild.deleteData(0, elt.firstChild.length);
+}
+
+function testReplaceData(elt, text) {
+ elt.firstChild.replaceData(0, elt.firstChild.length, text);
+}
+
+var cnodes = document.getElementById("display").childNodes;
+var divs = [];
+var i;
+for (i = 0; i < cnodes.length; ++i) {
+ if (cnodes[i].nodeName == "DIV")
+ divs.push(cnodes[i]);
+}
+
+for (i in divs) {
+ let div = divs[i];
+ if (div.className.match(/makeemptytext/))
+ div.insertBefore(document.createTextNode(""), div.firstChild);
+}
+
+const ORANGE = "rgb(255, 165, 0)";
+const MAROON = "rgb(128, 0, 0)";
+const GREEN = "rgb(0, 128, 0)";
+const NAVY = "rgb(0, 0, 128)";
+
+function color(div) {
+ return getComputedStyle(div, "").color;
+}
+function bg(div) {
+ return getComputedStyle(div, "").backgroundColor;
+}
+
+for (i in divs) {
+ let div = divs[i];
+ is(bg(div), ORANGE, "should be orange");
+ is(color(div), MAROON, "should be maroon");
+}
+
+for (i in divs) {
+ let div = divs[i];
+ var e = document.createEvent("MouseEvents");
+ e.initEvent("click", true, true);
+ div.dispatchEvent(e);
+}
+
+for (i in divs) {
+ let div = divs[i];
+ is(bg(div), GREEN, "should be green");
+ is(color(div), NAVY, "should be navy");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_cascade.html b/layout/style/test/test_cascade.html
new file mode 100644
index 0000000000..6479d52617
--- /dev/null
+++ b/layout/style/test/test_cascade.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=4 tabstop=8 autoindent expandtab: -->
+<!-- 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 http://mozilla.org/MPL/2.0/. -->
+<html>
+<head>
+ <title>Test for Author style sheet aspects of CSS cascading</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ </style>
+</head>
+<body id="thebody">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div class="content_class" id="content" style="position:relative"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Author style sheet aspects of CSS cascading **/
+
+var style_element = document.createElement("style");
+var style_contents = document.createTextNode("");
+style_element.appendChild(style_contents);
+document.getElementsByTagName("head")[0].appendChild(style_element);
+
+var div = document.getElementById("content");
+var cs = window.getComputedStyle(div);
+var zindex = 0;
+
+/**
+ * Given the selectors |sel1| and |sel2|, in that order (the "order"
+ * aspect of the cascade), with declarations that are !important if
+ * |imp1|/|imp2| are true, assert that the one that wins in the
+ * cascading order is given by |winning| (which must be either 1 or 2).
+ */
+function do_test(sel1, imp1, sel2, imp2, winning) {
+ var ind1 = ++zindex;
+ var ind2 = ++zindex;
+ style_contents.data =
+ sel1 + " { z-index: " + ind1 + (imp1 ? "!important" :"") + " } " +
+ sel2 + " { z-index: " + ind2 + (imp2 ? "!important" :"") + " } ";
+ var result = cs.zIndex;
+ is(result, String((winning == 1) ? ind1 : ind2),
+ "cascading of " + style_contents.data);
+}
+
+// Test order, and order combined with !important
+do_test("div", false, "div", false, 2);
+do_test("div", false, "div", true, 2);
+do_test("div", true, "div", false, 1);
+do_test("div", true, "div", true, 2);
+
+// Test specificity on a single element
+do_test("div", false, "div.content_class", false, 2);
+do_test("div.content_class", false, "div", false, 1);
+
+// Test specificity across elements
+do_test("body#thebody div", false, "body div.content_class", false, 1);
+do_test("body div.content_class", false, "body#thebody div", false, 2);
+
+// Test specificity combined with !important
+do_test("div.content_class", false, "div", false, 1);
+do_test("div.content_class", true, "div", false, 1);
+do_test("div.content_class", false, "div", true, 2);
+do_test("div.content_class", true, "div", true, 1);
+
+function do_test_greater(sel1, sel2) {
+ do_test(sel1, false, sel2, false, 1);
+ do_test(sel2, false, sel1, false, 2);
+}
+
+function do_test_equal(sel1, sel2) {
+ do_test(sel1, false, sel2, false, 2);
+ do_test(sel2, false, sel1, false, 2);
+}
+
+// Test specificity of contents of :not()
+do_test_equal("div.content_class", "div:not(.wrong_class)");
+do_test_greater("div.content_class.content_class", "div.content_class");
+do_test_greater("div.content_class", "div");
+do_test_greater("div:not(.wrong_class)", "div");
+do_test_greater("div:not(.wrong_class):not(.wrong_class)",
+ "div:not(.wrong_class)");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_ch_ex_no_infloops.html b/layout/style/test/test_ch_ex_no_infloops.html
new file mode 100644
index 0000000000..db9eda20df
--- /dev/null
+++ b/layout/style/test/test_ch_ex_no_infloops.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=678671
+-->
+<head>
+ <title>Test for Bug 678671</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=678671">Mozilla Bug 678671</a>
+<p id="display"></p>
+<div id="content"></div>
+<script type="application/javascript">
+
+/** Test for Bug 678671 **/
+
+/**
+ * Test 'ex' and 'ch' units in every place we possible can to make
+ * sure they don't cause an infinite loop.
+ */
+
+var content = document.getElementById("content");
+var cs = getComputedStyle(content, "");
+
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ function test_val(v) {
+ content.style.setProperty(prop, v, "");
+ isnot(get_computed_value(cs, prop), "",
+ "Setting '" + prop + "' to '" + v + "' should not cause infinite loop");
+ }
+ test_val('3ex');
+ test_val('2ch');
+ function test_replaced_values(value_list) {
+ // For each item in value_list, if it looks like it has a dimension
+ // in it, replace those dimensions with 3ex and 2ch and test it.
+ for (var i = 0; i < value_list.length; ++i) {
+ var value = value_list[i];
+ function try_replace(withval) {
+ var rep = value.replace(/[0-9.]+[a-zA-Z]+/g, withval)
+ if (rep != value) {
+ test_val(rep);
+ }
+ }
+ try_replace('3ex');
+ try_replace('2ch');
+ }
+ }
+ test_replaced_values(info.initial_values);
+ test_replaced_values(info.other_values);
+ content.style.removeProperty(prop);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_change_hint_optimizations.html b/layout/style/test/test_change_hint_optimizations.html
new file mode 100644
index 0000000000..82e149154c
--- /dev/null
+++ b/layout/style/test/test_change_hint_optimizations.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for style change hint optimizations</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTests() {
+
+ /** Test for Bug 1251075 **/
+ function test_bug1251075_div(id) {
+ var utils = SpecialPowers.DOMWindowUtils;
+
+ var div = document.getElementById(id);
+ div.style.display = "";
+
+ var description = div.style.cssText;
+
+ div.firstElementChild.offsetTop;
+ var constructedBefore = utils.framesConstructed;
+
+ div.style.transform = "translateX(10px)";
+ div.firstElementChild.offsetTop;
+ is(utils.framesConstructed, constructedBefore,
+ "adding a transform style to an element with " + description +
+ " should not cause frame reconstruction even when the element " +
+ "has absolutely positioned descendants");
+
+ div.style.display = "none";
+ }
+
+ test_bug1251075_div("bug1251075_a");
+ test_bug1251075_div("bug1251075_b");
+
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="runTests()">
+<div id="bug1251075_a" style="will-change: transform; display:none">
+ <div style="position: absolute; top: 0; left: 0;"></div>
+ <div style="position: fixed; top: 0; left: 0;"></div>
+</div>
+<div id="bug1251075_b" style="filter: blur(3px); display:none">
+ <div style="position: absolute; top: 0; left: 0;"></div>
+ <div style="position: fixed; top: 0; left: 0;"></div>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_clip-path_polygon.html b/layout/style/test/test_clip-path_polygon.html
new file mode 100644
index 0000000000..3c77103ba2
--- /dev/null
+++ b/layout/style/test/test_clip-path_polygon.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+body {padding: 0;margin:0;}
+div {
+ width: 200px;
+ height: 200px;
+ position: fixed;
+ top: 50px;
+ left: 50px;
+ margin: 50px;
+ padding: 50px;
+ border: 50px solid red;
+ transform-origin: 0 0;
+ transform: translate(50px, 50px) scale(0.5);
+ background-color: green;
+ clip-path: polygon(0 0, 200px 0, 0 200px) content-box;
+}
+</style>
+<title>clip-path with polygon() hit test</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<div id="a"></div>
+<p style="margin-top: 110px">
+<script>
+var a = document.getElementById("a");
+isnot(a, document.elementFromPoint(199, 199), "a shouldn't be found");
+isnot(a, document.elementFromPoint(199, 250), "a shouldn't be found");
+isnot(a, document.elementFromPoint(250, 199), "a shouldn't be found");
+isnot(a, document.elementFromPoint(255, 255), "a shouldn't be found");
+isnot(a, document.elementFromPoint(301, 200), "a shouldn't be found");
+isnot(a, document.elementFromPoint(200, 301), "a shouldn't be found");
+is(a, document.elementFromPoint(200, 200), "a should be found");
+is(a, document.elementFromPoint(299, 200), "a should be found");
+is(a, document.elementFromPoint(200, 299), "a should be found");
+is(a, document.elementFromPoint(250, 250), "a should be found");
+</script>
+</html>
diff --git a/layout/style/test/test_color_rounding.html b/layout/style/test/test_color_rounding.html
new file mode 100644
index 0000000000..46698ede3d
--- /dev/null
+++ b/layout/style/test/test_color_rounding.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test rounding of CSS color valus</title>
+ <link rel="author" title="Manish Goregaokar" href="mailto:mgoregaokar@mozilla.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+<div id="colordiv"></div>
+<script>
+
+function do_test(color, computed, reason) {
+ test(function() {
+ var element = document.getElementById('colordiv');
+ // assume this works; this way we clear any previous state
+ element.style.color = "red";
+ element.style.color = color;
+ assert_equals(getComputedStyle(element).color, computed);
+ }, `${reason}: ${color}`);
+}
+
+do_test("rgb(10%, 10%, 10%, 10%)", "rgba(26, 26, 26, 0.1)", "rgb percent-to-int should round");
+do_test("rgb(10%, 10%, 10%)", "rgb(26, 26, 26)", "rgb percent-to-int should round");
+do_test("hsl(0, 0%, 90%)", "rgb(230, 230, 230)", "hsl-to rgb should round");
+do_test("hsla(0, 0%, 90%, 10%)", "rgba(230, 230, 230, 0.1)", "hsl-to rgb should round");
+do_test("rgb(100%, 100%, 0%)", "rgb(255, 255, 0)", "handling of extrema");
+do_test("rgba(100%, 100%, 100%, 100%)", "rgb(255, 255, 255)", "handling of extrema");
+do_test("rgba(100%, 100%, 100%, 0%)", "rgba(255, 255, 255, 0)", "handling of extrema");
+do_test("rgb(255.5, 260, 500, 50)", "rgb(255, 255, 255)", "out of bounds should be handled");
+do_test("rgb(254.5, 254.55, 254.45)", "rgb(255, 255, 254)", "number values should be rounded");
+do_test("rgb(99.8%, 99.9%, 99.7%)", "rgb(254, 255, 254)", "percentage values should be rounded");
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_compute_data_with_start_struct.html b/layout/style/test/test_compute_data_with_start_struct.html
new file mode 100644
index 0000000000..6970270c95
--- /dev/null
+++ b/layout/style/test/test_compute_data_with_start_struct.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for correct handling of aStartStruct parameter to nsRuleNode::Compute*Data</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=216456">Mozilla Bug 216456</a>
+<p id="display">
+ <span id="base"></span>
+ <span id="test"></span>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * The purpose of this test was to test that in the old style system
+ * the nsRuleNode::Compute*Data functions were written correctly.
+ * In particular, in these functions, when the specified value of a
+ * property had unit eCSSUnit_Null, touching the computed data is
+ * forbidden. This is because we sometimes would stop walking up the
+ * rule tree when we find computed data for an initial subsequence of
+ * our rules (i.e., an ancestor rule node) that we can use as a starting
+ * point (aStartStruct) for the computation for the current rule node.
+ *
+ * However, we don't cache style structs in the rule tree in the current
+ * style system code, and property cascading no longer relies on hand
+ * written functions, so this particular failure mode isn't as likely to
+ * happen.
+ */
+
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#base, #test {}", gStyleSheet.cssRules.length)];
+var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#test {}", gStyleSheet.cssRules.length)];
+
+var gBase = getComputedStyle(document.getElementById("base"), "");
+var gTest = getComputedStyle(document.getElementById("test"), "");
+
+function test_property(prop, lower_set, higher_set) {
+ var info = gCSSProperties[prop];
+ if (info.subproperties || info.logical)
+ return;
+
+ gRule1.style.setProperty(prop, info[lower_set][0]);
+ gRule2.style.setProperty(prop, info[higher_set][0]);
+
+ if ("prerequisites" in info) {
+ for (var prereq in info.prerequisites) {
+ gRule2.style.setProperty(prereq, info.prerequisites[prereq], "");
+ }
+ }
+
+ gBase.getPropertyValue(prop);
+ var higher_set_val = gTest.getPropertyValue(prop);
+ gRule2.style.setProperty(prop, info[lower_set][0], "");
+ var lower_set_val = gTest.getPropertyValue(prop);
+ isnot(higher_set_val, lower_set_val, "initial and other values of " + prop + " are different");
+
+ gRule2.style.removeProperty(prop);
+ is(gTest.getPropertyValue(prop), lower_set_val, prop + " is not touched when its value comes from aStartStruct");
+
+ gRule1.style.removeProperty(prop);
+ if ("prerequisites" in info) {
+ for (var prereq in info.prerequisites) {
+ gRule2.style.removeProperty(prereq);
+ }
+ }
+}
+
+function round(lower_set, higher_set) {
+ for (var prop in gCSSProperties)
+ test_property(prop, lower_set, higher_set);
+}
+
+round("other_values", "initial_values");
+round("initial_values", "other_values");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style.html b/layout/style/test/test_computed_style.html
new file mode 100644
index 0000000000..c2dc667f2f
--- /dev/null
+++ b/layout/style/test/test_computed_style.html
@@ -0,0 +1,664 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for miscellaneous computed style issues</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for miscellaneous computed style issues **/
+
+var frame_container = document.getElementById("display");
+var noframe_container = document.getElementById("content");
+
+(function test_bug_595650() {
+ // Test handling of horizontal and vertical percentages for border-radius.
+ var p = document.createElement("p");
+ p.setAttribute("style", "width: 256px; height: 128px");
+ p.style.borderTopLeftRadius = "1.5625%"; /* 1/64 == 4px 2px */
+ p.style.borderTopRightRadius = "5px";
+ p.style.borderBottomRightRadius = "5px 3px";
+ p.style.borderBottomLeftRadius = "1.5625% 3.125%" /* 1/64 1/32 == 4px 4px */
+ var cs = getComputedStyle(p, "");
+
+ frame_container.appendChild(p);
+ is(cs.borderTopLeftRadius, "1.5625%",
+ "computed value of % border-radius, with frame");
+ is(cs.borderTopRightRadius, "5px",
+ "computed value of px border-radius, with frame");
+ is(cs.borderBottomRightRadius, "5px 3px",
+ "computed value of px border-radius, with frame");
+ is(cs.borderBottomLeftRadius, "1.5625% 3.125%",
+ "computed value of % border-radius, with frame");
+
+ noframe_container.appendChild(p);
+ is(cs.borderTopLeftRadius, "1.5625%",
+ "computed value of % border-radius, without frame");
+ is(cs.borderTopRightRadius, "5px",
+ "computed value of px border-radius, without frame");
+ is(cs.borderBottomRightRadius, "5px 3px",
+ "computed value of px border-radius, without frame");
+ is(cs.borderBottomLeftRadius, "1.5625% 3.125%",
+ "computed value of % border-radius, without frame");
+
+ p.remove();
+})();
+
+(function test_bug_1292447() {
+ // Was for bug 595651 which tests that clamping of border-radius
+ // is reflected in computed style.
+ // For compatibility issue, resolved value is computed value now.
+ var p = document.createElement("p");
+ p.setAttribute("style", "width: 190px; height: 90px; border: 5px solid;");
+ p.style.borderRadius = "1000px";
+ var cs = getComputedStyle(p, "");
+
+ frame_container.appendChild(p);
+ is(cs.borderTopLeftRadius, "1000px",
+ "computed value of clamped border radius (top left)");
+ is(cs.borderTopRightRadius, "1000px",
+ "computed value of clamped border radius (top right)");
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of clamped border radius (bottom right)");
+ is(cs.borderBottomLeftRadius, "1000px",
+ "computed value of clamped border radius (bottom left)");
+
+ p.style.overflowY = "scroll";
+ is(cs.borderTopLeftRadius, "1000px",
+ "computed value of clamped border radius (top left, overflow-y)");
+ // Fennec doesn't have scrollbars for overflow:scroll content
+ if (p.clientWidth == p.offsetWidth - 10) {
+ is(cs.borderTopRightRadius, "1000px",
+ "computed value of border radius (top right, overflow-y)");
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of border radius (bottom right, overflow-y)");
+ } else {
+ is(cs.borderTopRightRadius, "1000px",
+ "computed value of clamped border radius (top right, overflow-y)");
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of clamped border radius (bottom right, overflow-y)");
+ }
+ is(cs.borderBottomLeftRadius, "1000px",
+ "computed value of clamped border radius (bottom left, overflow-y)");
+
+ p.style.overflowY = "hidden";
+ p.style.overflowX = "scroll";
+ is(cs.borderTopLeftRadius, "1000px",
+ "computed value of clamped border radius (top left, overflow-x)");
+ is(cs.borderTopRightRadius, "1000px",
+ "computed value of clamped border radius (top right, overflow-x)");
+ // Fennec doesn't have scrollbars for overflow:scroll content
+ if (p.clientHeight == p.offsetHeight - 10) {
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of border radius (bottom right, overflow-x)");
+ is(cs.borderBottomLeftRadius, "1000px",
+ "computed value of border radius (bottom left, overflow-x)");
+ } else {
+ is(cs.borderBottomRightRadius, "1000px",
+ "computed value of clamped border radius (bottom right, overflow-x)");
+ is(cs.borderBottomLeftRadius, "1000px",
+ "computed value of clamped border radius (bottom left, overflow-x)");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_647885_1() {
+ // Test that various background-position styles round-trip correctly
+ var backgroundPositions = [
+ [ "0 0", "0px 0px", "unitless 0" ],
+ [ "0px 0px", "0px 0px", "0 with units" ],
+ [ "0% 0%", "0% 0%", "0%" ],
+ [ "calc(0px) 0", "0px 0px", "0 calc with units x" ],
+ [ "0 calc(0px)", "0px 0px", "0 calc with units y" ],
+ [ "calc(3px - 3px) 0", "0px 0px", "computed 0 calc with units x" ],
+ [ "0 calc(3px - 3px)", "0px 0px", "computed 0 calc with units y" ],
+ [ "calc(0%) 0", "0% 0px", "0% calc x"],
+ [ "0 calc(0%)", "0px 0%", "0% calc y"],
+ [ "calc(3px + 2% - 2%) 0", "calc(0% + 3px) 0px",
+ "computed 0% calc x"],
+ [ "0 calc(3px + 2% - 2%)", "0px calc(0% + 3px)",
+ "computed 0% calc y"],
+ [ "calc(3px - 5px) calc(6px - 7px)", "-2px -1px",
+ "negative pixel width"],
+ [ "", "0% 0%", "initial value" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundPositions.length; ++i) {
+ var test = backgroundPositions[i];
+ p.style.backgroundPosition = test[0];
+ is(cs.backgroundPosition, test[1], "computed value of " + test[2] + " background-position");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_647885_2() {
+ // Test that various background-size styles round-trip correctly
+ var backgroundSizes = [
+ [ "0 0", "0px 0px", "unitless 0" ],
+ [ "0px 0px", "0px 0px", "0 with units" ],
+ [ "0% 0%", "0% 0%", "0%" ],
+ [ "calc(0px) 0", "0px 0px", "0 calc with units horizontal" ],
+ [ "0 calc(0px)", "0px 0px", "0 calc with units vertical" ],
+ [ "calc(3px - 3px) 0", "0px 0px", "computed 0 calc with units horizontal" ],
+ [ "0 calc(3px - 3px)", "0px 0px", "computed 0 calc with units vertical" ],
+ [ "calc(0%) 0", "0% 0px", "0% calc horizontal"],
+ [ "0 calc(0%)", "0px 0%", "0% calc vertical"],
+ [ "calc(3px + 2% - 2%) 0", "calc(0% + 3px) 0px",
+ "computed 0% calc horizontal"],
+ [ "0 calc(3px + 2% - 2%)", "0px calc(0% + 3px)",
+ "computed 0% calc vertical"],
+ [ "calc(3px - 5px) calc(6px - 9px)", "0px 0px", "negative pixel width" ],
+ [ "", "auto", "initial value" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundSizes.length; ++i) {
+ var test = backgroundSizes[i];
+ p.style.backgroundSize = test[0];
+ is(cs.backgroundSize, test[1], "computed value of " + test[2] + " background-size");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_716628() {
+ // Test that various gradient styles round-trip correctly
+ var backgroundImages = [
+ [ "radial-gradient(at 10% bottom, #ffffff, black)",
+ "radial-gradient(at 10% 100%, rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient 1" ],
+ [ "radial-gradient(#ffffff, black)",
+ "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient 2" ],
+ [ "radial-gradient(farthest-corner, #ffffff, black)",
+ "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))",
+ "radial gradient 3" ],
+ [ "linear-gradient(red, blue)",
+ "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient 1" ],
+ [ "linear-gradient(to bottom, red, blue)",
+ "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient 2" ],
+ [ "linear-gradient(to right, red, blue)",
+ "linear-gradient(to right, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient 3" ],
+ [ "linear-gradient(-45deg, red, blue)",
+ "linear-gradient(-45deg, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient with angle in degrees" ],
+ [ "linear-gradient(-0.125turn, red, blue)",
+ "linear-gradient(-45deg, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "linear gradient with angle in turns" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundImages.length; ++i) {
+ var test = backgroundImages[i];
+ p.style.backgroundImage = test[0];
+ is(cs.backgroundImage, test[1], "computed value of " + test[2] + " background-image");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1363349_linear() {
+ const specPrefix = "-webkit-gradient(linear, ";
+ const specSuffix = ", from(blue), to(lime))";
+
+ const expPrefix = "linear-gradient(";
+ const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+ let testcases = [
+ [ "calc(5 + 5) top, calc(10 + 10) top",
+ "to right",
+ "calc(num+num) in position"
+ ],
+ [ "left calc(25% - 10%), right calc(75% + 10%)",
+ "to right bottom",
+ "calc(pct+pct) in position "
+ ]
+ ];
+
+ let p = document.createElement("p");
+ let cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (let test of testcases) {
+ let specifiedStyle = specPrefix + test[0] + specSuffix;
+ let expectedStyle = expPrefix;
+ if (test[1] != "") {
+ expectedStyle += test[1] + ", ";
+ }
+ expectedStyle += expSuffix;
+
+ p.style.backgroundImage = specifiedStyle;
+ is(cs.backgroundImage, expectedStyle,
+ "computed value of -webkit-gradient expression (" + test[2] + ")");
+ p.style.backgroundImage = "";
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1363349_radial() {
+ const specPrefix = "-webkit-gradient(radial, ";
+ const specSuffix = ", from(blue), to(lime))";
+
+ const expPrefix = "radial-gradient(";
+ const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+ let testcases = [
+ [ "1 2, 0, 3 4, calc(1 + 5)",
+ "6px at 3px 4px",
+ "calc(num+num) in radius"
+ ],
+ [ "1 2, calc(1 + 2), 3 4, calc(1 + 5)",
+ "6px at 3px 4px",
+ "calc(num+num) in radius"
+ ],
+ [ "calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5)",
+ "6px at 3px 4px",
+ "calc(num+num) in position and radius"
+ ]
+ ];
+
+ let p = document.createElement("p");
+ let cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (let test of testcases) {
+ let specifiedStyle = specPrefix + test[0] + specSuffix;
+ let expectedStyle = expPrefix;
+ if (test[1] != "") {
+ expectedStyle += test[1] + ", ";
+ }
+ expectedStyle += expSuffix;
+
+ p.style.backgroundImage = specifiedStyle;
+ is(cs.backgroundImage, expectedStyle,
+ "computed value of -webkit-gradient expression (" + test[2] + ")");
+ p.style.backgroundImage = "";
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1241623() {
+ // Test that -webkit-gradient() styles are approximated the way we expect:
+
+ // For compactness, we'll pull out the common prefix & suffix from all of the
+ // specified & expected styles, and construct the full expression on the fly:
+ const specPrefix = "-webkit-gradient(linear, ";
+ const specSuffix = ", from(blue), to(lime))";
+
+ const expPrefix = "linear-gradient(";
+ const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)";
+
+ let testcases = [
+ //
+ // [ legacyDirection,
+ // modernDirection, // (empty string means use default direction)
+ // descriptionOfTestcase ],
+
+ // If start & end are at same point, we just produce a gradient with
+ // the default direction.
+ [ "left top, left top",
+ "",
+ "start & end point are the same" ],
+ [ "40 40, 40 40",
+ "",
+ "start & end point are the same" ],
+ [ "center center, center center",
+ "",
+ "start & end point are the same" ],
+
+ // If start & end use different units in the same coordinate, we generally
+ // can't extract a direction (because we can't know whether arbitrary
+ // percent values are larger or smaller than arbitrary pixel values). So
+ // we produce a gradient in the default direction.
+ [ "left top, 30 100%", // (Note: keywords like "left" are really % vals.)
+ "",
+ "start & end point have different units" ],
+ [ "100% 15, right bottom",
+ "",
+ "start & end point have different units" ],
+ [ "0 0%, 20 20",
+ "",
+ "start & end point have different units" ],
+ [ "0 0, 100% 20",
+ "",
+ "start & end point have different units" ],
+ [ "5% 30, 20 50%",
+ "",
+ "start & end point have different units" ],
+ [ "5% 6%, 20 30",
+ "",
+ "start & end point have different units" ],
+
+ // Gradient starting/ending somewhere arbitrary in middle:
+ [ "center center, right top",
+ "to right top",
+ "from center to right top" ],
+ [ "left top, center center",
+ "to right bottom",
+ "from left top to center" ],
+ [ "10 15, 5 20",
+ "to left bottom",
+ "from arbitrary point to another point in lower-left direction" ],
+
+ // Gradient using negative coordinates:
+ [ "-10 -15, 0 0",
+ "to right bottom",
+ "from negative point to origin" ],
+ [ "-100 10, 20 30",
+ "to right bottom",
+ "from negative-x point to another point in lower-right direction" ],
+ [ "10 -100, 5 10",
+ "to left bottom",
+ "from negative-y point to another point in lower-left direction" ],
+
+ // Diagonal gradient between sides/corners:
+ [ "center top, left center",
+ "to left bottom",
+ "left/bottom-wards, using edge keywords" ],
+ [ "left center, center top",
+ "to right top",
+ "top/right-wards, using edge keywords" ],
+ [ "right center, center top",
+ "to left top",
+ "top/left-wards, using edge keywords" ],
+ [ "right top, center bottom",
+ "to left bottom",
+ "bottom/left-wards, using edge keywords" ],
+ [ "left top, right bottom",
+ "to right bottom",
+ "bottom/right-wards, using edge keywords" ],
+ [ "left bottom, right top",
+ "to right top",
+ "top/right-wards, using edge keywords" ],
+ ];
+
+ let p = document.createElement("p");
+ let cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (let test of testcases) {
+ let specifiedStyle = specPrefix + test[0] + specSuffix;
+ let expectedStyle = expPrefix;
+ if (test[1] != "") {
+ expectedStyle += test[1] + ", ";
+ }
+ expectedStyle += expSuffix;
+
+ p.style.backgroundImage = specifiedStyle;
+ is(cs.backgroundImage, expectedStyle,
+ "computed value of -webkit-gradient expression (" + test[2] + ")");
+ p.style.backgroundImage = "";
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1293164() {
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ var docPath = document.URL.substring(0, document.URL.lastIndexOf("/") + 1);
+
+ var localURL = "url(\"#foo\")";
+ var nonLocalURL = "url(\"foo.svg#foo\")";
+ var resolvedNonLocalURL = "url(\"" + docPath + "foo.svg#foo\")";
+
+ var testStyles = [
+ "maskImage",
+ "backgroundImage",
+ "markerStart",
+ "markerMid",
+ "markerEnd",
+ "clipPath",
+ "filter",
+ "fill",
+ "stroke",
+ ];
+
+ for (var prop of testStyles) {
+ p.style[prop] = localURL;
+ is(cs[prop], localURL, "computed value of " + prop);
+ p.style[prop] = nonLocalURL;
+ is(cs[prop], resolvedNonLocalURL, "computed value of " + prop);
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1347164() {
+ // Test that computed color values are serialized as "rgb()"
+ // IFF they're fully-opaque (and otherwise as "rgba()").
+ var color = [
+ ["rgba(0, 0, 0, 1)", "rgb(0, 0, 0)"],
+ ["rgba(0, 0, 0, 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["hsla(0, 0%, 0%, 1)", "rgb(0, 0, 0)"],
+ ["hsla(0, 0%, 0%, 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ];
+
+ var css_color_4 = [
+ ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgba(0 0 0 / 0.1)", "rgba(0, 0, 0, 0.1)"],
+ ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgb(0 0 0 / 0.2)", "rgba(0, 0, 0, 0.2)"],
+ ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsla(0deg 0% 0% / 0.3)", "rgba(0, 0, 0, 0.3)"],
+ ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsl(0 0% 0% / 0.4)", "rgba(0, 0, 0, 0.4)"],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < color.length; ++i) {
+ var test = color[i];
+ p.style.color = test[0];
+ is(cs.color, test[1], "computed value of " + test[0]);
+ }
+ for (var i = 0; i < css_color_4.length; ++i) {
+ var test = css_color_4[i];
+ p.style.color = test[0];
+ is(cs.color, test[1], "css-color-4 computed value of " + test[0]);
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1357117() {
+ // Test that vendor-prefixed gradient styles round-trip with the same prefix,
+ // or with no prefix.
+ var backgroundImages = [
+ // [ specified style,
+ // expected computed style,
+ // descriptionOfTestcase ],
+ // Linear gradient with legacy-gradient-line (needs prefixed syntax):
+ [ "-webkit-linear-gradient(10deg, red, blue)",
+ "-webkit-linear-gradient(10deg, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "-webkit-linear-gradient with angled legacy-gradient-line" ],
+
+ // Linear gradient with box corner (needs prefixed syntax):
+ [ "-webkit-linear-gradient(top left, red, blue)",
+ "-webkit-linear-gradient(left top, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "-webkit-linear-gradient with box corner" ],
+
+ // Linear gradient with default keyword (should be serialized without keyword):
+ [ "-webkit-linear-gradient(top, red, blue)",
+ "-webkit-linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))",
+ "-webkit-linear-gradient with legacy default direction keyword" ],
+
+ // Radial gradients (should be serialized using modern unprefixed style):
+ [ "-webkit-radial-gradient(contain, red, blue)",
+ "-webkit-radial-gradient(closest-side, rgb(255, 0, 0), rgb(0, 0, 255))",
+ "-webkit-radial-gradient with legacy 'contain' keyword" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundImages.length; ++i) {
+ var test = backgroundImages[i];
+ p.style.backgroundImage = test[0];
+ is(cs.backgroundImage, test[1],
+ "computed value of prefixed gradient expression (" + test[2] + ")");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1367028() {
+ const borderImageSubprops = [
+ "border-image-slice",
+ "border-image-outset",
+ "border-image-width"
+ ];
+ const rectValues = [
+ {
+ values: ["5 5 5 5", "5 5 5", "5 5", "5"],
+ expected: "5",
+ desc: "identical four sides",
+ },
+ {
+ values: ["5 6 5 6", "5 6 5", "5 6"],
+ expected: "5 6",
+ desc: "identical values on each axis",
+ },
+ {
+ values: ["5 6 7 6", "5 6 7"],
+ expected: "5 6 7",
+ desc: "identical values on left and right",
+ },
+ {
+ values: ["5 6 5 7"],
+ desc: "identical values on top and bottom",
+ },
+ {
+ values: ["5 5 6 6", "5 6 6 5"],
+ desc: "identical values on unrelated sides",
+ },
+ {
+ values: ["5 6 7 8"],
+ desc: "different values on all sides",
+ },
+ ];
+
+ let frameContainer = document.getElementById("display");
+ let p = document.createElement("p");
+ frameContainer.appendChild(p);
+ let cs = getComputedStyle(p);
+
+ for (let prop of borderImageSubprops) {
+ for (let {values, expected, desc} of rectValues) {
+ for (let value of values) {
+ p.style.setProperty(prop, value);
+ is(cs.getPropertyValue(prop),
+ expected ? expected : value, `${desc} for ${prop}`);
+ p.style.removeProperty(prop);
+ }
+ }
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1378368() {
+ // Test that negative results of calc()s in basic-shapes (e.g. polygon()) should
+ // not be clamped to 0px.
+ var clipPaths = [
+ // [ specified style,
+ // expected computed style,
+ // descriptionOfTestcase ],
+ // polygon:
+ [ "polygon(calc(10px - 20px) 0px, 100px 100px, 0px 100px)",
+ "polygon(-10px 0px, 100px 100px, 0px 100px)",
+ "polygon with negative calc() coordinates" ],
+ // inset:
+ [ "inset(calc(10px - 20px))",
+ "inset(-10px)",
+ "inset with negative calc() coordinates" ],
+ ];
+
+ var p = document.createElement("p");
+ var cs = getComputedStyle(p, "");
+ frame_container.appendChild(p);
+
+ for (let test of clipPaths) {
+ p.style.clipPath = test[0];
+ is(cs.clipPath, test[1],
+ "computed value of clip-path for basic-shapes (" + test[2] + ")");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1418433() {
+ // Test that the style data read through getComputedStyle is always up-to-date,
+ // even for non-displayed elements.
+
+ var d = document.createElement("div");
+ d.setAttribute("id", "nonDisplayedDiv");
+ var cs = getComputedStyle(d, null);
+ noframe_container.appendChild(d);
+
+ // Test for stylesheet change, i.e., added/changed/removed
+ var style = document.getElementById("style");
+ is(cs.height, "auto",
+ "computed value of display none element (before testing)");
+ style.textContent = "#nonDisplayedDiv { height: 100px; }";
+ is(cs.height, "100px",
+ "computed value of display none element (sheet added)");
+ style.textContent = "#nonDisplayedDiv { height: 10px; }";
+ is(cs.height, "10px",
+ "computed value of display none element (sheet changed)");
+ style.textContent = "";
+ is(cs.height, "auto",
+ "computed value of display none element (sheet removed)");
+
+ // Test for rule change, i.e., added/changed/removed
+ var styleSheet = style.sheet;
+ is(cs.width, "auto",
+ "computed value of display none element (before testing)");
+ styleSheet.insertRule("#nonDisplayedDiv { width: 100px; }", 0);
+ is(cs.width, "100px",
+ "computed value of display none element (rule added)");
+ styleSheet.deleteRule(0);
+ styleSheet.insertRule("#nonDisplayedDiv { width: 10px; }", 0);
+ is(cs.width, "10px",
+ "computed value of display none element (rule changed)");
+ styleSheet.deleteRule(0);
+ is(cs.width, "auto",
+ "computed value of display none element (rule removed)");
+
+ d.remove();
+})();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_bfcache_display_none.html b/layout/style/test/test_computed_style_bfcache_display_none.html
new file mode 100644
index 0000000000..8322e4977a
--- /dev/null
+++ b/layout/style/test/test_computed_style_bfcache_display_none.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<title>Test for getting the computed style on the root node of a display:none subtree in a document in the bfcache</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1377010">Mozilla Bug 1377010</a>
+<p id="display"></p>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+let testDiv;
+let loadedPromiseResolve;
+
+const TEST_PATH = "http://mochi.test:8888/tests/layout/style/test/";
+const TEST_FILE1 = TEST_PATH + "file_computed_style_bfcache_display_none.html";
+const TEST_FILE2 = TEST_PATH + "file_computed_style_bfcache_display_none2.html";
+
+// Open a new window.
+const w = window.open(TEST_FILE1);
+waitForLoadMessage().then(() => {
+ // Take a reference to a node in the new window.
+ testDiv = w.document.getElementById('div');
+
+ // Open a new document so that the test div now refers to a node in a
+ // document in the bfcache.
+ w.location = TEST_FILE2;
+ return waitForLoadMessage();
+}).then(() => {
+ // Compute styles for the node in the bfcache document.
+ is(w.getComputedStyle(testDiv).opacity, '1');
+
+ // Restore the bfcache document.
+ return goBack(w);
+}).then(() => {
+ // Fetch the style once again.
+ is(w.getComputedStyle(testDiv).opacity, '1');
+
+ w.close();
+ SimpleTest.finish();
+});
+
+window.addEventListener('message', e => {
+ if (e.data === 'loaded' && loadedPromiseResolve) {
+ loadedPromiseResolve();
+ loadedPromiseResolve = undefined;
+ }
+});
+
+function waitForLoadMessage() {
+ return new Promise(resolve => {
+ loadedPromiseResolve = resolve;
+ });
+}
+
+function goBack(win) {
+ return new Promise(resolve => {
+ win.onpagehide = e => resolve(win);
+ win.history.back();
+ });
+}
+</script>
diff --git a/layout/style/test/test_computed_style_difference.html b/layout/style/test/test_computed_style_difference.html
new file mode 100644
index 0000000000..f4008ff476
--- /dev/null
+++ b/layout/style/test/test_computed_style_difference.html
@@ -0,0 +1,104 @@
+<!doctype html>
+<title>Test that the difference of the computed style of an element is always correctly propagated</title>
+<!--
+ There are CSS property changes that don't have an effect in computed style.
+
+ It's relatively easy to return `nsChangeHint(0)` for the case where the
+ property changes but it should have no rendering difference.
+
+ That's however incorrect, since if it's an inherited property, or a
+ descendant explicitly inherits it, we should still propagate the change
+ downwards.
+
+ This test tests that computed style diffing is correct.
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="property_database.js"></script>
+<div id="outer">
+ <div id="inner"></div>
+</div>
+<script>
+// We need to skip checking for properties for which the value returned by
+// getComputedStyle depends on the parent.
+//
+// TODO(emilio): We could test a subset of these, see below.
+const kWhitelist = [
+ // Could test display values that don't force blockification of children.
+ "display",
+
+ // Could avoid testing only the ones that have percentages.
+ "transform",
+ "transform-origin",
+ "perspective-origin",
+
+ "padding-bottom",
+ "padding-left",
+ "padding-right",
+ "padding-top",
+ "padding-inline-end",
+ "padding-inline-start",
+ "padding-block-end",
+ "padding-block-start",
+
+ "margin-bottom",
+ "margin-left",
+ "margin-right",
+ "margin-top",
+ "margin-inline-end",
+ "margin-inline-start",
+ "margin-block-end",
+ "margin-block-start",
+
+ "width",
+ "height",
+ "block-size",
+ "inline-size",
+
+ "min-height",
+ "min-width",
+ "min-block-size",
+ "min-inline-size",
+];
+
+const outer = document.getElementById("outer");
+const inner = document.getElementById("inner");
+
+function testValue(prop, value) {
+ outer.style.setProperty(prop, value);
+ const computed = getComputedStyle(outer).getPropertyValue(prop);
+ assert_equals(
+ getComputedStyle(inner).getPropertyValue(prop), computed,
+ "Didn't handle the inherited change correctly?"
+ )
+}
+
+// Note that we intentionally ignore the "prerequisites" here, since that's
+// the most likely place where the diffing could potentially go wrong.
+function testProperty(prop, info) {
+ // We only care about longhands, changing shorthands is not that interesting,
+ // since we're interested of changing as little as possible, and changing
+ // them would be equivalent to changing all the longhands at the same time.
+ if (info.type !== CSS_TYPE_LONGHAND)
+ return;
+ if (kWhitelist.includes(prop))
+ return;
+
+ inner.style.setProperty(prop, "inherit");
+ for (const v of info.initial_values)
+ testValue(prop, v);
+ for (const v of info.other_values)
+ testValue(prop, v);
+ // Test again the first value so that we test changing to it, not just from
+ // it.
+ //
+ // TODO(emilio): We could test every value against every-value if we wanted,
+ // might be worth it.
+ testValue(prop, info.initial_values[0]);
+
+ inner.style.removeProperty(prop);
+}
+
+for (let prop in gCSSProperties)
+ test(() => testProperty(prop, gCSSProperties[prop]), "Diffing for " + prop);
+</script>
diff --git a/layout/style/test/test_computed_style_grid_with_pseudo.html b/layout/style/test/test_computed_style_grid_with_pseudo.html
new file mode 100644
index 0000000000..24eb520776
--- /dev/null
+++ b/layout/style/test/test_computed_style_grid_with_pseudo.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1350780
+-->
+<head>
+<title>Test for Bug 1350780</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<style>
+#container {
+ width: 100px;
+}
+
+.gridBefore::before {
+ content: "";
+ display: grid;
+ grid-template-columns: auto;
+}
+
+.gridBeforeNoContent::before {
+ display: grid;
+ grid-template-columns: 40px;
+}
+</style>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function checkTemplateWithData(data) {
+ let obj = document.createElement("div");
+
+ // We need either a template or an additionalClass.
+ if (typeof(data.template != "undefined")) {
+ obj.style.display = "grid";
+ obj.style.gridTemplateColumns = data.template;
+ }
+
+ if (typeof(data.additionalClass != "undefined")) {
+ obj.className = data.additionalClass;
+ }
+
+ let container = document.getElementById("container");
+ container.appendChild(obj);
+
+ let computedStyle = getComputedStyle(obj, data.pseudo);
+ let computedTemplate = computedStyle.getPropertyValue("grid-template-columns");
+
+ let message = "Got expected template with pseudo " + data.pseudo;
+ if (typeof(data.additionalClass != "undefined")) {
+ message += " with class " + data.additionalClass;
+ }
+ message += ".";
+
+ is(computedTemplate, data.expected, message);
+
+ container.removeChild(obj);
+}
+
+function runTest() {
+ let dataToTest = [
+ { template: "40px",
+ pseudo: "::selection",
+ expected: "none"},
+ { template: "40px",
+ pseudo: "::before",
+ expected: "none" },
+ { additionalClass: "gridBefore",
+ pseudo: "::before",
+ expected: "100px" },
+ { additionalClass: "gridBeforeNoContent",
+ pseudo: "::before",
+ expected: "40px" },
+ ];
+
+ for (let i = 0; i < dataToTest.length; ++i) {
+ checkTemplateWithData(dataToTest[i]);
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</head>
+<body onload="runTest()">
+<div id="container"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1350780">Mozilla Bug 1350780</a>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_in_created_document.html b/layout/style/test/test_computed_style_in_created_document.html
new file mode 100644
index 0000000000..72ff0f5921
--- /dev/null
+++ b/layout/style/test/test_computed_style_in_created_document.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for bug 1398619</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+SimpleTest.waitForExplicitFinish();
+let referenceFontSize = getComputedStyle(document.body).fontSize;
+
+function checkComputedStyle(desc, doc) {
+ try {
+ let fontSize = getComputedStyle(doc.body).fontSize;
+ is(fontSize, referenceFontSize, `${desc}: get computed font-size`);
+ } catch (e) {
+ ok(false, `${desc}: fail to get computed font-size, ${e}`);
+ }
+}
+
+async function runTest() {
+ // DOMParser
+ {
+ let parser = new DOMParser();
+ let doc = parser.parseFromString("<body>", "text/html");
+ checkComputedStyle("DOMParser", doc);
+ }
+ // DOMImplementation
+ {
+ let doc = document.implementation.createHTMLDocument("");
+ checkComputedStyle("DOMImplementation", doc);
+ }
+ // XMLHttpRequest
+ {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "empty.html");
+ xhr.responseType = "document";
+ let promise = new Promise(resolve => {
+ xhr.onload = resolve;
+ });
+ xhr.send();
+ await promise;
+ checkComputedStyle("XMLHttpRequest", xhr.responseXML);
+ }
+}
+runTest()
+ .catch(e => ok(false, `Exception: ${e}`))
+ .then(() => SimpleTest.finish());
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_min_size_auto.html b/layout/style/test/test_computed_style_min_size_auto.html
new file mode 100644
index 0000000000..12b4e48b46
--- /dev/null
+++ b/layout/style/test/test_computed_style_min_size_auto.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=763689
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test behavior of 'min-height:auto' and 'min-width:auto' (Bug 763689 and Bug 1304636)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=763689">Mozilla Bug 763689</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1304636">Mozilla Bug 1304636</a>
+<body>
+<div id="display">
+ <div id="block-item">abc</div>
+
+ <div style="display: flex">
+ <div id="horizontal-flex-item">abc</div>
+ <div id="horizontal-flex-item-OH" style="overflow: hidden">def</div>
+ </div>
+
+ <div style="display: flex; flex-direction: column">
+ <div id="vertical-flex-item">abc</div>
+ <div id="vertical-flex-item-OH" style="overflow: hidden">def</div>
+ </div>
+
+ <div style="display: grid">
+ <div id="grid-item"></div>
+ <div id="grid-item-OH" style="overflow: hidden"></div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/**
+ * Test 'min-height:auto' and 'min-width:auto' (Bug 763689 and Bug 1304636)
+ * ========================================================
+ * This test checks the computed-style value of the "auto" keyword introduced
+ * for the "min-height" and "min-width" properties in CSS3 Flexbox Section 4.5
+ * and CSS3 Grid Section 6.2.
+ * https://www.w3.org/TR/css-flexbox-1/#min-size-auto
+ * https://www.w3.org/TR/css-grid-1/#grid-item-sizing
+ *
+ * Quoting that chunk of spec:
+ * # auto
+ * # On a flex item whose overflow is visible in the main axis,
+ * # when specified on the flex item’s main-axis min-size property,
+ * # specifies an automatic minimum size. It otherwise computes to 0
+ * # (unless otherwise defined by a future specification).
+ *
+ */
+
+// Given an element ID, this function sets the corresponding
+// element's inline-style min-width and min-height explicitly to "auto".
+function setElemMinSizesToAuto(aElemId) {
+ var elem = document.getElementById(aElemId);
+
+ is(elem.style.minWidth, "", "min-width should be initially unset");
+ elem.style.minWidth = "auto";
+ is(elem.style.minWidth, "auto", "min-width should accept 'auto' value");
+
+ is(elem.style.minHeight, "", "min-height should be initially unset");
+ elem.style.minHeight = "auto";
+ is(elem.style.minHeight, "auto", "min-height should accept 'auto' value");
+}
+
+// Given an element ID, this function compares the corresponding element's
+// computed min-width and min-height against expected values.
+function checkElemMinSizes(aElemId,
+ aExpectedMinWidth,
+ aExpectedMinHeight)
+{
+ var elem = document.getElementById(aElemId);
+ is(window.getComputedStyle(elem).minWidth, aExpectedMinWidth,
+ "checking min-width of " + aElemId);
+
+ is(window.getComputedStyle(elem).minHeight, aExpectedMinHeight,
+ "checking min-height of " + aElemId);
+}
+
+// This function goes through all the elements we're interested in
+// and checks their computed min-sizes against expected values,
+// farming out each per-element job to checkElemMinSizes.
+function checkAllTheMinSizes() {
+ // This is the normal part -- generally, the default value of "min-width"
+ // and "min-height" (auto) computes to "0px".
+ checkElemMinSizes("block-item", "0px", "0px");
+
+ // ...but for a flex item or grid item, "min-width: auto" and
+ // "min-height: auto" both compute to "auto" (even in cases where
+ // we know it'll actually resolve to 0 in layout, like for example
+ // when the item has "overflow:hidden").
+ checkElemMinSizes("horizontal-flex-item", "auto", "auto");
+ checkElemMinSizes("horizontal-flex-item-OH", "auto", "auto");
+ checkElemMinSizes("vertical-flex-item", "auto", "auto");
+ checkElemMinSizes("vertical-flex-item-OH", "auto", "auto");
+ checkElemMinSizes("grid-item", "auto", "auto");
+ checkElemMinSizes("grid-item-OH", "auto", "auto");
+}
+
+// Main test function
+function main() {
+ // First: check that min-sizes are what we expect, with min-size properties
+ // at their initial value.
+ checkAllTheMinSizes();
+
+ // Now, we *explicitly* set min-size properties to "auto"...
+ var elemIds = [ "block-item",
+ "horizontal-flex-item",
+ "horizontal-flex-item-OH",
+ "vertical-flex-item",
+ "vertical-flex-item-OH",
+ "grid-item",
+ "grid-item-OH"];
+ elemIds.forEach(setElemMinSizesToAuto);
+
+ // ...and try again (should have the same result):
+ checkAllTheMinSizes();
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_no_flush.html b/layout/style/test/test_computed_style_no_flush.html
new file mode 100644
index 0000000000..2caea9d294
--- /dev/null
+++ b/layout/style/test/test_computed_style_no_flush.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1363805: We only restyle as little as needed
+</title>
+<link rel="author" href="mailto:wpan@mozilla.com" title="Wei-Cheng Pan">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+.black {
+ background-color: black;
+}
+.black + div {
+ background-color: gray;
+}
+</style>
+<div id="container">
+ <div>
+ <div id="foo">
+ </div>
+ <div id="bar">
+ </div>
+ </div>
+</div>
+<script>
+function flushStyle () {
+ getComputedStyle(document.body).width;
+}
+
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+const container = document.querySelector('#container');
+const foo = document.querySelector('#foo');
+const bar = document.querySelector('#bar');
+
+flushStyle();
+let currentRestyleGeneration = utils.restyleGeneration;
+
+// No style changed, so we should not restyle.
+getComputedStyle(foo).backgroundColor;
+is(utils.restyleGeneration, currentRestyleGeneration,
+ "Shouldn't restyle anything if no style changed");
+
+// foo's parent has changed, must restyle.
+container.classList.toggle('black');
+getComputedStyle(foo).backgroundColor;
+isnot(utils.restyleGeneration, currentRestyleGeneration,
+ "Should have restyled something");
+
+currentRestyleGeneration = utils.restyleGeneration;
+
+// The change of foo should not affect its parent.
+foo.classList.toggle('black');
+getComputedStyle(container).backgroundColor;
+is(utils.restyleGeneration, currentRestyleGeneration,
+ "Shouldn't restyle anything if no style changed");
+
+// It should restyle for foo's later sibling.
+getComputedStyle(bar).backgroundColor;
+isnot(utils.restyleGeneration, currentRestyleGeneration,
+ "Should have restyled something");
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_computed_style_no_pseudo.html b/layout/style/test/test_computed_style_no_pseudo.html
new file mode 100644
index 0000000000..efb0dda7b4
--- /dev/null
+++ b/layout/style/test/test_computed_style_no_pseudo.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=505515
+-->
+<head>
+ <title>Test for Bug 505515</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display { color: black; background: white; }
+ #display span { position: relative; display: inline-block; }
+ #display:first-line { color: blue; }
+
+ </style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=505515">Mozilla Bug 505515</a>
+<p id="display" style="width: 30em">This <span id="sp">is</span> some text in which the first line is in a different color.</p>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/** Test for Bug 505515 **/
+
+function run() {
+ var p = document.getElementById("display");
+ var span = document.getElementById("sp");
+
+ isnot(span.offsetWidth, 0,
+ "span should have width (and we flushed layout)");
+ is(getComputedStyle(p, "").color, "rgb(0, 0, 0)",
+ "p should be black too");
+
+ let spanStyle = getComputedStyle(span, "");
+ let width = spanStyle.width;
+
+ isnot(width.indexOf("px"), -1,
+ "should be able to get the used value")
+ is(width, spanStyle.width,
+ "shouldn't lose track of the frame");
+ is(spanStyle.color, "rgb(0, 0, 0)",
+ "span should be black");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_computed_style_prefs.html b/layout/style/test/test_computed_style_prefs.html
new file mode 100644
index 0000000000..0f297477d6
--- /dev/null
+++ b/layout/style/test/test_computed_style_prefs.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that preffed off properties do not appear in computed style</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=919594">Mozilla Bug 919594</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test that preffed off properties do not appear in computed style **/
+
+function testWithAllPrefsDisabled() {
+ let exposedProperties = Object.keys(gCS).map(i => gCS[i]);
+
+ // Store the number of properties for later tests to use.
+ gLengthWithAllPrefsDisabled = gCS.length;
+
+ // Check that all of the properties behind the prefs are not exposed.
+ for (let pref in gProps) {
+ for (let prop of gProps[pref]) {
+ ok(!exposedProperties.includes(prop), prop + " not exposed when prefs are false");
+ }
+ }
+}
+
+function testWithOnePrefEnabled(aPref) {
+ let exposedProperties = Object.keys(gCS).map(i => gCS[i]);
+
+ // Check that the number of properties on the object is as expected.
+ is(gCS.length, gLengthWithAllPrefsDisabled + gProps[aPref].length, "length when " + aPref + " is true");
+
+ // Check that the properties corresponding to aPref are exposed.
+ for (let prop of gProps[aPref]) {
+ ok(exposedProperties.includes(prop), prop + " exposed when " + aPref + " is true");
+ }
+}
+
+function step() {
+ if (gTestIndex == gTests.length) {
+ // Reached the end of the tests.
+ SimpleTest.finish();
+ return;
+ }
+
+ if (gPrefsPushed) {
+ // We've just finished running one tests. Pop the prefs and go on to
+ // the next test.
+ gTestIndex++;
+ gPrefsPushed = false;
+ SpecialPowers.popPrefEnv(step);
+ return;
+ }
+
+ // About to run one test. Push the prefs and run it.
+ let fn = gTests[gTestIndex].fn;
+ gPrefsPushed = true;
+ SpecialPowers.pushPrefEnv(gTests[gTestIndex].settings,
+ function() { fn(); SimpleTest.executeSoon(step); });
+}
+
+// ----
+
+var gProps = {
+ "layout.css.backdrop-filter.enabled": ["backdrop-filter"],
+};
+
+var gCS = getComputedStyle(document.body, "");
+var gLengthWithAllPrefsDisabled;
+
+var gTestIndex = 0;
+var gPrefsPushed = false;
+var gTests = [
+ // First, test when all of the prefs are disabled.
+ { settings: { set: Object.keys(gProps).map(x => [x, false]) },
+ fn: testWithAllPrefsDisabled },
+ // Then, test each pref enabled individually.
+ ...Object.keys(gProps).map(p =>
+ ({ settings: { set: Object.keys(gProps).map(x => [x, x == p]) },
+ fn: testWithOnePrefEnabled.bind(null, p) }))
+];
+
+SimpleTest.waitForExplicitFinish();
+step();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_condition_text.html b/layout/style/test/test_condition_text.html
new file mode 100644
index 0000000000..e2462979c2
--- /dev/null
+++ b/layout/style/test/test_condition_text.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814907
+-->
+<head>
+ <title>Test for Bug 814907</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+ @media all {}
+ @media only color {}
+ @media (color ) {}
+ @media color \0061ND ( monochrome ) {}
+ @media (max-width: 200px), (color) {}
+
+ @supports(color: green){}
+ @supports (color: green) {}
+ @supports ((color: green)) {}
+ @supports (color: green) and (color: blue) {}
+ @supports ( Font: 20px serif ! Important) {}
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=814907">Mozilla Bug 814907</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 814907 **/
+
+function runTest()
+{
+ // re-parse the style sheet with the pref turned on
+ var style = document.getElementById("style");
+ style.textContent += " ";
+
+ var sheet = style.sheet;
+
+ var conditions = [
+ "all",
+ "only color",
+ "(color)",
+ "color and (monochrome)",
+ "(max-width: 200px), (color)",
+ "(color: green)",
+ "(color: green)",
+ "((color: green))",
+ "(color: green) and (color: blue)",
+ "( Font: 20px serif ! Important)"
+ ];
+
+ is(sheet.cssRules.length, conditions.length);
+
+ for (var i = 0; i < sheet.cssRules.length; i++) {
+ var rule = sheet.cssRules[i];
+ is(rule.conditionText, conditions[i], "rule " + i + " has expected conditionText");
+ if (rule.type == CSSRule.MEDIA_RULE) {
+ is(rule.conditionText, rule.media.mediaText, "rule " + i + " conditionText matches media.mediaText");
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html b/layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html
new file mode 100644
index 0000000000..6d80b2bb7b
--- /dev/null
+++ b/layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Test for chrome-only rules in constructable stylesheets (in content)</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ add_task(async function chrome_rules_constructable_stylesheets_in_content() {
+ let sheet = new CSSStyleSheet();
+ sheet.replaceSync(".foo { -moz-default-appearance: none }");
+ is(sheet.cssRules[0].style.length, 0, "Should not parse chrome-only property in content document");
+ });
+
+ add_task(async function chrome_rules_constructable_stylesheets_in_content() {
+ let sheet = new CSSStyleSheet({ baseURL: "chrome://browser/content/browser.xhtml" })
+ sheet.replaceSync(".foo { -moz-default-appearance: none }");
+ is(sheet.cssRules[0].style.length, 0, "Should not parse chrome-only property in content document, even with chrome baseURL");
+ });
+</script>
diff --git a/layout/style/test/test_counter_descriptor_storage.html b/layout/style/test/test_counter_descriptor_storage.html
new file mode 100644
index 0000000000..bb91b6d12c
--- /dev/null
+++ b/layout/style/test/test_counter_descriptor_storage.html
@@ -0,0 +1,268 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for parsing, storage and serialization of CSS @counter-style descriptor values</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=966166">Mozilla Bug 966166</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+var gStyleElement = document.createElement("style");
+gStyleElement.setAttribute("type", "text/css");
+document.getElementsByTagName("head")[0].appendChild(gStyleElement);
+var gSheet = gStyleElement.sheet;
+gSheet.insertRule(
+ "@counter-style test { system: extends decimal }", 0);
+var gRule = gSheet.cssRules[0];
+
+function set_rule(ruleText) {
+ gSheet.deleteRule(0);
+ gSheet.insertRule("@counter-style test { " + ruleText + " }", 0);
+ gRule = gSheet.cssRules[0];
+}
+
+function run_tests(tests) {
+ for (var desc in tests) {
+ var items = tests[desc];
+ for (var i in items) {
+ var item = items[i];
+ var ref = item[0];
+ if (ref === null) {
+ ref = gRule[desc];
+ }
+ for (var j in item) {
+ if (item[j] !== null) {
+ gRule[desc] = item[j];
+ is(gRule[desc], ref,
+ "setting '" + item[j] + "' on '" + desc + "'");
+ }
+ }
+ }
+ }
+}
+
+function test_system_dep_desc() {
+ // for system requires at least one symbol
+ var oneSymbolTests = [
+ [null, "", "0"],
+ ["x y", "x y"],
+ ["\"x\"", "'x'"],
+ ["\\-", "\\2D"],
+ ["\\*", "\\2A"],
+ ];
+ // for system requires at least two symbols
+ var twoSymbolsTests = [
+ [null, "", "0", "x", "\"x\""],
+ ["x y", "x y"],
+ ["\"x\" \"y\"", "'x' 'y'"],
+ ];
+ var info = [
+ {
+ system: "cyclic",
+ base: "symbols: x",
+ base_tests: {
+ system: "cyclic",
+ symbols: "x"
+ },
+ tests: {
+ system: [
+ [null, "", "symbolic"],
+ ["cyclic", "Cyclic"],
+ ],
+ symbols: oneSymbolTests
+ }
+ },
+ {
+ system: "fixed",
+ base: "symbols: x",
+ base_tests: {
+ system: "fixed",
+ symbols: "x"
+ },
+ tests: {
+ system: [
+ [null, "", "symbolic"],
+ ["fixed 0"],
+ ["fixed", "FixeD"],
+ ["fixed 1", "FixeD 1"],
+ ["fixed -1"],
+ [null, "fixed a", "fixed \"0\"", "fixed 0 1"],
+ ],
+ symbols: oneSymbolTests
+ }
+ },
+ {
+ system: "symbolic",
+ base: "symbols: x",
+ base_tests: {
+ system: "symbolic",
+ symbols: "x"
+ },
+ tests: {
+ system: [
+ [null, "", "cyclic"],
+ ["symbolic", "SymBolic"],
+ ],
+ symbols: oneSymbolTests
+ }
+ },
+ {
+ system: "alphabetic",
+ base: "symbols: x y",
+ base_tests: {
+ system: "alphabetic",
+ symbols: "x y"
+ },
+ tests: {
+ system: [
+ [null, "", "cyclic"],
+ ["alphabetic", "AlphaBetic"],
+ ],
+ symbols: twoSymbolsTests
+ }
+ },
+ {
+ system: "numeric",
+ base: "symbols: x y",
+ base_tests: {
+ system: "numeric",
+ symbols: "x y"
+ },
+ tests: {
+ system: [
+ [null, "", "cyclic"],
+ ["numeric", "NumEric"],
+ ],
+ symbols: twoSymbolsTests
+ }
+ },
+ {
+ system: "additive",
+ base: "additive-symbols: 0 x",
+ base_tests: {
+ system: "additive",
+ additiveSymbols: "0 x"
+ },
+ tests: {
+ system: [
+ [null, "", "cyclic"],
+ ],
+ additiveSymbols: [
+ [null, "", "x", "0", "\"x\"", "1 x, 0", "0 x, 1 y"],
+ ["0 x", "x 0"],
+ ["1 y, 0 x", "y 1, 0 x", "1 y, x 0", "y 1, x 0"],
+ ["1 \"0\"", "\"0\" 1", "1 '0'"],
+ ]
+ }
+ },
+ {
+ system: "extends decimal",
+ base: "",
+ base_tests: {
+ system: "extends decimal",
+ symbols: "",
+ additiveSymbols: ""
+ },
+ tests: {
+ system: [
+ [null, "extends", "fixed", "cyclic", "extends symbols('*')"],
+ ["extends cjk-decimal", "ExTends cjk-decimal", "extends CJK-decimal"],
+ ],
+ symbols: [
+ [null, "x", "x y"],
+ ],
+ additiveSymbols: [
+ [null, "0 x", "1 y, 0 x"],
+ ]
+ }
+ }
+ ];
+ for (var i = 0; i < info.length; i++) {
+ var item = info[i];
+ set_rule("system: " + item.system + "; " + item.base);
+ for (var desc in item.base_tests) {
+ is(gRule[desc], item.base_tests[desc],
+ "checking base value of '" + desc + "' " +
+ "for system '" + item.system + "'");
+ }
+ run_tests(item.tests);
+ }
+}
+
+function test_system_indep_desc() {
+ var tests = {
+ name: [
+ [null, "", "-", " ", "a b"],
+ [null, "decimal", "none", "Decimal", "NONE"],
+ ["cjk-decimal", "CJK-Decimal", "cjk-Decimal"],
+ ["X"],
+ ["x", "\\78"],
+ ["\\-", "\\2D"],
+ ],
+ negative: [
+ [null, "-", "", "0", "a b c"],
+ ["\"-\"", "'-'", "\"\\2D\""],
+ ["\\-", "\\2D"],
+ ["a b"],
+ ["\"(\" \")\"", "'(' ')'"],
+ ],
+ prefix: [
+ [null, "0", "-", " ", "a b"],
+ ["a"],
+ ["\"a\""],
+ ],
+ suffix: [
+ [null, "0", "-", " ", "a b"],
+ ["a"],
+ ["\"a\""],
+ ],
+ range: [
+ ["auto", "auTO"],
+ ["infinite infinite", "INFinite inFinite"],
+ ["0 infinite", "0 INFINITE"],
+ ["infinite 100"],
+ ["1 1"],
+ ["0 100", "0 100"],
+ ["0 100, 2 300, -1 1, infinite -100"],
+ [null, "0", "0 a", "a 0"],
+ [null, "1 -1", "1 -1, 0 100", "-1 1, 100 0"],
+ ],
+ pad: [
+ ["0 \"\"", "\"\" 0"],
+ ["1 a", "a 1", "1 a", "\\61 1"],
+ [null, "0", "\"\"", "0 0", "a a", "0 a a"],
+ ],
+ fallback: [
+ [null, "", "-", "0", "a b", "symbols('*')"],
+ ["a"],
+ ["A"],
+ ["decimal", "Decimal"],
+ ],
+ speakAs: [
+ [null, "", "-", "0", "a b", "symbols('*')"],
+ ["auto", "AuTo"],
+ ["bullets", "BULLETs"],
+ ["numbers", "NumBers"],
+ ["words", "WordS"],
+ // Currently spell-out is not supported, so it should be treated
+ // as an invalid value.
+ [null, "spell-out", "Spell-Out"],
+ ["a"],
+ ["A"],
+ ["decimal", "Decimal"],
+ ],
+ };
+ set_rule("system: extends decimal");
+ run_tests(tests);
+}
+
+test_system_dep_desc();
+test_system_indep_desc();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_counter_style.html b/layout/style/test/test_counter_style.html
new file mode 100644
index 0000000000..58f5763451
--- /dev/null
+++ b/layout/style/test/test_counter_style.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for css3-counter-style (Bug 966166)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ #ol_test, #ol_ref {
+ display: inline-block;
+ list-style-position: inside;
+ }
+ #ol_test { list-style-type: test; }
+ #ol_ref { list-style-type: ref; }
+ #div_test, #div_ref {
+ display: inline-block;
+ counter-reset: a -1;
+ }
+ #div_test::before { content: counter(a, test); }
+ #div_ref::before { content: counter(a, ref); }
+ </style>
+ <style type="text/css" id="counter">
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=966166">Mozilla Bug 966166</a>
+<div id="display"></div>
+<ol id="ol_test" start="-1"><li></li></ol><br>
+<ol id="ol_ref" start="-1"><li></li></ol><br>
+<div id="div_test"></div><br>
+<div id="div_ref"></div><br>
+<pre id="test">
+<script type="application/javascript">
+var gOlTest = document.getElementById("ol_test"),
+ gOlRef = document.getElementById("ol_ref"),
+ gDivTest = document.getElementById("div_test"),
+ gDivRef = document.getElementById("div_ref"),
+ gCounterSheet = document.getElementById("counter").sheet;
+
+var testRule, refRule;
+
+var basicStyle = "system: extends decimal; range: infinite infinite; ";
+var info = [
+ ["system",
+ "system: fixed -1; symbols: xxx;",
+ "system: fixed; symbols: xxx;"],
+ ["system",
+ "system: extends decimal;",
+ "system: extends cjk-ideographic;"],
+ ["negative", "", "negative: '((' '))';"],
+ ["negative", "", "negative: '---';"],
+ ["prefix", "", "prefix: '###';"],
+ ["suffix", "", "suffix: '###';"],
+ ["range",
+ "fallback: cjk-ideographic;",
+ "fallback: cjk-ideographic; range: 10 infinite;"],
+ ["pad", "", "pad: 10 '0';"],
+ ["fallback",
+ "range: 0 infinite;",
+ "range: 0 infinite; fallback: cjk-ideographic;"],
+ ["symbols",
+ "system: symbolic; symbols: '1';",
+ "system: symbolic; symbols: '111';"],
+ ["additiveSymbols",
+ "system: additive; additive-symbols: 1 '1';",
+ "system: additive; additive-symbols: 1 '111';"],
+];
+
+// force a reflow before test to eliminate bug 994418
+gOlTest.getBoundingClientRect().width;
+
+for (var i in info) {
+ var item = info[i];
+ var desc = item[0],
+ testStyle = item[1],
+ refStyle = item[2];
+ var isFix = (desc == "prefix" || desc == "suffix");
+
+ while (gCounterSheet.cssRules.length > 0) {
+ gCounterSheet.deleteRule(0);
+ }
+ gCounterSheet.insertRule("@counter-style test { " +
+ basicStyle + testStyle + "}", 0);
+ gCounterSheet.insertRule("@counter-style ref { " +
+ basicStyle + refStyle + "}", 1);
+ testRule = gCounterSheet.cssRules[0];
+ refRule = gCounterSheet.cssRules[1];
+
+ var olTestWidth = gOlTest.getBoundingClientRect().width;
+ var olRefWidth = gOlRef.getBoundingClientRect().width;
+ ok(olTestWidth > 0, "test ol has width");
+ ok(olRefWidth > 0, "ref ol has width");
+ ok(olTestWidth != olRefWidth,
+ "OLs have different width " +
+ "for rule '" + testStyle + "' and '" + refStyle + "'");
+
+ var divTestWidth = gDivTest.getBoundingClientRect().width;
+ var divRefWidth = gDivRef.getBoundingClientRect().width;
+ if (!isFix) {
+ ok(divTestWidth > 0, "test div has width");
+ ok(divRefWidth > 0, "ref div has width");
+ ok(divTestWidth != divRefWidth,
+ "DIVs have different width" +
+ "for rule '" + testStyle + "' and '" + refStyle + "'");
+ }
+
+ ok(testRule[desc] != refRule[desc],
+ "rules have different values for desciptor '" + desc + "'");
+ testRule[desc] = refRule[desc];
+
+ var olNewWidth = gOlTest.getBoundingClientRect().width;
+ var divNewWidth = gDivTest.getBoundingClientRect().width;
+ is(olNewWidth, olRefWidth);
+ if (!isFix) {
+ is(divNewWidth, divRefWidth);
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_crash_with_content_policy.html b/layout/style/test/test_crash_with_content_policy.html
new file mode 100644
index 0000000000..9acec58243
--- /dev/null
+++ b/layout/style/test/test_crash_with_content_policy.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtests for style system with content policy</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<iframe id="iframe"></iframe>
+<script>
+const TESTS = [
+ "file_bug1381233.html",
+];
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+
+var policyID = SpecialPowers.wrap(SpecialPowers.Components).ID("{b80e19d0-878f-d41b-2654-194714a4115c}");
+var policyName = "@mozilla.org/testpolicy;1";
+var policy = {
+ // nsISupports implementation
+ QueryInterface: function(iid) {
+
+ iid = SpecialPowers.wrap(iid);
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsIContentPolicy))
+ return this;
+
+ throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIFactory implementation
+ createInstance: function(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad: function(contentLocation, loadInfo) {
+ info(`shouldLoad is invoked for ${SpecialPowers.wrap(contentLocation).spec}`);
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+ shouldProcess: function(contentLocation, loadInfo) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+}
+policy = SpecialPowers.wrapCallbackObject(policy);
+
+// Register content policy
+var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager
+ .QueryInterface(Ci.nsIComponentRegistrar);
+componentManager.registerFactory(policyID, "Test content policy", policyName, policy);
+var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+categoryManager.addCategoryEntry("content-policy", policyName, policyName, false, true);
+
+
+SimpleTest.waitForExplicitFinish();
+
+async function runTests() {
+ let iframe = document.getElementById("iframe");
+ for (let test of TESTS) {
+ iframe.src = test;
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ });
+ ok(true, `${test} doesn't crash`);
+ }
+ categoryManager.deleteCategoryEntry("content-policy", policyName, false);
+ componentManager.unregisterFactory(policyID, policy);
+ SimpleTest.finish();
+}
+runTests();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_css_cross_domain.html b/layout/style/test/test_css_cross_domain.html
new file mode 100644
index 0000000000..8541c9c5ce
--- /dev/null
+++ b/layout/style/test/test_css_cross_domain.html
@@ -0,0 +1,158 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=524223 -->
+<head>
+ <title>Test cross-domain CSS loading</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ hr { border: none; clear: both }
+ .column {
+ margin: 10px;
+ float: left;
+ }
+ iframe {
+ width: 40px;
+ height: 680px;
+ border: none;
+ margin: 0;
+ padding: 0;
+ }
+ h2 { font-weight: normal; padding: 0 }
+ ol, h2 { font-size: 13px; line-height: 20px; }
+ ol { padding-left: 1em;
+ list-style-type: upper-roman }
+ ol ol { list-style-type: upper-alpha }
+ ol ol ol { list-style-type: decimal }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524223">Mozilla
+ Bug 524223</a>
+
+<hr/>
+
+<div class="column">
+<h2>&nbsp;</h2>
+<ol><li>text/css<ol><li>same origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>same to cross<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross to same<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li></ol></li>
+ <li>text/html<ol><li>same origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>same to cross<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross to same<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li></ol></li>
+</ol>
+</div>
+
+<div class="column">
+<h2>Quirks</h2>
+<div id="quirks-placeholder"></div>
+</div>
+
+<div class="column">
+<h2>Standards</h2>
+<div id="standards-placeholder"></div>
+</div>
+
+<script type="application/javascript">
+
+const COLOR = {red: "rgb(255, 0, 0)", lime: "rgb(0, 255, 0)"};
+
+// Cross origin requests with text/html as the contentType.
+// These requests will be blocked by ORB (when ORB is enabled),
+// thus the color of the element is not going to be changed.
+const BLOCKED_BY_ORB = ["JD1i", "JD1l", "JD2i", "JD2l"];
+
+/** Test for Bug 524223 **/
+function check_iframe(ifr) {
+ var doc = ifr.contentDocument;
+ var cases = doc.getElementsByTagName("p");
+
+ for (var i = 0; i < cases.length; i++) {
+ var color = doc.defaultView.getComputedStyle(cases[i])
+ .getPropertyValue("background-color");
+
+ var id = cases[i].id;
+ // only 'quirks' can have requests that are blocked by ORB.
+ if (BLOCKED_BY_ORB.includes(id) && ifr.id === "quirks") {
+ is(color, COLOR.red, ifr.id + " " + id);
+ } else {
+ is(color, COLOR.lime, ifr.id + " " + id);
+ }
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+function insertIFrames(src, id) {
+ const quirks = document.createElement("iframe");
+ quirks.src = "ccd-quirks.html";
+ quirks.id = "quirks";
+ document.getElementById("quirks-placeholder").replaceWith(quirks);
+
+ const standards = document.createElement("iframe");
+ standards.src = "ccd-standards.html";
+ standards.id = "standards";
+ document.getElementById("standards-placeholder").replaceWith(standards);
+}
+
+var hasQuirksLoaded = false;
+var hasStandardsLoaded = false;
+
+function quirksLoaded() {
+ hasQuirksLoaded = true;
+ MaybeRunTest();
+}
+
+function standardsLoaded() {
+ hasStandardsLoaded = true;
+ MaybeRunTest();
+}
+
+function runTest() {
+ check_iframe(document.getElementById("quirks"));
+ check_iframe(document.getElementById("standards"));
+}
+
+function MaybeRunTest() {
+ if (!hasQuirksLoaded || !hasStandardsLoaded) {
+ return;
+ }
+
+ runTest();
+ SimpleTest.finish();
+}
+
+window.onload = async function() {
+ await SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ['browser.opaqueResponseBlocking', true],
+ ['browser.opaqueResponseBlocking.javascriptValidator', true],
+ ],
+ }
+ );
+ insertIFrames();
+};
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_css_cross_domain_no_orb.html b/layout/style/test/test_css_cross_domain_no_orb.html
new file mode 100644
index 0000000000..27ede793be
--- /dev/null
+++ b/layout/style/test/test_css_cross_domain_no_orb.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=524223 -->
+<head>
+ <title>Test cross-domain CSS loading</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ hr { border: none; clear: both }
+ .column {
+ margin: 10px;
+ float: left;
+ }
+ iframe {
+ width: 40px;
+ height: 680px;
+ border: none;
+ margin: 0;
+ padding: 0;
+ }
+ h2 { font-weight: normal; padding: 0 }
+ ol, h2 { font-size: 13px; line-height: 20px; }
+ ol { padding-left: 1em;
+ list-style-type: upper-roman }
+ ol ol { list-style-type: upper-alpha }
+ ol ol ol { list-style-type: decimal }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524223">Mozilla
+ Bug 524223</a>
+
+<hr/>
+
+<div class="column">
+<h2>&nbsp;</h2>
+<ol><li>text/css<ol><li>same origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>same to cross<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross to same<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li></ol></li>
+ <li>text/html<ol><li>same origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross origin<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>same to cross<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li>
+ <li>cross to same<ol><li>valid</li>
+ <li>malformed</li>
+ <li>http error</li></ol></li></ol></li>
+</ol>
+</div>
+
+<div class="column">
+<h2>Quirks</h2>
+<div id="quirks-placeholder"></div>
+</div>
+
+<div class="column">
+<h2>Standards</h2>
+<div id="standards-placeholder"></div>
+</div>
+
+<script type="application/javascript">
+
+const COLOR = {red: "rgb(255, 0, 0)", lime: "rgb(0, 255, 0)"};
+
+/** Test for Bug 524223 **/
+function check_iframe(ifr) {
+ var doc = ifr.contentDocument;
+ var cases = doc.getElementsByTagName("p");
+
+ for (var i = 0; i < cases.length; i++) {
+ var color = doc.defaultView.getComputedStyle(cases[i])
+ .getPropertyValue("background-color");
+
+ var id = cases[i].id;
+ is(color, COLOR.lime, ifr.id + " " + id);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+function insertIFrames(src, id) {
+ const quirks = document.createElement("iframe");
+ quirks.src = "ccd-quirks.html";
+ quirks.id = "quirks";
+ document.getElementById("quirks-placeholder").replaceWith(quirks);
+
+ const standards = document.createElement("iframe");
+ standards.src = "ccd-standards.html";
+ standards.id = "standards";
+ document.getElementById("standards-placeholder").replaceWith(standards);
+}
+
+var hasQuirksLoaded = false;
+var hasStandardsLoaded = false;
+
+function quirksLoaded() {
+ hasQuirksLoaded = true;
+ MaybeRunTest();
+}
+
+function standardsLoaded() {
+ hasStandardsLoaded = true;
+ MaybeRunTest();
+}
+
+function runTest() {
+ check_iframe(document.getElementById("quirks"));
+ check_iframe(document.getElementById("standards"));
+}
+
+function MaybeRunTest() {
+ if (!hasQuirksLoaded || !hasStandardsLoaded) {
+ return;
+ }
+
+ runTest();
+ SimpleTest.finish();
+}
+
+window.onload = async function() {
+ await SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ['browser.opaqueResponseBlocking', false],
+ ],
+ }
+ );
+ insertIFrames();
+};
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_css_eof_handling.html b/layout/style/test/test_css_eof_handling.html
new file mode 100644
index 0000000000..099ca4c752
--- /dev/null
+++ b/layout/style/test/test_css_eof_handling.html
@@ -0,0 +1,268 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS EOF handling</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p><a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=311616">bug 311616</a>,
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=325064">bug 325064</a></p>
+<iframe id="display"></iframe>
+<p id="log"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+const tests = [
+ {
+ name: "basic rule",
+ ref: "#r {background-color : orange}",
+ tst: "#t {background-color : orange",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "function",
+ ref: "#r {background-color: rgb(0,255,0)}",
+ tst: "#t {background-color: rgb(0,255,0",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "comment",
+ ref: "#r {background-color: aqua/*marine*/}",
+ tst: "#t {background-color: aqua/*marine",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "@media 1",
+ ref: "@media all { #r { background-color: yellow } }",
+ tst: "@media all { #t { background-color: yellow }",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "@media 2",
+ ref: "@media all { #r { background-color: magenta } }",
+ tst: "@media all { #t { background-color: magenta",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "@import 1",
+ ref: "@import 'data:text/css,%23r%7Bbackground-color%3Agray%7D';",
+ tst: "@import 'data:text/css,%23t%7Bbackground-color%3Agray%7D",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "@import 2",
+ ref: "@import 'data:text/css,%23r%7Bbackground-color%3Ablack%7D' all;",
+ tst: "@import 'data:text/css,%23t%7Bbackground-color%3Ablack%7D' all",
+ prop: "background-color", pseudo: ""
+ },
+ {
+ name: "url-token 1",
+ ref: "#r { background-image: url(data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAADklEQVQI12NI" +
+ "YJgAhAkAB4gB4Ry+pcoAAAAASUVORK5CYII=) }",
+ tst: "#t { background-image: url(data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAADklEQVQI12NI" +
+ "YJgAhAkAB4gB4Ry+pcoAAAAASUVORK5CYII=",
+ prop: "background-image", pseudo: ""
+ },
+ {
+ name: "url-token 2",
+ ref: "#r { background-image: url('data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12Mo" +
+ "YNjAcIHhAQAJ2ALR4kRk1gAAAABJRU5ErkJggg==') }",
+ tst: "#t { background-image: url('data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12Mo" +
+ "YNjAcIHhAQAJ2ALR4kRk1gAAAABJRU5ErkJggg==",
+ prop: "background-image", pseudo: ""
+ },
+ {
+ name: "url-token 3",
+ ref: "#r { background-image: url('data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12N4" +
+ "wHCBYQNDAQAMuALRrGb97AAAAABJRU5ErkJggg==') }",
+ tst: "#t { background-image: url('data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12N4" +
+ "wHCBYQNDAQAMuALRrGb97AAAAABJRU5ErkJggg=='",
+ prop: "background-image", pseudo: ""
+ },
+ {
+ name: "url-token 4", /*Bug 751939*/
+ ref: "#r { background-image: url( )}",
+ tst: "#t { background-image: url(" ,
+ prop: "background-image", pseudo: ""
+ },
+ {
+ name: "counter",
+ ref: "#r::before { content: counter(tr, upper-alpha) }",
+ tst: "#t::before { content: counter(tr, upper-alpha",
+ prop: "content", pseudo: "::before"
+ },
+ {
+ name: "string",
+ ref: "#r::before { content: 'B' }",
+ tst: "#t::before { content: 'B",
+ prop: "content", pseudo: "::before"
+ },
+
+ /* For these tests, there is no visible effect on computed style;
+ instead we have to audit the DOM stylesheet object. */
+
+ {
+ todo: 1, /* bug 446226 */
+ name: "selector 1",
+ ref: "td[colspan='3'] {}",
+ tst: "td[colspan='3"
+ },
+ {
+ todo: 1, /* bug 446226 */
+ name: "selector 2",
+ ref: "td[colspan='3'] {}",
+ tst: "td[colspan='3'"
+ },
+ {
+ todo: 1, /* bug 446226 */
+ name: "selector 3",
+ ref: "td:lang(en) {}",
+ tst: "td:lang(en"
+ },
+
+ {
+ name: "@media 3",
+ ref: "@media all {}",
+ tst: "@media all {",
+ },
+ {
+ name: "@namespace 1a",
+ ref: "@namespace foo url('http://foo.example.com/');",
+ tst: "@namespace foo url('http://foo.example.com/')"
+ },
+ {
+ name: "@namespace 1b",
+ ref: "@namespace foo url(http://foo.example.com/);",
+ tst: "@namespace foo url(http://foo.example.com/"
+ },
+ {
+ name: "@namespace 1c",
+ ref: "@namespace foo url('http://foo.example.com/');",
+ tst: "@namespace foo url('http://foo.example.com/"
+ },
+ {
+ name: "@namespace 1d",
+ ref: "@namespace foo 'http://foo.example.com/';",
+ tst: "@namespace foo 'http://foo.example.com/'"
+ },
+ {
+ name: "@namespace 1e",
+ ref: "@namespace foo 'http://foo.example.com/';",
+ tst: "@namespace foo 'http://foo.example.com/"
+ },
+ {
+ name: "@namespace 2a",
+ ref: "@namespace url('http://foo.example.com/');",
+ tst: "@namespace url('http://foo.example.com/')"
+ },
+ {
+ name: "@namespace 2b",
+ ref: "@namespace url('http://foo.example.com/');",
+ tst: "@namespace url('http://foo.example.com/'"
+ },
+ {
+ name: "@namespace 2c",
+ ref: "@namespace url('http://foo.example.com/');",
+ tst: "@namespace url('http://foo.example.com/"
+ },
+ {
+ name: "@namespace 2d",
+ ref: "@namespace 'http://foo.example.com/';",
+ tst: "@namespace 'http://foo.example.com/'"
+ },
+ {
+ name: "@namespace 2e",
+ ref: "@namespace 'http://foo.example.com/';",
+ tst: "@namespace 'http://foo.example.com/"
+ }
+];
+
+const basestyle = ("table {\n"+
+ " border-collapse: collapse;\n"+
+ "}\n"+
+ "td {\n"+
+ " width: 1.5em;\n"+
+ " height: 1.5em;\n"+
+ " border: 1px solid black;\n"+
+ " text-align: center;\n"+
+ " margin: 0;\n"+
+ "}\n"+
+ "tr { counter-increment: tr }\n");
+
+/* This is more complicated than it might look like it needs to be,
+ because for each subtest we have to splat stuff into the iframe,
+ allow the renderer to run, and only then interrogate the computed
+ styles. */
+
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function() {
+ const frame = document.getElementById("display");
+ var curTest = 0;
+
+ const prepareTest = function() {
+ var cd = frame.contentDocument;
+ cd.open();
+ cd.write('<!DOCTYPE HTML><html><head>' +
+ '<style>\n' + basestyle + '</style>\n' +
+ '<style>\n' + tests[curTest].ref + '</style>\n' +
+ '<style>\n' + tests[curTest].tst + '</style>\n' +
+ '</head><body>\n' +
+ '<table><tr><td id="r"><td id="t"></table>' +
+ '</body></html>');
+ cd.close();
+ };
+
+ const checkTest = function() {
+ var cd = frame.contentDocument;
+ var _is = tests[curTest].todo ? todo_is : is;
+ var _ok = tests[curTest].todo ? todo : ok;
+
+ if (cd.styleSheets[1].cssRules.length == 1 &&
+ cd.styleSheets[2].cssRules.length == 1) {
+ // If we have a .prop for this test, the .cssText of the reference
+ // and test rules will differ in the selector. Change #t to #r
+ // in the test rule.
+ var ref_canon = cd.styleSheets[1].cssRules[0].cssText;
+ var tst_canon = cd.styleSheets[2].cssRules[0].cssText;
+ tst_canon = tst_canon.replace(/(#|%23)t\b/, "$1r");
+ _is(tst_canon, ref_canon,
+ tests[curTest].name + " (canonicalized rule)");
+ } else {
+ _ok(false, tests[curTest].name + " (rule missing)");
+ }
+ if (tests[curTest].prop) {
+ var prop = tests[curTest].prop;
+ var pseudo = tests[curTest].pseudo;
+
+ var refElt = cd.getElementById("r");
+ var tstElt = cd.getElementById("t");
+ var refStyle = cd.defaultView.getComputedStyle(refElt, pseudo);
+ var tstStyle = cd.defaultView.getComputedStyle(tstElt, pseudo);
+ _is(tstStyle.getPropertyValue(prop),
+ refStyle.getPropertyValue(prop),
+ tests[curTest].name + " (computed style)");
+ }
+ curTest++;
+ if (curTest < tests.length) {
+ prepareTest();
+ } else {
+ SimpleTest.finish();
+ }
+ };
+
+ frame.onload = function(){setTimeout(checkTest, 0);};
+ prepareTest();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_css_escape_api.html b/layout/style/test/test_css_escape_api.html
new file mode 100644
index 0000000000..2bcc094be1
--- /dev/null
+++ b/layout/style/test/test_css_escape_api.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=955860
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 955860</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=955860">Mozilla Bug 955860</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script>
+// Tests taken from:
+// https://github.com/mathiasbynens/CSS.escape/blob/master/tests/tests.js
+
+SimpleTest.doesThrow(() => CSS.escape(), 'undefined');
+
+is(CSS.escape('\0'), '\uFFFD', "escaping for 0 char (1)");
+is(CSS.escape('a\0'), 'a\uFFFD', "escaping for 0 char (2)");
+is(CSS.escape('\0b'), '\uFFFDb', "escaping for 0 char (3)");
+is(CSS.escape('a\0b'), 'a\uFFFDb', "escaping for 0 char (4)");
+
+is(CSS.escape('\uFFFD'), '\uFFFD', "escaping for replacement char (1)");
+is(CSS.escape('a\uFFFD'), 'a\uFFFD', "escaping replacement char (2)");
+is(CSS.escape('\uFFFDb'), '\uFFFDb', "escaping replacement char (3)");
+is(CSS.escape('a\uFFFDb'), 'a\uFFFDb', "escaping replacement char (4)");
+
+is(CSS.escape(true), 'true', "escapingFailed Character : true(bool)");
+is(CSS.escape(false), 'false', "escapingFailed Character : false(bool)");
+is(CSS.escape(null), 'null', "escapingFailed Character : null");
+is(CSS.escape(''), '', "escapingFailed Character : '' ");
+
+is(CSS.escape('\x01\x02\x1E\x1F'), '\\1 \\2 \\1e \\1f ',"escapingFailed Char: \\x01\\x02\\x1E\\x1F");
+
+is(CSS.escape('0a'), '\\30 a', "escapingFailed Char: 0a");
+is(CSS.escape('1a'), '\\31 a', "escapingFailed Char: 1a");
+is(CSS.escape('2a'), '\\32 a', "escapingFailed Char: 2a");
+is(CSS.escape('3a'), '\\33 a', "escapingFailed Char: 3a");
+is(CSS.escape('4a'), '\\34 a', "escapingFailed Char: 4a");
+is(CSS.escape('5a'), '\\35 a', "escapingFailed Char: 5a");
+is(CSS.escape('6a'), '\\36 a', "escapingFailed Char: 6a");
+is(CSS.escape('7a'), '\\37 a', "escapingFailed Char: 7a");
+is(CSS.escape('8a'), '\\38 a', "escapingFailed Char: 8a");
+is(CSS.escape('9a'), '\\39 a', "escapingFailed Char: 9a");
+
+is(CSS.escape('a0b'), 'a0b', "escapingFailed Char: a0b");
+is(CSS.escape('a1b'), 'a1b', "escapingFailed Char: a1b");
+is(CSS.escape('a2b'), 'a2b', "escapingFailed Char: a2b");
+is(CSS.escape('a3b'), 'a3b', "escapingFailed Char: a3b");
+is(CSS.escape('a4b'), 'a4b', "escapingFailed Char: a4b");
+is(CSS.escape('a5b'), 'a5b', "escapingFailed Char: a5b");
+is(CSS.escape('a6b'), 'a6b', "escapingFailed Char: a6b");
+is(CSS.escape('a7b'), 'a7b', "escapingFailed Char: a7b");
+is(CSS.escape('a8b'), 'a8b', "escapingFailed Char: a8b");
+is(CSS.escape('a9b'), 'a9b', "escapingFailed Char: a9b");
+
+is(CSS.escape('-0a'), '-\\30 a', "escapingFailed Char: -0a");
+is(CSS.escape('-1a'), '-\\31 a', "escapingFailed Char: -1a");
+is(CSS.escape('-2a'), '-\\32 a', "escapingFailed Char: -2a");
+is(CSS.escape('-3a'), '-\\33 a', "escapingFailed Char: -3a");
+is(CSS.escape('-4a'), '-\\34 a', "escapingFailed Char: -4a");
+is(CSS.escape('-5a'), '-\\35 a', "escapingFailed Char: -5a");
+is(CSS.escape('-6a'), '-\\36 a', "escapingFailed Char: -6a");
+is(CSS.escape('-7a'), '-\\37 a', "escapingFailed Char: -7a");
+is(CSS.escape('-8a'), '-\\38 a', "escapingFailed Char: -8a");
+is(CSS.escape('-9a'), '-\\39 a', "escapingFailed Char: -9a");
+
+is(CSS.escape('--a'), '--a', 'Should not need to escape leading "--"');
+
+is(CSS.escape('\x7F\x80\x2D\x5F\xA9'), '\\7f \x80\x2D\x5F\xA9', "escapingFailed Char: \\x7F\\x80\\x2D\\x5F\\xA9");
+is(CSS.escape('\xA0\xA1\xA2'), '\xA0\xA1\xA2', "escapingFailed Char: \\xA0\\xA1\\xA2");
+is(CSS.escape('a0123456789b'), 'a0123456789b', "escapingFailed Char: a0123465789");
+is(CSS.escape('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz', "escapingFailed Char: abcdefghijklmnopqrstuvwxyz");
+is(CSS.escape('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', "escapingFailed Char: ABCDEFGHIJKLMNOPQRSTUVWXYZBCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+is(CSS.escape('\x20\x21\x78\x79'), '\\ \\!xy', "escapingFailed Char: \\x20\\x21\\x78\\x79");
+
+// astral symbol (U+1D306 TETRAGRAM FOR CENTRE)
+is(CSS.escape('\uD834\uDF06'), '\uD834\uDF06', "escapingFailed Char:\\uD834\\uDF06");
+// lone surrogates
+is(CSS.escape('\uDF06'), '\uDF06', "escapingFailed Char: \\uDF06");
+is(CSS.escape('\uD834'), '\uD834', "escapingFailed Char: \\uD834");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_css_function_mismatched_parenthesis.html b/layout/style/test/test_css_function_mismatched_parenthesis.html
new file mode 100644
index 0000000000..e7e78cc545
--- /dev/null
+++ b/layout/style/test/test_css_function_mismatched_parenthesis.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=897094
+
+This test verifies that:
+(1) Mismatched parentheses in a CSS function prevent parsing of subsequent CSS
+properties.
+(2) Properly matched parentheses do not prevent parsing of subsequent CSS
+properties.
+-->
+<head>
+ <title>Test for Bug 897094</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=897094">Mozilla Bug 897094</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="target"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 897094 **/
+function check_parens(declaration, parens_are_balanced)
+{
+ var element = document.getElementById("target");
+ element.setAttribute("style",
+ "background-color: " + (parens_are_balanced ? "red" : "green") + "; " +
+ declaration + "; " +
+ "background-color: " + (parens_are_balanced ? "green" : "red") + "; ");
+ var resultColor = element.style.getPropertyValue("background-color");
+ is(resultColor, "green", "parenthesis balancing within " + declaration);
+}
+
+check_parens("transform: scale()", true);
+check_parens("transform: scale(", false);
+check_parens("transform: scale(,)", true);
+check_parens("transform: scale(,", false);
+check_parens("transform: scale(1)", true);
+check_parens("transform: scale(1", false);
+check_parens("transform: scale(1,)", true);
+check_parens("transform: scale(1,", false);
+check_parens("transform: scale(1,1)", true);
+check_parens("transform: scale(1,1", false);
+check_parens("transform: scale(1,1,)", true);
+check_parens("transform: scale(1,1,", false);
+check_parens("transform: scale(1,1,1)", true);
+check_parens("transform: scale(1,1,1", false);
+check_parens("transform: scale(1,1,1,)", true);
+check_parens("transform: scale(1,1,1,", false);
+check_parens("transform: scale(1px)", true);
+check_parens("transform: scale(1px", false);
+check_parens("transform: scale(1px,)", true);
+check_parens("transform: scale(1px,", false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_css_loader_crossorigin_data_url.html b/layout/style/test/test_css_loader_crossorigin_data_url.html
new file mode 100644
index 0000000000..67105d61f0
--- /dev/null
+++ b/layout/style/test/test_css_loader_crossorigin_data_url.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for handling of 'crossorigin' attribute on CSS link with data: URL</title>
+<link id="testlink" crossorigin rel="stylesheet" href="data:text/css,%23someuniqueidhere{display:none}">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="someuniqueidhere"></div>
+<script>
+ var t = async_test("link@crossorigin with data: href");
+ window.addEventListener("load", t.step_func_done(function() {
+ assert_equals(getComputedStyle(document.getElementById("someuniqueidhere")).display,
+ "none", "sheet should be applied");
+ assert_equals(document.getElementById("testlink").sheet.cssRules[0].style.display,
+ "none", "should be able to read data from the sheet");
+ }));
+</script>
diff --git a/layout/style/test/test_css_parse_error_smoketest.html b/layout/style/test/test_css_parse_error_smoketest.html
new file mode 100644
index 0000000000..96d8edce3a
--- /dev/null
+++ b/layout/style/test/test_css_parse_error_smoketest.html
@@ -0,0 +1,160 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for CSS parser reporting parsing errors with expected precision</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<style id="testbench"></style>
+<script>
+ SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true;
+ // Tests that apply to all types of style sheets
+ var tests = [
+ {
+ css: "@unknown {}",
+ error: "Unrecognized at-rule or error parsing at-rule ‘@unknown’.",
+ }, {
+ css: "x { color: invalid; }",
+ error: "Expected color but found ‘invalid’. Error in parsing value for ‘color’. Declaration dropped.",
+ cssSelectors: "x",
+ }, {
+ css: "x { filter: alpha(foo); }",
+ error: "Expected ‘none’, URL, or filter function but found ‘alpha(’. Error in parsing value for ‘filter’. Declaration dropped.",
+ cssSelectors: "x",
+ }, {
+ css: "x { color: red; abc; }",
+ error: "Unknown property ‘abc;’. Declaration dropped.",
+ cssSelectors: "x",
+ }, {
+ css: "x { filter: 5; }",
+ error: "Expected ‘none’, URL, or filter function but found ‘5’. Error in parsing value for ‘filter’. Declaration dropped.",
+ cssSelectors: "x",
+ }, {
+ css: "::unknown {}",
+ error: "Unknown pseudo-class or pseudo-element ‘unknown’. Ruleset ignored due to bad selector.",
+ }, {
+ css: ":unknown {}",
+ error: "Unknown pseudo-class or pseudo-element ‘unknown’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "::5 {}",
+ error: "Expected identifier for pseudo-class or pseudo-element but found ‘5’. Ruleset ignored due to bad selector.",
+ }, {
+ css: ": {}",
+ error: "Expected identifier for pseudo-class or pseudo-element but found ‘ ’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x[a.]{}",
+ error: "Unexpected token in attribute selector: ‘.’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x[*a]{}",
+ error: "Expected ‘|’ but found ‘a’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x[a=5]{}",
+ error: "Expected identifier or string for value in attribute selector but found ‘5’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x[$] {}",
+ error: "Expected attribute name or namespace but found ‘$’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "a[|5] {}",
+ error: "Expected identifier for attribute name but found ‘5’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "a[x|] {}",
+ error: "Unknown namespace prefix ‘x’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "x| {}",
+ error: "Unknown namespace prefix ‘x’. Ruleset ignored due to bad selector.",
+ }, {
+ css: "a> {}",
+ error: "Dangling combinator. Ruleset ignored due to bad selector.",
+ }, {
+ css: "~ {}",
+ error: "Selector expected. Ruleset ignored due to bad selector.",
+ }, {
+ css: "| {}",
+ error: "Expected element name or ‘*’ but found ‘ ’. Ruleset ignored due to bad selector.",
+ }, {
+ css: ". {}",
+ error: "Expected identifier for class selector but found ‘ ’. Ruleset ignored due to bad selector.",
+ }, {
+ css: ":not() {}",
+ error: "Selector expected. Ruleset ignored due to bad selector.",
+ }, {
+ css: "* { -webkit-text-size-adjust: 100% }",
+ error: "Error in parsing value for ‘-webkit-text-size-adjust’. Declaration dropped.",
+ cssSelectors: "*",
+ }, {
+ css: "@media (totally-unknown-feature) {}",
+ error: "Expected media feature name but found ‘totally-unknown-feature’.",
+ }, {
+ css: "@media \"foo\" {}",
+ error: "Unexpected token ‘\"foo\"’ in media list.",
+ }, {
+ css: "@media (min-width) {}",
+ error: "Media features with min- or max- must have a value.",
+ }, {
+ css: "@media (min-width >= 3px) {}",
+ error: "Unexpected operator in media list.",
+ }, {
+ css: "@media (device-height: three) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (min-width: foo) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (min-resolution: 2) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (min-monochrome: 1.1) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (min-aspect-ratio: 1 invalid) {}",
+ error: "Unexpected token ‘invalid’ in media list.",
+ }, {
+ css: "@media (min-aspect-ratio: 1 / invalid) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "@media (orientation: invalid-orientation-value) {}",
+ error: "Found invalid value for media feature.",
+ }, {
+ css: "a, .b, #c { unknown: invalid; }",
+ error: "Unknown property ‘unknown’. Declaration dropped.",
+ cssSelectors: "a, .b, #c"
+ },
+ {
+ css: ":host:hover { color: red; }",
+ error: ":host selector in ‘:host:hover’ is not featureless and will never match. Maybe you intended to use :host()?"
+ },
+ ];
+
+ // Tests that apply only to constructed style sheets
+ var constructedSheetTests = [
+ {
+ css: '@import url("sheet.css");',
+ error: "@import rules are not yet valid in constructed stylesheets."
+ }
+ ];
+
+ function assertMessages(messages, action) {
+ return new Promise(resolve => {
+ SimpleTest.expectConsoleMessages(action, messages, resolve);
+ });
+ }
+
+ async function runTests() {
+ for (let {css, cssSelectors = "", error} of tests) {
+ let messages = [ { cssSelectors, errorMessage: error } ];
+ await assertMessages(messages, () => { testbench.innerHTML = css });
+ await assertMessages(messages, () => { new CSSStyleSheet().replaceSync(css) });
+ await assertMessages(messages, async () => { await new CSSStyleSheet().replace(css) });
+ }
+ for (let {css, cssSelectors = "", error} of constructedSheetTests) {
+ let messages = [ { cssSelectors, errorMessage: error } ];
+ await assertMessages(messages, () => { new CSSStyleSheet().replaceSync(css) });
+ await assertMessages(messages, async () => { await new CSSStyleSheet().replace(css) });
+ }
+ }
+
+ add_task(runTests);
+
+</script>
diff --git a/layout/style/test/test_css_supports.html b/layout/style/test/test_css_supports.html
new file mode 100644
index 0000000000..a6b1e8d303
--- /dev/null
+++ b/layout/style/test/test_css_supports.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=779917
+-->
+<head>
+ <title>Test for Bug 779917</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=779917">Mozilla Bug 779917</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 779917 **/
+
+function runTest()
+{
+ var passingConditions = [
+ "color: green",
+ "(color: green)",
+ "((color: green))",
+ "(color: green !important)",
+ "(color: rainbow) or (color: green)",
+ "(color: green) or (color: rainbow)",
+ "(color: green) and (color: blue)",
+ "(color: rainbow) or (color: iridescent) or (color: green)",
+ "(color: red) and (color: green) and (color: blue)",
+ "(color:green)",
+ "not (color: rainbow)",
+ "not (not (color: green))",
+ "(unknown:) or (color: green)",
+ "(unknown) or (color: green)",
+ "(font: 16px serif)",
+ "(color:) or (color: green)",
+ "not (@page)",
+ "not ({ something @with [ balanced ] brackets })",
+ "an-extension(of some kind) or (color: green)",
+ "not ()",
+ "( Font: 20px serif ! Important) ",
+ "(color: /* comment */ green)",
+ "(/* comment */ color: green)",
+ "(color: green /* comment */)",
+ "(color: green) /* comment */",
+ "/* comment */ (color: green)",
+ "(color /* comment */: green)",
+ "(color: green) /* unclosed comment",
+ "(color: green",
+ "(((((((color: green",
+ "(font-family: 'Helvetica"
+ ];
+
+ var failingConditions = [
+ "(color: rainbow)",
+ "(color: rainbow) and (color: green)",
+ "(color: blue) and (color: rainbow)",
+ "(color: green) and (color: green) or (color: green)",
+ "(color: green) or (color: green) and (color: green)",
+ "not not (color: green)",
+ "not (color: rainbow) and not (color: iridescent)",
+ "not (color: rainbow) or (color: green)",
+ "(not (color: rainbow) or (color: green))",
+ "(unknown: green)",
+ "not ({ something @with (unbalanced brackets })",
+ "(color: green) or an-extension(that is [unbalanced)",
+ "not(unknown: unknown)",
+ "(color: green) or(color: blue)",
+ "(color: green;)",
+ "(font-family: 'Helvetica\n",
+ "(font-family: 'Helvetica\n')",
+ "()",
+ ""
+ ];
+
+ var passingDeclarations = [
+ ["color", "green"],
+ ["color", " green "],
+ ["Color", "Green"],
+ ["color", "green /* comment */"],
+ ["color", "/* comment */ green"],
+ ["color", "green /* unclosed comment"],
+ ["font", "16px serif"],
+ ["font", "16px /* comment */ serif"],
+ ["font", "16px\nserif"],
+ ["color", "\\0067reen"]
+ ];
+
+ var failingDeclarations = [
+ ["color ", "green"],
+ ["color", "rainbow"],
+ ["color", "green green"],
+ ["color", "green !important"],
+ ["\\0063olor", "green"],
+ ["/* comment */color", "green"],
+ ["color/* comment */", "green"],
+ ["font-family", "'Helvetica\n"],
+ ["font-family", "'Helvetica\n'"],
+ ["color", "green;"],
+ ["color", ""],
+ ["unknown", "unknown"],
+ ["", "green"],
+ ["", ""]
+ ];
+
+ passingConditions.forEach(function(aCondition) {
+ is(CSS.supports(aCondition), true, "CSS.supports returns true for passing condition \"" + aCondition + "\"");
+ });
+
+ failingConditions.forEach(function(aCondition) {
+ is(CSS.supports(aCondition), false, "CSS.supports returns false for failing condition \"" + aCondition + "\"");
+ });
+
+ passingDeclarations.forEach(function(aDeclaration) {
+ is(CSS.supports(aDeclaration[0], aDeclaration[1]), true, "CSS.supports returns true for supported declaration \"" + aDeclaration.join(":") + "\"");
+ });
+
+ failingDeclarations.forEach(function(aDeclaration) {
+ is(CSS.supports(aDeclaration[0], aDeclaration[1]), false, "CSS.supports returns false for unsupported declaration \"" + aDeclaration.join(":") + "\"");
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_css_supports_variables.html b/layout/style/test/test_css_supports_variables.html
new file mode 100644
index 0000000000..7efc14a4fc
--- /dev/null
+++ b/layout/style/test/test_css_supports_variables.html
@@ -0,0 +1,247 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=773296
+-->
+<head>
+ <title>Test for Bug 773296</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=773296">Mozilla Bug 773296</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 773296 **/
+
+function runTest()
+{
+ var passingConditions = [
+ "(color:var(--a))",
+ "(color: var(--a))",
+ "(color: var(--a) )",
+ "(color: var( --a ) )",
+ "(color: var(--a, ))",
+ "(color: var(--a,/**/a))",
+ "(color: var(--a,))",
+ "(color: var(--a,/**/))",
+ "(color: 1px var(--a))",
+ "(color: var(--a) 1px)",
+ "(color: something 3px url(whereever) calc(var(--a) + 1px))",
+ "(color: var(--a) !important)",
+ "(color: var(--a)var(--b))",
+ "(color: var(--a, var(--b, var(--c, black))))",
+ "(color: var(--a) <!--)",
+ "(color: --> var(--a))",
+ "(color: { [ var(--a) ] })",
+ "(color: [;] var(--a))",
+ "(color: var(--a,(;)))",
+ "(color: VAR(--a))",
+ "(color: var(--0))",
+ "(color: var(--\\30))",
+ "(color: var(--\\d800))",
+ "(color: var(--\\ffffff))",
+ "(color: var(--a",
+ "(color: var(--a , ",
+ "(color: var(--a, ",
+ "(color: var(--a, var(--b",
+ "(color: var(--a /* unclosed comment",
+ "(color: var(--a, '",
+ "(color: var(--a, '\\",
+ "(color: var(--a, \\",
+
+ "(--a:var(--b))",
+ "(--a: var(--b))",
+ "(--a: var(--b) )",
+ "(--a: var( --b ) )",
+ "(--a: var(--b, ))",
+ "(--a: var(--b,/**/a))",
+ "(--a: var(--b,))",
+ "(--a: var(--b,/**/))",
+ "(--a: 1px var(--b))",
+ "(--a: var(--b) 1px)",
+ "(--a: something 3px url(whereever) calc(var(--b) + 1px))",
+ "(--a: var(--b) !important)",
+ "(--a: var(--b)var(--b))",
+ "(--a: var(--b, var(--c, var(--d, black))))",
+ "(--a: var(--b) <!--)",
+ "(--a: --> var(--b))",
+ "(--a: { [ var(--b) ] })",
+ "(--a: [;] var(--b))",
+ "(--a: )",
+ "(--a:var(--a))",
+ "(--0: a)",
+ "(--\\30: a)",
+ "(--\\61: a)",
+ "(--\\d800: a)",
+ "(--\\ffffff: a)",
+ "(--\0: 1)",
+ "(--a: ",
+ "(--a: /* unclosed comment",
+ "(--a: var(--b",
+ "(--a: var(--b, ",
+ "(--a: var(--b, var(--c",
+ "(--a: [{(((",
+ "(--a: '",
+ "(--a: '\\",
+ "(--a: \\",
+ "(--a:)",
+ ];
+
+ var failingConditions = [
+ "(color: var(--a,!))",
+ "(color: var(--a,!important))",
+ "(color: var(--a) !important !important)",
+ "(color: var(--a,;))",
+ "(color: var(--a);)",
+ "(color: var(1px))",
+ "(color: var(--a)))",
+ "(color: var(--a) \"\n",
+ "(color: var(--a) url(\"\n",
+ "(color: var(a))",
+ "(color: var(--",
+ "(color: var(--))",
+
+ "(--a: var(--b,!))",
+ "(--a: var(--b,!important))",
+ "((--a: var(--b) !important !important))",
+ "(--a: var(--b,;))",
+ "(--a: var(--b);)",
+ "(--a: var(1px))",
+ "(--a: a))",
+ "(--a: \"\n",
+ "(--a: url(\"\n",
+ "(--a: var(a))",
+ "(--: a)",
+ ];
+
+ var passingDeclarations = [
+ ["color", "var(--a)"],
+ ["color", " var(--a)"],
+ ["color", "var(--a) "],
+ ["color", "var( --a ) "],
+ ["color", "var(--a, )"],
+ ["color", "var(--a,/**/a)"],
+ ["color", "1px var(--a)"],
+ ["color", "var(--a) 1px"],
+ ["color", "something 3px url(whereever) calc(var(--a) + 1px)"],
+ ["color", "var(--a)var(--b)"],
+ ["color", "var(--a, var(--b, var(--c, black)))"],
+ ["color", "var(--a) <!--"],
+ ["color", "--> var(--a)"],
+ ["color", "{ [ var(--a) ] }"],
+ ["color", "[;] var(--a)"],
+ ["color", "var(--a,(;))"],
+ ["color", "VAR(--a)"],
+ ["color", "var(--0)"],
+ ["color", "var(--\\30)"],
+ ["color", "var(--\\d800)"],
+ ["color", "var(--\\ffffff)"],
+ ["color", "var(--a"],
+ ["color", "var(--a , "],
+ ["color", "var(--a, "],
+ ["color", "var(--a, var(--b"],
+ ["color", "var(--a /* unclosed comment"],
+ ["color", "var(--a, '"],
+ ["color", "var(--a, '\\"],
+ ["color", "var(--a, \\"],
+ ["color", "var(--a,)"],
+ ["color", "var(--a,/**/)"],
+
+ ["--a", " var(--b)"],
+ ["--a", "var(--b)"],
+ ["--a", "var(--b) "],
+ ["--a", "var( --b ) "],
+ ["--a", "var(--b, )"],
+ ["--a", "var(--b,/**/a)"],
+ ["--a", "var(--b,)"],
+ ["--a", "var(--b,/**/)"],
+ ["--a", "1px var(--b)"],
+ ["--a", "var(--b) 1px"],
+ ["--a", "something 3px url(whereever) calc(var(--b) + 1px)"],
+ ["--a", "var(--b)var(--b)"],
+ ["--a", "var(--b, var(--c, var(--d, black)))"],
+ ["--a", "var(--b) <!--"],
+ ["--a", "--> var(--b)"],
+ ["--a", "{ [ var(--b) ] }"],
+ ["--a", "[;] var(--b)"],
+ ["--a", " "],
+ ["--a", ""],
+ ["--a", "var(--a)"],
+ ["--0", "a"],
+ ["--\\30", "a"],
+ ["--\\61", "a"],
+ ["--\\d800", "a"],
+ ["--\\ffffff", "a"],
+ ["--\0", "a"],
+ ["--\ud800", "a"],
+ ["--a", "a /* unclosed comment"],
+ ["--a", "var(--b"],
+ ["--a", "var(--b, "],
+ ["--a", "var(--b, var(--c"],
+ ["--a", "[{((("],
+ ["--a ", "a"],
+ ["--a ", "'"],
+ ["--a ", "'\\"],
+ ["--a ", "\\"],
+ ];
+
+ var failingDeclarations = [
+ ["color", "var(--a,!)"],
+ ["color", "var(--a,!important)"],
+ ["color", "var(--a,;)"],
+ ["color", "var(--a);"],
+ ["color", "var(1px)"],
+ ["color", "var(--a))"],
+ ["color", "var(--a) \"\n"],
+ ["color", "var(--a) url(\"\n"],
+ ["color", "var(--a) !important"],
+ ["color", "var(--a) !important !important"],
+ ["color", "var(a)"],
+ ["color", "var(--"],
+
+ ["--a", "var(--b,!)"],
+ ["--a", "var(--b,!important)"],
+ ["--a", "var(--b) !important !important"],
+ ["--a", "var(--b,;)"],
+ ["--a", "var(--b);"],
+ ["--a", "var(1px)"],
+ ["(VAR-a", "a"],
+ ["--a", "a)"],
+ ["--a", "\"\n"],
+ ["--a", "url(\"\n"],
+ ["--a", "var(--b))"],
+ ["--a", "var(b)"],
+ ["--", "a"],
+ ];
+
+ passingConditions.forEach(function(aCondition) {
+ is(CSS.supports(aCondition), true, "CSS.supports returns true for passing condition \"" + aCondition + "\"");
+ });
+
+ failingConditions.forEach(function(aCondition) {
+ is(CSS.supports(aCondition), false, "CSS.supports returns false for failing condition \"" + aCondition + "\"");
+ });
+
+ passingDeclarations.forEach(function(aDeclaration) {
+ is(CSS.supports(aDeclaration[0], aDeclaration[1]), true, "CSS.supports returns true for supported declaration \"" + aDeclaration.join(":") + "\"");
+ });
+
+ failingDeclarations.forEach(function(aDeclaration) {
+ is(CSS.supports(aDeclaration[0], aDeclaration[1]), false, "CSS.supports returns false for unsupported declaration \"" + aDeclaration.join(":") + "\"");
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_cue_restrictions.html b/layout/style/test/test_cue_restrictions.html
new file mode 100644
index 0000000000..c5b3cf3bda
--- /dev/null
+++ b/layout/style/test/test_cue_restrictions.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for ::cue property restrictions.</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="property_database.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<style id="s"></style>
+<video id="test"></video>
+<video id="control"></video>
+<script>
+const test = getComputedStyle($("test"), "::cue");
+const control = getComputedStyle($("control"), "::cue");
+
+for (const prop in gCSSProperties) {
+ const info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND)
+ continue;
+
+ let prereqs = "";
+ if (info.prerequisites)
+ for (let name in info.prerequisites)
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+
+ $("s").textContent = `
+ #control::cue { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::cue { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+
+ (info.applies_to_cue ? isnot : is)(
+ get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should ${info.applies_to_cue ? "" : "not "}apply to ::cue`);
+}
+</script>
diff --git a/layout/style/test/test_custom_content_inheritance.html b/layout/style/test/test_custom_content_inheritance.html
new file mode 100644
index 0000000000..625f1ad9c3
--- /dev/null
+++ b/layout/style/test/test_custom_content_inheritance.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>Test for custom content inheritance</title>
+<style>
+ html { color: red !important; }
+</style>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+onload = function() {
+ let doc = SpecialPowers.wrap(document);
+ let div = doc.createElement('div');
+ div.id = "test-id";
+ ok(!!doc.insertAnonymousContent,
+ "Must have the insertAnonymousContent API");
+ let content = doc.insertAnonymousContent();
+ ok(!!content, "Must have anon content");
+ content.root.appendChild(div);
+ let color = SpecialPowers.wrap(window).getComputedStyle(div).color;
+ ok(!!color, "Should be able to get a color");
+ isnot(color, getComputedStyle(document.documentElement).color,
+ "Custom anon content shouldn't inherit from the root element");
+ SimpleTest.finish();
+};
+SimpleTest.waitForExplicitFinish();
+</script>
diff --git a/layout/style/test/test_default_bidi_css.html b/layout/style/test/test_default_bidi_css.html
new file mode 100644
index 0000000000..9033c9b6a7
--- /dev/null
+++ b/layout/style/test/test_default_bidi_css.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for default bidi css **/
+function styleOf(name, attributes) {
+ var element = document.createElement(name);
+ for (var name in attributes) {
+ var value = attributes[name];
+ element.setAttribute(name, value);
+ }
+ document.body.appendChild(element);
+ return getComputedStyle(element);
+}
+
+var tests = [
+ ['div', {}, 'ltr', 'isolate'],
+ ['div', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['div', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['div', {'dir': 'auto'}, 'ltr', 'isolate'],
+ ['div', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['span', {}, 'ltr', 'normal'],
+ ['span', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['span', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['span', {'dir': 'auto'}, 'ltr', 'isolate'],
+ ['span', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['bdi', {}, 'ltr', 'isolate'],
+ ['bdi', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['bdi', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['bdi', {'dir': 'auto'}, 'ltr', 'isolate'],
+ ['bdi', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['output', {}, 'ltr', 'isolate'],
+ ['output', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['output', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['output', {'dir': 'auto'}, 'ltr', 'isolate'],
+ ['output', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['bdo', {}, 'ltr', 'isolate-override'],
+ ['bdo', {'dir': 'ltr'}, 'ltr', 'isolate-override'],
+ ['bdo', {'dir': 'rtl'}, 'rtl', 'isolate-override'],
+ ['bdo', {'dir': 'auto'}, 'ltr', 'isolate-override'],
+ ['bdo', {'dir': ''}, 'ltr', 'isolate-override'],
+
+ ['textarea', {}, 'ltr', 'normal'],
+ ['textarea', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['textarea', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['textarea', {'dir': 'auto'}, 'ltr', 'plaintext'],
+ ['textarea', {'dir': ''}, 'ltr', 'isolate'],
+
+ ['pre', {}, 'ltr', 'isolate'],
+ ['pre', {'dir': 'ltr'}, 'ltr', 'isolate'],
+ ['pre', {'dir': 'rtl'}, 'rtl', 'isolate'],
+ ['pre', {'dir': 'auto'}, 'ltr', 'plaintext'],
+ ['pre', {'dir': ''}, 'ltr', 'isolate'],
+].forEach(function (test) {
+ var style = styleOf(test[0], test[1]);
+ is(style.direction, test[2], "default value for direction");
+ is(style.unicodeBidi, test[3], "default value for unicode-bidi");
+});
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_default_computed_style.html b/layout/style/test/test_default_computed_style.html
new file mode 100644
index 0000000000..56b5863935
--- /dev/null
+++ b/layout/style/test/test_default_computed_style.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=800983
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 800983</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #display::before { content: "Visible"; display: block }
+ #display {
+ display: inline;
+ margin-top: 0;
+ background: yellow;
+ color: blue;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=800983">Mozilla Bug 800983</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 800983 **/
+var cs = getComputedStyle($("display"));
+var cs_pseudo = getComputedStyle($("display"), "::before")
+
+var cs_default = getDefaultComputedStyle($("display"));
+var cs_default_pseudo = getDefaultComputedStyle($("display"), "::before");
+
+// Sanity checks for normal computed style
+is(cs.display, "inline", "We have inline display");
+is(cs.marginTop, "0px", "We have 0 margin");
+is(cs.backgroundColor, "rgb(255, 255, 0)", "We have yellow background");
+is(cs.color, "rgb(0, 0, 255)", "We have blue text");
+is(cs_pseudo.content, '"Visible"', "We have some content");
+is(cs_pseudo.display, "block", "Our ::before is block");
+
+// And now our actual tests
+is(cs_default.display, "block", "We have block display by default");
+is(cs_default.marginTop, "16px", "We have 16px margin by default");
+is(cs_default.backgroundColor, "rgba(0, 0, 0, 0)",
+ "We have transparent background by default");
+is(cs_default.color, "rgb(0, 0, 0)", "We have black text by default");
+is(cs_default_pseudo.content, "none", "We have no content by default");
+is(cs_default_pseudo.display, "inline", "Our ::before is inline by default");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_descriptor_storage.html b/layout/style/test/test_descriptor_storage.html
new file mode 100644
index 0000000000..27750a2bad
--- /dev/null
+++ b/layout/style/test/test_descriptor_storage.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS @font-face descriptor values</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="descriptor_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for parsing, storage, and serialization of CSS @font-face descriptor values **/
+
+/*
+ * For explanation of some of the more interesting tests here, see the comment
+ * in test_value_storage.html .
+ */
+
+var gStyleElement = document.createElement("style");
+gStyleElement.setAttribute("type", "text/css");
+document.getElementsByTagName("head")[0].appendChild(gStyleElement);
+var gSheet = gStyleElement.sheet;
+gSheet.insertRule("@font-face { }", 0);
+var gRule = gSheet.cssRules[0];
+var gDeclaration = gRule.style;
+
+function fake_set_property(descriptor, value) {
+ gSheet.deleteRule(0);
+ gSheet.insertRule("@font-face { " + descriptor + ": " + value + "}", 0);
+ gRule = gSheet.cssRules[0];
+ gDeclaration = gRule.style;
+}
+
+function xfail_parse(descriptor, value) {
+ switch (descriptor) {
+ case "src":
+ // not clear whether this is an error or not, so mark todo for now
+ return value == "local(serif)";
+ }
+ return false;
+}
+
+function test_descriptor(descriptor)
+{
+ var info = gCSSFontFaceDescriptors[descriptor];
+
+ function test_value(value) {
+// // We don't implement SetProperty yet (bug 443978).
+// gDeclaration.setProperty(descriptor, value, "");
+ fake_set_property(descriptor, value);
+
+ var idx;
+
+ var step1val = gDeclaration.getPropertyValue(descriptor);
+ var step1ser = gDeclaration.cssText;
+
+ var func = xfail_parse(descriptor, value) ? todo_isnot : isnot;
+ func(step1val, "", "setting '" + value + "' on '" + descriptor + "'");
+
+ // We don't care particularly about the whitespace or the placement of
+ // semicolons, but for simplicity we'll test the current behavior.
+ var expected_serialization = "";
+ if (step1val != "")
+ expected_serialization = descriptor + ": " + step1val + "; ";
+ is(step1ser, expected_serialization,
+ "serialization should match descriptor value");
+
+ gDeclaration.removeProperty(descriptor);
+// // We don't implement SetProperty yet (bug 443978).
+// gDeclaration.setProperty(descriptor, step1val, "");
+ fake_set_property(descriptor, step1val);
+
+ is(gDeclaration.getPropertyValue(descriptor), step1val,
+ "parse+serialize should be idempotent for '" +
+ descriptor + ": " + value + "'");
+
+ gDeclaration.removeProperty(descriptor);
+ }
+
+ var idx;
+ for (idx in info.values)
+ test_value(info.values[idx]);
+}
+
+// To avoid triggering the slow script dialog, we have to test one
+// descriptor at a time.
+SimpleTest.waitForExplicitFinish();
+function runTest() {
+ var descs = [];
+ for (var desc in gCSSFontFaceDescriptors)
+ descs.push(desc);
+ descs = descs.reverse();
+ function do_one() {
+ if (descs.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ test_descriptor(descs.pop());
+ SimpleTest.executeSoon(do_one);
+ }
+ SimpleTest.executeSoon(do_one);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(5);
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_descriptor_syntax_errors.html b/layout/style/test/test_descriptor_syntax_errors.html
new file mode 100644
index 0000000000..bf73b15b64
--- /dev/null
+++ b/layout/style/test/test_descriptor_syntax_errors.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test that we reject syntax errors listed in descriptor_database.js</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="descriptor_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gStyleElement = document.createElement("style");
+gStyleElement.setAttribute("type", "text/css");
+document.getElementsByTagName("head")[0].appendChild(gStyleElement);
+var gSheet = gStyleElement.sheet;
+gSheet.insertRule("@font-face { }", 0);
+var gRule = gSheet.cssRules[0];
+var gDeclaration = gRule.style;
+
+function fake_set_property(descriptor, value) {
+ gSheet.deleteRule(0);
+ gSheet.insertRule("@font-face { " + descriptor + ": " + value + "}", 0);
+ gRule = gSheet.cssRules[0];
+ gDeclaration = gRule.style;
+}
+
+for (var descriptor in gCSSFontFaceDescriptors) {
+ var info = gCSSFontFaceDescriptors[descriptor];
+ for (var idx in info.invalid_values) {
+ var badval = info.invalid_values[idx];
+
+// // We don't implement SetProperty yet (bug 443978).
+// gDeclaration.setProperty(descriptor, badval, "");
+ fake_set_property(descriptor, badval);
+
+ is(gDeclaration.getPropertyValue(descriptor), "",
+ "invalid value '" + badval + "' not accepted for '" + descriptor +
+ "' descriptor");
+
+ gDeclaration.removeProperty(descriptor);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_display_mode.html b/layout/style/test/test_display_mode.html
new file mode 100644
index 0000000000..2fbf78bdb4
--- /dev/null
+++ b/layout/style/test/test_display_mode.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1648157
+-->
+<head>
+ <title>Test for displayMode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="property_database.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+ <iframe id="iframe" src="http://example.org/tests/layout/style/test/media_queries_iframe2.html"></iframe>
+</p>
+<pre id="test">
+<script class="testbody">
+let iframe = document.getElementById("iframe");
+
+// Keep in sync with media_queries_iframe2.html
+const DISPLAY_MODES_BACKGROUND_COLOR = {
+ 'minimal-ui': 'rgb(255, 0, 0)',
+ 'standalone': 'rgb(0, 128, 0)',
+ 'fullscreen': 'rgb(0, 0, 255)',
+ 'browser': 'rgb(255, 255, 0)'
+};
+const DISPLAY_MODES = Object.keys(DISPLAY_MODES_BACKGROUND_COLOR);
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("load", async (event) => {
+ const wrappedWindow = SpecialPowers.wrap(window);
+
+ function setDisplayMode(mode) {
+ wrappedWindow.browsingContext.top.displayMode = mode;
+ }
+
+ async function displayModeApplies(mode) {
+ let responsePromise = new Promise(resolve => {
+ window.addEventListener("message", e => {
+ resolve(e.data.backgroundColor);
+ }, { once: true });
+ });
+ iframe.contentWindow.postMessage('get-background-color', '*');
+ let response = await responsePromise;
+ let expected = DISPLAY_MODES_BACKGROUND_COLOR[mode];
+
+ info(`displayModeApplies: ${response} === ${expected}`);
+ return response === expected;
+ }
+
+ async function checkIfApplies(q, shouldApply) {
+ let message = shouldApply ? "should apply" : "should not apply";
+ is((await displayModeApplies(q)), shouldApply, `${q} ${message}`);
+ }
+
+ for (let currentMode of DISPLAY_MODES) {
+ setDisplayMode(currentMode);
+
+ for (let mode of DISPLAY_MODES) {
+ await checkIfApplies(mode, currentMode === mode);
+ }
+ }
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_dont_use_document_colors.html b/layout/style/test/test_dont_use_document_colors.html
new file mode 100644
index 0000000000..71fc48278d
--- /dev/null
+++ b/layout/style/test/test_dont_use_document_colors.html
@@ -0,0 +1,201 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for preference not to use document colors</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ #one, #three { background: blue; color: yellow; border: thin solid red; column-rule: 2px solid green; text-shadow: 2px 2px green; box-shadow: 3px 7px blue; }
+ #two { background: transparent; border: thin solid; }
+ #five, #six {border: thick solid red; border-inline-start-color:green; border-inline-end-color:blue}
+ #seven {
+ border: 3px solid;
+ }
+ #eight {
+ border: 10px solid transparent;
+ border-image: repeating-linear-gradient(45deg, blue, blue 1%, red 1%, red 8%) 10;
+ }
+ #nine {
+ border: 10px solid blue;
+ border-image: none;
+ }
+
+ #eleven {
+ background-color: transparent;
+ }
+
+ /* XXX also test rgba() */
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=58048">Mozilla Bug 58048</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=255411">Mozilla Bug 255411</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430969">Mozilla Bug 1430969</a>
+<div id="display">
+
+<div id="one">Hello</div>
+<div id="two">Hello</div>
+<input id="three" type="button" value="Hello">
+<input id="four" type="button" value="Hello">
+<div id="five" dir="ltr">Hello</div>
+<div id="six" dir="rtl">Hello</div>
+<div id="seven">Hello</div>
+<div id="eight">I have a border-image</div>
+<div id="nine">I do not have a border-image</div>
+
+<input id="ten" type="button" value="Hello"><!-- Nothing should match this -->
+
+<button id="eleven">Hello</button>
+</div>
+<pre id="test">
+<script class="testbody">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout('nsPresContext internally delays applying prefs using an nsITimer');
+
+var cs1 = getComputedStyle(document.getElementById("one"));
+var cs2 = getComputedStyle(document.getElementById("two"));
+var cs3 = getComputedStyle(document.getElementById("three"));
+var cs4 = getComputedStyle(document.getElementById("four"));
+var cs5 = getComputedStyle(document.getElementById("five"));
+var cs6 = getComputedStyle(document.getElementById("six"));
+var cs7 = getComputedStyle(document.getElementById("seven"));
+var cs8 = getComputedStyle(document.getElementById("eight"));
+var cs9 = getComputedStyle(document.getElementById("nine"));
+var cs10 = getComputedStyle(document.getElementById("ten"));
+var cs11 = getComputedStyle(document.getElementById("eleven"));
+
+function pushPrefEnvAndWait(args, cb) {
+ SpecialPowers.pushPrefEnv(args).then(cb)
+}
+
+pushPrefEnvAndWait({'set': [['browser.display.document_color_use', 1]]}, part1);
+
+function part1()
+{
+ isnot(cs1.backgroundColor, cs2.backgroundColor, "background-color applies");
+ isnot(cs1.color, cs2.color, "color applies");
+ isnot(cs1.borderTopColor, cs2.borderTopColor, "border-top-color applies");
+ isnot(cs1.borderRightColor, cs2.borderRightColor,
+ "border-right-color applies");
+ isnot(cs1.borderLeftColor, cs2.borderLeftColor,
+ "border-left-color applies");
+ isnot(cs1.borderBottomColor, cs2.borderBottomColor,
+ "border-top-color applies");
+ isnot(cs1.columnRuleColor, cs2.columnRuleColor,
+ "column-rule-color applies");
+ isnot(cs1.textShadow, cs2.textShadow,
+ "text-shadow applies");
+ isnot(cs1.boxShadow, cs2.boxShadow,
+ "box-shadow applies");
+ is(cs1.borderTopColor, cs3.borderTopColor, "border-top-color applies");
+ is(cs1.borderRightColor, cs3.borderRightColor,
+ "border-right-color applies");
+ is(cs1.borderLeftColor, cs3.borderLeftColor,
+ "border-left-color applies");
+ is(cs1.borderBottomColor, cs3.borderBottomColor,
+ "border-top-color applies");
+ is(cs1.columnRuleColor, cs3.columnRuleColor,
+ "column-rule-color applies");
+ is(cs1.textShadow, cs3.textShadow,
+ "text-shadow applies");
+ is(cs1.boxShadow, cs3.boxShadow,
+ "box-shadow applies");
+ isnot(cs5.borderRightColor, cs2.borderRightColor,
+ "border-inline-end-color applies");
+ isnot(cs5.borderLeftColor, cs2.borderLeftColor,
+ "border-inline-start-color applies");
+ isnot(cs6.borderRightColor, cs2.borderRightColor,
+ "border-inline-start-color applies");
+ isnot(cs6.borderLeftColor, cs2.borderLeftColor,
+ "border-inline-end-color applies");
+ is(cs1.color, cs3.color, "color applies");
+ is(cs1.backgroundColor, cs3.backgroundColor, "background-color applies");
+ isnot(cs3.backgroundColor, cs4.backgroundColor, "background-color applies");
+ isnot(cs3.color, cs4.color, "color applies");
+ isnot(cs3.borderTopColor, cs4.borderTopColor, "border-top-color applies");
+ isnot(cs3.borderRightColor, cs4.borderRightColor,
+ "border-right-color applies");
+ isnot(cs3.borderLeftColor, cs4.borderLeftColor,
+ "border-left-color applies");
+ isnot(cs3.borderBottomColor, cs4.borderBottomColor,
+ "border-bottom-color applies");
+ isnot(cs8.borderImageSource, cs9.borderImageSource, "border-image-source applies");
+ pushPrefEnvAndWait({'set': [['browser.display.document_color_use', 2]]}, part2);
+}
+
+function toRGBA(c) {
+ return SpecialPowers.wrap(window).InspectorUtils.colorToRGBA(c, document);
+}
+
+function systemColor(c) {
+ let {r, g, b, a} = toRGBA(c);
+ if (a == 1)
+ return `rgb(${r}, ${g}, ${b})`;
+ // Match ColorComponentToFloat's max number of decimals (3), and remove trailing zeros.
+ let alphaString = a.toFixed(3);
+ if (alphaString.includes(".")) {
+ while (alphaString[alphaString.length - 1] == "0")
+ alphaString = alphaString.substring(0, alphaString.length - 1);
+ }
+ return `rgba(${r}, ${g}, ${b}, ${alphaString})`;
+}
+
+function part2()
+{
+ isnot(cs1.backgroundColor, cs2.backgroundColor, "background-color transparency preserved (opaque)");
+ is(toRGBA(cs2.backgroundColor).a, 0, "background-color transparency is preserved (transparent)");
+ is(cs1.color, cs2.color, "color is blocked");
+ is(cs1.borderTopColor, cs2.borderTopColor, "border-top-color is blocked");
+ is(cs1.borderRightColor, cs2.borderRightColor,
+ "border-right-color is blocked");
+ is(cs1.borderLeftColor, cs2.borderLeftColor,
+ "border-left-color is blocked");
+ is(cs5.borderRightColor, cs2.borderRightColor,
+ "border-inline-end-color is blocked");
+ is(cs5.borderLeftColor, cs2.borderLeftColor,
+ "border-inline-start-color is blocked");
+ is(cs6.borderRightColor, cs2.borderRightColor,
+ "border-inline-start-color is blocked");
+ is(cs6.borderLeftColor, cs2.borderLeftColor,
+ "border-inline-end-color is blocked");
+ is(cs1.borderBottomColor, cs2.borderBottomColor,
+ "border-bottom-color is blocked");
+ is(cs1.columnRuleColor, cs2.columnRuleColor,
+ "column-rule-color is blocked");
+ is(cs1.textShadow, cs2.textShadow,
+ "text-shadow is blocked");
+ is(cs1.boxShadow, cs2.boxShadow,
+ "box-shadow is blocked");
+ is(cs3.backgroundColor, cs10.backgroundColor, "background-color transparency preserved (opaque)");
+ is(cs3.color, cs10.color, "color is blocked");
+ is(cs3.borderTopColor, cs4.borderTopColor, "border-top-color is blocked");
+ is(cs3.borderRightColor, cs4.borderRightColor,
+ "border-right-color is blocked");
+ is(cs3.borderLeftColor, cs4.borderLeftColor,
+ "border-left-color is blocked");
+ is(cs3.borderBottomColor, cs4.borderBottomColor,
+ "border-bottom-color is blocked");
+ is(cs4.backgroundColor, systemColor("ButtonFace"), "background-color not broken on inputs");
+ is(cs4.color, systemColor("ButtonText"), "color not broken on inputs");
+ is(cs4.borderTopColor, systemColor("ButtonBorder"), "border-top-color not broken on inputs");
+ is(cs4.borderRightColor, systemColor("ButtonBorder"),
+ "border-right-color not broken on inputs");
+ is(cs4.borderLeftColor, systemColor("ButtonBorder"),
+ "border-left-color not broken on inputs");
+ is(cs4.borderBottomColor, systemColor("ButtonBorder"),
+ "border-bottom-color not broken on inputs");
+ is(cs8.borderImageSource, cs9.borderImageSource, "border-image-source is blocked");
+ is(toRGBA(cs11.backgroundColor).a, 0, "background-color transparency is preserved on buttons");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_dont_use_document_fonts.html b/layout/style/test/test_dont_use_document_fonts.html
new file mode 100644
index 0000000000..59bc6c6d62
--- /dev/null
+++ b/layout/style/test/test_dont_use_document_fonts.html
@@ -0,0 +1,116 @@
+<!doctype html>
+<title>Test for preference to not use document fonts</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel='stylesheet' href='/resources/testharness.css'>
+<div id="content"></div>
+<script>
+const content = document.getElementById("content");
+
+// This is just a subset of browser.display.use_document_fonts.icon_font_allowlist
+// that we feel are worth double-checking via this test. In particular:
+// * Chromium Bug Tracker and https://developers.google.com use "Material Icons"
+// * Google Translate and Google Timeline use "Material Icons Extended"
+// * https://fonts.google.com/icons uses "Material Symbols Outlined"
+// * Google Calendar and Google Contacts use "Google Material Icons"
+const kKnownLigatureIconFonts = "Material Icons, Material Icons Extended, " +
+ "Material Symbols Outlined, Google Material Icons";
+
+setup({explicit_done: true })
+
+content.style.fontFamily = "initial";
+const kInitialFamily = getComputedStyle(content).fontFamily;
+content.style.fontFamily = "";
+
+const kTests = [
+ {
+ specified: "monospace",
+ computed: "monospace",
+ description: "Single generic family should not be changed",
+ },
+ {
+ specified: "monospace, sans-serif",
+ computed: "monospace, sans-serif",
+ description: "Generic families should not be changed",
+ },
+ {
+ specified: "Courier, monospace",
+ computed: "monospace, Courier",
+ description: "Generics are preferred, but may still fall back to document fonts",
+ },
+ {
+ specified: "system-ui, sans-serif",
+ computed: "sans-serif, system-ui",
+ description: "system-ui is not prioritized",
+ },
+ {
+ specified: "Courier, something-else",
+ computed: `${kInitialFamily}, Courier, something-else`,
+ description: "Generic is prepended to the font-family if none is found",
+ },
+ {
+ specified: kKnownLigatureIconFonts + ", something-else, sans-serif",
+ computed: kKnownLigatureIconFonts + ", sans-serif, something-else",
+ description: "Known ligature-icon fonts remain ahead of the generic",
+ },
+ {
+ specified: "Material Icons, something-else, Material Symbols Outlined, sans-serif",
+ computed: "Material Icons, sans-serif, something-else, Material Symbols Outlined",
+ description: "Generic is moved ahead of the first non-allowlisted font",
+ },
+ {
+ specified: "Material Icons, something-else, Material Symbols Outlined",
+ computed: `Material Icons, ${kInitialFamily}, something-else, Material Symbols Outlined`,
+ description: "Default generic is inserted ahead of the first non-allowlisted font",
+ },
+ {
+ specified: "Material Icons, cursive, Material Symbols Outlined, serif",
+ computed: "Material Icons, serif, cursive, Material Symbols Outlined",
+ description: "cursive is not treated as a generic to be prioritized",
+ },
+ {
+ specified: "Material Icons, fantasy, Material Symbols Outlined",
+ computed: `Material Icons, ${kInitialFamily}, fantasy, Material Symbols Outlined`,
+ description: "fantasy is not treated as a generic to be prioritized",
+ },
+];
+
+let systemFont;
+
+// compute expectations while the pref is not active yet.
+test(function() {
+ for (const test of kTests) {
+ content.style.fontFamily = "";
+ content.style.fontFamily = test.computed;
+ assert_not_equals(content.style.fontFamily, "", `computed font ${test.computed} was invalid`);
+ test.expected = getComputedStyle(content).fontFamily;
+ }
+
+ content.style.font = "menu";
+ systemFont = getComputedStyle(content).fontFamily;
+ assert_not_equals(systemFont, "", `computed menu system font was invalid`);
+
+ content.style.font = "";
+}, "Sanity");
+
+function runTest({ specified, computed, description, expected }) {
+ test(function() {
+ content.style.fontFamily = "";
+ content.style.fontFamily = specified;
+ assert_equals(getComputedStyle(content).fontFamily, expected);
+ }, description);
+}
+
+(async function() {
+ await SpecialPowers.pushPrefEnv({'set': [['browser.display.use_document_fonts', 0]]});
+ for (const test of kTests)
+ runTest(test);
+
+ test(function() {
+ content.style.font = "menu";
+ assert_equals(getComputedStyle(content).fontFamily, systemFont);
+ }, "System font should be honored");
+
+ done();
+})();
+</script>
diff --git a/layout/style/test/test_dynamic_change_causing_reflow.html b/layout/style/test/test_dynamic_change_causing_reflow.html
new file mode 100644
index 0000000000..7a000c87a6
--- /dev/null
+++ b/layout/style/test/test_dynamic_change_causing_reflow.html
@@ -0,0 +1,1014 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1131371
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1131371</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1131371">Mozilla Bug 1131371</a>
+<style>
+ #elemWithScrollbars { overflow: scroll }
+ .flexScrollerWithTransformedItems {
+ display: flex;
+ overflow: hidden;
+ width: 200px;
+ position: relative;
+ }
+ .flexScrollerWithTransformedItems div {
+ min-width: 50px;
+ min-height: 50px;
+ margin: 10px;
+ will-change: transform;
+ }
+ .absposFlexItem {
+ left: 250px;
+ top: 0;
+ position: absolute;
+ }
+ #tableCell,
+ #tableCellWithAbsPosChild {
+ display: table-cell;
+ }
+ .blockmargin {
+ margin: 25px 0;
+ }
+</style>
+<div id="display">
+ <div id="content">
+ </div>
+ <div id="elemWithAbsPosChild"><div style="position:absolute"></div></div>
+ <div id="elemWithFixedPosChild"><div style="position:fixed"></div></div>
+ <div id="elemWithScrollbars"></div>
+ <div id="elemWithoutScrollbars"></div>
+ <div class="flexScrollerWithTransformedItems">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div></div>
+ <div id="flexItemMovementTarget"></div>
+ </div>
+ <div class="flexScrollerWithTransformedItems">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div id="flexItemMovementTarget2"></div>
+ <div class="absposFlexItem"></div>
+ </div>
+ <input id="inputElem" type="image">
+ <textarea id="textareaElem">
+ Some
+ lines
+ of
+ text
+ </textarea>
+ <select id="selectElem">
+ <option>A</option>
+ <option>B</option>
+ <option>C</option>
+ </select>
+ <button id="buttonElem">
+ Something
+ </button>
+ <button id="buttonElemWithAbsPosChild"><div style="position:absolute"></div></button>
+ <div id="tableCell"></div>
+ <div id="tableCellWithAbsPosChild">
+ <div style="position: absolute"></div>
+ </div>
+ <div id="containNode">
+ <div></div>
+ </div>
+ <div id="containNodeWithAbsPosChild">
+ <div style="position: absolute"></div>
+ </div>
+ <div id="containNodeWithFixedPosChild">
+ <div style="position: fixed"></div>
+ </div>
+ <div id="containNodeWithFloatingChild">
+ <div style="float: left"></div>
+ </div>
+ <div id="containNodeWithMarginCollapsing" class="blockmargin">
+ <div class="blockmargin"></div>
+ </div>
+ <div id="contentVisibilityNode">
+ <div></div>
+ </div>
+</div>
+<pre id="test">
+<script>
+"use strict";
+
+/** Test for Bug 1131371 **/
+
+const contentVisibilityEnabled = SpecialPowers.getBoolPref("layout.css.content-visibility.enabled");
+const elemWithAbsPosChild = document.getElementById("elemWithAbsPosChild");
+const elemWithFixedPosChild = document.getElementById("elemWithFixedPosChild");
+const elemWithScrollbars = document.getElementById("elemWithScrollbars");
+const elemWithoutScrollbars = document.getElementById("elemWithoutScrollbars");
+const inputElem = document.getElementById("inputElem");
+const textareaElem = document.getElementById("textareaElem");
+const selectElem = document.getElementById("selectElem");
+const buttonElem = document.getElementById("buttonElem");
+const buttonElemWithAbsPosChild = document.getElementById("buttonElemWithAbsPosChild");
+const tableCell = document.getElementById("tableCell");
+const tableCellWithAbsPosChild = document.getElementById("tableCellWithAbsPosChild");
+
+for (let scroller of document.querySelectorAll(".flexScrollerWithTransformedItems")) {
+ scroller.scrollLeft = 10000;
+}
+
+const flexItemMovementTarget = document.getElementById("flexItemMovementTarget");
+const flexItemMovementTarget2 = document.getElementById("flexItemMovementTarget2");
+
+/**
+ * This test verifies that certain style changes do or don't cause reflow
+ * and/or frame construction. We do this by checking the framesReflowed &
+ * framesConstructed counts, before & after a style-change, and verifying
+ * that any change to these counts is in line with our expectations.
+ *
+ * Each entry in gTestcases contains these member-values:
+ * - beforeStyle (optional): initial value to use for "style" attribute.
+ * - afterStyle: value to change the "style" attribute to.
+ *
+ * Testcases may also include two optional member-values to express that reflow
+ * and/or frame construction *are* in fact expected:
+ * - expectConstruction (optional): if set to something truthy, then we expect
+ * frame construction to occur when afterStyle is set. Otherwise, we
+ * expect that frame construction should *not* occur.
+ * - expectReflow (optional): if set to something truthy, then we expect
+ * reflow to occur when afterStyle is set. Otherwise, we expect that
+ * reflow should *not* occur.
+ */
+const gTestcases = [
+ // Things that shouldn't cause reflow:
+ // -----------------------------------
+ // * Adding an outline (e.g. for focus ring).
+ {
+ afterStyle: "outline: 1px dotted black",
+ },
+
+ // * Changing between completely different outlines.
+ {
+ beforeStyle: "outline: 2px solid black",
+ afterStyle: "outline: 6px dashed yellow",
+ },
+
+ // * Adding a box-shadow.
+ {
+ afterStyle: "box-shadow: inset 3px 3px gray",
+ },
+ {
+ afterStyle: "box-shadow: 0px 0px 10px 30px blue"
+ },
+
+ // * Changing between completely different box-shadow values,
+ // e.g. from an upper-left shadow to a bottom-right shadow:
+ {
+ beforeStyle: "box-shadow: -15px -20px teal",
+ afterStyle: "box-shadow: 30px 40px yellow",
+ },
+
+ // * Adding a text-shadow.
+ {
+ afterStyle: "text-shadow: 3px 3px gray",
+ },
+ {
+ afterStyle: "text-shadow: 0px 0px 10px blue"
+ },
+
+ // * Changing between completely different text-shadow values,
+ // e.g. from an upper-left shadow to a bottom-right shadow:
+ {
+ beforeStyle: "text-shadow: -15px -20px teal",
+ afterStyle: "text-shadow: 30px 40px yellow",
+ },
+
+ // * switching overflow between things that shouldn't create scrollframes.
+ {
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: clip",
+ },
+
+ // Things that *should* cause reflow:
+ // ----------------------------------
+ // (e.g. to make sure our counts are actually measuring something)
+
+ // * Changing 'height' should cause reflow, but not frame construction.
+ {
+ beforeStyle: "height: 10px",
+ afterStyle: "height: 15px",
+ expectReflow: true,
+ },
+
+ // * Changing 'shape-outside' on a non-floating box should not cause anything to happen.
+ {
+ beforeStyle: "shape-outside: none",
+ afterStyle: "shape-outside: circle()",
+ },
+
+ // * Changing 'shape-outside' should cause reflow, but not frame construction.
+ {
+ beforeStyle: "float: left; shape-outside: none",
+ afterStyle: "float: left; shape-outside: circle()",
+ expectReflow: true,
+ },
+
+ // * Changing 'overflow' on <body> should cause reflow,
+ // but not frame reconstruction
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: visible",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Changing 'overflow' on <html> should cause reflow,
+ // but not frame reconstruction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: visible",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Setting 'overflow' on arbitrary node should cause reflow as well as
+ // frame reconstruction
+ {
+ /* beforeStyle: implicitly 'overflow:visible' */
+ afterStyle: "overflow: auto",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: visible",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+ // * but only reflow if we don't need to construct / unconstruct a new frame.
+ {
+ beforeStyle: "overflow: scroll",
+ afterStyle: "overflow: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: scroll",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ {
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: auto",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: hidden",
+ afterStyle: "overflow: scroll",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "overflow: scroll",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ {
+ elem: elemWithoutScrollbars,
+ beforeStyle: "scrollbar-width: auto",
+ afterStyle: "scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: elemWithScrollbars,
+ beforeStyle: "scrollbar-width: auto",
+ afterStyle: "scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ // Not the scrolling element, so nothing should happen.
+ elem: document.body,
+ beforeStyle: "scrollbar-width: none",
+ afterStyle: "scrollbar-width: auto",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ // Not the scrolling element, so nothing should happen.
+ elem: document.body,
+ beforeStyle: "scrollbar-width: auto",
+ afterStyle: "scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "scrollbar-width: none;",
+ afterStyle: "scrollbar-width: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: scroll; scrollbar-width: none;",
+ afterStyle: "overflow: scroll; scrollbar-width: auto",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "overflow: scroll; scrollbar-width: auto;",
+ afterStyle: "overflow: scroll; scrollbar-width: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // * Changing 'display' should cause frame construction and reflow.
+ {
+ beforeStyle: "display: inline",
+ afterStyle: "display: table",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+
+ // * Position changes trigger a reframe, unless whether we're a containing
+ // block doesn't change, in which case we just need to reflow.
+ {
+ beforeStyle: "position: static",
+ afterStyle: "position: absolute",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "position: absolute",
+ afterStyle: "position: fixed",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ beforeStyle: "position: relative",
+ afterStyle: "position: fixed",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+ // This doesn't change whether we're a containing block because there are no
+ // abspos descendants.
+ {
+ afterStyle: "position: static",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+
+ // This doesn't change whether we're a containing block, shouldn't reframe.
+ {
+ afterStyle: "position: sticky",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+
+ // These don't change whether we're a containing block for our
+ // absolutely-positioned child, so shouldn't reframe.
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: sticky",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+ {
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: sticky",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+ {
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: relative",
+ expectReflow: true,
+ },
+ {
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: sticky",
+ expectReflow: true,
+ },
+ {
+ // Even if we're a scroll frame.
+ elem: elemWithFixedPosChild,
+ afterStyle: "position: static; overflow: auto;",
+ beforeStyle: "position: relative; overflow: auto;",
+ expectReflow: true,
+ },
+ {
+ elem: tableCell,
+ afterStyle: "position: static;",
+ beforeStyle: "position: relative;",
+ expectReflow: true,
+ },
+ {
+ elem: tableCell,
+ afterStyle: "filter: none",
+ beforeStyle: "filter: saturate(1)",
+ expectReflow: false,
+ },
+
+ // These ones do though.
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: relative",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: static",
+ beforeStyle: "position: sticky",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: elemWithAbsPosChild,
+ afterStyle: "position: static; overflow: auto;",
+ beforeStyle: "position: relative; overflow: auto;",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: tableCellWithAbsPosChild,
+ afterStyle: "position: static;",
+ beforeStyle: "position: relative;",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+
+ // Adding transform to a scrollframe without abspos / fixedpos children shouldn't reframe.
+ {
+ elem: elemWithScrollbars,
+ afterStyle: "transform: translateX(1px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+
+ // <select> can't contain abspos / floating children so shouldn't reframe
+ // when changing containing block-ness.
+ {
+ elem: selectElem,
+ afterStyle: "transform: translateX(1px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: selectElem,
+ afterStyle: "position: relative",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+
+ // <button> shouldn't be reframed either in the absence of positioned descendants.
+ {
+ elem: buttonElem,
+ afterStyle: "transform: translateX(1px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: buttonElem,
+ afterStyle: "position: relative",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: buttonElemWithAbsPosChild,
+ afterStyle: "position: relative",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ // changing scroll-behavior should not cause reflow or frame construction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'scroll-behavior: auto' */
+ afterStyle: "scroll-behavior: smooth",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "scroll-behavior: smooth",
+ afterStyle: "scroll-behavior: auto",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'scroll-behavior: auto' */
+ afterStyle: "scroll-behavior: smooth",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "scroll-behavior: smooth",
+ afterStyle: "scroll-behavior: auto",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ // changing scroll-snap-type should not cause reflow or frame construction
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: y mandatory",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: x proximity",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.documentElement,
+ beforeStyle: "scroll-snap-type: y mandatory",
+ afterStyle: "scroll-snap-type: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: y mandatory",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ /* beforeStyle: implicitly 'scroll-snap-type: none' */
+ afterStyle: "scroll-snap-type: x proximity",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: document.body,
+ beforeStyle: "scroll-snap-type: y mandatory",
+ afterStyle: "scroll-snap-type: none",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: inputElem,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: textareaElem,
+ beforeStyle: "overflow: auto",
+ afterStyle: "overflow: hidden",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: flexItemMovementTarget,
+ beforeStyle: "transform: translateX(0)",
+ afterStyle: "transform: translateX(-100px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ {
+ elem: flexItemMovementTarget2,
+ beforeStyle: "transform: translateX(0)",
+ afterStyle: "transform: translateX(-100px)",
+ expectConstruction: false,
+ expectReflow: false,
+ },
+ // Style containment affects counters so we need to re-frame.
+ // For other containments, we only need to reflow.
+ {
+ elem: containNode,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: style",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: style",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: size",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNode,
+ beforeStyle: "contain: size",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ // paint/layout containment boxes establish an absolute positioning
+ // containing block, so need a re-frame to handle abs/fixed positioned boxes.
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: size",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithAbsPosChild,
+ beforeStyle: "contain: size",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: true,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: size",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFixedPosChild,
+ beforeStyle: "contain: size",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ // paint/layout containment boxes establish an independent formatting context
+ // but floats and margin collapsing can be handled without reconstruction.
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: size",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithFloatingChild,
+ beforeStyle: "contain: size",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithMarginCollapsing,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: paint",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithMarginCollapsing,
+ beforeStyle: "contain: paint",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithMarginCollapsing,
+ beforeStyle: "contain: none",
+ afterStyle: "contain: layout",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ {
+ elem: containNodeWithMarginCollapsing,
+ beforeStyle: "contain: layout",
+ afterStyle: "contain: none",
+ expectConstruction: false,
+ expectReflow: true,
+ },
+ // content-visibility: auto/hidden implies style containment contrary to
+ // content-visibility: visible, so we generally need a re-frame (see above)
+ // when going from one case to the other.
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: visible",
+ afterStyle: "content-visibility: hidden",
+ expectConstruction: contentVisibilityEnabled,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: hidden",
+ afterStyle: "content-visibility: visible",
+ expectConstruction: contentVisibilityEnabled,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: visible",
+ afterStyle: "content-visibility: auto",
+ expectConstruction: contentVisibilityEnabled,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: auto",
+ afterStyle: "content-visibility: visible",
+ expectConstruction: contentVisibilityEnabled,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: hidden",
+ afterStyle: "content-visibility: auto",
+ expectConstruction: false,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: auto",
+ afterStyle: "content-visibility: hidden",
+ expectConstruction: false,
+ expectReflow: contentVisibilityEnabled,
+ },
+ // However that's not the case if we force style containment explicitly.
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: visible; contain: style",
+ afterStyle: "content-visibility: hidden; contain: style",
+ expectConstruction: false,
+ expectReflow: contentVisibilityEnabled,
+ },
+ {
+ elem: contentVisibilityNode,
+ beforeStyle: "content-visibility: hidden; contain: style",
+ afterStyle: "content-visibility: visible; contain: style",
+ expectConstruction: false,
+ expectReflow: contentVisibilityEnabled,
+ },
+];
+
+// Helper function to let us call either "is" or "isnot" & assemble
+// the failure message, based on the provided parameters.
+function checkFinalCount(aFinalCount, aExpectedCount,
+ aExpectChange, aMsgPrefix, aCountDescription)
+{
+ let compareFunc;
+ let msg = aMsgPrefix;
+ if (aExpectChange) {
+ compareFunc = isnot;
+ msg += "should cause " + aCountDescription;
+ } else {
+ compareFunc = is;
+ msg += "should not cause " + aCountDescription;
+ }
+
+ compareFunc(aFinalCount, aExpectedCount, msg);
+}
+
+// Vars used in runOneTest that we really only have to look up once:
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+const gElem = document.getElementById("content");
+
+function runOneTest(aTestcase)
+{
+ // sanity-check that we have the one main thing we need:
+ if (!aTestcase.afterStyle) {
+ ok(false, "testcase is missing an 'afterStyle' to change to");
+ return;
+ }
+
+ // Figure out which element we'll be tweaking (defaulting to gElem)
+ let elem = aTestcase.elem ? aTestcase.elem : gElem;
+
+ // Verify that 'style' attribute is unset (avoid causing ourselves trouble):
+ const oldStyle = elem.getAttribute("style");
+
+ // Set the "before" style, and compose the first part of the message
+ // to be used in our "is"/"isnot" invocations:
+ let msgPrefix = "Changing style ";
+ if (aTestcase.beforeStyle) {
+ elem.setAttribute("style", aTestcase.beforeStyle);
+ msgPrefix += "from '" + aTestcase.beforeStyle + "' ";
+ }
+ msgPrefix += "to '" + aTestcase.afterStyle + "' ";
+ msgPrefix += "on " + elem.nodeName + " ";
+
+ // Establish initial counts:
+ let unusedVal = elem.offsetHeight; // flush layout
+ let origFramesConstructed = gUtils.framesConstructed;
+ let origFramesReflowed = gUtils.framesReflowed;
+
+ // Make the change and flush:
+ elem.setAttribute("style", aTestcase.afterStyle);
+ unusedVal = elem.offsetHeight; // flush layout
+
+ // Make our is/isnot assertions about whether things should have changed:
+ checkFinalCount(gUtils.framesConstructed, origFramesConstructed,
+ aTestcase.expectConstruction, msgPrefix,
+ "frame construction");
+ checkFinalCount(gUtils.framesReflowed, origFramesReflowed,
+ aTestcase.expectReflow, msgPrefix,
+ "reflow");
+
+ // Clean up!
+ if (oldStyle) {
+ elem.setAttribute("style", oldStyle);
+ } else {
+ elem.removeAttribute("style");
+ }
+
+ unusedVal = elem.offsetHeight; // flush layout
+}
+
+gTestcases.forEach(runOneTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_exposed_prop_accessors.html b/layout/style/test/test_exposed_prop_accessors.html
new file mode 100644
index 0000000000..765818bae4
--- /dev/null
+++ b/layout/style/test/test_exposed_prop_accessors.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375363
+-->
+<head>
+ <title>Test for cloning of CSS property values (including 'inherit', 'initial' and 'unset')</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * Test that makes sure that we have exposed getters/setters for all the
+ * various variants of our CSS property names that the spec calls for.
+ */
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+
+ var s = document.createElement("div").style;
+
+ is(s[info.domProp], "", prop + " should not be set yet");
+ s[info.domProp] = info.initial_values[0];
+ isnot(s[info.domProp], "", prop + " should now be set");
+ is(s[prop], s[info.domProp],
+ "Getting " + prop + " via name should work")
+ s = document.createElement("div").style;
+ is(s[info.domProp], "", prop + " should not be set here either");
+ s[prop] = info.initial_values[0];
+ isnot(s[info.prop], "", prop + " should now be set again");
+ is(s[info.domProp], s[prop],
+ "Setting " + prop + " via name should work");
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_extra_inherit_initial.html b/layout/style/test/test_extra_inherit_initial.html
new file mode 100644
index 0000000000..34c63b3626
--- /dev/null
+++ b/layout/style/test/test_extra_inherit_initial.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=940229
+-->
+<head>
+ <title>Test handling extra inherit/initial/unset in CSS declarations (Bug 940229)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=940229">Mozilla Bug 940229</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+/*
+ * Inspired by mistake in quotes noticed while reviewing bug 189519.
+ */
+
+let gPropsNeedComma = {
+ "font": true,
+ "font-family": true,
+ "voice-family": true,
+};
+
+let gElement = document.getElementById("testnode");
+let gDeclaration = gElement.style;
+
+let kValuesToTestThoroughly = 3;
+
+function test_property(property)
+{
+ let info = gCSSProperties[property];
+
+ let delim = (property in gPropsNeedComma) ? ", " : " ";
+
+ function test_value_pair(relation, val1, val2, extraval) {
+ let decl = property + ": " + val1 + delim + val2;
+ gElement.setAttribute("style", decl);
+ if ("subproperties" in info) {
+ // Shorthand property; inspect each subproperty value.
+ for (let subprop of info.subproperties) {
+ is(gDeclaration.getPropertyValue(subprop), "",
+ ["expected", extraval, "ignored", relation, "value in",
+ "'" + decl + "'", "when looking at subproperty",
+ "'" + subprop + "'"].join(" "));
+ }
+ } else {
+ // Longhand property.
+ is(gDeclaration.getPropertyValue(property), "",
+ ["expected", extraval, "ignored", relation, "value in",
+ "'" + decl + "'"].join(" "));
+ }
+ }
+
+ function test_value(value, valueIdx) {
+ let specialKeywords = [ "inherit", "initial", "unset" ];
+
+ if (valueIdx < kValuesToTestThoroughly) {
+ // For the first few values, we test each special-keyword both before
+ // and after the value.
+ for (let keyword of specialKeywords) {
+ test_value_pair("before", keyword, value, keyword);
+ test_value_pair("after", value, keyword, keyword);
+ }
+ } else {
+ // For later values, only test one keyword before & after it.
+ let keywordIdx =
+ (valueIdx - kValuesToTestThoroughly) % specialKeywords.length;
+ keyword = specialKeywords[keywordIdx];
+ test_value_pair("before", keyword, value, keyword);
+ test_value_pair("after", value, keyword, keyword);
+ }
+ }
+
+ for (let idx in info.initial_values) {
+ test_value(info.initial_values[idx], idx);
+ }
+ for (let idx in info.other_values) {
+ test_value(info.initial_values[idx], idx);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
+
+function start_test() {
+ for (let prop in gCSSProperties) {
+ test_property(prop);
+ }
+ SimpleTest.finish();
+}
+
+// Turn off CSS error reporting for this test, since it's a bit expensive,
+// and we're expecting to generate tons and tons of parse errors here.
+SpecialPowers.pushPrefEnv({ "set": [["layout.css.report_errors", false]] },
+ start_test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_first_letter_restrictions.html b/layout/style/test/test_first_letter_restrictions.html
new file mode 100644
index 0000000000..99aaba8b93
--- /dev/null
+++ b/layout/style/test/test_first_letter_restrictions.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1382786
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1382786</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="s"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1382786">Mozilla Bug 1382786</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="test"></div>
+<div id="control"></div>
+<script type="application/javascript">
+
+/** Test for Bug 1382786 **/
+var test = getComputedStyle($("test"), "::first-letter");
+var control = getComputedStyle($("control"), "::first-letter");
+for (let prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND) {
+ // Can't get useful info out of getComputedStyle.
+ continue;
+ }
+ let prereqs = "";
+ if (info.prerequisites) {
+ for (let name in info.prerequisites) {
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+ }
+ }
+ $("s").textContent = `
+ #control::first-letter { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::first-letter { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+ if (info.applies_to_first_letter) {
+ isnot(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should apply to ::first-letter`);
+ } else {
+ is(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should not apply to ::first-letter`);
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_first_line_restrictions.html b/layout/style/test/test_first_line_restrictions.html
new file mode 100644
index 0000000000..bb8e116e8b
--- /dev/null
+++ b/layout/style/test/test_first_line_restrictions.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1382786
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1382786</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="s"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1382786">Mozilla Bug 1382786</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="test"></div>
+<div id="control"></div>
+<script type="application/javascript">
+
+/** Test for Bug 1382786 **/
+var test = getComputedStyle($("test"), "::first-line");
+var control = getComputedStyle($("control"), "::first-line");
+for (let prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND) {
+ // Can't get useful info out of getComputedStyle.
+ continue;
+ }
+ let prereqs = "";
+ if (info.prerequisites) {
+ for (let name in info.prerequisites) {
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+ }
+ }
+ $("s").textContent = `
+ #control::first-line { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::first-line { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+ if (info.applies_to_first_line) {
+ isnot(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should apply to ::first-line`);
+ } else {
+ is(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should not apply to ::first-line`);
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_child_display_values.xhtml b/layout/style/test/test_flexbox_child_display_values.xhtml
new file mode 100644
index 0000000000..7ba870c69f
--- /dev/null
+++ b/layout/style/test/test_flexbox_child_display_values.xhtml
@@ -0,0 +1,178 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783415
+-->
+<head>
+ <meta charset="utf-8"/>
+ <title>Test "display" values of content in a flex container (Bug 783415)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783415">Mozilla Bug 783415</a>
+<div id="display">
+ <div id="wrapper"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+"use strict";
+
+/**
+ * Test "display" values of content in a flex container (Bug 783415)
+ * ================================================================
+ *
+ * This test creates content with a variety of specified "display" values
+ * and checks what the computed "display" is when we put that content
+ * in a flex container. (Generally, it'll be the "blockified" form of the
+ * specified display-value.)
+ */
+
+/*
+ * Utility function for getting computed style of "display".
+ *
+ * @arg aElem The element to query for its computed "display" value.
+ * @return The computed display value
+ */
+function getComputedDisplay(aElem) {
+ return window.getComputedStyle(aElem).display;
+}
+
+/*
+ * Function used for testing a given specified "display" value and checking
+ * its computed value against expectations.
+ *
+ * @arg aSpecifiedDisplay
+ * The specified value of "display" that we should test.
+ *
+ * @arg aExpectedDisplayAsFlexContainerChild
+ * (optional) The expected computed "display" when an element with
+ * aSpecifiedDisplay is a child of a flex container. If omitted,
+ * this argument defaults to aSpecifiedDisplay.
+ *
+ * @arg aExpectedDisplayAsOutOfFlowFlexContainerChild
+ * (optional) The expected computed "display" when an element with
+ * aSpecifiedDisplay is a child of a flex container *and* has
+ * position:[fixed|absolute] or float: [left|right] set. If omitted,
+ * this argument defaults to aExpectedDisplayAsFlexContainerChild.
+ */
+function testDisplayValue(aSpecifiedDisplay,
+ aExpectedDisplayAsFlexContainerChild,
+ aExpectedDisplayAsOutOfFlowFlexContainerChild) {
+ // DEFAULT-ARGUMENT-VALUES MAGIC: Make 2nd and 3rd args each default to
+ // the preceding arg, if they're unspecified.
+ if (typeof aExpectedDisplayAsFlexContainerChild == "undefined") {
+ aExpectedDisplayAsFlexContainerChild = aSpecifiedDisplay;
+ }
+ if (typeof aExpectedDisplayAsOutOfFlowFlexContainerChild == "undefined") {
+ aExpectedDisplayAsOutOfFlowFlexContainerChild =
+ aExpectedDisplayAsFlexContainerChild;
+ }
+
+ // FIRST: Create a node with display:aSpecifiedDisplay, and make sure that
+ // this original display-type is honored in a non-flex-container context.
+ let wrapper = document.getElementById("wrapper");
+ let node = document.createElement("div");
+ wrapper.appendChild(node);
+
+ node.style.display = aSpecifiedDisplay;
+ is(getComputedDisplay(node), aSpecifiedDisplay,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "should be the same as specified value, when parent is a block");
+
+
+ // SECOND: We make our node's parent into a flex container, and make sure
+ // that this produces the correct computed "display" value.
+ wrapper.style.display = "flex";
+ is(getComputedDisplay(node), aExpectedDisplayAsFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container");
+
+
+ // THIRD: We set "float" and "position" on our node (still inside of a
+ // flex container), and make sure that this produces the correct computed
+ // "display" value.
+ node.style.cssFloat = "left";
+ is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container and we're floated left");
+ node.style.cssFloat = "";
+
+ node.style.cssFloat = "right";
+ is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container and we're floated right");
+ node.style.cssFloat = "";
+
+ node.style.position = "absolute";
+ is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container and we're abs-pos");
+ node.style.position = "";
+
+ node.style.position = "fixed";
+ is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild,
+ "checking computed value of 'display: " + aSpecifiedDisplay + "' " +
+ "when parent is a flex container and we're fixed-pos");
+ node.style.position = "";
+
+ // FINALLY: Clean up -- remove the node we created, and turn the wrapper
+ // back into its original display type (a block).
+ wrapper.removeChild(node);
+ wrapper.style.display = "";
+}
+
+/*
+ * Main test function
+ */
+function main() {
+ testDisplayValue("none");
+ testDisplayValue("block");
+ testDisplayValue("flex");
+ testDisplayValue("inline-flex", "flex");
+ testDisplayValue("list-item");
+ testDisplayValue("table");
+ testDisplayValue("inline-table", "table");
+
+ // These values all compute to "block" in a flex container. Do them in a
+ // loop, so that I don't have to type "block" a zillion times.
+ var dispValsThatComputeToBlockInAFlexContainer = [
+ "inline",
+ "inline-block",
+ ];
+
+ dispValsThatComputeToBlockInAFlexContainer.forEach(
+ function(aSpecifiedDisplay) {
+ testDisplayValue(aSpecifiedDisplay, "block");
+ });
+
+ // Table-parts are special. When they're a child of a flex container,
+ // they normally don't get blockified -- instead, they trigger table-fixup
+ // and get wrapped in a table. So, their expected display as the child of
+ // a flex container is the same as their specified display. BUT, if
+ // we apply out-of-flow styling, then *that* blockifies them before
+ // we get to the table-fixup stage -- so then, their computed display
+ // is "block".
+ let tablePartsDispVals = [
+ "table-row-group",
+ "table-column",
+ "table-column-group",
+ "table-header-group",
+ "table-footer-group",
+ "table-row",
+ "table-cell",
+ "table-caption"
+ ];
+
+ tablePartsDispVals.forEach(
+ function(aSpecifiedDisplay) {
+ testDisplayValue(aSpecifiedDisplay, "block", "block");
+ });
+}
+
+main();
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_flex_grow_and_shrink.html b/layout/style/test/test_flexbox_flex_grow_and_shrink.html
new file mode 100644
index 0000000000..fc5090fd61
--- /dev/null
+++ b/layout/style/test/test_flexbox_flex_grow_and_shrink.html
@@ -0,0 +1,154 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696253
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for flex-grow and flex-shrink animation (Bug 696253)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ /* Set flex-grow and flex-shrink to nonzero values,
+ when no animations are applied. */
+
+ * { flex-grow: 10; flex-shrink: 20 }
+
+ /* Animations that we'll test (individually) in the script below: */
+ @keyframes flexGrowTwoToThree {
+ 0% { flex-grow: 2 }
+ 100% { flex-grow: 3 }
+ }
+ @keyframes flexShrinkTwoToThree {
+ 0% { flex-shrink: 2 }
+ 100% { flex-shrink: 3 }
+ }
+ @keyframes flexGrowZeroToZero {
+ 0% { flex-grow: 0 }
+ 100% { flex-grow: 0 }
+ }
+ @keyframes flexShrinkZeroToZero {
+ 0% { flex-shrink: 0 }
+ 100% { flex-shrink: 0 }
+ }
+ @keyframes flexGrowZeroToOne {
+ 0% { flex-grow: 0 }
+ 100% { flex-grow: 1 }
+ }
+ @keyframes flexShrinkZeroToOne {
+ 0% { flex-shrink: 0 }
+ 100% { flex-shrink: 1 }
+ }
+ @keyframes flexGrowOneToZero {
+ 0% { flex-grow: 1 }
+ 100% { flex-grow: 0 }
+ }
+ @keyframes flexShrinkOneToZero {
+ 0% { flex-shrink: 1 }
+ 100% { flex-shrink: 0 }
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696253">Mozilla Bug 696253</a>
+<div id="display">
+ <div id="myDiv"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for flex-grow and flex-shrink animation (Bug 696253) **/
+
+// take over the refresh driver
+advance_clock(0);
+
+// ANIMATIONS THAT SHOULD AFFECT COMPUTED STYLE
+// --------------------------------------------
+
+// flexGrowTwoToThree: 2.0 at 0%, 2.5 at 50%, 10 after animation is over
+var [ div, cs ] = new_div("animation: flexGrowTwoToThree linear 1s");
+is_approx(+cs.flexGrow, 2, 0.01, "flexGrowTwoToThree at 0.0s");
+advance_clock(500);
+is_approx(+cs.flexGrow, 2.5, 0.01, "flexGrowTwoToThree at 0.5s");
+advance_clock(1000);
+is(cs.flexGrow, "10", "flexGrowTwoToThree at 1.5s");
+done_div();
+
+// flexShrinkTwoToThree: 2.0 at 0%, 2.5 at 50%, 20 after animation is over
+[ div, cs ] = new_div("animation: flexShrinkTwoToThree linear 1s");
+is_approx(cs.flexShrink, 2, 0.01, "flexShrinkTwoToThree at 0.0s");
+advance_clock(500);
+is_approx(cs.flexShrink, 2.5, 0.01, "flexShrinkTwoToThree at 0.5s");
+advance_clock(1000);
+is(cs.flexShrink, "20", "flexShrinkTwoToThree at 1.5s");
+done_div();
+
+// flexGrowZeroToZero: 0 at 0%, 0 at 50%, 10 after animation is over
+[ div, cs ] = new_div("animation: flexGrowZeroToZero linear 1s");
+is(cs.flexGrow, "0", "flexGrowZeroToZero at 0.0s");
+advance_clock(500);
+is(cs.flexGrow, "0", "flexGrowZeroToZero at 0.5s");
+advance_clock(1000);
+is(cs.flexGrow, "10", "flexGrowZeroToZero at 1.5s");
+done_div();
+
+// flexShrinkZeroToZero: 0 at 0%, 0 at 50%, 20 after animation is over
+[ div, cs ] = new_div("animation: flexShrinkZeroToZero linear 1s");
+is(cs.flexShrink, "0", "flexShrinkZeroToZero at 0.0s");
+advance_clock(500);
+is(cs.flexShrink, "0", "flexShrinkZeroToZero at 0.5s");
+advance_clock(1000);
+is(cs.flexShrink, "20", "flexShrinkZeroToZero at 1.5s");
+done_div();
+
+// ANIMATIONS THAT DIDN'T USED TO AFFECT COMPUTED STYLE, BUT NOW DO
+// ----------------------------------------------------------------
+// (In an older version of the flexbox spec, flex-grow & flex-shrink were not
+// allowed to animate between 0 and other values. But now that's allowed.)
+
+// flexGrowZeroToOne: 0 at 0%, 0.5 at 50%, 10 after animation is over.
+[ div, cs ] = new_div("animation: flexGrowZeroToOne linear 1s");
+is(cs.flexGrow, "0", "flexGrowZeroToOne at 0.0s");
+advance_clock(500);
+is(cs.flexGrow, "0.5", "flexGrowZeroToOne at 0.5s");
+advance_clock(1000);
+is(cs.flexGrow, "10", "flexGrowZeroToOne at 1.5s");
+done_div();
+
+// flexShrinkZeroToOne: 0 at 0%, 0.5 at 50%, 20 after animation is over.
+[ div, cs ] = new_div("animation: flexShrinkZeroToOne linear 1s");
+is(cs.flexShrink, "0", "flexShrinkZeroToOne at 0.0s");
+advance_clock(500);
+is(cs.flexShrink, "0.5", "flexShrinkZeroToOne at 0.5s");
+advance_clock(1000);
+is(cs.flexShrink, "20", "flexShrinkZeroToOne at 1.5s");
+done_div();
+
+// flexGrowOneToZero: 1 at 0%, 0.5 at 50%, 10 after animation is over.
+[ div, cs ] = new_div("animation: flexGrowOneToZero linear 1s");
+is(cs.flexGrow, "1", "flexGrowOneToZero at 0.0s");
+advance_clock(500);
+is(cs.flexGrow, "0.5", "flexGrowOneToZero at 0.5s");
+advance_clock(1000);
+is(cs.flexGrow, "10", "flexGrowOneToZero at 1.5s");
+done_div();
+
+// flexShrinkOneToZero: 1 at 0%, 0.5 at 50%, 20 after animation is over.
+[ div, cs ] = new_div("animation: flexShrinkOneToZero linear 1s");
+is(cs.flexShrink, "1", "flexShrinkOneToZero at 0.0s");
+advance_clock(500);
+is(cs.flexShrink, "0.5", "flexShrinkOneToZero at 0.5s");
+advance_clock(1000);
+is(cs.flexShrink, "20", "flexShrinkOneToZero at 1.5s");
+done_div();
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_flex_shorthand.html b/layout/style/test/test_flexbox_flex_shorthand.html
new file mode 100644
index 0000000000..b8416403b6
--- /dev/null
+++ b/layout/style/test/test_flexbox_flex_shorthand.html
@@ -0,0 +1,280 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696253
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 696253</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696253">Mozilla Bug 696253</a>
+<div id="display">
+ <div id="content">
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 696253 **/
+/* (Testing the 'flex' CSS shorthand property) */
+
+// The CSS property name for the shorthand we're testing:
+const gFlexPropName = "flex";
+
+// Info from property_database.js on this property:
+const gFlexPropInfo = gCSSProperties[gFlexPropName];
+
+// The name of the property in the DOM (i.e. in elem.style):
+// (NOTE: In this case it's actually the same as the CSS property name --
+// "flex" -- but that's not guaranteed in general.)
+const gFlexDOMName = gFlexPropInfo.domProp;
+
+// Default values for shorthand subproperties, when they're not specified
+// explicitly in a testcase. This lets the testcases be more concise.
+//
+// The values here are from the flexbox spec on the 'flex' shorthand:
+// "When omitted, [flex-grow and flex-shrink are] set to '1'."
+// "When omitted [..., flex-basis's] specified value is '0%'."
+let gFlexShorthandDefaults = {
+ "flex-grow": "1",
+ "flex-shrink": "1",
+ "flex-basis": "0%"
+};
+
+let gFlexShorthandTestcases = [
+/*
+ {
+ "flex": "SPECIFIED value for flex shorthand",
+
+ // Expected Computed Values of Subproperties
+ // Semi-optional -- if unspecified, the expected value is taken
+ // from gFlexShorthandDefaults.
+ "flex-grow": "EXPECTED computed value for flex-grow property",
+ "flex-shrink": "EXPECTED computed value for flex-shrink property",
+ "flex-basis": "EXPECTED computed value for flex-basis property",
+ },
+*/
+
+ // Initial values of subproperties:
+ // --------------------------------
+ // (checked by another test that uses property_database.js, too, but
+ // might as well check here, too, for thoroughness).
+ {
+ "flex": "",
+ "flex-grow": "0",
+ "flex-shrink": "1",
+ "flex-basis": "auto",
+ },
+ {
+ "flex": "initial",
+ "flex-grow": "0",
+ "flex-shrink": "1",
+ "flex-basis": "auto",
+ },
+
+ // Special keyword "none" --> "0 0 auto"
+ // -------------------------------------
+ {
+ "flex": "none",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ "flex-basis": "auto",
+ },
+
+ // One Value (numeric) --> sets flex-grow
+ // --------------------------------------
+ {
+ "flex": "0",
+ "flex-grow": "0",
+ },
+ {
+ "flex": "5",
+ "flex-grow": "5",
+ },
+ {
+ "flex": "1000",
+ "flex-grow": "1000",
+ },
+ {
+ "flex": "0.0000001",
+ "flex-grow": "1e-7"
+ },
+ {
+ "flex": "20000000",
+ "flex-grow": "20000000",
+ },
+
+ // One Value (length or other nonnumeric) --> sets flex-basis
+ // ----------------------------------------------------------
+ {
+ "flex": "0px",
+ "flex-basis": "0px",
+ },
+ {
+ "flex": "0%",
+ "flex-basis": "0%",
+ },
+ {
+ "flex": "25px",
+ "flex-basis": "25px",
+ },
+ {
+ "flex": "5%",
+ "flex-basis": "5%",
+ },
+ {
+ "flex": "auto",
+ "flex-basis": "auto",
+ },
+ {
+ "flex": "fit-content",
+ "flex-basis": "fit-content",
+ },
+ {
+ "flex": "calc(5px + 6px)",
+ "flex-basis": "11px",
+ },
+ {
+ "flex": "calc(15% + 30px)",
+ "flex-basis": "calc(15% + 30px)",
+ },
+
+ // Two Values (numeric) --> sets flex-grow, flex-shrink
+ // ----------------------------------------------------
+ {
+ "flex": "0 0",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ },
+ {
+ "flex": "0 2",
+ "flex-grow": "0",
+ "flex-shrink": "2",
+ },
+ {
+ "flex": "3 0",
+ "flex-grow": "3",
+ "flex-shrink": "0",
+ },
+ {
+ "flex": "0.5000 2.03",
+ "flex-grow": "0.5",
+ "flex-shrink": "2.03",
+ },
+ {
+ "flex": "300.0 500.0",
+ "flex-grow": "300",
+ "flex-shrink": "500",
+ },
+
+ // Two Values (numeric & length-ish) --> sets flex-grow, flex-basis
+ // ----------------------------------------------------------------
+ {
+ "flex": "0 0px",
+ "flex-grow": "0",
+ "flex-basis": "0px",
+ },
+ {
+ "flex": "0 0%",
+ "flex-grow": "0",
+ "flex-basis": "0%",
+ },
+ {
+ "flex": "10 30px",
+ "flex-grow": "10",
+ "flex-basis": "30px",
+ },
+ {
+ "flex": "99px 2.3",
+ "flex-grow": "2.3",
+ "flex-basis": "99px",
+ },
+ {
+ "flex": "99% 6",
+ "flex-grow": "6",
+ "flex-basis": "99%",
+ },
+ {
+ "flex": "auto 5",
+ "flex-grow": "5",
+ "flex-basis": "auto",
+ },
+ {
+ "flex": "5 fit-content",
+ "flex-grow": "5",
+ "flex-basis": "fit-content",
+ },
+ {
+ "flex": "calc(5% + 10px) 3",
+ "flex-grow": "3",
+ "flex-basis": "calc(5% + 10px)",
+ },
+
+ // Three Values --> Sets all three subproperties
+ // ---------------------------------------------
+ {
+ "flex": "0 0 0",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ "flex-basis": "0px",
+ },
+ {
+ "flex": "0.0 0.00 0px",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ "flex-basis": "0px",
+ },
+ {
+ "flex": "0% 0 0",
+ "flex-grow": "0",
+ "flex-shrink": "0",
+ "flex-basis": "0%",
+ },
+ {
+ "flex": "10px 3 2",
+ "flex-grow": "3",
+ "flex-shrink": "2",
+ "flex-basis": "10px",
+ },
+];
+
+function runFlexShorthandTest(aFlexShorthandTestcase)
+{
+ let content = document.getElementById("content");
+
+ let elem = document.createElement("div");
+
+ elem.style[gFlexDOMName] = aFlexShorthandTestcase[gFlexPropName];
+ content.appendChild(elem);
+
+ gFlexPropInfo.subproperties.forEach(function(aSubPropName) {
+ var expectedVal = aSubPropName in aFlexShorthandTestcase ?
+ aFlexShorthandTestcase[aSubPropName] :
+ gFlexShorthandDefaults[aSubPropName];
+
+ // Compare computed value against expected computed value (from testcase)
+ is(window.getComputedStyle(elem).getPropertyValue(aSubPropName),
+ expectedVal,
+ "Computed value of subproperty \"" + aSubPropName + "\" when we set \"" +
+ gFlexPropName + ": " + aFlexShorthandTestcase[gFlexPropName] + "\"");
+ });
+
+ // Clean up
+ content.removeChild(elem);
+}
+
+function main() {
+ gFlexShorthandTestcases.forEach(runFlexShorthandTest);
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_focus_order.html b/layout/style/test/test_flexbox_focus_order.html
new file mode 100644
index 0000000000..0c1f023e3c
--- /dev/null
+++ b/layout/style/test/test_flexbox_focus_order.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=812687
+-->
+<head>
+ <title>Test for Bug 812687: focus order of reordered flex items</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>
+ .container { display: flex; }
+
+ #focus1 { background: yellow; }
+ #focus2 { background: lightgray; }
+ #focus3 { background: orange; }
+ #focus4 { background: lightblue; }
+ #focus5 { background: pink; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=812687">Mozilla Bug 812687</a>
+<p id="display">
+ <a href="#" id="beforeContainer">Link before container</a>
+ <!-- This flex container's children are reordered visually with the "order"
+ CSS property, but their focus order (when tabbing through them) should
+ match their DOM order. So, #focus1 should receive focus before the other
+ flex items, even though it isn't visually the first flex item. And so
+ on, up to #focus5, which is visually first (due to its negative "order"
+ value) but should be focused last (due to being last in the DOM). -->
+ <div class="container">
+ <a href="#" id="focus1">1</a>
+ <div><a href="#" id="focus2">2</a></div>
+ <div style="order: 100"><a href="#" id="focus3">3</a></div>
+ <div><a href="#" id="focus4">4</a></div>
+ <a href="#" id="focus5" style="order: -1">5</a>
+ </div>
+</p>
+<div id="content" style="display: none"></div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 812687 **/
+
+const gExpectedFocusedIds = [
+ "focus1",
+ "focus2",
+ "focus3",
+ "focus4",
+ "focus5"
+];
+
+function doTest() {
+ // First, we focus something just before the flex container:
+ document.getElementById('beforeContainer').focus();
+ is(document.activeElement, document.getElementById('beforeContainer'),
+ "explicitly-focused link should gain focus");
+
+ // And then we advance focus across the focusable things in the flex container
+ // and check that we traverse them in the expected order:
+ for (let expectedId of gExpectedFocusedIds) {
+ synthesizeKey("KEY_Tab");
+ is(document.activeElement, document.getElementById(expectedId),
+ "expecting element '#" + expectedId + "' to be focused");
+ }
+
+ SimpleTest.finish();
+}
+
+// Before we start, we have to bump Mac to make its 'tab'-key focus behavior
+// predicatble:
+function begin() {
+ SpecialPowers.pushPrefEnv({ set: [[ "accessibility.tabfocus", 7 ]] }, doTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(begin);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_layout.html b/layout/style/test/test_flexbox_layout.html
new file mode 100644
index 0000000000..49ee287aa2
--- /dev/null
+++ b/layout/style/test/test_flexbox_layout.html
@@ -0,0 +1,184 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666041
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 666041</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="flexbox_layout_testcases.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666041">Mozilla Bug 666041</a>
+<div id="display">
+ <div id="content">
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 666041 **/
+
+/* Flexbox Layout Tests
+ * --------------------
+ * This mochitest exercises our implementation of the flexbox layout algorithm
+ * by creating a flex container, inserting some flexible children, and then
+ * verifying that the computed width of those children is what we expect.
+ *
+ * See flexbox_layout_testcases.js for the actual testcases & testcase format.
+ */
+
+function getComputedStyleWrapper(elem, prop)
+{
+ return window.getComputedStyle(elem).getPropertyValue(prop);
+}
+
+function setPossiblyAliasedProperty(aElem, aPropertyName, aPropertyValue,
+ aPropertyMapping)
+{
+ let actualPropertyName = (aPropertyName in aPropertyMapping ?
+ aPropertyMapping[aPropertyName] : aPropertyName);
+
+ if (!gCSSProperties[actualPropertyName]) {
+ ok(false, "Bug in test: property '" + actualPropertyName +
+ "' doesn't exist in gCSSProperties");
+ } else {
+ let domPropertyName = gCSSProperties[actualPropertyName].domProp;
+ aElem.style[domPropertyName] = aPropertyValue;
+ }
+}
+
+// Helper function to strip "px" off the end of a string
+// (so that we can compare two lengths using "isfuzzy()" with an epsilon)
+function stripPx(aLengthInPx)
+{
+ let pxOffset = aLengthInPx.length - 2; // subtract off length of "px"
+
+ // Sanity-check the arg:
+ ok(pxOffset > 0 && aLengthInPx.substr(pxOffset) == "px",
+ "expecting value with 'px' units");
+
+ return aLengthInPx.substr(0, pxOffset);
+}
+
+// The main test function.
+// aFlexboxTestcase is an entry from the list in flexbox_layout_testcases.js
+function testFlexboxTestcase(aFlexboxTestcase, aFlexDirection, aPropertyMapping)
+{
+ let content = document.getElementById("content");
+
+ // Create flex container
+ let flexContainer = document.createElement("div");
+ flexContainer.style.display = "flex";
+ flexContainer.style.flexDirection = aFlexDirection;
+ setPossiblyAliasedProperty(flexContainer, "_main-size",
+ gDefaultFlexContainerSize,
+ aPropertyMapping);
+
+ // Apply testcase's customizations for flex container (if any).
+ if (aFlexboxTestcase.container_properties) {
+ for (let propName in aFlexboxTestcase.container_properties) {
+ let propValue = aFlexboxTestcase.container_properties[propName];
+ setPossiblyAliasedProperty(flexContainer, propName, propValue,
+ aPropertyMapping);
+ }
+ }
+
+ // Create & append flex items
+ aFlexboxTestcase.items.forEach(function(aChildSpec) {
+ // Create an element for our item
+ let child = document.createElement("div");
+
+ // Set all the specified properties on our item
+ for (let propName in aChildSpec) {
+ // aChildSpec[propName] is either a specified value,
+ // or an array of [specifiedValue, computedValue]
+ let specifiedValue = Array.isArray(aChildSpec[propName]) ?
+ aChildSpec[propName][0] :
+ aChildSpec[propName];
+
+ // SANITY CHECK:
+ if (Array.isArray(aChildSpec[propName])) {
+ ok(aChildSpec[propName].length >= 2 &&
+ aChildSpec[propName].length <= 3,
+ "unexpected number of elements in array within child spec");
+ }
+
+ if (specifiedValue !== null) {
+ setPossiblyAliasedProperty(child, propName, specifiedValue,
+ aPropertyMapping);
+ }
+ }
+
+ // Append the item to the flex container
+ flexContainer.appendChild(child);
+ });
+
+ // Append the flex container
+ content.appendChild(flexContainer);
+
+ // NOW: Test the computed style on the flex items
+ let child = flexContainer.firstChild;
+ for (let i = 0; i < aFlexboxTestcase.items.length; i++) {
+ if (!child) { // sanity
+ ok(false, "should have created a child for each child-spec");
+ }
+
+ let childSpec = aFlexboxTestcase.items[i];
+ for (let propName in childSpec) {
+ if (Array.isArray(childSpec[propName])) {
+ let expectedVal = childSpec[propName][1];
+ let actualPropName = (propName in aPropertyMapping ?
+ aPropertyMapping[propName] : propName);
+ let actualVal = getComputedStyleWrapper(child, actualPropName);
+ let message = "computed value of '" + actualPropName +
+ "' should match expected";
+
+ if (childSpec[propName].length > 2) {
+ // 3rd entry in array is epsilon
+ // Need to strip off "px" units in order to use epsilon:
+ let actualValNoPx = stripPx(actualVal);
+ let expectedValNoPx = stripPx(expectedVal);
+ isfuzzy(actualValNoPx, expectedValNoPx,
+ childSpec[propName][2], message);
+ } else {
+ is(actualVal, expectedVal, message);
+ }
+ }
+ }
+
+ child = child.nextSibling;
+ }
+
+ // Clean up: drop the flex container.
+ content.removeChild(flexContainer);
+}
+
+function main()
+{
+ gFlexboxTestcases.forEach(
+ function(aTestcase) {
+ testFlexboxTestcase(aTestcase, "",
+ gRowPropertyMapping);
+ testFlexboxTestcase(aTestcase, "row",
+ gRowPropertyMapping);
+ testFlexboxTestcase(aTestcase, "row-reverse",
+ gRowReversePropertyMapping);
+ testFlexboxTestcase(aTestcase, "column",
+ gColumnPropertyMapping);
+ testFlexboxTestcase(aTestcase, "column-reverse",
+ gColumnReversePropertyMapping);
+ }
+ );
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_order.html b/layout/style/test/test_flexbox_order.html
new file mode 100644
index 0000000000..64b5431da8
--- /dev/null
+++ b/layout/style/test/test_flexbox_order.html
@@ -0,0 +1,194 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666041
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 666041</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ div.ref {
+ display: none;
+ height: 30px;
+ }
+
+ refA, refB, refC {
+ display: block;
+ float: left;
+ }
+
+ div#a, refA {
+ background: lightgreen;
+ width: 20px;
+ height: 30px;
+ }
+ div#b, refB {
+ background: orange;
+ width: 30px;
+ height: 30px;
+ }
+ div#c, refC {
+ background: blue;
+ width: 50px;
+ height: 30px;
+ }
+ div#flexContainer {
+ display: flex;
+ width: 100px;
+ height: 30px;
+ }
+ div#flexContainerParent {
+ display: none;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666041">Mozilla Bug 666041</a>
+<div id="display">
+
+ <!-- Reference cases (display:none; only shown during initRefSnapshots) -->
+ <div id="references">
+ <div class="ref" id="abc"><refA></refA><refB></refB><refC></refC></div>
+ <div class="ref" id="acb"><refA></refA><refC></refC><refB></refB></div>
+ <div class="ref" id="bac"><refB></refB><refA></refA><refC></refC></div>
+ <div class="ref" id="bca"><refB></refB><refC></refC><refA></refA></div>
+ <div class="ref" id="cab"><refC></refC><refA></refA><refB></refB></div>
+ <div class="ref" id="cba"><refC></refC><refB></refB><refA></refA></div>
+ </div>
+
+ <div id="flexContainerParent">
+ <!-- The flex container that we'll be testing
+ (its parent is display:none initially) -->
+ <div id="flexContainer">
+ <div id="a"></div>
+ <div id="b"></div>
+ <div id="c"></div>
+ </div>
+ </div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 666041 **/
+
+/* This testcase ensures that we honor the "order" property when ordering
+ * flex items within a flex container.
+ *
+ * Note: The items in this testcase don't overlap, so this testcase does _not_
+ * test paint ordering. It only tests horizontal ordering in a flex container.
+ */
+
+// DATA
+// ----
+
+// This will store snapshots of our reference divs
+let gRefSnapshots = {};
+
+// These are the sets of 'order' values that we'll test.
+// The first three values in each array are the 'order' values that we'll
+// assign to elements a, b, and c (respectively). The final value in each
+// array is the ID of the expected reference rendering.
+let gOrderTestcases = [
+ // The 6 basic permutations:
+ [ 1, 2, 3, "abc"],
+ [ 1, 3, 2, "acb"],
+ [ 2, 1, 3, "bac"],
+ [ 2, 3, 1, "cab"],
+ [ 3, 1, 2, "bca"],
+ [ 3, 2, 1, "cba"],
+
+ // Test negative values
+ [ 1, -5, -2, "bca"],
+ [ -50, 0, -2, "acb"],
+
+ // Non-integers should be ignored.
+ // (So, they'll leave their div with the initial 'order' value, which is 0.)
+ [ 1, 1.5, 2, "bac"],
+ [ 2.5, 3.4, 1, "abc"],
+ [ 0.5, 1, 1.5, "acb"],
+
+ // Decimal values that happen to be equal to integers (e.g. "3.0") are still
+ // <numbers>, and are _not_ <integers>.
+ // Source: http://www.w3.org/TR/CSS21/syndata.html#value-def-integer
+ // (So, they'll leave their div with the initial 'order' value, which is 0.)
+ // (NOTE: We have to use quotes around "3.0" and "2.0" to be sure JS doesn't
+ // coerce them into integers before we get a chance to set them in CSS.)
+ [ "3.0", "2.0", "1.0", "abc"],
+ [ 3, "2.0", 1, "bca"],
+];
+
+// FUNCTIONS
+// ---------
+
+function initRefSnapshots() {
+ let refIds = ["abc", "acb", "bac", "bca", "cab", "cba"];
+ for (let id of refIds) {
+ let elem = document.getElementById(id);
+ elem.style.display = "block";
+ gRefSnapshots[id] = snapshotWindow(window, false);
+ elem.style.display = "";
+ }
+}
+
+function complainIfSnapshotsDiffer(aSnap1, aSnap2, aMsg) {
+ let compareResult = compareSnapshots(aSnap1, aSnap2, true);
+ ok(compareResult[0],
+ "flex container rendering should match expected (" + aMsg +")");
+ if (!compareResult[0]) {
+ todo(false, "TESTCASE: " + compareResult[1]);
+ todo(false, "REFERENCE: "+ compareResult[2]);
+ }
+}
+
+function runOrderTestcase(aOrderTestcase) {
+ // Sanity-check
+ ok(Array.isArray(aOrderTestcase), "expecting testcase to be an array");
+ is(aOrderTestcase.length, 4, "expecting testcase to have 4 elements");
+
+ document.getElementById("a").style.order = aOrderTestcase[0];
+ document.getElementById("b").style.order = aOrderTestcase[1];
+ document.getElementById("c").style.order = aOrderTestcase[2];
+
+ let snapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(snapshot, gRefSnapshots[aOrderTestcase[3]],
+ aOrderTestcase);
+
+ // Clean up
+ for (let id of ["a", "b", "c"]) {
+ document.getElementById(id).style.order = "";
+ }
+}
+
+// Main Function
+function main() {
+ initRefSnapshots();
+
+ // un-hide the flex container's parent
+ let flexContainerParent = document.getElementById("flexContainerParent");
+ flexContainerParent.style.display = "block";
+
+ // Initial sanity-check: should be in expected document order
+ let initialSnapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(initialSnapshot, gRefSnapshots.abc,
+ "initial flex container rendering, " +
+ "no 'order' value yet");
+
+ // OK, now we run our tests
+ gOrderTestcases.forEach(runOrderTestcase);
+
+ // Re-hide the flex container at the end
+ flexContainerParent.style.display = "";
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_order_abspos.html b/layout/style/test/test_flexbox_order_abspos.html
new file mode 100644
index 0000000000..bf4c99aa76
--- /dev/null
+++ b/layout/style/test/test_flexbox_order_abspos.html
@@ -0,0 +1,217 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1345873
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1345873</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ div.ref {
+ display: none;
+ height: 30px;
+ }
+
+ refA, refB, refC {
+ display: block;
+ float: left;
+ }
+
+ div#a, refA {
+ background: lightgreen;
+ width: 20px;
+ height: 30px;
+ }
+ div#b, refB {
+ background: orange;
+ width: 30px;
+ height: 30px;
+ }
+ div#c, refC {
+ background: blue;
+ width: 50px;
+ height: 30px;
+ }
+ div#flexContainer {
+ display: flex;
+ width: 100px;
+ height: 30px;
+ }
+ div#flexContainerParent {
+ display: none;
+ }
+ .abs {
+ position: absolute !important;
+ width: 15px !important;
+ height: 15px !important;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1345873">Mozilla Bug 1345873</a>
+<div id="display">
+
+ <!-- Reference cases (display:none; only shown during initRefSnapshots) -->
+ <div id="references">
+ <div class="ref" id="abc"><refA></refA><refB></refB><refC></refC></div>
+ <div class="ref" id="Abc">
+ <refA class="abs"></refA><refB></refB><refC></refC></div>
+ <div class="ref" id="Bac">
+ <refB class="abs"></refB><refA></refA><refC></refC></div>
+ <div class="ref" id="Bca">
+ <refB class="abs"></refB><refC></refC><refA></refA></div>
+ <div class="ref" id="Cab">
+ <refC class="abs"></refC><refA></refA><refB></refB></div>
+ <div class="ref" id="ABc">
+ <refA class="abs"></refA><refB class="abs"></refB><refC></refC></div>
+ <div class="ref" id="ACb">
+ <refA class="abs"></refA><refC class="abs"></refC><refB></refB></div>
+ <div class="ref" id="BCa">
+ <refB class="abs"></refB><refC class="abs"></refC><refA></refA></div>
+ <div class="ref" id="ABC">
+ <refA class="abs"></refA><refB class="abs"></refB><refC class="abs"></refC></div>
+ </div>
+
+ <div id="flexContainerParent">
+ <!-- The flex container that we'll be testing
+ (its parent is display:none initially) -->
+ <div id="flexContainer">
+ <div id="a"></div>
+ <div id="b"></div>
+ <div id="c"></div>
+ </div>
+ </div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 1345873 **/
+
+/* This testcase ensures that we honor the "order" property when ordering
+ * flex items within a flex container.
+ *
+ * Note: The items in this testcase don't overlap, so this testcase does _not_
+ * test paint ordering. It only tests horizontal ordering in a flex container.
+ */
+
+// DATA
+// ----
+
+// This will store snapshots of our reference divs
+let gRefSnapshots = {};
+
+// These are the sets of 'order' values that we'll test.
+// * The first three values in each array are the 'order' values that we'll
+// assign to elements a, b, and c (respectively).
+// * The next value is a string containing the concatenated IDs of any
+// elements that should be absolutely positioned.
+// * The final value in each array is the ID of the expected reference
+// rendering. (By convention, in those IDs, capital = abspos)
+var gOrderTestcases = [
+ // Just one child is abspos:
+ [ 1, 2, 3, "a", "Abc"],
+ [ 1, 2, 3, "b", "Bac"],
+ [ 1, 2, 3, "c", "Cab"],
+ [ 2, 3, 1, "b", "Bca"],
+ [ 3, 1, 1, "b", "Bca"],
+
+ // Two children are abspos:
+ // (Note: "order" doesn't influence position or paint order for abspos
+ // children - only for (in-flow) flex items.)
+ [ 1, 2, 3, "ab", "ABc"],
+ [ 2, 1, 3, "ab", "ABc"],
+ [ 1, 2, 3, "ac", "ACb"],
+ [ 3, 2, 1, "ac", "ACb"],
+ [ 3, 2, 1, "bc", "BCa"],
+
+ // All three children are abspos:
+ // (Rendering always the same regardless of "order" values)
+ [ 1, 2, 3, "abc", "ABC"],
+ [ 3, 1, 2, "abc", "ABC"],
+ [ 3, 2, 1, "abc", "ABC"],
+];
+
+// FUNCTIONS
+// ---------
+
+function initRefSnapshots() {
+ let refIds = ["abc",
+ "Abc", "Bac", "Bca", "Cab",
+ "ABc", "ACb", "BCa",
+ "ABC"];
+ for (let id of refIds) {
+ let elem = document.getElementById(id);
+ elem.style.display = "block";
+ gRefSnapshots[id] = snapshotWindow(window, false);
+ elem.style.display = "";
+ }
+}
+
+function complainIfSnapshotsDiffer(aSnap1, aSnap2, aMsg) {
+ let compareResult = compareSnapshots(aSnap1, aSnap2, true);
+ ok(compareResult[0],
+ "flex container rendering should match expected (" + aMsg +")");
+ if (!compareResult[0]) {
+ todo(false, "TESTCASE: " + compareResult[1]);
+ todo(false, "REFERENCE: "+ compareResult[2]);
+ }
+}
+
+function runOrderTestcase(aOrderTestcase) {
+ // Sanity-check
+ ok(Array.isArray(aOrderTestcase), "expecting testcase to be an array");
+ is(aOrderTestcase.length, 5, "expecting testcase to have 5 elements");
+
+ document.getElementById("a").style.order = aOrderTestcase[0];
+ document.getElementById("b").style.order = aOrderTestcase[1];
+ document.getElementById("c").style.order = aOrderTestcase[2];
+
+ let idsToMakeAbspos = aOrderTestcase[3].split("");
+ for (let absPosId of idsToMakeAbspos) {
+ document.getElementById(absPosId).classList.add("abs");
+ }
+
+ let snapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(snapshot, gRefSnapshots[aOrderTestcase[4]],
+ aOrderTestcase);
+
+ // Clean up
+ for (let id of ["a", "b", "c"]) {
+ document.getElementById(id).style.order = "";
+ document.getElementById(id).classList.remove("abs");
+ }
+}
+
+// Main Function
+function main() {
+ initRefSnapshots();
+
+ // un-hide the flex container's parent
+ let flexContainerParent = document.getElementById("flexContainerParent");
+ flexContainerParent.style.display = "block";
+
+ // Initial sanity-check: should be in expected document order
+ let initialSnapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(initialSnapshot, gRefSnapshots.abc,
+ "initial flex container rendering, " +
+ "no 'order' value yet");
+
+ // OK, now we run our tests
+ gOrderTestcases.forEach(runOrderTestcase);
+
+ // Re-hide the flex container at the end
+ flexContainerParent.style.display = "";
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_order_table.html b/layout/style/test/test_flexbox_order_table.html
new file mode 100644
index 0000000000..2423d5d6d6
--- /dev/null
+++ b/layout/style/test/test_flexbox_order_table.html
@@ -0,0 +1,198 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=799775
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 799775</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ div.ref {
+ display: none;
+ height: 30px;
+ }
+
+ refA, refB, refC {
+ display: block;
+ float: left;
+ }
+
+ div#a, div#b, div#c {
+ display: table;
+ }
+
+ div#a, refA {
+ background: lightgreen;
+ width: 20px;
+ height: 30px;
+ }
+ div#b, refB {
+ background: orange;
+ width: 30px;
+ height: 30px;
+ }
+ div#c, refC {
+ background: blue;
+ width: 50px;
+ height: 30px;
+ }
+ div#flexContainer {
+ display: flex;
+ width: 100px;
+ height: 30px;
+ }
+ div#flexContainerParent {
+ display: none;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=799775">Mozilla Bug 799775</a>
+<div id="display">
+
+ <!-- Reference cases (display:none; only shown during initRefSnapshots) -->
+ <div id="references">
+ <div class="ref" id="abc"><refA></refA><refB></refB><refC></refC></div>
+ <div class="ref" id="acb"><refA></refA><refC></refC><refB></refB></div>
+ <div class="ref" id="bac"><refB></refB><refA></refA><refC></refC></div>
+ <div class="ref" id="bca"><refB></refB><refC></refC><refA></refA></div>
+ <div class="ref" id="cab"><refC></refC><refA></refA><refB></refB></div>
+ <div class="ref" id="cba"><refC></refC><refB></refB><refA></refA></div>
+ </div>
+
+ <div id="flexContainerParent">
+ <!-- The flex container that we'll be testing
+ (its parent is display:none initially) -->
+ <div id="flexContainer">
+ <div id="a"></div>
+ <div id="b"></div>
+ <div id="c"></div>
+ </div>
+ </div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 799775 **/
+
+/* This testcase ensures that we honor the "order" property when ordering
+ * tables as flex items within a flex container.
+ *
+ * Note: The items in this testcase don't overlap, so this testcase does _not_
+ * test paint ordering. It only tests horizontal ordering in a flex container.
+ */
+
+// DATA
+// ----
+
+// This will store snapshots of our reference divs
+let gRefSnapshots = {};
+
+// These are the sets of 'order' values that we'll test.
+// The first three values in each array are the 'order' values that we'll
+// assign to elements a, b, and c (respectively). The final value in each
+// array is the ID of the expected reference rendering.
+let gOrderTestcases = [
+ // The 6 basic permutations:
+ [ 1, 2, 3, "abc"],
+ [ 1, 3, 2, "acb"],
+ [ 2, 1, 3, "bac"],
+ [ 2, 3, 1, "cab"],
+ [ 3, 1, 2, "bca"],
+ [ 3, 2, 1, "cba"],
+
+ // Test negative values
+ [ 1, -5, -2, "bca"],
+ [ -50, 0, -2, "acb"],
+
+ // Non-integers should be ignored.
+ // (So, they'll leave their div with the initial 'order' value, which is 0.)
+ [ 1, 1.5, 2, "bac"],
+ [ 2.5, 3.4, 1, "abc"],
+ [ 0.5, 1, 1.5, "acb"],
+
+ // Decimal values that happen to be equal to integers (e.g. "3.0") are still
+ // <numbers>, and are _not_ <integers>.
+ // Source: http://www.w3.org/TR/CSS21/syndata.html#value-def-integer
+ // (So, they'll leave their div with the initial 'order' value, which is 0.)
+ // (NOTE: We have to use quotes around "3.0" and "2.0" to be sure JS doesn't
+ // coerce them into integers before we get a chance to set them in CSS.)
+ [ "3.0", "2.0", "1.0", "abc"],
+ [ 3, "2.0", 1, "bca"],
+];
+
+// FUNCTIONS
+// ---------
+
+function initRefSnapshots() {
+ let refIds = ["abc", "acb", "bac", "bca", "cab", "cba"];
+ for (let id of refIds) {
+ let elem = document.getElementById(id);
+ elem.style.display = "block";
+ gRefSnapshots[id] = snapshotWindow(window, false);
+ elem.style.display = "";
+ }
+}
+
+function complainIfSnapshotsDiffer(aSnap1, aSnap2, aMsg) {
+ let compareResult = compareSnapshots(aSnap1, aSnap2, true);
+ ok(compareResult[0],
+ "flex container rendering should match expected (" + aMsg +")");
+ if (!compareResult[0]) {
+ todo(false, "TESTCASE: " + compareResult[1]);
+ todo(false, "REFERENCE: "+ compareResult[2]);
+ }
+}
+
+function runOrderTestcase(aOrderTestcase) {
+ // Sanity-check
+ ok(Array.isArray(aOrderTestcase), "expecting testcase to be an array");
+ is(aOrderTestcase.length, 4, "expecting testcase to have 4 elements");
+
+ document.getElementById("a").style.order = aOrderTestcase[0];
+ document.getElementById("b").style.order = aOrderTestcase[1];
+ document.getElementById("c").style.order = aOrderTestcase[2];
+
+ let snapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(snapshot, gRefSnapshots[aOrderTestcase[3]],
+ aOrderTestcase);
+
+ // Clean up
+ for (let id of ["a", "b", "c"]) {
+ document.getElementById(id).style.order = "";
+ }
+}
+
+// Main Function
+function main() {
+ initRefSnapshots();
+
+ // un-hide the flex container's parent
+ let flexContainerParent = document.getElementById("flexContainerParent");
+ flexContainerParent.style.display = "block";
+
+ // Initial sanity-check: should be in expected document order
+ let initialSnapshot = snapshotWindow(window, false);
+ complainIfSnapshotsDiffer(initialSnapshot, gRefSnapshots.abc,
+ "initial flex container rendering, " +
+ "no 'order' value yet");
+
+ // OK, now we run our tests
+ gOrderTestcases.forEach(runOrderTestcase);
+
+ // Re-hide the flex container at the end
+ flexContainerParent.style.display = "";
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flexbox_reflow_counts.html b/layout/style/test/test_flexbox_reflow_counts.html
new file mode 100644
index 0000000000..a8f4913f7d
--- /dev/null
+++ b/layout/style/test/test_flexbox_reflow_counts.html
@@ -0,0 +1,199 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1142686
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1142686</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .flex {
+ display: flex;
+ }
+ #outerFlex {
+ border: 1px solid black;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142686">Mozilla Bug 1142686</a>
+<div id="display">
+ <div id="content">
+ <div class="flex" id="outerFlex">
+ <div class="flex" id="midFlex">
+ <div id="innerBlock">
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for Bug 1142686 **/
+
+/**
+ * This test checks how many reflows are required, when we make a change inside
+ * a set of two nested flex containers, with various styles applied to
+ * the containers & innermost child. Some flex layout operations require two
+ * passes (which can cause exponential blowup). This test is intended to verify
+ * that certain configurations do *not* require two-pass layout, by comparing
+ * the reflow-count for a more-complex scenario against a less-complex scenario.
+ *
+ * We have two nested flex containers around an initially-empty block. For each
+ * measurement, we put some text in the block, and we see how many frame-reflow
+ * operations occur as a result.
+ */
+
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+// The elements:
+const gOuterFlex = document.getElementById("outerFlex");
+const gMidFlex = document.getElementById("midFlex");
+const gInnerBlock = document.getElementById("innerBlock");
+
+// This cleanup helper-function removes all children from 'parent'
+// except for 'childToPreserve' (if specified)
+function removeChildrenExcept(parent, childToPreserve)
+{
+ if (childToPreserve && childToPreserve.parentNode != parent) {
+ // This is just a sanity/integrity-check -- if this fails, it's probably a
+ // bug in this test rather than in the code. I'm not checking this via
+ // e.g. "is(childToPreserve.parentNode, parent)", because this *should*
+ // always pass, and each "pass" is not interesting here since it's a
+ // sanity-check. It's only interesting/noteworthy if it fails. So, to
+ // avoid bloating this test's passed-subtest-count & output, we only bother
+ // reporting on this in the case where something's wrong.
+ ok(false, "bug in test; 'childToPreserve' should be child of 'parent'");
+ }
+
+ // For simplicity, we just remove *all children* and then reappend
+ // childToPreserve as the sole child.
+ while (parent.firstChild) {
+ parent.removeChild(parent.firstChild);
+ }
+ if (childToPreserve) {
+ parent.appendChild(childToPreserve);
+ }
+}
+
+// Appends 'childCount' new children to 'parent'
+function addNChildren(parent, childCount)
+{
+ for (let i = 0; i < childCount; i++) {
+ let newChild = document.createElement("div");
+ // Give the new child some text so it's got a nonzero content-size:
+ newChild.append("a");
+ parent.appendChild(newChild);
+ }
+}
+
+// Undoes whatever styling customizations and DOM insertions that a given
+// testcase has done, to prepare for running the next testcase.
+function cleanup()
+{
+ gOuterFlex.style = gMidFlex.style = gInnerBlock.style = "";
+ removeChildrenExcept(gInnerBlock);
+ removeChildrenExcept(gMidFlex, gInnerBlock);
+ removeChildrenExcept(gOuterFlex, gMidFlex);
+}
+
+// Each testcase here has a label (used in test output), a function to set up
+// the testcase, and (optionally) a function to set up the reference case.
+let gTestcases = [
+ {
+ label : "border on flex items",
+ addTestStyle : function() {
+ gMidFlex.style.border = gInnerBlock.style.border = "3px solid black";
+ },
+ },
+ {
+ label : "padding on flex items",
+ addTestStyle : function() {
+ gMidFlex.style.padding = gInnerBlock.style.padding = "5px";
+ },
+ },
+ {
+ label : "margin on flex items",
+ addTestStyle : function() {
+ gMidFlex.style.margin = gInnerBlock.style.margin = "2px";
+ },
+ },
+ {
+ // When we make a change in one flex item, the number of reflows should not
+ // scale with its number of siblings (as long as those siblings' sizes
+ // aren't impacted by the change):
+ label : "additional flex items in outer flex container",
+
+ // Compare 5 bonus flex items vs. 1 bonus flex item:
+ addTestStyle : function() {
+ addNChildren(gOuterFlex, 5);
+ },
+ addReferenceStyle : function() {
+ addNChildren(gOuterFlex, 1);
+ },
+ },
+ {
+ // (As above, but now the bonus flex items are one step deeper in the tree,
+ // on the nested flex container rather than the outer one)
+ label : "additional flex items in nested flex container",
+ addTestStyle : function() {
+ addNChildren(gMidFlex, 5);
+ },
+ addReferenceStyle : function() {
+ addNChildren(gMidFlex, 1);
+ },
+ },
+];
+
+// Flush layout & return the global frame-reflow-count
+function getReflowCount()
+{
+ let unusedVal = gOuterFlex.offsetHeight; // flush layout
+ return gUtils.framesReflowed;
+}
+
+// This function adds some text inside of gInnerBlock, and returns the number
+// of frames that need to be reflowed as a result.
+function makeTweakAndCountReflows()
+{
+ let beforeCount = getReflowCount();
+ gInnerBlock.appendChild(document.createTextNode("hello"));
+ let afterCount = getReflowCount();
+
+ let numReflows = afterCount - beforeCount;
+ if (numReflows <= 0) {
+ ok(false, "something's wrong -- we should've reflowed *something*");
+ }
+ return numReflows;
+}
+
+// Given a testcase (from gTestcases), this function verifies that the
+// testcase scenario requires the same number of reflows as the reference
+// scenario.
+function runOneTest(aTestcase)
+{
+ aTestcase.addTestStyle();
+ let numTestcaseReflows = makeTweakAndCountReflows();
+ cleanup();
+
+ if (aTestcase.addReferenceStyle) {
+ aTestcase.addReferenceStyle();
+ }
+ let numReferenceReflows = makeTweakAndCountReflows();
+ cleanup();
+
+ is(numTestcaseReflows, numReferenceReflows,
+ "Testcase & reference case should require same number of reflows" +
+ " (testcase label: '" + aTestcase.label + "')");
+}
+
+gTestcases.forEach(runOneTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_flushing_frame.html b/layout/style/test/test_flushing_frame.html
new file mode 100644
index 0000000000..fa6d751647
--- /dev/null
+++ b/layout/style/test/test_flushing_frame.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1545516: We don't flush layout unnecessarily on the parent
+ document when the frame is already disconnected.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div id="content"></div>
+<script>
+ SimpleTest.waitForExplicitFinish();
+ const iframe = document.createElement("iframe");
+ const content = document.querySelector("#content");
+ const parentUtils = SpecialPowers.getDOMWindowUtils(window);
+ iframe.onload = function() {
+ const win = iframe.contentWindow;
+ iframe.offsetTop; // flush layout
+ content.style.display = "inline"; // Dirty style with something that will reframe.
+
+ const previousConstructCount = parentUtils.framesConstructed;
+ let pagehideRan = false;
+ win.addEventListener("pagehide", function() {
+ pagehideRan = true;
+ win.foo = win.innerWidth;
+ is(parentUtils.framesConstructed, previousConstructCount, "innerWidth shouldn't have flushed parent document layout")
+ win.bar = win.document.documentElement.offsetHeight;
+ is(parentUtils.framesConstructed, previousConstructCount, "offsetHeight shouldn't have flushed parent document layout")
+ win.baz = win.getComputedStyle(win.document.documentElement).color;
+ is(parentUtils.framesConstructed, previousConstructCount, "getComputedStyle shouldn't have flushed parent document layout")
+ });
+
+ iframe.remove(); // Remove the iframe
+ is(pagehideRan, true, "pagehide handler should've ran");
+ is(parentUtils.framesConstructed, previousConstructCount, "Nothing should've flushed the parent document layout yet");
+ content.offsetTop;
+ isnot(parentUtils.framesConstructed, previousConstructCount, "We should've flushed layout now");
+ SimpleTest.finish();
+ };
+ document.body.appendChild(iframe);
+</script>
diff --git a/layout/style/test/test_font_face_cascade.html b/layout/style/test/test_font_face_cascade.html
new file mode 100644
index 0000000000..0d98f9d606
--- /dev/null
+++ b/layout/style/test/test_font_face_cascade.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<title>Test that @font-face rules from different origins cascade correctly</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<script>
+let io = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+
+let utils = SpecialPowers.getDOMWindowUtils(window);
+
+function load_sheet(sheet_text, level) {
+ if (level != "AGENT_SHEET" && level != "USER_SHEET" && level != "AUTHOR_SHEET") {
+ throw "unknown level";
+ }
+
+ let uri = io.newURI("data:text/css," + encodeURI(sheet_text));
+ utils.loadSheet(uri, utils[level]);
+}
+
+load_sheet(
+ "@font-face { font-family: TestAgent; src: url(about:invalid); }",
+ "AGENT_SHEET");
+
+load_sheet(
+ "@font-face { font-family: TestAuthor; src: url(about:invalid); }",
+ "AUTHOR_SHEET");
+
+load_sheet(
+ "@font-face { font-family: TestUser; src: url(about:invalid); }",
+ "USER_SHEET");
+
+is([...document.fonts].map(f => f.family).join(" "),
+ 'TestAuthor',
+ "document.fonts only contains author @font-face rules");
+</script>
diff --git a/layout/style/test/test_font_face_parser.html b/layout/style/test/test_font_face_parser.html
new file mode 100644
index 0000000000..448db4ff14
--- /dev/null
+++ b/layout/style/test/test_font_face_parser.html
@@ -0,0 +1,386 @@
+<!DOCTYPE HTML><html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=441469 -->
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test of @font-face parser</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<p>@font-face parsing (<a
+ target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441469"
+>bug 441469</a>)</p>
+<pre id="display"></pre>
+<style type="text/css" id="testbox"></style>
+<script class="testbody" type="text/javascript">
+function runTest() {
+ function _(b) { return "@font-face { " + b + " }"; };
+ var testset = [
+ // Complete nonsense - shouldn't make a font-face rule at all.
+ { rule: "@font-face;" },
+ { rule: "font-face { }" },
+ { rule: "@fontface { }" },
+ { rule: "@namespace foo url(http://example.com/foo);" },
+
+ // Empty rule.
+ { rule: "@font-face { }", d: {} },
+ { rule: "@font-face {", d: {} },
+ { rule: "@font-face { ; }", d: {}, noncanonical: true },
+
+ // Correct font-family.
+ { rule: _("font-family: \"Mouse\";"), d: {"font-family" : "\"Mouse\""} },
+ { rule: _("font-family: \"Mouse\""), d: {"font-family" : "\"Mouse\""},
+ noncanonical: true },
+ { rule: _("font-family: Mouse;"), d: {"font-family" : "Mouse" },
+ noncanonical: true },
+ { rule: _("font-family: Mouse"), d: {"font-family" : "Mouse" },
+ noncanonical: true },
+
+ // Correct but unusual font-family.
+ { rule: _("font-family: Hoefler Text;"),
+ d: {"font-family" : "Hoefler Text"},
+ noncanonical: true },
+
+ // Incorrect font-family.
+ { rule: _("font-family:"), d: {} },
+ { rule: _("font-family \"Mouse\""), d: {} },
+ { rule: _("font-family: *"), d: {} },
+ { rule: _("font-family: Mouse, Rat"), d: {} },
+ { rule: _("font-family: sans-serif"), d: {} },
+
+ // Correct font-style.
+ { rule: _("font-style: normal;"), d: {"font-style" : "normal"} },
+ { rule: _("font-style: italic;"), d: {"font-style" : "italic"} },
+ { rule: _("font-style: oblique;"), d: {"font-style" : "oblique"} },
+
+ // Correct font-weight.
+ { rule: _("font-weight: 100;"), d: {"font-weight" : "100"} },
+ { rule: _("font-weight: 200;"), d: {"font-weight" : "200"} },
+ { rule: _("font-weight: 300;"), d: {"font-weight" : "300"} },
+ { rule: _("font-weight: 400;"), d: {"font-weight" : "400"} },
+ { rule: _("font-weight: 500;"), d: {"font-weight" : "500"} },
+ { rule: _("font-weight: 600;"), d: {"font-weight" : "600"} },
+ { rule: _("font-weight: 700;"), d: {"font-weight" : "700"} },
+ { rule: _("font-weight: 800;"), d: {"font-weight" : "800"} },
+ { rule: _("font-weight: 900;"), d: {"font-weight" : "900"} },
+ { rule: _("font-weight: normal;"), d: {"font-weight" : "normal"} },
+ { rule: _("font-weight: bold;"), d: {"font-weight" : "bold"} },
+
+ // Incorrect font-weight.
+ { rule: _("font-weight: bolder;"), d: {} },
+ { rule: _("font-weight: lighter;"), d: {} },
+
+ // Correct font-stretch.
+ { rule: _("font-stretch: ultra-condensed;"),
+ d: {"font-stretch" : "ultra-condensed"} },
+ { rule: _("font-stretch: extra-condensed;"),
+ d: {"font-stretch" : "extra-condensed"} },
+ { rule: _("font-stretch: condensed;"),
+ d: {"font-stretch" : "condensed"} },
+ { rule: _("font-stretch: semi-condensed;"),
+ d: {"font-stretch" : "semi-condensed"} },
+ { rule: _("font-stretch: normal;"),
+ d: {"font-stretch" : "normal"} },
+ { rule: _("font-stretch: semi-expanded;"),
+ d: {"font-stretch" : "semi-expanded"} },
+ { rule: _("font-stretch: expanded;"),
+ d: {"font-stretch" : "expanded"} },
+ { rule: _("font-stretch: extra-expanded;"),
+ d: {"font-stretch" : "extra-expanded"} },
+ { rule: _("font-stretch: ultra-expanded;"),
+ d: {"font-stretch" : "ultra-expanded"} },
+
+ // Incorrect font-stretch.
+ { rule: _("font-stretch: wider;"), d: {} },
+ { rule: _("font-stretch: narrower;"), d: {} },
+
+ // Correct src:
+ { rule: _("src: url(\"/fonts/Mouse\");"),
+ d: { "src" : "url(\"/fonts/Mouse\")" } },
+ { rule: _("src: url(/fonts/Mouse);"),
+ d: { "src" : "url(\"/fonts/Mouse\")" }, noncanonical: true },
+
+ { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\");"),
+ d: { "src" : "url(\"/fonts/Mouse\") format(\"truetype\")" } },
+
+ { rule: _("src: url(\"/fonts/Mouse\"), url(\"/fonts/Rat\");"),
+ d: { "src" : "url(\"/fonts/Mouse\"), url(\"/fonts/Rat\")" } },
+
+ { rule: _("src: local(Mouse), url(\"/fonts/Mouse\");"),
+ d: { "src" : "local(Mouse), url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+
+ { rule: _("src: local(\"老鼠\"), url(\"/fonts/Mouse\");"),
+ d: { "src" : "local(\"老鼠\"), url(\"/fonts/Mouse\")" } },
+
+ { rule: _("src: local(\"老鼠\"), url(\"/fonts/Mouse\") format(\"truetype\");"),
+ d: { "src" : "local(\"老鼠\"), url(\"/fonts/Mouse\") format(\"truetype\")" } },
+
+ { rule: _("src: url(\"/fonts/Mouse\") format(truetype);"),
+ d: { "src" : "url(\"/fonts/Mouse\") format(truetype)" } },
+
+ // Correct but unusual src:
+ { rule: _("src: local(Hoefler Text);"),
+ d: {"src" : "local(Hoefler Text)"}, noncanonical: true },
+
+ // Incorrect src:
+ { rule: _("src:"), d: {} },
+ { rule: _("src: \"/fonts/Mouse\";"), d: {} },
+ { rule: _("src: /fonts/Mouse;"), d: {} },
+ { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\",opentype);"), d: {} },
+ { rule: _("src: local(*);"), d: {} },
+ { rule: _("src: format(\"truetype\");"), d: {} },
+ { rule: _("src: local(Mouse) format(\"truetype\");"), d: {} },
+ { rule: _("src: local(Mouse, Rat);"), d: {} },
+ { rule: _("src: local(sans-serif);"), d: {} },
+ { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\", \"opentype\");"), d: {} },
+
+ // Repeated descriptors
+ { rule: _("font-weight: 700; font-weight: 200;"),
+ d: {"font-weight" : "200"},
+ noncanonical: true },
+ { rule: _("src: url(\"/fonts/Cat\"); src: url(\"/fonts/Mouse\");"),
+ d: { "src" : "url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+ { rule: _("src: local(Cat); src: local(Mouse)"),
+ d: { "src" : "local(Mouse)" },
+ noncanonical: true },
+
+ // Correct parenthesis matching for local()
+ { rule: _("src: local(Mouse); src: local(Cat(); src: local(Rat); )"),
+ d: { "src" : "local(Mouse)" },
+ noncanonical: true },
+ { rule: _("src: local(Mouse); src: local(\"Cat\"; src: local(Rat); )"),
+ d: { "src" : "local(Mouse)" },
+ noncanonical: true },
+
+ // Correct parenthesis matching for format()
+ { rule: _("src: url(\"/fonts/Mouse\"); " +
+ "src: url(\"/fonts/Cat\") format(Cat(); src: local(Rat); )"),
+ d: { "src" : "url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+ { rule: _("src: url(\"/fonts/Mouse\"); " +
+ "src: url(\"/fonts/Cat\") format(\"Cat\"; src: local(Rat); )"),
+ d: { "src" : "url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+ { rule: _("src: url(\"/fonts/Mouse\"); " +
+ "src: url(\"/fonts/Cat\") format((); src: local(Rat); )"),
+ d: { "src" : "url(\"/fonts/Mouse\")" },
+ noncanonical: true },
+
+ // Correct unicode-range:
+ { rule: _("unicode-range: U+A5;"), d: { "unicode-range" : "U+A5" } },
+ { rule: _("unicode-range: U+00A5;"),
+ d: { "unicode-range" : "U+A5" }, noncanonical: true },
+ { rule: _("unicode-range: U+00a5;"),
+ d: { "unicode-range" : "U+A5" }, noncanonical: true },
+ { rule: _("unicode-range: u+00a5;"),
+ d: { "unicode-range" : "U+A5" }, noncanonical: true },
+ { rule: _("unicode-range: U+0-FF;"),
+ d: { "unicode-range" : "U+0-FF" } },
+ { rule: _("unicode-range: U+00??;"),
+ d: { "unicode-range" : "U+0-FF" }, noncanonical: true },
+ { rule: _("unicode-range: U+?"),
+ d: { "unicode-range" : "U+0-F" }, noncanonical: true },
+ { rule: _("unicode-range: U+590-5ff;"),
+ d: { "unicode-range" : "U+590-5FF" }, noncanonical: true },
+
+ { rule: _("unicode-range: U+A5, U+4E00-9FFF, U+30??, U+FF00-FF9F;"),
+ d: { "unicode-range" : "U+A5, U+4E00-9FFF, U+3000-30FF, U+FF00-FF9F" },
+ noncanonical: true },
+
+ { rule: _("unicode-range: U+104??;"),
+ d: { "unicode-range" : "U+10400-104FF" }, noncanonical: true },
+ { rule: _("unicode-range: U+320??, U+321??, U+322??, U+323??, U+324??, U+325??;"),
+ d: { "unicode-range" : "U+32000-320FF, U+32100-321FF, U+32200-322FF, U+32300-323FF, U+32400-324FF, U+32500-325FF" },
+ noncanonical: true },
+ { rule: _("unicode-range: U+100000-10ABCD;"),
+ d: { "unicode-range" : "U+100000-10ABCD" } },
+ { rule: _("unicode-range: U+0121 , U+1023"),
+ d: { "unicode-range" : "U+121, U+1023" }, noncanonical: true },
+ { rule: _("unicode-range: U+0121/**/, U+1023"),
+ d: { "unicode-range" : "U+121, U+1023" }, noncanonical: true },
+
+ // Incorrect unicode-range:
+ { rule: _("unicode-range:"), d: {} },
+ { rule: _("unicode-range: U+"), d: {} },
+ { rule: _("unicode-range: U+8FFFFFFF"), d: {} },
+ { rule: _("unicode-range: U+8FFF-7000"), d: {} },
+ { rule: _("unicode-range: U+8F??-9000"), d: {} },
+ { rule: _("unicode-range: U+9000-9???"), d: {} },
+ { rule: _("unicode-range: U+??00"), d: {} },
+ { rule: _("unicode-range: U+12345678?"), d: {} },
+ { rule: _("unicode-range: U+1????????"), d: {} },
+ { rule: _("unicode-range: twelve"), d: {} },
+ { rule: _("unicode-range: 1000"), d: {} },
+ { rule: _("unicode-range: 13??"), d: {} },
+ { rule: _("unicode-range: 1300-1377"), d: {} },
+ { rule: _("unicode-range: U-1000"), d: {} },
+ { rule: _("unicode-range: U+nnnn"), d: {} },
+ { rule: _("unicode-range: U+0121 U+1023"), d: {} },
+ { rule: _("unicode-range: U+ 0121"), d: {} },
+ { rule: _("unicode-range: U +0121"), d: {} },
+ { rule: _("unicode-range: U+0121-"), d: {} },
+ { rule: _("unicode-range: U+0121- 1023"), d: {} },
+ { rule: _("unicode-range: U+0121 -1023"), d: {} },
+ { rule: _("unicode-range: U+012 ?"), d: {} },
+ { rule: _("unicode-range: U+01 2?"), d: {} },
+ { rule: _("unicode-range: U+A0000-12FFFF"), d: {} },
+ { rule: _("unicode-range: U+??????"), d: {} },
+
+ // Thorough test of seven-digit rejection: all these are syntax errors
+ { rule: _("unicode-range: U+1034560, U+A5"), d: {} },
+ { rule: _("unicode-range: U+1034569, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456a, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456f, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456?, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456-1034560, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456-1034569, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456-103456a, U+A5"), d: {} },
+ { rule: _("unicode-range: U+103456-103456f, U+A5"), d: {} },
+
+ // Syntactically invalid unicode-range tokens invalidate the
+ // entire descriptor
+ { rule: _("unicode-range: U+1, U+2, U+X"), d: {} },
+ { rule: _("unicode-range: U+A5, U+0?F"), d: {} },
+ { rule: _("unicode-range: U+A5, U+0F?-E00"), d: {} },
+
+ // Descending ranges and ranges outside 0-10FFFF are invalid
+ { rule: _("unicode-range: U+A5, U+90-30"), d: {} },
+ { rule: _("unicode-range: U+A5, U+220043"), d: {} },
+
+ // font-feature-settings
+ { rule: _("font-feature-settings: normal;"),
+ d: { "font-feature-settings" : "normal" } },
+ { rule: _("font-feature-settings: \"dlig\";"),
+ d: { "font-feature-settings" : "\"dlig\"" } },
+ { rule: _("font-feature-settings: \"dlig\" 1;"),
+ d: { "font-feature-settings" : "\"dlig\"" }, noncanonical: true },
+ { rule: _("font-feature-settings: 'dlig' 1"),
+ d: { "font-feature-settings" : "\"dlig\"" }, noncanonical: true },
+
+ // incorrect font-feature-settings
+ { rule: _("font-feature-settings: dlig 1"), d: {} },
+ { rule: _("font-feature-settings: none;"), d: {} },
+ { rule: _("font-feature-settings: 0;"), d: {} },
+ { rule: _("font-feature-settings: 3.14;"), d: {} },
+ { rule: _("font-feature-settings: 'blah' 3.14;"), d: {} },
+ { rule: _("font-feature-settings: 'dlig' 1 'hist' 0;"), d: {} },
+ { rule: _("font-feature-settings: 'dlig=1,hist=1'"), d: {} },
+
+ // font-language-override:
+ { rule: _("font-language-override: normal;"),
+ d: { "font-language-override" : "normal" } },
+ { rule: _("font-language-override: \"TRK\";"),
+ d: { "font-language-override" : "\"TRK\"" } },
+ { rule: _("font-language-override: 'TRK'"),
+ d: { "font-language-override" : "\"TRK\"" }, noncanonical: true },
+
+ // incorrect font-language-override
+ { rule: _("font-language-override: TRK"), d: {} },
+ { rule: _("font-language-override: none;"), d: {} },
+ { rule: _("font-language-override: 0;"), d: {} },
+ { rule: _("font-language-override: #999;"), d: {} },
+ { rule: _("font-language-override: 'TRK' 'SRB'"), d: {} },
+ { rule: _("font-language-override: 'TRK', 'SRB'"), d: {} },
+
+ // font-display:
+ { rule: _("font-display: auto;"),
+ d: { "font-display" : "auto" } },
+ { rule: _("font-display: block;"),
+ d: { "font-display" : "block" } },
+ { rule: _("font-display: swap;"),
+ d: { "font-display" : "swap" } },
+ { rule: _("font-display: fallback;"),
+ d: { "font-display" : "fallback" } },
+ { rule: _("font-display: optional;"),
+ d: { "font-display" : "optional" } },
+
+ // incorrect font-display
+ { rule: _("font-display: hidden"), d: {} },
+ { rule: _("font-display: swap 3"), d: {} },
+ { rule: _("font-display: block 2 swap 0"), d: {} },
+ { rule: _("font-display: all"), d: {} },
+ ];
+
+ var display = document.getElementById("display");
+ var sheet = document.styleSheets[1];
+
+ for (var curTest = 0; curTest < testset.length; curTest++) {
+ try {
+ while(sheet.cssRules.length > 0)
+ sheet.deleteRule(0);
+ sheet.insertRule(testset[curTest].rule, 0);
+ } catch (e) {
+ ok(
+ e.name == "SyntaxError"
+ && e instanceof DOMException
+ && e.code == DOMException.SYNTAX_ERR
+ && !('d' in testset[curTest]),
+ testset[curTest].rule + " syntax error thrown - " + e
+ );
+ }
+
+ try {
+ if (testset[curTest].d) {
+ is(sheet.cssRules.length, 1,
+ testset[curTest].rule + " rule count");
+ is(sheet.cssRules[0].type, 5 /*FONT_FACE_RULE*/,
+ testset[curTest].rule + " rule type");
+
+ var d = testset[curTest].d;
+ var s = sheet.cssRules[0].style;
+ var n = 0;
+
+ is(s.getPropertyValue("pointless"), "", "Unknown descriptors don't assert");
+
+ // everything is set that should be
+ for (var name in d) {
+ is(s.getPropertyValue(name), d[name],
+ testset[curTest].rule + " (prop " + name + ")");
+ n++;
+ }
+ // nothing else is set
+ is(s.length, n, testset[curTest].rule + "prop count");
+ for (var i = 0; i < s.length; i++) {
+ ok(
+ s[i] in d,
+ testset[curTest].rule +
+ " - Unexpected item #" + i + ": " + s[i]
+ );
+ }
+
+ // round-tripping of cssText
+ // this is a strong test; it's okay if the exact serialization
+ // changes in the future
+ if (n && !testset[curTest].noncanonical) {
+ is(sheet.cssRules[0].cssText.replace(/[ \n]+/g, " "),
+ testset[curTest].rule,
+ testset[curTest].rule + " rule text");
+ }
+ } else {
+ if (sheet.cssRules.length == 0) {
+ is(sheet.cssRules.length, 0,
+ testset[curTest].rule + " rule count (0)");
+ } else {
+ is(sheet.cssRules.length, 1,
+ testset[curTest].rule + " rule count (1 non-fontface)");
+ isnot(sheet.cssRules[0].type, 5 /*FONT_FACE_RULE*/,
+ testset[curTest].rule + " rule type (1 non-fontface)");
+ }
+ }
+ } catch (e) {
+ ok(false, testset[curTest].rule + " - During test: " + e);
+ }
+ }
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_font_family_parsing.html b/layout/style/test/test_font_family_parsing.html
new file mode 100644
index 0000000000..59bedc0b94
--- /dev/null
+++ b/layout/style/test/test_font_family_parsing.html
@@ -0,0 +1,272 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Font family name parsing tests</title>
+ <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com">
+ <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-family-prop" />
+ <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-prop" />
+ <meta name="assert" content="tests that valid font family names parse and invalid ones don't" />
+ <script type="text/javascript" src="/resources/testharness.js"></script>
+ <script type="text/javascript" src="/resources/testharnessreport.js"></script>
+ <style type="text/css">
+ </style>
+</head>
+<body>
+<div id="log"></div>
+<pre id="display"></pre>
+<style type="text/css" id="testbox"></style>
+
+<script type="text/javascript">
+
+function fontProp(n, size, s1, s2) { return (s1 ? s1 + " " : "") + (s2 ? s2 + " " : "") + size + " " + n; }
+function font(n, size, s1, s2) { return "font: " + fontProp(n, size, s1, s2); }
+
+// testrules
+// namelist - font family list
+// invalid - true if declarations won't parse in either font-family or font
+// fontonly - only test with the 'font' property
+// single - namelist includes only a single name (@font-face rules only allow a single name)
+
+var testFontFamilyLists = [
+
+ /* basic syntax */
+ { namelist: "simple", single: true },
+ { namelist: "'simple'", single: true },
+ { namelist: '"simple"', single: true },
+ { namelist: "-simple", single: true },
+ { namelist: "_simple", single: true },
+ { namelist: "quite simple", single: true },
+ { namelist: "quite _simple", single: true },
+ { namelist: "quite -simple", single: true },
+ { namelist: "0simple", invalid: true, single: true },
+ { namelist: "simple!", invalid: true, single: true },
+ { namelist: "simple()", invalid: true, single: true },
+ { namelist: "quite@simple", invalid: true, single: true },
+ { namelist: "#simple", invalid: true, single: true },
+ { namelist: "quite 0simple", invalid: true, single: true },
+ { namelist: "納豆嫌い", single: true },
+ { namelist: "納豆嫌い, ick, patooey" },
+ { namelist: "ick, patooey, 納豆嫌い" },
+ { namelist: "納豆嫌い, 納豆大嫌い" },
+ { namelist: "納豆嫌い, 納豆大嫌い, 納豆本当に嫌い" },
+ { namelist: "納豆嫌い, 納豆大嫌い, 納豆本当に嫌い, 納豆は好みではない" },
+ { namelist: "arial, helvetica, sans-serif" },
+ { namelist: "arial, helvetica, 'times' new roman, sans-serif", invalid: true },
+ { namelist: "arial, helvetica, \"times\" new roman, sans-serif", invalid: true },
+
+ { namelist: "arial, helvetica, \"\\\"times new roman\", sans-serif" },
+ { namelist: "arial, helvetica, '\\\"times new roman', sans-serif" },
+ { namelist: "arial, helvetica, times 'new' roman, sans-serif", invalid: true },
+ { namelist: "arial, helvetica, times \"new\" roman, sans-serif", invalid: true },
+ { namelist: "\"simple", single: true },
+ { namelist: "\\\"simple", single: true },
+ { namelist: "\"\\\"simple\"", single: true },
+ { namelist: "İsimple", single: true },
+ { namelist: "ßsimple", single: true },
+ { namelist: "ẙsimple", single: true },
+
+ /* escapes */
+ { namelist: "\\s imple", single: true },
+ { namelist: "\\073 imple", single: true },
+
+ { namelist: "\\035 simple", single: true },
+ { namelist: "sim\\035 ple", single: true },
+ { namelist: "simple\\02cinitial", single: true },
+ { namelist: "simple, \\02cinitial" },
+ { namelist: "sim\\020 \\035 ple", single: true },
+ { namelist: "sim\\020 5ple", single: true },
+ { namelist: "\\@simple", single: true },
+ { namelist: "\\@simple\\;", single: true },
+ { namelist: "\\@font-face", single: true },
+ { namelist: "\\@font-face\\;", single: true },
+ { namelist: "\\031 \\036 px", single: true },
+ { namelist: "\\031 \\036 px", single: true },
+ { namelist: "\\1f4a9", single: true },
+ { namelist: "\\01f4a9", single: true },
+ { namelist: "\\0001f4a9", single: true },
+ { namelist: "\\AbAb", single: true },
+
+ /* keywords */
+ { namelist: "italic", single: true },
+ { namelist: "bold", single: true },
+ { namelist: "bold italic", single: true },
+ { namelist: "italic bold", single: true },
+ { namelist: "larger", single: true },
+ { namelist: "smaller", single: true },
+ { namelist: "bolder", single: true },
+ { namelist: "lighter", single: true },
+ { namelist: "default", invalid: true, fontonly: true, single: true },
+ { namelist: "initial", invalid: true, fontonly: true, single: true },
+ { namelist: "inherit", invalid: true, fontonly: true, single: true },
+ { namelist: "normal", single: true },
+ { namelist: "default, simple", invalid: true },
+ { namelist: "initial, simple", invalid: true },
+ { namelist: "inherit, simple", invalid: true },
+ { namelist: "normal, simple" },
+ { namelist: "simple, default", invalid: true },
+ { namelist: "simple, initial", invalid: true },
+ { namelist: "simple, inherit", invalid: true },
+ { namelist: "simple, default bongo" },
+ { namelist: "simple, initial bongo" },
+ { namelist: "simple, inherit bongo" },
+ { namelist: "simple, bongo default" },
+ { namelist: "simple, bongo initial" },
+ { namelist: "simple, bongo inherit" },
+ { namelist: "simple, normal" },
+ { namelist: "simple default", single: true },
+ { namelist: "simple initial", single: true },
+ { namelist: "simple inherit", single: true },
+ { namelist: "simple normal", single: true },
+ { namelist: "default simple", single: true },
+ { namelist: "initial simple", single: true },
+ { namelist: "inherit simple", single: true },
+ { namelist: "normal simple", single: true },
+ { namelist: "caption", single: true }, // these are keywords for the 'font' property but only when in the first position
+ { namelist: "icon", single: true },
+ { namelist: "menu", single: true },
+ { namelist: "unset", invalid: true, fontonly: true, single: true },
+ { namelist: "unset, simple", invalid: true },
+ { namelist: "simple, unset", invalid: true },
+ { namelist: "simple, unset bongo" },
+ { namelist: "simple, bongo unset" },
+ { namelist: "simple unset", single: true },
+ { namelist: "unset simple", single: true }
+
+];
+
+var gTest = 0;
+
+/* strip out just values */
+function extractDecl(rule)
+{
+ var t = rule.replace(/[ \n]+/g, " ");
+ t = t.replace(/.*{[ \n]*/, "");
+ t = t.replace(/[ \n]*}.*/, "");
+ return t;
+}
+
+
+function testStyleRuleParse(decl, invalid) {
+ var sheet = document.styleSheets[1];
+ var rule = ".test" + gTest++ + " { " + decl + "; }";
+
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+
+ // shouldn't throw but improper handling of punctuation may cause some parsers to throw
+ try {
+ sheet.insertRule(rule, 0);
+ } catch (e) {
+ assert_unreached("unexpected error with " + decl + " ==> " + e.name);
+ }
+
+ assert_equals(sheet.cssRules.length, 1,
+ "strange number of rules (" + sheet.cssRules.length + ") with " + decl);
+
+ var s = extractDecl(sheet.cssRules[0].cssText);
+
+ if (invalid) {
+ assert_equals(s, "", "rule declaration shouldn't parse - " + rule + " ==> " + s);
+ } else {
+ assert_not_equals(s, "", "rule declaration should parse - " + rule);
+
+ // check that the serialization also parses
+ var r = ".test" + gTest++ + " { " + s + " }";
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+ try {
+ sheet.insertRule(r, 0);
+ } catch (e) {
+ assert_unreached("exception occurred parsing serialized form of rule - " + rule + " ==> " + r + " " + e.name);
+ }
+ var s2 = extractDecl(sheet.cssRules[0].cssText);
+ assert_not_equals(s2, "", "serialized form of rule should also parse - " + rule + " ==> " + r);
+ }
+}
+
+var kDefaultFamilySetting = "onelittlepiggywenttomarket";
+
+function testFontFamilySetterParse(namelist, invalid) {
+ var el = document.getElementById("display");
+
+ el.style.fontFamily = kDefaultFamilySetting;
+ var def = el.style.fontFamily;
+ el.style.fontFamily = namelist;
+ if (!invalid) {
+ assert_not_equals(el.style.fontFamily, def, "fontFamily setter should parse - " + namelist);
+ var parsed = el.style.fontFamily;
+ el.style.fontFamily = kDefaultFamilySetting;
+ el.style.fontFamily = parsed;
+ assert_equals(el.style.fontFamily, parsed, "fontFamily setter should parse serialized form to identical serialization - " + parsed + " ==> " + el.style.fontFamily);
+ } else {
+ assert_equals(el.style.fontFamily, def, "fontFamily setter shouldn't parse - " + namelist);
+ }
+}
+
+var kDefaultFontSetting = "16px onelittlepiggywenttomarket";
+
+function testFontSetterParse(n, size, s1, s2, invalid) {
+ var el = document.getElementById("display");
+
+ el.style.font = kDefaultFontSetting;
+ var def = el.style.font;
+ var fp = fontProp(n, size, s1, s2);
+ el.style.font = fp;
+ if (!invalid) {
+ assert_not_equals(el.style.font, def, "font setter should parse - " + fp);
+ var parsed = el.style.font;
+ el.style.font = kDefaultFontSetting;
+ el.style.font = parsed;
+ assert_equals(el.style.font, parsed, "font setter should parse serialized form to identical serialization - " + parsed + " ==> " + el.style.font);
+ } else {
+ assert_equals(el.style.font, def, "font setter shouldn't parse - " + fp);
+ }
+}
+
+var testFontVariations = [
+ { size: "16px" },
+ { size: "900px" },
+ { size: "900em" },
+ { size: "35%" },
+ { size: "7832.3%" },
+ { size: "xx-large" },
+ { size: "larger", s1: "lighter" },
+ { size: "16px", s1: "italic" },
+ { size: "16px", s1: "italic", s2: "bold" },
+ { size: "smaller", s1: "normal" },
+ { size: "16px", s1: "normal", s2: "normal" },
+ { size: "16px", s1: "400", s2: "normal" },
+ { size: "16px", s1: "bolder", s2: "oblique" }
+];
+
+function testFamilyNameParsing() {
+ var i;
+ for (i = 0; i < testFontFamilyLists.length; i++) {
+ var tst = testFontFamilyLists[i];
+ var n = tst.namelist;
+ var t;
+
+ if (!tst.fontonly) {
+ t = "font-family: " + n;
+ test(function() { testStyleRuleParse(t, tst.invalid); }, t);
+ test(function() { testFontFamilySetterParse(n, tst.invalid); }, t + " (setter)");
+ }
+
+ var v;
+ for (v = 0; v < testFontVariations.length; v++) {
+ var f = testFontVariations[v];
+ t = font(n, f.size, f.s1, f.s2);
+ test(function() { testStyleRuleParse(t, tst.invalid); }, t);
+ test(function() { testFontSetterParse(n, f.size, f.s1, f.s2, tst.invalid); }, t + " (setter)");
+ }
+ }
+}
+
+testFamilyNameParsing();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_font_family_serialization.html b/layout/style/test/test_font_family_serialization.html
new file mode 100644
index 0000000000..ad922158f5
--- /dev/null
+++ b/layout/style/test/test_font_family_serialization.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="display"></div>
+<script>
+// This cannot be a web-platform test because this doesn't match what
+// the spec says at the moment. Specifically, the spec wants to have
+// all font family serialized to string, while in practice, all browsers
+// serialize simple them to identifiers in some cases.
+// We want to check our current behavior. This can be changed once
+// browsers have an agreement on the exact behavior to spec.
+
+// format: [input, expected serialization]
+const tests = [
+ // Basic cases
+ ['simple', 'simple'],
+ [' simple ', 'simple'],
+ ['multi idents font', 'multi idents font'],
+ [' multi idents font ', 'multi idents font'],
+ ['"wrapped name"', '"wrapped name"'],
+ ['" wrapped name "', '" wrapped name "'],
+
+ // Special whitespaces
+ ['\\ leading ws', '" leading ws"'],
+ [' \\ leading ws', '" leading ws"'],
+ ['\\ \\ leading ws', '" leading ws"'],
+ [' \\ \\ leading ws', '" leading ws"'],
+ ['\\ \\ \\ leading ws', '" leading ws"'],
+ ['trailing ws\\ ', '"trailing ws "'],
+ ['trailing ws\\ ', '"trailing ws "'],
+ ['trailing ws \\ ', '"trailing ws "'],
+ ['trailing ws\\ \\ ', '"trailing ws "'],
+ ['escaped\\ ws', '"escaped ws"'],
+ ['escaped\\ ws', '"escaped ws"'],
+ ['escaped\\ \\ ws', '"escaped ws"'],
+ ['escaped \\ ws', '"escaped ws"'],
+ ['escaped \\ ws', '"escaped ws"'],
+ ['escaped number\\ 5', '"escaped number 5"'],
+];
+
+let el = document.getElementById("display");
+for (let [input, expected] of tests) {
+ test(function() {
+ el.style.fontFamily = input;
+ assert_equals(el.style.fontFamily, expected);
+ }, "Reserialization for " + JSON.stringify(input));
+}
+</script>
diff --git a/layout/style/test/test_font_loading_api.html b/layout/style/test/test_font_loading_api.html
new file mode 100644
index 0000000000..7b2a0d9e1b
--- /dev/null
+++ b/layout/style/test/test_font_loading_api.html
@@ -0,0 +1,1895 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for the CSS Font Loading API</title>
+<script src=/tests/SimpleTest/SimpleTest.js></script>
+<link rel=stylesheet type=text/css href=/tests/SimpleTest/test.css>
+
+<script src=descriptor_database.js></script>
+
+<body onload="runTest()">
+<iframe id=v src="file_font_loading_api_vframe.html"></iframe>
+<iframe id=n style="display: none"></iframe>
+
+<script>
+// Map of FontFace descriptor attribute names to @font-face rule descriptor
+// names.
+var descriptorNames = {
+ style: "font-style",
+ weight: "font-weight",
+ stretch: "font-stretch",
+ unicodeRange: "unicode-range",
+ variant: "font-variant",
+ featureSettings: "font-feature-settings",
+ display: "font-display"
+};
+
+// Default values for the FontFace descriptor attributes other than family, as
+// Gecko currently serializes them.
+var defaultValues = {
+ style: "normal",
+ weight: "normal",
+ stretch: "normal",
+ unicodeRange: "U+0-10FFFF",
+ variant: "normal",
+ featureSettings: "normal",
+ display: "auto"
+};
+
+// Non-default values for the FontFace descriptor attributes other than family
+// along with how Gecko currently serializes them. Each value is chosen to be
+// different from the default value and also has a different serialized form.
+var nonDefaultValues = {
+ style: ["Italic", "italic"],
+ weight: ["Bold", "bold"],
+ stretch: ["Ultra-Condensed", "ultra-condensed"],
+ unicodeRange: ["U+3??", "U+300-3FF"],
+ variant: ["Small-Caps", "small-caps"],
+ featureSettings: ["'dlig' on", "\"dlig\""],
+ display: ["Block", "block"]
+};
+
+// Invalid values for the FontFace descriptor attributes other than family.
+var invalidValues = {
+ style: "initial",
+ weight: "bolder",
+ stretch: "wider",
+ unicodeRange: "U+1????-2????",
+ variant: "inherit",
+ featureSettings: "dlig",
+ display: "normal"
+};
+
+// Invalid font family names.
+var invalidFontFamilyNames = [
+ "", "sans-serif", "A, B", "inherit", "a 1"
+];
+
+// Font family list where at least one is likely to be available on
+// platforms we care about.
+var likelyPlatformFonts = "Helvetica Neue, Bitstream Vera Sans, Bitstream Vera Sans Roman, FreeSans, Free Sans, SwissA, DejaVu Sans, Arial";
+
+// Will hold an ArrayBuffer containing a valid font.
+var fontData;
+
+var queue = Promise.resolve();
+
+function is_resolved_with(aPromise, aExpectedValue, aDescription, aTestID) {
+ // This assumes that all Promise tasks come from the task source.
+ var handled = false;
+ return new Promise(function(aResolve, aReject) {
+ aPromise.then(function(aValue) {
+ if (!handled) {
+ handled = true;
+ is(aValue, aExpectedValue, aDescription + " should be resolved with the expected value " + aTestID);
+ aResolve();
+ }
+ }, function(aError) {
+ if (!handled) {
+ handled = true;
+ ok(false, aDescription + " should be resolved; instead it was rejected with " + aError + " " + aTestID);
+ aResolve();
+ }
+ });
+ Promise.resolve().then(function() {
+ if (!handled) {
+ handled = true;
+ ok(false, aDescription + " should be resolved; instead it is pending " + aTestID);
+ aResolve();
+ }
+ });
+ });
+}
+
+function is_pending(aPromise, aDescription, aTestID) {
+ // This assumes that all Promise tasks come from the task source.
+ var handled = false;
+ return new Promise(function(aResolve, aReject) {
+ aPromise.then(function(aValue) {
+ if (!handled) {
+ handled = true;
+ ok(false, aDescription + " should be pending; instead it was resolved with " + aValue + " " + aTestID);
+ aResolve();
+ }
+ }, function(aError) {
+ if (!handled) {
+ handled = true;
+ ok(false, aDescription + " should be pending; instead it was rejected with " + aError + " " + aTestID);
+ aResolve();
+ }
+ });
+ Promise.resolve().then(function() {
+ if (!handled) {
+ handled = true;
+ ok(true, aDescription + " should be pending " + aTestID);
+ aResolve();
+ }
+ });
+ });
+}
+
+function fetchAsArrayBuffer(aURL) {
+ return new Promise(function(aResolve, aReject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aURL);
+ xhr.responseType = "arraybuffer";
+ xhr.onreadystatechange = function(evt) {
+ if (xhr.readyState == 4) {
+ if (xhr.status >= 200 && xhr.status <= 299) {
+ aResolve(xhr.response);
+ } else {
+ aReject(new Error("Error fetching file " + aURL + ", status " + xhr.status));
+ }
+ }
+ };
+ xhr.send();
+ });
+}
+
+function setTimeoutZero() {
+ return new Promise(function(aResolve, aReject) {
+ setTimeout(aResolve, 0);
+ });
+}
+
+function awaitRefresh() {
+ function awaitOneRefresh() {
+ return new Promise(function(aResolve, aReject) {
+ requestAnimationFrame(aResolve);
+ });
+ }
+
+ return awaitOneRefresh().then(awaitOneRefresh);
+}
+
+function flushStyles() {
+ getComputedStyle(document.body).width;
+}
+
+function runTest() {
+ // Document and window from inside the display:none iframe.
+ var nframe = document.getElementById("n");
+ var ndocument = nframe.contentDocument;
+ var nwindow = nframe.contentWindow;
+
+ // Document and window from inside the visible iframe.
+ var vframe = document.getElementById("v");
+ var vdocument = vframe.contentDocument;
+ var vwindow = vframe.contentWindow;
+
+ // For iterating over different combinations of documents and windows
+ // to test with.
+ var sources = [
+ { win: window, doc: document, what: "window/document" },
+ { win: vwindow, doc: vdocument, what: "vwindow/vdocument" },
+ { win: nwindow, doc: ndocument, what: "nwindow/ndocument" },
+ { win: window, doc: vdocument, what: "window/vdocument" },
+ { win: window, doc: ndocument, what: "window/ndocument" },
+ { win: vwindow, doc: document, what: "vwindow/document" },
+ { win: vwindow, doc: ndocument, what: "vwindow/ndocument" },
+ { win: nwindow, doc: document, what: "nwindow/document" },
+ { win: nwindow, doc: vdocument, what: "nwindow/vdocument" },
+ ];
+
+ var sourceDocuments = [
+ { doc: document, what: "document" },
+ { doc: vdocument, what: "vdocument" },
+ { doc: ndocument, what: "ndocument" },
+ ];
+
+ var sourceWindows = [
+ { win: window, what: "window" },
+ { win: vwindow, what: "vwindow" },
+ { win: nwindow, what: "nwindow" },
+ ];
+
+ queue = queue.then(function() {
+
+ // First, initialize fontData.
+ return fetchAsArrayBuffer("BitPattern.woff")
+ .then(function(aResult) { fontData = aResult; });
+
+ }).then(function() {
+
+ // (TEST 1) Some miscellaneous tests for FontFaceSet and FontFace.
+ ok(window.FontFaceSet, "FontFaceSet interface object should be present (TEST 1)");
+ is(Object.getPrototypeOf(FontFaceSet.prototype), EventTarget.prototype, "FontFaceSet should inherit from EventTarget (TEST 1)");
+ ok(document.fonts instanceof FontFaceSet, "document.fonts should be a a FontFaceSet (TEST 1)");
+ ok(window.FontFace, "FontFace interface object should be present (TEST 1)");
+ is(Object.getPrototypeOf(FontFace.prototype), Object.prototype, "FontFace should inherit from Object (TEST 1)");
+
+ // (TEST 2) Some miscellaneous tests for FontFaceSetLoadEvent.
+ ok(window.FontFaceSetLoadEvent, "FontFaceSetLoadEvent interface object should be present (TEST 2)");
+ is(Object.getPrototypeOf(FontFaceSetLoadEvent.prototype), Event.prototype, "FontFaceSetLoadEvent should inherit from Event (TEST 2)");
+
+ }).then(function() {
+
+ // (TEST 3) Test that document.fonts.ready is resolved with the
+ // document.fonts FontFaceSet.
+ var p = Promise.resolve();
+ sourceDocuments.forEach(function({ doc, what }) {
+ p = p.then(_ => { return doc.fonts.ready }).then(function() {
+ return is_resolved_with(doc.fonts.ready, doc.fonts, "document.fonts.ready resolves with document.fonts.", "(TEST 3) (" + what + ")");
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 4) Test that document.fonts in this test document starts out with no
+ // FontFace objects in it.
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(Array.from(doc.fonts).length, 0, "initial number of FontFace objects in document.fonts (TEST 4) (" + what + ")");
+ });
+
+ // (TEST 5) Test that document.fonts.status starts off as loaded.
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(doc.fonts.status, "loaded", "initial value of document.fonts.status (TEST 5) (" + what + ")");
+ });
+
+ // (TEST 6) Test initial value of FontFace.status when a url() source is
+ // used.
+ sourceWindows.forEach(function({ win, what }) {
+ is(new win.FontFace("test", "url(x)").status, "unloaded", "initial value of FontFace.status when a url() source is used (TEST 6) (" + what + ")");
+ });
+
+ // (TEST 7) Test initial value of FontFace.status when an invalid
+ // ArrayBuffer source is used. Because it has an implicit initial
+ // load() call, it should either be "loading" if the browser is
+ // asynchronously parsing the font data, or "error" if it parsed
+ // it immediately.
+ sourceWindows.forEach(function({ win, what }) {
+ var status = new win.FontFace("test", new ArrayBuffer(0)).status;
+ ok(status == "loading" || status == "error", "initial value of FontFace.status when an invalid ArrayBuffer source is used (TEST 7) (" + what + ")");
+ });
+
+ // (TEST 8) Test initial value of FontFace.status when a valid ArrayBuffer
+ // source is used. Because it has an implicit initial load() call, it
+ // should either be "loading" if the browser is asynchronously parsing the
+ // font data, or "loaded" if it parsed it immediately.
+ sourceWindows.forEach(function({ win, what }) {
+ status = new win.FontFace("test", fontData).status;
+ ok(status == "loading" || status == "loaded", "initial value of FontFace.status when a valid ArrayBuffer source is used (TEST 8) (" + what + ")");
+ });
+
+ // (TEST 9) (old test became redundant with TEST 19)
+
+ }).then(function() {
+
+ // (TEST 10) Test initial value of FontFace.loaded when a valid url()
+ // source is used.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ return is_pending(new win.FontFace("test", "url(x)").loaded, "initial value of FontFace.loaded when a valid url() source is used", "(TEST 10) (" + what + ")");
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 11) (old test became redundant with TEST 21)
+
+ }).then(function() {
+
+ // (TEST 12) (old test became redundant with TEST 20)
+
+ }).then(function() {
+
+ // (TEST 13) Test initial values of the descriptor attributes on FontFace
+ // objects.
+ sourceWindows.forEach(function({ win, what }) {
+ var face = new win.FontFace("test", fontData);
+ // XXX Spec issue: what values do the descriptor attributes have before the
+ // constructor's dictionary argument is parsed?
+ for (var desc in defaultValues) {
+ is(face[desc], defaultValues[desc], "initial value of FontFace." + desc + " (TEST 13) (" + what + ")");
+ }
+ });
+
+ // (TEST 14) Test default values of the FontFaceDescriptors dictionary.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var face = new win.FontFace("test", fontData);
+ return face.loaded.then(function() {
+ for (var desc in defaultValues) {
+ is(face[desc], defaultValues[desc], "default value of FontFace." + desc + " (TEST 14) (" + what + ")");
+ }
+ }, function(aError) {
+ ok(false, "FontFace should have loaded succesfully (TEST 14) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 15) Test passing non-default descriptor values to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var descriptorTests = Promise.resolve();
+ Object.keys(nonDefaultValues).forEach(function(aDesc) {
+ descriptorTests = descriptorTests.then(function() {
+ var init = {};
+ init[aDesc] = nonDefaultValues[aDesc][0];
+ var face = new win.FontFace("test", fontData, init);
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " immediately after construction (TEST 15) (" + what + ")");
+ return face.loaded.then(function() {
+ ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " (TEST 15) (" + what + ")");
+ }, function(aError) {
+ ok(false, "FontFace should have loaded succesfully (TEST 15) (" + what + ")");
+ });
+ });
+ });
+ return descriptorTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 16) Test passing invalid descriptor values to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var descriptorTests = Promise.resolve();
+ Object.keys(invalidValues).forEach(function(aDesc) {
+ descriptorTests = descriptorTests.then(function() {
+ var init = {};
+ init[aDesc] = invalidValues[aDesc];
+ var face = new win.FontFace("test", fontData, init);
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ ok_todo(face.status == "error", "FontFace should be error immediately after construction with invalid value of FontFace." + aDesc + " (TEST 16) (" + what + ")");
+ return face.loaded.then(function() {
+ ok_todo(false, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")");
+ }, function(aError) {
+ ok(true, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")");
+ is(aError.name, "SyntaxError", "FontFace.loaded with invalid value of FontFace." + aDesc + " should be rejected with a SyntaxError (TEST 16) (" + what + ")");
+ });
+ });
+ });
+ return descriptorTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 17) Test passing an invalid font family name to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var familyTests = Promise.resolve();
+ invalidFontFamilyNames.forEach(function(aFamilyName) {
+ familyTests = familyTests.then(function() {
+ var face = new win.FontFace(aFamilyName, fontData);
+ is(face.status, "error", "FontFace should be error immediately after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")");
+ is(face.family, "", "FontFace.family should be the empty string after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")");
+ return face.loaded.then(function() {
+ ok(false, "FontFace should not load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")");
+ }, function(aError) {
+ ok(true, "FontFace should not load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")");
+ is(aError.name, "SyntaxError", "FontFace.loaded with invalid family name " + aFamilyName + " should be rejected with a SyntaxError (TEST 17) (" + what + ")");
+ });
+ });
+ });
+ return familyTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 18) Test passing valid url() source strings to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+
+ // The sub-test is very fragile on Android platform, see Bug 1455824,
+ // especially Comment 34.
+ if (navigator.appVersion.includes("Android")) {
+ return p;
+ }
+
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var srcTests = Promise.resolve();
+ gCSSFontFaceDescriptors.src.values.forEach(function(aSrc) {
+ srcTests = srcTests.then(function() {
+ var face = new win.FontFace("test", aSrc);
+ return face.load().then(function() {
+ ok(true, "FontFace should load with valid url() src " + aSrc + " (TEST 18) (" + what + ")");
+ }, function(aError) {
+ is(aError.name, "NetworkError", "FontFace had NetworkError when loading with valid url() src " + aSrc + " (TEST 18) (" + what + ")");
+ });
+ });
+ });
+ return srcTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 19) Test passing invalid url() source strings to the FontFace
+ // constructor.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var srcTests = Promise.resolve();
+ gCSSFontFaceDescriptors.src.invalid_values.forEach(function(aSrc) {
+ srcTests = srcTests.then(function() {
+ var face = new win.FontFace("test", aSrc);
+ is(face.status, "error", "FontFace.status should be \"error\" when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")");
+ return face.loaded.then(function() {
+ ok(false, "FontFace should not load with invalid url() src " + aSrc + " (TEST 19) (" + what + ")");
+ }, function(aError) {
+ is(aError.name, "SyntaxError", "FontFace.ready should have been rejected with a SyntaxError when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")");
+ });
+ });
+ });
+ return srcTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 20) Test that the status of a FontFace constructed with a valid
+ // ArrayBuffer source eventually becomes "loaded".
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var face = new win.FontFace("test", fontData);
+ return face.loaded.then(function(aFace) {
+ is(face.status, "loaded", "status of FontFace constructed with a valid ArrayBuffer source should eventually be \"loaded\" (TEST 20) (" + what + ")");
+ is(face, aFace, "FontFace.loaded was resolved with the FontFace object once loaded (TEST 20) (" + what + ")");
+ }, function(aError) {
+ ok(false, "FontFace constructed with a valid ArrayBuffer should eventually load (TEST 20) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 21) Test that the status of a FontFace constructed with an invalid
+ // ArrayBuffer source eventually becomes "error".
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var face = new win.FontFace("test", new ArrayBuffer(0));
+ return face.loaded.then(function() {
+ ok(false, "FontFace constructed with an invalid ArrayBuffer should not load (TEST 21) (" + what + ")");
+ }, function(aError) {
+ is(aError.name, "SyntaxError", "loaded of FontFace constructed with an invalid ArrayBuffer source should be rejected with TypeError (TEST 21) (" + what + ")");
+ is(face.status, "error", "status of FontFace constructed with an invalid ArrayBuffer source should eventually be \"error\" (TEST 21) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 22) Test assigning non-default descriptor values on the FontFace.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var descriptorTests = Promise.resolve();
+ Object.keys(nonDefaultValues).forEach(function(aDesc) {
+ descriptorTests = descriptorTests.then(function() {
+ var face = new win.FontFace("test", fontData);
+ return face.loaded.then(function() {
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ face[aDesc] = nonDefaultValues[aDesc][0];
+ ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "assigned valid non-default value to FontFace." + aDesc + " (TEST 22) (" + what + ")");
+ }, function(aError) {
+ ok(false, "FontFace should have loaded succesfully (TEST 22) (" + what + ")");
+ });
+ });
+ });
+ return descriptorTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 23) Test assigning invalid descriptor values on the FontFace.
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var descriptorTests = Promise.resolve();
+ Object.keys(invalidValues).forEach(function(aDesc) {
+ descriptorTests = descriptorTests.then(function() {
+ var face = new win.FontFace("test", fontData);
+ return face.loaded.then(function() {
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ var exceptionName = "";
+ try {
+ face[aDesc] = invalidValues[aDesc];
+ } catch (ex) {
+ exceptionName = ex.name;
+ }
+ ok_todo(exceptionName == "SyntaxError", "assigning invalid value to FontFace." + aDesc + " should throw a SyntaxError (TEST 23) (" + what + ")");
+ }, function(aError) {
+ ok(false, "FontFace should have loaded succesfully (TEST 23) (" + what + ")");
+ });
+ });
+ });
+ return descriptorTests;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 24) Test that the status of a FontFace with a non-existing url()
+ // source is set to "loading" right after load() is called, that its .loaded
+ // Promise is returned, and that the Promise is eventually rejected with a
+ // NetworkError and its status is set to "error".
+ var p = Promise.resolve();
+ sourceWindows.forEach(function({ win, what }) {
+ p = p.then(function() {
+ var face = new win.FontFace("test", "url(x)");
+ var result = face.load();
+ is(face.status, "loading", "FontFace.status should be \"loading\" right after load() is called (TEST 24) (" + what + ")");
+ is(result, face.loaded, "FontFace.load() should return the .loaded Promise (TEST 24) (" + what + ")");
+
+ return result.then(function() {
+ ok(false, "FontFace with a non-existing url() source should not load (TEST 24) (" + what + ")");
+ }, function(aError) {
+ is(aError.name, "NetworkError", "FontFace with a non-existing url() source should result in its .loaded Promise being rejected with a NetworkError (TEST 24) (" + what + ")");
+ is(face.status, "error", "FontFace with a non-existing url() source should result in its .status being set to \"error\" (TEST 24) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 25) Test simple manipulation of the FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var face, face2, all;
+ face = new win.FontFace("test", "url(x)");
+ face2 = new win.FontFace("test2", "url(x)");
+ ok(!doc.fonts.has(face), "newly created FontFace should not be in document.fonts (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ ok(doc.fonts.has(face), "should be able to add a FontFace to document.fonts (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ ok(doc.fonts.has(face), "should be able to repeatedly add a FontFace to document.fonts (TEST 25) (" + what + ")");
+ ok(doc.fonts.delete(face), "FontFaceSet.delete should return true when it succeeds (TEST 25) (" + what + ")");
+ ok(!doc.fonts.has(face), "FontFace should be gone from document.fonts after delete is called (TEST 25) (" + what + ")");
+ ok(!doc.fonts.delete(face), "FontFaceSet.delete should return false when it fails (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ doc.fonts.add(face2);
+ ok(doc.fonts.has(face2), "should be able to add a second FontFace to document.fonts (TEST 25) (" + what + ")");
+ doc.fonts.clear();
+ ok(!doc.fonts.has(face) && !doc.fonts.has(face2), "FontFaces should be gone from document.fonts after clear is called (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ doc.fonts.add(face2);
+ all = Array.from(doc.fonts);
+ is(all[0], face, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")");
+ is(all[1], face2, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")");
+ doc.fonts.add(face);
+ all = Array.from(doc.fonts);
+ is(all[0], face, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")");
+ is(all[1], face2, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")");
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 26) Test that FontFaceSet.ready is replaced, .status is set to
+ // "loading", and a loading event is dispatched when a loading FontFace is
+ // added to it.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingTriggered = false, loadingDispatched = false;
+
+ function check() {
+ if (onloadingTriggered && loadingDispatched) {
+ doc.fonts.onloading = null;
+ doc.fonts.removeEventListener("loading", listener);
+ aResolve();
+ }
+ }
+
+ var listener = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")");
+ loadingDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loading", listener);
+ doc.fonts.onloading = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")");
+ onloadingTriggered = true;
+ check();
+ };
+ });
+
+ is(doc.fonts.status, "loaded", "FontFaceSet.status initially (TEST 26) (" + what + ")");
+
+ var oldReady = doc.fonts.ready;
+ var face = new win.FontFace("test", "url(neverending_font_load.sjs)");
+ face.load();
+ doc.fonts.add(face);
+
+ var newReady = doc.fonts.ready;
+ isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when a loading FontFace is added to it (TEST 26) (" + what + ")");
+ is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when a loading FontFace is added to it (TEST 26) (" + what + ")");
+
+ return awaitEvents
+ .then(function() {
+ return is_pending(newReady, "FontFaceSet.ready should be replaced with a fresh pending Promise when a loading FontFace is added to it", "(TEST 26) (" + what + ")");
+ })
+ .then(function() {
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 27) Test that FontFaceSet.ready is resolved, .status is set to
+ // "loaded", and a loadingdone event (but no loadingerror event) is
+ // dispatched when the only loading FontFace in it is removed.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingdoneTriggered = false, loadingdoneDispatched = false;
+ var onloadingerrorTriggered = false, loadingerrorDispatched = false;
+
+ function check() {
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.removeEventListener("loadingdone", doneListener);
+ doc.fonts.removeEventListener("loadingerror", errorListener);
+ aResolve();
+ }
+
+ var doneListener = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")");
+ is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")");
+ loadingdoneDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loadingdone", doneListener);
+ doc.fonts.onloadingdone = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")");
+ is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")");
+ onloadingdoneTriggered = true;
+ check();
+ };
+ var errorListener = function(aEvent) {
+ loadingerrorDispatched = true;
+ check();
+ }
+ doc.fonts.addEventListener("loadingerror", errorListener);
+ doc.fonts.onloadingerror = function(aEvent) {
+ onloadingdoneTriggered = true;
+ check();
+ };
+ });
+
+ is(doc.fonts.status, "loaded", "FontFaceSet.status should be \"loaded\" initially (TEST 27) (" + what + ")");
+
+ var f = new win.FontFace("test", "url(neverending_font_load.sjs)");
+ f.load();
+ doc.fonts.add(f);
+
+ is(doc.fonts.status, "loading", "FontFaceSet.status should be \"loading\" when a loading FontFace is in it (TEST 27) (" + what + ")");
+
+ doc.fonts.clear();
+
+ return awaitEvents
+ .then(function() {
+ return is_resolved_with(doc.fonts.ready, doc.fonts, "FontFaceSet.ready when the FontFaceSet is cleared", "(TEST 27) (" + what + ")");
+ })
+ .then(function() {
+ is(doc.fonts.status, "loaded", "FontFaceSet.status should be set to \"loaded\" when it is cleared (TEST 27) (" + what + ")");
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 28) Test that FontFaceSet.ready is replaced, .status is set to
+ // "loading", and a loading event is dispatched when a FontFace in it
+ // starts loading.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingTriggered = false, loadingDispatched = false;
+
+ function check() {
+ if (onloadingTriggered && loadingDispatched) {
+ doc.fonts.onloading = null;
+ doc.fonts.removeEventListener("loading", listener);
+ aResolve();
+ }
+ }
+
+ var listener = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")");
+ loadingDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loading", listener);
+ doc.fonts.onloading = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")");
+ onloadingTriggered = true;
+ check();
+ };
+ });
+
+ var oldReady = doc.fonts.ready;
+ var face = new win.FontFace("test", "url(neverending_font_load.sjs)");
+ doc.fonts.add(face);
+ face.load();
+
+ var newReady = doc.fonts.ready;
+ isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when its only FontFace starts loading (TEST 28) (" + what + ")");
+ is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when its only FontFace starts loading (TEST 28) (" + what + ")");
+
+ return awaitEvents
+ .then(function() {
+ return is_pending(newReady, "FontFaceSet.ready when the FontFaceSet's only FontFace starts loading", "(TEST 28) (" + what + ")");
+ })
+ .then(function() {
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 29) Test that a loadingdone and a loadingerror event is dispatched
+ // when a FontFace that eventually becomes status "error" is added to the
+ // FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var face;
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingdoneTriggered = false, loadingdoneDispatched = false;
+ var onloadingerrorTriggered = false, loadingerrorDispatched = false;
+
+ function check() {
+ if (onloadingdoneTriggered && loadingdoneDispatched &&
+ onloadingerrorTriggered && loadingerrorDispatched) {
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.removeEventListener("loadingdone", doneListener);
+ doc.fonts.removeEventListener("loadingerror", errorListener);
+ aResolve();
+ }
+ }
+
+ var doneListener = function(aEvent) {
+ loadingdoneDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loadingdone", doneListener);
+ doc.fonts.onloadingdone = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")");
+ is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 29) (" + what + ")");
+ onloadingdoneTriggered = true;
+ check();
+ };
+ var errorListener = function(aEvent) {
+ is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingerror event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")");
+ is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 29) (" + what + ")");
+ loadingerrorDispatched = true;
+ check();
+ }
+ doc.fonts.addEventListener("loadingerror", errorListener);
+ doc.fonts.onloadingerror = function(aEvent) {
+ onloadingerrorTriggered = true;
+ check();
+ };
+ });
+
+ face = new win.FontFace("test", "url(x)");
+ face.load();
+ is(face.status, "loading", "FontFace should have status \"loading\" (TEST 29) (" + what + ")");
+ doc.fonts.add(face);
+
+ return face.loaded
+ .then(function() {
+ ok(false, "the FontFace should not load (TEST 29) (" + what + ")");
+ }, function(aError) {
+ is(face.status, "error", "FontFace should have status \"error\" (TEST 29) (" + what + ")");
+ return awaitEvents;
+ })
+ .then(function() {
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 30) Test that a loadingdone event is dispatched when a FontFace
+ // that eventually becomes status "loaded" is added to the FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }, i) {
+ p = p.then(function() {
+ var face;
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingdoneTriggered = false, loadingdoneDispatched = false;
+
+ function check() {
+ if (onloadingdoneTriggered && loadingdoneDispatched) {
+ doc.fonts.onloadingdone = null;
+ doc.fonts.removeEventListener("loadingdone", doneListener);
+ aResolve();
+ }
+ }
+
+ var doneListener = function(aEvent) {
+ loadingdoneDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loadingdone", doneListener);
+ doc.fonts.onloadingdone = function(aEvent) {
+ is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 30) (" + what + ")");
+ onloadingdoneTriggered = true;
+ check();
+ };
+ });
+
+ face = new win.FontFace("test", "url(BitPattern.woff?test30." + i + ")");
+ face.load();
+ is(face.status, "loading", "FontFace should have status \"loading\" (TEST 30) (" + what + ")");
+ doc.fonts.add(face);
+
+ return face.loaded
+ .then(function() {
+ is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 30) (" + what + ")");
+ return awaitEvents;
+ })
+ .then(function() {
+ doc.fonts.clear();
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 31) Test that a loadingdone event is dispatched when a FontFace
+ // with status "unloaded" is added to the FontFaceSet and load() is called
+ // on it.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }, i) {
+ p = p.then(function() {
+ var face;
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+
+ var onloadingdoneTriggered = false, loadingdoneDispatched = false;
+
+ function check() {
+ if (onloadingdoneTriggered && loadingdoneDispatched) {
+ doc.fonts.onloadingdone = null;
+ doc.fonts.removeEventListener("loadingdone", doneListener);
+ aResolve();
+ }
+ }
+
+ var doneListener = function(aEvent) {
+ loadingdoneDispatched = true;
+ check();
+ };
+ doc.fonts.addEventListener("loadingdone", doneListener);
+ doc.fonts.onloadingdone = function(aEvent) {
+ is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 31) (" + what + ")");
+ onloadingdoneTriggered = true;
+ check();
+ };
+ });
+
+ face = new win.FontFace("test", "url(BitPattern.woff?test31." + i + ")");
+ is(face.status, "unloaded", "FontFace should have status \"unloaded\" (TEST 31) (" + what + ")");
+ doc.fonts.add(face);
+
+ return face.load()
+ .then(function() {
+ return awaitEvents;
+ })
+ .then(function() {
+ is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 31) (" + what + ")");
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 32) Test that pending restyles prevent document.fonts.status
+ // from becoming loaded.
+ var face = new FontFace("test", "url(neverending_font_load.sjs)");
+ face.load();
+ document.fonts.add(face);
+
+ is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace (TEST 32)");
+
+ document.fonts.clear();
+ flushStyles();
+
+ is(document.fonts.status, "loaded", "FontFaceSet.status after clearing (TEST 32)");
+
+ document.fonts.add(face);
+
+ is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace again (TEST 32)");
+
+ var div = document.querySelector("div");
+ div.style.color = "blue";
+
+ document.fonts.clear();
+ is(document.fonts.status, "loading", "FontFaceSet.status after clearing but when there is a pending restyle (TEST 32)");
+
+ return awaitRefresh() // wait for a refresh driver tick
+ .then(function() {
+ is(document.fonts.status, "loaded", "FontFaceSet.status after clearing and the restyle has been flushed (TEST 32)");
+ return document.fonts.ready;
+ });
+
+ }).then(function() {
+
+ // (TEST 33) Test that CSS-connected FontFace objects are created
+ // for @font-face rules in the document.
+
+ is(document.fonts.status, "loaded", "document.fonts.status should initially be loaded (TEST 33)");
+
+ var style = document.querySelector("style");
+ var ruleText = "@font-face { font-family: something; src: url(x); ";
+ Object.keys(nonDefaultValues).forEach(function(aDesc) {
+ ruleText += descriptorNames[aDesc] + ": " + nonDefaultValues[aDesc][0] + "; ";
+ });
+ ruleText += "}";
+
+ style.textContent = ruleText;
+
+ var rule = style.sheet.cssRules[0];
+
+ var all = Array.from(document.fonts);
+ is(all.length, 1, "document.fonts should contain one FontFace (TEST 33)");
+
+ var face = all[0];
+ is(face.family, "something", "FontFace should have correct family value (TEST 33)");
+ Object.keys(nonDefaultValues).forEach(function(aDesc) {
+ var ok_todo = aDesc == "variant" ? todo : ok;
+ ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "FontFace should have correct " + aDesc + " value (TEST 33)");
+ });
+
+ is(document.fonts.status, "loaded", "document.fonts.status should still be loaded (TEST 33)");
+ is(face.status, "unloaded", "FontFace.status should be unloaded (TEST 33)");
+
+ document.fonts.clear();
+ ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when clear is called (TEST 33)");
+
+ is(document.fonts.delete(face), false, "attempting to remove CSS-connected FontFace from document.fonts should return false (TEST 33)");
+ ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when delete is called (TEST 33)");
+
+ style.textContent = "";
+
+ ok(!document.fonts.has(face), "CSS-connected FontFace should be removed from document.fonts once the rule has been removed (TEST 33)");
+
+ is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after rule is removed (TEST 33)");
+ is(face.status, "unloaded", "FontFace.status should still be unloaded after rule is removed (TEST 33)");
+
+ document.fonts.add(face);
+ ok(document.fonts.has(face), "previously CSS-connected FontFace should be able to be added to document.fonts (TEST 33)");
+
+ is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after now disconnected FontFace is added (TEST 33)");
+ is(face.status, "unloaded", "FontFace.status should still be unloaded after now disconnected FontFace is added (TEST 33)");
+
+ document.fonts.delete(face);
+ ok(!document.fonts.has(face), "previously CSS-connected FontFace should be able to be removed from document.fonts (TEST 33)");
+
+ }).then(function() {
+
+ // (TEST 34) Test that descriptor getters for unspecified descriptors on
+ // CSS-connected FontFace objects return their default values.
+ var style = document.querySelector("style");
+ var ruleText = "@font-face { font-family: something; src: url(x); }";
+
+ style.textContent = ruleText;
+
+ var all = Array.from(document.fonts);
+ var face = all[0];
+
+ Object.keys(defaultValues).forEach(function(aDesc) {
+ is(face[aDesc], defaultValues[aDesc], "FontFace should return default value for " + aDesc + " (TEST 34)");
+ });
+
+ style.textContent = "";
+
+ }).then(function() {
+
+ // (TEST 35) Test that no loadingdone event is dispatched when a FontFace
+ // with "loaded" status is added to a "loaded" FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ var gotLoadingDone = false;
+ doc.fonts.onloadingdone = function(aEvent) {
+ gotLoadingDone = true;
+ };
+
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 35) (" + what + ")");
+ var face = new win.FontFace("test", fontData);
+
+ return face.loaded
+ .then(function() {
+ is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 35) (" + what + ")");
+ doc.fonts.add(face);
+ is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 35) (" + what + ")");
+ return doc.fonts.ready;
+ })
+ .then(function() {
+ ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 35) (" + what + ")");
+ doc.fonts.onloadingdone = null;
+ doc.fonts.clear();
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 36) Test that no loadingdone or loadingerror event is dispatched
+ // when a FontFace with "error" status is added to a "loaded" FontFaceSet.
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ var doc = win.document;
+ p = p.then(function() {
+ var gotLoadingDone = false, gotLoadingError = false;
+ doc.fonts.onloadingdone = function(aEvent) {
+ gotLoadingDone = true;
+ };
+ doc.fonts.onloadingerror = function(aEvent) {
+ gotLoadingError = true;
+ };
+
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 36) (" + what + ")");
+ var face = new win.FontFace("test", new ArrayBuffer(0));
+
+ return face.loaded
+ .then(function() {
+ ok(false, "FontFace should not have loaded (TEST 36) (" + what + ")");
+ }, function() {
+ is(face.status, "error", "FontFace should have status \"error\" (TEST 36) (" + what + ")");
+ doc.fonts.add(face);
+ is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 36) (" + what + ")");
+ return doc.fonts.ready;
+ })
+ .then(function() {
+ ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 36) (" + what + ")");
+ ok(!gotLoadingError, "loadingerror event should not be dispatched (TEST 36) (" + what + ")");
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.clear();
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 37) Test that a FontFace only has one loadingdone event dispatched
+ // at the FontFaceSet containing it.
+
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what}, i) {
+ p = p.then(function() {
+ return setTimeoutZero(); // wait for any previous events to be dispatched
+ }).then(function() {
+ var events = [], face, face2;
+
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+ doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) {
+ events.push(e);
+ if (events.length == 2) {
+ aResolve();
+ }
+ };
+ });
+
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 37) (" + what + ")");
+
+ face = new win.FontFace("test", "url(BitPattern.woff?test37." + i + "a)");
+ face.load();
+ doc.fonts.add(face);
+ is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 37) (" + what + ")");
+
+ return doc.fonts.ready
+ .then(function() {
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 37) (" + what + ")");
+ is(face.status, "loaded", "first FontFace should have status \"loaded\" (TEST 37) (" + what + ")");
+
+ face2 = new win.FontFace("test2", "url(BitPattern.woff?test37." + i + "b)");
+ face2.load();
+ doc.fonts.add(face2);
+ is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 37) (" + what + ")");
+
+ return doc.fonts.ready;
+ }).then(function() {
+ return awaitEvents;
+ }).then(function() {
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 37) (" + what + ")");
+ is(face2.status, "loaded", "second FontFace should have status \"loaded\" (TEST 37) (" + what + ")");
+
+ is(events.length, 2, "should receive two events (TEST 37) (" + what + ")");
+
+ is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 37) (" + what + ")");
+ is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 37) (" + what + ")");
+ is(events[0].fontfaces[0], face, "first event should have the first FontFace");
+
+ is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 37) (" + what + ")");
+ is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 37) (" + what + ")");
+ is(events[1].fontfaces[0], face2, "second event should have the second FontFace (TEST 37) (" + what + ")");
+
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 38) Test that a FontFace only has one loadingerror event dispatched
+ // at the FontFaceSet containing it.
+
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ return setTimeoutZero(); // wait for any previous events to be dispatched
+ }).then(function() {
+ var events = [], face, face2;
+
+ var awaitEvents = new Promise(function(aResolve, aReject) {
+ doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) {
+ events.push(e);
+ if (events.length == 4) {
+ aResolve();
+ }
+ };
+ });
+
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 38) (" + what + ")");
+
+ face = new win.FontFace("test", "url(x)");
+ face.load();
+ doc.fonts.add(face);
+ is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 38) (" + what + ")");
+
+ return doc.fonts.ready
+ .then(function() {
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font failed to load (TEST 38) (" + what + ")");
+ is(face.status, "error", "first FontFace should have status \"error\" (TEST 38) (" + what + ")");
+
+ face2 = new win.FontFace("test2", "url(x)");
+ face2.load();
+ doc.fonts.add(face2);
+ is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 38) (" + what + ")");
+
+ return doc.fonts.ready;
+ }).then(function() {
+ return awaitEvents;
+ }).then(function() {
+ is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font failed to load (TEST 38) (" + what + ")");
+ is(face2.status, "error", "second FontFace should have status \"error\" (TEST 38) (" + what + ")");
+
+ is(events.length, 4, "should receive four events (TEST 38) (" + what + ")");
+
+ is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 38) (" + what + ")");
+ is(events[0].fontfaces.length, 0, "first event should have no FontFaces (TEST 38) (" + what + ")");
+
+ is(events[1].type, "loadingerror", "second event should be \"loadingerror\" (TEST 38) (" + what + ")");
+ is(events[1].fontfaces.length, 1, "second event should have 1 FontFace (TEST 38) (" + what + ")");
+ is(events[1].fontfaces[0], face, "second event should have the first FontFace");
+
+ is(events[2].type, "loadingdone", "third event should be \"loadingdone\" (TEST 38) (" + what + ")");
+ is(events[2].fontfaces.length, 0, "third event should have no FontFaces (TEST 38) (" + what + ")");
+
+ is(events[3].type, "loadingerror", "third event should be \"loadingerror\" (TEST 38) (" + what + ")");
+ is(events[3].fontfaces.length, 1, "third event should only have 1 FontFace (TEST 38) (" + what + ")");
+ is(events[3].fontfaces[0], face2, "third event should have the second FontFace");
+
+ doc.fonts.onloadingdone = null;
+ doc.fonts.onloadingerror = null;
+ doc.fonts.clear();
+ return doc.fonts.ready;
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 39) Test that a FontFace for an @font-face rule only has one
+ // loadingdone event dispatched at the FontFaceSet containing it.
+
+ var style, all, events, awaitEvents;
+
+ return setTimeoutZero() // wait for any previous events to be dispatched
+ .then(function() {
+ style = document.querySelector("style");
+ var ruleText = "@font-face { font-family: test; src: url(BitPattern.woff?test39a); } " +
+ "@font-face { font-family: test2; src: url(BitPattern.woff?test39b); }";
+
+ style.textContent = ruleText;
+
+ all = Array.from(document.fonts);
+ events = [];
+
+ awaitEvents = new Promise(function(aResolve, aReject) {
+ document.fonts.onloadingdone = document.fonts.onloadingerror = function(e) {
+ events.push(e);
+ if (events.length == 2) {
+ aResolve();
+ }
+ };
+ });
+
+ is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 39)");
+
+ all[0].load();
+ is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font loading (TEST 39)");
+
+ return document.fonts.ready
+ }).then(function() {
+ is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 39)");
+ is(all[0].status, "loaded", "first FontFace should have status \"loaded\" (TEST 39)");
+ is(all[1].status, "unloaded", "second FontFace should have status \"unloaded\" (TEST 39)");
+
+ all[1].load();
+ is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font loading (TEST 39)");
+
+ return document.fonts.ready;
+ }).then(function() {
+ return awaitEvents;
+ }).then(function() {
+ is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 39)");
+ is(all[1].status, "loaded", "second FontFace should have status \"loaded\" (TEST 39)");
+
+ is(events.length, 2, "should receive two events (TEST 39)");
+
+ is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 39)");
+ is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 39)");
+ is(events[0].fontfaces[0], all[0], "first event should have the first FontFace");
+
+ is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 39)");
+ is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 39)");
+ is(events[1].fontfaces[0], all[1], "second event should have the second FontFace (TEST 39)");
+
+ style.textContent = "";
+
+ document.fonts.onloadingdone = null;
+ document.fonts.onloadingerror = null;
+ document.fonts.clear();
+ return document.fonts.ready;
+ });
+
+ }).then(function() {
+
+ // (TEST 40) Test that an attempt to add the same FontFace object a second
+ // time to a FontFaceSet (where one of the FontFace objects is reflecting
+ // an @font-face rule) will be ignored.
+
+ // First set up a @font-face rule.
+ var style = document.querySelector("style");
+ style.textContent = "@font-face { font-family: something; src: url(x); }";
+
+ // Then add a couple of non-connected FontFace objects.
+ var f1 = new FontFace("test1", "url(x)");
+ var f2 = new FontFace("test2", "url(x)");
+
+ document.fonts.add(f1);
+ document.fonts.add(f2);
+
+ var all = Array.from(document.fonts);
+ var ruleFontFace = all[0];
+
+ is(all.length, 3, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 40)");
+ is(all[1], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)");
+ is(all[2], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)");
+
+ document.fonts.add(f1);
+
+ all = Array.from(document.fonts);
+ is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #1 (TEST 40)");
+ is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #1 (TEST 40)");
+ is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)");
+ is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)");
+
+ document.fonts.add(ruleFontFace);
+
+ all = Array.from(document.fonts);
+ is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #2 (TEST 40)");
+ is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #2 (TEST 40)");
+ is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)");
+ is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)");
+
+ style.textContent = "";
+
+ document.fonts.clear();
+
+ }).then(function() {
+
+ // (TEST 41) Test that an attempt to add the same FontFace object a second
+ // time to a FontFaceSet (where none of the FontFace objects are reflecting
+ // an @font-face rule) will be ignored.
+
+ sources.forEach(function({ win, doc, what }) {
+ // Add a couple of non-connected FontFace objects.
+ var f1 = new win.FontFace("test1", "url(x)");
+ var f2 = new win.FontFace("test2", "url(x)");
+
+ doc.fonts.add(f1);
+ doc.fonts.add(f2);
+
+ var all = Array.from(doc.fonts);
+
+ is(all.length, 2, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 41) (" + what + ")");
+ is(all[0], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")");
+ is(all[1], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")");
+
+ doc.fonts.add(f1);
+
+ all = Array.from(doc.fonts);
+ is(all.length, 2, "number of FontFace objects in the FontFaceSet after duplicate add (TEST 41) (" + what + ")");
+ is(all[0], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")");
+ is(all[1], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")");
+
+ doc.fonts.clear();
+ });
+
+ }).then(function() {
+
+ // (TEST 42) Test that adding a FontFace to multiple FontFaceSets and then
+ // loading it updates the status of all FontFaceSets.
+
+ var face = new FontFace("test", "url(x)");
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ doc.fonts.add(face);
+ });
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(doc.fonts.status, "loaded", what + ".fonts.status before loading (TEST 42)");
+ });
+
+ face.load();
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(doc.fonts.status, "loading", what + ".fonts.status after loading started (TEST 42)");
+ });
+
+ return Promise.all(sourceDocuments.map(function({ doc }) { return doc.fonts.ready; }))
+ .then(function() {
+ is(face.status, "error", "FontFace.status after loading finished (TEST 42)");
+ sourceDocuments.forEach(function({ doc, what }) {
+ is(doc.fonts.status, "loaded", what + ".fonts.status after loading finished (TEST 42)");
+ });
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ doc.fonts.clear();
+ });
+ });
+
+ }).then(function() {
+
+ // (TEST 43) Test the check method with platform fonts and some
+ // degenerate cases.
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ // Invalid font shorthands should throw a SyntaxError.
+ try {
+ doc.fonts.check("Helvetica");
+ ok(false, "check should throw when a syntactically invalid font shorthand is given (TEST 43) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "SyntaxError", "exception name when check is called with a syntactically invalid font shorthand (TEST 43) (" + what + ")");
+ }
+
+ // System fonts should throw a SyntaxError.
+ try {
+ doc.fonts.check("caption");
+ ok(false, "check should throw when a system font value is given (TEST 43) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "SyntaxError", "exception name when check is called with a system font value (TEST 43) (" + what + ")");
+ }
+
+ // CSS-wide keywords should throw a SyntaxError.
+ try {
+ doc.fonts.check("inherit");
+ ok(false, "check should throw when a CSS-wide keyword is given (TEST 43) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "SyntaxError", "exception name when check is called with a CSS-wide keyword (TEST 43) (" + what + ")");
+ }
+
+ // CSS variables should throw a SyntaxError.
+ try {
+ doc.fonts.check("16px var(--family)");
+ ok(false, "check should throw when CSS variables are used (TEST 43) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "SyntaxError", "exception name when check is called with CSS variables (TEST 43) (" + what + ")");
+ }
+
+ // No matching font family names => return true.
+ is(doc.fonts.check("16px NonExistentFont1, NonExistentFont2"), true, "check return value when no matching font family names are used (TEST 43) (" + what + ")");
+
+ // Matching platform font family name => return true.
+ is(doc.fonts.check("16px NonExistentFont, " + likelyPlatformFonts), true, "check return value when a matching platform font family name is used (TEST 43) (" + what + ")");
+
+ // Matching platform font family name, but using a different test
+ // strings. (Platform fonts always return true from check, regardless
+ // of the actual glyphs present.)
+ [
+ { test: "\0", desc: "a single non-matching glyph" },
+ { test: "A\0", desc: "a matching and a non-matching glyph" },
+ { test: "A", desc: "a matching glyph" },
+ { test: "AB", desc: "multiple matching glyphs" }
+ ].forEach(function({ test, desc }) {
+ is(doc.fonts.check("16px " + likelyPlatformFonts, test), true, "check return value when a matching platform font family name is used but with " + desc + " (TEST 43) (" + what + ")");
+ });
+
+ // No matching font family name, but an empty test string.
+ is(doc.fonts.check("16px NonExistentFont", ""), true, "check return value with a non-matching font family name and an empty test string (TEST 43) (" + what + ")");
+
+ // Matching platform font family name, but empty test string.
+ is(doc.fonts.check("16px " + likelyPlatformFonts, ""), true, "check return value with an empty test string (TEST 43) (" + what + ")");
+ });
+
+ }).then(function() {
+
+ // (TEST 44) Test the check method with script-created FontFaces.
+
+ var tests = [
+ // at least one matching FontFace is not loaded ==> false
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }] },
+ { result: false, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded" }] },
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "loaded" }] },
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "loading" }] },
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "error" }] },
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic" }] },
+ { result: false, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold" }] },
+ { result: false, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] },
+ { result: false, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] },
+
+ // all matching FontFaces are loaded ==> true
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "loaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Irrelevant", status: "unloaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed" }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }, { family: "Test", status: "unloaded", weight: "600" }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "loaded" }] },
+
+ // no matching FontFaces at all ==> true
+ { result: true, font: "16px Test", faces: [] },
+ { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] },
+
+ // matching FontFace for one sample text character is loaded but
+ // not the other ==> false
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "unloaded", unicodeRange: "U+62" }] },
+
+ // matching FontFaces for separate sample text characters are all
+ // loaded ==> true
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "loaded", unicodeRange: "U+62" }] },
+ ];
+
+ sources.forEach(function({ win, doc, what }, i) {
+ tests.forEach(function({ result, font, faces }, j) {
+ faces.forEach(function(f, k) {
+ var fontFace;
+ if (f.status == "loaded") {
+ fontFace = new win.FontFace(f.family, fontData, f);
+ } else if (f.status == "error") {
+ fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f);
+ } else {
+ fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test44." + [i, j, k] + ")", f);
+ if (f.status == "loading") {
+ fontFace.load();
+ }
+ }
+ is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 44) (" + what + ")");
+ doc.fonts.add(fontFace);
+ });
+ is(doc.fonts.check(font, "ab"), result, "check return value for subtest " + j + " (TEST 44) (" + what + ")");
+ doc.fonts.clear();
+ });
+ });
+
+ }).then(function() {
+
+ // (TEST 45) Test the load method with platform fonts and some
+ // degenerate cases.
+
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }) {
+ p = p.then(function() {
+ // Invalid font shorthands should reject the promise with a SyntaxError.
+ return doc.fonts.load("Helvetica").then(function() {
+ ok(false, "load should reject when a syntactically invalid font shorthand is given (TEST 45) (" + what + ")");
+ }, function(ex) {
+ is(ex.name, "SyntaxError", "exception name when load is called with a syntactically invalid font shorthand (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // System fonts should reject with a SyntaxError.
+ return doc.fonts.load("caption").then(function() {
+ ok(false, "load should throw when a system font value is given (TEST 45) (" + what + ")");
+ }, function(ex) {
+ is(ex.name, "SyntaxError", "exception name when load is called with a system font value (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // CSS-wide keywords should reject with a SyntaxError.
+ return doc.fonts.load("inherit").then(function() {
+ ok(false, "load should throw when a CSS-wide keyword is given (TEST 45) (" + what + ")");
+ }, function(ex) {
+ is(ex.name, "SyntaxError", "exception name when load is called with a CSS-wide keyword (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // CSS variables should throw a SyntaxError.
+ return doc.fonts.load("16px var(--family)").then(function() {
+ ok(false, "load should throw when CSS variables are used (TEST 45) (" + what + ")");
+ }, function(ex) {
+ is(ex.name, "SyntaxError", "exception name when load is called with CSS variables (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // No matching font family names => return true.
+ return doc.fonts.load("16px NonExistentFont1, NonExistentFont2").then(function(result) {
+ is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // Matching platform font family name => return true.
+ return doc.fonts.load("16px NonExistentFont1, " + likelyPlatformFonts).then(function(result) {
+ is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")");
+ });
+ });
+
+ // Matching platform font family name, but using a different test
+ // strings. (Platform fonts always return true from load, regardless
+ // of the actual glyphs present.)
+ [
+ { sample: "\0", desc: "a single non-matching glyph" },
+ { sample: "A\0", desc: "a matching and a non-matching glyph" },
+ { sample: "A", desc: "a matching glyph" },
+ { sample: "AB", desc: "multiple matching glyphs" }
+ ].forEach(function({ sample, desc }) {
+ p = p.then(function() {
+ return doc.fonts.load("16px " + likelyPlatformFonts, sample).then(function(result) {
+ is(result.length, 0, "load resolves with an empty array when a matching platform font family name is used but with " + desc + " (TEST 45) (" + what + ")");
+ });
+ });
+ });
+
+ p = p.then(function() {
+ // No matching font family name, but an empty test string.
+ return doc.fonts.load("16px NonExistentFont", "").then(function(result) {
+ is(result.length, 0, "load resolves with an empty array when a non-matching platform font family name and an empty test string is used (TEST 45) (" + what + ")");
+ });
+ });
+
+ p = p.then(function() {
+ // Matching font family name, but an empty test string.
+ return doc.fonts.load("16px " + likelyPlatformFonts, "").then(function(result) {
+ is(result.length, 0, "load resolves with an empty array when a matching platform font family name and an empty test string is used (TEST 45) (" + what + ")");
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 46) Test the load method with script-created FontFaces.
+
+ var tests = [
+ // at least one matching FontFace is not yet loaded, but will load ==> resolve
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }, { family: "Test", status: "loaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loading", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic", included: true }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600", included: true }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold", included: true }] },
+ { result: true, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] },
+
+ // at least one matching FontFace is in an error state ==> reject
+ { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "error" }] },
+
+ // all matching FontFaces are already loaded ==> resolve
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "loaded", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Irrelevant", status: "unloaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed", included: true }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }, { family: "Test", status: "loaded", weight: "600" }] },
+ { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "loaded", weight: "bold", included: true }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "loaded", included: true }] },
+
+ // no matching FontFaces at all ==> resolve
+ { result: true, font: "16px Test", faces: [] },
+ { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] },
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] },
+ { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] },
+
+ // matching FontFace for one sample text character is already loaded but
+ // the other is not (but will) ==> resolve
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+62", included: true }] },
+
+ // matching FontFaces for separate sample text characters are all
+ // loaded ==> resolve
+ { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "loaded", unicodeRange: "U+62", included: true }] },
+ ];
+
+ var p = Promise.resolve();
+ sources.forEach(function({ win, doc, what }, i) {
+ tests.forEach(function({ result, font, faces }, j) {
+ p = p.then(function() {
+ var fontFaces = [];
+ faces.forEach(function(f, k) {
+ var fontFace;
+ if (f.status == "loaded") {
+ fontFace = new win.FontFace(f.family, fontData, f);
+ } else if (f.status == "error") {
+ fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f);
+ } else {
+ fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test46." + [i, j, k] + ")", f);
+ if (f.status == "loading") {
+ fontFace.load();
+ }
+ }
+ is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 46) (" + what + ")");
+ doc.fonts.add(fontFace);
+ fontFaces.push(fontFace);
+ });
+ return doc.fonts.load(font, "ab").then(function(array) {
+ ok(result, "load should resolve for subtest " + j + " (TEST 46) (" + what + ")");
+ var expected = [];
+ for (var k = 0; k < faces.length; k++) {
+ if (faces[k].included) {
+ expected.push(fontFaces[k]);
+ }
+ }
+ is(array.length, expected.length, "length of array load resolves with for subtest " + j + " (TEST 46) (" + what + ")");
+ for (var k = 0; k < array.length; k++) {
+ is(array[k], expected[k], "value in array[" + k + "] load resolves with for subtest " + j + " (TEST 46) (" + what + ")");
+ }
+ }, function(ex) {
+ ok(!result, "load should not resolve for subtest " + j + " (TEST 46) (" + what + ")");
+ is(ex.name, "SyntaxError", "exception load's return value is rejected with for subtest " + j + " (TEST 46) (" + what + ")");
+ }).then(function() {
+ doc.fonts.clear();
+ });
+ });
+ });
+ });
+ return p;
+
+ }).then(function() {
+
+ // (TEST 47) Test that CSS-connected FontFaces can't be added to other
+ // FontFaceSets.
+
+ var style = document.querySelector("style");
+ style.textContent = "@font-face { font-family: something; src: url(x); }";
+
+ var rule = style.sheet.cssRules[0];
+
+ var all = Array.from(document.fonts);
+ is(all.length, 1, "document.fonts should contain one FontFace (TEST 47)");
+
+ var face = all[0];
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ if (doc == document) {
+ return;
+ }
+
+ var exceptionName;
+ try {
+ doc.fonts.add(face);
+ ok(false, "add should throw when attempting to add a CSS-connected FontFace to another FontFaceSet (TEST 47) (" + what + ")");
+ } catch (ex) {
+ is(ex.name, "InvalidModificationError", "exception name when add is called with a CSS-connected FontFace from another FontFaceSet (TEST 47) (" + what + ")");
+ }
+ });
+
+ style.textContent = "";
+ document.body.offsetTop;
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ if (doc == document) {
+ return;
+ }
+
+ ok(!doc.fonts.has(face), "FontFaceSet initially doesn't have the FontFace (TEST 47) (" + what + ")");
+ doc.fonts.add(face);
+ ok(doc.fonts.has(face), "add should allow a previously CSS-connected FontFace to be added to another FontFaceSet (TEST 47) (" + what + ")");
+ doc.fonts.clear();
+ });
+
+ document.fonts.clear();
+
+ }).then(function() {
+
+ // (TEST 48) Test that FontFaceSets that hold a combination of FontFaces
+ // from different documents expose the right set of FontFaces.
+
+ // Expected FontFaceSet contents.
+ var expected = {
+ document: [],
+ vdocument: [],
+ ndocument: [],
+ };
+
+ // Create a CSS-connected FontFace in the top-level document.
+ var style = document.querySelector("style");
+ style.textContent = "@font-face { font-family: something; src: url(x); }";
+
+ var all = Array.from(document.fonts);
+ is(all.length, 1, "document.fonts should contain one FontFace (TEST 48)");
+
+ all[0]._description = "CSS-connected in document";
+ expected.document.push(all[0]);
+
+ // Create a CSS-connected FontFace in the visible iframe.
+ var vstyle = vdocument.querySelector("style");
+ vstyle.textContent = "@font-face { font-family: somethingelse; src: url(x); }";
+
+ all = Array.from(vdocument.fonts);
+ all[0]._description = "CSS-connected in vdocument";
+ is(all.length, 1, "vdocument.fonts should contain one FontFace (TEST 48)");
+
+ expected.vdocument.push(all[0]);
+
+ // Create a FontFace in each window and add it to each document's FontFaceSet.
+ var faces = [];
+ sourceWindows.forEach(function({ win, what: whatWin }, index) {
+ var f = new win.FontFace("test" + index, "url(x)");
+ sourceDocuments.forEach(function({ doc, what: whatDoc }) {
+ doc.fonts.add(f);
+ expected[whatDoc].push(f);
+ f._description = whatWin + "/" + whatDoc;
+ });
+ });
+
+ sourceDocuments.forEach(function({ doc, what }) {
+ let allFonts = Array.from(doc.fonts);
+ is(expected[what].length, allFonts.length, "expected FontFaceSet size (TEST 48) (" + what + ")");
+ for (let i = 0; i < expected[what].length; i++) {
+ is(expected[what][i], allFonts[i], "expected FontFace (" + expected[what][i]._description + ") at index " + i + " (TEST 48) (" + what + ")");
+ }
+ });
+
+ vstyle.textContent = "";
+ style.textContent = "";
+
+ sourceDocuments.forEach(function({ doc }) { doc.fonts.clear(); });
+
+ }).then(function() {
+
+ // (TEST LAST) Test that a pending style sheet load prevents
+ // document.fonts.status from being set to "loaded".
+
+ // First, add a FontFace to document.fonts that will load soon.
+ var face = new FontFace("test", "url(BitPattern.woff?testlast)");
+ face.load();
+ document.fonts.add(face);
+
+ // Next, add a style sheet reference.
+ var link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = "neverending_stylesheet_load.sjs";
+ link.type = "text/css";
+ document.head.appendChild(link);
+
+ return setTimeoutZero() // wait for the style sheet to start loading
+ .then(function() {
+ document.fonts.clear();
+ is(document.fonts.status, "loading", "FontFaceSet.status when the FontFaceSet has been cleared of loading FontFaces but there is a pending style sheet load (TEST LAST)");
+ document.head.removeChild(link);
+ // XXX Removing the <link> element won't cancel the load of the
+ // style sheet, so we can't do that to test that
+ // document.fonts.ready is resolved once there are no more
+ // loading style sheets.
+ });
+
+ // NOTE: It is important that this style sheet test comes last in the file,
+ // as the neverending style sheet load will interfere with subsequent
+ // sub-tests.
+
+ }).then(function() {
+
+ // End of the tests.
+ SimpleTest.finish();
+
+ }, function(aError) {
+
+ // Something failed.
+ ok(false, "Something failed: " + aError);
+ SimpleTest.finish();
+
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(5);
+
+</script>
+
+<style></style>
+<div></div>
diff --git a/layout/style/test/test_garbage_at_end_of_declarations.html b/layout/style/test/test_garbage_at_end_of_declarations.html
new file mode 100644
index 0000000000..95882d1591
--- /dev/null
+++ b/layout/style/test/test_garbage_at_end_of_declarations.html
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test handling of garbage at the end of CSS declarations</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/* eslint-disable dot-notation */
+/** Test for correct ExpectEndProperty calls in CSS parser **/
+
+
+/*
+ * Inspired by review comments on bug 378217.
+ *
+ * The original idea was to test that ExpectEndProperty calls are made
+ * in the correct places in the CSS parser so that we don't accept
+ * garbage at the end of property values.
+ *
+ * However, there's actually other code (in ParseDeclaration) that
+ * ensures that we don't accept garbage.
+ *
+ * Despite that, I'm checking it in anyway, since it caught an infinite
+ * loop in the patch for bug 435441.
+ */
+
+var gElement = document.getElementById("testnode");
+var gDeclaration = gElement.style;
+
+/*
+ * This lists properties where garbage identifiers are allowed at the
+ * end, with values in property_database.js that are exceptions that
+ * should be tested anyway. "inherit", "initial" and "unset" are always
+ * tested.
+ */
+var gAllowsExtra = {
+ "counter-increment": { "none": true },
+ "counter-reset": { "none": true },
+ "font-family": {},
+ "font": { "caption": true, "icon": true, "menu": true, "message-box": true,
+ "small-caption": true, "status-bar": true },
+ "voice-family": {},
+ "list-style": {
+ "inside none": true, "none inside": true, "none": true,
+ "none outside": true, "outside none": true,
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")': true,
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside': true,
+ 'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")': true
+ },
+};
+
+/* These are the reverse of the above list; they're the unusual values
+ that do allow extra keywords afterwards */
+var gAllowsExtraUnusual = {
+ "transition": { "all": true, "0s": true, "0s 0s": true, "ease": true,
+ "1s 2s linear": true, "1s linear 2s": true,
+ "linear 1s 2s": true, "linear 1s": true,
+ "1s linear": true, "1s 2s": true, "2s 1s": true,
+ "linear": true, "1s": true, "2s": true,
+ "ease-in-out": true, "2s ease-in": true,
+ "ease-out 2s": true, "1s width, 2s": true },
+ "animation": { "none": true, "0s": true, "ease": true,
+ "normal": true, "running": true, "1.0": true,
+ "1s 2s linear": true, "1s linear 2s": true,
+ "linear 1s 2s": true, "linear 1s": true,
+ "1s linear": true, "1s 2s": true, "2s 1s": true,
+ "linear": true, "1s": true, "2s": true,
+ "ease-in-out": true, "2s ease-in": true,
+ "ease-out 2s": true, "1s bounce, 2s": true,
+ "1s bounce, 2s none": true },
+ "font-family": { "inherit": true, "initial": true, "unset": true }
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.transitions")) {
+ gAllowsExtraUnusual["-moz-transition"] = gAllowsExtraUnusual["transition"];
+}
+if (IsCSSPropertyPrefEnabled("layout.css.prefixes.animations")) {
+ gAllowsExtraUnusual["-moz-animation"] = gAllowsExtraUnusual["animation"];
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ function test_value(value) {
+ if (property in gAllowsExtra &&
+ value != "inherit" && value != "initial" && value != "unset" &&
+ !(value in gAllowsExtra[property])) {
+ return;
+ }
+ if (property in gAllowsExtraUnusual &&
+ value in gAllowsExtraUnusual[property]) {
+ return;
+ }
+
+ // Include non-identifier characters in the garbage
+ // in case |value| would also be valid with a <custom-ident> added.
+ gElement.setAttribute("style", property + ": " + value + " +blah/");
+ if ("subproperties" in info) {
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ is(gDeclaration.getPropertyValue(subprop), "",
+ ["expected garbage ignored after '", property, ": ", value,
+ "' when looking at subproperty '", subprop, "'"].join(""));
+ }
+ } else {
+ is(gDeclaration.getPropertyValue(property), "",
+ ["expected garbage ignored after '", property, ": ", value,
+ "'"].join(""));
+ }
+ }
+
+ var idx;
+ test_value("inherit");
+ test_value("initial");
+ test_value("unset");
+ for (idx in info.initial_values)
+ test_value(info.initial_values[idx]);
+ for (idx in info.other_values)
+ test_value(info.other_values[idx]);
+}
+
+// To avoid triggering the slow script dialog, we have to test one
+// property at a time.
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+var props = [];
+for (var prop in gCSSProperties)
+ props.push(prop);
+props = props.reverse();
+function do_one() {
+ if (props.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ test_property(props.pop());
+ SimpleTest.executeSoon(do_one);
+}
+SimpleTest.executeSoon(do_one);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_grid_computed_values.html b/layout/style/test/test_grid_computed_values.html
new file mode 100644
index 0000000000..68a183606c
--- /dev/null
+++ b/layout/style/test/test_grid_computed_values.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test computed grid values</title>
+ <link rel="author" title="Tobias Schneider" href="mailto:schneider@jancona.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+ <style>
+
+ #grid {
+ display: grid;
+ width: 500px;
+ height: 400px;
+ grid-template-columns:
+ [a] auto
+ [b] minmax(min-content, 1fr)
+ [b c d] repeat(2, [e] 40px)
+ repeat(5, auto);
+ grid-template-rows:
+ [a] minmax(min-content, 1fr)
+ [b] auto
+ [b c d e] 30px 30px
+ auto auto;
+ grid-auto-columns: 3fr;
+ grid-auto-rows: 2fr;
+ }
+ #grid2 {
+ display: grid;
+ width: 500px;
+ height: 400px;
+ grid-auto-columns: 10px;
+ grid-auto-rows: 2fr;
+ }
+
+ </style>
+</head>
+<body>
+
+<div>
+ <div id="grid">
+ <div style="grid-column-start:1; width:50px"></div>
+ <div style="grid-column-start:9; width:50px"></div>
+ </div>
+ <div id="grid2">
+ <div style="grid-column: span X / 1"></div>
+ <div style="grid-column: 1 / span X 2"></div>
+ </div>
+<div>
+
+<script>
+
+ var gridElement = document.getElementById("grid");
+
+ function test_grid_template(assert_fn, width, height, desc) {
+ test(function() {
+ assert_fn(getComputedStyle(gridElement).gridTemplateColumns,
+ "[a] 50px [b] " + width + "px [b c d e] 40px [e] 40px 0px 0px 0px 0px 50px");
+ assert_fn(getComputedStyle(gridElement).gridTemplateRows,
+ "[a] " + height + "px [b] 0px [b c d e] 30px 30px 0px 0px");
+ }, desc);
+ }
+
+ test_grid_template(assert_equals, 320, 340, "test computed grid-template-{columns,rows} values");
+
+ gridElement.style.overflow = 'scroll';
+ var v_scrollbar = gridElement.offsetWidth - gridElement.clientWidth;
+ var h_scrollbar = gridElement.offsetHeight - gridElement.clientHeight;
+ test_grid_template(assert_equals, 320 - v_scrollbar, 340 - h_scrollbar,
+ "test computed grid-template-{columns,rows} values, overflow: scroll");
+
+ gridElement.style.width = '600px';
+ gridElement.style.overflow = 'visible';
+ test_grid_template(assert_equals, 420, 340,
+ "test computed grid-template-{columns,rows} values, after reflow");
+
+ gridElement.style.display = 'none';
+ test_grid_template(assert_not_equals, 420, 340,
+ "test computed grid-template-{columns,rows} values, display: none");
+
+ gridElement.style.display = 'grid';
+ gridElement.parentNode.style.display = 'none';
+ test_grid_template(assert_not_equals, 420, 340,
+ "test computed grid-template-{columns,rows} values, display: none on parent");
+
+ gridElement.parentNode.style.display = '';
+ function test_grid2() {
+ gridElement = document.getElementById("grid2");
+ test(function() {
+ const expectedCols = SpecialPowers.getBoolPref("layout.css.serialize-grid-implicit-tracks")
+ ? "10px 10px 10px"
+ : "none";
+ const expectedRows = SpecialPowers.getBoolPref("layout.css.serialize-grid-implicit-tracks")
+ ? "400px"
+ : "none";
+
+ assert_equals(getComputedStyle(gridElement).gridTemplateColumns,
+ expectedCols);
+ assert_equals(getComputedStyle(gridElement).gridTemplateRows,
+ expectedRows);
+ }, "test #grid2 computed grid-template-{columns,rows} values");
+ }
+
+ test(function() {
+ assert_equals(getComputedStyle(gridElement).gridAutoColumns, "3fr");
+ assert_equals(getComputedStyle(gridElement).gridAutoRows, "2fr");
+ test_grid2();
+ }, "test computed grid-auto-{columns,rows} values");
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_grid_container_shorthands.html b/layout/style/test/test_grid_container_shorthands.html
new file mode 100644
index 0000000000..1b7a434208
--- /dev/null
+++ b/layout/style/test/test_grid_container_shorthands.html
@@ -0,0 +1,271 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test parsing of grid container shorthands (grid-template, grid)</title>
+ <link rel="author" title="Simon Sapin" href="mailto:simon.sapin@exyr.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+
+<script>
+
+var initial_values = {
+ gridTemplateAreas: "none",
+ gridTemplateRows: "none",
+ gridTemplateColumns: "none",
+ gridAutoFlow: "row",
+ // Computed value for 'auto'
+ gridAutoRows: "auto",
+ gridAutoColumns: "auto",
+};
+
+// For various specified values of the grid-template shorthand,
+// test the computed values of the corresponding longhands.
+var grid_template_test_cases = [
+ {
+ specified: "none",
+ },
+ {
+ specified: "40px / 100px",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "100px",
+ },
+ {
+ specified: "minmax(auto,1fr) / minmax(auto,1fr)",
+ gridTemplateRows: "1fr",
+ gridTemplateColumns: "1fr",
+ },
+ {
+ specified: "[foo] 40px [bar] / [baz] 100px [fizz]",
+ gridTemplateRows: "[foo] 40px [bar]",
+ gridTemplateColumns: "[baz] 100px [fizz]",
+ },
+ {
+ specified: " none/100px",
+ gridTemplateRows: "none",
+ gridTemplateColumns: "100px",
+ },
+ {
+ specified: "40px/none",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "none",
+ },
+ {
+ specified: "40px/repeat(1, 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(1, 20px)",
+ },
+ {
+ specified: "40px/[a]repeat(1, 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(1, 20px)",
+ },
+ {
+ specified: "40px/repeat(1, [a] 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(1, [a] 20px)",
+ },
+ {
+ specified: "40px/[a]repeat(2, [b]20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(2, [b] 20px)",
+ },
+ {
+ specified: "40px/[a]repeat(2, 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(2, 20px)",
+ },
+ {
+ specified: "40px/repeat(2, [a] 20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(2, [a] 20px)",
+ },
+ {
+ specified: "40px/[a]repeat(2, [b]20px)",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(2, [b] 20px)",
+ },
+ {
+ specified: "40px/repeat(2, 20px[a])",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(2, 20px [a])",
+ },
+ {
+ specified: "40px/repeat(2, 20px[a]) [b]",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(2, 20px [a]) [b]",
+ },
+ {
+ specified: "40px/repeat(2, [a] 20px[b]) [c]",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "repeat(2, [a] 20px [b]) [c]",
+ },
+ {
+ specified: "40px/[a] repeat(3, [b c] 20px [d] 100px [e f]) [g]",
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "[a] repeat(3, [b c] 20px [d] 100px [e f]) [g]",
+ },
+ {
+ specified: "'fizz'",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "auto",
+ },
+ {
+ specified: "[bar] 'fizz'",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "[bar] auto",
+ },
+ {
+ specified: "'fizz' / [foo] 40px",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "auto",
+ gridTemplateColumns: "[foo] 40px",
+ },
+ {
+ specified: "[bar] 'fizz' / [foo] 40px",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "[bar] auto",
+ gridTemplateColumns: "[foo] 40px",
+ },
+ {
+ specified: "'fizz' 100px / [foo] 40px",
+ gridTemplateAreas: "\"fizz\"",
+ gridTemplateRows: "100px",
+ gridTemplateColumns: "[foo] 40px",
+ },
+ {
+ specified: "[bar] 'fizz' 100px [buzz] \n [a] '.' 200px [b] / [foo] 40px",
+ gridTemplateAreas: "\"fizz\" \".\"",
+ gridTemplateRows: "[bar] 100px [buzz a] 200px [b]",
+ gridTemplateColumns: "[foo] 40px",
+ },
+ {
+ specified: "subgrid / subgrid",
+ gridTemplateColumns: "subgrid",
+ gridTemplateRows: "subgrid",
+ },
+ {
+ specified: "subgrid [foo] / subgrid",
+ gridTemplateColumns: "subgrid",
+ gridTemplateRows: "subgrid [foo]",
+ },
+ {
+ specified: "subgrid [foo] repeat(3, [] [a b] [c]) / subgrid",
+ gridTemplateColumns: "subgrid",
+ gridTemplateRows: "subgrid [foo] repeat(3, [] [a b] [c])",
+ },
+ {
+ // Test that the number of lines is clamped to kMaxLine = 10000.
+ // https://drafts.csswg.org/css-grid/#overlarge-grids
+ specified: "subgrid [foo] repeat(999999999, [a]) / subgrid",
+ gridTemplateColumns: "subgrid",
+ gridTemplateRows: "subgrid [foo] repeat(10000, [a])",
+ },
+ {
+ specified: "subgrid [bar]/ subgrid [] [foo",
+ gridTemplateColumns: "subgrid [] [foo]",
+ gridTemplateRows: "subgrid [bar]",
+ },
+ {
+ specified: "'fizz' repeat(1, 100px)",
+ },
+ {
+ specified: "'fizz' repeat(auto-fill, 100px)",
+ },
+ {
+ specified: "'fizz' / repeat(1, 100px)",
+ },
+ {
+ specified: "'fizz' / repeat(auto-fill, 100px)",
+ },
+];
+
+grid_test_cases = grid_template_test_cases.concat([
+ {
+ specified: "auto-flow / 0",
+ gridAutoFlow: "row",
+ gridAutoRows: "auto",
+ gridTemplateColumns: "0px",
+ },
+ {
+ specified: "auto-flow dense / 0",
+ gridAutoFlow: "dense",
+ gridAutoRows: "auto",
+ gridTemplateColumns: "0px",
+ },
+ {
+ specified: "auto-flow minmax(auto,1fr) / none",
+ gridAutoFlow: "row",
+ gridAutoRows: "1fr",
+ },
+ {
+ specified: "auto-flow 40px / none",
+ gridAutoFlow: "row",
+ gridAutoRows: "40px",
+ },
+ {
+ specified: "none / auto-flow 40px",
+ gridAutoFlow: "column",
+ gridAutoRows: "auto",
+ gridAutoColumns: "40px",
+ },
+ {
+ specified: "none / auto-flow minmax(auto,1fr)",
+ gridAutoFlow: "column",
+ gridAutoRows: "auto",
+ gridAutoColumns: "1fr",
+ },
+ {
+ specified: "0 / auto-flow dense auto",
+ gridAutoFlow: "column dense",
+ gridAutoRows: "auto",
+ gridAutoColumns: "auto",
+ gridTemplateRows: "0px",
+ },
+ {
+ specified: "dense auto-flow minmax(min-content, 2fr) / 0",
+ gridAutoFlow: "dense",
+ gridAutoRows: "minmax(min-content, 2fr)",
+ gridAutoColumns: "auto",
+ gridTemplateColumns: "0px",
+ },
+ {
+ specified: "auto-flow 40px / 100px",
+ gridAutoFlow: "row",
+ gridAutoRows: "40px",
+ gridAutoColumns: "auto",
+ gridTemplateColumns: "100px",
+ },
+]);
+
+function run_tests(test_cases, shorthand, subproperties) {
+ test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ element.style[shorthand] = test_case.specified;
+ var computed = window.getComputedStyle(element);
+ subproperties.forEach(function(longhand) {
+ assert_equals(
+ computed[longhand],
+ test_case[longhand] || initial_values[longhand],
+ longhand
+ );
+ });
+ }, "test parsing of 'grid-template: " + test_case.specified + "'");
+ });
+}
+
+run_tests(grid_template_test_cases, "gridTemplate", [
+ "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows"]);
+
+run_tests(grid_test_cases, "grid", [
+ "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows",
+ "gridAutoFlow", "gridAutoColumns", "gridAutoRows"]);
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_grid_item_shorthands.html b/layout/style/test/test_grid_item_shorthands.html
new file mode 100644
index 0000000000..a50be6112d
--- /dev/null
+++ b/layout/style/test/test_grid_item_shorthands.html
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test parsing of grid item shorthands (grid-column, grid-row, grid-area)</title>
+ <link rel="author" title="Simon Sapin" href="mailto:simon.sapin@exyr.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+
+<script>
+
+// For various specified values of the grid-column and grid-row shorthands,
+// test the computed values of the corresponding longhands.
+var grid_column_row_test_cases = [
+ {
+ specified: "3 / 4",
+ expected_start: "3",
+ expected_end: "4",
+ },
+ {
+ specified: "foo / span bar",
+ expected_start: "foo",
+ expected_end: "span bar",
+ },
+ // http://dev.w3.org/csswg/css-grid/#placement-shorthands
+ // "When the second value is omitted,
+ // if the first value is a <custom-ident>,
+ // the grid-row-end/grid-column-end longhand
+ // is also set to that <custom-ident>;
+ // otherwise, it is set to auto."
+ {
+ specified: "foo",
+ expected_start: "foo",
+ expected_end: "foo",
+ },
+ {
+ specified: "7",
+ expected_start: "7",
+ expected_end: "auto",
+ },
+ {
+ specified: "foo 7",
+ expected_start: "7 foo",
+ expected_end: "auto",
+ },
+ {
+ specified: "span foo",
+ expected_start: "span foo",
+ expected_end: "auto",
+ },
+ {
+ specified: "foo 7 span",
+ expected_start: "span 7 foo",
+ expected_end: "auto",
+ },
+ {
+ specified: "7 span",
+ expected_start: "span 7",
+ expected_end: "auto",
+ },
+];
+
+// For various specified values of the grid-area shorthand,
+// test the computed values of the corresponding longhands.
+var grid_area_test_cases = [
+ {
+ specified: "10 / 20 / 30 / 40",
+ gridRowStart: "10",
+ gridColumnStart: "20",
+ gridRowEnd: "30",
+ gridColumnEnd: "40",
+ },
+ {
+ specified: "foo / bar / baz",
+ gridRowStart: "foo",
+ gridColumnStart: "bar",
+ gridRowEnd: "baz",
+ gridColumnEnd: "bar",
+ },
+ {
+ specified: "foo / span bar / baz",
+ gridRowStart: "foo",
+ gridColumnStart: "span bar",
+ gridRowEnd: "baz",
+ gridColumnEnd: "auto",
+ },
+ {
+ specified: "foo / bar",
+ gridRowStart: "foo",
+ gridColumnStart: "bar",
+ gridRowEnd: "foo",
+ gridColumnEnd: "bar",
+ },
+ {
+ specified: "foo / 4",
+ gridRowStart: "foo",
+ gridColumnStart: "4",
+ gridRowEnd: "foo",
+ gridColumnEnd: "auto",
+ },
+ {
+ specified: "foo",
+ gridRowStart: "foo",
+ gridColumnStart: "foo",
+ gridRowEnd: "foo",
+ gridColumnEnd: "foo",
+ },
+ {
+ specified: "7",
+ gridRowStart: "7",
+ gridColumnStart: "auto",
+ gridRowEnd: "auto",
+ gridColumnEnd: "auto",
+ },
+]
+
+grid_column_row_test_cases.forEach(function(test_case) {
+ ["Column", "Row"].forEach(function(axis) {
+ var shorthand = "grid" + axis;
+ var start_longhand = "grid" + axis + "Start";
+ var end_longhand = "grid" + axis + "End";
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ element.style[shorthand] = test_case.specified;
+ var computed = window.getComputedStyle(element);
+ assert_equals(computed[start_longhand], test_case.expected_start);
+ assert_equals(computed[end_longhand], test_case.expected_end);
+ }, "test parsing of '" + shorthand + ": " + test_case.specified + "'");
+ });
+});
+
+grid_area_test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ element.style.gridArea = test_case.specified;
+ var computed = window.getComputedStyle(element);
+ [
+ "gridRowStart", "gridColumnStart", "gridRowEnd", "gridColumnEnd"
+ ].forEach(function(longhand) {
+ assert_equals(computed[longhand], test_case[longhand], longhand);
+ });
+ }, "test parsing of 'grid-area: " + test_case.specified + "'");
+});
+
+</script>
+
+</body>
+</html>
diff --git a/layout/style/test/test_grid_shorthand_serialization.html b/layout/style/test/test_grid_shorthand_serialization.html
new file mode 100644
index 0000000000..b2d32b9364
--- /dev/null
+++ b/layout/style/test/test_grid_shorthand_serialization.html
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test serialization of CSS 'grid' shorthand property</title>
+ <link rel="author" title="Simon Sapin" href="mailto:simon.sapin@exyr.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+
+<script>
+
+var initial_values = {
+ gridTemplateAreas: "none",
+ gridTemplateRows: "none",
+ gridTemplateColumns: "none",
+ gridAutoFlow: "row",
+ gridAutoRows: "auto",
+ gridAutoColumns: "auto",
+ gridRowGap: "0px",
+ gridColumnGap: "0px",
+};
+
+// For various specified values of the grid-template subproperties,
+// test the serialization of the shorthand.
+var grid_template_test_cases = [
+ {
+ gridTemplateColumns: "100px",
+ shorthand: "none / 100px",
+ },
+ {
+ gridTemplateRows: "minmax(auto,1fr)",
+ shorthand: "1fr / none",
+ },
+ {
+ gridTemplateColumns: "minmax(auto,1fr)",
+ shorthand: "none / 1fr",
+ },
+ {
+ gridTemplateRows: "40px",
+ shorthand: "40px / none",
+ },
+ {
+ gridTemplateRows: "40px",
+ gridTemplateColumns: "subgrid",
+ shorthand: "40px / subgrid",
+ },
+ {
+ gridTemplateRows: "[foo] 40px [bar]",
+ gridTemplateColumns: "[baz] 100px [fizz]",
+ shorthand: "[foo] 40px [bar] / [baz] 100px [fizz]",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "20px",
+ shorthand: "\"a\" 20px",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "[foo] 20px [bar]",
+ shorthand: "[foo] \"a\" 20px [bar]",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "[foo] repeat(1, 20px) [bar]",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a a\"",
+ gridTemplateColumns: "repeat(2, 100px)",
+ gridTemplateRows: "auto",
+ shorthand: "",
+ },
+ // Combinations of longhands that make the shorthand non-serializable:
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "20px 100px",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\" \"b\"",
+ gridTemplateRows: "20px",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "subgrid",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "subgrid [foo]",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "20px",
+ gridTemplateColumns: "subgrid",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateRows: "repeat(auto-fill, 20px)",
+ shorthand: "",
+ },
+ {
+ gridTemplateAreas: "\"a\"",
+ gridTemplateColumns: "repeat(auto-fill, 100px)",
+ gridTemplateRows: "auto",
+ shorthand: "",
+ },
+];
+
+grid_test_cases = grid_template_test_cases.concat([
+ {
+ gridAutoFlow: "row",
+ shorthand: "none",
+ },
+ {
+ gridAutoRows: "40px",
+ shorthand: "auto-flow 40px / none",
+ },
+ {
+ gridAutoRows: "minmax(auto,1fr)",
+ shorthand: "auto-flow 1fr / none",
+ },
+ {
+ gridAutoFlow: "column dense",
+ gridAutoRows: "minmax(min-content, max-content)",
+ shorthand: "",
+ },
+ {
+ gridAutoFlow: "column dense",
+ gridAutoColumns: "minmax(min-content, max-content)",
+ shorthand: "none / auto-flow dense minmax(min-content, max-content)",
+ },
+ {
+ gridAutoFlow: "column",
+ gridAutoColumns: "minmax(auto,1fr)",
+ shorthand: "none / auto-flow 1fr",
+ },
+ {
+ gridAutoFlow: "row dense",
+ gridAutoColumns: "minmax(min-content, 2fr)",
+ shorthand: "",
+ },
+ {
+ gridAutoFlow: "row dense",
+ gridAutoRows: "minmax(min-content, 2fr)",
+ shorthand: "auto-flow dense minmax(min-content, 2fr) / none",
+ },
+ {
+ gridAutoFlow: "row",
+ gridAutoRows: "40px",
+ gridTemplateColumns: "100px",
+ shorthand: "auto-flow 40px / 100px",
+ },
+ // Test that grid-gap properties don't affect serialization.
+ {
+ gridRowGap: "1px",
+ shorthand: "none",
+ },
+ {
+ gridColumnGap: "1px",
+ shorthand: "none",
+ },
+]);
+
+var grid_important_test_cases = [
+ {
+ "grid-auto-flow": "row",
+ shorthand: "",
+ },
+];
+
+
+function run_tests(test_cases, shorthand, subproperties) {
+ test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ subproperties.forEach(function(longhand) {
+ element.style[longhand] = test_case[longhand] ||
+ initial_values[longhand];
+ });
+ assert_equals(element.style[shorthand], test_case.shorthand);
+ }, "test shorthand serialization " + JSON.stringify(test_case));
+ });
+}
+
+function run_important_tests(test_cases, shorthand, subproperties) {
+ test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ subproperties.forEach(function(longhand) {
+ element.style.setProperty(longhand,
+ test_case[longhand] || initial_values[longhand],
+ "important");
+ });
+ assert_equals(element.style[shorthand], test_case.shorthand);
+ }, "test shorthand serialization " + JSON.stringify(test_case));
+ });
+}
+
+run_tests(grid_template_test_cases, "gridTemplate", [
+ "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows"]);
+
+run_tests(grid_test_cases, "grid", [
+ "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows",
+ "gridAutoFlow", "gridAutoColumns", "gridAutoRows"]);
+
+run_important_tests(grid_important_test_cases, "grid", [
+ "grid-template-areas", "grid-template-columns", "grid-template-rows",
+ "grid-auto-flow", "grid-auto-columns", "grid-auto-rows"]);
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_group_insertRule.html b/layout/style/test/test_group_insertRule.html
new file mode 100644
index 0000000000..85edc2a1a0
--- /dev/null
+++ b/layout/style/test/test_group_insertRule.html
@@ -0,0 +1,243 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>CSS Variables Allowed Syntax</title>
+ <link rel="author" title="L. David Baron" href="https://dbaron.org/">
+ <link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" />
+ <link rel="help" href="http://www.w3.org/TR/css3-conditional/#the-cssgroupingrule-interface">
+ <meta name="assert" content="requirements in definition of insertRule">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+<style id="style">
+@media print {}
+</style>
+<script id="metadata_cache">/*
+{
+ "rule_type": {},
+ "rule_length": {},
+ "insert_import_throws": {},
+ "insert_index_throws1": {},
+ "insert_index_throws2": {},
+ "insert_media_succeed": {},
+ "insert_style_succeed": {},
+ "insert_bad_media_throw": {},
+ "insert_empty_throw": {},
+ "insert_garbage_after_media_throw": {},
+ "insert_garbage_after_style_throw": {},
+ "insert_two_media_throw": {},
+ "insert_style_media_throw": {},
+ "insert_media_style_throw": {},
+ "insert_two_style_throw": {},
+ "insert_retval": {}
+}
+*/</script>
+</head>
+<body onload="run()">
+<div id=log></div>
+<div id="test"></div>
+<script>
+
+ var sheet = document.getElementById("style").sheet;
+
+ var grouping_rule = sheet.cssRules[0];
+
+ test(function() {
+ assert_equals(grouping_rule.type, CSSRule.MEDIA_RULE,
+ "Rule type of @media rule");
+ },
+ "rule_type");
+
+ test(function() {
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Starting cssRules.length of @media rule");
+ },
+ "rule_length");
+
+ test(function() {
+ assert_throws("HIERARCHY_REQUEST_ERR",
+ function() {
+ grouping_rule.insertRule("@import url(foo.css);", 0);
+ },
+ "inserting a disallowed rule should throw HIERARCHY_REQUEST_ERR");
+ },
+ "insert_import_throws");
+
+ test(function() {
+ assert_throws("INDEX_SIZE_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: green }", 1);
+ },
+ "inserting at a bad index throws INDEX_SIZE_ERR");
+ },
+ "insert_index_throws1");
+ test(function() {
+ grouping_rule.insertRule("p { color: green }", 0);
+ assert_equals(grouping_rule.cssRules.length, 1,
+ "Modified cssRules.length of @media rule");
+ grouping_rule.insertRule("p { color: blue }", 1);
+ assert_equals(grouping_rule.cssRules.length, 2,
+ "Modified cssRules.length of @media rule");
+ grouping_rule.insertRule("p { color: aqua }", 1);
+ assert_equals(grouping_rule.cssRules.length, 3,
+ "Modified cssRules.length of @media rule");
+ assert_throws("INDEX_SIZE_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: green }", 4);
+ },
+ "inserting at a bad index throws INDEX_SIZE_ERR");
+ assert_equals(grouping_rule.cssRules.length, 3,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_index_throws2");
+
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ grouping_rule.insertRule("@media print {}", 0);
+ assert_equals(grouping_rule.cssRules.length, 1,
+ "Modified cssRules.length of @media rule");
+ assert_equals(grouping_rule.cssRules[0].type, CSSRule.MEDIA_RULE,
+ "inserting syntactically correct media rule succeeds");
+ },
+ "insert_media_succeed");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ grouping_rule.insertRule("p { color: yellow }", 0);
+ assert_equals(grouping_rule.cssRules.length, 1,
+ "Modified cssRules.length of @media rule");
+ assert_equals(grouping_rule.cssRules[0].type, CSSRule.STYLE_RULE,
+ "inserting syntactically correct style rule succeeds");
+ },
+ "insert_style_succeed");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("@media bad syntax;", 0);
+ },
+ "inserting syntactically invalid rule throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_bad_media_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("", 0);
+ },
+ "inserting empty rule throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_empty_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("@media print {} foo", 0);
+ },
+ "inserting rule with garbage afterwards throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_garbage_after_media_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: yellow } foo", 0);
+ },
+ "inserting rule with garbage afterwards throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_garbage_after_style_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("@media print {} @media print {}", 0);
+ },
+ "inserting multiple rules throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_two_media_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: yellow } @media print {}", 0);
+ },
+ "inserting multiple rules throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_style_media_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("@media print {} p { color: yellow }", 0);
+ },
+ "inserting multiple rules throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_media_style_throw");
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ assert_throws("SYNTAX_ERR",
+ function() {
+ grouping_rule.insertRule("p { color: yellow } p { color: yellow }", 0);
+ },
+ "inserting multiple rules throws syntax error");
+ assert_equals(grouping_rule.cssRules.length, 0,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_two_style_throw");
+
+ test(function() {
+ while (grouping_rule.cssRules.length > 0) {
+ grouping_rule.deleteRule(0);
+ }
+ var res = grouping_rule.insertRule("p { color: green }", 0);
+ assert_equals(res, 0, "return value should be index");
+ assert_equals(grouping_rule.cssRules.length, 1,
+ "Modified cssRules.length of @media rule");
+ res = grouping_rule.insertRule("p { color: green }", 0);
+ assert_equals(res, 0, "return value should be index");
+ assert_equals(grouping_rule.cssRules.length, 2,
+ "Modified cssRules.length of @media rule");
+ res = grouping_rule.insertRule("p { color: green }", 2);
+ assert_equals(res, 2, "return value should be index");
+ assert_equals(grouping_rule.cssRules.length, 3,
+ "Modified cssRules.length of @media rule");
+ },
+ "insert_retval");
+
+
+</script>
+</body>
+</html>
+
diff --git a/layout/style/test/test_hover_on_part.html b/layout/style/test/test_hover_on_part.html
new file mode 100644
index 0000000000..fc39dcc307
--- /dev/null
+++ b/layout/style/test/test_hover_on_part.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<title>Shadow parts are invalidated correctly when only a pseudo-class state to the right of the part matches</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ div {
+ width: 100px;
+ height: 100px;
+ }
+ #host::part(p) {
+ background-color: red;
+ }
+ #host::part(p):hover {
+ background-color: lime;
+ }
+</style>
+<div id="host"></div>
+<div id="random-element-to-force-change"></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+let host = document.getElementById("host");
+host.attachShadow({ mode: "open" }).innerHTML = `
+ <style>
+ div {
+ width: 100px;
+ height: 100px;
+ }
+ </style>
+ <div part=p></div>
+`;
+
+let part = host.shadowRoot.querySelector("div");
+let other = document.getElementById("random-element-to-force-change");
+
+SimpleTest.waitForFocus(function() {
+ synthesizeMouseAtCenter(other, {type: "mousemove"});
+ is(
+ getComputedStyle(part).backgroundColor,
+ "rgb(255, 0, 0)",
+ "Part is red"
+ );
+
+ synthesizeMouseAtCenter(part, {type: "mousemove"});
+ is(
+ getComputedStyle(part).backgroundColor,
+ "rgb(0, 255, 0)",
+ "Part is lime"
+ );
+ SimpleTest.finish();
+});
+</script>
diff --git a/layout/style/test/test_hover_quirk.html b/layout/style/test/test_hover_quirk.html
new file mode 100644
index 0000000000..61e19f2a60
--- /dev/null
+++ b/layout/style/test/test_hover_quirk.html
@@ -0,0 +1,118 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783213
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for the :active and :hover quirk</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style type="text/css">
+ /* Should apply to all elements: */
+ #content :hover:first-of-type {
+ color: rgb(255, 0, 0);
+ }
+ #content :-moz-any(:hover) {
+ text-transform: lowercase;
+ }
+ #content :hover::after {
+ content: "any element";
+ }
+
+ #content :hover:first-of-type .child::after {
+ content: "any child";
+ }
+
+ #content .parent .child::after {
+ content: "wrong" !important;
+ }
+
+ #content .parent:hover .child::after {
+ content: "any child" !important;
+ }
+
+ /* Should apply only to links: */
+ #content :hover {
+ color: rgb(0, 255, 0) !important;
+ text-transform: uppercase !important;
+ }
+ #content :hover .child::after {
+ content: "link child" !important;
+ }
+
+ #dynamic-test {
+ width: 100px;
+ height: 100px;
+ background: green;
+ }
+
+ #dynamic-test > * {
+ width: 100%;
+ height: 100%;
+ background: red;
+ }
+
+ #dynamic-test:hover > * {
+ background: rgb(0, 255, 0);
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript">
+ /** Test for the :active and :hover quirk **/
+ function test(element, isLink) {
+ if (!isLink)
+ var styles = {color: "rgb(255, 0, 0)", textTransform: "lowercase",
+ childContent: '"any child"'};
+ else
+ var styles = {color: "rgb(0, 255, 0)", textTransform: "uppercase",
+ childContent: '"link child"'};
+
+ // Trigger the :hover pseudo-class.
+ synthesizeMouseAtCenter(element, {type: "mousemove"});
+
+ var computedStyle = getComputedStyle(element);
+ is(computedStyle.color, styles.color, "Unexpected color value");
+ is(computedStyle.textTransform, styles.textTransform,
+ "Unexpected text-transform value");
+
+ computedStyle = getComputedStyle(element, "::after");
+ is(computedStyle.content, '"any element"',
+ "Unexpected pseudo-element content");
+
+ computedStyle = getComputedStyle(
+ element.getElementsByClassName("child")[0], "::after");
+ is(computedStyle.content, styles.childContent,
+ "Unexpected pseudo-element content for child");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ test(document.getElementById("span"), false);
+ test(document.getElementById("label"), false);
+ test(document.getElementById("link"), true);
+ test(document.getElementById("div"), false);
+ // Dynamic change test.
+ // Trigger the :hover pseudo-class.
+ synthesizeMouseAtCenter(document.getElementById('dynamic-test'), {type: "mousemove"});
+ is(getComputedStyle(document.getElementById('should-be-green-on-hover')).backgroundColor,
+ "rgb(0, 255, 0)",
+ "Dynamic change should invalidate properly");
+ SimpleTest.finish();
+ });
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783213">Mozilla Bug 783213</a>
+ <p id="display"></p>
+ <div id="dynamic-test">
+ <div id="should-be-green-on-hover"></div>
+ </div>
+ <div id="content">
+ <span id="span">Span<span class="child"></span></span><br>
+ <label id="label">Label<span class="child"></span></label><br>
+ <a id="link" href="#">Link<span class="child"></span></a><br>
+ <div id="div" class="parent">Div <span><span class="child"></span></span></div><br>
+ </div>
+ <pre id="test"></pre>
+</body>
+</html>
diff --git a/layout/style/test/test_html_attribute_computed_values.html b/layout/style/test/test_html_attribute_computed_values.html
new file mode 100644
index 0000000000..74e5b9754a
--- /dev/null
+++ b/layout/style/test/test_html_attribute_computed_values.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="content"></div>
+<pre id="test">
+<script type="application/javascript">
+
+
+var gValues = [
+ {
+ element: "<li type='i'></li>",
+ property: "list-style-type",
+ value: "lower-roman"
+ },
+ {
+ element: "<li type='I'></li>",
+ property: "list-style-type",
+ value: "upper-roman"
+ },
+ {
+ element: "<li type='a'></li>",
+ property: "list-style-type",
+ value: "lower-alpha"
+ },
+ {
+ element: "<li type='A'></li>",
+ property: "list-style-type",
+ value: "upper-alpha"
+ },
+ {
+ element: "<li type='1'></li>",
+ property: "list-style-type",
+ value: "decimal"
+ },
+ {
+ element: "<ol type='i'></ol>",
+ property: "list-style-type",
+ value: "lower-roman"
+ },
+ {
+ element: "<ol type='I'></ol>",
+ property: "list-style-type",
+ value: "upper-roman"
+ },
+ {
+ element: "<ol type='a'></ol>",
+ property: "list-style-type",
+ value: "lower-alpha"
+ },
+ {
+ element: "<ol type='A'></ol>",
+ property: "list-style-type",
+ value: "upper-alpha"
+ },
+ {
+ element: "<ol type='1'></ol>",
+ property: "list-style-type",
+ value: "decimal"
+ },
+];
+
+var content = document.getElementById("content");
+for (var i = 0; i < gValues.length; ++i) {
+ var v = gValues[i];
+
+ content.innerHTML = v.element;
+ is(getComputedStyle(content.firstChild, "").getPropertyValue(v.property),
+ v.value,
+ v.property + " for " + v.element);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_ident_escaping.html b/layout/style/test/test_ident_escaping.html
new file mode 100644
index 0000000000..d727e7f207
--- /dev/null
+++ b/layout/style/test/test_ident_escaping.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=543428
+-->
+<head>
+ <title>Test for Bug 543428</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css" id="sheet">p { color: blue; }</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=543428">Mozilla Bug 543428</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 543428 **/
+
+var sheet = document.getElementById("sheet").sheet;
+var rule = sheet.cssRules[0];
+
+function set_selector_text(selector)
+ // no cssText or selectorText setter implemented yet
+{
+ try {
+ // insertRule might throw on syntax error
+ sheet.insertRule(selector + " { color : green }", 0);
+ sheet.deleteRule(1);
+ } catch(ex) {}
+ rule = sheet.cssRules[0];
+}
+
+is(rule.selectorText, "p", "simple identifier not escaped");
+set_selector_text('\\P');
+is(rule.selectorText, "P", "simple identifier not escaped");
+set_selector_text('\\70');
+is(rule.selectorText, "p", "simple identifier not escaped");
+set_selector_text('font-family_72756');
+is(rule.selectorText, "font-family_72756", "simple identifier not escaped");
+set_selector_text('-font-family_72756');
+is(rule.selectorText, "-font-family_72756", "simple identifier not escaped");
+set_selector_text('-0invalid');
+set_selector_text('0invalid');
+is(rule.selectorText, "-font-family_72756", "setting invalid value ignored");
+set_selector_text('Håkon\\ Lie');
+is(rule.selectorText, "Håkon\\ Lie", "escaping done only where needed");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_img_src_causing_reflow.html b/layout/style/test/test_img_src_causing_reflow.html
new file mode 100644
index 0000000000..e63b39f9fe
--- /dev/null
+++ b/layout/style/test/test_img_src_causing_reflow.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for bug 1787072</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+img {
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+}
+</style>
+<img> <!-- Initially broken -->
+<script>
+add_task(async function() {
+ const utils = SpecialPowers.DOMWindowUtils;
+ const img = document.querySelector("img");
+ img.getBoundingClientRect();
+
+ let origFramesConstructed = utils.framesConstructed;
+ let origFramesReflowed = utils.framesReflowed;
+
+ let error = new Promise(r => img.addEventListener("error", r, { once: true }));
+
+ // Doesn't need to be an actual image.
+ img.src = "/some-valid-url";
+
+ img.getBoundingClientRect();
+ is(origFramesReflowed, utils.framesReflowed, "Shouldn't have reflowed when going broken -> loading");
+ is(origFramesConstructed, utils.framesConstructed, "Shouldn't have reflowed when going broken -> loading");
+
+ await error;
+
+ is(origFramesReflowed, utils.framesReflowed, "Shouldn't have reflowed when going loading -> broken");
+ is(origFramesConstructed, utils.framesConstructed, "Shouldn't have reflowed when going loading -> broken");
+});
+</script>
diff --git a/layout/style/test/test_import_preload.html b/layout/style/test/test_import_preload.html
new file mode 100644
index 0000000000..1c095c9768
--- /dev/null
+++ b/layout/style/test/test_import_preload.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script src="slow_load.sjs"></script>
+<script>
+ let time = Date.now();
+ SimpleTest.waitForExplicitFinish();
+
+ onload = function() {
+ let styleTime = parseInt(getComputedStyle(document.documentElement).zIndex, 10);
+ isnot(styleTime, 0, "Should apply the @import sheet");
+ ok(!isNaN(styleTime), "Should apply the @import sheet (and not be nan)");
+ // This is technically a bit racy... Also see the comment in slow_load.sjs about the clamping.
+ time = time % (Math.pow(2, 31) - 1);
+ ok(styleTime < time, "Should try to fetch the import before running the script: " + styleTime + " vs. " + time);
+ SimpleTest.finish();
+ }
+</script>
+<style>
+ @import url(slow_load.sjs?css)
+</style>
diff --git a/layout/style/test/test_inherit_computation.html b/layout/style/test/test_inherit_computation.html
new file mode 100644
index 0000000000..9ac056518c
--- /dev/null
+++ b/layout/style/test/test_inherit_computation.html
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for computation of CSS 'inherit' on all properties and 'unset' on inherited properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"><span id="fparent"><span id="fchild"></span></span></p>
+<div id="content" style="display: none">
+
+<div id="testnode"><span id="nparent"><span id="nchild"></span></span></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for computation of CSS 'inherit' on all properties and 'unset' on
+ inherited properties **/
+
+var gDisplayTree = document.getElementById("display");
+// elements without a frame
+var gNParent = document.getElementById("nparent");
+var gNChild = document.getElementById("nchild");
+// elements with a frame
+var gFParent = document.getElementById("fparent");
+var gFChild = document.getElementById("fchild");
+
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gChildRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild, #fchild {}", gStyleSheet.cssRules.length)];
+var gChildRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild, #fchild {}", gStyleSheet.cssRules.length)];
+var gChildRule3 = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild.allother, #fchild.allother {}", gStyleSheet.cssRules.length)];
+var gChildRuleTop = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild, #nchild.allother, #fchild, #fchild.allother {}", gStyleSheet.cssRules.length)];
+var gParentRuleTop = gStyleSheet.cssRules[gStyleSheet.insertRule("#nparent, #fparent {}", gStyleSheet.cssRules.length)];
+
+function get_computed_value_node(node, property)
+{
+ var cs = getComputedStyle(node, "");
+ return get_computed_value(cs, property);
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ var keywords = ["inherit"];
+ if (info.inherited)
+ keywords.push("unset");
+
+ keywords.forEach(function(keyword) {
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gParentRuleTop.style.setProperty(prereq, prereqs[prereq], "");
+ gChildRuleTop.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+
+ if (info.inherited) {
+ gParentRuleTop.style.setProperty(property, info.initial_values[0], "");
+ var initial_computed_n = get_computed_value_node(gNChild, property);
+ var initial_computed_f = get_computed_value_node(gFChild, property);
+ gChildRule1.style.setProperty(property, info.other_values[0], "");
+ var other_computed_n = get_computed_value_node(gNChild, property);
+ var other_computed_f = get_computed_value_node(gFChild, property);
+ isnot(other_computed_n, initial_computed_n,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ isnot(other_computed_f, initial_computed_f,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ gChildRule3.style.setProperty(property, keyword, "");
+ gFChild.className="allother";
+ gNChild.className="allother";
+ var inherit_initial_computed_n = get_computed_value_node(gNChild, property);
+ var inherit_initial_computed_f = get_computed_value_node(gFChild, property);
+ is(inherit_initial_computed_n, initial_computed_n,
+ keyword + " should cause inheritance of initial value for '" +
+ property + "'");
+ is(inherit_initial_computed_f, initial_computed_f,
+ keyword + " should cause inheritance of initial value for '" +
+ property + "'");
+ gParentRuleTop.style.setProperty(property, info.other_values[0], "");
+ var inherit_other_computed_n = get_computed_value_node(gNChild, property);
+ var inherit_other_computed_f = get_computed_value_node(gFChild, property);
+ is(inherit_other_computed_n, other_computed_n,
+ keyword + " should cause inheritance of other value for '" +
+ property + "'");
+ is(inherit_other_computed_f, other_computed_f,
+ keyword + " should cause inheritance of other value for '" +
+ property + "'");
+ gParentRuleTop.style.removeProperty(property);
+ gChildRule1.style.removeProperty(property);
+ gChildRule3.style.setProperty(property, info.other_values[0], "");
+ gFChild.className="";
+ gNChild.className="";
+ } else {
+ gParentRuleTop.style.setProperty(property, info.other_values[0], "");
+ var initial_computed_n = get_computed_value_node(gNChild, property);
+ var initial_computed_f = get_computed_value_node(gFChild, property);
+ var other_computed_n = get_computed_value_node(gNParent, property);
+ var other_computed_f = get_computed_value_node(gFParent, property);
+ isnot(other_computed_n, initial_computed_n,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ isnot(other_computed_f, initial_computed_f,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ gChildRule2.style.setProperty(property, keyword, "");
+ var inherit_other_computed_n = get_computed_value_node(gNChild, property);
+ var inherit_other_computed_f = get_computed_value_node(gFChild, property);
+ is(inherit_other_computed_n, other_computed_n,
+ keyword + " should cause inheritance of other value for '" +
+ property + "'");
+ is(inherit_other_computed_f, other_computed_f,
+ keyword + " should cause inheritance of other value for '" +
+ property + "'");
+ gParentRuleTop.style.removeProperty(property);
+ gChildRule1.style.setProperty(property, info.other_values[0], "");
+ var inherit_initial_computed_n = get_computed_value_node(gNChild, property);
+ var inherit_initial_computed_f = get_computed_value_node(gFChild, property);
+ is(inherit_initial_computed_n, initial_computed_n,
+ keyword + " should cause inheritance of initial value for '" +
+ property + "'");
+ // For width and inline-size, getComputedStyle returns used value
+ // when the element is displayed. Their initial value "auto" makes
+ // the element fill available space of the parent, so it doesn't
+ // make sense to compare it with the value we get before.
+ if (property != "width" && property != "inline-size") {
+ is(inherit_initial_computed_f, initial_computed_f,
+ keyword + " should cause inheritance of initial value for '" +
+ property + "'");
+ }
+ gParentRuleTop.style.removeProperty(property);
+ gChildRule1.style.removeProperty(property);
+ gChildRule2.style.removeProperty(property);
+ }
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gParentRuleTop.style.removeProperty(prereq);
+ gChildRuleTop.style.removeProperty(prereq);
+ }
+ }
+
+ // FIXME -moz-binding value makes gElementF and its parent loses
+ // their frame. Force it to get recreated after each property.
+ // See bug 1331903.
+ gDisplayTree.style.display = "none";
+ get_computed_value(getComputedStyle(gFChild, ""), "width");
+ gDisplayTree.style.display = "";
+ get_computed_value(getComputedStyle(gFChild, ""), "width");
+ });
+}
+
+for (var prop in gCSSProperties) {
+ // Skip zoom because it affects other props.
+ if (prop == "zoom") {
+ continue;
+ }
+ var info = gCSSProperties[prop];
+ gChildRule3.style.setProperty(prop, info.other_values[0], "");
+}
+
+for (var prop in gCSSProperties)
+ test_property(prop);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_inherit_storage.html b/layout/style/test/test_inherit_storage.html
new file mode 100644
index 0000000000..a9587d34d9
--- /dev/null
+++ b/layout/style/test/test_inherit_storage.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375363
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS 'inherit' on all properties and 'unset' on inherited properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=375363">Mozilla Bug 375363</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for parsing, storage, and serialization of CSS 'inherit' on all
+ properties and 'unset' on inherited properties **/
+
+var gDeclaration = document.getElementById("testnode").style;
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ var keywords = ["inherit"];
+ if (info.inherited)
+ keywords.push("unset");
+
+ keywords.forEach(function(keyword) {
+ function check_initial(sproperty) {
+ var sinfo = gCSSProperties[sproperty];
+ var val = gDeclaration.getPropertyValue(sproperty);
+ is(val, "", "value of '" + sproperty + "' before we do anything");
+ is(val, gDeclaration[sinfo.domProp],
+ "consistency between decl.getPropertyValue('" + sproperty + "') and decl." + sinfo.domProp);
+ }
+ check_initial(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_initial(info.subproperties[idx]);
+
+ gDeclaration.setProperty(property, keyword, "");
+
+ function check_set(sproperty) {
+ var sinfo = gCSSProperties[sproperty];
+ val = gDeclaration.getPropertyValue(sproperty);
+ is(val, keyword,
+ keyword + " reported back for property '" + sproperty + "'");
+ is(val, gDeclaration[sinfo.domProp],
+ "consistency between decl.getPropertyValue('" + sproperty +
+ "') and decl." + sinfo.domProp);
+ }
+ check_set(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_set(info.subproperties[idx]);
+
+ // We don't care particularly about the whitespace or the placement of
+ // semicolons, but for simplicity we'll test the current behavior.
+ if ("alias_for" in info) {
+ is(gDeclaration.cssText, info.alias_for + ": " + keyword + ";",
+ "declaration should serialize to exactly what went in (for " + keyword + ")");
+ } else if (info.type == CSS_TYPE_LEGACY_SHORTHAND) {
+ // We can't assert anything more meaningful here, really.
+ is(property, "zoom", "Zoom is a bit special because it never " +
+ "serializes as-is, we always serialize the longhands, " +
+ "but it doesn't just map to a single property " +
+ "(and thus we can't use the 'alias_for' mechanism)");
+ } else {
+ is(gDeclaration.cssText, property + ": " + keyword + ";",
+ "declaration should serialize to exactly what went in (for " + keyword + ")");
+ }
+
+ gDeclaration.removeProperty(property);
+
+ function check_final(sproperty) {
+ var sinfo = gCSSProperties[sproperty];
+ var val = gDeclaration.getPropertyValue(sproperty);
+ is(val, "", "value of '" + sproperty + "' after removal of value");
+ is(val, gDeclaration[sinfo.domProp],
+ "consistency between decl.getPropertyValue('" + sproperty + "') and decl." + sinfo.domProp);
+ }
+ check_final(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_final(info.subproperties[idx]);
+
+ // can all properties be removed from the style?
+ function test_remove_all_properties(propName, value) {
+ var i, p = [];
+ for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]);
+ for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]);
+ var errstr = "when setting property " + propName + " to " + value;
+ is(gDeclaration.length, 0, "unremovable properties " + errstr);
+ is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr);
+ }
+
+ // sanity check shorthands to make sure disabled props aren't exposed
+ if (info.type != CSS_TYPE_LONGHAND) {
+ gDeclaration.setProperty(property, keyword, "");
+ test_remove_all_properties(property, keyword);
+ gDeclaration.removeProperty(property);
+ }
+ });
+}
+
+for (var prop in gCSSProperties)
+ test_property(prop);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_initial_computation.html b/layout/style/test/test_initial_computation.html
new file mode 100644
index 0000000000..c8a36af227
--- /dev/null
+++ b/layout/style/test/test_initial_computation.html
@@ -0,0 +1,159 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for computation of CSS 'initial' on all properties and 'unset' on reset properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <style type="text/css">
+ /* For 'width', 'height', etc., need a constant size container. */
+ #display { width: 500px; height: 200px }
+ </style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var load_count = 0;
+ function load_done() {
+ if (++load_count == 3)
+ run_tests();
+ }
+ </script>
+</head>
+<body>
+<p id="display"><span><span id="elementf"></span></span>
+<iframe id="unstyledn" src="unstyled.xml" height="10" width="10" onload="load_done()"></iframe>
+<iframe id="unstyledf" src="unstyled-frame.xml" height="10" width="10" onload="load_done()"></iframe>
+</p>
+<div id="content" style="display: none">
+
+<div><span id="elementn"></span></div>
+
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for computation of CSS 'initial' on all properties and 'unset' on
+ reset properties **/
+
+var gBrokenInitial = {
+};
+
+function xfail_initial(property) {
+ return property in gBrokenInitial;
+}
+
+var gDisplayTree = document.getElementById("display");
+var gElementN = document.getElementById("elementn");
+var gElementF = document.getElementById("elementf");
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)];
+var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)];
+
+var gInitialValuesN;
+var gInitialValuesF;
+var gInitialPrereqsRuleN;
+var gInitialPrereqsRuleF;
+
+function setup_initial_values(id, ivalprop, prereqprop) {
+ var iframe = document.getElementById(id);
+ window[ivalprop] = iframe.contentWindow.getComputedStyle(
+ iframe.contentDocument.documentElement.firstChild);
+ var sheet = iframe.contentDocument.styleSheets[0];
+ // For 'width', 'height', etc., need a constant size container.
+ sheet.insertRule(":root { height: 200px; width: 500px }", sheet.cssRules.length);
+
+ window[prereqprop] = sheet.cssRules[sheet.insertRule(":root > * {}", sheet.cssRules.length)];
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ var keywords = ["initial"];
+ if (!info.inherited)
+ keywords.push("unset");
+
+ keywords.forEach(function(keyword) {
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gRule1.style.setProperty(prereq, prereqs[prereq], "");
+ gInitialPrereqsRuleN.style.setProperty(prereq, prereqs[prereq], "");
+ gInitialPrereqsRuleF.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+ if (info.inherited) {
+ gElementN.parentNode.style.setProperty(property, info.other_values[0], "");
+ gElementF.parentNode.style.setProperty(property, info.other_values[0], "");
+ }
+
+ var initial_computed_n = get_computed_value(gInitialValuesN, property);
+ var initial_computed_f = get_computed_value(gInitialValuesF, property);
+ gRule1.style.setProperty(property, info.other_values[0], "");
+ var other_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property);
+ var other_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property);
+ isnot(other_computed_n, initial_computed_n,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ isnot(other_computed_f, initial_computed_f,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ // It used to be important for values that are supposed to compute to the
+ // initial value (given the design of the old rule tree, nsRuleNode) that
+ // we're modifying the most specific rule that matches the element, and
+ // that we've already requested style while that rule was empty. This
+ // means we'd have a cached aStartStruct from the parent in the rule
+ // tree (caching the "other" value), so we'd make sure we don't get the
+ // initial value from the luck of default-initialization. This means
+ // that it would've been important that we set the prereqs on
+ // gRule1.style rather than on gElement.style.
+ //
+ // However, the rule tree no longer stores cached structs, and we only
+ // temporarily cache reset structs during a single restyle. So the
+ // particular failure mode this was designed to test for isn't as
+ // likely to eventuate.
+ gRule2.style.setProperty(property, keyword, "");
+ var initial_val_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property);
+ var initial_val_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property);
+ (xfail_initial(property) ? todo_is : is)(
+ initial_val_computed_n, initial_computed_n,
+ keyword + " should cause initial value for '" + property + "'");
+ (xfail_initial(property) ? todo_is : is)(
+ initial_val_computed_f, initial_computed_f,
+ keyword + " should cause initial value for '" + property + "'");
+ gRule1.style.removeProperty(property);
+ gRule2.style.removeProperty(property);
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gRule1.style.removeProperty(prereq);
+ gInitialPrereqsRuleN.style.removeProperty(prereq);
+ gInitialPrereqsRuleF.style.removeProperty(prereq);
+ }
+ }
+ if (info.inherited) {
+ gElementN.parentNode.style.removeProperty(property);
+ gElementF.parentNode.style.removeProperty(property);
+ }
+ });
+}
+
+function run_tests() {
+ setup_initial_values("unstyledn", "gInitialValuesN", "gInitialPrereqsRuleN");
+ setup_initial_values("unstyledf", "gInitialValuesF", "gInitialPrereqsRuleF");
+ for (var prop in gCSSProperties)
+ test_property(prop);
+ SimpleTest.finish();
+}
+
+load_done();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_initial_storage.html b/layout/style/test/test_initial_storage.html
new file mode 100644
index 0000000000..a1a081c5a6
--- /dev/null
+++ b/layout/style/test/test_initial_storage.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375363
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS 'initial' on all properties and 'unset' on reset properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=375363">Mozilla Bug 375363</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for parsing, storage, and serialization of CSS 'initial' on all
+ properties and 'unset' on reset properties **/
+
+var gDeclaration = document.getElementById("testnode").style;
+
+/**
+ * Checks that the passed-in property-value (returned by getPropertyValue) is
+ * consistent with the DOM accessors that we know about for the given sproperty.
+ */
+function check_consistency(sproperty, valFromGetPropertyValue, messagePrefix)
+{
+ var sinfo = gCSSProperties[sproperty];
+ is(valFromGetPropertyValue, gDeclaration[sinfo.domProp],
+ `(${messagePrefix}) consistency between ` +
+ `decl.getPropertyValue(${sproperty}) and decl.${sinfo.domProp}`);
+
+ if (sinfo.domProp.startsWith("webkit")) {
+ // For webkit-prefixed DOM accessors, test with lowercase and uppercase
+ // first letter.
+ var uppercaseDomProp = "W" + sinfo.domProp.substring(1);
+ is(valFromGetPropertyValue, gDeclaration[uppercaseDomProp],
+ `(${messagePrefix}) consistency between ` +
+ `decl.getPropertyValue(${sproperty}) and decl.${uppercaseDomProp}`);
+ }
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ var keywords = ["initial"];
+ if (!info.inherited)
+ keywords.push("unset");
+
+ keywords.forEach(function(keyword) {
+ function check_initial(sproperty) {
+ var val = gDeclaration.getPropertyValue(sproperty);
+ is(val, "", "value of '" + sproperty + "' before we do anything");
+ check_consistency(sproperty, val, "initial");
+ }
+ check_initial(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_initial(info.subproperties[idx]);
+
+ gDeclaration.setProperty(property, keyword, "");
+
+ function check_set(sproperty) {
+ val = gDeclaration.getPropertyValue(sproperty);
+ is(val, keyword,
+ keyword + " reported back for property '" + sproperty + "'");
+ check_consistency(sproperty, val, "set");
+ }
+ check_set(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_set(info.subproperties[idx]);
+
+ // We don't care particularly about the whitespace or the placement of
+ // semicolons, but for simplicity we'll test the current behavior.
+ if ("alias_for" in info) {
+ is(gDeclaration.cssText, info.alias_for + ": " + keyword + ";",
+ "declaration should serialize to exactly what went in (for " + keyword + ")");
+ } else if (info.type == CSS_TYPE_LEGACY_SHORTHAND) {
+ // We can't assert anything more meaningful here, really.
+ is(property, "zoom", "Zoom is a bit special because it never " +
+ "serializes as-is, we always serialize the longhands, " +
+ "but it doesn't just map to a single property " +
+ "(and thus we can't use the 'alias_for' mechanism)");
+ } else {
+ is(gDeclaration.cssText, property + ": " + keyword + ";",
+ "declaration should serialize to exactly what went in (for " + keyword + ")");
+ }
+
+ gDeclaration.removeProperty(property);
+
+ function check_final(sproperty) {
+ var val = gDeclaration.getPropertyValue(sproperty);
+ is(val, "", "value of '" + sproperty + "' after removal of value");
+ check_consistency(sproperty, val, "final");
+ }
+ check_final(property);
+ if ("subproperties" in info)
+ for (var idx in info.subproperties)
+ check_final(info.subproperties[idx]);
+
+ // can all properties be removed from the style?
+ function test_remove_all_properties(propName, value) {
+ var i, p = [];
+ for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]);
+ for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]);
+ var errstr = "when setting property " + propName + " to " + value;
+ is(gDeclaration.length, 0, "unremovable properties " + errstr);
+ is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr);
+ }
+
+ // sanity check shorthands to make sure disabled props aren't exposed
+ if (info.type != CSS_TYPE_LONGHAND) {
+ gDeclaration.setProperty(property, keyword, "");
+ test_remove_all_properties(property, keyword);
+ gDeclaration.removeProperty(property);
+ }
+ });
+}
+
+for (var prop in gCSSProperties)
+ test_property(prop);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_invalidation_basic.html b/layout/style/test/test_invalidation_basic.html
new file mode 100644
index 0000000000..b5a6928405
--- /dev/null
+++ b/layout/style/test/test_invalidation_basic.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1368240: We only invalidate style as little as needed
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+.foo .bar {
+ color: red;
+}
+#container ~ .bar {
+ color: green;
+}
+</style>
+<div id="container">
+ <div></div>
+ <div></div>
+ <div></div>
+</div>
+<div></div>
+<div></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+
+// TODO(emilio): Add an API to get the ComputedStyles we've recreated, to make
+// more elaborated tests.
+document.documentElement.offsetTop;
+const initialRestyleGeneration = utils.restyleGeneration;
+
+// Normally we'd restyle the whole subtree in this case, but we should go down
+// the tree invalidating as little as needed (nothing in this case).
+container.classList.add("foo");
+document.documentElement.offsetTop;
+is(utils.restyleGeneration, initialRestyleGeneration,
+ "Shouldn't have restyled any descendant");
+
+container.setAttribute("id", "");
+document.documentElement.offsetTop;
+is(utils.restyleGeneration, initialRestyleGeneration,
+ "Shouldn't have restyled any sibling");
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_keyframes_rules.html b/layout/style/test/test_keyframes_rules.html
new file mode 100644
index 0000000000..027a406009
--- /dev/null
+++ b/layout/style/test/test_keyframes_rules.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=577974
+-->
+<head>
+ <title>Test for Bug 577974</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+
+ @keyframes bounce {
+ from {
+ margin-left: 0
+ }
+
+ /*
+ * These rules should get dropped due to syntax errors. The script
+ * below tests that the 25% rule following them is at cssRules[1].
+ */
+ from, { margin-left: 0 }
+ from , { margin-left: 0 }
+ , from { margin-left: 0 }
+ ,from { margin-left: 0 }
+ from from { margin-left: 0 }
+ from, 1 { margin-left: 0 }
+ 1 { margin-left: 0 }
+ 1, from { margin-left: 0 }
+ from, 1.0 { margin-left: 0 }
+ 1.0 { margin-left: 0 }
+ 1.0, from { margin-left: 0 }
+
+ 25% {
+ margin-left: 25px;
+ }
+
+ 75%, 85% {
+ margin-left: 90px;
+ }
+
+ 100% {
+ margin-left: 100px;
+ }
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=577974">Mozilla Bug 577974</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 577974 **/
+
+var sheet = document.getElementById("style").sheet;
+
+var bounce = sheet.cssRules[0];
+is(bounce.type, CSSRule.KEYFRAMES_RULE, "bounce.type");
+is(bounce.type, 7, "bounce.type");
+is(bounce.name, "bounce", "bounce.name");
+bounce.name = "bouncier";
+is(bounce.name, "bouncier", "setting bounce.name");
+
+is(bounce.cssRules[0].type, CSSRule.KEYFRAME_RULE, "keyframe rule type");
+is(bounce.cssRules[0].type, 8, "keyframe rule type");
+is(bounce.cssRules[0].keyText, "0%", "keyframe rule keyText");
+is(bounce.cssRules[1].keyText, "25%", "keyframe rule keyText");
+is(bounce.cssRules[2].keyText, "75%, 85%", "keyframe rule keyText");
+is(bounce.cssRules[3].keyText, "100%", "keyframe rule keyText");
+is(bounce.cssRules[0].style.marginLeft, "0px", "keyframe rule style");
+is(bounce.cssRules[1].style.marginLeft, "25px", "keyframe rule style");
+
+is(bounce.cssRules[0].cssText, "0% { margin-left: 0px; }");
+is(bounce.cssText, "@keyframes bouncier {\n" +
+ "0% { margin-left: 0px; }\n" +
+ "25% { margin-left: 25px; }\n" +
+ "75%, 85% { margin-left: 90px; }\n" +
+ "100% { margin-left: 100px; }\n" +
+ "}");
+
+bounce.cssRules[1].keyText = "from, 1"; // syntax error
+bounce.cssRules[1].keyText = "from, x"; // syntax error
+bounce.cssRules[1].keyText = "from,"; // syntax error
+bounce.cssRules[1].keyText = "from x"; // syntax error
+bounce.cssRules[1].keyText = "x"; // syntax error
+is(bounce.cssRules[1].keyText, "25%", "keyframe rule keyText parsing");
+bounce.cssRules[1].keyText = "from, 10%";
+is(bounce.cssRules[1].keyText, "0%, 10%", "keyframe rule keyText parsing");
+bounce.cssRules[1].keyText = "from, 0%";
+is(bounce.cssRules[1].keyText, "0%, 0%", "keyframe rule keyText parsing");
+bounce.cssRules[1].keyText = "from, from, from";
+is(bounce.cssRules[1].keyText, "0%, 0%, 0%", "keyframe rule keyText parsing");
+
+is(bounce.findRule("75%"), null, "findRule should match all keys");
+is(bounce.findRule("85%, 75%"), null,
+ "findRule should match all keys in order");
+is(bounce.findRule("75%,85%"), bounce.cssRules[2],
+ "findRule should match all keys in order, parsed");
+is(bounce.findRule("to"), bounce.cssRules[3],
+ "findRule should match keys as parsed");
+is(bounce.findRule("100%"), bounce.cssRules[3],
+ "findRule should match keys as parsed");
+is(bounce.findRule("100%, 100%"), null,
+ "findRule should match key list");
+is(bounce.findRule("100%,"), null,
+ "findRule should fail when given bad selector");
+is(bounce.findRule(",100%"), null,
+ "findRule should fail when given bad selector");
+is(bounce.cssRules.length, 4, "length of css rules");
+bounce.deleteRule("85%");
+is(bounce.cssRules.length, 4, "deleteRule should match all keys");
+bounce.deleteRule("85%, 75%");
+is(bounce.cssRules.length, 4, "deleteRule should match key list");
+bounce.deleteRule("75% ,85%");
+is(bounce.cssRules.length, 3, "deleteRule should match keys in order, parsed");
+bounce.deleteRule("0%");
+is(bounce.cssRules.length, 2, "deleteRule should match keys in order, parsed");
+bounce.appendRule("from { color: blue }");
+is(bounce.cssRules.length, 3, "appendRule should append");
+is(bounce.cssRules[2].keyText, "0%", "appendRule should append");
+bounce.appendRule("from { color: blue }");
+is(bounce.cssRules.length, 4, "appendRule should append");
+is(bounce.cssRules[3].keyText, "0%", "appendRule should append");
+bounce.appendRule("from { color: blue } to { color: green }");
+is(bounce.cssRules.length, 4, "appendRule should ignore garbage at end");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_keyframes_vendor_prefix.html b/layout/style/test/test_keyframes_vendor_prefix.html
new file mode 100644
index 0000000000..4463bd259a
--- /dev/null
+++ b/layout/style/test/test_keyframes_vendor_prefix.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>
+Test for interaction between prefixed and non-prefixed @keyframes rules with
+the same name
+</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<div id='log'></div>
+<script>
+/**
+ * Appends a style element to the document head.
+ *
+ * @param t The testharness.js Test object. If provided, this will be used
+ * to register a cleanup callback to remove the style element
+ * when the test finishes.
+ *
+ * @param rules A dictionary object with selector names and rules to set on
+ * the style sheet.
+ */
+function addStyle(t, rules) {
+ var extraStyle = document.createElement('style');
+ document.head.appendChild(extraStyle);
+ if (rules) {
+ var sheet = extraStyle.sheet;
+ for (var selector in rules) {
+ sheet.insertRule(selector + '{' + rules[selector] + '}',
+ sheet.cssRules.length);
+ }
+ }
+
+ if (t && typeof t.add_cleanup === 'function') {
+ t.add_cleanup(function() {
+ extraStyle.remove();
+ });
+ }
+}
+
+/**
+ * Appends a div to the document body.
+ *
+ * @param t The testharness.js Test object. If provided, this will be used
+ * to register a cleanup callback to remove the div when the test
+ * finishes.
+ *
+ * @param attrs A dictionary object with attribute names and values to set on
+ * the div.
+ */
+function addDiv(t, attrs) {
+ var div = document.createElement('div');
+ if (attrs) {
+ for (var attrName in attrs) {
+ div.setAttribute(attrName, attrs[attrName]);
+ }
+ }
+ document.body.appendChild(div);
+ if (t && typeof t.add_cleanup === 'function') {
+ t.add_cleanup(function() {
+ div.remove();
+ });
+ }
+ return div;
+}
+
+test(function(t) {
+ addStyle(t,
+ { '@-webkit-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-webkit- prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-moz-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-moz- prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-WEBKIT-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-WEBKIT- prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-MOZ-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-MOZ- prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-webkit-KEYFRAMES anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-webkit- prefix KEYFRAMES');
+
+test(function(t) {
+ addStyle(t,
+ { '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }',
+ '@-webkit-keyframes anim': 'from,to { color: rgb(255, 0, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-webkit-keyframes should not override earlier non-prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }',
+ '@-moz-keyframes anim': 'from,to { color: rgb(255, 0, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, '-moz-keyframes should not override earlier non-prefix keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-moz-keyframes anim': 'from,to { color: rgb(255, 0, 0); }',
+ '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, 'non-prefix keyframes should override earlier -moz-keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-webkit-keyframes anim': 'from,to { color: rgb(255, 0, 0); }',
+ '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, 'non-prefix keyframes should override earlier -webkit-keyframes');
+
+test(function(t) {
+ addStyle(t,
+ { '@-webkit-keyframes anim': 'from,to { color: rgb(255, 0, 0); }',
+ '@-moz-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+
+ addStyle(t,
+ { '@-moz-keyframes anim2': 'from,to { color: rgb(255, 0, 0); }',
+ '@-webkit-keyframes anim2': 'from,to { color: rgb(0, 255, 0); }' });
+
+ var div = addDiv(t, { style: 'animation: anim2 100s' });
+
+ assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)');
+}, 'last prefixed keyframes should override earlier prefixed keyframes');
+</script>
diff --git a/layout/style/test/test_load_events_on_stylesheets.html b/layout/style/test/test_load_events_on_stylesheets.html
new file mode 100644
index 0000000000..7344e27b22
--- /dev/null
+++ b/layout/style/test/test_load_events_on_stylesheets.html
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=185236
+-->
+<head>
+ <title>Test for Bug 185236</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script>
+ var pendingEventCounter = 0;
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ is(pendingEventCounter, 0,
+ "How did onload for the page fire before onload for all the stylesheets?");
+ SimpleTest.finish();
+ });
+ // Count the link we're about to parse
+ pendingEventCounter = 1;
+ </script>
+ <link rel="stylesheet" href="data:text/css,*{}"
+ onload="--pendingEventCounter;
+ ok(true, 'Load event firing on basic stylesheet')"
+ onerror="--pendingEventCounter;
+ ok(false, 'Error event firing on basic stylesheet')">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=185236">Mozilla Bug 185236</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 185236 **/
+
+function checkSheetComplete(sheet, length) {
+ try {
+ is(sheet.cssRules.length, length, `Should be loaded with ${length} rule(s)`);
+ } catch (e) {
+ ok(false, "Sheet has not been loaded completely");
+ }
+}
+
+
+// There should be usually 1 pending event but the load event might have fired
+// if the parser yields between the link and starting the script execution.
+const pendingEventCounterAtStart = pendingEventCounter;
+
+ok(pendingEventCounter <= 1, `There should be at most one pending event, got ${pendingEventCounterAtStart}`);
+
+is(document.styleSheets.length, 2, "Should have two stylesheets");
+checkSheetComplete(document.styleSheets[1], 1);
+
+// Test sheet that will already be complete when we write it out
+++pendingEventCounter;
+document.write('<link rel="stylesheet" href="data:text/css,*{}"\
+ onload="--pendingEventCounter;\
+ ok(true, \'Load event firing on basic stylesheet\')"\
+ onerror="--pendingEventCounter;\
+ ok(false, \'Error event firing on basic stylesheet\')">');
+
+// Make sure we have that second stylesheet
+is(document.styleSheets.length, 3, "Should have three stylesheets");
+
+// Make sure that the second stylesheet is all loaded
+// If we ever switch away from sync loading of already-complete sheets, this
+// test will need adjusting
+checkSheetComplete(document.styleSheets[2], 1);
+
+// Make sure the load event for that stylesheet has not fired yet
+is(pendingEventCounter, pendingEventCounterAtStart + 1, "After first document write");
+++pendingEventCounter;
+
+document.write('<style\
+ onload="--pendingEventCounter;\
+ ok(true, \'Load event firing on inline stylesheet\')"\
+ onerror="--pendingEventCounter;\
+ ok(false, \'Error event firing on inline stylesheet\')"></style>');
+
+// Make sure the load event for that second stylesheet has not fired yet
+is(pendingEventCounter, pendingEventCounterAtStart + 2, "after second document write");
+++pendingEventCounter;
+
+document.write('<link rel="stylesheet" href="http://www.example.com"\
+ onload="--pendingEventCounter;\
+ ok(false, \'Load event firing on broken stylesheet 1\')"\
+ onerror="--pendingEventCounter;\
+ ok(true, \'Error event firing on broken stylesheet 1\')">');
+++pendingEventCounter;
+
+var link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "http://www.example.com";
+link.onload = function() { --pendingEventCounter;
+ ok(false, 'Load event firing on broken stylesheet 2');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet 2');
+}
+document.body.appendChild(link);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "data:text/css,*{}";
+link.onload = function() { --pendingEventCounter;
+ ok(true, 'Load event firing on external stylesheet');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(false, 'Error event firing on external stylesheet');
+}
+document.body.appendChild(link);
+
+// If we ever switch away from sync loading of already-complete sheets, this
+// test will need adjusting
+checkSheetComplete(link.sheet, 1);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "data:text/css,@import url('data:text/css,*{}')";
+link.onload = function() { --pendingEventCounter;
+ ok(true, 'Load event firing on external stylesheet');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(false, 'Error event firing on external stylesheet');
+}
+document.body.appendChild(link);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = "data:text/css,@import url('http://www.example.com')";
+link.onload = function() { --pendingEventCounter;
+ ok(false, 'Load event firing on broken stylesheet 3');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet 3');
+}
+document.body.appendChild(link);
+
+function absoluteURL(relativeURL) {
+ return new URL(relativeURL, location.href).href;
+}
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = `data:text/css,@import url('http://www.example.com'); @import url(${absoluteURL('slow_ok_sheet.sjs')});`;
+link.onload = function() { --pendingEventCounter;
+ ok(false, 'Load event firing on broken stylesheet 4');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet 4');
+}
+document.body.appendChild(link);
+
+++pendingEventCounter;
+link = document.createElement("link");
+link.rel = "stylesheet";
+link.href = `data:text/css,@import url(${absoluteURL('slow_broken_sheet.sjs')}); @import url('data:text/css,');`;
+link.onload = function() { --pendingEventCounter;
+ ok(false, 'Load event firing on broken stylesheet 5');
+};
+link.onerror = function() { --pendingEventCounter;
+ ok(true, 'Error event firing on broken stylesheet 5');
+}
+document.body.appendChild(link);
+
+// Make sure the load events for all those stylesheets have not fired yet
+is(pendingEventCounter, pendingEventCounterAtStart + 9, "There should be nine more pending events");
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_logical_properties.html b/layout/style/test/test_logical_properties.html
new file mode 100644
index 0000000000..a6947791cb
--- /dev/null
+++ b/layout/style/test/test_logical_properties.html
@@ -0,0 +1,422 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for handling of logical and physical properties</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<style id="sheet"></style>
+
+<!-- specify size for <body> to avoid unconstrained-isize warnings
+ when writing-mode of the test <div> is vertical-* -->
+<body style="width:100px; height: 100px;">
+ <div id="test" class="test"></div>
+</body>
+
+<script>
+var gSheet = document.getElementById("sheet");
+var gTest = document.getElementById("test");
+
+// list of groups of physical and logical box properties, such as
+//
+// { left: "margin-left", right: "margin-right",
+// top: "margin-top", bottom: "margin-bottom",
+// inlineStart: "margin-inline-start", inlineEnd: "margin-inline-end",
+// blockStart: "margin-block-start", blockEnd: "margin-block-end",
+// type: "length", prerequisites: "..." }
+//
+// where the type is a key from the gValues object and the prerequisites
+// is a declaration including gCSSProperties' listed prerequisites for
+// all four physical properties.
+var gBoxPropertyGroups;
+
+// list of groups of physical and logical axis properties, such as
+//
+// { horizontal: "width", vertical: "height",
+// inline: "inline-size", block: "block-size",
+// type: "length", prerequisites: "..." }
+var gAxisPropertyGroups;
+
+// values to use while testing
+var gValues = {
+ "length": ["1px", "2px", "3px", "4px", "5px"],
+ "color": ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"],
+ "border-style": ["solid", "dashed", "dotted", "double", "groove"],
+};
+
+// Six unique overall writing modes for property-mapping purposes.
+// Note that text-orientation does not affect these mappings, now that
+// the proposed sideways-left value no longer exists (superseded in CSS
+// Writing Modes by writing-mode: sideways-lr).
+var gWritingModes = [
+ { style: [
+ "writing-mode: horizontal-tb; direction: ltr; ",
+ ],
+ blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right",
+ block: "vertical", inline: "horizontal" },
+ { style: [
+ "writing-mode: horizontal-tb; direction: rtl; ",
+ ],
+ blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left",
+ block: "vertical", inline: "horizontal" },
+ { style: [
+ "writing-mode: vertical-rl; direction: rtl; ",
+ "writing-mode: sideways-rl; direction: rtl; ",
+ ],
+ blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top",
+ block: "horizontal", inline: "vertical" },
+ { style: [
+ "writing-mode: vertical-rl; direction: ltr; ",
+ "writing-mode: sideways-rl; direction: ltr; ",
+ ],
+ blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom",
+ block: "horizontal", inline: "vertical" },
+ { style: [
+ "writing-mode: vertical-lr; direction: rtl; ",
+ "writing-mode: sideways-lr; direction: ltr; ",
+ ],
+ blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top",
+ block: "horizontal", inline: "vertical" },
+ { style: [
+ "writing-mode: vertical-lr; direction: ltr; ",
+ "writing-mode: sideways-lr; direction: rtl; ",
+ ],
+ blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom",
+ block: "horizontal", inline: "vertical" },
+];
+
+function init() {
+ gBoxPropertyGroups = [];
+
+ for (var p in gCSSProperties) {
+ var type = gCSSProperties[p].type;
+
+ if ((type == CSS_TYPE_SHORTHAND_AND_LONGHAND ||
+ type == CSS_TYPE_LONGHAND && gCSSProperties[p].logical) &&
+ /-inline-end/.test(p)) {
+ var valueType;
+ if (/margin|padding|width|inset|offset/.test(p)) {
+ valueType = "length";
+ } else if (/color/.test(p)) {
+ valueType = "color";
+ } else if (/border.*style/.test(p)) {
+ valueType = "border-style";
+ } else {
+ throw `unexpected property ${p}`;
+ }
+ var group = {
+ inlineStart: p.replace("-inline-end", "-inline-start"),
+ inlineEnd: p,
+ blockStart: p.replace("-inline-end", "-block-start"),
+ blockEnd: p.replace("-inline-end", "-block-end"),
+ type: valueType
+ };
+ if (/^(offset|inset)/.test(p)) {
+ group.left = "left";
+ group.right = "right";
+ group.top = "top";
+ group.bottom = "bottom";
+ } else {
+ group.left = p.replace("-inline-end", "-left");
+ group.right = p.replace("-inline-end", "-right");
+ group.top = p.replace("-inline-end", "-top");
+ group.bottom = p.replace("-inline-end", "-bottom");
+ }
+ group.prerequisites =
+ make_declaration(gCSSProperties[group.top].prerequisites) +
+ make_declaration(gCSSProperties[group.right].prerequisites) +
+ make_declaration(gCSSProperties[group.bottom].prerequisites) +
+ make_declaration(gCSSProperties[group.left].prerequisites);
+ gBoxPropertyGroups.push(group);
+ }
+ }
+
+ // We don't populate this automatically since the only entries we have, for
+ // inline-size etc., don't lend themselves to automatically determining
+ // the names "width", "height", "min-width", etc.
+ gAxisPropertyGroups = [];
+ ["", "max-", "min-"].forEach(function(aPrefix) {
+ gAxisPropertyGroups.push({
+ horizontal: `${aPrefix}width`, vertical: `${aPrefix}height`,
+ inline: `${aPrefix}inline-size`, block: `${aPrefix}block-size`,
+ type: "length",
+ prerequisites:
+ make_declaration(gCSSProperties[`${aPrefix}height`].prerequisites)
+ });
+ });
+}
+
+function test_computed_values(aTestName, aRules, aExpectedValues) {
+ gSheet.textContent = aRules;
+ var cs = getComputedStyle(gTest);
+ aExpectedValues.forEach(function(aPair) {
+ is(cs.getPropertyValue(aPair[0]), aPair[1], `${aTestName}, ${aPair[0]}`);
+ });
+ gSheet.textContent = "";
+}
+
+function make_declaration(aObject) {
+ var decl = "";
+ if (aObject) {
+ for (var p in aObject) {
+ decl += `${p}: ${aObject[p]}; `;
+ }
+ }
+ return decl;
+}
+
+function start() {
+ var script = document.createElement("script");
+ script.src = "property_database.js";
+ script.onload = function() {
+ init();
+ run_tests();
+ };
+ document.body.appendChild(script);
+}
+
+function run_axis_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl) {
+ var values = gValues[aGroup.type];
+ var decl;
+
+ // Test that logical axis properties are converted to their physical
+ // equivalent correctly when all four are present on a single
+ // declaration, with no overwriting of previous properties and
+ // no physical properties present. We put the writing mode properties
+ // on a separate declaration to test that the computed values of these
+ // properties are used, rather than those on the same declaration.
+
+ decl = aGroup.prerequisites +
+ `${aGroup.inline}: ${values[0]}; ` +
+ `${aGroup.block}: ${values[1]}; `;
+ test_computed_values('logical properties on one declaration, writing ' +
+ 'mode properties on another, ' +
+ `'${aWritingModeDecl}'`,
+ `.test { ${aWritingModeDecl} } ` +
+ `.test { ${decl} }`,
+ [[aGroup[aWritingMode.inline], values[0]],
+ [aGroup[aWritingMode.block], values[1]]]);
+
+
+ // Test that logical and physical axis properties are cascaded together,
+ // honoring their relative order on a single declaration.
+
+ // (a) with a single logical property after the physical ones
+
+ ["inline", "block"].forEach(function(aLogicalAxis) {
+ decl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.horizontal}: ${values[0]}; ` +
+ `${aGroup.vertical}: ${values[1]}; ` +
+ `${aGroup[aLogicalAxis]}: ${values[2]}; `;
+ var expected = ["horizontal", "vertical"].map(
+ (axis, i) => [aGroup[axis],
+ values[axis == aWritingMode[aLogicalAxis] ? 2 : i]]
+ );
+ test_computed_values(`${aLogicalAxis} last on single declaration, ` +
+ `'${aWritingModeDecl}'`,
+ `.test { ${decl} }`,
+ expected);
+ });
+
+ // (b) with a single physical property after the logical ones
+
+ ["horizontal", "vertical"].forEach(function(aPhysicalAxis) {
+ decl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.inline}: ${values[0]}; ` +
+ `${aGroup.block}: ${values[1]}; ` +
+ `${aGroup[aPhysicalAxis]}: ${values[2]}; `;
+ var expected = ["inline", "block"].map(
+ (axis, i) => [aGroup[aWritingMode[axis]],
+ values[aWritingMode[axis] == aPhysicalAxis ? 2 : i]]
+ );
+ test_computed_values(`${aPhysicalAxis} last on single declaration, ` +
+ `'${aWritingModeDecl}'`,
+ `.test { ${decl} }`,
+ expected);
+ });
+
+
+ // Test that logical and physical axis properties are cascaded properly when
+ // on different declarations.
+
+ var loDecl; // lower specifity
+ var hiDecl; // higher specificity
+
+ // (a) with a logical property in the high specificity rule
+
+ loDecl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.horizontal}: ${values[0]}; ` +
+ `${aGroup.vertical}: ${values[1]}; `;
+
+ ["inline", "block"].forEach(function(aLogicalAxis) {
+ hiDecl = `${aGroup[aLogicalAxis]}: ${values[2]}; `;
+ var expected = ["horizontal", "vertical"].map(
+ (axis, i) => [aGroup[axis],
+ values[axis == aWritingMode[aLogicalAxis] ? 2 : i]]
+ );
+ test_computed_values(`${aLogicalAxis}, two declarations, ` +
+ `'${aWritingModeDecl}'`,
+ `#test { ${hiDecl} } ` +
+ `.test { ${loDecl} }`,
+ expected);
+ });
+
+ // (b) with a physical property in the high specificity rule
+
+ loDecl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.inline}: ${values[0]}; ` +
+ `${aGroup.block}: ${values[1]}; `;
+
+ ["horizontal", "vertical"].forEach(function(aPhysicalAxis) {
+ hiDecl = `${aGroup[aPhysicalAxis]}: ${values[2]}; `;
+ var expected = ["inline", "block"].map(
+ (axis, i) => [aGroup[aWritingMode[axis]],
+ values[aWritingMode[axis] == aPhysicalAxis ? 2 : i]]
+ );
+ test_computed_values(`${aPhysicalAxis}, two declarations, ` +
+ `'${aWritingModeDecl}'`,
+ `#test { ${hiDecl} } ` +
+ `.test { ${loDecl} }`,
+ expected);
+ });
+}
+
+function run_box_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl) {
+ var values = gValues[aGroup.type];
+ var decl;
+
+ // Test that logical box properties are converted to their physical
+ // equivalent correctly when all four are present on a single
+ // declaration, with no overwriting of previous properties and
+ // no physical properties present. We put the writing mode properties
+ // on a separate declaration to test that the computed values of these
+ // properties are used, rather than those on the same declaration.
+
+ decl = aGroup.prerequisites +
+ `${aGroup.inlineStart}: ${values[0]}; ` +
+ `${aGroup.inlineEnd}: ${values[1]}; ` +
+ `${aGroup.blockStart}: ${values[2]}; ` +
+ `${aGroup.blockEnd}: ${values[3]}; `;
+ test_computed_values('logical properties on one declaration, writing ' +
+ 'mode properties on another, ' +
+ `'${aWritingModeDecl}'`,
+ `.test { ${aWritingModeDecl} } ` +
+ `.test { ${decl} }`,
+ [[aGroup[aWritingMode.inlineStart], values[0]],
+ [aGroup[aWritingMode.inlineEnd], values[1]],
+ [aGroup[aWritingMode.blockStart], values[2]],
+ [aGroup[aWritingMode.blockEnd], values[3]]]);
+
+ // Test that logical and physical box properties are cascaded together,
+ // honoring their relative order on a single declaration.
+
+ // (a) with a single logical property after the physical ones
+
+ ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].forEach(function(aLogicalSide) {
+ decl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.left}: ${values[0]}; ` +
+ `${aGroup.right}: ${values[1]}; ` +
+ `${aGroup.top}: ${values[2]}; ` +
+ `${aGroup.bottom}: ${values[3]}; ` +
+ `${aGroup[aLogicalSide]}: ${values[4]}; `;
+ var expected = ["left", "right", "top", "bottom"].map(
+ (side, i) => [aGroup[side],
+ values[side == aWritingMode[aLogicalSide] ? 4 : i]]
+ );
+ test_computed_values(`${aLogicalSide} last on single declaration, ` +
+ `'${aWritingModeDecl}'`,
+ `.test { ${decl} }`,
+ expected);
+ });
+
+ // (b) with a single physical property after the logical ones
+
+ ["left", "right", "top", "bottom"].forEach(function(aPhysicalSide) {
+ decl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.inlineStart}: ${values[0]}; ` +
+ `${aGroup.inlineEnd}: ${values[1]}; ` +
+ `${aGroup.blockStart}: ${values[2]}; ` +
+ `${aGroup.blockEnd}: ${values[3]}; ` +
+ `${aGroup[aPhysicalSide]}: ${values[4]}; `;
+ var expected = ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].map(
+ (side, i) => [aGroup[aWritingMode[side]],
+ values[aWritingMode[side] == aPhysicalSide ? 4 : i]]
+ );
+ test_computed_values(`${aPhysicalSide} last on single declaration, ` +
+ `'${aWritingModeDecl}'`,
+ `.test { ${decl} }`,
+ expected);
+ });
+
+
+ // Test that logical and physical box properties are cascaded properly when
+ // on different declarations.
+
+ var loDecl; // lower specifity
+ var hiDecl; // higher specificity
+
+ // (a) with a logical property in the high specificity rule
+
+ loDecl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.left}: ${values[0]}; ` +
+ `${aGroup.right}: ${values[1]}; ` +
+ `${aGroup.top}: ${values[2]}; ` +
+ `${aGroup.bottom}: ${values[3]}; `;
+
+ ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].forEach(function(aLogicalSide) {
+ hiDecl = `${aGroup[aLogicalSide]}: ${values[4]}; `;
+ var expected = ["left", "right", "top", "bottom"].map(
+ (side, i) => [aGroup[side],
+ values[side == aWritingMode[aLogicalSide] ? 4 : i]]
+ );
+ test_computed_values(`${aLogicalSide}, two declarations, ` +
+ `'${aWritingModeDecl}'`,
+ `#test { ${hiDecl} } ` +
+ `.test { ${loDecl} }`,
+ expected);
+ });
+
+ // (b) with a physical property in the high specificity rule
+
+ loDecl = aWritingModeDecl + aGroup.prerequisites +
+ `${aGroup.inlineStart}: ${values[0]}; ` +
+ `${aGroup.inlineEnd}: ${values[1]}; ` +
+ `${aGroup.blockStart}: ${values[2]}; ` +
+ `${aGroup.blockEnd}: ${values[3]}; `;
+
+ ["left", "right", "top", "bottom"].forEach(function(aPhysicalSide) {
+ hiDecl = `${aGroup[aPhysicalSide]}: ${values[4]}; `;
+ var expected = ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].map(
+ (side, i) => [aGroup[aWritingMode[side]],
+ values[aWritingMode[side] == aPhysicalSide ? 4 : i]]
+ );
+ test_computed_values(`${aPhysicalSide}, two declarations, ` +
+ `'${aWritingModeDecl}'`,
+ `#test { ${hiDecl} } ` +
+ `.test { ${loDecl} }`,
+ expected);
+ });
+}
+
+function run_tests() {
+ gBoxPropertyGroups.forEach(function(aGroup) {
+ gWritingModes.forEach(function(aWritingMode) {
+ aWritingMode.style.forEach(function(aWritingModeDecl) {
+ run_box_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl);
+ });
+ });
+ });
+
+ gAxisPropertyGroups.forEach(function(aGroup) {
+ gWritingModes.forEach(function(aWritingMode) {
+ aWritingMode.style.forEach(function(aWritingModeDecl) {
+ run_axis_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl);
+ });
+ });
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+start();
+</script>
diff --git a/layout/style/test/test_marker_restrictions.html b/layout/style/test/test_marker_restrictions.html
new file mode 100644
index 0000000000..f547f928cb
--- /dev/null
+++ b/layout/style/test/test_marker_restrictions.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for ::marker property restrictions.</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="property_database.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<style id="s"></style>
+<div id="test"></div>
+<div id="control"></div>
+<script>
+const test = getComputedStyle($("test"), "::marker");
+const control = getComputedStyle($("control"), "::marker");
+
+for (const prop in gCSSProperties) {
+ const info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND)
+ continue;
+
+ let prereqs = "";
+ if (info.prerequisites)
+ for (let name in info.prerequisites)
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+
+ $("s").textContent = `
+ #control::marker { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::marker { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+
+ (info.applies_to_marker ? isnot : is)(
+ get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should ${info.applies_to_marker ? "" : " not"} apply to ::marker`);
+}
+
+</script>
diff --git a/layout/style/test/test_mask_image_CORS.html b/layout/style/test/test_mask_image_CORS.html
new file mode 100644
index 0000000000..8edd8af48e
--- /dev/null
+++ b/layout/style/test/test_mask_image_CORS.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test mask-image CORS anonymous retrieval</title>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+<style>
+.block100 {
+ width: 100px;
+ height: 100px;
+}
+#allow {
+ /*
+ * shape-outside is unnecessary for the mask, but using it ensures that the first frame
+ * of the image is decoded and reflow is called before onload is fired. Since the
+ * shape-outside uses the same url as the mask, this ensures that the css image resource
+ * is decoded and available for the repaint triggered by the call to snapshotRect.
+ */
+ shape-outside: url("support/blue-100x100.png");
+ mask-image: url("support/blue-100x100.png");
+ background-color: #00FF00
+}
+#refuse {
+ shape-outside: url("http://example.com/tests/layout/style/test/support/blue-100x100.png");
+ mask-image: url("http://example.com/tests/layout/style/test/support/blue-100x100.png");
+ background-color: #FF0000
+}
+</style>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function checkBothSquares() {
+ checkIsColor("allow", "0,255,0,255");
+ checkIsColor("refuse", "255,255,255,255");
+
+ SimpleTest.finish();
+}
+
+function checkIsColor(elementId, color) {
+ let e = document.getElementById(elementId);
+ let r = e.getBoundingClientRect();
+ info("Element " + elementId + " has rect " + r.top + ", " + r.left + ", " + r.width + ", " + r.height + ".");
+
+ let canvas = snapshotRect(window, r);
+ let context = canvas.getContext('2d');
+
+ // Only check the top left pixel.
+ let image = context.getImageData(0, 0, 1, 1);
+ let pixel = image.data.toString();
+ is(pixel, color, "Element " + elementId + " has expected color.");
+}
+</script>
+
+</head>
+<body onload="checkBothSquares()">
+ <p>There should be a green square, but no red square.</p>
+ <div id="allow" class="block100"></div>
+ <div id="refuse" class="block100"></div>
+</body>
+</html>
diff --git a/layout/style/test/test_media_queries.html b/layout/style/test/test_media_queries.html
new file mode 100644
index 0000000000..441fc1105a
--- /dev/null
+++ b/layout/style/test/test_media_queries.html
@@ -0,0 +1,867 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=156716
+-->
+<head>
+ <title>Test for Bug 156716</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome/chrome-only-media-queries.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=156716">Mozilla Bug 156716</a>
+<iframe id="subdoc" src="media_queries_iframe.html"></iframe>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 156716 **/
+
+// Note that many other tests are in test_acid3_test46.html .
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+var iframe;
+
+function getScreenPixelsPerCSSPixel() {
+ return window.devicePixelRatio;
+}
+
+function run() {
+ iframe = document.getElementById("subdoc");
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+ var style = subdoc.getElementById("style");
+ var iframe_style = iframe.style;
+ var body_cs = subdoc.defaultView.getComputedStyle(subdoc.body);
+
+ function query_applies(q) {
+ style.setAttribute("media", q);
+ return body_cs.getPropertyValue("text-decoration-line") == "underline";
+ }
+
+ function should_apply(q) {
+ ok(query_applies(q), q + " should apply");
+ test_serialization(q, true, true);
+ }
+
+ function should_not_apply(q) {
+ ok(!query_applies(q), q + " should not apply");
+ test_serialization(q, true, false);
+ }
+
+ /* for queries that are parseable standalone but not within CSS */
+ function should_apply_unbalanced(q) {
+ ok(query_applies(q), q + " should apply");
+ }
+
+ /* for queries that are parseable standalone but not within CSS */
+ function should_not_apply_unbalanced(q) {
+ ok(!query_applies(q), q + " should not apply");
+ }
+
+ /*
+ * Functions to test whether a query is parseable at all. (Should not
+ * be used for parse errors within expressions.)
+ */
+ var parse_test_style_element = document.createElement("style");
+ parse_test_style_element.type = "text/css";
+ parse_test_style_element.disabled = true; // for performance, hopefully
+ var parse_test_style_text = document.createTextNode("");
+ parse_test_style_element.appendChild(parse_test_style_text);
+ document.getElementsByTagName("head")[0]
+ .appendChild(parse_test_style_element);
+
+ function query_is_parseable(q) {
+ parse_test_style_text.data = "@media screen, " + q + " {}";
+ var sheet = parse_test_style_element.sheet; // XXX yikes, not live!
+ if (sheet.cssRules.length == 1 &&
+ sheet.cssRules[0].type == CSSRule.MEDIA_RULE)
+ return sheet.cssRules[0].media.mediaText != "screen, not all";
+ ok(false, "unexpected result testing whether query " + q +
+ " is parseable");
+ return true; // doesn't matter, we already failed
+ }
+
+ function query_should_be_parseable(q) {
+ ok(query_is_parseable(q), "query " + q + " should be parseable");
+ test_serialization(q, false, false);
+ }
+
+ function query_should_not_be_parseable(q) {
+ ok(!query_is_parseable(q), "query " + q + " should not be parseable");
+ }
+
+ function expression_should_be_known(e) {
+ should_apply(`(${e}) or (not (${e}))`);
+ }
+
+ function expression_should_not_be_known(e) {
+ should_not_apply(`(${e}) or (not (${e}))`);
+ }
+
+ // Helper to share code between -moz & -webkit device-pixel-ratio versions:
+ function test_device_pixel_ratio(equal_name, min_name, max_name) {
+ var real_dpr = 1.0 * getScreenPixelsPerCSSPixel();
+ var high_dpr = 1.1 * getScreenPixelsPerCSSPixel();
+ var low_dpr = 0.9 * getScreenPixelsPerCSSPixel();
+ should_apply("all and (" + max_name + ": " + real_dpr + ")");
+ should_apply("all and (" + min_name + ": " + real_dpr + ")");
+ should_not_apply("not all and (" + max_name + ": " + real_dpr + ")");
+ should_not_apply("not all and (" + min_name + ": " + real_dpr + ")");
+ should_apply("all and (" + min_name + ": " + low_dpr + ")");
+ should_apply("all and (" + max_name + ": " + high_dpr + ")");
+ should_not_apply("all and (" + max_name + ": " + low_dpr + ")");
+ should_not_apply("all and (" + min_name + ": " + high_dpr + ")");
+ should_apply("not all and (" + max_name + ": " + low_dpr + ")");
+ should_apply("not all and (" + min_name + ": " + high_dpr + ")");
+ should_apply("(" + equal_name + ": " + real_dpr + ")");
+ should_not_apply("(" + equal_name + ": " + high_dpr + ")");
+ should_not_apply("(" + equal_name + ": " + low_dpr + ")");
+ should_apply("(" + equal_name + ")");
+ expression_should_not_be_known(min_name);
+ expression_should_not_be_known(max_name);
+ }
+
+ function test_serialization(q, test_application, expected_to_apply) {
+ style.setAttribute("media", q);
+ var ser1 = style.sheet.media.mediaText;
+ isnot(ser1, "", "serialization of '" + q + "' should not be empty");
+ style.setAttribute("media", ser1);
+ var ser2 = style.sheet.media.mediaText;
+ is(ser2, ser1, "parse+serialize of '" + q + "' should be idempotent");
+ if (test_application) {
+ let applies = body_cs.getPropertyValue("text-decoration-line") ==
+ "underline";
+ is(applies, expected_to_apply,
+ "Media query '" + q + "' should " + (expected_to_apply ? "" : "NOT ") +
+ "apply after serialize + reparse");
+ }
+
+ // Test cloning
+ var sheet = "@media " + q + " { body { text-decoration: underline } }"
+ var sheeturl = "data:text/css," + escape(sheet);
+ var link = "<link rel='stylesheet' href='" + sheeturl + "'>";
+ var htmldoc = "<!DOCTYPE HTML>" + link + link + "<body>";
+ post_clone_test(htmldoc, function() {
+ var clonedoc = iframe.contentDocument;
+ var clonewin = iframe.contentWindow;
+ var links = clonedoc.getElementsByTagName("link");
+ // cause a clone
+ var clonedsheet = links[1].sheet;
+ clonedsheet.insertRule("#nonexistent { color: purple}", 1);
+ // remove the uncloned sheet
+ links[0].remove();
+
+ var ser3 = clonedsheet.cssRules[0].media.mediaText;
+ is(ser3, ser1, "cloning query '" + q + "' should not change " +
+ "serialization");
+ if (test_application) {
+ let applies = clonewin.getComputedStyle(clonedoc.body).
+ textDecorationLine == "underline";
+ is(applies, expected_to_apply,
+ "Media query '" + q + "' should " + (expected_to_apply ? "" : "NOT ") +
+ "apply after cloning");
+ }
+ });
+ }
+
+ // The no-type syntax doesn't mix with the not and only keywords.
+ expression_should_be_known("(orientation)");
+ expression_should_be_known("not (orientation)");
+ query_should_not_be_parseable("only (orientation)");
+ query_should_be_parseable("all and (orientation)");
+ query_should_be_parseable("not all and (orientation)");
+ query_should_be_parseable("only all and (orientation)");
+
+ query_should_not_be_parseable("not not (orientation)");
+ expression_should_be_known("(orientation) and (orientation)");
+ expression_should_be_known("(orientation) or (orientation)");
+ expression_should_be_known("(orientation) or ((orientation) and ((orientation) or (orientation) or (not (orientation))))");
+
+ query_should_not_be_parseable("all and (orientation) or (orientation)");
+ query_should_be_parseable("all and (orientation) and (orientation)");
+
+ query_should_not_be_parseable("(orientation) and (orientation) or (orientation)");
+ query_should_not_be_parseable("(orientation) and not (orientation)");
+
+ query_should_be_parseable("(-moz-device-orientation)");
+ query_should_be_parseable("not (-moz-device-orientation)");
+ query_should_not_be_parseable("only (-moz-device-orientation)");
+ query_should_be_parseable("all and (-moz-device-orientation)");
+ query_should_be_parseable("not all and (-moz-device-orientation)");
+ query_should_be_parseable("only all and (-moz-device-orientation)");
+
+ // Test that the 'not', 'only', 'and', and 'or' keywords are not
+ // allowed as media types.
+ query_should_not_be_parseable("not");
+ query_should_not_be_parseable("and");
+ query_should_not_be_parseable("or");
+ query_should_not_be_parseable("only");
+ query_should_be_parseable("unknowntype");
+ query_should_not_be_parseable("not not");
+ query_should_not_be_parseable("not and");
+ query_should_not_be_parseable("not or");
+ query_should_not_be_parseable("not only");
+ query_should_be_parseable("not unknowntype");
+ query_should_not_be_parseable("only not");
+ query_should_not_be_parseable("only and");
+ query_should_not_be_parseable("only or");
+ query_should_not_be_parseable("only only");
+ query_should_be_parseable("only unknowntype");
+ query_should_not_be_parseable("not and (width)");
+ query_should_not_be_parseable("and and (width)");
+ query_should_not_be_parseable("or and (width)");
+ query_should_not_be_parseable("only and (width)");
+ query_should_be_parseable("unknowntype and (width)");
+ query_should_not_be_parseable("not not and (width)");
+ query_should_not_be_parseable("not and and (width)");
+ query_should_not_be_parseable("not or and (width)");
+ query_should_not_be_parseable("not only and (width)");
+ query_should_be_parseable("not unknowntype and (width)");
+ query_should_not_be_parseable("only not and (width)");
+ query_should_not_be_parseable("only and and (width)");
+ query_should_not_be_parseable("only or and (width)");
+ query_should_not_be_parseable("only only and (width)");
+ query_should_be_parseable("only unknowntype and (width)");
+
+ var features = [ "width", "height", "device-width", "device-height" ];
+ var separators = [ ":", ">", ">=", "=", "<=", "<" ];
+
+ var feature;
+ var i;
+ for (i in features) {
+ feature = features[i];
+ expression_should_be_known(feature);
+ expression_should_not_be_known("min-" + feature);
+ expression_should_not_be_known("max-" + feature);
+ for (let separator of separators) {
+ expression_should_be_known(feature + " " + separator + " 0");
+ expression_should_be_known(feature + " " + separator + " 0px");
+ expression_should_be_known(feature + " " + separator + " 0em");
+ expression_should_be_known(feature + " " + separator + " -0");
+ expression_should_be_known(feature + " " + separator + " -0cm");
+ expression_should_be_known(feature + " " + separator + " 1px");
+ expression_should_be_known(feature + " " + separator + " 0.001mm");
+ expression_should_be_known(feature + " " + separator + " 100000px");
+ expression_should_be_known(feature + " " + separator + " -1px");
+ if (separator == ":") {
+ expression_should_be_known("min-" + feature + " " + separator + " -0");
+ expression_should_be_known("max-" + feature + " " + separator + " -0");
+ expression_should_be_known("min-" + feature + " " + separator + " -1px");
+ expression_should_be_known("max-" + feature + " " + separator + " -1px");
+ expression_should_be_known(feature + " " + separator + " -0.00001mm");
+ expression_should_be_known(feature + " " + separator + " -100000em");
+ } else {
+ expression_should_not_be_known("min-" + feature + " " + separator + " -0");
+ expression_should_not_be_known("max-" + feature + " " + separator + " -0");
+ expression_should_not_be_known("min-" + feature + " " + separator + " -1px");
+ expression_should_not_be_known("max-" + feature + " " + separator + " -1px");
+ let multi_range = "0px " + separator + " " + feature + " " + separator + " 100000px"
+ if (separator == "=") {
+ expression_should_not_be_known(multi_range);
+ } else {
+ expression_should_be_known(multi_range);
+ }
+ }
+ if (separator == ">=") {
+ expression_should_not_be_known(feature + " > = 0px");
+ } else if (separator == "<=") {
+ expression_should_not_be_known(feature + " < = 0px");
+ }
+ }
+ }
+
+ var mediatypes = ["browser", "minimal-ui", "standalone", "fullscreen"];
+
+ mediatypes.forEach(function(type) {
+ expression_should_be_known("display-mode: " + type);
+ });
+
+ expression_should_not_be_known("display-mode: invalid")
+
+ var content_div = document.getElementById("content");
+ content_div.style.font = "initial";
+ var em_size =
+ getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1];
+
+ // in this test, assume the common underlying implementation is correct
+ var width_val = 117; // pick two not-too-round numbers
+ var height_val = 76;
+ change_state(function() {
+ iframe_style.width = width_val + "px";
+ iframe_style.height = height_val + "px";
+ });
+ var device_width = window.screen.width;
+ var device_height = window.screen.height;
+ features = {
+ "width": width_val,
+ "height": height_val,
+ "device-width": device_width,
+ "device-height": device_height
+ };
+ for (feature in features) {
+ var value = features[feature];
+ should_apply("all and (" + feature + ": " + value + "px)");
+ should_apply("all and (" + feature + " = " + value + "px)");
+ should_not_apply("all and (" + feature + ": " + (value + 1) + "px)");
+ should_not_apply("all and (" + feature + ": " + (value - 1) + "px)");
+ should_not_apply("all and (" + feature + " = " + (value + 1) + "px)");
+ should_not_apply("all and (" + feature + " = " + (value - 1) + "px)");
+
+ should_apply("all and (min-" + feature + ": " + value + "px)");
+ should_not_apply("all and (min-" + feature + ": " + (value + 1) + "px)");
+ should_apply("all and (min-" + feature + ": " + (value - 1) + "px)");
+ should_apply("all and (max-" + feature + ": " + value + "px)");
+ should_apply("all and (max-" + feature + ": " + (value + 1) + "px)");
+ should_not_apply("all and (max-" + feature + ": " + (value - 1) + "px)");
+ should_not_apply("all and (min-" + feature + ": " +
+ (Math.ceil(value/em_size) + 1) + "em)");
+ should_apply("all and (min-" + feature + ": " +
+ (Math.floor(value/em_size) - 1) + "em)");
+ should_apply("all and (max-" + feature + ": " +
+ (Math.ceil(value/em_size) + 1) + "em)");
+ should_not_apply("all and (max-" + feature + ": " +
+ (Math.floor(value/em_size) - 1) + "em)");
+ should_not_apply("all and (min-" + feature + ": " +
+ (Math.ceil(value/em_size) + 1) + "rem)");
+ should_apply("all and (min-" + feature + ": " +
+ (Math.floor(value/em_size) - 1) + "rem)");
+ should_apply("all and (max-" + feature + ": " +
+ (Math.ceil(value/em_size) + 1) + "rem)");
+ should_not_apply("all and (max-" + feature + ": " +
+ (Math.floor(value/em_size) - 1) + "rem)");
+
+ should_apply("(" + feature + " <= " + value + "px)");
+ should_apply("(" + feature + " >= " + value + "px)");
+
+ should_apply("(0px < " + feature + " <= " + value + "px)");
+ should_apply("(" + value + "px >= " + feature + " > 0px)");
+
+ should_not_apply("(0px < " + feature + " < " + value + "px)");
+ should_not_apply("(" + value + "px > " + feature + " > 0px)");
+
+ should_not_apply("(" + feature + " < " + value + "px)");
+ should_not_apply("(" + feature + " > " + value + "px)");
+
+ should_apply("(" + feature + " < " + (value + 1) + "px)");
+ should_apply("(" + feature + " <= " + (value + 1) + "px)");
+ should_not_apply("(" + feature + " > " + (value + 1) + "px)");
+ should_not_apply("(" + feature + " >= " + (value + 1) + "px)");
+
+ should_apply("(" + feature + " > " + (value - 1) + "px)");
+ should_apply("(" + feature + " >= " + (value - 1) + "px)");
+ should_not_apply("(" + feature + " < " + (value - 1) + "px)");
+ should_not_apply("(" + feature + " <= " + (value - 1) + "px)");
+ }
+
+ change_state(function() {
+ iframe_style.width = "0";
+ });
+ should_apply("all and (height)");
+ should_not_apply("all and (width)");
+ change_state(function() {
+ iframe_style.height = "0";
+ });
+ should_not_apply("all and (height)");
+ should_not_apply("all and (width)");
+ should_apply("all and (device-height)");
+ should_apply("all and (device-width)");
+ change_state(function() {
+ iframe_style.width = width_val + "px";
+ });
+ should_not_apply("all and (height)");
+ should_apply("all and (width)");
+ change_state(function() {
+ iframe_style.height = height_val + "px";
+ });
+ should_apply("all and (height)");
+ should_apply("all and (width)");
+
+ // ratio that reduces to 59/40
+ change_state(function() {
+ iframe_style.width = "236px";
+ iframe_style.height = "160px";
+ });
+ expression_should_be_known("orientation");
+ expression_should_be_known("orientation: portrait");
+ expression_should_be_known("orientation: landscape");
+ expression_should_not_be_known("min-orientation");
+ expression_should_not_be_known("min-orientation: portrait");
+ expression_should_not_be_known("min-orientation: landscape");
+ expression_should_not_be_known("max-orientation");
+ expression_should_not_be_known("max-orientation: portrait");
+ expression_should_not_be_known("max-orientation: landscape");
+ should_apply("(orientation)");
+ should_apply("(orientation: landscape)");
+ should_not_apply("(orientation: portrait)");
+ should_apply("not all and (orientation: portrait)");
+ // ratio that reduces to 59/80
+ change_state(function() {
+ iframe_style.height = "320px";
+ });
+ should_apply("(orientation)");
+ should_not_apply("(orientation: landscape)");
+ should_apply("not all and (orientation: landscape)");
+ should_apply("(orientation: portrait)");
+
+ expression_should_be_known("-moz-device-orientation");
+ expression_should_be_known("-moz-device-orientation: portrait");
+ expression_should_be_known("-moz-device-orientation: landscape");
+ expression_should_not_be_known("min--moz-device-orientation");
+ expression_should_not_be_known("min--moz-device-orientation: portrait");
+ expression_should_not_be_known("min--moz-device-orientation: landscape");
+ expression_should_not_be_known("max--moz-device-orientation");
+ expression_should_not_be_known("max--moz-device-orientation: portrait");
+ expression_should_not_be_known("max--moz-device-orientation: landscape");
+
+ // determine the actual configuration of the screen and test against it
+ var device_orientation = (device_width > device_height) ? "landscape" : "portrait";
+ var not_device_orientation = (device_orientation == "landscape") ? "portrait" : "landscape";
+ should_apply("(-moz-device-orientation)");
+ should_apply("(-moz-device-orientation: " + device_orientation + ")");
+ should_not_apply("(-moz-device-orientation: " + not_device_orientation + ")");
+ should_apply("not all and (-moz-device-orientation: " + not_device_orientation + ")");
+
+ should_apply("(aspect-ratio: 59/80)");
+ should_not_apply("(aspect-ratio: 58/80)");
+ should_not_apply("(aspect-ratio: 59/81)");
+ should_not_apply("(aspect-ratio: 60/80)");
+ should_not_apply("(aspect-ratio: 59/79)");
+ should_apply("(aspect-ratio: 177/240)");
+ should_apply("(aspect-ratio: 413/560)");
+ should_apply("(aspect-ratio: 5900/8000)");
+ should_not_apply("(aspect-ratio: 5901/8000)");
+ should_not_apply("(aspect-ratio: 5899/8000)");
+ should_not_apply("(aspect-ratio: 5900/8001)");
+ should_not_apply("(aspect-ratio: 5900/7999)");
+ should_apply("(aspect-ratio)");
+
+ // Test "unreasonable", but still valid aspect ratios, such as aspect ratios with negative numbers,
+ // and zeros, and with numbers near 2^32 and 2^64 (to check overflow).
+ should_not_apply("(aspect-ratio: 0/1)");
+ should_not_apply("(aspect-ratio: 1/0)");
+ should_not_apply("(aspect-ratio: -1/1)");
+ should_not_apply("(aspect-ratio: 1/-1)");
+ should_not_apply("(aspect-ratio: -1/-1)");
+ should_not_apply("(aspect-ratio: -59/-80)");
+ should_not_apply("(aspect-ratio: 4294967295/4294967295)");
+ should_not_apply("(aspect-ratio: 4294967297/4294967297)");
+ should_not_apply("(aspect-ratio: 18446744073709560000/18446744073709560000)");
+
+ // Test min and max aspect ratios.
+ should_apply("(min-aspect-ratio: 59/80)");
+ should_apply("(min-aspect-ratio: 58/80)");
+ should_apply("(min-aspect-ratio: 59/81)");
+ should_not_apply("(min-aspect-ratio: 60/80)");
+ should_not_apply("(min-aspect-ratio: 59/79)");
+ expression_should_not_be_known("min-aspect-ratio");
+
+ should_apply("(max-aspect-ratio: 59/80)");
+ should_not_apply("(max-aspect-ratio: 58/80)");
+ should_not_apply("(max-aspect-ratio: 59/81)");
+ should_apply("(max-aspect-ratio: 60/80)");
+ should_apply("(max-aspect-ratio: 59/79)");
+ expression_should_not_be_known("max-aspect-ratio");
+
+ var real_dar = device_width + "/" + device_height;
+ var high_dar_1 = (device_width + 1) + "/" + device_height;
+ var high_dar_2 = device_width + "/" + (device_height - 1);
+ var low_dar_1 = (device_width - 1) + "/" + device_height;
+ var low_dar_2 = device_width + "/" + (device_height + 1);
+ should_apply("(device-aspect-ratio: " + real_dar + ")");
+ should_apply("not all and (device-aspect-ratio: " + high_dar_1 + ")");
+ should_not_apply("all and (device-aspect-ratio: " + high_dar_2 + ")");
+ should_not_apply("all and (device-aspect-ratio: " + low_dar_1 + ")");
+ should_apply("not all and (device-aspect-ratio: " + low_dar_2 + ")");
+ should_apply("(device-aspect-ratio)");
+
+ should_apply("(min-device-aspect-ratio: " + real_dar + ")");
+ should_not_apply("all and (min-device-aspect-ratio: " + high_dar_1 + ")");
+ should_apply("not all and (min-device-aspect-ratio: " + high_dar_2 + ")");
+ should_not_apply("not all and (min-device-aspect-ratio: " + low_dar_1 + ")");
+ should_apply("all and (min-device-aspect-ratio: " + low_dar_2 + ")");
+ expression_should_not_be_known("min-device-aspect-ratio");
+
+ should_apply("all and (max-device-aspect-ratio: " + real_dar + ")");
+ should_apply("(max-device-aspect-ratio: " + high_dar_1 + ")");
+ should_apply("(max-device-aspect-ratio: " + high_dar_2 + ")");
+ should_not_apply("all and (max-device-aspect-ratio: " + low_dar_1 + ")");
+ should_apply("not all and (max-device-aspect-ratio: " + low_dar_2 + ")");
+ expression_should_not_be_known("max-device-aspect-ratio");
+
+ // Tests for -moz- & -webkit versions of "device-pixel-ratio"
+ // (Note that the vendor prefixes go in different places.)
+ test_device_pixel_ratio("-moz-device-pixel-ratio",
+ "min--moz-device-pixel-ratio",
+ "max--moz-device-pixel-ratio");
+ test_device_pixel_ratio("-webkit-device-pixel-ratio",
+ "-webkit-min-device-pixel-ratio",
+ "-webkit-max-device-pixel-ratio");
+
+ // Make sure that we don't accidentally start accepting *unprefixed*
+ // "device-pixel-ratio" expressions:
+ expression_should_be_known("-webkit-device-pixel-ratio: 1.0");
+ expression_should_not_be_known("device-pixel-ratio: 1.0");
+ expression_should_be_known("-webkit-min-device-pixel-ratio: 1.0");
+ expression_should_not_be_known("min-device-pixel-ratio: 1.0");
+ expression_should_be_known("-webkit-max-device-pixel-ratio: 1.0");
+ expression_should_not_be_known("max-device-pixel-ratio: 1.0");
+
+ should_apply("(-webkit-transform-3d)");
+
+ features = [ "max-aspect-ratio", "device-aspect-ratio" ];
+ for (i in features) {
+ feature = features[i];
+ expression_should_be_known(feature + ": 1/1");
+ expression_should_be_known(feature + ": 1 /1");
+ expression_should_be_known(feature + ": 1 / \t\n1");
+ expression_should_be_known(feature + ": 1/\r1");
+ expression_should_be_known(feature + ": 1");
+ expression_should_be_known(feature + ": 0.5");
+ expression_should_be_known(feature + ": 1.0/1");
+ expression_should_be_known(feature + ": 1/1.0");
+ expression_should_be_known(feature + ": 1.0/1.0");
+ expression_should_be_known(feature + ": 1.5/1.2");
+ expression_should_be_known(feature + ": 1.5");
+ expression_should_be_known(feature + ": calc(1.2 * 1.3)");
+ expression_should_be_known(feature + ": 1.1/calc(2.2 * 2.3)");
+ expression_should_be_known(feature + ": calc(1.2 * 1.3)/2.2");
+ expression_should_be_known(feature + ": calc(1.2 * 1.3)/calc(2.2 * 2.3)");
+ expression_should_be_known(feature + ": 0/1");
+ expression_should_be_known(feature + ": 1/0");
+ expression_should_be_known(feature + ": 0/0");
+ expression_should_not_be_known(feature + ": -1/1");
+ expression_should_not_be_known(feature + ": 1/-1");
+ expression_should_not_be_known(feature + ": -1/-1");
+ expression_should_not_be_known(feature + ": -1/-1");
+ expression_should_not_be_known(feature + ": -1/-1");
+ expression_should_not_be_known(feature + ": invalid");
+ expression_should_not_be_known(feature + ": 1 / invalid");
+ expression_should_not_be_known(feature + ": 1 invalid");
+ }
+
+ var is_monochrome = query_applies("all and (min-monochrome: 1)");
+ test_serialization("all and (min-monochrome: 1)", true, is_monochrome);
+ var is_color = query_applies("all and (min-color: 1)");
+ test_serialization("all and (min-color: 1)", true, is_color);
+ isnot(is_monochrome, is_color, "should be either monochrome or color");
+
+ function depth_query(prefix, depth) {
+ return "all and (" + prefix + (is_color ? "color" : "monochrome") +
+ ":" + depth + ")";
+ }
+
+ var depth = 0;
+ do {
+ if (depth > 50) {
+ ok(false, "breaking from loop, depth > 50");
+ break;
+ }
+ } while (query_applies(depth_query("min-", ++depth)));
+ --depth;
+
+ should_apply(depth_query("", depth));
+ should_not_apply(depth_query("", depth - 1));
+ should_not_apply(depth_query("", depth + 1));
+ should_apply(depth_query("max-", depth));
+ should_not_apply(depth_query("max-", depth - 1));
+ should_apply(depth_query("max-", depth + 1));
+
+ (is_color ? should_apply : should_not_apply)("all and (color)");
+ expression_should_not_be_known("max-color");
+ expression_should_not_be_known("min-color");
+ (is_color ? should_not_apply : should_apply)("all and (monochrome)");
+ expression_should_not_be_known("max-monochrome");
+ expression_should_not_be_known("min-monochrome");
+ (is_color ? should_apply : should_not_apply)("not all and (monochrome)");
+ (is_color ? should_not_apply : should_apply)("not all and (color)");
+ (is_color ? should_apply : should_not_apply)("only all and (color)");
+ (is_color ? should_not_apply : should_apply)("only all and (monochrome)");
+
+ features = [ "color", "min-monochrome", "max-color-index" ];
+ for (i in features) {
+ feature = features[i];
+ expression_should_be_known(feature + ": 1");
+ expression_should_be_known(feature + ": 327");
+ expression_should_be_known(feature + ": 0");
+ expression_should_be_known(feature + ": -1");
+ expression_should_not_be_known(feature + ": 1.0");
+ expression_should_not_be_known(feature + ": 1/1");
+ }
+
+ // Presume that we never support indexed color (at least not usefully
+ // enough to call it indexed color).
+ should_apply("(color-index: 0)");
+ should_not_apply("(color-index: 1)");
+ should_apply("(min-color-index: 0)");
+ should_not_apply("(min-color-index: 1)");
+ should_apply("(max-color-index: 0)");
+ should_apply("(max-color-index: 1)");
+ should_apply("(max-color-index: 157)");
+
+ features = [ "resolution", "min-resolution", "max-resolution" ];
+ for (i in features) {
+ feature = features[i];
+ expression_should_be_known(feature + ": 3dpi");
+ expression_should_be_known(feature + ":3dpi");
+ expression_should_be_known(feature + ": 3.0dpi");
+ expression_should_be_known(feature + ": 3.4dpi");
+ expression_should_be_known(feature + "\t: 120dpcm");
+ expression_should_be_known(feature + ": 1dppx");
+ expression_should_be_known(feature + ": 1x");
+ expression_should_be_known(feature + ": 1.5dppx");
+ expression_should_be_known(feature + ": 1.5x");
+ expression_should_be_known(feature + ": 2.0dppx");
+ expression_should_be_known(feature + ": 0dpi");
+ expression_should_be_known(feature + ": 0dppx");
+ expression_should_be_known(feature + ": 0x");
+ expression_should_not_be_known(feature + ": -3dpi");
+ }
+
+ // Find the resolution using max-resolution
+ var resolution = 0;
+ do {
+ ++resolution;
+ if (resolution > 10000) {
+ ok(false, "resolution greater than 10000dpi???");
+ break;
+ }
+ } while (!query_applies("(max-resolution: " + resolution + "dpi)"));
+
+ // resolution should now be Math.ceil() of the actual resolution.
+ var dpi_high;
+ var dpi_low = resolution - 1;
+ if (query_applies("(min-resolution: " + resolution + "dpi)")) {
+ // It's exact!
+ should_apply("(resolution: " + resolution + "dpi)");
+ should_apply("(resolution: " + Math.floor(resolution/96) + "dppx)");
+ should_apply("(resolution: " + Math.floor(resolution/96) + "x)");
+ should_not_apply("(resolution: " + (resolution + 1) + "dpi)");
+ should_not_apply("(resolution: " + (resolution - 1) + "dpi)");
+ dpi_high = resolution + 1;
+ } else {
+ // We have no way to test resolution applying since it need not be
+ // an integer.
+ should_not_apply("(resolution: " + resolution + "dpi)");
+ should_not_apply("(resolution: " + (resolution - 1) + "dpi)");
+ dpi_high = resolution;
+ }
+
+ should_apply("(min-resolution: " + dpi_low + "dpi)");
+ should_not_apply("not all and (min-resolution: " + dpi_low + "dpi)");
+ should_apply("not all and (min-resolution: " + dpi_high + "dpi)");
+ should_not_apply("all and (min-resolution: " + dpi_high + "dpi)");
+
+ // Test dpcm units based on what we computed in dpi.
+ var dpcm_high = Math.ceil(dpi_high / 2.54);
+ var dpcm_low = Math.floor(dpi_low / 2.54);
+ should_apply("(min-resolution: " + dpcm_low + "dpcm)");
+ should_apply("(max-resolution: " + dpcm_high + "dpcm)");
+ should_not_apply("(max-resolution: " + dpcm_low + "dpcm)");
+ should_apply("not all and (min-resolution: " + dpcm_high + "dpcm)");
+
+ expression_should_be_known("scan");
+ expression_should_be_known("scan: progressive");
+ expression_should_be_known("scan:interlace");
+ expression_should_not_be_known("min-scan:interlace");
+ expression_should_not_be_known("scan: 1");
+ expression_should_not_be_known("max-scan");
+ expression_should_not_be_known("max-scan: progressive");
+ // Assume we don't support tv devices.
+ should_not_apply("(scan)");
+ should_not_apply("(scan: progressive)");
+ should_not_apply("(scan: interlace)");
+ should_apply("not all and (scan)");
+ should_apply("not all and (scan: progressive)");
+ should_apply("not all and (scan: interlace)");
+
+ expression_should_be_known("grid");
+ expression_should_be_known("grid: 0");
+ expression_should_be_known("grid: 1");
+ expression_should_be_known("grid: 1");
+ expression_should_not_be_known("min-grid");
+ expression_should_not_be_known("min-grid:0");
+ expression_should_not_be_known("max-grid: 1");
+ expression_should_not_be_known("grid: 2");
+ expression_should_not_be_known("grid: -1");
+
+ // Assume we don't support grid devices
+ should_not_apply("(grid)");
+ should_apply("(grid: 0)");
+ should_not_apply("(grid: 1)");
+ should_not_apply("(grid: 2)");
+ should_not_apply("(grid: -1)");
+
+ for (let toggle of CHROME_ONLY_TOGGLES) {
+ expression_should_not_be_known(toggle);
+ expression_should_not_be_known(toggle + ": 1");
+ expression_should_not_be_known(toggle + ": 0");
+ expression_should_not_be_known(toggle + ": -1");
+ expression_should_not_be_known(toggle + ": true");
+ expression_should_not_be_known(toggle + ": false");
+ }
+
+ for (let query of CHROME_ONLY_QUERIES) {
+ expression_should_not_be_known(query);
+ }
+
+ {
+ let should_be_parseable_if_enabled = SpecialPowers.getBoolPref('layout.css.prefers-contrast.enabled')
+ ? expression_should_be_known
+ : expression_should_not_be_known;
+ should_be_parseable_if_enabled("prefers-contrast");
+ should_be_parseable_if_enabled("prefers-contrast: more");
+ should_be_parseable_if_enabled("prefers-contrast: less");
+ should_be_parseable_if_enabled("prefers-contrast: custom");
+ should_be_parseable_if_enabled("prefers-contrast: no-preference");
+ }
+
+ {
+ let should_be_parseable_if_enabled = SpecialPowers.getBoolPref('layout.css.forced-colors.enabled')
+ ? expression_should_be_known
+ : expression_should_not_be_known;
+ should_be_parseable_if_enabled("forced-colors");
+ should_be_parseable_if_enabled("forced-colors: none");
+ should_be_parseable_if_enabled("forced-colors: active");
+ }
+
+ // OpenType SVG media features
+ expression_should_not_be_known("(-moz-is-glyph)");
+ expression_should_not_be_known("not (-moz-is-glyph)");
+ expression_should_not_be_known("only (-moz-is-glyph)");
+ expression_should_not_be_known("all and (-moz-is-glyph)");
+ expression_should_not_be_known("not all and (-moz-is-glyph)");
+ expression_should_not_be_known("only all and (-moz-is-glyph)");
+
+ expression_should_not_be_known("(-moz-is-glyph:0)");
+ expression_should_not_be_known("not (-moz-is-glyph:0)");
+ expression_should_not_be_known("only (-moz-is-glyph:0)");
+ expression_should_not_be_known("all and (-moz-is-glyph:0)");
+ expression_should_not_be_known("not all and (-moz-is-glyph:0)");
+ expression_should_not_be_known("only all and (-moz-is-glyph:0)");
+
+ expression_should_not_be_known("(-moz-is-glyph:1)");
+ expression_should_not_be_known("not (-moz-is-glyph:1)");
+ expression_should_not_be_known("only (-moz-is-glyph:1)");
+ expression_should_not_be_known("all and (-moz-is-glyph:1)");
+ expression_should_not_be_known("not all and (-moz-is-glyph:1)");
+ expression_should_not_be_known("only all and (-moz-is-glyph:1)");
+
+ expression_should_not_be_known("(min--moz-is-glyph:0)");
+ expression_should_not_be_known("(max--moz-is-glyph:0)");
+ expression_should_not_be_known("(min--moz-is-glyph:1)");
+ expression_should_not_be_known("(max--moz-is-glyph:1)");
+
+ should_not_apply("not all and (-moz-is-glyph)");
+ should_not_apply("(-moz-is-glyph:0)");
+ should_not_apply("not all and (-moz-is-glyph:1)");
+ should_not_apply("only all and (-moz-is-glyph:0)");
+ should_not_apply("(-moz-is-glyph)");
+ should_not_apply("(-moz-is-glyph:1)");
+ should_not_apply("not all and (-moz-is-glyph:0)");
+ should_not_apply("only all and (-moz-is-glyph:1)");
+
+ // Resource documents (UA-only).
+ expression_should_not_be_known("(-moz-is-resource-document)");
+
+ // Parsing tests
+ // bug 454227
+ should_apply_unbalanced("(orientation");
+ should_not_apply_unbalanced("not all and (orientation");
+ should_not_apply_unbalanced("(orientation:");
+ should_apply_unbalanced("all,(orientation:");
+ should_not_apply_unbalanced("(orientation:,all");
+ should_apply_unbalanced("not all and (grid");
+ should_not_apply_unbalanced("only all and (grid");
+ should_not_apply_unbalanced("(grid");
+ should_apply_unbalanced("all,(grid");
+ should_not_apply_unbalanced("(grid,all");
+ // bug 454226
+ should_apply(",all");
+ should_apply("all,");
+ should_apply(",all,");
+ should_apply("all,badmedium");
+ should_apply("badmedium,all");
+ should_not_apply(",badmedium,");
+ should_apply("all,(badexpression)");
+ should_apply("(badexpression),all");
+ should_not_apply("(badexpression),badmedium");
+ should_not_apply("badmedium,(badexpression)");
+ should_apply("all,[badsyntax]");
+ should_apply("[badsyntax],all");
+ should_not_apply("badmedium,[badsyntax]");
+ should_not_apply("[badsyntax],badmedium");
+ // bug 528096
+ should_not_apply_unbalanced("((resolution),all");
+ should_not_apply_unbalanced("(resolution(),all");
+ should_not_apply_unbalanced("(resolution (),all");
+ should_not_apply_unbalanced("(resolution:(),all");
+
+ for (let rangeFeature of ["dynamic-range", "video-dynamic-range"]) {
+ should_apply("(" + rangeFeature + ": standard)");
+ expression_should_be_known("(" + rangeFeature + ": high)");
+ expression_should_not_be_known("(" + rangeFeature + ": low)");
+ }
+
+ handle_posted_items();
+}
+
+/*
+ * The cloning tests have to post tests that wait for onload. However,
+ * we also make a bunch of state changes during the tests above. So we
+ * always change state using the change_state call, with both makes the
+ * change immediately and posts an item in the same queue so that we
+ * make the same state change again later.
+ */
+
+var posted_items = [];
+
+function change_state(func)
+{
+ func();
+ posted_items.push({state: func});
+}
+
+function post_clone_test(srcdoc, testfunc)
+{
+ posted_items.push({srcdoc, testfunc});
+}
+
+function handle_posted_items()
+{
+ if (posted_items.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+
+ if ("state" in posted_items[0]) {
+ var item = posted_items.shift();
+ item.state();
+ handle_posted_items();
+ return;
+ }
+
+ var srcdoc = posted_items[0].srcdoc;
+ iframe.onload = handle_iframe_onload;
+ iframe.srcdoc = srcdoc;
+}
+
+function handle_iframe_onload(event)
+{
+ if (event.target != iframe)
+ return;
+
+ var item = posted_items.shift();
+ item.testfunc();
+ handle_posted_items();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_media_queries_dynamic.html b/layout/style/test/test_media_queries_dynamic.html
new file mode 100644
index 0000000000..52e5fda9ca
--- /dev/null
+++ b/layout/style/test/test_media_queries_dynamic.html
@@ -0,0 +1,207 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=473400
+-->
+<head>
+ <title>Test for Bug 473400</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=473400">Mozilla Bug 473400</a>
+<iframe id="subdoc" src="about:blank"></iframe>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 473400 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var subdoc = document.getElementById("subdoc").contentDocument;
+ var subwin = document.getElementById("subdoc").contentWindow;
+ var style = subdoc.createElement("style");
+ style.setAttribute("type", "text/css");
+ subdoc.getElementsByTagName("head")[0].appendChild(style);
+ var sheet = style.sheet;
+ var iframe_style = document.getElementById("subdoc").style;
+
+ // Create a style rule and an element now based on the given media
+ // query "q", and return the computed style that should be passed to
+ // query_applies to see if that query currently applies.
+ var n = 0;
+ function make_query(q) {
+ var i = ++n;
+ sheet.insertRule("@media " + q + " { #e" + i + " { text-decoration: underline; } }", sheet.cssRules.length);
+ var e = subdoc.createElement("div");
+ e.id = "e" + i;
+ subdoc.body.appendChild(e);
+ var cs = subdoc.defaultView.getComputedStyle(e);
+ cs._originalQueryText = q;
+ return cs;
+ }
+ function query_applies(cs) {
+ return cs.getPropertyValue("text-decoration-line") == "underline";
+ }
+
+ function should_apply(cs) {
+ ok(query_applies(cs), cs._originalQueryText + " should apply");
+ }
+
+ function should_not_apply(cs) {
+ ok(!query_applies(cs), cs._originalQueryText + " should not apply");
+ }
+
+ var content_div = document.getElementById("content");
+ content_div.style.font = "initial";
+ var em_size =
+ getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1];
+
+ let width_val = 317; // pick two not-too-round numbers
+ let height_val = 228;
+ iframe_style.width = width_val + "px";
+ iframe_style.height = height_val + "px";
+ var wh_queries = [
+ make_query("all and (min-width: " +
+ (Math.ceil(width_val/em_size) + 1) + "em)"),
+ make_query("all and (min-width: " +
+ (Math.floor(width_val/em_size) - 1) + "em)"),
+ make_query("all and (max-width: " +
+ (Math.ceil(width_val/em_size) + 1) + "em)"),
+ make_query("all and (max-width: " +
+ (Math.floor(width_val/em_size) - 1) + "em)"),
+ make_query("all and (min-width: " +
+ (Math.ceil(width_val/(em_size*2)) + 1) + "em)"),
+ make_query("all and (min-width: " +
+ (Math.floor(width_val/(em_size*2)) - 1) + "em)"),
+ make_query("all and (max-width: " +
+ (Math.ceil(width_val/(em_size*2)) + 1) + "em)"),
+ make_query("all and (max-width: " +
+ (Math.floor(width_val/(em_size*2)) - 1) + "em)")
+ ];
+
+ is(wh_queries[0].fontSize, em_size + "px", "text zoom is 1.0");
+ should_not_apply(wh_queries[0]);
+ should_apply(wh_queries[1]);
+ should_apply(wh_queries[2]);
+ should_not_apply(wh_queries[3]);
+ SpecialPowers.setTextZoom(subwin, 2.0);
+ isnot(wh_queries[0].fontSize, em_size + "px", "text zoom is not 1.0");
+ should_not_apply(wh_queries[4]);
+ should_apply(wh_queries[5]);
+ should_apply(wh_queries[6]);
+ should_not_apply(wh_queries[7]);
+ SpecialPowers.setTextZoom(subwin, 1.0);
+ is(wh_queries[0].fontSize, em_size + "px", "text zoom is 1.0");
+ is(subwin.innerHeight, 228, "full zoom is 1.0");
+ should_not_apply(wh_queries[0]);
+ should_apply(wh_queries[1]);
+ should_apply(wh_queries[2]);
+ should_not_apply(wh_queries[3]);
+ SpecialPowers.setFullZoom(subwin, 2.0);
+ isnot(subwin.innerHeight, 228, "full zoom is not 1.0");
+ should_not_apply(wh_queries[4]);
+ should_apply(wh_queries[5]);
+ should_apply(wh_queries[6]);
+ should_not_apply(wh_queries[7]);
+ SpecialPowers.setFullZoom(subwin, 1.0);
+ is(subwin.innerHeight, 228, "full zoom is 1.0");
+
+
+ // Now test that certain things *don't* happen, i.e., that we're
+ // making the optimizations we expect.
+ subdoc.body.textContent = "";
+ subdoc.body.appendChild(subdoc.createElement("div"));
+ for (var ruleIdx = sheet.cssRules.length; ruleIdx-- != 0; ) {
+ sheet.deleteRule(ruleIdx);
+ }
+
+ var utils = SpecialPowers.getDOMWindowUtils(subwin);
+ var restyleGeneration, framesConstructed, framesReflowed;
+ function reset_change_counters()
+ {
+ restyleGeneration = utils.restyleGeneration;
+ framesConstructed = utils.framesConstructed;
+ framesReflowed = utils.framesReflowed;
+ }
+
+ function flush_and_assert_change_counters(desc, expected) {
+ subdoc.body.offsetHeight;
+
+ if (!("restyle" in expected) ||
+ !("construct" in expected) ||
+ !("reflow" in expected)) {
+ ok(false, "parameter missing expectation");
+ return;
+ }
+
+ var didRestyle = utils.restyleGeneration != restyleGeneration;
+ var constructs = utils.framesConstructed - framesConstructed;
+ var reflows = utils.framesReflowed - framesReflowed;
+
+ (expected.restyle ? isnot : is)(didRestyle, false, "restyle: " + desc);
+ (expected.construct ? isnot : is)(constructs, 0,
+ "frame construct count: " + desc);
+ (expected.reflow ? isnot : is)(reflows, 0, "reflow count: " + desc);
+
+ reset_change_counters();
+ }
+
+ subdoc.body.offsetHeight;
+ reset_change_counters();
+
+ iframe_style.width = "103px";
+ flush_and_assert_change_counters("change width with no media queries",
+ { restyle: false, construct: false, reflow: true });
+
+ flush_and_assert_change_counters("no change",
+ { restyle: false, construct: false, reflow: false });
+
+ iframe_style.height = "123px";
+ flush_and_assert_change_counters("change height with no media queries",
+ { restyle: false, construct: false, reflow: true });
+
+ sheet.insertRule("@media (min-width: 150px) { div { display:flex } }", 0);
+ flush_and_assert_change_counters("add non-matching media query",
+ { restyle: false, construct: false, reflow: false });
+
+ iframe_style.width = "177px";
+ flush_and_assert_change_counters("resize width across media query with 'display'",
+ { restyle: true, construct: true, reflow: true });
+
+ iframe_style.width = "162px";
+ flush_and_assert_change_counters("resize width without crossing media query",
+ { restyle: false, construct: false, reflow: true });
+
+ sheet.deleteRule(0);
+ flush_and_assert_change_counters("remove matching media query with 'display'",
+ { restyle: true, construct: true, reflow: true });
+
+ sheet.insertRule("@media (max-height: 150px) { div { display:flex } }", 0);
+ flush_and_assert_change_counters("add matching media query with 'display'",
+ { restyle: true, construct: true, reflow: true });
+
+ iframe_style.height = "111px";
+ flush_and_assert_change_counters("resize height without crossing media query",
+ { restyle: false, construct: false, reflow: true });
+
+ iframe_style.height = "184px";
+ flush_and_assert_change_counters("resize height across media query with 'display'",
+ { restyle: true, construct: true, reflow: true });
+
+ sheet.deleteRule(0);
+ flush_and_assert_change_counters("remove non-matching media query",
+ { restyle: false, construct: false, reflow: false });
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_media_query_list.html b/layout/style/test/test_media_query_list.html
new file mode 100644
index 0000000000..5f213117e5
--- /dev/null
+++ b/layout/style/test/test_media_query_list.html
@@ -0,0 +1,373 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=542058
+-->
+<head>
+ <title>Test for MediaQueryList (Bug 542058)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=542058">Mozilla Bug 542058</a>
+<iframe id="subdoc" src="about:blank"></iframe>
+<div id="content" style="display:none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for MediaQueryList (Bug 542058) **/
+
+SimpleTest.waitForExplicitFinish();
+
+function tick() {
+ // MediaQueryList events are guaranteed to run before requestAnimationFrame
+ // per spec.
+ return new Promise(r => requestAnimationFrame(r));
+}
+
+async function run() {
+ var iframe = document.getElementById("subdoc");
+ var subdoc = iframe.contentDocument;
+ var subwin = iframe.contentWindow;
+ var subroot = subdoc.documentElement;
+
+ var content_div = document.getElementById("content");
+ content_div.style.font = "initial";
+ var em_size =
+ getComputedStyle(content_div).fontSize.match(/^(\d+)px$/)[1];
+
+ var w = Math.floor(em_size * 9.3);
+ var h = Math.floor(em_size * 4.2);
+ iframe.style.width = w + "px";
+ iframe.style.height = h + "px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ function setup_mql(str) {
+ var obj = {
+ str: str,
+ mql: subwin.matchMedia(str),
+ notifyCount: 0,
+ listener: function(event) {
+ ok(event instanceof subwin.MediaQueryListEvent,
+ "correct argument to listener: " + obj.str);
+ is(event.media, obj.mql.media,
+ "correct media in the event: " + obj.str);
+ is(event.target, obj.mql,
+ "correct target in the event: " + obj.str);
+ ++obj.notifyCount;
+ // Test the last match result only on odd
+ // notifications.
+ if (obj.notifyCount & 1) {
+ obj.lastOddMatchResult = event.target.matches;
+ }
+ }
+ }
+ obj.mql.addListener(obj.listener);
+ return obj;
+ }
+
+ function finish_mql(obj) {
+ obj.mql.removeListener(obj.listener);
+ }
+
+ var w_exact_w = setup_mql("(width: " + w + "px)");
+ var w_min_9em = setup_mql("(min-width : 9em)");
+ var w_min_10em = setup_mql("( min-width: 10em ) ");
+ var w_max_9em = setup_mql("(max-width: 9em)");
+ var w_max_10em = setup_mql("(max-width: 10em)");
+
+ is(w_exact_w.mql.media, "(width: " + w + "px)", "serialization");
+ is(w_min_9em.mql.media, "(min-width: 9em)", "serialization");
+ is(w_min_10em.mql.media, "(min-width: 10em)", "serialization");
+ is(w_max_9em.mql.media, "(max-width: 9em)", "serialization");
+ is(w_max_10em.mql.media, "(max-width: 10em)", "serialization");
+
+ function check_match(obj, expected, desc) {
+ is(obj.mql.matches, expected,
+ obj.str + " media query list .matches " + desc);
+ if (obj.notifyCount & 1) { // odd notifications only
+ is(obj.lastOddMatchResult, expected,
+ obj.str + " media query list last notify result " + desc);
+ }
+ }
+ function check_notify(obj, expected, desc) {
+ is(obj.notifyCount, expected,
+ obj.str + " media query list .notify count " + desc);
+ }
+ check_match(w_exact_w, true, "initially");
+ check_notify(w_exact_w, 0, "initially");
+ check_match(w_min_9em, true, "initially");
+ check_notify(w_min_9em, 0, "initially");
+ check_match(w_min_10em, false, "initially");
+ check_notify(w_min_10em, 0, "initially");
+ check_match(w_max_9em, false, "initially");
+ check_notify(w_max_9em, 0, "initially");
+ check_match(w_max_10em, true, "initially");
+ check_notify(w_max_10em, 0, "initially");
+
+ var w2 = Math.floor(em_size * 10.3);
+ iframe.style.width = w2 + "px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ check_match(w_exact_w, false, "after width increase to around 10.3em");
+ check_notify(w_exact_w, 1, "after width increase to around 10.3em");
+ check_match(w_min_9em, true, "after width increase to around 10.3em");
+ check_notify(w_min_9em, 0, "after width increase to around 10.3em");
+ check_match(w_min_10em, true, "after width increase to around 10.3em");
+ check_notify(w_min_10em, 1, "after width increase to around 10.3em");
+ check_match(w_max_9em, false, "after width increase to around 10.3em");
+ check_notify(w_max_9em, 0, "after width increase to around 10.3em");
+ check_match(w_max_10em, false, "after width increase to around 10.3em");
+ check_notify(w_max_10em, 1, "after width increase to around 10.3em");
+
+ var w3 = w * 2;
+ iframe.style.width = w3 + "px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ check_match(w_exact_w, false, "after width double from original");
+ check_notify(w_exact_w, 1, "after width double from original");
+ check_match(w_min_9em, true, "after width double from original");
+ check_notify(w_min_9em, 0, "after width double from original");
+ check_match(w_min_10em, true, "after width double from original");
+ check_notify(w_min_10em, 1, "after width double from original");
+ check_match(w_max_9em, false, "after width double from original");
+ check_notify(w_max_9em, 0, "after width double from original");
+ check_match(w_max_10em, false, "after width double from original");
+ check_notify(w_max_10em, 1, "after width double from original");
+
+ SpecialPowers.setFullZoom(subwin, 2.0);
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ check_match(w_exact_w, true, "after zoom");
+ check_notify(w_exact_w, 2, "after zoom");
+ check_match(w_min_9em, true, "after zoom");
+ check_notify(w_min_9em, 0, "after zoom");
+ check_match(w_min_10em, false, "after zoom");
+ check_notify(w_min_10em, 2, "after zoom");
+ check_match(w_max_9em, false, "after zoom");
+ check_notify(w_max_9em, 0, "after zoom");
+ check_match(w_max_10em, true, "after zoom");
+ check_notify(w_max_10em, 2, "after zoom");
+
+ SpecialPowers.setFullZoom(subwin, 1.0);
+
+ await tick();
+
+ finish_mql(w_exact_w);
+ finish_mql(w_min_9em);
+ finish_mql(w_min_10em);
+ finish_mql(w_max_9em);
+ finish_mql(w_max_10em);
+
+ // Additional tests of listener mutation.
+ {
+ let received = [];
+ let received_mql = [];
+ function listener1(event) {
+ received.push(1);
+ received_mql.push(event.target);
+ }
+ function listener2(event) {
+ received.push(2);
+ received_mql.push(event.target);
+ }
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ let mql = subwin.matchMedia("(min-width: 150px)");
+ mql.addListener(listener1);
+ mql.addListener(listener1);
+ mql.addListener(listener2);
+ is(JSON.stringify(received), "[]", "listeners before notification");
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[1,2]", "duplicate listeners removed");
+ received = [];
+ mql.removeListener(listener1);
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[2]", "listener removal");
+ received = [];
+ mql.addListener(listener1);
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[2,1]", "listeners notified in order");
+ received = [];
+ mql.addListener(listener2);
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[2,1]", "add of existing listener is no-op");
+ received = [];
+ mql.addListener(listener1);
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(received), "[2,1]", "add of existing listener is no-op");
+ mql.removeListener(listener2);
+ received = [];
+ received_mql = [];
+
+ var mql2 = subwin.matchMedia("(min-width: 160px)");
+ mql2.addListener(listener1);
+ mql.addListener(listener2);
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ // mql (1, 2), mql2 (1)
+ is(JSON.stringify(received), "[1,2,1]",
+ "notification of lists in order created");
+ is(received_mql[0], mql,
+ "notification of lists in order created");
+ is(received_mql[1], mql,
+ "notification of lists in order created");
+ is(received_mql[2], mql2,
+ "notification of lists in order created");
+ received = [];
+ received_mql = [];
+
+ function removing_listener(event) {
+ received.push(3);
+ received_mql.push(event.target);
+ event.target.removeListener(listener2);
+ mql2.removeListener(listener1);
+ }
+
+ mql.addListener(removing_listener);
+ mql.removeListener(listener2);
+ mql.addListener(listener2); // after removing_listener (3)
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ // mql(1, 3)
+ is(JSON.stringify(received), "[1,3]",
+ "listeners still notified after removed if change was before");
+ is(received_mql[0], mql,
+ "notification order (removal tests)");
+ is(received_mql[1], mql,
+ "notification order (removal tests)");
+ received = [];
+ received_mql = [];
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ // mql(1, 3)
+ is(JSON.stringify(received), "[1,3]",
+ "listeners not notified for changes after their removal");
+ is(received_mql[0], mql,
+ "notification order (removal tests)");
+ is(received_mql[1], mql,
+ "notification order (removal tests)");
+ }
+
+ /* Bug 753777: test that things work in a freshly-created iframe */
+ {
+ let newIframe = document.createElement("iframe");
+ document.body.appendChild(newIframe);
+
+ is(newIframe.contentWindow.matchMedia("all").matches, true,
+ "matchMedia should work in newly-created iframe");
+ is(newIframe.contentWindow.matchMedia("(min-width: 1px)").matches, true,
+ "(min-width: 1px) should match in newly-created iframe");
+ is(newIframe.contentWindow.matchMedia("(max-width: 1px)").matches, false,
+ "(max-width: 1px) should not match in newly-created iframe");
+
+ document.body.removeChild(newIframe);
+ }
+
+ /* Bug 716751: listeners lost due to GC */
+ var gc_received = [];
+ {
+ let received = [];
+ let listener1 = function(event) {
+ gc_received.push(1);
+ }
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ let mql = subwin.matchMedia("(min-width: 150px)");
+ mql.addListener(listener1);
+ is(JSON.stringify(gc_received), "[]", "GC test: before notification");
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(gc_received), "[1]", "GC test: after notification 1");
+
+ // Because of conservative GC, we need to go back to the event loop
+ // to GC properly.
+ setTimeout(step2, 0);
+ }
+
+ async function step2() {
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(JSON.stringify(gc_received), "[1,1]", "GC test: after notification 2");
+
+ bug1270626();
+ }
+
+ /* Bug 1270626: listeners that throw exceptions */
+ async function bug1270626() {
+ var throwingListener = function(event) {
+ throw "error";
+ }
+
+ iframe.style.width = "200px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ var mql = subwin.matchMedia("(min-width: 150px)");
+ mql.addListener(throwingListener);
+
+ SimpleTest.expectUncaughtException(true);
+ is(SimpleTest.isExpectingUncaughtException(), true,
+ "should be waiting for an uncaught exception");
+
+ iframe.style.width = "100px";
+ subroot.offsetWidth; // flush layout
+ await tick();
+
+ is(SimpleTest.isExpectingUncaughtException(), false,
+ "should have gotten an uncaught exception");
+
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_media_query_serialization.html b/layout/style/test/test_media_query_serialization.html
new file mode 100644
index 0000000000..ed82653db0
--- /dev/null
+++ b/layout/style/test/test_media_query_serialization.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test media query list serialization</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style>
+ @media PrInT {}
+ @media screen, PrInT, SPEECH {}
+
+ @media GARbAGE7 {}
+ @media AAAAA {}
+ @media ZZZZZ {}
+
+ @media NotAValidMediaType_!000 {}
+ @media NotAValidMediaType_!000, AlsoInvalid!!!! {}
+
+ @media PrInT, GARbAGE7, notAValidMediaType_!000 {}
+</style>
+<script type="application/javascript">
+
+let expectedValues = [
+ // Valid types
+ ["print", "Valid media types are ascii lowercased."],
+ ["screen, print, speech", "Media query lists with only valid types are ascii lowercased."],
+
+ // Invalid types
+ ["garbage7", "Invalid media types are ascii lowercased."],
+ ["aaaaa", "Ascii conversion handles 'A' correctly."],
+ ["zzzzz", "Ascii conversion handles 'Z' correctly."],
+
+ // Malformed types
+ ["not all", "Malformed media types are serialized to 'not all'."],
+ ["not all, not all", "Multiple malformed media types are each serialized to 'not all'."],
+
+ // Mixes
+ ["print, garbage7, not all", "Media query lists with a mix of valid, invalid, and malformed types serialize " +
+ "as lowercase with malformed types changed to 'not all'."],
+];
+
+let sheet = document.styleSheets[1];
+
+expectedValues.forEach(function (entry, index) {
+ let rule = sheet.cssRules[index];
+ let serializedList = rule.media.mediaText;
+ is(serializedList, entry[0], entry[1]);
+});
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/style/test/test_moz_device_pixel_ratio.html b/layout/style/test/test_moz_device_pixel_ratio.html
new file mode 100644
index 0000000000..0e3e143fe8
--- /dev/null
+++ b/layout/style/test/test_moz_device_pixel_ratio.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=474356
+-->
+<head>
+ <title>Test for Bug 474356</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>.zoom-test { visibility: hidden; }</style>
+ <style><!-- placeholder for dynamic additions --></style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=474356">Mozilla Bug 474356</a>
+<div id="content" style="display: none">
+
+</div>
+<script type="text/javascript">
+</script>
+<pre id="test">
+<div id="zoom1" class="zoom-test"></div>
+<div id="zoom2" class="zoom-test"></div>
+<div id="zoom3" class="zoom-test"></div>
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 474356 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ function zoom(factor) {
+ var previous = SpecialPowers.getFullZoom(window);
+ SpecialPowers.setFullZoom(window, factor);
+ return previous;
+ }
+
+ function isVisible(divName) {
+ return window.getComputedStyle(document.getElementById(divName)).visibility == "visible";
+ }
+
+ var screenPixelsPerCSSPixel = window.devicePixelRatio;
+ var baseRatio = 1.0 * screenPixelsPerCSSPixel;
+ var doubleRatio = 2.0 * screenPixelsPerCSSPixel;
+ var halfRatio = 0.5 * screenPixelsPerCSSPixel;
+ var styleElem = document.getElementsByTagName("style")[1];
+ styleElem.textContent =
+ ["@media all and (-moz-device-pixel-ratio: " + baseRatio + ") {",
+ "#zoom1 { visibility: visible; }",
+ "}",
+ "@media all and (-moz-device-pixel-ratio: " + doubleRatio + ") {",
+ "#zoom2 { visibility: visible; }",
+ "}",
+ "@media all and (-moz-device-pixel-ratio: " + halfRatio + ") {",
+ "#zoom3 { visibility: visible; }",
+ "}"
+ ].join("\n");
+
+ ok(isVisible("zoom1"), "Base ratio rule should apply at base zoom level");
+ ok(!isVisible("zoom2") && !isVisible("zoom3"), "no other rules should apply");
+ var origZoom = zoom(2);
+ ok(isVisible("zoom2"), "Double ratio rule should apply at double zoom level");
+ ok(!isVisible("zoom1") && !isVisible("zoom3"), "no other rules should apply");
+ zoom(0.5);
+ ok(isVisible("zoom3"), "Half ratio rule should apply at half zoom level");
+ ok(!isVisible("zoom1") && !isVisible("zoom2"), "no other rules should apply");
+ zoom(origZoom);
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_moz_prefixed_cursor.html b/layout/style/test/test_moz_prefixed_cursor.html
new file mode 100644
index 0000000000..db7ffaaf56
--- /dev/null
+++ b/layout/style/test/test_moz_prefixed_cursor.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>Cursor aliases compute to the unprefixed keyword</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div></div>
+<script>
+test(function() {
+ let div = document.querySelector("div");
+ for (const kw of ["grab", "grabbing", "zoom-in", "zoom-out"]) {
+ div.style.cursor = "-moz-" + kw;
+ assert_equals(getComputedStyle(div).cursor, kw);
+ }
+}, "-moz- prefixed cursor keywords compute to its unprefixed version");
+</script>
diff --git a/layout/style/test/test_mq_any_hover_and_any_pointer.html b/layout/style/test/test_mq_any_hover_and_any_pointer.html
new file mode 100644
index 0000000000..1725af0725
--- /dev/null
+++ b/layout/style/test/test_mq_any_hover_and_any_pointer.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1483111
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1035774</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1483111">Mozilla Bug 1483111</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+
+const NO_POINTER = 0x00;
+const COARSE_POINTER = 0x01;
+const FINE_POINTER = 0x02;
+const HOVER_CAPABLE_POINTER = 0x04;
+
+var isAndroid = navigator.appVersion.includes("Android");
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['privacy.resistFingerprinting', true] ]
+ });
+
+ // When resistFingerprinting is enabled, we pretend that the system has a
+ // mouse pointer (or finger on mobile).
+ let invertIfAndroid = function(b) { return isAndroid ? !b : b; };
+
+ ok(!matchMedia("(any-pointer: none)").matches,
+ "Doesn't match (any-pointer: none)");
+ ok(matchMedia("(any-pointer)").matches, "Matches (any-pointer)");
+
+ ok(invertIfAndroid(!matchMedia("(any-pointer: coarse)").matches),
+ "Doesn't match (any-pointer: coarse)");
+ ok(invertIfAndroid(matchMedia("(any-pointer: fine)").matches), "Matches (any-pointer: fine)");
+
+ ok(invertIfAndroid(!matchMedia("(any-hover: none)").matches),
+ "Doesn't match (any-hover: none)");
+ ok(invertIfAndroid(matchMedia("(any-hover: hover)").matches),
+ "Matches (any-hover: hover)");
+ ok(invertIfAndroid(matchMedia("(any-hover)").matches), "Matches (any-hover)");
+
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async () => {
+ // No pointer.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.allPointerCapabilities', NO_POINTER] ]
+ });
+
+ ok(matchMedia("(any-pointer: none)").matches, "Matches (any-pointer: none)");
+ ok(!matchMedia("(any-pointer: coarse)").matches,
+ "Doesn't match (any-pointer: coarse)");
+ ok(!matchMedia("(any-pointer: fine)").matches,
+ "Doesn't match (any-pointer: fine)");
+ ok(!matchMedia("(any-pointer)").matches, "Matches (any-pointer)");
+
+ ok(matchMedia("(any-hover: none)").matches, "Matches (any-hover: none)");
+ ok(!matchMedia("(any-hover: hover)").matches,
+ "Doesn't match (any-hover: hover)");
+ ok(!matchMedia("(any-hover)").matches, "Doesn't match (any-hover)");
+});
+
+add_task(async () => {
+ // Mouse type pointer and touchscreen
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.allPointerCapabilities',
+ FINE_POINTER | COARSE_POINTER | HOVER_CAPABLE_POINTER] ]
+ });
+
+ ok(!matchMedia("(any-pointer: none)").matches,
+ "Doesn't match (any-pointer: none)");
+ ok(matchMedia("(any-pointer: coarse)").matches,
+ "Matches (any-pointer: coarse)");
+ ok(matchMedia("(any-pointer: fine)").matches, "Matches (any-pointer: fine)");
+ ok(matchMedia("(any-pointer)").matches, "Matches (any-pointer)");
+
+ ok(!matchMedia("(any-hover: none)").matches,
+ "Doesn't match (any-hover: none)");
+ ok(matchMedia("(any-hover: hover)").matches,
+ "Matches (any-hover: hover)");
+ ok(matchMedia("(any-hover)").matches, "Matches (any-hover)");
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mq_changes_in_iframe.html b/layout/style/test/test_mq_changes_in_iframe.html
new file mode 100644
index 0000000000..3a36476c42
--- /dev/null
+++ b/layout/style/test/test_mq_changes_in_iframe.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Media feature value change propagation in an iframe</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<iframe id="iframe"></iframe>
+<pre id="test"></pre>
+<script>
+add_task(async () => {
+ const mqString = "(prefers-reduced-motion: reduce)";
+
+ await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 0]]});
+
+ iframe.src = SimpleTest.getTestFileURL("mq_changes_child.html")
+ .replace("mochi.test:8888", "example.com");
+ await new Promise(resolve => window.addEventListener("message", event => {
+ if (event.data == "ready") {
+ resolve();
+ }
+ }, { once: true } ));
+
+ const mql = matchMedia(mqString);
+ ok(!mql.matches, `Doesn't matches ${mqString}`);
+
+ const changedInThisDocument = new Promise(resolve => {
+ mql.addEventListener("change", event => { resolve(event.matches); });
+ });
+ const changedInIFrame = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if ("matches" in event.data) {
+ resolve(event.data.matches);
+ }
+ }, { once: true });
+ });
+
+ await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 1]]});
+
+ const results =
+ await Promise.allSettled([ changedInThisDocument, changedInIFrame ]);
+
+ results.forEach(result => {
+ is(result.status, "fulfilled");
+ ok(result.value, `Matches ${mqString}`);
+ });
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mq_hover_and_pointer.html b/layout/style/test/test_mq_hover_and_pointer.html
new file mode 100644
index 0000000000..40eaed5d0b
--- /dev/null
+++ b/layout/style/test/test_mq_hover_and_pointer.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1035774
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1035774</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1035774">Mozilla Bug 1035774</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+
+const NO_POINTER = 0x00;
+const COARSE_POINTER = 0x01;
+const FINE_POINTER = 0x02;
+const HOVER_CAPABLE_POINTER = 0x04;
+
+var isAndroid = navigator.appVersion.includes("Android");
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['privacy.resistFingerprinting', true] ]
+ });
+
+ // When resistFingerprinting is enabled, we pretend that the system has a
+ // mouse pointer (or finger on mobile).
+ let invertIfAndroid = function(b) { return isAndroid ? !b : b; };
+
+ ok(!matchMedia("(pointer: none)").matches,
+ "Doesn't match (pointer: none)");
+
+ ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(invertIfAndroid(!matchMedia("(pointer: coarse)").matches),
+ "Doesn't match (pointer: coarse)");
+ ok(invertIfAndroid(matchMedia("(pointer: fine)").matches), "Matches (pointer: fine)");
+
+ ok(invertIfAndroid(!matchMedia("(hover: none)").matches), "Doesn't match (hover: none)");
+ ok(invertIfAndroid(matchMedia("(hover: hover)").matches), "Matches (hover: hover)");
+ ok(invertIfAndroid(matchMedia("(hover)").matches), "Matches (hover)");
+
+ await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async () => {
+ // No pointer.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.primaryPointerCapabilities', NO_POINTER] ]
+ });
+
+ ok(matchMedia("(pointer: none)").matches, "Matches (pointer: none)");
+ ok(!matchMedia("(pointer: coarse)").matches,
+ "Doesn't match (pointer: coarse)");
+ ok(!matchMedia("(pointer: fine)").matches, "Doesn't match (pointer: fine)");
+ ok(!matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(matchMedia("(hover: none)").matches, "Matches (hover: none)");
+ ok(!matchMedia("(hover: hover)").matches, "Doesn't match (hover: hover)");
+ ok(!matchMedia("(hover)").matches, "Doesn't match (hover)");
+});
+
+add_task(async () => {
+ // Mouse type pointer.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.primaryPointerCapabilities', FINE_POINTER | HOVER_CAPABLE_POINTER] ]
+ });
+
+ ok(!matchMedia("(pointer: none)").matches,
+ "Doesn't match (pointer: none)");
+ ok(!matchMedia("(pointer: coarse)").matches,
+ "Doesn't match (pointer: coarse)");
+ ok(matchMedia("(pointer: fine)").matches, "Matches (pointer: fine)");
+ ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(!matchMedia("(hover: none)").matches, "Doesn't match (hover: none)");
+ ok(matchMedia("(hover: hover)").matches, "Matches (hover: hover)");
+ ok(matchMedia("(hover)").matches, "Matches (hover)");
+});
+
+add_task(async () => {
+ // Mouse type pointer.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.primaryPointerCapabilities',
+ FINE_POINTER |
+ HOVER_CAPABLE_POINTER] ]
+ });
+
+ ok(!matchMedia("(pointer: none)").matches,
+ "Doesn't match (pointer: none)");
+ ok(!matchMedia("(pointer: coarse)").matches,
+ "Doesn't match (pointer: coarse)");
+ ok(matchMedia("(pointer: fine)").matches, "Matches (pointer: fine)");
+ ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(!matchMedia("(hover: none)").matches, "Doesn't match (hover: none)");
+ ok(matchMedia("(hover: hover)").matches, "Matches (hover: hover)");
+ ok(matchMedia("(hover)").matches, "Matches (hover)");
+});
+
+add_task(async () => {
+ // Touch screen.
+ await SpecialPowers.pushPrefEnv({
+ set: [ ['ui.primaryPointerCapabilities', COARSE_POINTER] ]
+ });
+
+ ok(!matchMedia("(pointer: none)").matches, "Doesn't match (pointer: none)");
+ ok(matchMedia("(pointer: coarse)").matches, "Matches (pointer: coarse)");
+ ok(!matchMedia("(pointer: fine)").matches, "Doesn't match (pointer: fine)");
+ ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+ ok(matchMedia("(hover: none)").matches, "Match (hover: none)");
+ ok(!matchMedia("(hover: hover)").matches, "Doesn't match (hover: hover)");
+ ok(!matchMedia("(hover)").matches, "Doesn't match (hover)");
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mq_prefers_contrast_dynamic.html b/layout/style/test/test_mq_prefers_contrast_dynamic.html
new file mode 100644
index 0000000000..c3ccebbe82
--- /dev/null
+++ b/layout/style/test/test_mq_prefers_contrast_dynamic.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1691793
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1691793</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1691793">Mozilla Bug 1691793</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+"use strict";
+
+SimpleTest.registerCleanupFunction(async () => {
+ await SpecialPowers.flushPrefEnv();
+});
+
+// Returns a Promise which will be resolved when the "change" event is received
+// for the given media query string.
+function promiseForChange(mediaQuery) {
+ return new Promise(resolve => {
+ window.matchMedia(mediaQuery).addEventListener("change", event => {
+ resolve(event.matches);
+ }, { once: true });
+ });
+}
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({ set: [["layout.css.prefers-contrast.enabled", true]]});
+ const initiallyMatches =
+ window.matchMedia("(prefers-contrast: more)").matches;
+ const changePromise = initiallyMatches ?
+ promiseForChange("(prefers-contrast: more)") : null;
+ await SpecialPowers.pushPrefEnv({ set: [["ui.useAccessibilityTheme", 0]]});
+
+ if (changePromise) {
+ await changePromise;
+ }
+
+ ok(!window.matchMedia("(prefers-contrast: more)").matches,
+ "Does not match prefers-contrast: more) when the system unsets " +
+ "UseAccessibilityTheme");
+ ok(!window.matchMedia("(prefers-contrast)").matches,
+ "Does not match (prefers-contrast) when the system unsets " +
+ "UseAccessibilityTheme");
+ ok(window.matchMedia("(prefers-contrast: no-preference)").matches,
+ "Matches (prefers-contrast: no-preference) when the system unsets " +
+ "UseAccessibilityTheme");
+});
+
+add_task(async () => {
+ const more = promiseForChange("(prefers-contrast: more)");
+ const booleanContext = promiseForChange("(prefers-contrast)");
+ const noPreference = promiseForChange("(prefers-contrast: no-preference)");
+
+ await SpecialPowers.pushPrefEnv({ set: [["ui.useAccessibilityTheme", 1]]});
+
+ const [ moreResult, booleanContextResult, noPreferenceResult ] =
+ await Promise.all([ more, booleanContext, noPreference ]);
+
+ ok(moreResult,
+ "Matches (prefers-contrast: more) when the system sets " +
+ "UseAccessibilityTheme");
+ ok(booleanContextResult,
+ "Matches (prefers-contrast) when the system sets UseAccessibilityTheme");
+ ok(!noPreferenceResult,
+ "Does not match (prefers-contrast: no-preference) when the " +
+ "system sets UseAccessibilityTheme");
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mq_prefers_reduced_motion_dynamic.html b/layout/style/test/test_mq_prefers_reduced_motion_dynamic.html
new file mode 100644
index 0000000000..570c0d3954
--- /dev/null
+++ b/layout/style/test/test_mq_prefers_reduced_motion_dynamic.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1486971
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1478519</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1486971">Mozilla Bug 1486971</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+'use strict';
+
+async function waitForFrame() {
+ return new Promise(resolve => {
+ window.requestAnimationFrame(resolve);
+ });
+}
+
+// Returns a Promise which will be resolved when the 'change' event is received
+// for the given media query string.
+async function promiseForChange(mediaQuery) {
+ return new Promise(resolve => {
+ window.matchMedia(mediaQuery).addEventListener('change', event => {
+ resolve(event.matches);
+ }, { once: true });
+ });
+}
+
+add_task(async () => {
+ const initiallyMatches =
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+
+ const changePromise = initiallyMatches ? promiseForChange('(prefers-reduced-motion: reduce)') : null;
+
+ await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 0]]});
+
+ if (changePromise) {
+ await changePromise;
+ }
+
+ ok(!window.matchMedia('(prefers-reduced-motion: reduce)').matches,
+ 'Does not matches prefers-reduced-motion: reduced) when the system sets ' +
+ 'prefers-reduced-motion false');
+ ok(!window.matchMedia('(prefers-reduced-motion)').matches,
+ 'Does not matches (prefers-reduced-motion) when the system sets ' +
+ 'prefers-reduced-motion false');
+ ok(window.matchMedia('(prefers-reduced-motion: no-preference)').matches,
+ 'Matches (prefers-reduced-motion: no-preference) when the system sets ' +
+ 'prefers-reduced-motion false');
+});
+
+add_task(async () => {
+ const reduce = promiseForChange('(prefers-reduced-motion: reduce)');
+ const booleanContext = promiseForChange('(prefers-reduced-motion)');
+ const noPreference = promiseForChange('(prefers-reduced-motion: no-preference)');
+
+ await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 1]]});
+
+ const [ reduceResult, booleanContextResult, noPreferenceResult ] =
+ await Promise.all([ reduce, booleanContext, noPreference ]);
+
+ ok(reduceResult,
+ 'Matches (prefers-reduced-motion: reduced) when the system sets ' +
+ 'prefers-reduced-motion true');
+ ok(booleanContextResult,
+ 'Matches (prefers-reduced-motion) when the system sets ' +
+ 'prefers-reduced-motion true');
+ ok(!noPreferenceResult,
+ 'Does not matches (prefers-reduced-motion: no-preference) when the ' +
+ 'system sets prefers-reduced-motion true');
+
+ await SpecialPowers.flushPrefEnv();
+});
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_mql_event_listener_leaks.html b/layout/style/test/test_mql_event_listener_leaks.html
new file mode 100644
index 0000000000..3ceb5412a2
--- /dev/null
+++ b/layout/style/test/test_mql_event_listener_leaks.html
@@ -0,0 +1,43 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1450271 - Test MediaQueryList event listener leak conditions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+// Manipulate MediaQueryList. Its important here that we create a
+// listener callback from the DOM objects back to the frame's global
+// in order to exercise the leak condition.
+async function useMediaQuery(contentWindow) {
+ contentWindow.messageCount = 0;
+
+ let mql = contentWindow.matchMedia("(max-width: 600px)");
+ mql.onchange = _ => {
+ contentWindow.mediaCount += 1;
+ };
+}
+
+async function runTest() {
+ try {
+ await checkForEventListenerLeaks("MediaQueryList", useMediaQuery);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", runTest, { once: true });
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/style/test/test_namespace_rule.html b/layout/style/test/test_namespace_rule.html
new file mode 100644
index 0000000000..6e8ba52c90
--- /dev/null
+++ b/layout/style/test/test_namespace_rule.html
@@ -0,0 +1,461 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS Namespace rules</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<p id="display"><iframe id="iframe" src="data:application/xhtml+xml,<html%20xmlns='http://www.w3.org/1999/xhtml'><head/><body/></html>"></iframe></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function run() {
+ var wrappedFrame = SpecialPowers.wrap($("iframe"));
+ var ifwin = wrappedFrame.contentWindow;
+ var ifdoc = wrappedFrame.contentDocument;
+ var ifbody = ifdoc.getElementsByTagName("body")[0];
+
+ function setup_style_text() {
+ var style_elem = ifdoc.createElement("style");
+ style_elem.setAttribute("type", "text/css");
+ ifdoc.getElementsByTagName("head")[0].appendChild(style_elem);
+ let style_text = ifdoc.createCDATASection("");
+ style_elem.appendChild(style_text);
+ return style_text;
+ }
+
+ let style_text = setup_style_text();
+ var gCounter = 0;
+
+ /*
+ * namespaceRules: the @namespace rules to use
+ * selector: the selector to test
+ * body_contents: what to set the body's innerHTML to
+ * match_fn: a function that, given the document object into which
+ * body_contents has been inserted, produces an array of nodes that
+ * should match selector
+ * notmatch_fn: likewise, but for nodes that should not match
+ */
+ function test_selector_in_html(namespaceRules, selector, body_contents,
+ match_fn, notmatch_fn)
+ {
+ var zi = ++gCounter;
+ if (typeof(body_contents) == "string") {
+ ifbody.innerHTML = body_contents;
+ } else {
+ // It's a function.
+ ifbody.innerHTML = "";
+ body_contents(ifbody);
+ }
+ style_text.data =
+ namespaceRules + " " + selector + "{ z-index: " + zi + " }";
+ var should_match = match_fn(ifdoc);
+ var should_not_match = notmatch_fn(ifdoc);
+ if (should_match.length + should_not_match.length == 0) {
+ ok(false, "nothing to check");
+ }
+
+ for (var i = 0; i < should_match.length; ++i) {
+ var e = should_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched " + selector);
+ }
+ for (var i = 0; i < should_not_match.length; ++i) {
+ var e = should_not_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match " + selector);
+ }
+
+ // Now, since we're here, may as well make sure serialization
+ // works correctly. It need not produce the exact same text,
+ // but it should produce a selector that matches the same
+ // elements.
+ zi = ++gCounter;
+ var ruleList = style_text.parentNode.sheet.cssRules;
+ var ser1 = ruleList[ruleList.length-1].selectorText;
+ style_text.data =
+ namespaceRules + " " + ser1 + "{ z-index: " + zi + " }";
+ for (var i = 0; i < should_match.length; ++i) {
+ var e = should_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched " + ser1 +
+ " which is the reserialization of " + selector);
+ }
+ for (var i = 0; i < should_not_match.length; ++i) {
+ var e = should_not_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match " + ser1 +
+ " which is the reserialization of " + selector);
+ }
+
+ // But when we serialize the serialized result, we should get
+ // the same text.
+ var ser2 = ruleList[ruleList.length-1].selectorText;
+ is(ser2, ser1, "parse+serialize of selector \"" + selector +
+ "\" is idempotent");
+
+ ifbody.innerHTML = "";
+ style_text.data = "";
+ }
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-001.xml
+ test_selector_in_html(
+ '@namespace foo "x"; @namespace Foo "y";',
+ 'Foo|test',
+ '<test xmlns="y"/>',
+ function (doc) { return doc.getElementsByTagName("test"); },
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace foo "x"; @namespace Foo "y";',
+ 'foo|test',
+ '<test xmlns="y"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-002.xml
+ test_selector_in_html(
+ '@namespace foo "";',
+ 'test',
+ '<test xmlns=""/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace foo "";',
+ 'foo|test',
+ '<test xmlns=""/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-003.xml
+ test_selector_in_html(
+ '@namespace foo "";',
+ 'test',
+ '<foo xmlns=""><test/></foo>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace foo "";',
+ 'foo|test',
+ '<foo xmlns=""><test/></foo>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // 4 tests from http://tc.labs.opera.com/css/namespaces/prefix-004.xml
+ test_selector_in_html(
+ '@namespace ""; @namespace x "test";',
+ 'test[x]',
+ '<foo xmlns=""><test x=""/></foo>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace ""; @namespace x "test";',
+ '*|test',
+ '<foo xmlns=""><test x=""/></foo>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace ""; @namespace x "test";',
+ '*|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace ""; @namespace x "test";',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-005.xml
+ test_selector_in_html(
+ '@namespace x "test";',
+ 'test',
+ '<test/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace x "test";',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // Skipping the scope tests because they involve import, and we have no way
+ // to know when the import load completes.
+
+ // 1 test from http://tc.labs.opera.com/css/namespaces/syntax-001.xml
+ test_selector_in_html(
+ '@NAmespace x "http://www.w3.org/1999/xhtml";',
+ 'x|test',
+ '<test/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // 1 test from http://tc.labs.opera.com/css/namespaces/syntax-002.xml
+ test_selector_in_html(
+ '@NAmespac\\65 x "http://www.w3.org/1999/xhtml";',
+ 'x|test',
+ '<test/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // 3 tests from http://tc.labs.opera.com/css/namespaces/syntax-003.xml
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ 'test',
+ '<test/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // 3 tests from http://tc.labs.opera.com/css/namespaces/syntax-004.xml
+ test_selector_in_html(
+ '@namespace u\\00072l("test");',
+ '*|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace u\\00072l("test");',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace u\\00072l("test");',
+ 'test',
+ '<test/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // Skipping http://tc.labs.opera.com/css/namespaces/syntax-005.xml because it
+ // involves import, and we have no way // to know when the import load completes.
+
+ // Skipping http://tc.labs.opera.com/css/namespaces/syntax-006.xml because it
+ // involves import, and we have no way // to know when the import load completes.
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/syntax-007.xml
+ test_selector_in_html(
+ '@charset "x"; @namespace url("test"); @namespace url("test2");',
+ '*|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@charset "x"; @namespace url("test"); @namespace url("test2");',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ // 2 tests from http://tc.labs.opera.com/css/namespaces/syntax-008.xml
+ test_selector_in_html(
+ '@namespace \\72x url("test");',
+ 'rx|test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace \\72x url("test");',
+ 'test',
+ '<test xmlns="test"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ // And now some :not() tests
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not(test)',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not(test)',
+ '<test xmlns="testing"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace x url("test");',
+ '*|*:not(x|test)',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace x url("test");',
+ '*|*:not(x|test)',
+ '<test xmlns="testing"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not(*)',
+ '<test xmlns="testing"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not(*)',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace x url("test");',
+ '*|*:not(x|*)',
+ '<test xmlns="testing"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace x url("test");',
+ '*|*:not(x|*)',
+ '<test xmlns="test"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not([foo])',
+ '<test xmlns="testing" foo="bar"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*:not([foo])',
+ '<test xmlns="test" foo="bar"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*[foo]',
+ '<test foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*[|foo]',
+ '<test foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace test url("test");',
+ '*|*[test|foo]',
+ '<test foo="bar"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace test url("test");',
+ '*|*[test|foo]',
+ '<test xmlns:t="test" t:foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*[foo]',
+ '<test xmlns:t="test" t:foo="bar"/>',
+ function (doc) { return []; },
+ function (doc) { return doc.getElementsByTagName("test");}
+ );
+
+ test_selector_in_html(
+ '@namespace url("test");',
+ '*|*[*|foo]',
+ '<test xmlns:t="test" t:foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ test_selector_in_html(
+ '',
+ '*|*[*|foo]',
+ '<test xmlns:t="test" t:foo="bar"/>',
+ function (doc) { return doc.getElementsByTagName("test");},
+ function (doc) { return []; }
+ );
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_non_content_accessible_env_vars.html b/layout/style/test/test_non_content_accessible_env_vars.html
new file mode 100644
index 0000000000..d9714216b7
--- /dev/null
+++ b/layout/style/test/test_non_content_accessible_env_vars.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe></iframe>
+<iframe srcdoc="Foo"></iframe>
+<script>
+const NON_CONTENT_ACCESSIBLE_ENV_VARS = [
+ "-moz-gtk-csd-titlebar-radius",
+ "-moz-gtk-csd-minimize-button-position",
+ "-moz-gtk-csd-maximize-button-position",
+ "-moz-gtk-csd-close-button-position",
+ "-moz-content-preferred-color-scheme",
+ "-moz-overlay-scrollbar-fade-duration",
+ "scrollbar-inline-size",
+];
+
+function testInWin(win) {
+ let doc = win.document;
+ const div = doc.createElement("div");
+ doc.documentElement.appendChild(div);
+ for (const envVar of NON_CONTENT_ACCESSIBLE_ENV_VARS) {
+ div.style.setProperty("--foo", `env(${envVar},FALLBACK_VALUE)`);
+
+ assert_equals(
+ win.getComputedStyle(div).getPropertyValue("--foo"),
+ "FALLBACK_VALUE",
+ `${envVar} shouldn't be exposed to content in ${doc.documentURI}`
+ );
+ }
+}
+
+let t = async_test("Test non-content-accessible env() vars");
+onload = t.step_func_done(function() {
+ testInWin(window);
+ for (let f of document.querySelectorAll("iframe")) {
+ testInWin(f.contentWindow);
+ }
+});
+</script>
diff --git a/layout/style/test/test_non_content_accessible_properties.html b/layout/style/test/test_non_content_accessible_properties.html
new file mode 100644
index 0000000000..220ddf2d26
--- /dev/null
+++ b/layout/style/test/test_non_content_accessible_properties.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe></iframe>
+<iframe srcdoc="Foo"></iframe>
+<script>
+const CHROME_ONLY_PROPERTIES = [
+ "-moz-window-input-region-margin",
+ "-moz-window-shadow",
+ "-moz-window-opacity",
+ "-moz-window-transform",
+ "-moz-window-transform-origin",
+ "-moz-default-appearance",
+ "-moz-user-focus",
+];
+
+const UA_ONLY_PROPERTIES = [
+ "-x-span",
+ "-x-lang",
+ "-x-text-scale",
+ "-moz-control-character-visibility",
+ "-moz-top-layer",
+ "-moz-script-level",
+ "-moz-math-display",
+ "-moz-math-variant",
+ "-moz-inert",
+ "-moz-min-font-size-ratio",
+ // TODO: This should ideally be in CHROME_ONLY_PROPERTIES, but due to how
+ // [Pref] and [ChromeOnly] interact in WebIDL, the former wins.
+ "-moz-context-properties",
+];
+
+function testInWin(win) {
+ const doc = win.document;
+ const sheet = doc.createElement("style");
+ const div = doc.createElement("div");
+ doc.documentElement.appendChild(sheet);
+ doc.documentElement.appendChild(div);
+
+ sheet.textContent = `div { color: initial }`;
+ assert_equals(sheet.sheet.cssRules[0].style.length, 1, `sanity: ${doc.documentURI}`);
+
+ for (const prop of CHROME_ONLY_PROPERTIES.concat(UA_ONLY_PROPERTIES)) {
+ sheet.textContent = `div { ${prop}: initial }`;
+ let block = sheet.sheet.cssRules[0].style;
+ assert_false(prop in block, `${prop} shouldn't be exposed in CSS2Properties`);
+
+ let isUAOnly = UA_ONLY_PROPERTIES.includes(prop);
+ assert_equals(prop in SpecialPowers.wrap(block), !isUAOnly, `${prop} should be exposed to chrome code if needed`);
+
+ assert_equals(
+ block.length,
+ 0,
+ `${prop} shouldn't be parsed in ${doc.documentURI}`
+ );
+ block.setProperty(prop, "initial");
+ assert_equals(
+ block.length,
+ 0,
+ `${prop} shouldn't be settable via CSSOM in ${doc.documentURI}`
+ );
+ assert_equals(
+ win.getComputedStyle(div).getPropertyValue(prop),
+ "",
+ `${prop} shouldn't be accessible via CSSOM in ${doc.documentURI}`
+ );
+ assert_false(
+ win.CSS.supports(prop + ': initial'),
+ `${prop} shouldn't be exposed in CSS.supports in ${doc.documentURI}`
+ );
+ assert_false(
+ win.CSS.supports(prop, 'initial'),
+ `${prop} shouldn't be exposed in CSS.supports in ${doc.documentURI} (2-value version)`
+ );
+ }
+}
+
+let t = async_test("test non-content-accessible props");
+onload = t.step_func_done(function() {
+ testInWin(window);
+ for (let f of document.querySelectorAll("iframe")) {
+ testInWin(f.contentWindow);
+ }
+});
+</script>
diff --git a/layout/style/test/test_non_content_accessible_pseudos.html b/layout/style/test/test_non_content_accessible_pseudos.html
new file mode 100644
index 0000000000..81493b1a4a
--- /dev/null
+++ b/layout/style/test/test_non_content_accessible_pseudos.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style id="sheet"></style>
+<script>
+// Even though some of these no longer exist, we still do want to test that
+// they aren't exposed to the web.
+const NON_CONTENT_ACCESIBLE_PSEUDOS = [
+ "::-moz-complex-control-wrapper",
+ "::-moz-number-wrapper",
+ "::-moz-number-text",
+ "::-moz-number-spin-up",
+ "::-moz-number-spin-down",
+ "::-moz-number-spin-box",
+ "::-moz-search-clear-button",
+
+ ":-moz-native-anonymous",
+ ":-moz-table-border-nonzero",
+ ":-moz-browser-frame",
+ ":-moz-devtools-highlighted",
+ ":-moz-styleeditor-transitioning",
+ ":-moz-handler-clicktoplay",
+ ":-moz-handler-vulnerable-updatable",
+ ":-moz-handler-vulnerable-no-update",
+ ":-moz-handler-disabled",
+ ":-moz-handler-blocked",
+ ":-moz-handler-chrased",
+ ":-moz-has-dir-attr",
+ ":-moz-dir-attr-ltr",
+ ":-moz-dir-attr-rtl",
+ ":-moz-dir-attr-like-auto",
+ ":-moz-autofill-preview",
+ ":-moz-lwtheme",
+ ":-moz-is-html",
+ ":-moz-locale-dir(rtl)",
+ ":-moz-locale-dir(ltr)",
+
+ "::-moz-tree-row",
+ "::-moz-tree-row(foo)",
+];
+
+test(function() {
+ sheet.textContent = `div { color: initial }`;
+ assert_equals(sheet.sheet.cssRules.length, 1);
+}, "sanity");
+
+for (const pseudo of NON_CONTENT_ACCESIBLE_PSEUDOS) {
+ test(function() {
+ sheet.textContent = `${pseudo} { color: blue; }`;
+ assert_equals(sheet.sheet.cssRules.length, 0,
+ pseudo + " shouldn't be accessible to content");
+ if (pseudo.indexOf("::") === 0) {
+ let pseudoStyle;
+ try {
+ pseudoStyle = getComputedStyle(document.documentElement, pseudo);
+ } catch (ex) {
+ // We can hit this when layout.css.computed-style.throw-on-invalid-pseudo is true
+ assert_true(true, `${pseudo} shouldn't be visible to content`);
+ return;
+ }
+ let elementStyle = getComputedStyle(document.documentElement);
+ for (prop of pseudoStyle) {
+ assert_equals(pseudoStyle[prop], elementStyle[prop],
+ pseudo + " styles shouldn't be visible from getComputedStyle");
+ }
+ }
+ }, pseudo);
+}
+</script>
diff --git a/layout/style/test/test_non_content_accessible_values.html b/layout/style/test/test_non_content_accessible_values.html
new file mode 100644
index 0000000000..633427f8e1
--- /dev/null
+++ b/layout/style/test/test_non_content_accessible_values.html
@@ -0,0 +1,172 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style id="sheet"></style>
+<div></div>
+<script>
+const NON_CONTENT_ACCESSIBLE_VALUES = {
+ "color": [
+ "-moz-buttonactivetext",
+ "-moz-buttonactiveface",
+ "-moz-buttondisabledface",
+ "-moz-disabledfield",
+ "-moz-colheaderhovertext",
+ "-moz-colheadertext",
+ "-moz-nativevisitedhyperlinktext",
+ "text-select-disabled-background",
+ "text-select-attention-background",
+ "text-select-attention-foreground",
+ "-moz-autofill-background",
+ ],
+ "display": [
+ "-moz-box",
+ "-moz-inline-box",
+ ],
+ "font": [
+ "-moz-pull-down-menu",
+ "-moz-button",
+ "-moz-list",
+ "-moz-field",
+ ],
+ "-moz-appearance": [
+ "button-arrow-down",
+ "button-arrow-next",
+ "button-arrow-previous",
+ "button-arrow-up",
+ "button-focus",
+ "dualbutton",
+ "groupbox",
+ "menubar",
+ "menuitem",
+ "checkmenuitem",
+ "radiomenuitem",
+ "menuitemtext",
+ "menupopup",
+ "menucheckbox",
+ "menuradio",
+ "menuseparator",
+ "menuimage",
+ "-moz-menulist-arrow-button",
+ "checkbox-container",
+ "radio-container",
+ "checkbox-label",
+ "radio-label",
+ "resizerpanel",
+ "resizer",
+ "scrollbar",
+ "scrollbar-small",
+ "scrollbar-horizontal",
+ "scrollbar-vertical",
+ "scrollbarbutton-up",
+ "scrollbarbutton-down",
+ "scrollbarbutton-left",
+ "scrollbarbutton-right",
+ "scrollcorner",
+ "separator",
+ "spinner",
+ "spinner-upbutton",
+ "spinner-downbutton",
+ "spinner-textfield",
+ "splitter",
+ "statusbar",
+ "statusbarpanel",
+ "tab",
+ "tabpanel",
+ "tabpanels",
+ "tab-scroll-arrow-back",
+ "tab-scroll-arrow-forward",
+ "toolbar",
+ "toolbarbutton",
+ "toolbarbutton-dropdown",
+ "toolbargripper",
+ "toolbox",
+ "tooltip",
+ "treeheader",
+ "treeheadercell",
+ "treeheadersortarrow",
+ "treeitem",
+ "treeline",
+ "treetwisty",
+ "treetwistyopen",
+ "treeview",
+ "window",
+ "dialog",
+ "-moz-win-communications-toolbox",
+ "-moz-win-media-toolbox",
+ "-moz-win-browsertabbar-toolbox",
+ "-moz-win-borderless-glass",
+ "-moz-win-exclude-glass",
+ "-moz-mac-help-button",
+ "-moz-window-button-box",
+ "-moz-window-button-box-maximized",
+ "-moz-window-button-close",
+ "-moz-window-button-maximize",
+ "-moz-window-button-minimize",
+ "-moz-window-button-restore",
+ "-moz-window-titlebar",
+ "-moz-window-titlebar-maximized",
+ "-moz-mac-active-source-list-selection",
+ "-moz-mac-disclosure-button-closed",
+ "-moz-mac-disclosure-button-open",
+ "-moz-mac-source-list",
+ "-moz-mac-source-list-selection",
+
+ "button-bevel",
+ "caret",
+ "listitem",
+ "menulist-textfield",
+ "menulist-text",
+ ],
+ "user-select": [
+ "-moz-text",
+ ],
+ "line-height": [
+ "-moz-block-height",
+ ],
+ "text-align": [
+ "-moz-center-or-inherit",
+ ],
+};
+
+const sheet = document.getElementById("sheet");
+const div = document.querySelector("div");
+
+test(function() {
+ sheet.textContent = `div { color: initial }`;
+ assert_equals(sheet.sheet.cssRules[0].style.length, 1);
+}, "sanity");
+
+for (const prop in NON_CONTENT_ACCESSIBLE_VALUES) {
+ const values = NON_CONTENT_ACCESSIBLE_VALUES[prop];
+ test(function() {
+ for (const value of values) {
+ sheet.textContent = `div { ${prop}: ${value} }`;
+ const block = sheet.sheet.cssRules[0].style;
+ assert_equals(
+ block.length,
+ 0,
+ `${prop}: ${value} should not be parsed in content`
+ );
+ block.setProperty(prop, value);
+ assert_equals(
+ block.length,
+ 0,
+ `${prop}: ${value} should not be settable via CSSOM in content`
+ );
+ div.style.setProperty(prop, value);
+ assert_equals(
+ div.style.length,
+ 0,
+ `${prop}: ${value} should not be settable via CSSOM in content (inline style)`
+ );
+ assert_not_equals(
+ getComputedStyle(div).getPropertyValue(prop),
+ value,
+ `${prop}: ${value} should not be settable via CSSOM in content (gcs)`
+ );
+
+ assert_false(CSS.supports(prop, value), `${prop}: ${value} should not claim to be supported`)
+ }
+ }, prop + " non-accessible values: " + values.join(", "))
+}
+</script>
diff --git a/layout/style/test/test_non_matching_sheet_media.html b/layout/style/test/test_non_matching_sheet_media.html
new file mode 100644
index 0000000000..a57444df45
--- /dev/null
+++ b/layout/style/test/test_non_matching_sheet_media.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1386840 -->
+<title>Test for Bug 1386840: non-matching media list doesn't block rendering</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var t = async_test("Test that <link> doesn't block rendering with non-matching media");
+var loadFired = false;
+var scriptExecuted = false;
+
+function sheetLoaded() {
+ loadFired = true;
+ assert_true(scriptExecuted, "Shouldn't wait for load to execute script");
+ t.done();
+}
+</script>
+<!--
+ NOTE(emilio): This can theoretically race if an HTTP packet boundary with a
+ very long delay came right after the link and before the script. If this
+ ever becomes a problem, the way to fix this is using document.write() as
+ explained in bug 1386840 comment 12.
+-->
+<link rel="stylesheet" href="data:text/css,foo {}" media="print" onload="t.step(sheetLoaded)">
+<script>
+ t.step(function() {
+ scriptExecuted = true;
+ assert_false(loadFired, "Shouldn't have waited for load");
+ });
+</script>
diff --git a/layout/style/test/test_of_type_selectors.xhtml b/layout/style/test/test_of_type_selectors.xhtml
new file mode 100644
index 0000000000..edf2e6ee97
--- /dev/null
+++ b/layout/style/test/test_of_type_selectors.xhtml
@@ -0,0 +1,97 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=75375
+-->
+<head>
+ <title>Test for *-of-type selectors in Bug 75375</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=75375">Mozilla Bug 75375</a>
+<div id="content" style="display: none"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+<p>This is a <code>p</code> element in the HTML namespace.</p>
+<p>This is a second <code>p</code> element in the HTML namespace.</p>
+<html:p>This is an <code>html:p</code> element in the HTML namespace.</html:p>
+<p xmlns="http://www.example.com/ns">This is a <code>p</code> element in the <code>http://www.example.com/ns</code> namespace.</p>
+<html:address>This is an <code>html:address</code> element in the HTML namespace.</html:address>
+<address xmlns="">This is a <code>address</code> element in no namespace.</address>
+<address xmlns="">This is a <code>address</code> element in no namespace.</address>
+<p xmlns="">This is a <code>p</code> element in no namespace.</p>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for *-of-type selectors in Bug 75375 **/
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function setup_style_text() {
+ var result = document.createCDATASection("");
+ var style = document.createElementNS(HTML_NS, "style");
+ style.appendChild(result);
+ document.getElementsByTagName("head")[0].appendChild(style);
+ return result;
+}
+
+function run() {
+ var styleText = setup_style_text();
+
+ var elements = [];
+
+ var div = document.getElementById("content");
+ for (let i = 0; i < div.childNodes.length; ++i) {
+ var child = div.childNodes[i];
+ if (child.nodeType == Node.ELEMENT_NODE)
+ elements.push(child);
+ }
+
+ var counter = 0;
+
+ function test_selector(selector, match_indices, notmatch_indices)
+ {
+ var zi = ++counter;
+ styleText.data = selector + " { z-index: " + zi + " }";
+ let i;
+ for (i in match_indices) {
+ var e = elements[match_indices[i]];
+ is(getComputedStyle(e, "").zIndex, String(zi),
+ "element " + match_indices[i] + " matched " + selector);
+ }
+ for (i in notmatch_indices) {
+ var e = elements[notmatch_indices[i]];
+ is(getComputedStyle(e, "").zIndex, "auto",
+ "element " + notmatch_indices[i] + " did not match " + selector);
+ }
+ }
+
+ // 0 - html:p
+ // 1 - html:p
+ // 2 - html:p
+ // 3 - example:p
+ // 4 - html:address
+ // 5 - :address
+ // 6 - :address
+ // 7 - :p
+ test_selector(":nth-of-type(1)", [0, 3, 4, 5, 7], [1, 2, 6]);
+ test_selector(":nth-last-of-type(1)", [2, 3, 4, 6, 7], [0, 1, 5]);
+ test_selector(":nth-last-of-type(-n+1)", [2, 3, 4, 6, 7], [0, 1, 5]);
+ test_selector(":nth-of-type(even)", [1, 6], [0, 2, 3, 4, 5, 7]);
+ test_selector(":nth-last-of-type(odd)", [0, 2, 3, 4, 6, 7], [1, 5]);
+ test_selector(":nth-last-of-type(n+2)", [0, 1, 5], [2, 3, 4, 6, 7]);
+ test_selector(":first-of-type", [0, 3, 4, 5, 7], [1, 2, 6]);
+ test_selector(":last-of-type", [2, 3, 4, 6, 7], [0, 1, 5]);
+ test_selector(":only-of-type", [3, 4, 7], [0, 1, 2, 5, 6]);
+}
+
+run();
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_overscroll_behavior_pref.html b/layout/style/test/test_overscroll_behavior_pref.html
new file mode 100644
index 0000000000..c12d4ce081
--- /dev/null
+++ b/layout/style/test/test_overscroll_behavior_pref.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Test pref for overscroll-behavior property</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+<script class="testbody" type="text/javascript">
+function runTest() {
+ let css = "div { overscroll-behavior: auto; }";
+ let style = document.createElement('style');
+ style.appendChild(document.createTextNode(css));
+ document.head.appendChild(style);
+
+ is(document.styleSheets[0].cssRules[0].style.length,
+ 0,
+ "overscroll-behavior shouldn't be parsed if the pref is off");
+ SimpleTest.finish();
+}
+SpecialPowers.pushPrefEnv({ set: [["layout.css.overscroll-behavior.enabled", false]] },
+ runTest);
+SimpleTest.waitForExplicitFinish();
+</script>
+</html>
diff --git a/layout/style/test/test_page_parser.html b/layout/style/test/test_page_parser.html
new file mode 100644
index 0000000000..6f6860d0f2
--- /dev/null
+++ b/layout/style/test/test_page_parser.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=115199 -->
+<head>
+ <meta charset="UTF-8">
+ <title>Test of @page parser</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<p>@page parsing (<a
+ target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=115199"
+>bug 115199</a>)</p>
+<pre id="display"></pre>
+<style type="text/css" id="testbox"></style>
+<script class="testbody" type="text/javascript">
+ function _(b) { return "@page { " + b + " }"; };
+
+ var testset = [
+ // CSS 2.1 only allows margin properties in the page rule.
+
+ // Check a bad property.
+ { rule: _("position: absolute;") },
+
+ // Check good properties with invalid units.
+ { rule: _("margin: 2in; margin: 2vw;"), expected: {
+ "margin-top": "2in",
+ "margin-right": "2in",
+ "margin-bottom": "2in",
+ "margin-left": "2in"
+ }},
+ { rule: _("margin-top: 2in; margin-top: 2vw;"), expected: {"margin-top": "2in"}},
+ { rule: _("margin-top: 2in; margin-top: 2vh;"), expected: {"margin-top": "2in"}},
+ { rule: _("margin-top: 2in; margin-top: 2vmax;"), expected: {"margin-top": "2in"}},
+ { rule: _("margin-top: 2in; margin-top: 2vmin;"), expected: {"margin-top": "2in"}},
+
+ // Check good properties.
+ { rule: _("margin: 2in;"), expected: {
+ "margin-top": "2in",
+ "margin-right": "2in",
+ "margin-bottom": "2in",
+ "margin-left": "2in"
+ }},
+ { rule: _("margin-top: 2in;"), expected: {"margin-top": "2in"}},
+ { rule: _("margin-left: 2in;"), expected: {"margin-left": "2in"}},
+ { rule: _("margin-bottom: 2in;"), expected: {"margin-bottom": "2in"}},
+ { rule: _("margin-right: 2in;"), expected: {"margin-right": "2in"}}
+ ];
+
+ var display = document.getElementById("display");
+ var sheet = document.styleSheets[1];
+
+ for (var curTest = 0; curTest < testset.length; curTest++) {
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+ sheet.insertRule(testset[curTest].rule, 0);
+
+ try {
+ is(sheet.cssRules.length, 1,
+ testset[curTest].rule + " rule count");
+ is(sheet.cssRules[0].type, CSSRule.PAGE_RULE,
+ testset[curTest].rule + " rule type");
+
+ if (testset[curTest].expected) {
+ var expected = testset[curTest].expected;
+ var s = sheet.cssRules[0].style;
+ var n = 0;
+
+ // everything is set that should be
+ for (var name in expected) {
+ is(s.getPropertyValue(name), expected[name],
+ testset[curTest].rule + " (prop " + name + ")");
+ n++;
+ }
+ // nothing else is set
+ is(s.length, n, testset[curTest].rule + " prop count");
+ for (var i = 0; i < s.length; i++) {
+ ok(s[i] in expected, testset[curTest].rule +
+ " - Unexpected item #" + i + ": " + s[i]);
+ }
+ } else {
+ is(Object.keys(sheet.cssRules[0].style).length, 0,
+ testset[curTest].rule + " rule has no properties");
+ }
+ } catch (e) {
+ ok(false, testset[curTest].rule + " - During test: " + e);
+ }
+ }
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_parse_eof.html b/layout/style/test/test_parse_eof.html
new file mode 100644
index 0000000000..63ef95b980
--- /dev/null
+++ b/layout/style/test/test_parse_eof.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test parsing behaviour of backslash just before EOF</title>
+ <link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+ <meta name="flags" content="">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+
+<style>#a::before { content: "ab\</style>
+<style>#b { background-image: url("ab\</style>
+<style>#c { background-image: url(ab\</style>
+<style>#d { counter-reset: ab\</style>
+
+<style>
+#a-ref::before { content: "ab"; }
+#b-ref { background-image: url("ab"); }
+#c-ref { background-image: url(ab�); }
+#d-ref { counter-reset: ab�; }
+</style>
+
+<div style="display: none">
+ <div id="a"></div>
+ <div id="b"></div>
+ <div id="c"></div>
+ <div id="d"></div>
+
+ <div id="a-ref"></div>
+ <div id="b-ref"></div>
+ <div id="c-ref"></div>
+ <div id="d-ref"></div>
+</div>
+
+<script>
+var a = document.getElementById("a");
+var b = document.getElementById("b");
+var c = document.getElementById("c");
+var d = document.getElementById("d");
+var a_ref = document.getElementById("a-ref");
+var b_ref = document.getElementById("b-ref");
+var c_ref = document.getElementById("c-ref");
+var d_ref = document.getElementById("d-ref");
+
+test(function() {
+ assert_equals(window.getComputedStyle(a, ":before").content,
+ window.getComputedStyle(a_ref, ":before").content);
+}, "test backslash before EOF inside a string");
+
+test(function() {
+ assert_equals(window.getComputedStyle(b).backgroundImage,
+ window.getComputedStyle(b_ref).backgroundImage);
+}, "test backslash before EOF inside a url(\"\")");
+
+test(function() {
+ assert_equals(window.getComputedStyle(c).backgroundImage,
+ window.getComputedStyle(c_ref).backgroundImage);
+}, "test backslash before EOF inside a url()");
+
+test(function() {
+ assert_equals(window.getComputedStyle(d).counterReset,
+ window.getComputedStyle(d_ref).counterReset);
+}, "test backslash before EOF outside a string");
+</script>
+
+</body>
+</html>
diff --git a/layout/style/test/test_parse_ident.html b/layout/style/test/test_parse_ident.html
new file mode 100644
index 0000000000..390412e2fc
--- /dev/null
+++ b/layout/style/test/test_parse_ident.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS identifier parsing</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+var div = document.getElementById("content");
+
+function counter_increment_parses(i)
+{
+ div.style.counterIncrement = "";
+ div.style.counterIncrement = i;
+ return div.style.counterIncrement != "";
+}
+
+function is_valid_identifier(i)
+{
+ ok(counter_increment_parses(i),
+ "'" + i + "' is a valid CSS identifier");
+}
+
+function is_invalid_identifier(i)
+{
+ ok(!counter_increment_parses(i),
+ "'" + i + "' is not a valid CSS identifier");
+}
+
+for (var i = 0x7B; i < 0x80; ++i) {
+ is_invalid_identifier(String.fromCharCode(i));
+ is_invalid_identifier("a" + String.fromCharCode(i));
+ is_invalid_identifier(String.fromCharCode(i) + "a");
+}
+
+for (var i = 0x80; i < 0xFF; ++i) {
+ is_valid_identifier(String.fromCharCode(i));
+}
+
+is_valid_identifier(String.fromCharCode(0x100));
+is_valid_identifier(String.fromCharCode(0x375));
+is_valid_identifier(String.fromCharCode(0xFEFF));
+is_valid_identifier(String.fromCharCode(0xFFFD));
+is_valid_identifier(String.fromCharCode(0xFFFE));
+is_valid_identifier(String.fromCharCode(0xFFFF));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_parse_rule.html b/layout/style/test/test_parse_rule.html
new file mode 100644
index 0000000000..0fb1cc80ed
--- /dev/null
+++ b/layout/style/test/test_parse_rule.html
@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<html lang=en>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<body>
+<iframe></iframe>
+<!-- Note that the following style and div elements are duplicates
+ of the ones written into the iframe; they are here for convienience
+ in resolving the "standard" computed value for a given specification
+-->
+<style></style>
+<div id=a class='a b c' title='zxcv weeqweqeweasd&#13;&#10;a&#10;'></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+window.onload=function(){
+
+var base;
+
+// A short note about escaping: all of the strings in this test go through
+// Javascript unescaping before getting passed to CSS. This means that
+// sequences like "\n" refer to a newline, a single backslash is written "\\",
+// a CSS escape sequence is something like "\\A", and some quotes must be
+// escaped.
+
+var testset = [
+
+// Color tests
+// Generic property for testing
+{ base : base = "div {color:green}",
+ tests : [
+// My misc tests
+"<!--#a {color:green}",
+base + "<!-#a {color:red}",
+base + "#a<!--{color:red}",
+"-->#a{color:green}",
+base + "--#a {color:red}",
+base + "--aasdf, #a {color:green}",
+base + "-0aasdf, #a {color:red}",
+"-asdf, #a {color:green}",
+base + "#a {color: rgb\n(255, 0, 0)}",
+"#a {font: \"Arial\n;color:green}",
+"#a {color: @charset{}\"\\\n'\"url(\na\na); color:green}",
+"#a\r{color:green}",
+"#a\n{color:green}",
+"#a\t{color:green}",
+"@threedee maroon url('asdf\n) ra('asdf\n); " + base,
+"@threedee {maroon url('asdf\n) ra('asdf\n);} " + base,
+"div[title='zxcv weeqweqeweasd\\D\\A a']{color:green}",
+"div[title~='weeqweqeweasd']{color:green}",
+base + "#a\\\n{color:red}",
+base + "#a\v{color:red}",
+
+// CSS1 section 7.1
+"#a {color: green; rotation: 70deg;}",
+"#a {color: green;} #a{color:invalidValue;}",
+base + "#a {color: \"red\"}",
+base + "@three-dee {\n @background-lighting {\n azimuth: 30deg;\n elevation: 190deg;\n }\n #a { color: red }\n }",
+"#a {COLOR: GREEN}",
+base + "#a:wait {color: red}",
+"#a:lang(en) {color: green}",
+"#a:lang(\nen\r\t ) {color: green}",
+base + "div ! em, #a {color: red}",
+base + "//asdf.zxcv,\n#a {color: red}",
+"#a {rotation-code: \"}\"; color: green;}",
+"#a {rotation-code: \"\\\"}\\\"\"; color: green;}",
+"#a {rotation-code: '}'; color: green;}",
+"#a {rotation-code: '\\'}\\''; color: green;}",
+"#a {\n type-display: @threedee {rotation-code: '}';};\n color: green;\n }",
+base + "p {text-indent: 0.5in;} color: maroon #a {color: red;}",
+base + "p {text-indent: 0.5in;} color: maroon; #a {color: red;}",
+
+// string tokenization as error token, not EOF (bug 311566 comment 70)
+"#a { color: green; foo: { \"bar\n;color: red}",
+
+// CSS 2.1 section 4.1.3
+"@MediA All {#a {ColOR :RgB(\t0,\r128,\n0 ) } };",
+base + "\\#a{color:red;}",
+base + "#a\\{color:red;\\}",
+base + "#a{color\\:red;}",
+base + "#a{color:red\\;}",
+"#a {c\\o\\l\\o\\r:\\g\\ree\\n}",
+"#a{ co\\00006Cor: gr\\000065en; }",
+"#a{ co\\4C or: gr\\000045en; }",
+".IdE6n-3t0_6, #a { color: green }",
+"#IdE6n-3t0_6, #a { color: green }",
+"._ident, #a { color: green }",
+"#_ident, #a { color: green }",
+".-ident, .a { color: green; }", // Testsuite has incorrect version
+"#怀ident, .a { color: green }",
+"#iden怀t怀, .a { color: green }",
+"#\\6000ident, .a { color: green }",
+"#iden\\6000t\\6000, .a { color: green }",
+".怀ident, .a { color: green }",
+".iden怀t怀, .a { color: green }",
+".\\6000ident, .a { color: green }",
+".iden\\6000t\\6000, .a { color: green }",
+base + "#6ident, #a {color: red }",
+".id4ent6, .a { color: green }",
+"#\\ident, .a { color: green; }",
+"#ide\\n\\t, .a { color: green; }",
+".\\6ident, .a { color: green; }",
+".\\--ident, .a { color: green; }",
+
+// CSS2.1 section 4.1.5 and 4.2
+"@import 'data:text/css,%23a{color:green}';",
+"@import \"data:text/css,%23a{color:green}\";",
+"@import url(data:text/css,%23a{color:green});",
+"@import 'data:text/css,%23a{color:green}' screen;",
+base + "@import 'data:text/css,%23a{color:red}' blahblahblah;",
+"@import 'data:text/css,%23a{color:green}'",
+"@import 'data:text/css,%23a{color:green}",
+"@foo {}" + base,
+"@foo bar {}" + base,
+"@foo; " + base,
+"@foo bar baz; " + base,
+base + "@foo {}; #a {color: red}",
+
+// CSS2.1 section 4.1.9
+"/* This is a CSS comment. */" + base,
+base + "/* #a {color: red} */",
+"/*********** /*/" + base,
+
+// CSS2.1 section 4.3.6
+base + "#a {color: rgb(255, 0, 0%)}",
+base + "#a {color: rgb(100%, 0, 0)}",
+"#a {color: rgb(0, 128, 0)}",
+"#a {color: rgb(0%, 50%, 0%)}",
+"#a {color: rgb(0%, 49.999999999999%, 0%)}",
+
+// CSS-Color-4
+// https://drafts.csswg.org/css-color/#rgb-functions
+"#a {color: rgb(0, 128.0, 0)}",
+], prop: "color", pseudo: ""
+},
+
+// Border tests
+// For testing lengths
+{ base : base = "#a {border-style:solid}",
+ tests : [
+// CSS1 section 7.1
+base + "#a {border-width: funny}",
+base + "#a {border-width: 50zu}",
+base + "#a {border-width: px}",
+
+// Number/unit parsing
+base + "#a {border-width: 0.px}",
+base + "#a {border-width: ..0px}",
+base + "#a {border-width: 0..0px}",
+base + "#a {border-width: 0.}",
+base + "#a {border-width: ..0}",
+base + "#a {border-width: 0..0}",
+base + "#a {border-width: 0; border-width: .0px medium}",
+base + "#a {border-width: 0; border-width: .0 medium}",
+base + "#a {border-width: 0; border-width: 0.0px medium}",
+], prop: "borderRightWidth", pseudo: ""},
+
+// Content tests
+// Tests for strings and pseudos
+{base : base = ".a::before {content: 'This is \\a'}",
+ tests : [
+// CSS 2.1 section 4.1.3
+"#a::before {content: 'This is \\a '}",
+"#a::before {content: 'This is \\A '}",
+"#a::before {content: 'This is \\0000a '}",
+"#a::before {content: 'This is \\00000a '}",
+"#a::before {content: 'This is \\\n\\00000a '}",
+"#a::before {content: 'This is \\\015\012\\00000a '}",
+"#a::before {content: 'This is \\\015\\00000a '}",
+"#a::before {content: 'This is \\\f\\00000a '}",
+"#a::before {content: 'This is\\20\f\\a'}",
+"#a::before {content: 'This is\\20\r\\a'}",
+"#a::before {content: 'This is\\20\n\\a'}",
+"#a::before {content: 'This is\\20\r\n\\a'}",
+base + "#a::before {content: 'FAIL \f\\a'}",
+base + "#a::before {content: 'FAIL \\\n\r\\a'}",
+"#a:before {content: 'This is \\a'}",
+
+base + "#a:: before {content: 'FAIL'}",
+base + "#a ::before {content: 'FAIL'}",
+"#a::before {content: 'This is \\a",
+
+], prop: "content", pseudo: "::before"
+},
+
+// Background color tests
+// For basic URL parsing sanity checks
+{ base : base = "div {background: blue}",
+ tests : [
+"#a {background: url() blue}",
+"#a {background: url(怀) blue}",
+], prop: "backgroundColor", pseudo: ""
+},
+
+// A one-off test I couldn't come up with a better way to do
+{ base : base = "div {border-style: dotted}",
+ tests : [
+// Sanity-check to make sure this test will work
+// This test requires a color name that starts with a "-"
+base + "#a {border: dotted 0 -moz-menuhover}",
+// The actual test: check that 0-moz-menuhover get parsed as an unknown dimension
+// rather than a separate identifier
+base + "#a {border: solid 0-moz-menuhover}",
+], prop: "borderLeftStyle", pseudo: ""
+},
+
+];
+
+var curTest = -1;
+var curSubTest = 0;
+
+var styleElement = document.getElementsByTagName("style")[0];
+var divElement = document.getElementById("a");
+var frame = document.getElementsByTagName("iframe")[0];
+
+var canonical;
+
+var doTests = function() {
+ if (curTest >= 0) {
+ var curElement = frame.contentDocument.getElementsByTagName("div")[0];
+ var curStyle = frame.contentDocument.defaultView.getComputedStyle(curElement, testset[curTest].pseudo);
+ if (testset[curTest].todo && testset[curTest].todo[testset[curTest].tests[curSubTest]]) {
+ todo_is(curStyle[testset[curTest].prop], canonical, testset[curTest].tests[curSubTest]);
+ } else {
+ is(curStyle[testset[curTest].prop], canonical, testset[curTest].tests[curSubTest]);
+ }
+ curSubTest++;
+ }
+ if (curTest == -1 || curSubTest >= testset[curTest].tests.length) {
+ curTest++;
+ curSubTest = 0;
+ }
+ if (!(curTest < testset.length)) {
+ SimpleTest.finish();
+ return;
+ }
+ if (curSubTest == 0) {
+ styleElement.textContent = "";
+ let computedBase = document.defaultView.getComputedStyle(divElement, testset[curTest].pseudo)[testset[curTest].prop];
+ styleElement.textContent = testset[curTest].base;
+ canonical = document.defaultView.getComputedStyle(divElement, testset[curTest].pseudo)[testset[curTest].prop];
+ styleElement.textContent = "";
+ isnot(computedBase, canonical, "Sanity check for rule: " + testset[curTest].base);
+ }
+ frame.contentDocument.open();
+ frame.contentDocument.write("<html lang=en><style>" + testset[curTest].tests[curSubTest] + "</style><div id=a class='a b c' title='zxcv weeqweqeweasd&#13;&#10;a'></div>");
+ frame.contentWindow.onload = function(){setTimeout(doTests, 0);};
+ frame.contentDocument.close();
+};
+
+// Running a debug build on the Android emulator is slooow and collecting
+// all the session history this test amasses through repeated navigations
+// adds considerably to the running time. Therefore, we restrict the
+// amount of session history we keep around during this test.
+SpecialPowers.pushPrefEnv({"set": [['browser.sessionhistory.max_entries', 4]]}, doTests);
+
+};
+
+</script>
diff --git a/layout/style/test/test_parse_url.html b/layout/style/test/test_parse_url.html
new file mode 100644
index 0000000000..7a637c18e3
--- /dev/null
+++ b/layout/style/test/test_parse_url.html
@@ -0,0 +1,195 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=473914
+-->
+<head>
+ <title>Test for Bug 473914</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=473914">Mozilla Bug 473914</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 473914 **/
+
+var div = document.getElementById("content");
+
+// This test relies on normalization (insertion of quote marks) that
+// we're not really guaranteed to continue doing in the future.
+div.style.listStyleImage = 'url(http://example.org/**/)';
+is(div.style.listStyleImage, 'url("http://example.org/**/")',
+ "not treated as comment");
+div.style.listStyleImage = 'url("http://example.org/**/")';
+is(div.style.listStyleImage, 'url("http://example.org/**/")',
+ "not treated as comment");
+div.style.listStyleImage = 'url(/**/foo)';
+is(div.style.listStyleImage, 'url("/**/foo")',
+ "not treated as comment");
+div.style.listStyleImage = 'url("/**/foo")';
+is(div.style.listStyleImage, 'url("/**/foo")',
+ "not treated as comment");
+div.style.listStyleImage = 'url(/**/)';
+is(div.style.listStyleImage, 'url("/**/")',
+ "not treated as comment");
+div.style.listStyleImage = 'url("/**/")';
+is(div.style.listStyleImage, 'url("/**/")',
+ "not treated as comment");
+
+// Tests from Alfred Keyser's patch in bug 337287 (modified by dbaron)
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good /*bad comment*/)';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url(good /*bad comments*/ /*Hello*/)';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url(good/*commentaspartofurl*/)';
+is(div.style.listStyleImage, 'url("good/*commentaspartofurl*/")',
+ "comment-like syntax not comment inside of url");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good/**/ /*secondcommentcanbeskipped*/ )';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url(/*partofurl*/good)';
+is(div.style.listStyleImage, 'url("/*partofurl*/good")',
+ "comment not parsed as part of url");
+
+div.style.listStyleImage = 'url(good';
+is(div.style.listStyleImage, 'url("good")',
+ "URL ending with eof not correctly handled");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good /*)*/';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good /*)*/ tokenaftercommentevenwithclosebracketisinvalid';
+is(div.style.listStyleImage, 'url("bad")',
+ "comment not allowed inside token");
+
+div.style.listStyleImage = 'url(bad)';
+div.style.listStyleImage = 'url("good"';
+is(div.style.listStyleImage, 'url("good")',
+ "URL as string without close bracket");
+
+div.style.listStyleImage = 'url(bad)';
+div.style.listStyleImage = 'url("good';
+is(div.style.listStyleImage, 'url("good")',
+ "URL as string without closing quote");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good notgood';
+is(div.style.listStyleImage, 'url("bad")',
+ "second token should make url invalid");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good(notgood';
+is(div.style.listStyleImage, 'url("bad")',
+ "open bracket in url not recognized as invalid");
+
+var longurl = '';
+for (i=0;i<1000;i++) {
+ longurl = longurl + 'verylongurlindeed_thequickbrownfoxjumpsoverthelazydoq';
+}
+div.style.listStyleImage = 'url(' + longurl;
+is(div.style.listStyleImage, 'url("' + longurl + '")',
+ "very long url not correctly parsed");
+
+
+// Additional tests from
+// https://bugzilla.mozilla.org/show_bug.cgi?id=337287#c21
+
+div.style.listStyleImage = 'url(good/*)';
+is(div.style.listStyleImage, 'url("good/*")',
+ "URL containing comment start is valid");
+
+div.style.listStyleImage = 'url("bad")';
+div.style.listStyleImage = 'url(good bad)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(\\g b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url( \\g b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(c\\g b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(cc\\g b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(\\f b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url( \\f b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(c\\f b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+div.style.listStyleImage = 'url(cc\\f b)';
+is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with spaces not allowed");
+
+var chars = [ 1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 15, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127];
+
+for (var i in chars) {
+ var charcode = chars[i];
+ div.style.listStyleImage = 'url(' + String.fromCharCode(charcode) + ')';
+ is(div.style.listStyleImage, 'url("bad")',
+ "unquoted URL with control character " + charcode + " not allowed");
+}
+
+div.style.listStyleImage = 'url(\u00ff)';
+is(div.style.listStyleImage, 'url("\u00ff")', "U+A0-U+FF allowed in unquoted URL");
+
+div.style.listStyleImage = 'url(\\f good)';
+is(div.style.listStyleImage, 'url("\\f good")', "URL allowed");
+div.style.listStyleImage = 'url( \\f good)';
+is(div.style.listStyleImage, 'url("\\f good")', "URL allowed");
+div.style.listStyleImage = 'url(f\\f good)';
+is(div.style.listStyleImage, 'url("f\\f good")', "URL allowed");
+div.style.listStyleImage = 'url(go\\od)';
+is(div.style.listStyleImage, 'url("good")', "URL allowed");
+div.style.listStyleImage = 'url(goo\\d)';
+is(div.style.listStyleImage, 'url("goo\\d ")', "URL allowed");
+div.style.listStyleImage = 'url(go\\o)';
+is(div.style.listStyleImage, 'url("goo")', "URL allowed");
+
+div.setAttribute("style", "color: url(/*); color: green");
+is(div.style.color, 'green',
+ "URL tokenized correctly outside properties taking URLs");
+
+div.style.listStyleImage = 'url("foo\\\nbar1")';
+is(div.style.listStyleImage, 'url("foobar1")',
+ "escaped newline allowed in string form of URL");
+div.style.listStyleImage = 'url(foo\\\nbar2)';
+is(div.style.listStyleImage, 'url("foobar1")',
+ "escaped newline NOT allowed in NON-string form of URL");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_parser_diagnostics_unprintables.html b/layout/style/test/test_parser_diagnostics_unprintables.html
new file mode 100644
index 0000000000..f872c00a8f
--- /dev/null
+++ b/layout/style/test/test_parser_diagnostics_unprintables.html
@@ -0,0 +1,220 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for CSS parser diagnostics escaping unprintable
+ characters correctly</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=229827"
+>Mozilla Bug 229827</a>
+<style id="testbench"></style>
+<script type="application/javascript">
+// This test has intimate knowledge of how to get the CSS parser to
+// emit diagnostics that contain text under control of the user.
+// That's not the point of the test, though; the point is only that
+// *that text* is properly escaped.
+
+SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true;
+
+// There is one "pattern" for each code path through the error reporter
+// that might need to escape some kind of user-supplied text.
+// Each "pattern" is tested once with each of the "substitution"s below:
+// <t>, <i>, and <s> are replaced by the t:, i:, and s: fields of
+// each substitution object in turn.
+let patterns = [
+ // REPORT_UNEXPECTED_P (only ever used in contexts where identifier-like
+ // escaping is appropriate)
+ { i: "<t>|x{}", o: "prefix \u2018<i>\u2019" },
+ // REPORT_UNEXPECTED_TOKEN with:
+ // _Ident
+ { i: "@namespace fnord <t>;", o: "within @namespace: \u2018<i>\u2019" },
+ // _Ref
+ { i: "@namespace fnord #<t>;", o: "within @namespace: \u2018#<i>\u2019" },
+ // _Function
+ { i: "@namespace fnord <t>();", o: "within @namespace: \u2018<i>(\u2019" },
+ // _Dimension
+ { i: "@namespace fnord 14<t>;", o: "within @namespace: \u201814<i>\u2019" },
+ // _AtKeyword
+ { i: "x{@<t>: }", o: "declaration but found \u2018@<i>\u2019." },
+ // _String
+ { i: "x{ color: '<t>'}" , o: 'color but found \u2018"<s>"\u2019.' },
+ // _Bad_String
+ { i: "x{ color: '<t>\n}", o: 'color but found \u2018"<s>\u2019.' },
+];
+
+// Blocks of characters to test, and how they should be escaped when
+// they appear in identifiers and string constants.
+const substitutions = [
+ // ASCII printables that _can_ normally appear in identifiers,
+ // so should of course _not_ be escaped.
+ { t: "-_0123456789", i: "-_0123456789",
+ s: "-_0123456789" },
+ { t: "abcdefghijklmnopqrstuvwxyz", i: "abcdefghijklmnopqrstuvwxyz",
+ s: "abcdefghijklmnopqrstuvwxyz" },
+ { t: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", i: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ s: "ABCDEFGHIJKLMNOPQRSTUVWXYZ" },
+
+ // ASCII printables that are not normally valid as the first character
+ // of an identifier, or the character immediately after a leading dash,
+ // but can be forced into that position with escapes.
+ { t: "\\-", i: "\\-", s: "-" },
+ { t: "\\30 ", i: "\\30 ", s: "0" },
+ { t: "\\31 ", i: "\\31 ", s: "1" },
+ { t: "\\32 ", i: "\\32 ", s: "2" },
+ { t: "\\33 ", i: "\\33 ", s: "3" },
+ { t: "\\34 ", i: "\\34 ", s: "4" },
+ { t: "\\35 ", i: "\\35 ", s: "5" },
+ { t: "\\36 ", i: "\\36 ", s: "6" },
+ { t: "\\37 ", i: "\\37 ", s: "7" },
+ { t: "\\38 ", i: "\\38 ", s: "8" },
+ { t: "\\39 ", i: "\\39 ", s: "9" },
+ { t: "-\\-", i: "--", s: "--" },
+ { t: "-\\30 ", i: "-\\30 ", s: "-0" },
+ { t: "-\\31 ", i: "-\\31 ", s: "-1" },
+ { t: "-\\32 ", i: "-\\32 ", s: "-2" },
+ { t: "-\\33 ", i: "-\\33 ", s: "-3" },
+ { t: "-\\34 ", i: "-\\34 ", s: "-4" },
+ { t: "-\\35 ", i: "-\\35 ", s: "-5" },
+ { t: "-\\36 ", i: "-\\36 ", s: "-6" },
+ { t: "-\\37 ", i: "-\\37 ", s: "-7" },
+ { t: "-\\38 ", i: "-\\38 ", s: "-8" },
+ { t: "-\\39 ", i: "-\\39 ", s: "-9" },
+
+ // ASCII printables that must be escaped in identifiers.
+ // Most of these should not be escaped in strings.
+ { t: "\\!\\\"\\#\\$", i: "\\!\\\"\\#\\$", s: "!\\\"#$" },
+ { t: "\\%\\&\\'\\(", i: "\\%\\&\\'\\(", s: "%&'(" },
+ { t: "\\)\\*\\+\\,", i: "\\)\\*\\+\\,", s: ")*+," },
+ { t: "\\.\\/\\:\\;", i: "\\.\\/\\:\\;", s: "./:;" },
+ { t: "\\<\\=\\>\\?", i: "\\<\\=\\>\\?", s: "<=>?", },
+ { t: "\\@\\[\\\\\\]", i: "\\@\\[\\\\\\]", s: "@[\\\\]" },
+ { t: "\\^\\`\\{\\}\\~", i: "\\^\\`\\{\\}\\~", s: "^`{}~" },
+
+ // U+0000 - U+0020 (C0 controls, space)
+ // U+000A LINE FEED, U+000C FORM FEED, and U+000D CARRIAGE RETURN
+ // cannot be put into a CSS token as escaped literal characters, so
+ // we do them with hex escapes instead.
+ // The parser replaces U+0000 with U+FFFD.
+ { t: "\\\x00\\\x01\\\x02\\\x03", i: "�\\1 \\2 \\3 ",
+ s: "�\\1 \\2 \\3 " },
+ { t: "\\\x04\\\x05\\\x06\\\x07", i: "\\4 \\5 \\6 \\7 ",
+ s: "\\4 \\5 \\6 \\7 " },
+ { t: "\\\x08\\\x09\\000A\\\x0B", i: "\\8 \\9 \\a \\b ",
+ s: "\\8 \\9 \\a \\b " },
+ { t: "\\000C\\000D\\\x0E\\\x0F", i: "\\c \\d \\e \\f ",
+ s: "\\c \\d \\e \\f " },
+ { t: "\\\x10\\\x11\\\x12\\\x13", i: "\\10 \\11 \\12 \\13 ",
+ s: "\\10 \\11 \\12 \\13 " },
+ { t: "\\\x14\\\x15\\\x16\\\x17", i: "\\14 \\15 \\16 \\17 ",
+ s: "\\14 \\15 \\16 \\17 " },
+ { t: "\\\x18\\\x19\\\x1A\\\x1B", i: "\\18 \\19 \\1a \\1b ",
+ s: "\\18 \\19 \\1a \\1b " },
+ { t: "\\\x1C\\\x1D\\\x1E\\\x1F\\ ", i: "\\1c \\1d \\1e \\1f \\ ",
+ s: "\\1c \\1d \\1e \\1f " },
+
+ // U+007F (DELETE) and U+0080 - U+009F (C1 controls)
+ { t: "\\\x7f\\\x80\\\x81\\\x82", i: "\\7f \x80\x81\x82",
+ s: "\\7f \x80\x81\x82" },
+ { t: "\\\x83\\\x84\\\x85\\\x86", i: "\x83\x84\x85\x86",
+ s: "\x83\x84\x85\x86" },
+ { t: "\\\x87\\\x88\\\x89\\\x8A", i: "\x87\x88\x89\x8A",
+ s: "\x87\x88\x89\x8A" },
+ { t: "\\\x8B\\\x8C\\\x8D\\\x8E", i: "\x8B\x8C\x8D\x8E",
+ s: "\x8B\x8C\x8D\x8E" },
+ { t: "\\\x8F\\\x90\\\x91\\\x92", i: "\x8F\x90\x91\x92",
+ s: "\x8F\x90\x91\x92" },
+ { t: "\\\x93\\\x94\\\x95\\\x96", i: "\x93\x94\x95\x96",
+ s: "\x93\x94\x95\x96" },
+ { t: "\\\x97\\\x98\\\x99\\\x9A", i: "\x97\x98\x99\x9A",
+ s: "\x97\x98\x99\x9A" },
+ { t: "\\\x9B\\\x9C\\\x9D\\\x9E\\\x9F", i: "\x9B\x9C\x9D\x9E\x9F",
+ s: "\x9B\x9C\x9D\x9E\x9F" },
+
+ // CSS doesn't bother with the full Unicode rules for identifiers,
+ // instead declaring that any code point greater than or equal to
+ // U+0080 is a valid identifier character. Test a small handful
+ // of both basic and astral plane characters.
+
+ // Arabic (caution to editors: there is a possibly-invisible U+200E
+ // LEFT-TO-RIGHT MARK in each string, just before the close quote)
+ { t: "أبجدهوزحطيكلمنسعفصقرشتثخذضظغ‎",
+ i: "أبجدهوزحطيكلمنسعفصقرشتثخذضظغ‎",
+ s: "أبجدهوزحطيكلمنسعفصقرشتثخذضظغ‎" },
+
+ // Box drawing
+ { t: "─│┌┐└┘├┤┬┴┼╭╮╯╰╴╵╶╷",
+ i: "─│┌┐└┘├┤┬┴┼╭╮╯╰╴╵╶╷",
+ s: "─│┌┐└┘├┤┬┴┼╭╮╯╰╴╵╶╷" },
+
+ // CJK Unified Ideographs
+ { t: "一丁丂七丄丅丆万丈三上下丌不与丏",
+ i: "一丁丂七丄丅丆万丈三上下丌不与丏",
+ s: "一丁丂七丄丅丆万丈三上下丌不与丏" },
+
+ // CJK Unified Ideographs Extension B (astral)
+ { t: "𠀀𠀁𠀂𠀃𠀄𠀅𠀆𠀇𠀈𠀉𠀊𠀋𠀌𠀍𠀎𠀏",
+ i: "𠀀𠀁𠀂𠀃𠀄𠀅𠀆𠀇𠀈𠀉𠀊𠀋𠀌𠀍𠀎𠀏",
+ s: "𠀀𠀁𠀂𠀃𠀄𠀅𠀆𠀇𠀈𠀉𠀊𠀋𠀌𠀍𠀎𠀏" },
+
+ // Devanagari
+ { t: "कखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसह",
+ i: "कखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसह",
+ s: "कखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसह" },
+
+ // Emoticons (astral)
+ { t: "😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐",
+ i: "😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐",
+ s: "😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐" },
+
+ // Greek
+ { t: "αβγδεζηθικλμνξοπρςστυφχψω",
+ i: "αβγδεζηθικλμνξοπρςστυφχψω",
+ s: "αβγδεζηθικλμνξοπρςστυφχψω" }
+];
+
+const npatterns = patterns.length;
+const nsubstitutions = substitutions.length;
+
+function quotemeta(str) {
+ return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+}
+function subst(str, sub) {
+ return str.replace("<t>", sub.t)
+ .replace("<i>", sub.i)
+ .replace("<s>", sub.s);
+}
+
+var curpat = 0;
+var cursubst = -1;
+var testbench = document.getElementById("testbench");
+
+function nextTest() {
+ cursubst++;
+ if (cursubst == nsubstitutions) {
+ curpat++;
+ cursubst = 0;
+ }
+ if (curpat == npatterns) {
+ SimpleTest.finish();
+ return;
+ }
+
+ let css = subst(patterns[curpat].i, substitutions[cursubst]);
+ let msg = quotemeta(subst(patterns[curpat].o, substitutions[cursubst]));
+
+ info(css);
+ info(msg);
+ SimpleTest.expectConsoleMessages(function () { testbench.innerHTML = css },
+ [{ errorMessage: new RegExp(msg) }],
+ nextTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+nextTest();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_pixel_lengths.html b/layout/style/test/test_pixel_lengths.html
new file mode 100644
index 0000000000..346547507c
--- /dev/null
+++ b/layout/style/test/test_pixel_lengths.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that pixel lengths don't change based on DPI</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+
+<div id="pt" style="width:90pt; height:90pt; background:lime;">pt</div>
+<div id="pc" style="width:5pc; height:5pc; background:yellow;">pc</div>
+<div id="mm" style="width:25.4mm; height:25.4mm; background:orange;">mm</div>
+<div id="cm" style="width:2.54cm; height:2.54cm; background:purple;">cm</div>
+<div id="in" style="width:1in; height:1in; background:magenta;">in</div>
+<div id="q" style="width:101.6q; height:101.6q; background:blue;">q</div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var oldDPI = SpecialPowers.getIntPref("layout.css.dpi");
+var dpi = oldDPI;
+
+function check(id, val) {
+ var e = document.getElementById(id);
+ is(Math.round(e.getBoundingClientRect().width), Math.round(val),
+ "Checking width in " + id + " at " + dpi + " DPI");
+ is(Math.round(e.getBoundingClientRect().height), Math.round(val),
+ "Checking height in " + id + " at " + dpi + " DPI");
+}
+
+function checkPixelRelativeUnits() {
+ check("pt", 120);
+ check("pc", 80);
+ check("mm", 96);
+ check("cm", 96);
+ check("in", 96);
+ check("q", 96);
+}
+
+checkPixelRelativeUnits();
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({'set': [['layout.css.dpi', dpi=96]]}, test1);
+
+function test1() {
+ checkPixelRelativeUnits();
+ SpecialPowers.pushPrefEnv({'set': [['layout.css.dpi', dpi=192]]}, test2);
+}
+
+function test2() {
+ checkPixelRelativeUnits();
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_placeholder_restrictions.html b/layout/style/test/test_placeholder_restrictions.html
new file mode 100644
index 0000000000..4e3e87ef16
--- /dev/null
+++ b/layout/style/test/test_placeholder_restrictions.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1382786
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1382786</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="s"></style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1382786">Mozilla Bug 1382786</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<input id="test">
+<input id="control">
+<script type="application/javascript">
+
+/** Test for Bug 1382786 **/
+var test = getComputedStyle($("test"), "::placeholder");
+var control = getComputedStyle($("control"), "::placeholder");
+for (let prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.type == CSS_TYPE_TRUE_SHORTHAND) {
+ // Can't get useful info out of getComputedStyle.
+ continue;
+ }
+ let prereqs = "";
+ if (info.prerequisites) {
+ for (let name in info.prerequisites) {
+ prereqs += `${name}: ${info.prerequisites[name]}; `;
+ }
+ }
+ $("s").textContent = `
+ #control::placeholder { ${prop}: ${info.initial_values[0]}; ${prereqs} }
+ #test::placeholder { ${prop}: ${info.other_values[0]}; ${prereqs} }
+ `;
+ // line-height does apply to ::placeholder, but only on <textarea>. We could
+ // switch the test to use a <textarea>.
+ if (info.applies_to_placeholder && prop != "line-height") {
+ isnot(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should apply to ::placeholder`);
+ } else {
+ is(get_computed_value(test, prop),
+ get_computed_value(control, prop),
+ `${prop} should not apply to ::placeholder`);
+ }
+}
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_pointer-events.html b/layout/style/test/test_pointer-events.html
new file mode 100644
index 0000000000..faeb6c6335
--- /dev/null
+++ b/layout/style/test/test_pointer-events.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for pointer-events in HTML</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ div { height: 10px; width: 10px; background: black; }
+
+ </style>
+</head>
+<!-- need a set timeout because we need things to start after painting suppression ends -->
+<body onload="setTimeout(run_test, 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="display" style="position: absolute; top: 0; left: 0; width: 300px; height: 300px">
+
+ <div id="one"></div>
+ <div id="two" style="pointer-events: visiblePainted;"></div>
+ <div id="three" style="height: 20px; pointer-events: none;">
+ <div id="four"style="margin-top: 10px;"></div>
+ </div>
+ <a id="five" style="pointer-events: none;" href="http://mozilla.org/">link</a>
+ <input id="six" style="pointer-events: none;" type="button" value="button" />
+ <table>
+ <tr style="pointer-events: none;">
+ <td id="seven">no</td>
+ <td id="eight" style="pointer-events: visiblePainted;">yes</td>
+ <td id="nine" style="pointer-events: auto;">yes</td>
+ </td>
+ <tr style="opacity: 0.5; pointer-events: none;">
+ <td id="ten">no</td>
+ <td id="eleven" style="pointer-events: visiblePainted;">yes</td>
+ <td id="twelve" style="pointer-events: auto;">yes</td>
+ </td>
+ </table>
+ <iframe id="thirteen" style="pointer-events: none;" src="about:blank" width="100" height="100"></iframe>
+ <script type="application/javascript">
+ var iframe = document.getElementById("thirteen");
+ iframe.contentDocument.open();
+ iframe.contentDocument.writeln("<script type='application/javascript'>");
+ iframe.contentDocument.writeln("document.addEventListener('mousedown', fail, false);");
+ iframe.contentDocument.writeln("function fail() { parent.ok(false, 'thirteen: iframe content must not get pointer events with explicit none') }");
+ iframe.contentDocument.writeln("<"+"/script>");
+ iframe.contentDocument.close();
+ </script>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.expectAssertions(0, 1);
+
+SimpleTest.waitForExplicitFinish();
+
+function catches_pointer_events(element_id)
+{
+ // we just assume the element is on top here.
+ var element = document.getElementById(element_id);
+ var bounds = element.getBoundingClientRect();
+ var point = { x: bounds.left + bounds.width/2, y: bounds.top + bounds.height/2 };
+ return element == document.elementFromPoint(point.x, point.y);
+}
+
+function synthesizeMouseEvent(type, // string
+ x, // float
+ y, // float
+ button, // long
+ clickCount, // long
+ modifiers, // long
+ ignoreWindowBounds) // boolean
+{
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent(type, x, y, button, clickCount,
+ modifiers, ignoreWindowBounds);
+}
+
+function run_test()
+{
+ ok(catches_pointer_events("one"), "one: div should default to catching pointer events");
+ ok(catches_pointer_events("two"), "two: div should catch pointer events with explicit visiblePainted");
+ ok(!catches_pointer_events("three"), "three: div should not catch pointer events with explicit none");
+ ok(!catches_pointer_events("four"), "four: div should not catch pointer events with inherited none");
+ ok(!catches_pointer_events("five"), "five: link should not catch pointer events with explicit none");
+ ok(!catches_pointer_events("six"), "six: native-themed form control should not catch pointer events with explicit none");
+ ok(!catches_pointer_events("seven"), "seven: td should not catch pointer events with inherited none");
+ ok(catches_pointer_events("eight"), "eight: td should catch pointer events with explicit visiblePainted overriding inherited none");
+ ok(catches_pointer_events("nine"), "nine: td should catch pointer events with explicit auto overriding inherited none");
+ ok(!catches_pointer_events("ten"), "ten: td should not catch pointer events with inherited none");
+ ok(catches_pointer_events("eleven"), "eleven: td should catch pointer events with explicit visiblePainted overriding inherited none");
+ ok(catches_pointer_events("twelve"), "twelve: td should catch pointer events with explicit auto overriding inherited none");
+
+ // elementFromPoint can't be used for iframe
+ var iframe = document.getElementById("thirteen");
+ iframe.parentNode.addEventListener('mousedown', handleIFrameClick);
+ var bounds = iframe.getBoundingClientRect();
+ var x = bounds.left + bounds.width/2;
+ var y = bounds.top + bounds.height/2;
+ synthesizeMouseEvent('mousedown', x, y, 0, 1, 0, true);
+}
+
+function handleIFrameClick()
+{
+ ok(true, "thirteen: iframe content must not get pointer events with explicit none");
+
+ document.getElementById("display").style.display = "none";
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_position_float_display.html b/layout/style/test/test_position_float_display.html
new file mode 100644
index 0000000000..ee75144dcb
--- /dev/null
+++ b/layout/style/test/test_position_float_display.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1038929
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1038929</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1038929">Mozilla Bug 1038929</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="float-left" style="float: left"></div>
+ <div id="float-right" style="float: right"></div>
+ <div id="position-absolute" style="position: absolute"></div>
+ <div id="position-fixed" style="position: fixed"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1038929: Test that "display" on a floated or absolutely/fixed
+ position node is correctly converted to a block display as given in the table
+ in CSS 2.1 9.7. */
+
+// Maps from display value to expected conversion when floated/positioned
+// This loosely follows the spec in CSS 2.1 section 9.7. Except for "other"
+// values which the spec says should be "same as specified." For these, we do
+// whatever the spec for the value itself says.
+var mapping = {
+ "inline": "block",
+ "table-row-group": "block",
+ "table-column": "block",
+ "table-column-group": "block",
+ "table-header-group": "block",
+ "table-footer-group": "block",
+ "table-row": "block",
+ "table-cell": "block",
+ "table-caption": "block",
+ "inline-block": "block",
+ "block ruby": "block ruby",
+ "ruby": "block ruby",
+ "ruby-base": "block",
+ "ruby-base-container": "block",
+ "ruby-text": "block",
+ "ruby-text-container": "block",
+ "flex": "flex",
+ "grid": "grid",
+ "none": "none",
+ "table": "table",
+ "inline-grid": "grid",
+ "inline-flex": "flex",
+ "inline-table": "table",
+ "block": "block",
+ "contents": "contents",
+ "flow-root": "flow-root",
+ // Note: this is sometimes block
+ "list-item": "list-item",
+ "inline list-item": "list-item",
+ "inline flow-root list-item": "list-item",
+};
+
+function test_display_value(val)
+{
+ var floatLeftElem = document.getElementById("float-left");
+ floatLeftElem.style.display = val;
+ var floatLeftConversion = window.getComputedStyle(floatLeftElem).display;
+ floatLeftElem.style.display = "";
+
+ var floatRightElem = document.getElementById("float-right");
+ floatRightElem.style.display = val;
+ var floatRightConversion = window.getComputedStyle(floatRightElem).display;
+ floatRightElem.style.display = "";
+
+ var posAbsoluteElem = document.getElementById("position-absolute");
+ posAbsoluteElem.style.display = val;
+ var posAbsoluteConversion = window.getComputedStyle(posAbsoluteElem).display;
+ posAbsoluteElem.style.display = "";
+
+ var posFixedElem = document.getElementById("position-fixed");
+ posFixedElem.style.display = val;
+ var posFixedConversion = window.getComputedStyle(posFixedElem).display;
+ posFixedElem.style.display = "";
+
+ if (mapping[val]) {
+ is(floatLeftConversion, mapping[val],
+ "Element display should be converted when floated left");
+ is(floatRightConversion, mapping[val],
+ "Element display should be converted when floated right");
+ is(posAbsoluteConversion, mapping[val],
+ "Element display should be converted when absolutely positioned");
+ is(posFixedConversion, mapping[val],
+ "Element display should be converted when fixed positioned");
+ } else {
+ ok(false, "missing rules for display value " + val);
+ }
+}
+
+var displayInfo = gCSSProperties.display;
+displayInfo.initial_values.forEach(test_display_value);
+displayInfo.other_values.forEach(test_display_value);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_position_sticky.html b/layout/style/test/test_position_sticky.html
new file mode 100644
index 0000000000..b542737e54
--- /dev/null
+++ b/layout/style/test/test_position_sticky.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=886646
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886646</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ #scroller {
+ width: 100px;
+ height: 100px;
+ padding: 10px;
+ border: 10px solid black;
+ margin: 10px;
+ overflow: hidden;
+ }
+ #container {
+ width: 50px;
+ height: 50px;
+ }
+ #sticky {
+ position: sticky;
+ width: 10px;
+ height: 10px;
+ overflow: hidden;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=886646">Mozilla Bug 886646</a>
+<div id="display">
+ <div id="scroller">
+ <div id="container">
+ <div id="sticky"></div>
+ </div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+
+/** Test for Bug 886646 - Offsets for sticky positioning, when accessed through
+ * getComputedStyle(), should be accurately computed. In particular,
+ * percentage offsets should be computed in terms of the scroll container's
+ * content box. */
+
+// Test that percentage sticky offsets are computed in terms of the
+// scroll container's content box
+var offsets = {
+ "top": 10,
+ "left": 20,
+ "bottom": 30,
+ "right": 40,
+};
+
+var scroller = document.getElementById("scroller");
+var container = document.getElementById("container");
+var sticky = document.getElementById("sticky");
+var cs = getComputedStyle(sticky, "");
+
+for (var prop in offsets) {
+ sticky.style[prop] = offsets[prop] + "%";
+ is(cs[prop], offsets[prop] + "px");
+}
+
+// ... even in the presence of scrollbars
+scroller.style.overflow = "scroll";
+container.style.width = "100%";
+container.style.height = "100%";
+
+var ccs = getComputedStyle(container, "");
+
+function isApproximatelyEqual(a, b) {
+ return Math.abs(a - b) < 0.001;
+}
+
+for (var prop in offsets) {
+ sticky.style[prop] = offsets[prop] + "%";
+ var basis = parseFloat(ccs[prop == "left" || prop == "right" ?
+ "width" : "height"]) / 100;
+ ok(isApproximatelyEqual(parseFloat(cs[prop]), offsets[prop] * basis));
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_prefers_contrast_color_pairs.html b/layout/style/test/test_prefers_contrast_color_pairs.html
new file mode 100644
index 0000000000..f4a8945804
--- /dev/null
+++ b/layout/style/test/test_prefers_contrast_color_pairs.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<title>Test for Bug 922669</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script>
+ function assertMatches(query) {
+ ok(matchMedia(query).matches, `${query} should match`);
+ }
+ function assertPrefersContrastIs(value) {
+ assertMatches(`(prefers-contrast: ${value})`);
+ }
+ add_task(async function setupPrefs() {
+ assertPrefersContrastIs("no-preference");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.display.document_color_use", 2],
+ ["browser.display.use_system_colors", false],
+ ]
+ });
+ assertMatches("(prefers-contrast)");
+ });
+ async function testColors(foreground, background, expected) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.display.foreground_color", foreground],
+ ["browser.display.background_color", background],
+ ]
+ });
+
+ assertPrefersContrastIs(expected);
+
+ // Test the inversed order too.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.display.foreground_color", background],
+ ["browser.display.background_color", foreground],
+ ]
+ });
+
+ assertPrefersContrastIs(expected);
+ }
+
+ add_task(async function test_prefers_contrast_colors() {
+ await testColors("black", "black", "less");
+ await testColors("black", "white", "more");
+ await testColors("red", "black", "custom");
+ });
+</script>
diff --git a/layout/style/test/test_priority_preservation.html b/layout/style/test/test_priority_preservation.html
new file mode 100644
index 0000000000..7177949555
--- /dev/null
+++ b/layout/style/test/test_priority_preservation.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for property priority preservation</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test that priorities are preserved correctly when setProperty is
+ * called, and during declaration block expansion/compression when other
+ * properties are manipulated.
+ */
+
+var div = document.getElementById("content");
+var s = div.style;
+
+s.setProperty("text-decoration", "underline", "");
+is(s.getPropertyValue("text-decoration"), "underline",
+ "text-decoration stored");
+is(s.getPropertyPriority("text-decoration"), "",
+ "text-decoration priority stored");
+s.setProperty("z-index", "7", "important");
+is(s.getPropertyValue("z-index"), "7",
+ "z-index stored");
+is(s.getPropertyPriority("z-index"), "important",
+ "z-index priority stored");
+s.setProperty("z-index", "3", "");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index overridden by setting non-important");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority overridden by setting non-important");
+is(s.getPropertyValue("text-decoration"), "underline",
+ "text-decoration still stored");
+is(s.getPropertyPriority("text-decoration"), "",
+ "text-decoration priority still stored");
+s.setProperty("text-decoration", "overline", "");
+is(s.getPropertyValue("text-decoration"), "overline",
+ "text-decoration stored");
+is(s.getPropertyPriority("text-decoration"), "",
+ "text-decoration priority stored");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index still stored");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority still stored");
+s.setProperty("text-decoration", "line-through", "important");
+is(s.getPropertyValue("text-decoration"), "line-through",
+ "text-decoration stored at new priority");
+is(s.getPropertyPriority("text-decoration"), "important",
+ "text-decoration priority overridden");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index still stored");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority still stored");
+
+ // also test setting a shorthand
+s.setProperty("font", "italic bold 12px/30px serif", "important");
+is(s.getPropertyValue("font-style"), "italic", "font-style stored");
+is(s.getPropertyPriority("font-style"), "important",
+ "font-style priority stored");
+is(s.getPropertyValue("font-weight"), "bold", "font-weight stored");
+is(s.getPropertyPriority("font-weight"), "important",
+ "font-weight priority stored");
+is(s.getPropertyValue("font-size"), "12px", "font-size stored");
+is(s.getPropertyPriority("font-size"), "important",
+ "font-size priority stored");
+is(s.getPropertyValue("line-height"), "30px", "line-height stored");
+is(s.getPropertyPriority("line-height"), "important",
+ "line-height priority stored");
+is(s.getPropertyValue("font-family"), "serif", "font-family stored");
+is(s.getPropertyPriority("font-family"), "important",
+ "font-family priority stored");
+
+is(s.getPropertyValue("text-decoration"), "line-through",
+ "text-decoration still stored");
+is(s.getPropertyPriority("text-decoration"), "important",
+ "text-decoration priority still stored");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index still stored");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority still stored");
+
+ // and overriding one element of that shorthand with some longhand
+ // test omitting the third argument to setProperty too (bug 655478)
+s.setProperty("font-style", "normal");
+
+is(s.getPropertyValue("font-style"), "normal", "font-style overridden");
+is(s.getPropertyPriority("font-style"), "", "font-style priority overridden");
+
+is(s.getPropertyValue("font-weight"), "bold", "font-weight unchanged");
+is(s.getPropertyPriority("font-weight"), "important",
+ "font-weight priority unchanged");
+is(s.getPropertyValue("font-size"), "12px", "font-size unchanged");
+is(s.getPropertyPriority("font-size"), "important",
+ "font-size priority unchanged");
+is(s.getPropertyValue("line-height"), "30px", "line-height unchanged");
+is(s.getPropertyPriority("line-height"), "important",
+ "line-height priority unchanged");
+is(s.getPropertyValue("font-family"), "serif", "font-family unchanged");
+is(s.getPropertyPriority("font-family"), "important",
+ "font-family priority unchanged");
+
+is(s.getPropertyValue("text-decoration"), "line-through",
+ "text-decoration still stored");
+is(s.getPropertyPriority("text-decoration"), "important",
+ "text-decoration priority still stored");
+is(s.getPropertyValue("z-index"), "3",
+ "z-index still stored");
+is(s.getPropertyPriority("z-index"), "",
+ "z-index priority still stored");
+
+s.setProperty("border-radius", "2em", "");
+is(s.getPropertyValue("border-radius"), "2em",
+ "border-radius serialization 1")
+
+s.setProperty("border-top-left-radius", "3em 4em", "");
+is(s.getPropertyValue("border-radius"),
+ "3em 2em 2em / 4em 2em 2em",
+ "border-radius serialization 2");
+
+s.setProperty("border-radius", "2em / 3em", "");
+is(s.getPropertyValue("border-radius"),
+ "2em / 3em",
+ "border-radius serialization 3")
+
+s.setProperty("border-top-left-radius", "4em", "");
+is(s.getPropertyValue("border-radius"),
+ "4em 2em 2em / 4em 3em 3em",
+ "border-radius serialization 3");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_property_database.html b/layout/style/test/test_property_database.html
new file mode 100644
index 0000000000..ba04ec341a
--- /dev/null
+++ b/layout/style/test/test_property_database.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test that property_database.js contains all supported CSS properties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="css_properties.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test that property_database.js contains all supported CSS properties **/
+
+/*
+ * Here we are testing the hand-written property_database.js against
+ * the autogenerated css_properties.js to make sure that everything in
+ * css_properties.js is in property_database.js.
+ *
+ * This prevents CSS properties from being added to the code without
+ * also being put under the minimal test coverage provided by the tests
+ * that use property_database.js.
+ */
+
+for (var idx in gLonghandProperties) {
+ var prop = gLonghandProperties[idx];
+ if (prop.pref && !IsCSSPropertyPrefEnabled(prop.pref)) {
+ continue;
+ }
+ var present = prop.name in gCSSProperties;
+ ok(present,
+ "'" + prop.name + "' listed in gCSSProperties");
+ if (present) {
+ is(gCSSProperties[prop.name].type, CSS_TYPE_LONGHAND,
+ "'" + prop.name + "' listed as CSS_TYPE_LONGHAND");
+ is(gCSSProperties[prop.name].domProp, prop.prop,
+ "'" + prop.name + "' listed with correct DOM property name");
+ }
+}
+for (var idx in gShorthandProperties) {
+ var prop = gShorthandProperties[idx];
+ if (prop.pref && !IsCSSPropertyPrefEnabled(prop.pref)) {
+ continue;
+ }
+ if (prop.name == "all") {
+ // "all" isn't listed in property_database.js.
+ continue;
+ }
+ var present = prop.name in gCSSProperties;
+ ok(present,
+ "'" + prop.name + "' listed in gCSSProperties");
+ if (present) {
+ ok(gCSSProperties[prop.name].type == CSS_TYPE_TRUE_SHORTHAND ||
+ gCSSProperties[prop.name].type == CSS_TYPE_LEGACY_SHORTHAND ||
+ gCSSProperties[prop.name].type == CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ "'" + prop.name + "' listed as CSS_TYPE_TRUE_SHORTHAND, CSS_TYPE_LEGACY_SHORTHAND, or CSS_TYPE_SHORTHAND_AND_LONGHAND");
+ ok(gCSSProperties[prop.name].domProp == prop.prop,
+ "'" + prop.name + "' listed with correct DOM property name");
+ }
+}
+for (var idx in gShorthandPropertiesLikeLonghand) {
+ var prop = gShorthandPropertiesLikeLonghand[idx];
+ if (prop.pref && !IsCSSPropertyPrefEnabled(prop.pref)) {
+ continue;
+ }
+ var present = prop.name in gCSSProperties;
+ ok(present,
+ "'" + prop.name + "' listed in gCSSProperties");
+ if (present) {
+ ok(gCSSProperties[prop.name].type == CSS_TYPE_SHORTHAND_AND_LONGHAND,
+ "'" + prop.name + "' listed as CSS_TYPE_SHORTHAND_AND_LONGHAND");
+ ok(gCSSProperties[prop.name].domProp == prop.prop,
+ "'" + prop.name + "' listed with correct DOM property name");
+ }
+}
+
+/*
+ * Test that all shorthand properties have a subproperty list and all
+ * longhand properties do not.
+ */
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ if (entry.pref && !IsCSSPropertyPrefEnabled(entry.pref)) {
+ continue;
+ }
+ if (entry.type == CSS_TYPE_LONGHAND) {
+ ok(!("subproperties" in entry),
+ "longhand property '" + prop + "' must not have subproperty list");
+ } else if (entry.type == CSS_TYPE_TRUE_SHORTHAND ||
+ entry.type == CSS_TYPE_SHORTHAND_AND_LONGHAND) {
+ ok("subproperties" in entry,
+ "shorthand property '" + prop + "' must have subproperty list");
+ }
+
+ if ("subproperties" in entry) {
+ var good = true;
+ if (entry.subproperties.length < 1) {
+ info("subproperty list for '" + prop + "' is empty");
+ good = false;
+ }
+ for (var idx in entry.subproperties) {
+ var subprop = entry.subproperties[idx];
+ if (!(subprop in gCSSProperties)) {
+ info("subproperty list for '" + prop + "' lists nonexistent subproperty '" + subprop + "'");
+ good = false;
+ }
+ }
+ ok(good, "property '" + prop + "' has a good subproperty list");
+ }
+
+ ok("initial_values" in entry && entry.initial_values.length >= 1,
+ "must have initial values for property '" + prop + "'");
+ ok("other_values" in entry && entry.other_values.length >= 1,
+ "must have non-initial values for property '" + prop + "'");
+}
+
+/*
+ * Test that only longhand properties or its aliases are listed as logical
+ * properties.
+ */
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ if (entry.logical) {
+ ok(entry.type == CSS_TYPE_LONGHAND ||
+ (entry.alias_for && gCSSProperties[entry.alias_for].logical),
+ "property '" + prop + "' is listed as CSS_TYPE_LONGHAND or is an alias due to it " +
+ "being a logical property");
+ }
+}
+
+/*
+ * Test that axis is only specified for logical properties.
+ */
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ if (entry.axis) {
+ ok(entry.logical,
+ "property '" + prop + "' is listed as an logical property due to its " +
+ "being listed as an axis-related property");
+ }
+}
+
+/*
+ * Test that DOM properties match the expected rules.
+ */
+for (var prop in gCSSProperties) {
+ var entry = gCSSProperties[prop];
+ var expectedDOMProp = prop.replace(/-([a-z])/g,
+ function(m, p1, offset, str) {
+ return p1.toUpperCase();
+ });
+ if (expectedDOMProp == "float") {
+ expectedDOMProp = "cssFloat";
+ } else if (prop.startsWith("-webkit")) {
+ // Our DOM accessors for webkit-prefixed properties start with lowercase w,
+ // not uppercase like standard DOM accessors.
+ expectedDOMProp = expectedDOMProp.replace(/^W/, "w");
+ }
+ is(entry.domProp, expectedDOMProp, "DOM property for " + prop);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_property_syntax_errors.html b/layout/style/test/test_property_syntax_errors.html
new file mode 100644
index 0000000000..1e3f382036
--- /dev/null
+++ b/layout/style/test/test_property_syntax_errors.html
@@ -0,0 +1,155 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test that we reject syntax errors listed in property_database.js</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<p id="display"></p>
+<iframe id="quirks" src="data:text/html,<div id='testnode'></div>"></iframe>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
+
+function check_not_accepted(decl, property, info, badval)
+{
+ decl.setProperty(property, badval, "");
+
+ is(decl.getPropertyValue(property), "",
+ "invalid value '" + badval + "' not accepted for '" + property +
+ "' property");
+
+ if ("subproperties" in info) {
+ for (var sidx in info.subproperties) {
+ var subprop = info.subproperties[sidx];
+ is(decl.getPropertyValue(subprop), "",
+ "invalid value '" + badval + "' not accepted for '" + property +
+ "' property when testing subproperty '" + subprop + "'");
+ }
+ }
+
+ decl.removeProperty(property);
+}
+
+function check_value_balanced(decl, property, badval)
+{
+ var goodProp =
+ (property == "background-color") ? "color" : "background-color";
+ decl.cssText = goodProp + ": red; " + property + ": " + badval + "; " +
+ goodProp + ": green";
+ is(decl.getPropertyValue(goodProp), "green",
+ "invalid value '" + property + ": " + badval +
+ "' is balanced and does not lead to parsing errors afterwards");
+ decl.cssText = "";
+}
+
+function check_value_unbalanced(decl, property, badval)
+{
+ var goodProp =
+ (property == "background-color") ? "color" : "background-color";
+ decl.cssText = goodProp + ": green; " + property + ": " + badval + "; " +
+ goodProp + ": red";
+ is(decl.getPropertyValue(goodProp), "green",
+ "invalid value '" + property + ": " + badval +
+ "' is unbalanced and absorbs what follows it");
+ decl.cssText = "";
+}
+
+function check_empty_value_rejected(decl, emptyval, property)
+{
+ var goodProp =
+ (property == "background-color") ? "color" : "background-color";
+ decl.cssText = goodProp + ": red; " + property + ":" + emptyval + "; " +
+ goodProp + ": green";
+ is(decl.length, 1,
+ "empty value '" + property + ":" + emptyval +
+ "' is not accepted");
+ is(decl.getPropertyValue(goodProp), "green",
+ "empty value '" + property + ":" + emptyval +
+ "' is balanced and does not lead to parsing errors afterwards");
+ decl.cssText = "";
+}
+
+function run()
+{
+ var gDeclaration = document.getElementById("testnode").style;
+ var quirksFrame = document.getElementById("quirks");
+ var wrappedFrame = SpecialPowers.wrap(quirksFrame);
+ var gQuirksDeclaration = wrappedFrame.contentDocument
+ .getElementById("testnode").style;
+
+ for (var property in gCSSProperties) {
+ var info = gCSSProperties[property];
+
+ check_empty_value_rejected(gDeclaration, "", property);
+ check_empty_value_rejected(gDeclaration, " ", property);
+
+ for (var idx in info.invalid_values) {
+ check_not_accepted(gDeclaration, property, info,
+ info.invalid_values[idx]);
+ check_not_accepted(gQuirksDeclaration, property, info,
+ info.invalid_values[idx]);
+ check_value_balanced(gDeclaration, property,
+ info.invalid_values[idx]);
+ }
+
+ if ("quirks_values" in info) {
+ for (var quirkval in info.quirks_values) {
+ var standardval = info.quirks_values[quirkval];
+ check_not_accepted(gDeclaration, property, info, quirkval);
+ check_value_balanced(gDeclaration, property, quirkval);
+
+ gQuirksDeclaration.setProperty(property, quirkval, "");
+ gDeclaration.setProperty(property, standardval, "");
+ var quirkret = gQuirksDeclaration.getPropertyValue(property);
+ var standardret = gDeclaration.getPropertyValue(property);
+ isnot(quirkret, "", property + ": " + quirkval +
+ " should be accepted in quirks mode");
+ is(quirkret, standardret, property + ": " + quirkval + " result");
+
+ if ("subproperties" in info) {
+ for (var sidx in info.subproperties) {
+ var subprop = info.subproperties[sidx];
+ var quirksub = gQuirksDeclaration.getPropertyValue(subprop);
+ var standardsub = gDeclaration.getPropertyValue(subprop);
+ isnot(quirksub, "", property + ": " + quirkval +
+ " should be accepted in quirks mode" +
+ " when testing subproperty " + subprop);
+ is(quirksub, standardsub, property + ": " + quirkval + " result" +
+ " when testing subproperty " + subprop);
+ }
+ }
+
+ gQuirksDeclaration.removeProperty(property);
+ gDeclaration.removeProperty(property);
+ }
+ }
+
+ for (var idx in info.unbalanced_values) {
+ check_not_accepted(gDeclaration, property, info,
+ info.invalid_values[idx]);
+ check_not_accepted(gQuirksDeclaration, property, info,
+ info.invalid_values[idx]);
+ check_value_unbalanced(gDeclaration, property,
+ info.unbalanced_values[idx]);
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_pseudo_display_fixup.html b/layout/style/test/test_pseudo_display_fixup.html
new file mode 100644
index 0000000000..de4dd0f810
--- /dev/null
+++ b/layout/style/test/test_pseudo_display_fixup.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test item blockification of pseudo-elements</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #test {
+ display: flex;
+ }
+ #test::before, #test::after {
+ content: "test";
+ display: inline-block;
+ color: green;
+ /*
+ * NOTE(emilio): The transition rule is very intentional, to avoid testing
+ * the eagerly resolved style.
+ */
+ transition: color 1s ease;
+ }
+</style>
+<div id="test"></div>
+<script>
+test(function() {
+ document.body.offsetTop;
+ let test = document.getElementById("test");
+ assert_equals(getComputedStyle(test, "::before").display, "block");
+ assert_equals(getComputedStyle(test, "::after").display, "block");
+}, "::before and ::after pseudo-elements are blockified");
+</script>
diff --git a/layout/style/test/test_pseudoelement_parsing.html b/layout/style/test/test_pseudoelement_parsing.html
new file mode 100644
index 0000000000..b6fcf783f7
--- /dev/null
+++ b/layout/style/test/test_pseudoelement_parsing.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Test for Bug 922669</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+
+<style></style>
+
+<script>
+var style = document.querySelector("style");
+
+var gValidTests = [
+ "::-moz-progress-bar",
+ "::-moz-progress-bar:hover",
+ "::-moz-progress-bar:active",
+ "::-moz-progress-bar:focus",
+ "::-moz-progress-bar:hover:focus",
+ "#a::-moz-progress-bar:hover",
+ ":hover::-moz-progress-bar"
+];
+
+var gInvalidTests = [
+ "::foo",
+ "::-moz-progress-bar::-moz-progress-bar",
+ "::-moz-progress-bar::first-line",
+ "::-moz-progress-bar#a",
+ "::-moz-progress-bar:invalid",
+ "::-moz-focus-inner:active"
+];
+
+gValidTests.forEach(function(aTest) {
+ style.textContent = aTest + "{}";
+ is(style.sheet.cssRules.length, 1, aTest);
+ style.textContent = "";
+});
+
+gInvalidTests.forEach(function(aTest) {
+ style.textContent = aTest + "{}";
+ is(style.sheet.cssRules.length, 0, aTest);
+ style.textContent = "";
+});
+</script>
diff --git a/layout/style/test/test_pseudoelement_state.html b/layout/style/test/test_pseudoelement_state.html
new file mode 100644
index 0000000000..0a8c3d52f6
--- /dev/null
+++ b/layout/style/test/test_pseudoelement_state.html
@@ -0,0 +1,185 @@
+<!DOCTYPE html>
+<title>Test for Bug 922669</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+
+<iframe srcdoc="<!DOCTYPE html><style></style><div></div>"></iframe>
+
+<script>
+var gIsAndroid = navigator.appVersion.includes("Android");
+
+var gTests = [
+ // Interact with the ::-moz-progress-bar.
+ { markup: '<progress value="75" max="100"></progress>',
+ pseudoelement: '::-moz-progress-bar',
+ common_style: 'progress { -moz-appearance: none; } progress::-moz-progress-bar { background: black; }',
+ hover_test_style: 'progress::-moz-progress-bar:hover { background: green; }',
+ hover_reference_style: 'progress::-moz-progress-bar { background: green; }',
+ active_test_style: 'progress::-moz-progress-bar:active { background: lime; }',
+ active_reference_style: 'progress::-moz-progress-bar { background: lime; }' },
+
+ // Interact with the part of the <progress> not covered by the ::-moz-progress-bar.
+ { markup: '<progress value="25" max="100"></progress>',
+ pseudoelement: '::-moz-progress-bar',
+ common_style: 'progress { -moz-appearance: none; } progress::-moz-progress-bar { background: black; }',
+ hover_test_style: 'progress::-moz-progress-bar { background: green; } progress::-moz-progress-bar:hover { background: red; }',
+ hover_reference_style: 'progress::-moz-progress-bar { background: green; }',
+ active_test_style: 'progress::-moz-progress-bar { background: lime; } progress::-moz-progress-bar:active { background: red; }',
+ active_reference_style: 'progress::-moz-progress-bar { background: lime; }' },
+
+ // Interact with the ::-moz-range-thumb.
+ { markup: '<input type="range" value="50" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }',
+ hover_test_style: 'input::-moz-range-thumb:hover { background: green; }',
+ hover_reference_style: 'input::-moz-range-thumb { background: green; }',
+ active_test_style: 'input::-moz-range-thumb:active { background: lime; }',
+ active_reference_style: 'input::-moz-range-thumb { background: lime; }' },
+
+ // Interact with the part of the <input type="range"> not covered by the ::-moz-range-thumb.
+ { markup: '<input type="range" value="25" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }',
+ hover_test_style: 'input::-moz-range-thumb { background: green; } input::-moz-range-thumb:hover { background: red; }',
+ hover_reference_style: 'input::-moz-range-thumb { background: green; }',
+ active_test_style: 'input::-moz-range-thumb { background: lime; } input::-moz-range-thumb:active { background: red; }',
+ active_reference_style: 'input::-moz-range-thumb { background: lime; }' },
+
+ // Interact with the ::-moz-meter-bar.
+ { markup: '<meter value="75" min="0" max="100"></meter>',
+ pseudoelement: '::-moz-meter-bar',
+ common_style: 'meter { -moz-appearance: none; } meter::-moz-meter-bar { background: black; }',
+ hover_test_style: 'meter::-moz-meter-bar:hover { background: green; }',
+ hover_reference_style: 'meter::-moz-meter-bar { background: green; }',
+ active_test_style: 'meter::-moz-meter-bar:active { background: lime; }',
+ active_reference_style: 'meter::-moz-meter-bar { background: lime; }' },
+
+ // Interact with the part of the <meter> not covered by the ::-moz-meter-bar.
+ { markup: '<meter value="25" min="0" max="100"></meter>',
+ pseudoelement: '::-moz-meter-bar',
+ common_style: 'meter { -moz-appearance: none; } meter::-moz-meter-bar { background: black; }',
+ hover_test_style: 'meter::-moz-meter-bar { background: green; } meter::-moz-meter-bar:hover { background: red; }',
+ hover_reference_style: 'meter::-moz-meter-bar { background: green; }',
+ active_test_style: 'meter::-moz-meter-bar { background: lime; } meter::-moz-meter-bar:active { background: red; }',
+ active_reference_style: 'meter::-moz-meter-bar { background: lime; }' },
+
+ // Do the same as the "Interact with the ::-moz-range-thumb" subtest above,
+ // but with selectors that include descendant operators.
+ { markup: '<input type="range" value="50" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'body input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }',
+ hover_test_style: 'body input::-moz-range-thumb:hover { background: green; }',
+ hover_reference_style: 'body input::-moz-range-thumb { background: green; }',
+ active_test_style: 'body input::-moz-range-thumb:active { background: lime; }',
+ active_reference_style: 'body input::-moz-range-thumb { background: lime; }' },
+
+ // Do the same as above, but using :is instead.
+ { markup: '<input type="range" value="50" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'body input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }',
+ hover_test_style: 'body input::-moz-range-thumb:is(:hover) { background: green; }',
+ hover_reference_style: 'body input::-moz-range-thumb { background: green; }',
+ active_test_style: 'body input::-moz-range-thumb:is(:active) { background: lime; }',
+ active_reference_style: 'body input::-moz-range-thumb { background: lime; }' },
+
+ // Do the same as above, but using :not instead.
+ { markup: '<input type="range" value="50" min="0" max="100">',
+ pseudoelement: '::-moz-range-thumb',
+ common_style: 'input { -moz-appearance: none; } input::-moz-range-thumb { background: green } input::-moz-range-thumb:not(:hover) { background: black; }',
+ hover_test_style: '',
+ hover_reference_style: 'input::-moz-range-thumb { background: green !important; }',
+ active_test_style: 'input::-moz-range-thumb:active { background: lime !important; }',
+ active_reference_style: 'input::-moz-range-thumb { background: lime !important; }' },
+
+ // ::placeholder can't be tested, since the UA style sheet sets it to
+ // be pointer-events:none.
+];
+
+function countPixelDifferences(aCanvas1, aCanvas2) {
+ var ctx1 = aCanvas1.getContext("2d");
+ var ctx2 = aCanvas2.getContext("2d");
+ var data1 = ctx1.getImageData(0, 0, aCanvas1.width, aCanvas1.height);
+ var data2 = ctx2.getImageData(0, 0, aCanvas2.width, aCanvas2.height);
+ var n = 0;
+ for (var i = 0; i < data1.width * data2.height * 4; i += 4) {
+ if (data1.data[i] != data2.data[i] ||
+ data1.data[i + 1] != data2.data[i + 1] ||
+ data1.data[i + 2] != data2.data[i + 2] ||
+ data1.data[i + 3] != data2.data[i + 3]) {
+ n++;
+ }
+ }
+ return n;
+}
+
+function runTests() {
+ var iframe = document.querySelector("iframe");
+ var style = iframe.contentDocument.querySelector("style");
+ var div = iframe.contentDocument.querySelector("div");
+ var canvas1, canvas2;
+
+ function runTestPart1(aIndex) {
+ var test = gTests[aIndex];
+ div.innerHTML = test.markup;
+ style.textContent = test.common_style;
+ is(getComputedStyle(div.firstChild, test.pseudoelement).backgroundColor, "rgb(0, 0, 0)", "subtest #" + aIndex + ", computed style");
+ style.textContent += test.hover_test_style;
+ synthesizeMouseAtCenter(div.lastChild, { type: 'mouseover' }, iframe.contentWindow);
+ }
+
+ function runTestPart2(aIndex) {
+ var test = gTests[aIndex];
+ canvas1 = SpecialPowers.snapshotWindow(iframe.contentWindow, false);
+ style.textContent = test.common_style + test.hover_reference_style;
+ }
+
+ function runTestPart3(aIndex) {
+ var test = gTests[aIndex];
+ canvas2 = SpecialPowers.snapshotWindow(iframe.contentWindow, false);
+ ok(canvas1.width == canvas2.width && canvas1.height == canvas2.height, "hover subtest #" + aIndex + ", canvas sizes equal");
+ is(countPixelDifferences(canvas1, canvas2), 0, "hover subtest #" + aIndex + ", number of different pixels");
+ is(getComputedStyle(div.firstChild, test.pseudoelement).backgroundColor, "rgb(0, 128, 0)", "hover subtest #" + aIndex + ", computed style");
+
+ if (!gIsAndroid) {
+ style.textContent = test.common_style + test.active_test_style;
+ synthesizeMouseAtCenter(div.lastChild, { type: 'mousedown' }, iframe.contentWindow);
+ }
+ }
+
+ function runTestPart4(aIndex) {
+ if (!gIsAndroid) {
+ var test = gTests[aIndex];
+ canvas1 = SpecialPowers.snapshotWindow(iframe.contentWindow, false);
+ synthesizeMouseAtCenter(div.lastChild, { type: 'mouseup' }, iframe.contentWindow);
+ style.textContent = test.common_style + test.active_reference_style;
+ }
+ }
+
+ function runTestPart5(aIndex) {
+ if (!gIsAndroid) {
+ var test = gTests[aIndex];
+ canvas2 = SpecialPowers.snapshotWindow(iframe.contentWindow, false);
+ ok(canvas1.width == canvas2.width && canvas1.height == canvas2.height, "active subtest #" + aIndex + ", canvas sizes equal");
+ is(countPixelDifferences(canvas1, canvas2), 0, "active subtest #" + aIndex + ", number of different pixels");
+ is(getComputedStyle(div.firstChild, test.pseudoelement).backgroundColor, "rgb(0, 255, 0)", "active subtest #" + aIndex + ", computed style");
+ }
+ }
+
+ for (var i = 0; i < gTests.length; i++) {
+ setTimeout(runTestPart1, 0, i);
+ setTimeout(runTestPart2, 0, i);
+ setTimeout(runTestPart3, 0, i);
+ setTimeout(runTestPart4, 0, i);
+ setTimeout(runTestPart5, 0, i);
+ }
+ setTimeout(function() { SimpleTest.finish(); }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+window.addEventListener("load", async function() {
+ await SpecialPowers.contentTransformsReceived(window);
+ runTests();
+});
+</script>
diff --git a/layout/style/test/test_query_container_for.html b/layout/style/test/test_query_container_for.html
new file mode 100644
index 0000000000..90dca9fab9
--- /dev/null
+++ b/layout/style/test/test_query_container_for.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<title>Test for bug 1789191</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<style>
+@container (min-width: 0px) {
+}
+
+@container name (min-width: 0px) {
+}
+
+@container (min-height: 0px) {
+}
+
+div {
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+ margin: 10px;
+}
+
+.container-height {
+ container-type: size;
+}
+
+.container-unnamed {
+ container-type: inline-size;
+}
+
+.container-named {
+ container-type: inline-size;
+ container-name: name;
+}
+</style>
+
+<div id="child1"></div>
+
+<div class="container-height" id="container1">
+ <div class="container-named" id="container2">
+ <div class="container-unnamed" id="container3">
+ <div id="child2"></div>
+ </div>
+ </div>
+</div>
+
+<script>
+ let sheet = document.querySelector("style").sheet;
+ let withoutFilter = SpecialPowers.wrap(sheet.cssRules[0]);
+ let withFilter = SpecialPowers.wrap(sheet.cssRules[1]);
+ let heightQuery = SpecialPowers.wrap(sheet.cssRules[2]);
+
+ // Container query selection requires up-to-date layout information.
+ document.body.getBoundingClientRect();
+
+ is(withoutFilter.queryContainerFor(child1), null, "No filter, no container");
+ is(withFilter.queryContainerFor(child1), null, "Filter, no container");
+ is(heightQuery.queryContainerFor(child1), null, "Height, no container");
+
+ is(SpecialPowers.unwrap(withoutFilter.queryContainerFor(child2)), container3, "No filter, container");
+ is(SpecialPowers.unwrap(withFilter.queryContainerFor(child2)), container2, "Filter");
+ is(SpecialPowers.unwrap(heightQuery.queryContainerFor(child2)), container1, "Height");
+</script>
diff --git a/layout/style/test/test_redundant_font_download.html b/layout/style/test/test_redundant_font_download.html
new file mode 100644
index 0000000000..c6930ae401
--- /dev/null
+++ b/layout/style/test/test_redundant_font_download.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=879963 -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for bug 879963</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+ <!-- Two <style> elements with identical @font-face rules.
+ Although multiple @font-face at-rules for the same family are legal,
+ and add faces to the family, we should NOT download the same resource
+ twice just because we have a duplicate face entry. -->
+ <style type="text/css">
+ @font-face {
+ font-family: foo;
+ src: url("redundant_font_download.sjs?q=font");
+ }
+ .test {
+ font-family: foo;
+ }
+ </style>
+
+ <style type="text/css">
+ @font-face {
+ font-family: foo;
+ src: url("redundant_font_download.sjs?q=font");
+ }
+ .test {
+ font-family: foo;
+ }
+ </style>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=879963">Mozilla Bug 879963</a>
+
+ <div>
+ <!-- the 'init' request returns an image (just so we can see it's working)
+ and initializes the request logging in our sjs server -->
+ <img src="redundant_font_download.sjs?q=init">
+ </div>
+
+ <div id="test">
+ Test
+ </div>
+
+ <div>
+ <img id="image2" src="">
+ </div>
+
+ <script type="application/javascript">
+ // helper to retrieve the server's request log as text, synchronously
+ function getRequestLog() {
+ var xmlHttp = new XMLHttpRequest();
+ xmlHttp.open("GET", "redundant_font_download.sjs?q=report", false);
+ xmlHttp.send(null);
+ return xmlHttp.responseText;
+ }
+
+ // retrieve just the most recent request the server has seen
+ function getLastRequest() {
+ return getRequestLog().split(";").pop();
+ }
+
+ // poll the server at intervals of animation frame callback until it has
+ // seen a specific request, or until maxTime ms have passed
+ function waitForRequest(request, maxTime, func) {
+ timeLimit = Date.now() + maxTime;
+ requestAnimationFrame(function rAF() {
+ var req = getLastRequest();
+ if (req == request || Date.now() > timeLimit) {
+ func();
+ return;
+ }
+ requestAnimationFrame(rAF);
+ });
+ }
+
+ // initially disable the second of the <style> elements,
+ // so we only have a single copy of the font-face
+ document.getElementsByTagName("style")[1].disabled = true;
+
+ SimpleTest.waitForExplicitFinish();
+
+ // We perform a series of actions that trigger requests to the .sjs server,
+ // and poll the server's request log at each stage to check that it has
+ // seen the request we expected before we proceed to the next step.
+ function startTest() {
+ is(getRequestLog(), "init", "request log has been initialized");
+
+ // this should trigger a font download
+ document.getElementById("test").className = "test";
+
+ // wait to confirm that the server has received the request
+ waitForRequest("font", 5000, continueTest1);
+ }
+
+ function continueTest1() {
+ is(getRequestLog(), "init;font", "server received font request");
+
+ // trigger a request for the second image, to provide an explicit
+ // delimiter in the log before we enable the second @font-face rule
+ document.getElementById("image2").src = "redundant_font_download.sjs?q=image";
+
+ waitForRequest("image", 5000, continueTest2);
+ }
+
+ function continueTest2() {
+ is(getRequestLog(), "init;font;image", "server received image request");
+
+ // enable the second <style> element: we should NOT see a second font request,
+ // we expect waitForRequest to time out instead
+ document.getElementsByTagName("style")[1].disabled = false;
+
+ waitForRequest("font", 1000, continueTest3);
+ }
+
+ function continueTest3() {
+ is(getRequestLog(), "init;font;image", "should NOT have re-requested the font");
+
+ SimpleTest.finish();
+ }
+
+ waitForRequest("init", 5000, startTest);
+
+ </script>
+
+</body>
+
+</html>
diff --git a/layout/style/test/test_reframe_cb.html b/layout/style/test/test_reframe_cb.html
new file mode 100644
index 0000000000..549d04c5c0
--- /dev/null
+++ b/layout/style/test/test_reframe_cb.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1519371: We don't reframe for changes that affect our containing
+ block status unless whether we're a containing block really changed.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div id="content"></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+
+function expectReframe(shouldHaveReframed, callback) {
+ document.documentElement.offsetTop;
+ const previousConstructCount = utils.framesConstructed;
+ const previousRestyleGeneration = utils.restyleGeneration;
+
+ callback();
+
+ document.documentElement.offsetTop;
+ isnot(previousRestyleGeneration, utils.restyleGeneration,
+ "We should have restyled");
+ (shouldHaveReframed ? isnot : is)(previousConstructCount,
+ utils.framesConstructed,
+ `We should ${shouldHaveReframed ? "" : "not"} have reframed`);
+}
+
+const content = document.getElementById("content");
+
+// Without fixed-pos descendants, we should not reframe.
+expectReframe(false, () => {
+ content.style.transform = "scale(1)";
+});
+
+content.style.transform = "";
+content.innerHTML = `<div style="position: fixed"></div>`;
+
+// With fixed-pos descendants, got to reframe.
+expectReframe(true, () => {
+ content.style.transform = "scale(1)";
+});
+
+// If our containing-block-ness didn't change, we should not need to reframe.
+expectReframe(false, () => {
+ content.style.willChange = "transform";
+});
+
+// If it does change, we need to reframe.
+expectReframe(true, () => {
+ content.style.willChange = "";
+ content.style.transform = "";
+});
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_reframe_image_loading.html b/layout/style/test/test_reframe_image_loading.html
new file mode 100644
index 0000000000..2f8a44c361
--- /dev/null
+++ b/layout/style/test/test_reframe_image_loading.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1395964: We don't reframe for image loading state changes.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<img id="content" src="support/1x1-transparent.png"></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+
+const content = document.getElementById("content");
+
+onload = function() {
+ content.offsetTop;
+
+ const previousConstructCount = utils.framesConstructed;
+
+ content.addEventListener("load", function() {
+ content.offsetTop;
+ is(previousConstructCount, utils.framesConstructed,
+ "We should not have reframed");
+ SimpleTest.finish();
+ });
+
+ content.src = "support/blue-100x100.png";
+}
+</script>
diff --git a/layout/style/test/test_reframe_input.html b/layout/style/test/test_reframe_input.html
new file mode 100644
index 0000000000..2887548abf
--- /dev/null
+++ b/layout/style/test/test_reframe_input.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for bug 1658302: We don't reframe for placeholder attribute value changes.</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<input id="input">
+<textarea id="textarea"></textarea>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.DOMWindowUtils;
+
+function expectReframe(shouldHaveReframed, callback) {
+ document.documentElement.offsetTop;
+ const previousConstructCount = utils.framesConstructed;
+ const previousReflowCount = utils.framesReflowed;
+
+ callback();
+
+ document.documentElement.offsetTop;
+ isnot(previousReflowCount, utils.framesReflowed, "We should have reflowed");
+ (shouldHaveReframed ? isnot : is)(previousConstructCount,
+ utils.framesConstructed,
+ `We should ${shouldHaveReframed ? "" : "not"} have reframed`);
+}
+
+for (const control of document.querySelectorAll("input, textarea")) {
+ // Creating the placeholder attribute reframes right now.
+ //
+ // TODO: Could be avoided with some more work.
+ expectReframe(true, () => {
+ control.placeholder = "foo";
+ });
+
+ // Incrementally changing it should not reframe, just reflow.
+ expectReframe(false, () => {
+ control.placeholder = "bar";
+ });
+
+ // Removing the placeholder attribute reframes right now.
+ //
+ // TODO: Could maybe be avoided with some more work.
+ expectReframe(true, () => {
+ control.removeAttribute("placeholder");
+ });
+}
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_reframe_pseudo_element.html b/layout/style/test/test_reframe_pseudo_element.html
new file mode 100644
index 0000000000..0e0b418e81
--- /dev/null
+++ b/layout/style/test/test_reframe_pseudo_element.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1376352: We don't reframe all the time a replaced element that
+ matches generated content rules.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+#flex::before,
+input::before {
+ content: "Foo";
+}
+</style>
+<input type="text">
+<div id="flex"></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+
+function testNoReframe(callback) {
+ document.documentElement.offsetTop;
+ const previousConstructCount = utils.framesConstructed;
+ const previousRestyleGeneration = utils.restyleGeneration;
+
+ callback();
+
+ document.documentElement.offsetTop;
+ isnot(previousRestyleGeneration, utils.restyleGeneration,
+ "We should have restyled");
+ is(previousConstructCount, utils.framesConstructed,
+ "We shouldn't have reframed");
+}
+
+testNoReframe(function() {
+ const input = document.querySelector('input');
+ input.style.color = "blue";
+});
+
+testNoReframe(function() {
+ const flex = document.getElementById('flex');
+ flex.style.color = "blue";
+});
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_rem_unit.html b/layout/style/test/test_rem_unit.html
new file mode 100644
index 0000000000..bd46524798
--- /dev/null
+++ b/layout/style/test/test_rem_unit.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=478321
+-->
+<head>
+ <title>Test for CSS 'rem' unit</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478321">Mozilla Bug 478321</a>
+<p id="display"></p>
+<p id="display2"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for CSS 'rem' unit **/
+
+function px_to_num(str)
+{
+ return Number(String(str).match(/^([\d.]+)px$/)[1]);
+}
+
+function fs(elt)
+{
+ return px_to_num(getComputedStyle(elt, "").fontSize);
+}
+
+var html = document.documentElement;
+var body = document.body;
+var p = document.getElementById("display");
+var p2 = document.getElementById("display2");
+
+html.style.font = "initial";
+
+var defaultFontSize = fs(html);
+
+// NOTE: This test assumes that the default font size is an
+// integral number of pixels (which is always the case at present).
+// If that ever becomes false, the calls to "is" may need to be replaced by
+// calls to "isapprox" that allows errors of up to some small fraction
+// of a pixel.
+
+html.style.fontSize = "3rem";
+is(fs(html), 3 * defaultFontSize,
+ "3rem on root should triple root's font size");
+body.style.font = "initial";
+is(fs(body), defaultFontSize,
+ "initial should produce initial font size");
+body.style.fontSize = "1em";
+is(fs(body), 3 * defaultFontSize, "1em should inherit from parent");
+body.style.fontSize = "200%";
+is(fs(body), 6 * defaultFontSize, "200% should double parent");
+body.style.fontSize = "2rem";
+is(fs(body), 6 * defaultFontSize, "2rem should double root");
+p.style.font = "inherit";
+is(fs(p), 6 * defaultFontSize, "inherit should inherit from parent");
+p2.style.fontSize = "2rem";
+is(fs(p2), 6 * defaultFontSize, "2rem should double root");
+body.style.font = "initial";
+is(fs(p), defaultFontSize, "inherit should inherit from parent");
+is(fs(p2), 6 * defaultFontSize, "2rem should double root");
+body.style.fontSize = "5em";
+html.style.fontSize = "200%";
+is(fs(p), 10 * defaultFontSize, "inherit should inherit from parent");
+is(fs(p2), 4 * defaultFontSize, "2rem should double root");
+
+
+// Make things readable again.
+html.style.fontSize = "1em";
+body.style.fontSize = "1em";
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_restyle_table_wrapper.html b/layout/style/test/test_restyle_table_wrapper.html
new file mode 100644
index 0000000000..d8049b142e
--- /dev/null
+++ b/layout/style/test/test_restyle_table_wrapper.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1371955: We don't incorrectly think that a table wrapper style
+ is the main table element style.
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+/* Test implicitly ::before and ::after too */
+span::before, span::after {
+ content: "";
+ display: table;
+}
+</style>
+<table id="realTable" style="margin: 10px"></table>
+<span id="spanTable" style="display: table; padding: 10px;"></span>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+document.documentElement.offsetTop;
+for (const element of [realTable, spanTable]) {
+ const previousReflowCount = utils.framesReflowed;
+ const previousRestyleGeneration = utils.restyleGeneration;
+ element.style.color = "blue";
+ document.documentElement.offsetTop;
+ isnot(previousRestyleGeneration, utils.restyleGeneration,
+ "We should have restyled");
+ is(previousReflowCount, utils.framesReflowed,
+ "We shouldn't have reflowed");
+}
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_restyles_in_smil_animation.html b/layout/style/test/test_restyles_in_smil_animation.html
new file mode 100644
index 0000000000..17c1ebb2ab
--- /dev/null
+++ b/layout/style/test/test_restyles_in_smil_animation.html
@@ -0,0 +1,137 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Tests restyles in smil animation</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+
+<div id="target-div">
+ <svg>
+ <rect id="svg-rect" width="100%" height="100%" fill="lime"/>
+ </svg>
+</div>
+
+<script>
+"use strict";
+
+// Waits for |frameCount| requestAnimationFrame callbacks.
+// Returns the number of frame actually waited.
+function waitForAnimationFrames(frameCount) {
+ return new Promise(function(resolve, reject) {
+ let previousTime = document.timeline.currentTime;
+ let framesWaited = 0;
+ function handleFrame() {
+ // SMIL uses a time resolution of 1ms but our software-based vsync timer
+ // sometimes produces ticks with an interval of less than 1ms. In such
+ // cases we will skip restyling for SMIL animations since the SMIL time
+ // will not change.
+ //
+ // In the following we attempt to detect such situations and wait an
+ // additional frame when we detect it. However, the detection is not
+ // completely accurate since it uses the timeline time which is based on
+ // the navigation start time whereas the SMIL start time is based on the
+ // refresh driver time.
+ //
+ // To account for this inaccuracy the Promise returned by this method
+ // resolves with the number of frames waited with the additional frames.
+ // This can be used by the call site to add a suitable tolerance to the
+ // number of restylings it expects to happen. For example, if a call site
+ // is anticipating each animation frame to cause restyling, then the
+ // number of restylings, x, it should expect is frameCount <= x <=
+ // framesWaited.
+ const difference = document.timeline.currentTime - previousTime;
+ framesWaited++;
+ if (difference >= 1.0 && --frameCount <= 0) {
+ resolve(framesWaited);
+ return;
+ }
+
+ previousTime = document.timeline.currentTime;
+ window.requestAnimationFrame(handleFrame); // wait another frame
+ }
+ window.requestAnimationFrame(handleFrame);
+ });
+}
+
+// Returns an object consisting of observed styling count and the number of
+// frames actually waited because we detected a possibly overflapping SMIL
+// time.
+function observeStyling(frameCount) {
+ let priorAnimationTriggeredRestyles = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles;
+
+ return new Promise(function(resolve) {
+ return waitForAnimationFrames(frameCount).then(framesWaited => {
+ const restyleCount = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles - priorAnimationTriggeredRestyles;
+ resolve({
+ stylingCount: restyleCount,
+ framesWaited: framesWaited,
+ });
+ });
+ });
+}
+
+function ensureElementRemoval(aElement) {
+ return new Promise(function(resolve) {
+ aElement.remove();
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+function waitForPaintFlushed() {
+ return new Promise(function(resolve) {
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+add_task(async function smil_is_in_display_none_subtree() {
+ await waitForPaintFlushed();
+
+ var animate =
+ document.createElementNS("http://www.w3.org/2000/svg", "animate");
+ animate.setAttribute("attributeType", "XML");
+ animate.setAttribute("attributeName", "fill");
+ animate.setAttribute("values", "red;lime");
+ animate.setAttribute("dur", "1s");
+ animate.setAttribute("repeatCount", "indefinite");
+ document.getElementById("svg-rect").appendChild(animate);
+
+ await waitForAnimationFrames(2);
+
+ let result = await observeStyling(5);
+ // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
+ // element is newly associated with an nsIFrame.
+ ok(result.stylingCount >= 4 &&
+ result.stylingCount <= result.framesWaited,
+ `should restyle in most frames (got ${result.stylingCount} restyles ` +
+ `over ${result.framesWaited} frames, expected 4~${result.framesWaited})`);
+
+ var div = document.getElementById("target-div");
+
+ div.style.display = "none";
+ getComputedStyle(div).display;
+ await waitForPaintFlushed();
+
+ result = await observeStyling(5);
+ is(result.stylingCount, 0, "should never restyle if display:none");
+
+ div.style.display = "";
+ getComputedStyle(div).display;
+ await waitForAnimationFrames(2);
+
+ result = await observeStyling(5);
+ // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
+ // element is newly associated with an nsIFrame.
+ ok(result.stylingCount >= 4 &&
+ result.stylingCount <= result.framesWaited,
+ `should restyle again (got ${result.stylingCount} restyles over ` +
+ `${result.framesWaited} frames, expected 4~${result.framesWaited})`);
+
+ await ensureElementRemoval(animate);
+});
+</script>
+</body>
diff --git a/layout/style/test/test_revert.html b/layout/style/test/test_revert.html
new file mode 100644
index 0000000000..48897a75ca
--- /dev/null
+++ b/layout/style/test/test_revert.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<title>Test for computation of CSS 'revert' on all properties</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="property_database.js"></script>
+<style id="stylesheet"></style>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<div id="inheritanceParent">
+ <div id="inherited"></div>
+</div>
+<div id="nonInherited"></div>
+<div id="noAuthorStyleApplied"></div>
+<pre id="test">
+<script class="testbody">
+
+const kSheet = document.getElementById("stylesheet").sheet;
+const kAllDifferentFromInitialRule = kSheet.cssRules[kSheet.insertRule("#inheritanceParent {}", kSheet.cssRules.length)];
+const kPrerequisites = kSheet.cssRules[kSheet.insertRule("#inherited, #inheritanceParent, #nonInherited, #noAuthorStyleApplied {}", kSheet.cssRules.length)];
+const kResetPropRule = kSheet.cssRules[kSheet.insertRule("#nonInherited {}", kSheet.cssRules.length)];
+
+const kInheritedDiv = document.getElementById("inherited");
+const kResetDiv = document.getElementById("nonInherited");
+const kNoAuthorStylesDiv = document.getElementById("noAuthorStyleApplied");
+
+function computedValue(node, property) {
+ return get_computed_value(getComputedStyle(node), property);
+}
+
+function getInitialValue(node, property) {
+ node.style.setProperty(property, "initial");
+ const initial = computedValue(node, property);
+ node.style.removeProperty(property);
+ return initial;
+}
+
+function testResetProperty(property, info) {
+ kResetPropRule.style.setProperty(property, info.other_values[0]);
+
+ const div = kResetDiv;
+ const initial = getInitialValue(div, property);
+ const computed = computedValue(div, property);
+
+ isnot(computed, initial, `${property}: Should get something non-initial to begin with`);
+
+ const defaultValue = computedValue(kNoAuthorStylesDiv, property);
+
+ div.style.setProperty(property, "revert");
+ const reverted = computedValue(div, property);
+ is(reverted, defaultValue, `${property}: Should behave as if there was no author style`);
+ div.style.removeProperty(property);
+ kResetPropRule.style.removeProperty(property);
+}
+
+function testInheritedProperty(property, info) {
+ // Given how line-height works, and that it always returns the used value, we
+ // cannot test it. The prerequisites for line-height makes getComputedStyle
+ // and getDefaultComputedStyle return the same, even though the computed value
+ // is different (normal vs. 19px).
+ if (property == "line-height")
+ return;
+
+ const div = kInheritedDiv;
+ const initial = getInitialValue(div, property);
+ const parentValue = computedValue(div.parentNode, property);
+
+ isnot(parentValue, initial, `${property}: Should inherit something non-initial to begin with`);
+
+ const inheritedValue = computedValue(div, property);
+ const hasUARule = inheritedValue != parentValue;
+
+ const defaultValue = computedValue(kNoAuthorStylesDiv, property);
+ (hasUARule ? is : isnot)(defaultValue, inheritedValue, `${property}: Should get the non-inherited value from somewhere (expected ${hasUARule ? "UA Rule" : "inheritance"} - inherited: ${inheritedValue} - parent: ${parentValue} - default: ${defaultValue})`);
+
+ div.style.setProperty(property, "revert");
+ const reverted = computedValue(div, property);
+ if (hasUARule)
+ is(reverted, defaultValue, `${property}: Should behave as if there was no author style`);
+ else
+ is(reverted, inheritedValue, `${property}: Should behave as if there was no author style`);
+ div.style.removeProperty(property);
+}
+
+function testProperty(property, info) {
+ if (info.prerequisites)
+ for (const prereq in info.prerequisites)
+ kPrerequisites.style.setProperty(prereq, info.prerequisites[prereq]);
+ if (info.inherited)
+ testInheritedProperty(property, info);
+ else
+ testResetProperty(property, info);
+ kPrerequisites.style = "";
+}
+
+for (const prop in gCSSProperties) {
+ const info = gCSSProperties[prop];
+ if (info.type != CSS_TYPE_LONGHAND)
+ continue;
+ kAllDifferentFromInitialRule.style.setProperty(prop, info.other_values[0]);
+}
+
+for (const prop in gCSSProperties)
+ testProperty(prop, gCSSProperties[prop]);
+</script>
+</pre>
diff --git a/layout/style/test/test_root_node_display.html b/layout/style/test/test_root_node_display.html
new file mode 100644
index 0000000000..54dd9c222c
--- /dev/null
+++ b/layout/style/test/test_root_node_display.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=969460
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 969460</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=969460">Mozilla Bug 969460</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="float" style="float: left"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 969460: Test that "display" on the root node is computed
+ using the same conversion that we use for display on floated elements **/
+
+function test_display_value(val)
+{
+ var floatElem = document.getElementById("float");
+ floatElem.style.display = val;
+ var floatConversion = window.getComputedStyle(floatElem).display;
+ floatElem.style.display = "";
+
+ var rootNode = document.documentElement;
+ rootNode.style.display = val;
+ rootNode.offsetHeight; // (Flush layout, to be sure layout can handle 'val')
+ var rootConversion = window.getComputedStyle(rootNode).display;
+ rootNode.style.display = "";
+
+ // Special case: "display:list-item" does not get modified by 'float',
+ // but the spec allows us to convert it to 'block' on the root node
+ // (and we do convert it, so that we don't have to support documents whose
+ // root node is a list-item).
+ if (val == "list-item") {
+ is(floatConversion, val, "'float' shouldn't affect 'display:list-item'");
+ is(rootConversion, "block",
+ "We traditionally convert '" + val + "' on the root node to " +
+ "'display:block' (though if that changes, it's not technically a bug, " +
+ "as long as we support it properly).");
+} else if (val == "inline list-item" ||
+ val == "inline flow-root list-item") {
+ is(floatConversion, "list-item", "'float' should blockify '" + val + "'");
+ is(rootConversion, "block",
+ "We traditionally convert '" + val + "' on the root node to " +
+ "'display:block' (though if that changes, it's not technically a bug, " +
+ "as long as we support it properly).");
+ } else if (val == "contents") {
+ is(floatConversion, val, "'float' shouldn't affect 'display:contents'");
+ is(rootConversion, "block",
+ "'display:contents' on the root node computes to block-level per" +
+ "http://dev.w3.org/csswg/css-display/#transformations");
+ } else {
+ is(rootConversion, floatConversion,
+ "root node should make 'display:" + val + "' compute to the same " +
+ "value that it computes to on a floated element");
+ }
+}
+
+var displayInfo = gCSSProperties.display;
+displayInfo.initial_values.forEach(test_display_value);
+displayInfo.other_values.forEach(test_display_value);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_rule_insertion.html b/layout/style/test/test_rule_insertion.html
new file mode 100644
index 0000000000..e3103309aa
--- /dev/null
+++ b/layout/style/test/test_rule_insertion.html
@@ -0,0 +1,240 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=816720
+-->
+<head>
+ <title>Test for Bug 816720</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css" id="style"></style>
+</head>
+<body>
+
+<pre id="test"></pre>
+
+<p><span id=control-serif>........</span></p>
+<p><span id=control-monospace>........</span></p>
+<p><span id=test-font>........</span></p>
+
+<style id=other-styles>
+ #test { font-size: 16px; animation: test 1s both }
+ #control-serif { font: 16px serif }
+ #test-font { font: 16px UnlikelyFontName, serif }
+</style>
+
+<p><span id=control-decimal></span></p>
+<p><span id=control-cjk-decimal></span></p>
+<p><span id=test-counter-style></span></p>
+
+<style>
+ #control-decimal::before { content: counter(a, decimal); }
+ #control-cjk-decimal::before { content: counter(a, cjk-decimal); }
+ #test-counter-style::before { content: counter(a, unlikely-counter-style); }
+</style>
+
+<script type="application/javascript">
+
+// Monospace fonts available on all the platforms we're testing on.
+//
+// XXX Once bug 817220 is fixed we could instead use the value of
+// font.name.monospace.x-western as the monospace font to use.
+var MONOSPACE_FONTS = [
+ "Courier",
+ "Courier New",
+ "Monaco",
+ "DejaVu Sans Mono",
+ "Droid Sans Mono"
+];
+
+var test = document.getElementById("test");
+var controlSerif = document.getElementById("control-serif");
+var controlMonospace = document.getElementById("control-monospace");
+var testFont = document.getElementById("test-font");
+var otherStyles = document.getElementById("other-styles");
+
+otherStyles.sheet.insertRule("#control-monospace { font: 16px " +
+ MONOSPACE_FONTS + ", serif }", 0);
+
+var monospaceWidth = controlMonospace.getBoundingClientRect().width;
+var serifWidth = controlSerif.getBoundingClientRect().width;
+
+var controlDecimal = document.getElementById("control-decimal");
+var controlCJKDecimal = document.getElementById("control-cjk-decimal");
+var testCounterStyle = document.getElementById("test-counter-style");
+
+var decimalWidth = controlDecimal.getBoundingClientRect().width;
+var cjkDecimalWidth = controlCJKDecimal.getBoundingClientRect().width;
+
+// [at-rule type, passing condition, failing condition]
+var outerRuleInfo = [
+ ["@media", "all", "not all"],
+ ["@supports", "(color: green)", "(unknown: unknown)"]
+];
+
+// [rule, function to test whether the rule was successfully inserted and applied]
+var innerRuleInfo = [
+ ["#test { text-decoration: underline; }",
+ function(aApplied, aParent, aException) {
+ return !aException &&
+ window.getComputedStyle(test).textDecorationLine ==
+ (aApplied ? "underline" : "none");
+ }],
+ ["@page { margin: 4cm; }",
+ function(aApplied, aParent, aException) {
+ // just test whether it threw
+ return !aException;
+ }],
+ ["@keyframes test { from { font-size: 100px; } to { font-size: 100px; } }",
+ function(aApplied, aParent, aException) {
+ return !aException &&
+ window.getComputedStyle(test).fontSize ==
+ (aApplied ? "100px" : "16px")
+ }],
+ ["@font-face { font-family: UnlikelyFontName; src: " +
+ MONOSPACE_FONTS.map(function(s) { return "local('" + s + "')" }).join(", ") + "; }",
+ function(aApplied, aParent, aException) {
+ var width = testFont.getBoundingClientRect().width;
+ if (aException) {
+ return false;
+ }
+ if (navigator.oscpu.match(/Linux/) ||
+ navigator.oscpu.match(/Android/) ||
+ SpecialPowers.Services.appinfo.name == "B2G") {
+ return true;
+ }
+ return Math.abs(width - (aApplied ? monospaceWidth : serifWidth)) <= 1; // bug 769194 prevents local()
+ // fonts working on Android
+ }],
+ ["@import url(nothing.css);",
+ function(aApplied, aParent, aException) {
+ // just test whether it threw
+ return aParent instanceof CSSRule ? aException : !aException;
+ }],
+ ["@namespace test url(http://example.org);",
+ function(aApplied, aParent, aException) {
+ // just test whether it threw
+ return aParent instanceof CSSRule ? aException : !aException;
+ }],
+ ["@counter-style unlikely-counter-style { system: extends cjk-decimal; }",
+ function (aApplied, aParent, aException) {
+ var width = testCounterStyle.getBoundingClientRect().width;
+ if (aException) {
+ return false;
+ }
+ return width == (aApplied ? cjkDecimalWidth : decimalWidth);
+ }],
+];
+
+function runTest()
+{
+ // First, assert that our assumed available fonts are indeed available
+ // and have expected metrics.
+ ok(monospaceWidth > 0, "monospace text has width");
+ ok(serifWidth > 0, "serif text has width");
+ ok(Math.abs(monospaceWidth - serifWidth) > 1, "monospace and serif text have sufficiently different widths");
+
+ // And that the #test-font element starts off using the "serif" font.
+ var initialFontTestWidth = testFont.getBoundingClientRect().width;
+ is(initialFontTestWidth, serifWidth);
+
+ ok(decimalWidth > 0, "decimal counter has width");
+ ok(cjkDecimalWidth > 0, "cjk-decimal counter has width");
+ ok(decimalWidth != cjkDecimalWidth, "decimal and cjk-decimal counter have different width")
+
+ var initialCounterStyleWidth = testCounterStyle.getBoundingClientRect().width;
+ is(initialCounterStyleWidth, decimalWidth, "initial counter style is decimal");
+
+ // We construct a style sheet with zero, one or two levels of conditional
+ // grouping rules (taken from outerRuleInfo), with one of the inner rules
+ // at the deepest level.
+ var style = document.getElementById("style");
+
+ // For each of the outer rule types...
+ for (var outerRule1 = 0; outerRule1 < outerRuleInfo.length; outerRule1++) {
+ // For each of { 0 = don't create an outer rule,
+ // 1 = create an outer rule with a passing condition,
+ // 2 = create an outer rule with a failing condition }...
+ for (var outerRuleCondition1 = 0; outerRuleCondition1 <= 2; outerRuleCondition1++) {
+
+ // For each of the outer rule types again...
+ for (var outerRule2 = 0; outerRule2 < outerRuleInfo.length; outerRule2++) {
+ // For each of { 0 = don't create an outer rule,
+ // 1 = create an outer rule with a passing condition,
+ // 2 = create an outer rule with a failing condition } again...
+ for (var outerRuleCondition2 = 0; outerRuleCondition2 <= 2; outerRuleCondition2++) {
+
+ // For each of the inner rule types...
+ for (var innerRule = 0; innerRule < innerRuleInfo.length; innerRule++) {
+
+ // Clear rules
+ var object = style.sheet;
+ while (object.cssRules.length) {
+ object.deleteRule(0);
+ }
+
+ // We'll record whether the inner rule should have been applied,
+ // according to whether we put passing or failing conditional
+ // grouping rules around it.
+ var applied = true;
+
+ if (outerRuleCondition1) {
+ // Create an outer conditional rule.
+ object.insertRule([outerRuleInfo[outerRule1][0],
+ outerRuleInfo[outerRule1][outerRuleCondition1],
+ "{}"].join(" "), 0);
+ object = object.cssRules[0];
+
+ if (outerRuleCondition1 == 2) {
+ // If we used a failing condition, we don't expect the inner
+ // rule to be applied.
+ applied = false;
+ }
+ }
+
+ if (outerRuleCondition2) {
+ // Create another outer conditional rule as a child of the first
+ // outer conditional rule (or the style sheet, if we didn't create
+ // a first outer conditional rule).
+ object.insertRule([outerRuleInfo[outerRule2][0],
+ outerRuleInfo[outerRule2][outerRuleCondition2],
+ "{}"].join(" "), 0);
+ object = object.cssRules[0];
+
+ if (outerRuleCondition2 == 2) {
+ // If we used a failing condition, we don't expect the inner
+ // rule to be applied.
+ applied = false;
+ }
+ }
+
+ var outer = object instanceof CSSRule ? object.cssText : "style sheet";
+ var inner = innerRuleInfo[innerRule][0];
+
+ // Insert the inner rule.
+ var exception = null;
+ try {
+ object.insertRule(inner, 0);
+ } catch (e) {
+ exception = e;
+ }
+
+ ok(innerRuleInfo[innerRule][1](applied, object, exception),
+ "<" + [outerRule1, outerRuleCondition1, outerRule2,
+ outerRuleCondition2, innerRule].join(",") + "> " +
+ "inserting " + inner + " into " + outer.replace(/ *\n */g, ' '));
+ }
+ }
+ }
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+runTest();
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_rules_out_of_sheets.html b/layout/style/test/test_rules_out_of_sheets.html
new file mode 100644
index 0000000000..2ca53d31b7
--- /dev/null
+++ b/layout/style/test/test_rules_out_of_sheets.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=634373
+-->
+<head>
+ <title>Test for Bug 634373</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=634373">Mozilla Bug 634373</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 634373 **/
+
+function make_rule_and_remove_sheet(text, getter) {
+ var style = document.createElement("style");
+ style.setAttribute("type", "text/css");
+ style.appendChild(document.createTextNode(text));
+ document.head.appendChild(style);
+ var result = style.sheet.cssRules[0];
+ if (getter) {
+ result = getter(result);
+ }
+ document.head.removeChild(style);
+ style = null;
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+ return result;
+}
+
+var gDisplayCS = getComputedStyle(document.getElementById("display"), "");
+
+function keep_rule_alive_by_matching(rule) {
+ // It's the caller's job to guarantee that the rule matches a p.
+ // This just causes a style flush, which in turn keeps the rule alive
+ // until the next style flush.
+ var color = gDisplayCS.color;
+ return rule;
+}
+
+function get_rule_and_child(rule) {
+ return [rule, rule.cssRules[0]];
+}
+
+function get_only_child(rule) {
+ return rule.cssRules[0];
+}
+
+var rule;
+
+// In this case, the rule goes away immediately, so we're testing
+// the DOM wrapper's handling of a null rule, rather than the rule's
+// handling of a null sheet.
+rule = make_rule_and_remove_sheet("p { color: blue }");
+rule.style.color = "";
+try {
+ rule.style.color = "fuchsia";
+} catch(ex) {}
+
+rule = make_rule_and_remove_sheet("p { color: blue }",
+ keep_rule_alive_by_matching);
+try {
+ rule.style.color = "";
+} catch(ex) {}
+try {
+ rule.style.color = "fuchsia";
+} catch(ex) {}
+
+rule = make_rule_and_remove_sheet("@media screen { p { color: blue } }",
+ get_rule_and_child);
+rule[1].style.color = "";
+try {
+ rule[1].style.color = "fuchsia";
+} catch(ex) {}
+
+// In this case, the rule goes away immediately, so we're testing
+// the DOM wrapper's handling of a null rule, rather than the rule's
+// handling of a null sheet.
+rule = make_rule_and_remove_sheet("@media screen { p { color: blue } }",
+ get_only_child);
+rule.style.color = "";
+try {
+ rule.style.color = "fuchsia";
+} catch(ex) {}
+
+rule = make_rule_and_remove_sheet("@media screen { p { color: blue } }",
+ function(ruleInner) {
+ return keep_rule_alive_by_matching(
+ get_only_child(ruleInner));
+ });
+try {
+ rule.style.color = "";
+} catch(ex) {}
+try {
+ rule.style.color = "fuchsia";
+} catch(ex) {}
+
+rule = make_rule_and_remove_sheet("@keyframes a { from { color: blue } }");
+rule.appendRule("from { color: fuchsia}");
+rule.deleteRule("from");
+rule.name = "b";
+rule.cssRules[0].keyText = "50%";
+
+ok(true, "didn't crash");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_selectors.html b/layout/style/test/test_selectors.html
new file mode 100644
index 0000000000..d688139d3c
--- /dev/null
+++ b/layout/style/test/test_selectors.html
@@ -0,0 +1,1348 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSS Selectors</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<p id="display"><iframe id="iframe" src="about:blank"></iframe><iframe id="cloneiframe" src="about:blank"></iframe></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+var cloneiframe;
+
+function run() {
+ SpecialPowers.pushPrefEnv({ set: [["layout.css.xul-tree-pseudos.content.enabled", true]] }, runTests);
+}
+function runTests() {
+ var iframe = document.getElementById("iframe");
+ var ifwin = iframe.contentWindow;
+ var ifdoc = iframe.contentDocument;
+
+ cloneiframe = document.getElementById("cloneiframe");
+
+ var style_elem = ifdoc.createElement("style");
+ style_elem.setAttribute("type", "text/css");
+ ifdoc.getElementsByTagName("head")[0].appendChild(style_elem);
+ var style_text = ifdoc.createTextNode("");
+ style_elem.appendChild(style_text);
+
+ var gCounter = 0;
+
+ /*
+ * selector: the selector to test
+ * body_contents: what to set the body's innerHTML to
+ * match_fn: a function that, given the document object into which
+ * body_contents has been inserted, produces an array of nodes that
+ * should match selector
+ * notmatch_fn: likewise, but for nodes that should not match
+ * namespaces (optional): @namespace rules to be included in the sheet
+ */
+ function test_selector_in_html(selector, body_contents, match_fn, notmatch_fn, namespaces)
+ {
+ var zi = ++gCounter;
+ if (typeof(body_contents) == "string") {
+ ifdoc.body.innerHTML = body_contents;
+ } else {
+ // It's a function.
+ ifdoc.body.innerHTML = "";
+ body_contents(ifdoc.body);
+ }
+ if (!namespaces) {
+ namespaces = "";
+ }
+ style_text.data = namespaces + selector + "{ z-index: " + zi + " }";
+
+ var idx = style_text.parentNode.sheet.cssRules.length - 1;
+ if (namespaces == "") {
+ is(idx, 0, "unexpected rule index for " + selector);
+ }
+ if (idx < 0 ||
+ style_text.parentNode.sheet.cssRules[idx].type !=
+ CSSRule.STYLE_RULE)
+ {
+ ok(false, "selector " + selector + " could not be parsed");
+ return;
+ }
+
+ var should_match = match_fn(ifdoc);
+ var should_not_match = notmatch_fn(ifdoc);
+ if (should_match.length + should_not_match.length == 0) {
+ ok(false, "nothing to check");
+ }
+
+ for (let i = 0; i < should_match.length; ++i) {
+ let e = should_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched " + selector);
+ }
+ for (let i = 0; i < should_not_match.length; ++i) {
+ let e = should_not_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match " + selector);
+ }
+
+ // Now, since we're here, may as well make sure serialization
+ // works correctly. It need not produce the exact same text,
+ // but it should produce a selector that matches the same
+ // elements.
+ zi = ++gCounter;
+ var ser1 = style_text.parentNode.sheet.cssRules[idx].selectorText;
+ style_text.data = namespaces + ser1 + "{ z-index: " + zi + " }";
+ for (let i = 0; i < should_match.length; ++i) {
+ let e = should_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched " + ser1 +
+ " which is the reserialization of " + selector);
+ }
+ for (let i = 0; i < should_not_match.length; ++i) {
+ let e = should_not_match[i];
+ is(ifwin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match " + ser1 +
+ " which is the reserialization of " + selector);
+ }
+
+ // But when we serialize the serialized result, we should get
+ // the same text.
+ isnot(style_text.parentNode.sheet.cssRules[idx], undefined,
+ "parse of selector \"" + ser1 + "\" failed");
+
+ if (style_text.parentNode.sheet.cssRules[idx] !== undefined) {
+ var ser2 = style_text.parentNode.sheet.cssRules[idx].selectorText;
+ is(ser2, ser1, "parse+serialize of selector \"" + selector +
+ "\" is idempotent");
+ }
+
+ ifdoc.body.innerHTML = "";
+ style_text.data = "";
+
+ // And now test that when we clone the style sheet, we end up
+ // with the same selector (serializes to same string, and
+ // matches the same things).
+ zi = ++gCounter;
+ var style_sheet = "data:text/css," +
+ escape(namespaces + selector + "{ z-index: " + zi + " }");
+ var style_sheet_link =
+ "<link rel='stylesheet' href='" + style_sheet + "'>";
+ var html_doc = "<!DOCTYPE HTML>" +
+ style_sheet_link + style_sheet_link +
+ "<body>";
+ if (typeof(body_contents) == "string") {
+ html_doc += body_contents;
+ }
+ var docurl = "data:text/html," + escape(html_doc);
+ defer_clonedoc_tests(docurl, function() {
+ var wrappedCloneFrame = SpecialPowers.wrap(cloneiframe);
+ var clonedoc = wrappedCloneFrame.contentDocument;
+ var clonewin = wrappedCloneFrame.contentWindow;
+
+ if (typeof(body_contents) != "string") {
+ body_contents(clonedoc.body);
+ }
+
+ var links = clonedoc.getElementsByTagName("link");
+ // cause a clone
+ links[1].sheet.insertRule("#nonexistent { color: purple}", idx + 1);
+ // remove the uncloned sheet
+ links[0].remove();
+
+ var should_match1 = match_fn(clonedoc);
+ var should_not_match1 = notmatch_fn(clonedoc);
+
+ if (should_match1.length + should_not_match1.length == 0) {
+ ok(false, "nothing to check");
+ }
+
+ for (let i = 0; i < should_match1.length; ++i) {
+ let e = should_match1[i];
+ is(clonewin.getComputedStyle(e).zIndex, String(zi),
+ "element in " + body_contents + " matched clone of " +
+ selector);
+ }
+ for (let i = 0; i < should_not_match1.length; ++i) {
+ let e = should_not_match1[i];
+ is(clonewin.getComputedStyle(e).zIndex, "auto",
+ "element in " + body_contents + " did not match clone of " +
+ selector);
+ }
+
+ var ser3 = links[0].sheet.cssRules[idx].selectorText;
+ is(ser3, ser1,
+ "selector " + selector + " serializes correctly after cloning");
+ });
+ }
+
+ function should_serialize_to(selector, serialization)
+ {
+ style_text.data = selector + "{ z-index: 0 }";
+ is(style_text.parentNode.sheet.cssRules[0].selectorText,
+ serialization,
+ "selector '" + selector + "' should serialize to '" +
+ serialization + "'.");
+ }
+
+ function test_parseable(selector)
+ {
+ ifdoc.body.innerHTML = "<p></p>";
+
+ var zi = ++gCounter;
+ style_text.data = "p, " + selector + "{ z-index: " + zi + " }";
+ var should_match = ifdoc.getElementsByTagName("p")[0];
+ var parsed = ifwin.getComputedStyle(should_match).zIndex == zi;
+ ok(parsed, "selector " + selector + " was parsed");
+ if (!parsed) {
+ return;
+ }
+
+ // Test that it serializes to something that is also parseable.
+ var ser1 = style_elem.sheet.cssRules[0].selectorText;
+ zi = ++gCounter;
+ style_text.data = ser1 + "{ z-index: " + zi + " }";
+ is(ifwin.getComputedStyle(should_match).zIndex, String(zi),
+ "serialization " + ser1 + " of selector p, " + selector +
+ " was parsed");
+ var ser2 = style_elem.sheet.cssRules[0].selectorText;
+ is(ser2, ser1,
+ "parse+serialize of selector " + selector + " is idempotent");
+
+ ifdoc.body.innerHTML = "";
+ style_text.data = "";
+
+ // Test that it clones to the same thing it serializes to.
+ zi = ++gCounter;
+ var style_sheet = "data:text/css," +
+ escape("p, " + selector + "{ z-index: " + zi + " }");
+ var style_sheet_link =
+ "<link rel='stylesheet' href='" + style_sheet + "'>";
+ var html_doc = "<!DOCTYPE HTML>" +
+ style_sheet_link + style_sheet_link +
+ "<p></p>";
+ var docurl = "data:text/html," + escape(html_doc);
+
+ defer_clonedoc_tests(docurl, function() {
+ var wrappedCloneFrame = SpecialPowers.wrap(cloneiframe);
+ var clonedoc = wrappedCloneFrame.contentDocument;
+ var clonewin = wrappedCloneFrame.contentWindow;
+ var links = clonedoc.getElementsByTagName("link");
+ // cause a clone
+ links[1].sheet.insertRule("#nonexistent { color: purple}", 0);
+ // remove the uncloned sheet
+ links[0].remove();
+
+ should_match = clonedoc.getElementsByTagName("p")[0];
+ is(clonewin.getComputedStyle(should_match).zIndex, String(zi),
+ "selector " + selector + " was cloned correctly");
+ var ser3 = links[0].sheet.cssRules[1].selectorText;
+ is(ser3, ser1,
+ "selector " + selector + " serializes correctly after cloning");
+ });
+ }
+
+ function test_unparseable_via_api(selector)
+ {
+ try {
+ // Test that it is also unparseable when followed by EOF.
+ ifdoc.body.matches(selector);
+ ok(false, "selector '" + selector + "' plus EOF is parse error");
+ } catch(ex) {
+ is(ex.name, "SyntaxError",
+ "selector '" + selector + "' plus EOF is parse error");
+ is(ex.code, DOMException.SYNTAX_ERR,
+ "selector '" + selector + "' plus EOF is parse error");
+ }
+ }
+
+ function test_parseable_via_api(selector)
+ {
+ var threw = false;
+ try {
+ // Test that a selector is parseable when followed by EOF.
+ ifdoc.body.matches(selector);
+ } catch(ex) {
+ threw = true;
+ }
+ ok(!threw, "selector '" + selector + "' was parsed");
+ }
+
+ function test_balanced_unparseable(selector)
+ {
+ var zi1 = ++gCounter;
+ var zi2 = ++gCounter;
+ ifdoc.body.innerHTML = "<p></p><div></div>";
+ style_text.data = "p, " + selector + "{ z-index: " + zi1 + " }" +
+ "div { z-index: " + zi2 + " }";
+ var should_not_match = ifdoc.getElementsByTagName("p")[0];
+ var should_match = ifdoc.getElementsByTagName("div")[0];
+ is(ifwin.getComputedStyle(should_not_match).zIndex, "auto",
+ "selector " + selector + " was a parser error");
+ is(ifwin.getComputedStyle(should_match).zIndex, String(zi2),
+ "selector " + selector + " error was recovered from");
+ ifdoc.body.innerHTML = "";
+ style_text.data = "";
+ test_unparseable_via_api(selector);
+ }
+
+ function test_unbalanced_unparseable(selector)
+ {
+ var zi1 = ++gCounter;
+ var zi2 = ++gCounter;
+ ifdoc.body.innerHTML = "<p></p>";
+ style_text.data = "p, " + selector + "{ z-index: " + zi1 + " }";
+ var should_not_match = ifdoc.getElementsByTagName("p")[0];
+ is(ifwin.getComputedStyle(should_not_match).zIndex, "auto",
+ "selector " + selector + " was a parser error");
+ is(style_text.parentNode.sheet.cssRules.length, 0,
+ "sheet should have no rules since " + selector + " is parse error");
+ ifdoc.body.innerHTML = "";
+ style_text.data = "";
+ test_unparseable_via_api(selector);
+ }
+
+ // [attr] selector
+ test_parseable("[attr]")
+ test_parseable_via_api("[attr");
+ test_parseable("[ATTR]")
+ should_serialize_to("[attr]", "[attr]");
+ should_serialize_to("[ATTR]", "[ATTR]");
+
+ // Whether we should drop the bar is debatable. This matches Edge
+ // and Safari at the time of writing.
+ should_serialize_to("[|attr]", "[attr]");
+ should_serialize_to("[|ATTR]", "[ATTR]");
+
+ // [attr= ] selector
+ test_parseable("[attr=\"x\"]");
+ test_parseable("[attr='x']");
+ test_parseable("[attr=x]");
+ test_parseable("[attr=\"\"]");
+ test_parseable("[attr='']");
+ test_parseable("[attr=\"foo bar\"]");
+ test_parseable_via_api("[attr=x");
+
+ test_balanced_unparseable("[attr=]");
+ test_balanced_unparseable("[attr=foo bar]");
+
+ test_selector_in_html(
+ '[title=""]',
+ '<p title=""></p>'
+ + '<div lang=" "></div><div lang="\t"></div><div lang="\n"></div>',
+ function(doc) { return doc.getElementsByTagName("p"); },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+
+ // [attr~= ] selector
+ test_parseable("[attr~=\"x\"]");
+ test_parseable("[attr~='x']");
+ test_parseable("[attr~=x]");
+ test_parseable("[attr~=\"\"]");
+ test_parseable("[attr~='']");
+ test_parseable("[attr~=\"foo bar\"]");
+ test_parseable_via_api("[attr~=x");
+
+ test_balanced_unparseable("[attr~=]");
+ test_balanced_unparseable("[attr~=foo bar]");
+
+ test_selector_in_html(
+ '[class~="x x"]',
+ '<div class="x x"></div><div class="x"></div><div class="x\tx"></div>div class="x\nx"></div>',
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+
+ // [attr|="x"]
+ test_parseable('[attr|="x"]');
+ test_parseable("[attr|='x']");
+ test_parseable('[attr|=x]');
+ test_parseable_via_api("[attr|=x");
+
+ test_parseable('[attr|=""]');
+ test_parseable("[attr|='']");
+ test_balanced_unparseable('[attr|=]');
+
+ test_selector_in_html(
+ '[lang|=""]',
+ '<p lang=""></p><p lang="-"></p><p lang="-GB"></p>'
+ + '<div lang="en-GB"></div><div lang="en-"></div>',
+ function(doc) { return doc.getElementsByTagName("p"); },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+
+ // [attr$= ] selector
+ test_parseable("[attr$=\"x\"]");
+ test_parseable("[attr$='x']");
+ test_parseable("[attr$=x]");
+ test_parseable("[attr$=\"\"]");
+ test_parseable("[attr$='']");
+ test_parseable("[attr$=\"foo bar\"]");
+ test_parseable_via_api("[attr$=x");
+
+ test_balanced_unparseable("[attr$=]");
+ test_balanced_unparseable("[attr$=foo bar]");
+
+ // [attr^= ] selector
+ test_parseable("[attr^=\"x\"]");
+ test_parseable("[attr^='x']");
+ test_parseable("[attr^=x]");
+ test_parseable("[attr^=\"\"]");
+ test_parseable("[attr^='']");
+ test_parseable("[attr^=\"foo bar\"]");
+ test_parseable_via_api("[attr^=x");
+
+ test_balanced_unparseable("[attr^=]");
+ test_balanced_unparseable("[attr^=foo bar]");
+
+ // attr[*= ] selector
+ test_parseable("[attr*=\"x\"]");
+ test_parseable("[attr*='x']");
+ test_parseable("[attr*=x]");
+ test_parseable("[attr*=\"\"]");
+ test_parseable("[attr*='']");
+ test_parseable("[attr*=\"foo bar\"]");
+ test_parseable_via_api("[attr^=x");
+
+ test_balanced_unparseable("[attr*=]");
+ test_balanced_unparseable("[attr*=foo bar]");
+
+ // And now tests for correctness of matching of attr selectors.
+ var attrTestBody =
+ // Paragraphs 1-5
+ "<p attr></p> <p attr=''></p> <p attr='foo'></p> <p att></p> <p></p>" +
+ // Paragraphs 6-8
+ "<p attr='foo bar'></p> <p attr='foo-bar'></p> <p attr='foobar'></p>" +
+ // Paragraphs 9-10
+ "<p attr='foo bar baz'></p> <p attr='foo-bar-baz'></p>" +
+ // Paragraphs 11-12
+ "<p attr='foo-bar baz'></p> <p attr=' foo-bar '></p> " +
+ // Paragraph 13-15
+ "<p attr=' foo '></p> <p attr='fo'></p> <p attr='bar baz-foo'></p>";
+ test_selector_in_html(
+ "[attr]", attrTestBody,
+ pset([1,2,3,6,7,8,9,10,11,12,13,14,15]), pset([4,5]));
+ test_selector_in_html(
+ "[attr=foo]", attrTestBody,
+ pset([3]), pset([1,2,4,5,6,7,8,9,10,11,12,13,14,15]));
+ test_selector_in_html(
+ "[attr~=foo]", attrTestBody,
+ pset([3,6,9,13]), pset([1,2,4,5,7,8,10,11,12,14,15]));
+ test_selector_in_html(
+ "[attr~=bar]", attrTestBody,
+ pset([6,9,15]), pset([1,2,3,4,5,7,8,10,11,12,13,14]));
+ test_selector_in_html(
+ "[attr~=baz]", attrTestBody,
+ pset([9,11]), pset([1,2,3,4,5,6,7,8,10,12,13,14,15]));
+ test_selector_in_html(
+ "[attr|=foo]", attrTestBody,
+ pset([3,7,10,11]), pset([1,2,4,5,6,8,9,12,13,14,15]));
+ test_selector_in_html(
+ "[attr|='bar baz']", attrTestBody,
+ pset([15]), pset([1,2,3,4,5,6,7,8,9,10,11,12,13,14]));
+ test_selector_in_html(
+ "[attr$=foo]", attrTestBody,
+ pset([3,15]), pset([1,2,4,5,6,7,8,9,10,11,12,13,14]));
+ test_selector_in_html(
+ "[attr$=bar]", attrTestBody,
+ pset([6,7,8]), pset([1,2,3,4,5,9,10,11,12,13,14,15]));
+ test_selector_in_html(
+ "[attr^=foo]", attrTestBody,
+ pset([3,6,7,8,9,10,11]), pset([1,2,4,5,12,13,14,15]));
+ test_selector_in_html(
+ "[attr*=foo]", attrTestBody,
+ pset([3,6,7,8,9,10,11,12,13,15]), pset([1,2,4,5,14]));
+
+ // Bug 420814
+ test_selector_in_html(
+ "div ~ div p",
+ "<div></div><div><div><p>match</p></div></div>",
+ function(doc) { return doc.getElementsByTagName("p"); },
+ function(doc) { return []; }
+ );
+
+ // Bug 420245
+ test_selector_in_html(
+ "p[attr$=\"\"]",
+ "<p attr=\"foo\">This should not match</p>",
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("p"); }
+ );
+ test_selector_in_html(
+ "div + p[attr~=\"\"]",
+ "<div>Dummy</div><p attr=\"foo\">This should not match</p>",
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("p"); }
+ );
+ test_selector_in_html(
+ "div[attr^=\"\"]",
+ "<div attr=\"dummy1\">Dummy</div><div attr=\"dummy2\">Dummy</div>",
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+ test_selector_in_html(
+ "div[attr*=\"\"]",
+ "<div attr=\"dummy1\">Dummy</div><div attr=\"dummy2\">Dummy</div>",
+ function(doc) { return []; },
+ function(doc) { return doc.getElementsByTagName("div"); }
+ );
+
+ // :nth-child(), etc.
+ // Follow the whitespace rules as proposed in
+ // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
+ test_balanced_unparseable(":nth-child()");
+ test_balanced_unparseable(":nth-of-type( )");
+ test_parseable(":nth-last-child( odd)");
+ test_parseable(":nth-last-of-type(even )");
+ test_parseable(":nth-child(n )");
+ test_parseable(":nth-of-type( 2n)");
+ test_parseable(":nth-last-child( -n)");
+ test_parseable(":nth-last-of-type(-2n )");
+ test_balanced_unparseable(":nth-child(- n)");
+ test_balanced_unparseable(":nth-of-type(-2 n)");
+ test_balanced_unparseable(":nth-last-of-type(2n1)");
+ test_balanced_unparseable(":nth-child(2n++1)");
+ test_balanced_unparseable(":nth-of-type(2n-+1)");
+ test_balanced_unparseable(":nth-last-child(2n+-1)");
+ test_balanced_unparseable(":nth-last-of-type(2n--1)");
+ test_parseable(":nth-child( 3n + 1 )");
+ test_parseable(":nth-child( +3n - 2 )");
+ test_parseable(":nth-child( -n+ 6)");
+ test_parseable(":nth-child( +6 )");
+ test_balanced_unparseable(":nth-child(3 n)");
+ test_balanced_unparseable(":nth-child(+ 2n)");
+ test_balanced_unparseable(":nth-child(+ 2)");
+ test_parseable(":nth-child(3)");
+ test_parseable(":nth-of-type(-3)");
+ test_parseable(":nth-last-child(+3)");
+ test_parseable(":nth-last-of-type(0)");
+ test_parseable(":nth-child(-0)");
+ test_parseable(":nth-of-type(3n)");
+ test_parseable(":nth-last-child(-3n)");
+ test_parseable(":nth-last-of-type(+3n)");
+ test_parseable(":nth-last-of-type(0n)");
+ test_parseable(":nth-child(-0n)");
+ test_parseable(":nth-of-type(n)");
+ test_parseable(":nth-last-child(-n)");
+ test_parseable(":nth-last-of-type(2n+1)");
+ test_parseable(":nth-child(2n-1)");
+ test_parseable(":nth-of-type(2n+0)");
+ test_parseable(":nth-last-child(2n-0)");
+ test_parseable(":nth-child(-0n+0)");
+ test_parseable(":nth-of-type(n+1)");
+ test_parseable(":nth-last-child(n-1)");
+ test_parseable(":nth-last-of-type(-n+1)");
+ test_parseable(":nth-child(-n-1)");
+ test_balanced_unparseable(":nth-child(2-n)");
+ test_balanced_unparseable(":nth-child(2-n-1)");
+ test_balanced_unparseable(":nth-child(n-2-1)");
+ // Bug 750388
+ test_parseable(":nth-child(+n)");
+ test_balanced_unparseable(":nth-child(+ n)");
+ test_parseable(":nth-child(+n+2)");
+ test_parseable(":nth-child(+n-2)");
+ test_parseable(":nth-child(+n + 2)");
+ test_parseable(":nth-child(+n - 2)");
+ test_balanced_unparseable(":nth-child(+ n+2)");
+ test_balanced_unparseable(":nth-child(+ n-2)");
+ test_balanced_unparseable(":nth-child(+ n + 2)");
+ test_balanced_unparseable(":nth-child(+ n - 2)");
+ test_parseable(":nth-child(+n-100)");
+ test_parseable(":nth-child(+n - 100)");
+ test_balanced_unparseable(":nth-child(+ n-100)");
+ test_balanced_unparseable(":nth-child(+-n+2)");
+ test_balanced_unparseable(":nth-child(+ -n+2)");
+ test_balanced_unparseable(":nth-child(+-n-100)");
+ test_balanced_unparseable(":nth-child(+ -n-100)");
+ test_balanced_unparseable(":nth-child(++n-100)");
+ test_balanced_unparseable(":nth-child(-+n-100)");
+ test_balanced_unparseable(":nth-child(++2n - 100)");
+ test_balanced_unparseable(":nth-child(+-2n - 100)");
+ test_balanced_unparseable(":nth-child(-+2n - 100)");
+ test_balanced_unparseable(":nth-child(--2n - 100)");
+ test_balanced_unparseable(":nth-child(+/**/+2n - 100)");
+ test_balanced_unparseable(":nth-child(+/**/-2n - 100)");
+ test_balanced_unparseable(":nth-child(-/**/+2n - 100)");
+ test_balanced_unparseable(":nth-child(-/**/-2n - 100)");
+ test_balanced_unparseable(":nth-child(+/**/+/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(+/**/-/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(-/**/+/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(-/**/-/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(++/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(+-/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(-+/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(--/**/2n - 100)");
+ test_balanced_unparseable(":nth-child(-even)");
+ test_balanced_unparseable(":nth-child(-odd)");
+ test_balanced_unparseable(":nth-child(+even)");
+ test_balanced_unparseable(":nth-child(+odd)");
+ test_balanced_unparseable(":nth-child(+ even)");
+ test_balanced_unparseable(":nth-child(+ odd)");
+ test_balanced_unparseable(":nth-child(+-n)");
+ test_balanced_unparseable(":nth-child(+-n-)");
+ test_balanced_unparseable(":nth-child(-+n)");
+ test_balanced_unparseable(":nth-child(+n--)");
+ test_parseable(":nth-child(n+2)");
+ test_parseable(":nth-child(n/**/+/**/2)");
+ test_parseable(":nth-child(n-2)");
+ test_parseable(":nth-child(n/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(n++2)");
+ test_balanced_unparseable(":nth-child(n+-2)");
+ test_balanced_unparseable(":nth-child(n-+2)");
+ test_balanced_unparseable(":nth-child(n--2)");
+ test_balanced_unparseable(":nth-child(n/**/++2)");
+ test_balanced_unparseable(":nth-child(n/**/+-2)");
+ test_balanced_unparseable(":nth-child(n/**/-+2)");
+ test_balanced_unparseable(":nth-child(n/**/--2)");
+ test_balanced_unparseable(":nth-child(n/**/+/**/+2)");
+ test_balanced_unparseable(":nth-child(n/**/+/**/-2)");
+ test_balanced_unparseable(":nth-child(n/**/-/**/+2)");
+ test_balanced_unparseable(":nth-child(n/**/-/**/-2)");
+ test_balanced_unparseable(":nth-child(n+/**/+2)");
+ test_balanced_unparseable(":nth-child(n+/**/-2)");
+ test_balanced_unparseable(":nth-child(n-/**/+2)");
+ test_balanced_unparseable(":nth-child(n-/**/-2)");
+ test_balanced_unparseable(":nth-child(n++/**/2)");
+ test_balanced_unparseable(":nth-child(n+-/**/2)");
+ test_balanced_unparseable(":nth-child(n-+/**/2)");
+ test_balanced_unparseable(":nth-child(n--/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/++/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/+-/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/-+/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/--/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/+/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/+/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/-/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(n/**/-/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(n+/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(n+/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(n-/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(n-/**/-/**/2)");
+ test_parseable(":nth-child(2n+2)");
+ test_parseable(":nth-child(2n/**/+/**/2)");
+ test_parseable(":nth-child(2n-2)");
+ test_parseable(":nth-child(2n/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(2n++2)");
+ test_balanced_unparseable(":nth-child(2n+-2)");
+ test_balanced_unparseable(":nth-child(2n-+2)");
+ test_balanced_unparseable(":nth-child(2n--2)");
+ test_balanced_unparseable(":nth-child(2n/**/++2)");
+ test_balanced_unparseable(":nth-child(2n/**/+-2)");
+ test_balanced_unparseable(":nth-child(2n/**/-+2)");
+ test_balanced_unparseable(":nth-child(2n/**/--2)");
+ test_balanced_unparseable(":nth-child(2n/**/+/**/+2)");
+ test_balanced_unparseable(":nth-child(2n/**/+/**/-2)");
+ test_balanced_unparseable(":nth-child(2n/**/-/**/+2)");
+ test_balanced_unparseable(":nth-child(2n/**/-/**/-2)");
+ test_balanced_unparseable(":nth-child(2n+/**/+2)");
+ test_balanced_unparseable(":nth-child(2n+/**/-2)");
+ test_balanced_unparseable(":nth-child(2n-/**/+2)");
+ test_balanced_unparseable(":nth-child(2n-/**/-2)");
+ test_balanced_unparseable(":nth-child(2n++/**/2)");
+ test_balanced_unparseable(":nth-child(2n+-/**/2)");
+ test_balanced_unparseable(":nth-child(2n-+/**/2)");
+ test_balanced_unparseable(":nth-child(2n--/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/++/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/+-/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/-+/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/--/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/+/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/+/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/-/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(2n/**/-/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(2n+/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(2n+/**/-/**/2)");
+ test_balanced_unparseable(":nth-child(2n-/**/+/**/2)");
+ test_balanced_unparseable(":nth-child(2n-/**/-/**/2)");
+ test_parseable(":nth-child(+/**/n+2)");
+ test_parseable(":nth-child(+n/**/+2)");
+ test_parseable(":nth-child(+n/**/+2)");
+ test_parseable(":nth-child(+n+/**/2)");
+ test_parseable(":nth-child(+n+2/**/)");
+ test_balanced_unparseable(":nth-child(+1/**/n+2)");
+ test_parseable(":nth-child(+1n/**/+2)");
+ test_parseable(":nth-child(+1n/**/+2)");
+ test_parseable(":nth-child(+1n+/**/2)");
+ test_parseable(":nth-child(+1n+2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/n+2)");
+ test_parseable(":nth-child(-n/**/+2)");
+ test_parseable(":nth-child(-n/**/+2)");
+ test_parseable(":nth-child(-n+/**/2)");
+ test_parseable(":nth-child(-n+2/**/)");
+ test_balanced_unparseable(":nth-child(-1/**/n+2)");
+ test_parseable(":nth-child(-1n/**/+2)");
+ test_parseable(":nth-child(-1n/**/+2)");
+ test_parseable(":nth-child(-1n+/**/2)");
+ test_parseable(":nth-child(-1n+2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/ n+2)");
+ test_balanced_unparseable(":nth-child(- /**/n+2)");
+ test_balanced_unparseable(":nth-child(+/**/ n+2)");
+ test_balanced_unparseable(":nth-child(+ /**/n+2)");
+ test_parseable(":nth-child(+/**/n-2)");
+ test_parseable(":nth-child(+n/**/-2)");
+ test_parseable(":nth-child(+n/**/-2)");
+ test_parseable(":nth-child(+n-/**/2)");
+ test_parseable(":nth-child(+n-2/**/)");
+ test_balanced_unparseable(":nth-child(+1/**/n-2)");
+ test_parseable(":nth-child(+1n/**/-2)");
+ test_parseable(":nth-child(+1n/**/-2)");
+ test_parseable(":nth-child(+1n-/**/2)");
+ test_parseable(":nth-child(+1n-2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/n-2)");
+ test_parseable(":nth-child(-n/**/-2)");
+ test_parseable(":nth-child(-n/**/-2)");
+ test_parseable(":nth-child(-n-/**/2)");
+ test_parseable(":nth-child(-n-2/**/)");
+ test_balanced_unparseable(":nth-child(-1/**/n-2)");
+ test_parseable(":nth-child(-1n/**/-2)");
+ test_parseable(":nth-child(-1n/**/-2)");
+ test_parseable(":nth-child(-1n-/**/2)");
+ test_parseable(":nth-child(-1n-2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/ n-2)");
+ test_balanced_unparseable(":nth-child(- /**/n-2)");
+ test_balanced_unparseable(":nth-child(+/**/ n-2)");
+ test_balanced_unparseable(":nth-child(+ /**/n-2)");
+ test_parseable(":nth-child(+/**/N-2)");
+ test_parseable(":nth-child(+N/**/-2)");
+ test_parseable(":nth-child(+N/**/-2)");
+ test_parseable(":nth-child(+N-/**/2)");
+ test_parseable(":nth-child(+N-2/**/)");
+ test_balanced_unparseable(":nth-child(+1/**/N-2)");
+ test_parseable(":nth-child(+1N/**/-2)");
+ test_parseable(":nth-child(+1N/**/-2)");
+ test_parseable(":nth-child(+1N-/**/2)");
+ test_parseable(":nth-child(+1N-2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/N-2)");
+ test_parseable(":nth-child(-N/**/-2)");
+ test_parseable(":nth-child(-N/**/-2)");
+ test_parseable(":nth-child(-N-/**/2)");
+ test_parseable(":nth-child(-N-2/**/)");
+ test_balanced_unparseable(":nth-child(-1/**/N-2)");
+ test_parseable(":nth-child(-1N/**/-2)");
+ test_parseable(":nth-child(-1N/**/-2)");
+ test_parseable(":nth-child(-1N-/**/2)");
+ test_parseable(":nth-child(-1N-2/**/)");
+ test_balanced_unparseable(":nth-child(-/**/ N-2)");
+ test_balanced_unparseable(":nth-child(- /**/N-2)");
+ test_balanced_unparseable(":nth-child(+/**/ N-2)");
+ test_balanced_unparseable(":nth-child(+ /**/N-2)");
+ test_parseable(":nth-child( +n + 1 )");
+ test_parseable(":nth-child( +/**/n + 1 )");
+ test_balanced_unparseable(":nth-child( -/**/2/**/n/**/+/**/4 )");
+ test_parseable(":nth-child( -2n/**/ + /**/4 )");
+ test_parseable(":nth-child( -2n/**/+/**/4 )");
+ test_parseable(":nth-child( -2n /**/+/**/4 )");
+ test_balanced_unparseable(":nth-child( -/**/n /**/+ /**/ 4 )");
+ test_parseable(":nth-child( +/**/n /**/+ /**/ 4 )");
+ test_balanced_unparseable(":nth-child(+1/**/n-1)");
+ test_balanced_unparseable(":nth-child(1/**/n-1)");
+ // bug 876570
+ test_balanced_unparseable(":nth-child(+2n-)");
+ test_balanced_unparseable(":nth-child(+n-)");
+ test_balanced_unparseable(":nth-child(-2n-)");
+ test_balanced_unparseable(":nth-child(-n-)");
+ test_balanced_unparseable(":nth-child(2n-)");
+ test_balanced_unparseable(":nth-child(n-)");
+ test_balanced_unparseable(":nth-child(+2n+)");
+ test_balanced_unparseable(":nth-child(+n+)");
+ test_balanced_unparseable(":nth-child(-2n+)");
+ test_balanced_unparseable(":nth-child(-n+)");
+ test_balanced_unparseable(":nth-child(2n+)");
+ test_balanced_unparseable(":nth-child(n+)");
+
+ // exercise the an+b matching logic particularly hard for
+ // :nth-child() (since we know we use the same code for all 4)
+ var seven_ps = "<p></p><p></p><p></p><p></p><p></p><p></p><p></p>";
+ function pset(indices) { // takes an array of 1-based indices
+ return function pset_filter(doc) {
+ var a = doc.getElementsByTagName("p");
+ var result = [];
+ for (var i in indices)
+ result.push(a[indices[i] - 1]);
+ return result;
+ }
+ }
+ test_selector_in_html(":nth-child(0)", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(-3)", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(3)", seven_ps,
+ pset([3]), pset([1, 2, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(0n+3)", seven_ps,
+ pset([3]), pset([1, 2, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(-0n+3)", seven_ps,
+ pset([3]), pset([1, 2, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(8)", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(odd)", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child(even)", seven_ps,
+ pset([2, 4, 6]), pset([1, 3, 5, 7]));
+ test_selector_in_html(":nth-child(2n-1)", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child( 2n - 1 )", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child(2n+1)", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child( 2n + 1 )", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child(2n+0)", seven_ps,
+ pset([2, 4, 6]), pset([1, 3, 5, 7]));
+ test_selector_in_html(":nth-child(2n-0)", seven_ps,
+ pset([2, 4, 6]), pset([1, 3, 5, 7]));
+ test_selector_in_html(":nth-child(-n+3)", seven_ps,
+ pset([1, 2, 3]), pset([4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(-n-3)", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(n)", seven_ps,
+ pset([1, 2, 3, 4, 5, 6, 7]), pset([]));
+ test_selector_in_html(":nth-child(n-3)", seven_ps,
+ pset([1, 2, 3, 4, 5, 6, 7]), pset([]));
+ test_selector_in_html(":nth-child(n+3)", seven_ps,
+ pset([3, 4, 5, 6, 7]), pset([1, 2]));
+ test_selector_in_html(":nth-child(2n+3)", seven_ps,
+ pset([3, 5, 7]), pset([1, 2, 4, 6]));
+ test_selector_in_html(":nth-child(2n)", seven_ps,
+ pset([2, 4, 6]), pset([1, 3, 5, 7]));
+ test_selector_in_html(":nth-child(2n-3)", seven_ps,
+ pset([1, 3, 5, 7]), pset([2, 4, 6]));
+ test_selector_in_html(":nth-child(-1n+3)", seven_ps,
+ pset([1, 2, 3]), pset([4, 5, 6, 7]));
+ test_selector_in_html(":nth-child(-2n+3)", seven_ps,
+ pset([1, 3]), pset([2, 4, 5, 6, 7]));
+ // And a few spot-checks for the other :nth-* selectors
+ test_selector_in_html(":nth-child(4n+1)", seven_ps,
+ pset([1, 5]), pset([2, 3, 4, 6, 7]));
+ test_selector_in_html(":nth-last-child(4n+1)", seven_ps,
+ pset([3, 7]), pset([1, 2, 4, 5, 6]));
+ test_selector_in_html(":nth-of-type(4n+1)", seven_ps,
+ pset([1, 5]), pset([2, 3, 4, 6, 7]));
+ test_selector_in_html(":nth-last-of-type(4n+1)", seven_ps,
+ pset([3, 7]), pset([1, 2, 4, 5, 6]));
+ test_selector_in_html(":nth-child(6)", seven_ps,
+ pset([6]), pset([1, 2, 3, 4, 5, 7]));
+ test_selector_in_html(":nth-last-child(6)", seven_ps,
+ pset([2]), pset([1, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":nth-of-type(6)", seven_ps,
+ pset([6]), pset([1, 2, 3, 4, 5, 7]));
+ test_selector_in_html(":nth-last-of-type(6)", seven_ps,
+ pset([2]), pset([1, 3, 4, 5, 6, 7]));
+
+ // Test [first|last|only]-[child|node|of-type]
+ var interesting_doc = "<!----> <div id='p1'> <!---->x<p id='s1'></p> <!----><p id='s2'></p> <!----></div> <!----><p id='p2'> <!----><span id='s3'></span> <!----><span id='s4'></span> <!---->x</p> <!----><div id='p3'> <!----><p id='s5'></p> <!----></div> <!---->";
+ function idset(ids) { // takes an array of ids
+ return function idset_filter(doc) {
+ var result = [];
+ for (var id of ids)
+ result.push(doc.getElementById(id));
+ return result;
+ }
+ }
+ function classset(classes) { // takes an array of classes
+ return function classset_filter(doc) {
+ var i, j, els;
+ var result = [];
+ for (i = 0; i < classes.length; i++) {
+ els = doc.getElementsByClassName(classes[i]);
+ for (j = 0; j < els.length; j++) {
+ result.push(els[j]);
+ }
+ }
+ return result;
+ }
+ }
+ function emptyset(doc) { return []; }
+ test_parseable(":first-child");
+ test_parseable(":last-child");
+ test_parseable(":only-child");
+ test_parseable(":-moz-first-node");
+ test_parseable(":-moz-last-node");
+ test_parseable(":first-of-type");
+ test_parseable(":last-of-type");
+ test_parseable(":only-of-type");
+ test_selector_in_html(":first-child", seven_ps,
+ pset([1]), pset([2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":first-child", interesting_doc,
+ idset(["p1", "s1", "s3", "s5"]),
+ idset(["s2", "p2", "s4", "p3"]));
+ test_selector_in_html(":-moz-first-node", interesting_doc,
+ idset(["p1", "s3", "s5"]),
+ idset(["s1", "s2", "p2", "s4", "p3"]));
+ test_selector_in_html(":last-child", seven_ps,
+ pset([7]), pset([1, 2, 3, 4, 5, 6]));
+ test_selector_in_html(":last-child", interesting_doc,
+ idset(["s2", "s4", "p3", "s5"]),
+ idset(["p1", "s1", "p2", "s3"]));
+ test_selector_in_html(":-moz-last-node", interesting_doc,
+ idset(["s2", "p3", "s5"]),
+ idset(["p1", "s1", "p2", "s3", "s4"]));
+ test_selector_in_html(":only-child", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":only-child", interesting_doc,
+ idset(["s5"]),
+ idset(["p1", "s1", "s2", "p2", "s3", "s4", "p3"]));
+ test_selector_in_html(":first-of-type", seven_ps,
+ pset([1]), pset([2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":first-of-type", interesting_doc,
+ idset(["p1", "s1", "p2", "s3", "s5"]),
+ idset(["s2", "s4", "p3"]));
+ test_selector_in_html(":last-of-type", seven_ps,
+ pset([7]), pset([1, 2, 3, 4, 5, 6]));
+ test_selector_in_html(":last-of-type", interesting_doc,
+ idset(["s2", "p2", "s4", "p3", "s5"]),
+ idset(["p1", "s1", "s3"]));
+ test_selector_in_html(":only-of-type", seven_ps,
+ pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
+ test_selector_in_html(":only-of-type", interesting_doc,
+ idset(["p2", "s5"]),
+ idset(["p1", "s1", "s2", "s3", "s4", "p3"]));
+
+ // And a bunch of tests for the of-type aspect of :nth-of-type() and
+ // :nth-last-of-type(). Note that the last div here contains two
+ // children.
+ var mixed_elements="<p></p><p></p><div></div><p></p><div><p></p><address></address></div><address></address>";
+ function pdaset(ps, divs, addresses) { // takes an array of 1-based indices
+ var l = { p: ps, div: divs, address: addresses };
+ return function pdaset_filter(doc) {
+ var result = [];
+ for (var tag in l) {
+ var a = doc.getElementsByTagName(tag);
+ var indices = l[tag];
+ for (var i in indices)
+ result.push(a[indices[i] - 1]);
+ }
+ return result;
+ }
+ }
+ test_selector_in_html(":nth-of-type(odd)", mixed_elements,
+ pdaset([1, 3, 4], [1], [1, 2]),
+ pdaset([2], [2], []));
+ test_selector_in_html(":nth-of-type(2n-0)", mixed_elements,
+ pdaset([2], [2], []),
+ pdaset([1, 3, 4], [1], [1, 2]));
+ test_selector_in_html(":nth-last-of-type(even)", mixed_elements,
+ pdaset([2], [1], []),
+ pdaset([1, 3, 4], [2], [1, 2]));
+
+ // Test greediness of descendant combinators.
+ var four_children="<div id='a'><div id='b'><div id='c'><div id='d'><\/div><\/div><\/div><\/div>";
+ test_selector_in_html("#a > div div", four_children,
+ idset(["c", "d"]), idset(["a", "b"]));
+ test_selector_in_html("#a > #b div", four_children,
+ idset(["c", "d"]), idset(["a", "b"]));
+ test_selector_in_html("#a div > div", four_children,
+ idset(["c", "d"]), idset(["a", "b"]));
+ test_selector_in_html("#a #b > div", four_children,
+ idset(["c"]), idset(["a", "b", "d"]));
+ test_selector_in_html("#a > #b div", four_children,
+ idset(["c", "d"]), idset(["a", "b"]));
+ test_selector_in_html("#a #c > div", four_children,
+ idset(["d"]), idset(["a", "b", "c"]));
+ test_selector_in_html("#a > #c div", four_children,
+ idset([]), idset(["a", "b", "c", "d"]));
+
+ // More descendant combinator greediness (bug 511147)
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b"]));
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="x"></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b", "x"]));
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"><p>filler filler <i>filler</i> filler</p></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b", "x"]));
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="x"><p>filler filler <i>filler</i> filler</p></div><div></div><div class="b"></div><div></div><div class="x"><p>filler filler <i>filler</i> filler</p></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b", "x"]));
+ test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="match"></div><div class="match"></div></div>',
+ classset(["match"]), classset(["a", "b"]));
+
+ test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div><div class="b"></div><div class="nomatch"></div></div></div>',
+ emptyset, classset(["a", "b", "nomatch"]));
+ test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div><div class="b"></div><div class="nomatch"></div></div><div class="nomatch"></div></div>',
+ emptyset, classset(["a", "b", "nomatch"]));
+ test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div class="b"></div><div><div class="nomatch"></div></div><div></div></div>',
+ emptyset, classset(["a", "b", "nomatch"]));
+ test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div class="b"></div></div><div class="nomatch"></div>',
+ emptyset, classset(["a", "b", "nomatch"]));
+
+ // Test serialization of pseudo-elements.
+ should_serialize_to("p::first-letter", "p::first-letter");
+ should_serialize_to("p:first-letter", "p::first-letter");
+ should_serialize_to("div>p:first-letter", "div > p::first-letter");
+ should_serialize_to("span +div:first-line", "span + div::first-line");
+ should_serialize_to("input::placeholder", "input::placeholder");
+ should_serialize_to("input:placeholder-shown", "input:placeholder-shown");
+
+ // Test serialization of ::-moz-placeholder.
+ should_serialize_to("input::-moz-placeholder", "input::placeholder");
+
+ should_serialize_to(':lang("foo\\"bar")', ':lang(foo\\"bar)');
+
+ // Test default namespaces, including inside :not().
+ var html_default_ns = "@namespace url(http://www.w3.org/1999/xhtml);";
+ var html_ns = "@namespace html url(http://www.w3.org/1999/xhtml);";
+ var xul_default_ns = "@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);";
+ var single_a = "<a id='a' href='data:text/plain,this_better_be_unvisited'></a>";
+ var set_single = idset(['a']);
+ var empty_set = idset([]);
+ test_selector_in_html("a", single_a, set_single, empty_set,
+ html_default_ns);
+ test_selector_in_html("a", single_a, empty_set, set_single,
+ xul_default_ns);
+ test_selector_in_html("*|a", single_a, set_single, empty_set,
+ xul_default_ns);
+ test_selector_in_html("html|a", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ // Type selectors inside :not() bring in default namespaces, but
+ // non-type selectors don't.
+ test_selector_in_html("*|a:not(*)", single_a, set_single, empty_set,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(a)", single_a, set_single, empty_set,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(*|*)", single_a, empty_set, set_single,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(*|a)", single_a, empty_set, set_single,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(:link)", single_a + "<a id='b'></a>",
+ idset(["b"]), set_single,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(:visited)", single_a + "<a id='b'></a>",
+ idset(["a", "b"]), empty_set,
+ xul_default_ns);
+ test_selector_in_html("*|a:not(html|*)", single_a, empty_set, set_single,
+ xul_default_ns + html_ns);
+ test_selector_in_html("*|a:not(html|a)", single_a, empty_set, set_single,
+ xul_default_ns + html_ns);
+ test_selector_in_html("*|a:not(|*)", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ test_selector_in_html("*|a:not(|a)", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ test_selector_in_html("html|a:not(|*)", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ test_selector_in_html("html|a:not(|a)", single_a, set_single, empty_set,
+ xul_default_ns + html_ns);
+ test_selector_in_html("html|a:not(*|*)", single_a, empty_set, set_single,
+ xul_default_ns + html_ns);
+ test_selector_in_html("html|a:not(*|a)", single_a, empty_set, set_single,
+ xul_default_ns + html_ns);
+
+ // Test -moz-locale-dir
+ test_balanced_unparseable(":-moz-locale-dir(ltr)");
+ test_balanced_unparseable(":-moz-locale-dir(rtl)");
+ test_balanced_unparseable(":-moz-locale-dir(rTl)");
+ test_balanced_unparseable(":-moz-locale-dir(LTR)");
+ test_balanced_unparseable(":-moz-locale-dir(other)");
+
+ test_balanced_unparseable(":-moz-locale-dir()");
+ test_balanced_unparseable(":-moz-locale-dir(())");
+ test_balanced_unparseable(":-moz-locale-dir(3())");
+ test_balanced_unparseable(":-moz-locale-dir(f{})");
+ test_balanced_unparseable(":-moz-locale-dir('ltr')");
+ test_balanced_unparseable(":-moz-locale-dir(ltr, other)");
+ test_balanced_unparseable(":-moz-locale-dir(ltr other)");
+ test_balanced_unparseable(":-moz-locale-dir");
+
+ // Test :dir()
+ test_parseable(":dir(ltr)");
+ test_parseable(":dir(rtl)");
+ test_parseable(":dir(rTl)");
+ test_parseable(":dir(LTR)");
+ test_parseable(":dir(other)");
+ if (document.body.matches(":dir(ltr)")) {
+ test_selector_in_html("a:dir(LTr)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(ltR)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(LTR)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(RTl)", single_a,
+ empty_set, set_single);
+ } else {
+ test_selector_in_html("a:dir(RTl)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(rtL)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(RTL)", single_a,
+ set_single, empty_set);
+ test_selector_in_html("a:dir(LTr)", single_a,
+ empty_set, set_single);
+ }
+ test_selector_in_html("a:dir(other)", single_a,
+ empty_set, set_single);
+
+ test_balanced_unparseable(":dir()");
+ test_balanced_unparseable(":dir(())");
+ test_balanced_unparseable(":dir(3())");
+ test_balanced_unparseable(":dir(f{})");
+ test_balanced_unparseable(":dir('ltr')");
+ test_balanced_unparseable(":dir(ltr, other)");
+ test_balanced_unparseable(":dir(ltr other)");
+ test_balanced_unparseable(":dir");
+
+ // Test chrome-only -moz-lwtheme
+ test_balanced_unparseable(":-moz-lwtheme");
+ test_balanced_unparseable(":-moz-broken");
+
+ test_balanced_unparseable(":-moz-tree-row(selected)");
+ test_balanced_unparseable("::-moz-tree-row(selected)");
+ test_balanced_unparseable("::-MoZ-trEE-RoW(sElEcTeD)");
+ test_balanced_unparseable(":-moz-tree-row(selected focus)");
+ test_balanced_unparseable(":-moz-tree-row(selected , focus)");
+ test_balanced_unparseable("::-moz-tree-row(selected ,focus)");
+ test_balanced_unparseable(":-moz-tree-row(selected, focus)");
+ test_balanced_unparseable("::-moz-tree-row(selected,focus)");
+ test_balanced_unparseable(":-moz-tree-row(selected focus)");
+ test_balanced_unparseable("::-moz-tree-row(selected , focus)");
+ test_balanced_unparseable("::-moz-tree-twisty( hover open )");
+ test_balanced_unparseable("::-moz-tree-row(selected {[]} )");
+ test_balanced_unparseable(":-moz-tree-twisty(open())");
+ test_balanced_unparseable("::-moz-tree-twisty(hover ())");
+
+ test_parseable(":-moz-window-inactive");
+ test_parseable("div p:-moz-window-inactive:hover span");
+
+ // Chrome-only
+ test_unbalanced_unparseable(":-moz-browser-frame");
+
+ // Plugin pseudoclasses are chrome-only:
+ test_unbalanced_unparseable(":-moz-type-unsupported");
+ test_unbalanced_unparseable(":-moz-type-unsupported-platform");
+ test_unbalanced_unparseable(":-moz-handler-clicktoplay");
+ test_unbalanced_unparseable(":-moz-handler-vulnerable-updatable");
+ test_unbalanced_unparseable(":-moz-handler-vulnerable-no-update");
+ test_unbalanced_unparseable(":-moz-handler-disabled");
+ test_unbalanced_unparseable(":-moz-handler-blocked");
+ test_unbalanced_unparseable(":-moz-handler-crashed");
+
+ // We're not in a UA sheet, so this should be invalid.
+ test_balanced_unparseable(":-moz-inert");
+ test_balanced_unparseable(":-moz-native-anonymous");
+ test_balanced_unparseable(":-moz-table-border-nonzero");
+
+ // Case sensitivity of tag selectors
+ function setup_cased_spans(body) {
+ var data = [
+ { tag: "span" },
+ { tag: "sPaN" },
+ { tag: "Span" },
+ { tag: "SPAN" },
+ { ns: "http://www.w3.org/1999/xhtml", tag: "span" },
+ { ns: "http://www.w3.org/1999/xhtml", tag: "sPaN" },
+ { ns: "http://www.w3.org/1999/xhtml", tag: "Span" },
+ { ns: "http://www.w3.org/1999/xhtml", tag: "SPAN" },
+ { ns: "http://example.com/useless", tag: "span" },
+ { ns: "http://example.com/useless", tag: "sPaN" },
+ { ns: "http://example.com/useless", tag: "Span" },
+ { ns: "http://example.com/useless", tag: "SPAN" },
+ ]
+ for (var i in data) {
+ var ent = data[i];
+ var elem;
+ if ("ns" in ent) {
+ elem = body.ownerDocument.createElementNS(ent.ns, ent.tag);
+ } else {
+ elem = body.ownerDocument.createElement(ent.tag);
+ }
+ body.appendChild(elem);
+ }
+ }
+ function bodychildset(indices) {
+ return function bodychildset_filter(doc) {
+ var body = doc.body;
+ var result = [];
+ for (var i in indices) {
+ result.push(body.childNodes[indices[i]]);
+ }
+ return result;
+ }
+ }
+ test_selector_in_html("span", setup_cased_spans,
+ bodychildset([0, 1, 2, 3, 4, 8]),
+ bodychildset([5, 6, 7, 9, 10, 11]));
+ test_selector_in_html("sPaN", setup_cased_spans,
+ bodychildset([0, 1, 2, 3, 4, 9]),
+ bodychildset([5, 6, 7, 8, 10, 11]));
+ test_selector_in_html("Span", setup_cased_spans,
+ bodychildset([0, 1, 2, 3, 4, 10]),
+ bodychildset([5, 6, 7, 8, 9, 11]));
+ test_selector_in_html("SPAN", setup_cased_spans,
+ bodychildset([0, 1, 2, 3, 4, 11]),
+ bodychildset([5, 6, 7, 8, 9, 10]));
+
+ // bug 528096 (tree pseudos)
+ test_unbalanced_unparseable(":-moz-tree-column((){} a");
+ test_unbalanced_unparseable(":-moz-tree-column(x(){} a");
+ test_unbalanced_unparseable(":-moz-tree-column(a b (){} a");
+ test_unbalanced_unparseable(":-moz-tree-column(a, b (){} a");
+
+ // Bug 543428 (escaping)
+ test_selector_in_html("\\32|a", single_a, set_single, empty_set,
+ "@namespace \\32 url(http://www.w3.org/1999/xhtml);");
+ test_selector_in_html("-\\32|a", single_a, set_single, empty_set,
+ "@namespace -\\32 url(http://www.w3.org/1999/xhtml);");
+ test_selector_in_html("\\2|a", single_a, set_single, empty_set,
+ "@namespace \\0002 url(http://www.w3.org/1999/xhtml);");
+ test_selector_in_html("-\\2|a", single_a, set_single, empty_set,
+ "@namespace -\\000002 url(http://www.w3.org/1999/xhtml);");
+ var spans = "<span class='2'></span><span class='&#x2;'></span>" +
+ "<span id='2'></span><span id='&#x2;'></span>"
+ test_selector_in_html(".\\32", spans,
+ bodychildset([0]), bodychildset([1, 2, 3]));
+ test_selector_in_html("[class=\\32]", spans,
+ bodychildset([0]), bodychildset([1, 2, 3]));
+ test_selector_in_html(".\\2", spans,
+ bodychildset([1]), bodychildset([0, 2, 3]));
+ test_selector_in_html("[class=\\2]", spans,
+ bodychildset([1]), bodychildset([0, 2, 3]));
+ test_selector_in_html("#\\32", spans,
+ bodychildset([2]), bodychildset([0, 1, 3]));
+ test_selector_in_html("[id=\\32]", spans,
+ bodychildset([2]), bodychildset([0, 1, 3]));
+ test_selector_in_html("#\\2", spans,
+ bodychildset([3]), bodychildset([0, 1, 2]));
+ test_selector_in_html("[id=\\2]", spans,
+ bodychildset([3]), bodychildset([0, 1, 2]));
+ test_balanced_unparseable("#2");
+
+ // Bug 553805: :not() containing nothing is forbidden
+ test_balanced_unparseable(":not()");
+ test_balanced_unparseable(":not( )");
+ test_balanced_unparseable(":not( \t\n )");
+ test_balanced_unparseable(":not(/*comment*/)");
+ test_balanced_unparseable(":not( /*comment*/ /* comment */ )");
+ test_balanced_unparseable("p :not()");
+ test_balanced_unparseable("p :not( )");
+ test_balanced_unparseable("p :not( \t\n )");
+ test_balanced_unparseable("p :not(/*comment*/)");
+ test_balanced_unparseable("p :not( /*comment*/ /* comment */ )");
+ test_balanced_unparseable("p:not()");
+ test_balanced_unparseable("p:not( )");
+ test_balanced_unparseable("p:not( \t\n )");
+ test_balanced_unparseable("p:not(/*comment*/)");
+ test_balanced_unparseable("p:not( /*comment*/ /* comment */ )");
+
+ test_balanced_unparseable(":not(:nth-child(2k))");
+ test_balanced_unparseable(":not(:nth-child(()))");
+
+ // Bug 1685621 - Serialization of :not()
+ should_serialize_to(":not([disabled][selected])", ":not([disabled][selected])");
+ should_serialize_to(":not([disabled],[selected])", ":not([disabled], [selected])");
+
+ // :-moz-any()
+ test_parseable(":-moz-any()");
+ test_parseable(":-moz-any('foo')");
+ test_parseable(":-moz-any(div p)");
+ test_parseable(":-moz-any(div ~ p)");
+ test_parseable(":-moz-any(div~p)");
+ test_parseable(":-moz-any(div + p)");
+ test_parseable(":-moz-any(div+p)");
+ test_parseable(":-moz-any(div > p)");
+ test_parseable(":-moz-any(div>p)");
+ test_parseable(":-moz-any(div, p)");
+ test_parseable(":-moz-any( div , p )");
+ test_parseable(":-moz-any(div,p)");
+ test_parseable(":-moz-any(div)");
+ test_parseable(":-moz-any(div,p,:link,span:focus)");
+ test_parseable(":-moz-any(:active,:focus)");
+ test_parseable(":-moz-any(:active,:link:focus)");
+ test_parseable(":-moz-any(div,:nonexistentpseudo)");
+ var any_elts = "<input type='text'><a href='http://www.example.com/'></a><div></div><a name='foo'>";
+ test_selector_in_html(":-moz-any(a,input)", any_elts,
+ bodychildset([0, 1, 3]), bodychildset([2]));
+ test_selector_in_html(":-moz-any(:link,:not(a))", any_elts,
+ bodychildset([0, 1, 2]), bodychildset([3]));
+ test_selector_in_html(":-moz-any([href],input[type],input[name])", any_elts,
+ bodychildset([0, 1]), bodychildset([2, 3]));
+ test_selector_in_html(":-moz-any(div,a):-moz-any([type],[href],[name])",
+ any_elts,
+ bodychildset([1, 3]), bodychildset([0, 2]));
+
+ // Test that we don't tokenize an empty HASH.
+ test_balanced_unparseable("#");
+ test_balanced_unparseable("# ");
+ test_balanced_unparseable("#, p");
+ test_balanced_unparseable("# , p");
+ test_balanced_unparseable("p #");
+ test_balanced_unparseable("p # ");
+ test_balanced_unparseable("p #, p");
+ test_balanced_unparseable("p # , p");
+
+ // Test that a backslash alone at EOF outside of a string is treated
+ // as U+FFFD.
+ test_parseable_via_api("#a\\");
+ test_parseable_via_api("#\\");
+ test_parseable_via_api("\\");
+
+ // Test that newline escapes are only supported in strings.
+ test_balanced_unparseable("di\\\nv");
+ test_balanced_unparseable("div \\\n p");
+ test_balanced_unparseable("div\\\n p");
+ test_balanced_unparseable("div \\\np");
+ test_balanced_unparseable("div\\\np");
+
+ // Test that :-moz-placeholder is parsable.
+ test_parseable(":-moz-placeholder");
+
+ // Test that things other than user-action pseudo-classes are
+ // rejected after pseudo-elements. Some of these tests rely on
+ // using a pseudo-element that supports a user-action pseudo-class
+ // after it, so we need to use the prefixed ::-moz-color-swatch,
+ // which is one of the ones with
+ // CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE (none of which are
+ // unprefixed).
+ test_parseable("::-moz-color-swatch:hover");
+ test_parseable("::-moz-color-swatch:is(:hover)");
+ test_parseable("::-moz-color-swatch:not(:hover)");
+ test_parseable("::-moz-color-swatch:where(:hover)");
+ test_balanced_unparseable("::-moz-color-swatch:not(.foo)");
+ test_balanced_unparseable("::-moz-color-swatch:first-child");
+ test_balanced_unparseable("::-moz-color-swatch:host");
+ test_balanced_unparseable("::-moz-color-swatch:host(div)");
+ test_balanced_unparseable("::-moz-color-swatch:nth-child(1)");
+ test_balanced_unparseable("::-moz-color-swatch:hover#foo");
+ test_balanced_unparseable(".foo::after:not(.bar) ~ h3");
+
+ for (let selector of [
+ "::-moz-color-swatch:where(.foo)",
+ "::-moz-color-swatch:is(.foo)",
+ "::-moz-color-swatch:where(p, :hover)",
+ "::-moz-color-swatch:is(p, :hover)",
+ ]) {
+ test_parseable(selector);
+ should_serialize_to(selector, selector);
+ ok(!CSS.supports(`selector(${selector})`), "supports should report false for forging selector parse failure");
+ }
+
+ run_deferred_tests();
+}
+
+var deferred_tests = [];
+
+function defer_clonedoc_tests(docurl, onloadfunc)
+{
+ deferred_tests.push( { docurl: docurl, onloadfunc: onloadfunc } );
+}
+
+function run_deferred_tests()
+{
+ if (deferred_tests.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+
+ cloneiframe.onload = deferred_tests_onload;
+ cloneiframe.src = deferred_tests[0].docurl;
+}
+
+function deferred_tests_onload(event)
+{
+ if (event.target != cloneiframe)
+ return;
+
+ deferred_tests[0].onloadfunc();
+ deferred_tests.shift();
+
+ run_deferred_tests();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_setPropertyWithNull.html b/layout/style/test/test_setPropertyWithNull.html
new file mode 100644
index 0000000000..d54fd03cdd
--- /dev/null
+++ b/layout/style/test/test_setPropertyWithNull.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=830260
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 830260</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 830260 **/
+ var div = document.createElement("div");
+ div.style.color = "green";
+ div.style.color = "null";
+ is(div.style.color, "green", 'Assigning "null" as a color should not parse');
+ div.style.setProperty("color", "null");
+ is(div.style.color, "green",
+ 'Passing "null" as a color to setProperty should not parse');
+
+ div.style.setProperty("color", null);
+ is(div.style.color, "",
+ 'Passing null as a color to setProperty should remove the property');
+
+ div.style.color = "green";
+ is(div.style.color, "green", 'Assigning "green" as a color should parse');
+
+ div.style.color = null;
+ is(div.style.color, "",
+ 'Assigning null as a color should remove the property');
+
+
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=830260">Mozilla Bug 830260</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_shape_outside_CORS.html b/layout/style/test/test_shape_outside_CORS.html
new file mode 100644
index 0000000000..2d2ee1c32f
--- /dev/null
+++ b/layout/style/test/test_shape_outside_CORS.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: shape-outside with a CORS violation</title>
+<link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/>
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-outside-property"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+
+<style>
+.container {
+ clear: both;
+ width: 500px;
+}
+.shaper {
+ width: 50px;
+ height: 50px;
+ float: left;
+ background-color: green;
+}
+.shapeAllow {
+ shape-outside: url("support/1x1-transparent.png");
+}
+.shapeRefuse {
+ shape-outside: url("http://example.com/layout/style/test/support/1x1-transparent.png");
+}
+.sibling {
+ display: inline-block;
+}
+</style>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function runTests() {
+ let divAllow = document.getElementById("allow");
+ let divAllowSib = divAllow.nextElementSibling;
+ ok(divAllowSib.getBoundingClientRect().left == divAllow.getBoundingClientRect().left,
+ "Test 1: Sibling should be at same left offset as div (shape-outside should be allowed), and onload should only fire after layout is complete.");
+
+ let divRefuse = document.getElementById("refuse");
+ let divRefuseSib = divRefuse.nextElementSibling;
+ ok(divRefuseSib.getBoundingClientRect().left != divRefuse.getBoundingClientRect().left,
+ "Test 2: Sibling should be at different left offset from div (shape-outside should be refused).");
+
+ SimpleTest.finish();
+}
+</script>
+</head>
+<body onload="runTests()">
+ <div class="container">
+ <div id="allow" class="shaper shapeAllow"></div><div class="sibling">allow (image is blank, so text is flush left)</div>
+ </div>
+ <div class="container">
+ <div id="refuse" class="shaper shapeRefuse"></div><div class="sibling">refuse (image unread, so text is moved to box edge)</div>
+ </div>
+</body>
+</html>
diff --git a/layout/style/test/test_shared_sheet_caching.html b/layout/style/test/test_shared_sheet_caching.html
new file mode 100644
index 0000000000..e59c0c139e
--- /dev/null
+++ b/layout/style/test/test_shared_sheet_caching.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ function childWindowLoaded(childWin) {
+ is(
+ childWin.getComputedStyle(childWin.document.documentElement).backgroundColor,
+ "rgb(0, 255, 0)",
+ "Sheet should apply",
+ )
+ is(
+ SpecialPowers.getDOMWindowUtils(childWin).parsedStyleSheets,
+ 0,
+ `Shouldn't need to parse the stylesheet ${childWin.parent == window ? "frame" : "top"}`
+ );
+ if (childWin.top == childWin) {
+ SimpleTest.finish();
+ }
+ }
+</script>
+<link rel="stylesheet" href="file_shared_sheet_caching.css">
+<iframe src="file_shared_sheet_caching.html"></iframe>
+<a rel="opener" href="file_shared_sheet_caching.html" target="_blank">Navigation</a>
+<script>
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+ info("Sheets parsed: " + SpecialPowers.DOMWindowUtils.parsedStyleSheets);
+ is(
+ getComputedStyle(document.documentElement).backgroundColor,
+ "rgb(0, 255, 0)",
+ "Sheet should apply",
+ )
+ document.querySelector("a").click();
+}
+</script>
diff --git a/layout/style/test/test_sheet_privilege.html b/layout/style/test/test_sheet_privilege.html
new file mode 100644
index 0000000000..d089fdc137
--- /dev/null
+++ b/layout/style/test/test_sheet_privilege.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>Test whether it can access a filed in MediaList with normal privilege after accessing with chrome privilege.</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<style>
+@media screen and (max-width: 800px) {
+ body {
+ background-color: lime;
+ }
+}
+</style>
+<script>
+"use strict";
+
+function assertMediaText(rule, description) {
+ is(rule.media.mediaText, "screen and (max-width: 800px)", description);
+}
+
+add_task(function testCssRuleMedia() {
+ assertMediaText(SpecialPowers.wrap(document).styleSheets[0].cssRules[0], "privileged document");
+ assertMediaText(document.styleSheets[0].cssRules[0], "unprivileged document");
+});
+
+add_task(function testSheetFromCache() {
+ for (let i = 0; i < 2; ++i) {
+ const style = document.createElement("style");
+ style.textContent = `@media screen and (max-width: 800px) {}`;
+ document.head.append(style);
+ assertMediaText(SpecialPowers.wrap(style).sheet.cssRules[0], "privileged sheet " + i);
+ assertMediaText(style.sheet.cssRules[0], "unprivileged sheet " + i);
+ }
+});
+</script>
diff --git a/layout/style/test/test_shorthand_property_getters.html b/layout/style/test/test_shorthand_property_getters.html
new file mode 100644
index 0000000000..cade526183
--- /dev/null
+++ b/layout/style/test/test_shorthand_property_getters.html
@@ -0,0 +1,248 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=376075
+-->
+<head>
+ <title>Test for Bug 376075</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=376075">Mozilla Bug 376075</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 376075 **/
+
+var e = document.getElementById("display");
+
+var borderExtras = "; border-image: none";
+
+// Test that we only serialize the 'border' shorthand when appropriate.
+e.setAttribute("style", "border-left: medium solid blue; border-right: medium solid blue; border-top: medium blue solid; border-bottom: blue medium solid" + borderExtras);
+isnot(e.style.border, "", "should be able to serialize border");
+e.setAttribute("style", "border-left: medium solid blue; border-right: medium solid blue; border-top: medium blue solid; border-bottom: green medium solid" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: green" + borderExtras);
+isnot(e.style.border, "", "should be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: green blue blue blue" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: blue green blue blue" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: blue blue green blue" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: blue blue blue green" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 2px 3px 3px; border-style: solid; border-color: green" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid dashed; border-color: green" + borderExtras);
+is(e.style.border, "", "should not be able to serialize border");
+
+// Test suppression of currentcolor in border shorthands.
+e.setAttribute("style", "border: medium solid");
+is(e.style.border, "solid", "implied default color omitted serializing border");
+is(e.style.borderLeft, "solid", "implied default color omitted serializing border-left");
+is(e.style.cssText, "border: solid;", "implied default color omitted serializing declaration");
+e.setAttribute("style", "border-right: medium solid");
+is(e.style.borderRight, "solid", "implied default color omitted serializing border-right");
+is(e.style.cssText, "border-right: solid;", "implied default color omitted serializing declaration");
+
+// Test that we shorten box properties to the shortest possible.
+e.setAttribute("style", "margin: 7px");
+is(e.style.margin, "7px", "should condense to shortest possible");
+is(e.style.cssText, "margin: 7px;", "should condense to shortest possible");
+e.setAttribute("style", "padding: 7px 7px 7px");
+is(e.style.padding, "7px", "should condense to shortest possible");
+is(e.style.cssText, "padding: 7px;", "should condense to shortest possible");
+e.setAttribute("style", "border-width: 7px 7px 7px 7px");
+is(e.style.borderWidth, "7px", "should condense to shortest possible");
+is(e.style.cssText, "border-width: 7px;", "should condense to shortest possible");
+e.setAttribute("style", "margin: 7px 7px 7px 6px");
+is(e.style.margin, "7px 7px 7px 6px", "should not condense");
+is(e.style.cssText, "margin: 7px 7px 7px 6px;", "should not condense");
+e.setAttribute("style", "border-style: solid dotted none dotted");
+is(e.style.borderStyle, "solid dotted none", "should condense");
+is(e.style.cssText, "border-style: solid dotted none;", "should condense");
+e.setAttribute("style", "border-color: green blue");
+is(e.style.borderColor, "green blue", "should condense");
+is(e.style.cssText, "border-color: green blue;", "should condense");
+e.setAttribute("style", "border-color: green blue green");
+is(e.style.borderColor, "green blue", "should condense");
+is(e.style.cssText, "border-color: green blue;", "should condense");
+e.setAttribute("style", "border-color: green blue green blue");
+is(e.style.borderColor, "green blue", "should condense");
+is(e.style.cssText, "border-color: green blue;", "should condense");
+e.setAttribute("style", "border-color: currentColor currentColor currentcolor CURRENTcolor");
+is(e.style.borderColor, "currentcolor", "should condense to canonical case");
+is(e.style.cssText, "border-color: currentcolor;", "should condense to canonical case");
+e.setAttribute("style", "border-style: ridge none none none");
+is(e.style.borderStyle, "ridge none none", "should condense");
+is(e.style.cssText, "border-style: ridge none none;", "should condense");
+e.setAttribute("style", "border-radius: 1px 1px 1px 1px");
+is(e.style.borderRadius, "1px", "should condense to shortest possible");
+is(e.style.cssText, "border-radius: 1px;", "should condense to shortest possible");
+e.setAttribute("style", "border-radius: 1px 1px 1px 1px / 2px 2px 2px 2px");
+is(e.style.borderRadius, "1px / 2px", "should condense to shortest possible");
+is(e.style.cssText, "border-radius: 1px / 2px;", "should condense to shortest possible");
+e.setAttribute("style", "border-radius: 1px 2px 1px 2px / 1% 2% 3% 2%");
+is(e.style.borderRadius, "1px 2px / 1% 2% 3%", "should condense to shortest possible");
+is(e.style.cssText, "border-radius: 1px 2px / 1% 2% 3%;", "should condense to shortest possible");
+
+// Test that we refuse to serialize the 'background' and 'font'
+// shorthands when some subproperties that can't be expressed in the
+// shorthand syntax are present.
+e.setAttribute("style", "font: medium serif");
+isnot(e.style.font, "", "should have font shorthand");
+e.setAttribute("style", "font: medium serif; font-size-adjust: 0.45");
+is(e.style.font, "", "should not have font shorthand");
+e.setAttribute("style", "font: medium serif; font-feature-settings: 'liga' off");
+is(e.style.font, "", "should not have font shorthand");
+
+// Test that all combinations of background-clip and background-origin
+// can be expressed in the shorthand (which wasn't the case previously).
+e.setAttribute("style", "background: red");
+isnot(e.style.background, "", "should have background shorthand");
+e.setAttribute("style", "background: red; background-origin: border-box");
+isnot(e.style.background, "", "should have background shorthand (origin:border-box)");
+e.setAttribute("style", "background: red; background-clip: padding-box");
+isnot(e.style.background, "", "should have background shorthand (clip:padding-box)");
+e.setAttribute("style", "background: red; background-origin: content-box");
+isnot(e.style.background, "", "should have background shorthand (origin:content-box)");
+e.setAttribute("style", "background: red; background-clip: content-box");
+isnot(e.style.background, "", "should have background shorthand (clip:content-box)");
+e.setAttribute("style", "background: red; background-clip: content-box; background-origin: content-box;");
+isnot(e.style.background, "", "should have background shorthand (clip:content-box;origin:content-box)");
+
+// Test background-size in the background shorthand.
+e.setAttribute("style", "background: red; background-size: 100% 100%");
+isnot(e.style.background, "", "should have background shorthand (size:100% 100%)");
+e.setAttribute("style", "background: red; background-size: 100% auto");
+isnot(e.style.background, "", "should have background shorthand (size:100% auto)");
+e.setAttribute("style", "background: red; background-size: auto 100%");
+isnot(e.style.background, "", "should have background shorthand (size:auto 100%)");
+
+// Check that we only serialize background when the lists (of layers) for
+// the subproperties are the same length.
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+isnot(e.style.background, "", "should have background shorthand (all lists length 3)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-clip too long)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-origin too short)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png), none; background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-image too long)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-attachment too short)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px, bottom; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-position too long)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat");
+is(e.style.background, "", "should not have background shorthand (background-repeat too short)");
+e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
+is(e.style.background, "", "should not have background shorthand (background-size too long)");
+
+// Check that we only serialize background-position when the lists (of layers) for
+// the -x/-y subproperties are the same length.
+e.setAttribute("style", "background-position-x: 10%, left 2em, right; background-position-y: top 2em, bottom, 10%");
+is(e.style.backgroundPosition, "left 10% top 2em, left 2em bottom, right 10%", "should have background-position shorthand (both lists length 3)");
+e.setAttribute("style", "background-position-x: 10%, left 2em; background-position-y: top 2em, bottom, 10%");
+is(e.style.backgroundPosition, "", "should not have background-position shorthand (background-position-x too short)");
+e.setAttribute("style", "background-position-x: 10%, left 2em, right; background-position-y: top 2em");
+is(e.style.backgroundPosition, "", "should not have background-position shorthand (background-position-y too short)");
+
+// Check that background-position serialization doesn't produce invalid values.
+e.setAttribute("style", "background-position: 0px");
+is(e.style.backgroundPosition, "0px center", "1-value form should be accepted, with implied center value for background-position-y");
+e.setAttribute("style", "background-position: 0px center");
+is(e.style.backgroundPosition, "0px center", "2-value form 'x-offset' 'y-edge' should be accepted, and serialize to 2-value form");
+e.setAttribute("style", "background-position: left 0px center");
+is(e.style.backgroundPosition, "left 0px center", "3-value form 'x-edge' 'x-offset' 'y-edge' should be accepted and serialize to 3-value form");
+e.setAttribute("style", "background-position: left top 0px");
+is(e.style.backgroundPosition, "left top 0px", "3-value form 'x-edge' 'y-edge' 'y-offset' should be accepted and serialize to 3-value form");
+e.setAttribute("style", "background-position: left 0px top 0px");
+is(e.style.backgroundPosition, "left 0px top 0px", "4-value form should be accepted and serialize to 4-value form");
+e.setAttribute("style", "background-position-x: 0px; background-position-y: center");
+is(e.style.backgroundPosition, "0px center", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: 0px; background-position-y: 0px");
+is(e.style.backgroundPosition, "0px 0px", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: center; background-position-y: 0px");
+is(e.style.backgroundPosition, "center 0px", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: left; background-position-y: top");
+is(e.style.backgroundPosition, "left top", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: left 0px; background-position-y: center");
+is(e.style.backgroundPosition, "left 0px center", "should always serialize to 3-value form if both -x and -y specified an edge");
+e.setAttribute("style", "background-position-x: right; background-position-y: top 0px");
+is(e.style.backgroundPosition, "right top 0px", "should always serialize to 3-value form if both -x and -y specified an edge");
+e.setAttribute("style", "background-position-x: left 0px; background-position-y: 0px");
+is(e.style.backgroundPosition, "left 0px top 0px", "should serialize to 4-value form if 3-value form would only have one edge");
+e.setAttribute("style", "background-position-x: 0px; background-position-y: top 0px");
+is(e.style.backgroundPosition, "left 0px top 0px", "should serialize to 4-value form if 3-value form would only have one edge");
+
+// Check that we only serialize transition when the lists are the same length.
+e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
+isnot(e.style.transition, "", "should have transition shorthand (lists same length)");
+e.setAttribute("style", "transition-property: color, width, left; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition-property: all; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms, 300ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear, ease-out; transition-delay: 0s, 1s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s, 0s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition: color, width; transition-delay: 0s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+isnot(e.style.animation, "", "should have animation shorthand (lists same length)");
+e.setAttribute("style", "animation-name: bounce, roll, left; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms, 300ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear, ease-out; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s, 0s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards, both; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2, 1; animation-play-state: paused, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running, running; animation-timeline: auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running; animation-timeline: auto, auto, auto;");
+is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
+
+// Check that the 'border' shorthand resets 'border-image' and
+// but that other 'border-*' shorthands don't
+// (bug 482692).
+e.setAttribute("style", 'border-image: url("foo.png") 5 5 5 5 / 5 5 5 5 / 5 5 5 5 repeat repeat; border-left: medium solid green');
+is(e.style.cssText,
+ 'border-image: url("foo.png") 5 / 5 / 5 repeat; border-left: solid green;',
+ "border-left does NOT reset border-image");
+e.setAttribute("style", 'border-image: url("foo.png") 5 5 5 5; border: solid green');
+is(e.style.cssText, 'border: solid green;', "border DOES reset border-image");
+
+// Test that the color goes at the beginning of the last item of the
+// background shorthand.
+e.setAttribute("style", "background: url(foo.png) blue");
+is(e.style.cssText,
+ "background: blue url(\"foo.png\");",
+ "color should be at start of single-item background");
+e.setAttribute("style", "background: url(bar.png), url(foo.png) blue");
+is(e.style.cssText,
+ "background: url(\"bar.png\"), blue url(\"foo.png\");",
+ "color should be at start of single-item background");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_specified_value_serialization.html b/layout/style/test/test_specified_value_serialization.html
new file mode 100644
index 0000000000..5624b7398a
--- /dev/null
+++ b/layout/style/test/test_specified_value_serialization.html
@@ -0,0 +1,281 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test for miscellaneous specified value issues</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+(function test_bug_721136() {
+ // Test for transform property serialization.
+ [
+ [" mAtRiX(1, 2,3,4, 5,6 ) ", "matrix(1, 2, 3, 4, 5, 6)"],
+ [" mAtRiX3d( 1,2,3,0,4 ,5,6,0,7,8 , 9,0,10, 11,12,1 ) ",
+ "matrix3d(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 10, 11, 12, 1)"],
+ [" pErSpEcTiVe( 400Px ) ", "perspective(400px)"],
+ [" rOtAtE( 90dEg ) ", "rotate(90deg)"],
+ [" rOtAtE3d( 0,0 , 1 ,180DeG ) ", "rotate3d(0, 0, 1, 180deg)"],
+ [" rOtAtEx( 100GrAD ) ", "rotateX(100grad)"],
+ [" rOtAtEy( 1.57RaD ) ", "rotateY(1.57rad)"],
+ [" rOtAtEz( 0.25TuRn ) ", "rotateZ(0.25turn)"],
+ [" sCaLe( 2 ) ", "scale(2)"],
+ [" sCaLe( 2,3 ) ", "scale(2, 3)"],
+ [" sCaLe3D( 2,4 , -9 ) ", "scale3d(2, 4, -9)"],
+ [" sCaLeX( 2 ) ", "scaleX(2)"],
+ [" sCaLeY( 2 ) ", "scaleY(2)"],
+ [" sCaLeZ( 2 ) ", "scaleZ(2)"],
+ [" sKeW( 45dEg ) ", "skew(45deg)"],
+ [" sKeW( 45dEg,45DeG ) ", "skew(45deg, 45deg)"],
+ [" sKeWx( 45DeG ) ", "skewX(45deg)"],
+ [" sKeWy( 45DeG ) ", "skewY(45deg)"],
+ [" tRaNsLaTe( 1Px ) ", "translate(1px)"],
+ [" tRaNsLaTe( 1Px,3Pt ) ", "translate(1px, 3pt)"],
+ [" tRaNsLaTe3D( 21pX,-6pX , 4pX ) ", "translate3d(21px, -6px, 4px)"],
+ [" tRaNsLaTeX( 1pT ) ", "translateX(1pt)"],
+ [" tRaNsLaTeY( 1iN ) ", "translateY(1in)"],
+ [" tRaNsLaTeZ( 15.4pX ) ", "translateZ(15.4px)"],
+ ["tranSlatex( 16px )rotatez(-90deg) rotate(100grad)\ttranslate3d(12pt, 0pc, 0.0em)",
+ "translateX(16px) rotateZ(-90deg) rotate(100grad) translate3d(12pt, 0pc, 0em)"],
+ ].forEach(function(arr) {
+ document.documentElement.style.transform = arr[0];
+ is(document.documentElement.style.transform, arr[1],
+ "bug-721136 incorrect serialization");
+ });
+
+ var elt = document.documentElement;
+
+ elt.setAttribute("style",
+ "transform: tRANslatEX(5px) TRanslATey(10px) translatez(2px) ROTATEX(30deg) rotateY(30deg) rotatez(5deg) SKEWx(10deg) skewy(10deg) scaleX(2) SCALEY(0.5) scalez(2)");
+ is(elt.style.getPropertyValue("transform"),
+ "translateX(5px) translateY(10px) translateZ(2px) rotateX(30deg) rotateY(30deg) rotateZ(5deg) skewX(10deg) skewY(10deg) scaleX(2) scaleY(0.5) scaleZ(2)",
+ "bug-721136 expected case canonicalization of transform functions");
+
+ elt.setAttribute("style",
+ "font-variant-alternates: SWASH(fOo) stYLIStiC(Bar)");
+ is(elt.style.getPropertyValue("font-variant-alternates"),
+ "stylistic(Bar) swash(fOo)",
+ "expected case and ordering canonicalization of font-variant-alternates functions");
+
+ elt.setAttribute("style", ""); // leave the page in a useful state
+})();
+
+(function test_bug_1347164() {
+ // Test that specified color values are serialized as "rgb()"
+ // IFF they're fully-opaque (and otherwise as "rgba()").
+ var color = [
+ ["rgba(0, 0, 0, 1)", "rgb(0, 0, 0)"],
+ ["rgba(0, 0, 0, 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ["hsla(0, 0%, 0%, 1)", "rgb(0, 0, 0)"],
+ ["hsla(0, 0%, 0%, 0.5)", "rgba(0, 0, 0, 0.5)"],
+ ];
+
+ var css_color_4 = [
+ ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgba(0 0 0 / 0.1)", "rgba(0, 0, 0, 0.1)"],
+ ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"],
+ ["rgb(0 0 0 / 0.2)", "rgba(0, 0, 0, 0.2)"],
+ ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsla(0deg 0% 0% / 0.3)", "rgba(0, 0, 0, 0.3)"],
+ ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"],
+ ["hsl(0 0% 0% / 0.4)", "rgba(0, 0, 0, 0.4)"],
+ ];
+
+ var frame_container = document.getElementById("display");
+ var p = document.createElement("p");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < color.length; ++i) {
+ var test = color[i];
+ p.style.color = test[0];
+ is(p.style.color, test[1], "serialization value of " + test[0]);
+ }
+ for (var i = 0; i < css_color_4.length; ++i) {
+ var test = css_color_4[i];
+ p.style.color = test[0];
+ is(p.style.color, test[1], "css-color-4 serialization value of " + test[0]);
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1357117() {
+ // Test that vendor-prefixed gradient styles round-trip with the same prefix,
+ // or with no prefix.
+ var backgroundImages = [
+ // [ specified style,
+ // expected serialization,
+ // descriptionOfTestcase ],
+ // Linear gradient with legacy-gradient-line (needs prefixed syntax):
+ [ "-webkit-linear-gradient(10deg, red, blue)",
+ "-webkit-linear-gradient(10deg, red, blue)",
+ "-webkit-linear-gradient with angled legacy-gradient-line" ],
+
+ // Linear gradient with box corner (needs prefixed syntax):
+ [ "-webkit-linear-gradient(top left, red, blue)",
+ "-webkit-linear-gradient(left top, red, blue)",
+ "-webkit-linear-gradient with box corner" ],
+
+ // Servo keeps the original prefix form which is closer to other impls.
+ [ "-webkit-radial-gradient(contain, red, blue)",
+ "-webkit-radial-gradient(closest-side, red, blue)",
+ "-webkit-radial-gradient with legacy 'contain' keyword" ],
+ ];
+
+ var frame_container = document.getElementById("display");
+ var p = document.createElement("p");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundImages.length; ++i) {
+ var test = backgroundImages[i];
+ p.style.backgroundImage = test[0];
+ is(p.style.backgroundImage, test[1],
+ "serialization value of prefixed gradient expression (" + test[2] + ")");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1357932() {
+ // Test for box-position keyword ordering, in serialization of specified CSS
+ // gradients.
+ var backgroundImages = [
+ // [ specified style,
+ // expected serialization,
+ // descriptionOfTestcase ],
+ // Linear gradient to box position ordering:
+ [ "linear-gradient(to right top, gray, pink)",
+ "linear-gradient(to right top, gray, pink)",
+ "linear-gradient ordering to right top" ],
+ [ "linear-gradient(to top right, yellow, teal)",
+ "linear-gradient(to right top, yellow, teal)",
+ "linear-gradient ordering to top right" ],
+ ];
+
+ var frame_container = document.getElementById("display");
+ var p = document.createElement("p");
+ frame_container.appendChild(p);
+
+ for (var i = 0; i < backgroundImages.length; ++i) {
+ var test = backgroundImages[i];
+ p.style.backgroundImage = test[0];
+ is(p.style.backgroundImage, test[1],
+ "serialization value of gradient expression (" + test[2] + ")");
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1367028() {
+ const borderImageSubprops = [
+ "border-image-slice",
+ "border-image-outset",
+ "border-image-width"
+ ];
+ const rectValues = [
+ {
+ values: ["5 5 5 5", "5 5 5", "5 5", "5"],
+ expected: "5",
+ desc: "identical four sides",
+ },
+ {
+ values: ["5 6 5 6", "5 6 5", "5 6"],
+ expected: "5 6",
+ desc: "identical values on each axis",
+ },
+ {
+ values: ["5 6 7 6", "5 6 7"],
+ expected: "5 6 7",
+ desc: "identical values on left and right",
+ },
+ {
+ values: ["5 6 5 7"],
+ desc: "identical values on top and bottom",
+ },
+ {
+ values: ["5 5 6 6", "5 6 6 5"],
+ desc: "identical values on unrelated sides",
+ },
+ {
+ values: ["5 6 7 8"],
+ desc: "different values on all sides",
+ },
+ ];
+
+ let frameContainer = document.getElementById("display");
+ let p = document.createElement("p");
+ frameContainer.appendChild(p);
+
+ for (let prop of borderImageSubprops) {
+ for (let {values, expected, desc} of rectValues) {
+ for (let value of values) {
+ p.style.setProperty(prop, value);
+ is(p.style.getPropertyValue(prop),
+ expected ? expected : value, `${desc} for ${prop}`);
+ p.style.removeProperty(prop);
+ }
+ }
+ }
+
+ p.remove();
+})();
+
+(function test_bug_1397619() {
+ var borderRadius = [
+ // [ specified style,
+ // expected serialization,
+ // descriptionOfTestcase ],
+ [ "10px 10px",
+ "10px",
+ "Width and height specified for " ],
+ [ "10px",
+ "10px",
+ "Only width specified for " ],
+ ];
+
+
+ var frame_container = document.getElementById("display");
+ var p = document.createElement("p");
+ frame_container.appendChild(p);
+
+ [
+ "border-top-left-radius",
+ "border-bottom-left-radius",
+ "border-top-right-radius",
+ "border-bottom-right-radius",
+ "border-spacing",
+ ].forEach(function(property) {
+ for (var i = 0; i < borderRadius.length; ++i) {
+ var test = borderRadius[i];
+ p.style.setProperty(property, test[0]);
+ is(p.style.getPropertyValue(property), test[1], test[2] + property);
+ }
+ });
+
+ p.style.paintOrder = "markers";
+ is(p.style.paintOrder, "markers",
+ "specified value serialization for paint-order doesn't contain repetitive values");
+
+ p.remove();
+})();
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {
+ set: [['layout.css.individual-transform.enabled', true]],
+ },
+ () =>
+ window.open('file_specified_value_serialization_individual_transforms.html')
+);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_style_attr_listener.html b/layout/style/test/test_style_attr_listener.html
new file mode 100644
index 0000000000..b824fe7ff4
--- /dev/null
+++ b/layout/style/test/test_style_attr_listener.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for Bug 338679 (from bug 1340341)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div style=""></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+// Run the test first with mutation events enabled and then disabled.
+SpecialPowers.pushPrefEnv(
+ { 'set': [['dom.mutation-events.cssom.disabled', false]] },
+ runTest
+);
+
+function runTest(stop) {
+ let div = document.querySelector('div');
+ let expectation;
+ let count = 0;
+ div.style.color = "red";
+ div.addEventListener('DOMAttrModified', function(evt) {
+ count++;
+ is(evt.prevValue, expectation.prevValue, `Previous value for event ${count}`);
+ is(evt.newValue, expectation.newValue, `New value for event ${count}`);
+ });
+ expectation = { prevValue: 'color: red;', newValue: 'color: green;' };
+ div.style.color = "green";
+ expectation = { prevValue: 'color: green;', newValue: '' };
+ div.style.color = '';
+ if (SpecialPowers.getBoolPref("dom.mutation-events.cssom.disabled")) {
+ is(count, 0, "No DOMAttrModified event should be triggered");
+ } else {
+ is(count, 2, "DOMAttrModified events should have been triggered");
+ }
+
+ if (!stop) {
+ div.setAttribute("style", "");
+ SpecialPowers.pushPrefEnv(
+ { 'set': [['dom.mutation-events.cssom.disabled', true]] },
+ function() {
+ runTest(true);
+ SimpleTest.finish();
+ }
+ );
+ }
+}
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_style_attribute_quirks.html b/layout/style/test/test_style_attribute_quirks.html
new file mode 100644
index 0000000000..5a5b87e122
--- /dev/null
+++ b/layout/style/test/test_style_attribute_quirks.html
@@ -0,0 +1,18 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=915093
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 915093</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="style_attribute_tests.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=915093">Mozilla Bug 915093</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_style_attribute_standards.html b/layout/style/test/test_style_attribute_standards.html
new file mode 100644
index 0000000000..e6e64afc24
--- /dev/null
+++ b/layout/style/test/test_style_attribute_standards.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=915093
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 915093</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="style_attribute_tests.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=915093">Mozilla Bug 915093</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_style_struct_copy_constructors.html b/layout/style/test/test_style_struct_copy_constructors.html
new file mode 100644
index 0000000000..95f727a58d
--- /dev/null
+++ b/layout/style/test/test_style_struct_copy_constructors.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for style struct copy constructors</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"><span id="one"></span><span id="two"></span><span id="parent"><span id="child"></span></span></p>
+<div id="content" style="display: none">
+
+<div id="testnode"><span id="element"></span></div>
+
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for style struct copy constructors **/
+
+/**
+ * XXX Why doesn't putting a bug in the nsStyleFont copy-constructor for
+ * font-weight (initializing to normal) trigger a failure of this test?
+ * It works for leaving -moz-image-region uninitialized (both halves),
+ * overwriting text-decoration (only the first half, since it's not
+ * inherited), and leaving visibility uninitialized (only the second
+ * half; passes the first half ok).
+ */
+
+var gElementOne = document.getElementById("one");
+var gElementTwo = document.getElementById("two");
+var gElementParent = document.getElementById("parent");
+var gElementChild = document.getElementById("child");
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#one, #two, #parent {}", gStyleSheet.cssRules.length)];
+var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#two, #child {}", gStyleSheet.cssRules.length)];
+
+/** Test using aStartStruct **/
+
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (!("subproperties" in info)) {
+ gRule1.style.setProperty(prop, info.other_values[0], "");
+ gRule2.style.setProperty(prop, info.other_values[0], "");
+ }
+}
+
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (!("subproperties" in info)) {
+ gRule2.style.removeProperty(prop);
+
+ var one = getComputedStyle(gElementOne, "").getPropertyValue(prop);
+ var two = getComputedStyle(gElementTwo, "").getPropertyValue(prop);
+ is(two, one,
+ "property '" + prop + "' was copy-constructed correctly (aStartStruct)");
+
+ gRule2.style.setProperty(prop, info.other_values[0], "");
+ }
+}
+
+/** Test using inheritance **/
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (info.inherited && !("subproperties" in info)) {
+ gRule2.style.removeProperty(prop);
+
+ var parent = getComputedStyle(gElementParent, "").getPropertyValue(prop);
+ var child = getComputedStyle(gElementChild, "").getPropertyValue(prop);
+
+ is(child, parent,
+ "property '" + prop + "' was copy-constructed correctly (inheritance)");
+
+ gRule2.style.setProperty(prop, info.other_values[0], "");
+ }
+}
+
+for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (!("subproperties" in info)) {
+ gRule1.style.removeProperty(prop);
+ gRule2.style.removeProperty(prop);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_stylesheet_additions.html b/layout/style/test/test_stylesheet_additions.html
new file mode 100644
index 0000000000..0e115e91f4
--- /dev/null
+++ b/layout/style/test/test_stylesheet_additions.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>
+ Test for bug 1273303: Stylesheet additions and removals known to not
+ affect the document don't trigger restyles
+</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div class="classScope">
+ <div></div>
+</div>
+<div id="idScope">
+ <div></div>
+</div>
+<!--
+ We do it this way, using `disabled`, because appending stylesheets to the
+ document marks a restyle for that <style> element as needed, so we can't
+ accurately measure whether we've restyled or not.
+-->
+<style id="target" disabled></style>
+<script>
+SimpleTest.waitForExplicitFinish();
+const utils = SpecialPowers.getDOMWindowUtils(window);
+const TESTS = [
+ { selector: ".nonexistentClassScope", restyle: false },
+ { selector: ".nonexistentClassScope + div", restyle: true },
+ { selector: ".nonexistentClassScope div + div", restyle: false },
+ { selector: ".classScope", restyle: true },
+ { selector: ".classScope div", restyle: true },
+ { selector: "#idScope", restyle: true },
+ { selector: "#nonexistentIdScope", restyle: false },
+ { selector: "#nonexistentIdScope div + bar", restyle: false },
+ { selector: "baz", restyle: false },
+ { cssText: " ", restyle: false },
+ { cssText: "@keyframes foo { from { color: green } to { color: red } } #whatever { animation-name: foo; }", restyle: false },
+];
+
+for (const test of TESTS) {
+ let cssText = test.cssText ? test.cssText : (test.selector + " { color: green; }");
+ target.innerHTML = cssText;
+
+ document.body.offsetWidth;
+ const prevGeneration = utils.restyleGeneration;
+
+ target.disabled = true;
+
+ document.body.offsetWidth;
+ (test.restyle ? isnot : is)(utils.restyleGeneration, prevGeneration,
+ `Stylesheet removal with ${cssText} should ${test.restyle ? "have" : "not have"} caused a restyle`);
+
+ target.disabled = false; // Make the stylesheet effective.
+
+ if (test.selector) {
+ let element = document.querySelector(test.selector);
+ if (element) {
+ is(test.restyle, true, "How could we not expect a restyle?");
+ is(getComputedStyle(element).color, "rgb(0, 128, 0)",
+ "Element style should've changed appropriately");
+ }
+ }
+
+ document.body.offsetWidth;
+ (test.restyle ? isnot : is)(utils.restyleGeneration, prevGeneration,
+ `Stylesheet addition with ${cssText} should ${test.restyle ? "have" : "not have"} caused a restyle`);
+}
+
+SimpleTest.finish();
+</script>
diff --git a/layout/style/test/test_stylesheet_clone_font_face.html b/layout/style/test/test_stylesheet_clone_font_face.html
new file mode 100644
index 0000000000..e50bfec661
--- /dev/null
+++ b/layout/style/test/test_stylesheet_clone_font_face.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<link rel="stylesheet" href="data:text/css,@font-face { font-family: 'MarkA'; src: url(../fonts/markA.ttf); }">
+<link rel="stylesheet" href="data:text/css,@font-face { font-family: 'MarkA'; src: url(../fonts/markA.ttf); }">
+
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style>
+ body { font-family: "MarkA"; }
+</style>
+<div>ABC</div>
+<script>
+ function runTest() {
+ var links = document.getElementsByTagName("link");
+ links[0].sheet.cssRules[0].style.src = "../fonts/markB.ttf";
+
+ // Test that the cloned sheet is unaffected.
+ isnot(links[1].sheet.cssRules[0].style.src, "../fonts/markB.ttf", "Cloned sheet left unchanged.");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest();
+</script>
+</html>
diff --git a/layout/style/test/test_supports_rules.html b/layout/style/test/test_supports_rules.html
new file mode 100644
index 0000000000..8b88fd7836
--- /dev/null
+++ b/layout/style/test/test_supports_rules.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=649740
+-->
+<head>
+ <title>Test for Bug 649740</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="style">
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=649740">Mozilla Bug 649740</a>
+<p id="display1"></p>
+<p id="display2"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 649740 **/
+
+function condition(s) {
+ return s.replace(/^@supports\s*/, '').replace(/ \s*{\s*}\s*$/, '');
+}
+
+var styleSheetText =
+ "@supports(color: green){ }\n" +
+ "@supports (color: green) { }\n" +
+ "@supports ((color: green)) { }\n" +
+ "@supports (color: green) and (color: blue) { }\n" +
+ "@supports ( Font: 20px serif ! Important) { }";
+
+function runTest() {
+ var style = document.getElementById("style");
+ style.textContent = styleSheetText;
+
+ var sheet = style.sheet;
+
+ is(condition(sheet.cssRules[0].cssText), "(color: green)");
+ is(condition(sheet.cssRules[1].cssText), "(color: green)");
+ is(condition(sheet.cssRules[2].cssText), "((color: green))");
+ is(condition(sheet.cssRules[3].cssText), "(color: green) and (color: blue)");
+ is(condition(sheet.cssRules[4].cssText), "( Font: 20px serif ! Important)");
+
+ var cs1 = getComputedStyle(document.getElementById("display1"), "");
+ var cs2 = getComputedStyle(document.getElementById("display2"), "");
+ function check_balanced_condition(condition_inner, expected_match) {
+ style.textContent = "#display1, #display2 { text-decoration: overline }\n" +
+ "@supports " + condition_inner + "{\n" +
+ " #display1 { text-decoration: line-through }\n" +
+ "}\n" +
+ "#display2 { text-decoration: underline }\n";
+ is(cs1.textDecorationLine,
+ expected_match ? "line-through" : "overline",
+ "@supports condition \"" + condition_inner + "\" should " +
+ (expected_match ? "" : "NOT ") + "match");
+ is(cs2.textDecorationLine, "underline",
+ "@supports condition \"" + condition_inner + "\" should be balanced");
+ }
+
+ check_balanced_condition("not (color: green)", false);
+ check_balanced_condition("not (colour: green)", true);
+ check_balanced_condition("not(color: green)", false);
+ check_balanced_condition("not(colour: green)", false);
+ check_balanced_condition("not/* */(color: green)", false);
+ check_balanced_condition("not/* */(colour: green)", true);
+ check_balanced_condition("not /* */ (color: green)", false);
+ check_balanced_condition("not /* */ (colour: green)", true);
+ check_balanced_condition("(color: green) and (color: blue)", true);
+ check_balanced_condition("(color: green) /* */ /* */ and /* */ /* */ (color: blue)", true);
+ check_balanced_condition("(color: green) and(color: blue)", false);
+ check_balanced_condition("(color: green) and/* */(color: blue)", true);
+ check_balanced_condition("(color: green)and (color: blue)", true);
+ check_balanced_condition("(color: green) or (color: blue)", true);
+ check_balanced_condition("(color: green) /* */ /* */ or /* */ /* */ (color: blue)", true);
+ check_balanced_condition("(color: green) or(color: blue)", false);
+ check_balanced_condition("(color: green) or/* */(color: blue)", true);
+ check_balanced_condition("(color: green)or (color: blue)", true);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_system_font_serialization.html b/layout/style/test/test_system_font_serialization.html
new file mode 100644
index 0000000000..10fb54c45a
--- /dev/null
+++ b/layout/style/test/test_system_font_serialization.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=475214
+-->
+<head>
+ <title>Test for Bug 475214</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=475214">Mozilla Bug 475214</a>
+<p id="display"></p>
+<div id="content">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/* Helper copied from property_database.js */
+function IsCSSPropertyPrefEnabled(prefName)
+{
+ try {
+ if (SpecialPowers.getBoolPref(prefName)) {
+ return true;
+ }
+ } catch (ex) {
+ ok(false, "Failed to look up property-controlling pref '" +
+ prefName + "' (" + ex + ")");
+ }
+
+ return false;
+}
+
+/** Test for Bug 475214 **/
+
+var e = document.getElementById("content");
+var s = e.style;
+
+e.style.font = "menu";
+is(e.style.cssText, "font: menu;", "serialize system font alone");
+is(e.style.font, "menu", "font getter returns value");
+
+e.style.fontFamily = "inherit";
+is(e.style.font, "", "font getter should be empty");
+
+e.style.font = "message-box";
+is(e.style.cssText, "font: message-box;", "serialize system font alone");
+is(e.style.font, "message-box", "font getter returns value");
+
+e.setAttribute("style", "font-weight:bold;font:caption;line-height:3;");
+is(e.style.font, "", "font getter should be empty");
+
+e.setAttribute("style", "font: menu; font-weight: -moz-use-system-font");
+is(e.style.cssText, "font: menu;", "serialize system font alone");
+is(e.style.font, "menu", "font getter returns value");
+
+e.setAttribute("style", "font: inherit; font-family: Helvetica;");
+EXPECTED_DECLS = [
+ "font-family: Helvetica;",
+ "font-feature-settings: inherit;",
+ "font-kerning: inherit;",
+ "font-language-override: inherit;",
+ "font-size-adjust: inherit;",
+ "font-size: inherit;",
+ "font-stretch: inherit;",
+ "font-style: inherit;",
+ "font-variant: inherit;",
+ "font-weight: inherit;",
+ "line-height: inherit;",
+];
+if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) {
+ EXPECTED_DECLS.push("font-optical-sizing: inherit;");
+ EXPECTED_DECLS.push("font-variation-settings: inherit;");
+}
+EXPECTED_DECLS = EXPECTED_DECLS.sort().join(" ");
+let sortedDecls = e.style.cssText.split(/ (?=font-|line-)/).sort().join(" ");
+is(sortedDecls, EXPECTED_DECLS, "don't serialize system font for font:inherit");
+is(e.style.font, "", "font getter returns nothing");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_text_decoration_shorthands.html b/layout/style/test/test_text_decoration_shorthands.html
new file mode 100644
index 0000000000..d2cfed6667
--- /dev/null
+++ b/layout/style/test/test_text_decoration_shorthands.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test parsing of text-decoration shorthands</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+</head>
+<body>
+
+<script>
+
+var initial_values = {
+ textDecorationColor: "rgb(255, 0, 255)",
+ textDecorationLine: "none",
+ textDecorationStyle: "solid",
+ textDecorationThickness: "auto",
+};
+
+// For various specified values of the text-decoration shorthand,
+// test the computed values of the corresponding longhands.
+var text_decoration_test_cases = [
+ {
+ specified: "none",
+ },
+ {
+ specified: "red",
+ textDecorationColor: "rgb(255, 0, 0)",
+ },
+ {
+ specified: "line-through",
+ textDecorationLine: "line-through",
+ },
+ {
+ specified: "dotted",
+ textDecorationStyle: "dotted",
+ },
+ {
+ specified: "20px",
+ textDecorationThickness: "20px",
+ },
+ {
+ specified: "auto",
+ textDecorationThickness: "auto",
+ },
+ {
+ specified: "from-font",
+ textDecorationThickness: "from-font",
+ },
+ {
+ specified: "#00ff00 underline",
+ textDecorationColor: "rgb(0, 255, 0)",
+ textDecorationLine: "underline",
+ },
+ {
+ specified: "#ffff00 wavy",
+ textDecorationColor: "rgb(255, 255, 0)",
+ textDecorationStyle: "wavy",
+ },
+ {
+ specified: "overline double",
+ textDecorationLine: "overline",
+ textDecorationStyle: "double",
+ },
+ {
+ specified: "red underline solid",
+ textDecorationColor: "rgb(255, 0, 0)",
+ textDecorationLine: "underline",
+ textDecorationStyle: "solid",
+ },
+ {
+ specified: "double overline blue",
+ textDecorationColor: "rgb(0, 0, 255)",
+ textDecorationLine: "overline",
+ textDecorationStyle: "double",
+ },
+ {
+ specified: "solid underline 10px",
+ textDecorationStyle: "solid",
+ textDecorationLine: "underline",
+ textDecorationThickness: "10px",
+ },
+ {
+ specified: "line-through blue 200px",
+ textDecorationLine: "line-through",
+ textDecorationColor: "rgb(0, 0, 255)",
+ textDecorationThickness: "200px",
+ },
+ {
+ specified: "underline wavy red 0",
+ textDecorationLine: "underline",
+ textDecorationStyle: "wavy",
+ textDecorationColor: "rgb(255, 0, 0)",
+ textDecorationThickness: "0px",
+ },
+ {
+ specified: "overline -30px",
+ textDecorationLine: "overline",
+ textDecorationThickness: "-30px",
+ },
+ {
+ specified: "underline red from-font",
+ textDecorationLine: "underline",
+ textDecorationColor: "rgb(255, 0, 0)",
+ textDecorationThickness: "from-font",
+ },
+];
+
+function run_tests(test_cases, shorthand, subproperties) {
+ test_cases.forEach(function(test_case) {
+ test(function() {
+ var element = document.createElement('div');
+ document.body.appendChild(element);
+ // Set text color to test initial value of text-decoration-color
+ // (currentColor).
+ element.style.color = "#ff00ff";
+ element.style[shorthand] = test_case.specified;
+ var computed = window.getComputedStyle(element);
+ subproperties.forEach(function(longhand) {
+ assert_equals(
+ computed[longhand],
+ test_case[longhand] || initial_values[longhand],
+ longhand
+ );
+ });
+ }, "test parsing of 'text-decoration: " + test_case.specified + "'");
+ });
+}
+
+run_tests(text_decoration_test_cases, "textDecoration", [
+ "textDecorationColor", "textDecorationLine", "textDecorationStyle", "textDecorationThickness"]);
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions.html b/layout/style/test/test_transitions.html
new file mode 100644
index 0000000000..c26ba9be8f
--- /dev/null
+++ b/layout/style/test/test_transitions.html
@@ -0,0 +1,787 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display p { margin-top: 0; margin-bottom: 0; }
+ #display .before, #display .after {
+ width: -moz-fit-content; border: 1px solid black;
+ }
+ #display .before::before, #display .after::after {
+ display: block;
+ width: 0;
+ text-indent: 0;
+ }
+ #display .before.started::before, #display .after.started::after {
+ width: 100px;
+ text-indent: 100px;
+ transition: 8s width ease-in-out, 8s text-indent ease-in-out;
+ }
+ #display .before::before {
+ content: "Before";
+ }
+ #display .after::after {
+ content: "After";
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>
+<div id="display">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 435441 **/
+
+// Run tests simultaneously so we don't have to take up too much time.
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+var gTestsRunning = 0;
+function TestStarted() { ++gTestsRunning; }
+function TestFinished() { if (--gTestsRunning == 0) SimpleTest.finish(); }
+
+// An array of arrays of functions to be called at the outer index number
+// of seconds after the present.
+var gFutureCalls = [];
+
+function add_future_call(index, func)
+{
+ if (!(index in gFutureCalls)) {
+ gFutureCalls[index] = [];
+ }
+ gFutureCalls[index].push(func);
+ TestStarted();
+}
+var gStartTime1, gStartTime2;
+var gCurrentTime;
+var gSetupComplete = false;
+
+function process_future_calls(index)
+{
+ var calls = gFutureCalls[index];
+ if (!calls)
+ return;
+ gCurrentTime = Date.now();
+ for (var i = 0; i < calls.length; ++i) {
+ calls[i]();
+ TestFinished();
+ }
+}
+
+var timingFunctions = {
+ // a map from the value of 'transition-timing-function' to an array of
+ // the portions this function yields at 0 (always 0), 1/4, 1/2, and
+ // 3/4 and all (always 1) of the way through the time of the
+ // transition. Each portion is represented as a value and an
+ // acceptable error tolerance (based on a time error of 1%) for that
+ // value.
+
+ // ease
+ "ease": bezier(0.25, 0.1, 0.25, 1),
+ "cubic-bezier(0.25, 0.1, 0.25, 1.0)": bezier(0.25, 0.1, 0.25, 1),
+
+ // linear and various synonyms for it
+ "linear": function(x) { return x; },
+ "cubic-bezier(0.0, 0.0, 1.0, 1.0)": function(x) { return x; },
+ "cubic-bezier(0, 0, 1, 1)": function(x) { return x; },
+ "cubic-bezier(0, 0, 0, 0.0)": function(x) { return x; },
+ "cubic-bezier(1.0, 1, 0, 0)": function(x) { return x; },
+
+ // ease-in
+ "ease-in": bezier(0.42, 0, 1, 1),
+ "cubic-bezier(0.42, 0, 1.0, 1.0)": bezier(0.42, 0, 1, 1),
+
+ // ease-out
+ "ease-out": bezier(0, 0, 0.58, 1),
+ "cubic-bezier(0, 0, 0.58, 1.0)": bezier(0, 0, 0.58, 1),
+
+ // ease-in-out
+ "ease-in-out": bezier(0.42, 0, 0.58, 1),
+ "cubic-bezier(0.42, 0, 0.58, 1.0)": bezier(0.42, 0, 0.58, 1),
+
+ // other cubic-bezier values
+ "cubic-bezier(0.4, 0.1, 0.7, 0.95)": bezier(0.4, 0.1, 0.7, 0.95),
+ "cubic-bezier(1, 0, 0, 1)": bezier(1, 0, 0, 1),
+ "cubic-bezier(0, 1, 1, 0)": bezier(0, 1, 1, 0),
+
+};
+
+var div = document.getElementById("display");
+
+// Set up all the elements on which we are going to initiate transitions.
+
+// We have two reference elements to check the expected timing range.
+// They both have 16s linear transitions from 0 to 1000px.
+// This means they move through 62.5 pixels per second.
+const REF_PX_PER_SEC = 62.5;
+function make_reference_p() {
+ let p = document.createElement("p");
+ p.appendChild(document.createTextNode("reference"));
+ p.style.textIndent = "0px";
+ p.style.transition = "16s text-indent linear";
+ div.appendChild(p);
+ return p;
+}
+var earlyref = make_reference_p();
+var earlyrefcs = getComputedStyle(earlyref, "");
+
+// Test all timing functions using a set of 8-second transitions, which
+// we check at times 0, 2s, 4s, 6s, and 8s.
+var tftests = [];
+for (let tf in timingFunctions) {
+ let p = document.createElement("p");
+ let t = document.createTextNode("transition-timing-function: " + tf);
+ p.appendChild(t);
+ p.style.textIndent = "0px";
+ p.style.transition = "8s text-indent linear";
+ p.style.transitionTimingFunction = tf;
+ div.appendChild(p);
+ is(getComputedStyle(p, "").textIndent, "0px",
+ "should be zero before changing value");
+ tftests.push([ p, tf ]);
+}
+
+// Check that the timing function continues even when we restyle in the
+// middle.
+var interrupt_tests = [];
+for (var restyleParent of [true, false]) {
+ for (let itime = 2; itime < 8; itime += 2) {
+ let p = document.createElement("p");
+ let t = document.createTextNode("interrupt on " +
+ (restyleParent ? "parent" : "node itself") +
+ " at " + itime + "s");
+ p.appendChild(t);
+ p.style.textIndent = "0px";
+ p.style.transition = "8s text-indent cubic-bezier(0, 1, 1, 0)";
+ if (restyleParent) {
+ let d = document.createElement("div");
+ d.appendChild(p);
+ div.appendChild(d);
+ } else {
+ div.appendChild(p);
+ }
+ is(getComputedStyle(p, "").textIndent, "0px",
+ "should be zero before changing value");
+ setTimeout("interrupt_tests[" + interrupt_tests.length + "]" +
+ "[0]" + (restyleParent ? ".parentNode" : "") +
+ ".style.color = 'blue';" +
+ "check_interrupt_tests()", itime*1000);
+ interrupt_tests.push([ p, itime ]);
+ }
+}
+
+// Test transition-delay values of -4s through 4s on a 4s transition
+// with 'ease-out' timing function.
+var delay_tests = {};
+for (let d = -4; d <= 4; ++d) {
+ let p = document.createElement("p");
+ let delay = d + "s";
+ let t = document.createTextNode("transition-delay: " + delay);
+ p.appendChild(t);
+ p.style.marginLeft = "0px";
+ p.style.transition = "4s margin-left ease-out " + delay;
+ div.appendChild(p);
+ is(getComputedStyle(p, "").marginLeft, "0px",
+ "should be zero before changing value");
+ delay_tests[d] = p;
+}
+
+// Test transition-delay values of -4s through 4s on a 4s transition
+// with duration of zero.
+var delay_zero_tests = {};
+for (let d = -4; d <= 4; ++d) {
+ let p = document.createElement("p");
+ let delay = d + "s";
+ let t = document.createTextNode("transition-delay: " + delay);
+ p.appendChild(t);
+ p.style.marginLeft = "0px";
+ p.style.transition = "0s margin-left linear " + delay;
+ div.appendChild(p);
+ is(getComputedStyle(p, "").marginLeft, "0px",
+ "should be zero before changing value");
+ delay_zero_tests[d] = p;
+}
+
+// Test that changing the value on an already-running transition to the
+// value it currently happens to have resets the transition.
+function make_reset_test(transition, description)
+{
+ let p = document.createElement("p");
+ let t = document.createTextNode(description);
+ p.appendChild(t);
+ p.style.marginLeft = "0px";
+ p.style.transition = transition;
+ div.appendChild(p);
+ is(getComputedStyle(p, "").marginLeft, "0px",
+ "should be zero before changing value");
+ return p;
+}
+var reset_test = make_reset_test("4s margin-left ease-out 4s", "transition-delay reset to starting point");
+var reset_test_reference = make_reset_test("4s margin-left linear -3s", "reference for previous test (reset test)");
+
+// Test that transitions on descendants start correctly when the
+// inherited value is itself transitioning. In other words, when
+// ancestor and descendant both have a transition for the same property,
+// and the descendant inherits the property from the ancestor, the
+// descendant's transition starts as specified, based on the concepts of
+// the before-change style, the after-change style, and the
+// after-transition style.
+var descendant_tests = [
+ { parent_transition: "",
+ child_transition: "4s text-indent" },
+ { parent_transition: "4s text-indent",
+ child_transition: "" },
+ { parent_transition: "4s text-indent",
+ child_transition: "16s text-indent" },
+ { parent_transition: "4s text-indent",
+ child_transition: "1s text-indent" },
+ { parent_transition: "8s letter-spacing",
+ child_transition: "4s text-indent" },
+ { parent_transition: "4s text-indent",
+ child_transition: "8s letter-spacing" },
+ { parent_transition: "4s text-indent",
+ child_transition: "8s all" },
+ { parent_transition: "8s text-indent",
+ child_transition: "4s all" },
+ // examples with positive and negative delay
+ { parent_transition: "4s text-indent 1s",
+ child_transition: "8s text-indent" },
+ { parent_transition: "4s text-indent -1s",
+ child_transition: "8s text-indent" }
+];
+
+for (let i in descendant_tests) {
+ let test = descendant_tests[i];
+ test.parentNode = document.createElement("div");
+ test.childNode = document.createElement("p");
+ test.parentNode.appendChild(test.childNode);
+ test.childNode.appendChild(document.createTextNode(
+ "parent with \"" + test.parent_transition + "\" and " +
+ "child with \"" + test.child_transition + "\""));
+ test.parentNode.style.transition = test.parent_transition;
+ test.childNode.style.transition = test.child_transition;
+ test.parentNode.style.textIndent = "50px"; // transition from 50 to 150
+ test.parentNode.style.letterSpacing = "10px"; // transition from 10 to 5
+ div.appendChild(test.parentNode);
+ var parentCS = getComputedStyle(test.parentNode, "");
+ var childCS = getComputedStyle(test.childNode, "");
+ is(parentCS.textIndent, "50px",
+ "parent text-indent should be 50px before changing");
+ is(parentCS.letterSpacing, "10px",
+ "parent letter-spacing should be 10px before changing");
+ is(childCS.textIndent, "50px",
+ "child text-indent should be 50px before changing");
+ is(childCS.letterSpacing, "10px",
+ "child letter-spacing should be 10px before changing");
+ test.childCS = childCS;
+}
+
+// For all of these transitions, the transition for margin-left should
+// have a duration of 8s, and the default timing function (ease) and
+// delay (0).
+// This is because we're implementing the proposal in
+// http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
+var number_tests = [
+ { style: "transition: 4s margin, 8s margin-left" },
+ { style: "transition: 4s margin-left, 8s margin" },
+ { style: "transition-property: margin-left; " +
+ "transition-duration: 8s, 2s" },
+ { style: "transition-property: margin-left, margin-left; " +
+ "transition-duration: 2s, 8s" },
+ { style: "transition-property: margin-left, margin-left, margin-left; " +
+ "transition-duration: 8s, 2s" },
+ { style: "transition-property: margin-left; " +
+ "transition-duration: 8s, 16s" },
+ { style: "transition-property: margin-left, margin-left; " +
+ "transition-duration: 16s, 8s" },
+ { style: "transition-property: margin-left, margin-left, margin-left; " +
+ "transition-duration: 8s, 16s" },
+ { style: "transition-property: text-indent,word-spacing,margin-left; " +
+ "transition-duration: 8s; " +
+ "transition-delay: 0, 8s" },
+ { style: "transition-property: text-indent,word-spacing,margin-left; " +
+ "transition-duration: 8s, 16s; " +
+ "transition-delay: 8s, 8s, 0, 8s, 8s, 8s" },
+];
+
+for (let i in number_tests) {
+ let test = number_tests[i];
+ let p = document.createElement("p");
+ p.setAttribute("style", test.style);
+ let t = document.createTextNode(test.style);
+ p.appendChild(t);
+ p.style.marginLeft = "100px";
+ div.appendChild(p);
+ is(getComputedStyle(p, "").marginLeft, "100px",
+ "should be 100px before changing value");
+ test.node = p;
+}
+
+// Test transitions that are also from-display:none, to-display:none, and
+// display:none throughout.
+var from_none_test, to_none_test, always_none_test;
+function make_display_test(initially_none, text)
+{
+ let p = document.createElement("p");
+ p.appendChild(document.createTextNode(text));
+ p.style.textIndent = "0px";
+ p.style.transition = "8s text-indent ease-in-out";
+ if (initially_none)
+ p.style.display = "none";
+ div.appendChild(p);
+ return p;
+}
+from_none_test = make_display_test(true, "transition from display:none");
+to_none_test = make_display_test(false, "transition to display:none");
+always_none_test = make_display_test(true, "transition always display:none");
+var display_tests = [ from_none_test, to_none_test, always_none_test ];
+
+// Test transitions on pseudo-elements
+var before_test, after_test;
+function make_pseudo_elem_test(pseudo)
+{
+ let p = document.createElement("p");
+ p.className = pseudo;
+ div.appendChild(p);
+ return {"pseudo": pseudo, element: p};
+}
+before_test = make_pseudo_elem_test("before");
+after_test = make_pseudo_elem_test("after");
+var pseudo_element_tests = [ before_test, after_test ];
+
+// FIXME (Bug 522599): Test a transition that reverses partway through.
+
+var lateref = make_reference_p();
+var laterefcs = getComputedStyle(lateref, "");
+
+// flush style changes
+var x = getComputedStyle(div, "").width;
+
+// Start our timer as close as possible to when we start the first
+// transition.
+// Do not use setInterval because once it gets off in time, it stays off.
+for (let i = 1; i <= 8; ++i) {
+ setTimeout(process_future_calls, i * 1000, i);
+}
+gStartTime1 = Date.now(); // set before any transitions have started
+
+// Start all the transitions.
+earlyref.style.textIndent = "1000px";
+for (let test in tftests) {
+ let p = tftests[test][0];
+ p.style.textIndent = "100px";
+}
+for (let test in interrupt_tests) {
+ let p = interrupt_tests[test][0];
+ p.style.textIndent = "100px";
+}
+for (let d in delay_tests) {
+ let p = delay_tests[d];
+ p.style.marginLeft = "100px";
+}
+for (let d in delay_zero_tests) {
+ let p = delay_zero_tests[d];
+ p.style.marginLeft = "100px";
+}
+reset_test.style.marginLeft = "100px";
+reset_test_reference.style.marginLeft = "100px";
+for (let i in descendant_tests) {
+ let test = descendant_tests[i];
+ test.parentNode.style.textIndent = "150px";
+ test.parentNode.style.letterSpacing = "5px";
+}
+for (let i in number_tests) {
+ let test = number_tests[i];
+ test.node.style.marginLeft = "50px";
+}
+from_none_test.style.textIndent = "100px";
+from_none_test.style.display = "";
+to_none_test.style.textIndent = "100px";
+to_none_test.style.display = "none";
+always_none_test.style.textIndent = "100px";
+for (let i in pseudo_element_tests) {
+ let test = pseudo_element_tests[i];
+ test.element.classList.add("started");
+}
+lateref.style.textIndent = "1000px";
+
+// flush style changes
+x = getComputedStyle(div, "").width;
+
+gStartTime2 = Date.now(); // set after all transitions have started
+gCurrentTime = gStartTime2;
+
+/**
+ * Assert that a transition whose timing function yields the bezier
+ * |func|, running from |start_time| to |end_time| (both in seconds
+ * relative to when the transitions were started) should have produced
+ * computed value |cval| given that the transition was from
+ * |start_value| to |end_value| (both numbers in CSS pixels).
+ */
+function check_transition_value(func, start_time, end_time,
+ start_value, end_value, cval, desc,
+ xfail)
+{
+ /**
+ * Compute the value at a given time |elapsed|, by normalizing the
+ * input to the timing function using start_time and end_time and
+ * then turning the output into a value using start_value and
+ * end_value.
+ *
+ * The |error_direction| argument should be either -1, 0, or 1,
+ * suggesting adding on a little bit of error, to allow for the
+ * cubic-bezier calculation being an approximation. The amount of
+ * error is proportional to the slope of the timing function, since
+ * the error is added to the *input* of the timing function (after
+ * normalization to 0-1 based on start_time and end_time).
+ */
+ function value_at(elapsed, error_direction) {
+ var time_portion = (elapsed - start_time) / (end_time - start_time);
+ if (time_portion < 0)
+ time_portion = 0;
+ else if (time_portion > 1)
+ time_portion = 1;
+ // Assume a small error since bezier computation can be off slightly.
+ // (This test's computation is probably more accurate than Mozilla's.)
+ var value_portion = func(time_portion + error_direction * 0.0005);
+ if (value_portion < 0)
+ value_portion = 0;
+ else if (value_portion > 1)
+ value_portion = 1;
+ var value = (1 - value_portion) * start_value + value_portion * end_value;
+ if (start_value > end_value)
+ error_direction = -error_direction;
+ // Computed values get rounded to 1/60th of a pixel.
+ return value + error_direction * 0.02;
+ }
+
+ var time_range; // in seconds
+ var uns_range; // |range| before being sorted (so errors give it
+ // in the original order
+ if (!gSetupComplete) {
+ // No timers involved
+ time_range = [0, 0];
+ if (start_time < 0) {
+ uns_range = [ value_at(0, -1), value_at(0, 1) ];
+ } else {
+ var val = value_at(0, 0);
+ uns_range = [val, val];
+ }
+ } else {
+ time_range = [ px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC,
+ px_to_num(laterefcs.textIndent) / REF_PX_PER_SEC ];
+ // seconds
+ uns_range = [ value_at(time_range[0], -1),
+ value_at(time_range[1], 1) ];
+ }
+ var range = uns_range.concat(). /* concat to clone array */
+ sort(function compareNumbers(a,b) { return a - b; });
+ var actual = px_to_num(cval);
+
+ var fn = ok;
+ if (xfail && xfail(range))
+ fn = todo;
+
+ fn(range[0] <= actual && actual <= range[1],
+ desc + ": computed value " + cval + " should be between " +
+ uns_range[0].toFixed(6) + "px and " + uns_range[1].toFixed(6) +
+ "px at time between " + time_range[0] + "s and " + time_range[1] + "s.");
+}
+
+function check_ref_range()
+{
+ // This is the only test where we compare the progress of the
+ // transitions to an actual time; we need considerable tolerance at
+ // the low end (we are using half a second).
+ var expected_range = [ (gCurrentTime - gStartTime2 - 40) / 16,
+ (Date.now() - gStartTime1 + 20) / 16 ];
+ if (expected_range[0] > 1000) {
+ expected_range[0] = 1000;
+ }
+ if (expected_range[1] > 1000) {
+ expected_range[1] = 1000;
+ }
+ function check(desc, value) {
+ // The timing on the unit test VMs is not reliable, so make this
+ // test report PASS when it succeeds and TODO when it fails.
+ var passed = expected_range[0] <= value && value <= expected_range[1];
+ (passed ? ok : todo)(passed,
+ desc + ": computed value " + value + "px should be between " +
+ expected_range[0].toFixed(6) + "px and " +
+ expected_range[1].toFixed(6) + "px at time between " +
+ expected_range[0]/REF_PX_PER_SEC + "s and " +
+ expected_range[1]/REF_PX_PER_SEC + "s.");
+ }
+ check("early reference", px_to_num(earlyrefcs.textIndent));
+ check("late reference", px_to_num(laterefcs.textIndent));
+}
+
+for (let i = 1; i <= 8; ++i) {
+ add_future_call(i, check_ref_range);
+}
+
+function check_tf_test()
+{
+ for (var test in tftests) {
+ var p = tftests[test][0];
+ var tf = tftests[test][1];
+
+ check_transition_value(timingFunctions[tf], 0, 8, 0, 100,
+ getComputedStyle(p, "").textIndent,
+ "timing function test for timing function " + tf);
+
+ }
+
+ check_interrupt_tests();
+}
+
+check_tf_test();
+add_future_call(2, check_tf_test);
+add_future_call(4, check_tf_test);
+add_future_call(6, check_tf_test);
+add_future_call(8, check_tf_test);
+
+function check_interrupt_tests()
+{
+ for (let test in interrupt_tests) {
+ var p = interrupt_tests[test][0];
+ var itime = interrupt_tests[test][1];
+
+ check_transition_value(timingFunctions["cubic-bezier(0, 1, 1, 0)"],
+ 0, 8, 0, 100,
+ getComputedStyle(p, "").textIndent,
+ "interrupt " +
+ (p.parentNode == div ? "" : "on parent ") +
+ "test for time " + itime + "s");
+ }
+}
+
+// check_interrupt_tests is called from check_tf_test and from
+// where we reset the interrupts
+
+function check_delay_test(time)
+{
+ let tf = timingFunctions["ease-out"];
+ for (let d in delay_tests) {
+ let p = delay_tests[d];
+
+ check_transition_value(tf, Number(d), Number(d) + 4, 0, 100,
+ getComputedStyle(p, "").marginLeft,
+ "delay test for delay " + d + "s");
+ }
+}
+
+check_delay_test(0);
+for (let i = 1; i <= 8; ++i) {
+ add_future_call(i, check_delay_test);
+}
+
+function check_delay_zero_test(time)
+{
+ for (let d in delay_zero_tests) {
+ let p = delay_zero_tests[d];
+
+ time_range = [ px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC,
+ px_to_num(laterefcs.textIndent) / REF_PX_PER_SEC ];
+ var m = getComputedStyle(p, "").marginLeft;
+ var desc = "delay_zero test for delay " + d + "s";
+ if (time_range[0] < d && time_range[1] < d) {
+ is(m, "0px", desc);
+ } else if ((time_range[0] > d && time_range[1] > d) ||
+ (d == 0 && time == 0)) {
+ is(m, "100px", desc);
+ }
+ }
+}
+
+check_delay_zero_test(0);
+for (let i = 1; i <= 8; ++i) {
+ add_future_call(i, check_delay_zero_test);
+}
+
+function reset_reset_test(time)
+{
+ reset_test.style.marginLeft = "0px";
+}
+function check_reset_test(time)
+{
+ is(getComputedStyle(reset_test, "").marginLeft, "0px",
+ "reset test value at time " + time + "s.");
+}
+check_reset_test(0);
+// reset the reset test right now so we don't have to worry about clock skew
+// To make sure that this is valid, check that a pretty-much-identical test is
+// already transitioning.
+is(getComputedStyle(reset_test_reference, "").marginLeft, "75px",
+ "reset test reference value");
+reset_reset_test();
+check_reset_test(0);
+for (let i = 1; i <= 8; ++i) {
+ (function(j) {
+ add_future_call(j, function() { check_reset_test(j); });
+ })(i);
+}
+
+check_descendant_tests();
+add_future_call(2, check_descendant_tests);
+add_future_call(6, check_descendant_tests);
+
+function check_descendant_tests() {
+ // text-indent: transition from 50px to 150px
+ // letter-spacing: transition from 10px to 5px
+ var values = {};
+ values["text-indent"] = [ 50, 150 ];
+ values["letter-spacing"] = [ 10, 5 ];
+ let tf = timingFunctions.ease;
+
+ var time = px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC;
+
+ for (let i in descendant_tests) {
+ let test = descendant_tests[i];
+
+ /* ti=text-indent, ls=letter-spacing */
+ var child_ti_duration = 0;
+ var child_ls_duration = 0;
+ var child_ti_delay = 0;
+ var child_ls_delay = 0;
+
+ if (test.parent_transition != "") {
+ var props = test.parent_transition.split(" ");
+ var duration = parseInt(props[0]);
+ var delay = (props.length > 2) ? parseInt(props[2]) : 0;
+ var property = props[1];
+ if (property == "text-indent") {
+ child_ti_duration = duration;
+ child_ti_delay = delay;
+ } else if (property == "letter-spacing") {
+ child_ls_duration = duration;
+ child_ls_delay = delay;
+ } else {
+ ok(false, "fix this test (unexpected transition-property " +
+ property + " on parent)");
+ }
+ }
+
+ if (test.child_transition != "") {
+ var props = test.child_transition.split(" ");
+ var duration = parseInt(props[0]);
+ var delay = (props.length > 2) ? parseInt(props[2]) : 0;
+ var property = props[1];
+ if (property != "text-indent" && property != "letter-spacing" &&
+ property != "all") {
+ ok(false, "fix this test (unexpected transition-property " +
+ property + " on child)");
+ }
+
+ // Override the parent's transition with the child's as long
+ // as the child transition is still running.
+ if (property != "letter-spacing" && duration + delay > time) {
+ child_ti_duration = duration;
+ child_ti_delay = delay;
+ }
+ if (property != "text-indent" && duration + delay > time) {
+ child_ls_duration = duration;
+ child_ls_delay = delay;
+ }
+ }
+
+ var time_portions = {
+ "text-indent":
+ { duration: child_ti_duration, delay: child_ti_delay },
+ "letter-spacing":
+ { duration: child_ls_duration, delay: child_ls_delay },
+ };
+
+ for (var prop in {"text-indent": true, "letter-spacing": true}) {
+ var time_portion = time_portions[prop];
+
+ if (time_portion.duration == 0) {
+ time_portion.duration = 0.01;
+ time_portion.delay = -1;
+ }
+
+ check_transition_value(tf, time_portion.delay,
+ time_portion.delay + time_portion.duration,
+ values[prop][0], values[prop][1],
+ test.childCS.getPropertyValue(prop),
+ `descendant test #${Number(i)+1}, property ${prop}`);
+ }
+ }
+}
+
+function check_number_tests()
+{
+ let tf = timingFunctions.ease;
+ for (let d in number_tests) {
+ let test = number_tests[d];
+ let p = test.node;
+
+ check_transition_value(tf, 0, 8, 100, 50,
+ getComputedStyle(p, "").marginLeft,
+ "number of transitions test for style " +
+ test.style);
+ }
+}
+
+check_number_tests(0);
+add_future_call(2, check_number_tests);
+add_future_call(4, check_number_tests);
+add_future_call(6, check_number_tests);
+add_future_call(8, check_number_tests);
+
+function check_display_tests(time)
+{
+ for (let i in display_tests) {
+ let p = display_tests[i];
+
+ // There is no transition if the old or new style is display:none, so
+ // the computed value is always the end value.
+ var computedValue = getComputedStyle(p, "").textIndent;
+ is(computedValue, "100px",
+ "display test for test with " + p.childNodes[0].data +
+ ": computed value " + computedValue + " should be 100px.");
+ }
+}
+
+check_display_tests(0);
+add_future_call(2, function() { check_display_tests(2); });
+add_future_call(4, function() { check_display_tests(4); });
+add_future_call(6, function() { check_display_tests(6); });
+add_future_call(8, function() { check_display_tests(8); });
+
+function check_pseudo_element_tests(time)
+{
+ let tf = timingFunctions["ease-in-out"];
+ for (let i in pseudo_element_tests) {
+ let test = pseudo_element_tests[i];
+
+ check_transition_value(tf, 0, 8, 0, 100,
+ getComputedStyle(test.element, "").width,
+ "::"+test.pseudo+" test");
+ check_transition_value(tf, 0, 8, 0, 100,
+ getComputedStyle(test.element,
+ "::"+test.pseudo).textIndent,
+ "::"+test.pseudo+" indent test");
+ }
+}
+check_pseudo_element_tests(0);
+add_future_call(2, function() { check_pseudo_element_tests(2); });
+add_future_call(4, function() { check_pseudo_element_tests(4); });
+add_future_call(6, function() { check_pseudo_element_tests(6); });
+add_future_call(8, function() { check_pseudo_element_tests(8); });
+
+gSetupComplete = true;
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_and_reframes.html b/layout/style/test/test_transitions_and_reframes.html
new file mode 100644
index 0000000000..dfc16e54d1
--- /dev/null
+++ b/layout/style/test/test_transitions_and_reframes.html
@@ -0,0 +1,298 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=625289
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 625289</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ :root,
+ #e1, #e2 > div,
+ #b1::before, #b2 > div::before,
+ #a1::after, #a2 > div::after {
+ margin-left: 0;
+ }
+ :root.t,
+ #e1.t, #e2.t > div,
+ #b1.t::before, #b2.t > div::before,
+ #a1.t::after, #a2.t > div::after {
+ transition: margin-left linear 1s;
+ }
+ #b1::before, #b2 > div::before,
+ #a1::after, #a2 > div::after {
+ content: "x";
+ display: block;
+ }
+ :root.m,
+ #e1.m, #e2.m > div,
+ #b1.m::before, #b2.m > div::before,
+ #a1.m::after, #a2.m > div::after {
+ margin-left: 100px;
+ }
+ .o { overflow: hidden }
+ .n { display: none }
+
+ #fline { color: blue; font-size: 20px; width: 800px; }
+ #fline::first-line { color: yellow }
+ #fline.narrow { width: 50px }
+ #fline i { transition: color linear 1s }
+
+ #flexboxtest #flex { display: flex; flex-direction: column }
+ #flexboxtest #flextransition { color: blue; transition: color 5s linear }
+
+ #flexboxtest #flexkid[newstyle] { resize: both }
+ #flexboxtest #flextransition[newstyle] { color: yellow }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=625289">Mozilla Bug 625289</a>
+<div id="container"></div>
+<div id="fline">
+ This text has an <i>i element</i> in it.
+</div>
+<div id="flexboxtest">
+ <div id="flex">
+ hello
+ <span id="flexkid">this appears</span>
+ hello
+ <div id="flextransition">color transition</div>
+ </div>
+</div>
+<pre id="test">
+<script>
+"use strict";
+
+function advance_clock(milliseconds) {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
+}
+
+var container = document.getElementById("container");
+
+function make_elements(idName, child) {
+ var e = document.createElement("div");
+ e.setAttribute("id", idName);
+ if (child) {
+ e.appendChild(document.createElement("div"));
+ }
+ container.appendChild(e);
+ return e;
+}
+
+function assert_margin_at_quarter(element, pseudo, passes)
+{
+ var desc;
+ var useParent = false;
+ if (element == document.documentElement) {
+ desc = "root element";
+ } else if (element.id) {
+ desc = "element " + element.id;
+ } else {
+ desc = "child of element " + element.parentNode.id;
+ useParent = true;
+ }
+ var classes = (useParent ? element.parentNode : element).getAttribute("class");
+ if (classes) {
+ desc += " (classes: " + classes + ")";
+ }
+ if (pseudo) {
+ desc += " " + pseudo + " pseudo-element";
+ }
+ (passes ? is : todo_is)(getComputedStyle(element, pseudo).marginLeft, "25px",
+ "margin of " + desc);
+}
+
+function do_test(test)
+{
+ var expected_props = [ "element", "test_child", "pseudo", "passes",
+ "dynamic_change_transition", "start_from_none" ];
+ for (var propidx in expected_props) {
+ if (! expected_props[propidx] in test) {
+ ok(false, "expected " + expected_props[propidx] + " on test object");
+ }
+ }
+
+ var e;
+ if (typeof(test.element) == "string") {
+ e = make_elements(test.element, test.test_child);
+ } else {
+ if (test.test_child) {
+ ok(false, "test_child unexpected");
+ }
+ e = test.element;
+ }
+
+ var target = test.test_child ? e.firstChild : e;
+
+ if (!test.dynamic_change_transition) {
+ e.classList.add("t");
+ }
+ if (test.start_from_none) {
+ e.classList.add("n");
+ }
+
+ advance_clock(100);
+ e.classList.add("m");
+ e.classList.add("o");
+ if (test.dynamic_change_transition) {
+ e.classList.add("t");
+ }
+ if (test.start_from_none) {
+ e.classList.remove("n");
+ }
+ advance_clock(0);
+ advance_clock(250);
+ assert_margin_at_quarter(target, test.pseudo, test.passes);
+ if (typeof(test.element) == "string") {
+ e.remove();
+ } else {
+ target.style.transition = "";
+ target.removeAttribute("class");
+ }
+}
+
+advance_clock(0);
+
+var tests = [
+ { element:"e1", test_child:false, pseudo:"", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"e2", test_child:true, pseudo:"", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"b1", test_child:false, pseudo:"::before", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"b2", test_child:true, pseudo:"::before", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"a1", test_child:false, pseudo:"::after", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:"a2", test_child:true, pseudo:"::after", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ { element:document.documentElement, test_child:false, pseudo:"", passes:true,
+ dynamic_change_transition:false, start_from_none:false },
+ // Recheck with a dynamic change in transition
+ { element:"e1", test_child:false, pseudo:"", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"e2", test_child:true, pseudo:"", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"b1", test_child:false, pseudo:"::before", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"b2", test_child:true, pseudo:"::before", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"a1", test_child:false, pseudo:"::after", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:"a2", test_child:true, pseudo:"::after", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ { element:document.documentElement, test_child:false, pseudo:"", passes:true,
+ dynamic_change_transition:true, start_from_none:false },
+ // Recheck starting from display:none. Note that these tests all fail,
+ // although we could get *some* of them to pass by calling
+ // RestyleManager::TryInitiatingTransition from
+ // ElementRestyler::RestyleUndisplayedChildren.
+ { element:"e1", test_child:false, pseudo:"", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"e2", test_child:true, pseudo:"", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"b1", test_child:false, pseudo:"::before", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"b2", test_child:true, pseudo:"::before", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"a1", test_child:false, pseudo:"::after", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:"a2", test_child:true, pseudo:"::after", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ { element:document.documentElement, test_child:false, pseudo:"", passes:false,
+ dynamic_change_transition:false, start_from_none:true },
+ // Recheck with a dynamic change in transition and starting from display:none
+ { element:"e1", test_child:false, pseudo:"", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"e2", test_child:true, pseudo:"", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"b1", test_child:false, pseudo:"::before", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"b2", test_child:true, pseudo:"::before", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"a1", test_child:false, pseudo:"::after", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:"a2", test_child:true, pseudo:"::after", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+ { element:document.documentElement, test_child:false, pseudo:"", passes:false,
+ dynamic_change_transition:true, start_from_none:true },
+];
+
+for (var testidx in tests) {
+ do_test(tests[testidx]);
+}
+
+var fline = document.getElementById("fline");
+var fline_i_cs = getComputedStyle(fline.firstElementChild, "");
+// Note that the color in the ::first-line is never used in the test
+// since we avoid reporting ::first-line data in getComputedStyle.
+// However, if we were to start a transition (incorrectly), that would
+// show up in getComputedStyle.
+var COLOR_IN_LATER_LINES = "rgb(0, 0, 255)";
+
+function do_firstline_test(test) {
+ if (test.widening) {
+ fline.classList.add("narrow");
+ is (fline_i_cs.color, COLOR_IN_LATER_LINES, "initial color");
+ } else {
+ is (fline_i_cs.color, COLOR_IN_LATER_LINES, "initial color");
+ }
+
+ if (test.widening) {
+ fline.classList.remove("narrow");
+ } else {
+ fline.classList.add("narrow");
+ }
+
+ if (test.set_overflow) {
+ fline.classList.add("o");
+ }
+
+ advance_clock(100);
+
+ if (test.widening) {
+ is (fline_i_cs.color, COLOR_IN_LATER_LINES,
+ "::first-line changes don't trigger transitions");
+ } else {
+ is (fline_i_cs.color, COLOR_IN_LATER_LINES,
+ "::first-line changes don't trigger transitions");
+ }
+
+ fline.removeAttribute("class");
+}
+
+var firstline_tests = [
+ { widening: true, set_overflow: false },
+ { widening: false, set_overflow: false },
+ { widening: true, set_overflow: true },
+ { widening: false, set_overflow: true },
+];
+
+for (var firstline_test_idx in firstline_tests) {
+ do_firstline_test(firstline_tests[firstline_test_idx]);
+}
+
+function do_flexbox_reframe_test()
+{
+ var flextransition = document.getElementById("flextransition");
+ var cs = getComputedStyle(flextransition, "");
+ cs.backgroundColor;
+ flextransition.setAttribute("newstyle", "");
+ document.getElementById("flexkid").setAttribute("newstyle", "");
+ is(cs.color, "rgb(0, 0, 255)",
+ "color at start of wrapped flexbox transition");
+ advance_clock(1000);
+ is(cs.color, "rgb(51, 51, 204)",
+ "color one second in to wrapped flexbox transition");
+}
+
+do_flexbox_reframe_test();
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_and_restyles.html b/layout/style/test/test_transitions_and_restyles.html
new file mode 100644
index 0000000000..35fc608c20
--- /dev/null
+++ b/layout/style/test/test_transitions_and_restyles.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1030993
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1030993</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #display {
+ background: blue; height: 10px; width: 0; color: black;
+ transition: width linear 1s, color linear 1s;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1030993">Mozilla Bug 1030993</a>
+<p id="display"></p>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+
+/** Test for Bug 1030993 **/
+
+function advance_clock(milliseconds) {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
+}
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+advance_clock(0);
+cs.width; // flush
+p.style.width = "1000px"; // initiate transition
+is(cs.width, "0px", "transition at 0ms"); // flush (and test)
+advance_clock(100);
+is(cs.width, "100px", "transition at 100ms"); // flush
+// restyle *and* trigger new transitions
+p.style.color = "blue";
+// flush again, at the same timestamp
+is(cs.width, "100px", "transition at 100ms, after restyle");
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_and_zoom.html b/layout/style/test/test_transitions_and_zoom.html
new file mode 100644
index 0000000000..e95581be32
--- /dev/null
+++ b/layout/style/test/test_transitions_and_zoom.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=583219
+-->
+<head>
+ <title>Test for Bug 583219</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display {
+ transition: margin-left 1s linear;
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=583219">Mozilla Bug 583219</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 583219 **/
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+cs.marginLeft;
+
+p.addEventListener("transitionend", TransitionEndHandler);
+p.style.marginLeft = "100px";
+cs.marginLeft;
+
+SpecialPowers.setFullZoom(window, 2.0)
+
+SimpleTest.waitForExplicitFinish();
+
+function TransitionEndHandler(event) {
+ ok(true, "transition has completed");
+ is(event.propertyName, "margin-left", "event.propertyName");
+ is(cs.marginLeft, "100px", "value of margin-left");
+ SpecialPowers.setFullZoom(window, 1.0)
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_at_start.html b/layout/style/test/test_transitions_at_start.html
new file mode 100644
index 0000000000..bad36e7673
--- /dev/null
+++ b/layout/style/test/test_transitions_at_start.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1380133
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for transition value at start (Bug 1380133)</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="stylesheet" href="/resources/testharness.css">
+ <style>
+ a {
+ color: red;
+ transition: 100s color;
+ }
+ a.active {
+ color: blue;
+ }
+ </style>
+</head>
+<body>
+<a id=anchor><span id=span>Test</span></a>
+</body>
+<script>
+'use strict';
+
+const anchor = document.getElementById('anchor');
+const span = document.getElementById('span');
+
+test(() => {
+ anchor.getBoundingClientRect();
+ anchor.classList.add('active');
+ assert_equals(getComputedStyle(span).color, 'rgb(255, 0, 0)',
+ 'The child of a transitioning element should inherit its'
+ + ' parent\'s transition start value');
+}, 'Transition start value should be inherited');
+</script>
+</html>
diff --git a/layout/style/test/test_transitions_bug537151.html b/layout/style/test/test_transitions_bug537151.html
new file mode 100644
index 0000000000..8d3b84a5fc
--- /dev/null
+++ b/layout/style/test/test_transitions_bug537151.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=537151
+-->
+<head>
+ <title>Test for Bug 537151</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display {
+ transition: margin-left 200ms;
+ }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=537151">Mozilla Bug 537151</a>
+<p id="display">Paragraph</p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 537151 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var p = document.getElementById("display");
+p.addEventListener("transitionend", listener);
+var ignored = getComputedStyle(p, "").marginLeft;
+p.style.marginLeft = "150px";
+
+var event_count = 0;
+function listener(event)
+{
+ ++event_count;
+ setTimeout(finish, 400);
+ p.style.color = "blue";
+}
+
+function finish()
+{
+ is(event_count, 1, "should have gotten only 1 transitionend event");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_cancel_near_end.html b/layout/style/test/test_transitions_cancel_near_end.html
new file mode 100644
index 0000000000..496d95e6a1
--- /dev/null
+++ b/layout/style/test/test_transitions_cancel_near_end.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=613888
+-->
+<head>
+ <title>Test for Bug 613888</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ #animated-elements-container > span {
+ color: black;
+ background: white;
+ transition:
+ color 10s ease-out,
+ background 1s ease-out;
+ }
+ #animated-elements-container > span.another {
+ color: white;
+ background: black;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=613888">Mozilla Bug 613888</a>
+<pre id="animated-elements-container">
+ <span should-restyle="true">canceled on a half of the animation</span>
+ <span should-restyle="true">canceled too fast, and restyled on transitionend</span>
+ <span>canceled too fast, but not restyled on transitionend</span>
+</pre>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 613888: that we don't cancel transitions when they're
+ about to end (current interpolated value rounds to ending value) and
+ they get an unrelated style change. **/
+
+var count_remaining = 6;
+
+window.addEventListener('load', function() {
+ var cases = Array.from(document.querySelectorAll('#animated-elements-container > span'));
+
+ cases.forEach(function(aTarget) {
+ aTarget.addEventListener('transitionend', function(aEvent) {
+ if (aTarget.hasAttribute('should-restyle'))
+ aTarget.style.outline = '1px solid';
+ var attr = 'transitionend-' + aEvent.propertyName;
+ if (aTarget.hasAttribute(attr)) {
+ // It's possible, given bad timers, that we might get a
+ // transition that completed before we reversed it, which could
+ // lead to two transitionend events for the same thing. We
+ // don't want to decrement count_remaining in this case.
+ return;
+ }
+ aTarget.setAttribute(attr, "true");
+ if (--count_remaining == 0) {
+ cases.forEach(function(aCase, aIndex) {
+ ok(aCase.hasAttribute('transitionend-color'),
+ "transitionend for color was fired for case "+aIndex);
+ ok(aCase.hasAttribute('transitionend-background-color'),
+ "transitionend for background-color was fired for case "+aIndex);
+ });
+ SimpleTest.finish();
+ }
+ });
+ });
+
+ cases.forEach(aCase => aCase.className = 'another' );
+
+ window.setTimeout(() => cases[0].className = '', 500);
+ window.setTimeout(() => cases[1].className = cases[2].className = '', 250);
+
+});
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_computed_value_combinations.html b/layout/style/test/test_transitions_computed_value_combinations.html
new file mode 100644
index 0000000000..3dfad41e58
--- /dev/null
+++ b/layout/style/test/test_transitions_computed_value_combinations.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 435441 **/
+
+
+/**
+ * I want to test a reasonable number of combinations rather than all of
+ * them, but I also want the test results to be reproducable. So use a
+ * simple random number generator with my own seed. See
+ * http://en.wikipedia.org/wiki/Linear_congruential_generator
+ * (Using the numbers from Numerical Recipes.)
+ */
+var rand_state = 1938266273; // a randomly (once) generated number in [0,2^32)
+var all_integers = true;
+function myrand()
+{
+ rand_state = ((rand_state * 1664525) + 1013904223) % 0x100000000;
+ all_integers = all_integers &&
+ Math.ceil(rand_state) == Math.floor(rand_state);
+ return rand_state / 0x100000000; // return value in [0,1)
+}
+
+// We want to test a bunch of values for each property.
+// Each of these values will also have a "computed" property filled in
+// below, so that we ensure it always computes to the same value.
+var values = {
+ "transition-duration":
+ [
+ { lone: true, specified: "initial" },
+ { lone: false, specified: "2s" },
+ { lone: false, specified: "0s" },
+ { lone: false, specified: "430ms" },
+ { lone: false, specified: "1s" },
+ ],
+ "transition-property":
+ [
+ { lone: true, specified: "initial" },
+ { lone: true, specified: "none" },
+ { lone: true, specified: "all" },
+ { lone: false, specified: "color" },
+ { lone: false, specified: "border-spacing" },
+ // Make sure to test the "unknown property" case.
+ { lone: false, specified: "unsupported-property" },
+ { lone: false, specified: "-other-unsupported-property" },
+ ],
+ "transition-timing-function":
+ [
+ { lone: true, specified: "initial" },
+ { lone: false, specified: "linear" },
+ { lone: false, specified: "ease" },
+ { lone: false, specified: "ease-in-out" },
+ { lone: false, specified: "cubic-bezier(0, 0, 0.63, 1.00)" },
+ ],
+ "transition-delay":
+ [
+ { lone: true, specified: "initial" },
+ { lone: false, specified: "2s" },
+ { lone: false, specified: "0s" },
+ { lone: false, specified: "430ms" },
+ { lone: false, specified: "-1s" },
+ ],
+};
+
+var elt = document.getElementById("content");
+var cs = getComputedStyle(elt, "");
+
+// Add the "computed" property to all of the above values.
+for (var prop in values) {
+ var valueset = values[prop];
+ for (var index in valueset) {
+ var item = valueset[index];
+ elt.style.setProperty(prop, item.specified, "");
+ item.computed = cs.getPropertyValue(prop);
+ elt.style.removeProperty(prop);
+ isnot(item.computed, "", "computed value must not be empty");
+ if (index != 0) {
+ isnot(item.computed, valueset[index-1].computed,
+ "computed value must not be the same as the last one");
+ }
+ }
+}
+
+var child = document.createElement("div");
+elt.appendChild(child);
+var child_cs = getComputedStyle(child, "");
+
+// Now test a hundred random combinations of values on the parent and
+// child.
+for (var iteration = 0; iteration < 100; ++iteration) {
+ // Figure out values on the parent.
+ var parent_vals = {};
+ for (var prop in values) {
+ var valueset = values[prop];
+ var list_length = Math.ceil(Math.pow(myrand(), 2) * 6);
+ // 41% chance of length 1
+ var specified = [];
+ var computed = [];
+ for (var i = 0; i < list_length; ++i) {
+ var index;
+ do {
+ index = Math.floor(myrand() * valueset.length);
+ } while (list_length != 1 && valueset[index].lone);
+ specified.push(valueset[index].specified);
+ computed.push(valueset[index].computed);
+ }
+ parent_vals[prop] = { specified: specified.join(", "),
+ computed: computed.join(", ") };
+ elt.style.setProperty(prop, parent_vals[prop].specified, "");
+ }
+
+ // Figure out values on the child.
+ var child_vals = {};
+ for (var prop in values) {
+ var valueset = values[prop];
+ // Use 0 as a magic value for "inherit".
+ var list_length = Math.floor(Math.pow(myrand(), 1.5) * 7);
+ // 27% chance of inherit
+ // 16% chance of length 1
+ if (list_length == 0) {
+ child_vals[prop] = { specified: "inherit",
+ computed: parent_vals[prop].computed };
+ } else {
+ var specified = [];
+ var computed = [];
+ for (var i = 0; i < list_length; ++i) {
+ var index;
+ do {
+ index = Math.floor(myrand() * valueset.length);
+ } while (list_length != 1 && valueset[index].lone);
+ specified.push(valueset[index].specified);
+ computed.push(valueset[index].computed);
+ }
+ child_vals[prop] = { specified: specified.join(", "),
+ computed: computed.join(", ") };
+ }
+ child.style.setProperty(prop, child_vals[prop].specified, "");
+ }
+
+ // Test computed values
+ for (var prop in values) {
+ is(cs.getPropertyValue(prop), parent_vals[prop].computed,
+ "computed value of " + prop + ": " + parent_vals[prop].specified +
+ " on parent.");
+ is(child_cs.getPropertyValue(prop), child_vals[prop].computed,
+ "computed value of " + prop + ": " + child_vals[prop].specified +
+ " on child.");
+ }
+}
+
+ok(all_integers, "pseudo-random number generator kept its numbers " +
+ "as integers throughout run");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_computed_values.html b/layout/style/test/test_transitions_computed_values.html
new file mode 100644
index 0000000000..7b350de6b2
--- /dev/null
+++ b/layout/style/test/test_transitions_computed_values.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 435441 **/
+
+
+/*
+ * test that when transition properties are inherited, the length of the
+ * computed value stays the same
+ */
+
+var p = document.getElementById("content");
+var c = document.createElement("div");
+p.appendChild(c);
+var cs = getComputedStyle(c, "");
+
+p.style.transitionProperty = "margin-left, margin-right";
+c.style.transitionProperty = "inherit";
+is(cs.transitionProperty, "margin-left, margin-right",
+ "computed style match with no other properties");
+c.style.transitionDuration = "5s";
+is(cs.transitionProperty, "margin-left, margin-right",
+ "computed style match with shorter property");
+is(cs.transitionDuration, "5s",
+ "shorter property not extended");
+c.style.transitionDuration = "5s, 4s, 3s, 2000ms";
+is(cs.transitionProperty, "margin-left, margin-right",
+ "computed style match with longer property");
+is(cs.transitionDuration, "5s, 4s, 3s, 2s",
+ "longer property computed correctly");
+p.style.transitionProperty = "";
+c.style.transitionProperty = "";
+c.style.transitionDuration = "";
+
+// and repeat the above set of tests with property and duration swapped
+p.style.transitionDuration = "5s, 4s";
+c.style.transitionDuration = "inherit";
+is(cs.transitionDuration, "5s, 4s",
+ "computed style match with no other properties");
+c.style.transitionProperty = "margin-left";
+is(cs.transitionDuration, "5s, 4s",
+ "computed style match with shorter property");
+is(cs.transitionProperty, "margin-left",
+ "shorter property not extended");
+c.style.transitionProperty =
+ "margin-left, margin-right, margin-top, margin-bottom";
+is(cs.transitionDuration, "5s, 4s",
+ "computed style match with longer property");
+is(cs.transitionProperty,
+ "margin-left, margin-right, margin-top, margin-bottom",
+ "longer property computed correctly");
+p.style.transitionDuration = "";
+c.style.transitionDuration = "";
+c.style.transitionProperty = "";
+
+// And do the same pair of tests for animations:
+
+p.style.animationName = "bounce, roll";
+c.style.animationName = "inherit";
+is(cs.animationName, "bounce, roll",
+ "computed style match with no other properties");
+c.style.animationDuration = "5s";
+is(cs.animationName, "bounce, roll",
+ "computed style match with shorter property");
+is(cs.animationDuration, "5s",
+ "shorter property not extended");
+c.style.animationDuration = "5s, 4s, 3s, 2000ms";
+is(cs.animationName, "bounce, roll",
+ "computed style match with longer property");
+is(cs.animationDuration, "5s, 4s, 3s, 2s",
+ "longer property computed correctly");
+p.style.animationName = "";
+c.style.animationName = "";
+c.style.animationDuration = "";
+
+// and repeat the above set of tests with name and duration swapped
+p.style.animationDuration = "5s, 4s";
+c.style.animationDuration = "inherit";
+is(cs.animationDuration, "5s, 4s",
+ "computed style match with no other properties");
+c.style.animationName = "bounce";
+is(cs.animationDuration, "5s, 4s",
+ "computed style match with shorter property");
+is(cs.animationName, "bounce",
+ "shorter property not extended");
+c.style.animationName =
+ "bounce, roll, wiggle, spin";
+is(cs.animationDuration, "5s, 4s",
+ "computed style match with longer property");
+is(cs.animationName,
+ "bounce, roll, wiggle, spin",
+ "longer property computed correctly");
+p.style.animationDuration = "";
+c.style.animationDuration = "";
+c.style.animationName = "";
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_dynamic_changes.html b/layout/style/test/test_transitions_dynamic_changes.html
new file mode 100644
index 0000000000..4d49db1e3a
--- /dev/null
+++ b/layout/style/test/test_transitions_dynamic_changes.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=525530
+-->
+<head>
+ <title>Test for Bug 525530</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525530">Mozilla Bug 525530</a>
+<p id="display" style="text-indent: 100px"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 525530 **/
+
+var p = document.getElementById("display");
+var cs = getComputedStyle(p, "");
+var utils = SpecialPowers.DOMWindowUtils;
+
+p.style.transitionProperty = "all";
+p.style.transitionDuration = "4s";
+p.style.transitionDelay = "-2s";
+p.style.transitionTimingFunction = "linear";
+
+is(cs.textIndent, "100px", "initial value");
+
+p.style.textIndent = "0";
+is(cs.textIndent, "50px", "transition is halfway");
+p.style.transitionDuration = "0s";
+is(cs.textIndent, "50px", "changing duration doesn't change transitioning");
+p.style.transitionDelay = "0s";
+is(cs.textIndent, "50px", "changing delay doesn't change transitioning");
+p.style.transitionProperty = "text-indent";
+is(cs.textIndent, "50px",
+ "irrelevant change to transition property doesn't change transitioning");
+p.style.transitionProperty = "letter-spacing";
+is(cs.textIndent, "0px",
+ "relevant change to transition property does change transitioning");
+
+/** Test for Bug 522643 */
+p.style.transitionDuration = "4s";
+p.style.transitionDelay = "-2s";
+p.style.transitionProperty = "text-indent";
+p.style.textIndent = "100px";
+is(cs.textIndent, "50px", "transition is halfway");
+p.style.transitionDuration = "0s";
+p.style.transitionDelay = "0s";
+is(cs.textIndent, "50px",
+ "changing duration and delay doesn't change transitioning");
+p.style.textIndent = "0px";
+is(cs.textIndent, "0px",
+ "changing property after changing duration and delay stops transition");
+
+/** Test for Bug 1133375 */
+p.style.transitionDuration = "1s";
+p.style.transitionDelay = "-1s";
+p.style.transitionProperty = "text-indent";
+var endCount = 0;
+function incrementEndCount(event) { ++endCount; }
+p.addEventListener("transitionend", incrementEndCount);
+utils.advanceTimeAndRefresh(0);
+p.style.textIndent = "100px";
+is(cs.textIndent, "100px", "value should now be 100px");
+utils.advanceTimeAndRefresh(10);
+is(endCount, 0, "should not have started transition when combined duration less than or equal to 0");
+p.style.transitionDelay = "-2s";
+p.style.textIndent = "0";
+is(cs.textIndent, "0px", "value should now be 0px");
+utils.advanceTimeAndRefresh(10);
+is(endCount, 0, "should not have started transition when combined duration less than or equal to 0");
+utils.restoreNormalRefresh();
+p.style.textIndent = "";
+
+/** Test for bug 1144410 */
+utils.advanceTimeAndRefresh(0);
+p.style.transition = "opacity 200ms linear";
+p.style.opacity = "1";
+is(cs.opacity, "1", "bug 1144410 test - initial opacity");
+p.style.opacity = "0";
+is(cs.opacity, "1", "bug 1144410 test - opacity after starting transition");
+utils.advanceTimeAndRefresh(100);
+is(cs.opacity, "0.5", "bug 1144410 test - opacity during transition");
+utils.advanceTimeAndRefresh(200);
+is(cs.opacity, "0", "bug 1144410 test - opacity after transition");
+document.body.style.display = "none";
+is(cs.opacity, "0", "bug 1144410 test - opacity after display:none");
+p.style.opacity = "1";
+document.body.style.display = "";
+is(cs.opacity, "1", "bug 1144410 test - second transition, initial opacity");
+p.style.opacity = "0";
+is(cs.opacity, "1", "bug 1144410 test - opacity after starting second transition");
+utils.advanceTimeAndRefresh(100);
+is(cs.opacity, "0.5", "bug 1144410 test - opacity during second transition");
+utils.advanceTimeAndRefresh(200);
+is(cs.opacity, "0", "bug 1144410 test - opacity after second transition");
+utils.restoreNormalRefresh();
+p.style.opacity = "";
+p.style.transition = "";
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_events.html b/layout/style/test/test_transitions_events.html
new file mode 100644
index 0000000000..d04348bc0a
--- /dev/null
+++ b/layout/style/test/test_transitions_events.html
@@ -0,0 +1,294 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=531585
+-->
+<head>
+ <title>Test for Bug 531585 (transitionend event)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<style type="text/css">
+
+.bar { margin: 10px; }
+
+#one { transition-duration: 500ms; transition-property: all; }
+#two { transition: margin-left 1s; }
+#three { transition: margin 0.5s 0.25s; }
+
+#four, #five, #six, #seven::before, #seven::after {
+ transition: 500ms color;
+ border-color: black; /* don't derive from color */
+ column-rule-color: black; /* don't derive from color */
+ text-decoration-color: black; /* don't derive from color */
+ outline-color: black; /* don't derive from color */
+}
+
+#four {
+ /* give the reversing transition a long duration; the reversing will
+ still be quick */
+ transition-duration: 30s;
+ transition-timing-function: cubic-bezier(0, 1, 1, 0);
+}
+
+#seven::before, #seven::after {
+ content: "x";
+ transition-duration: 50ms;
+}
+#seven[foo]::before, #seven[foo]::after { color: lime; }
+
+</style>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=531585">Mozilla Bug 531585</a>
+<p id="display">
+
+<span id="one" style="color:blue"></span>
+<span id="two"></span>
+<span id="three"></span>
+<span id="four" style="color: blue"></span>
+<span id="five" style="color: blue"></span>
+<span id="six" style="color: blue"></span>
+<span id="seven" style="color: blue"></span>
+
+</p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 531585 (transitionend event) **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+var gTestCount = 0;
+function started_test() { ++gTestCount; }
+function finished_test() { if (--gTestCount == 0) { SimpleTest.finish(); } }
+
+function $(id) { return document.getElementById(id); }
+function cs(id) { return getComputedStyle($(id), ""); }
+
+var got_one_root = false;
+var got_one_target = false;
+var got_two_target = false;
+var got_three_top = false;
+var got_three_right = false;
+var got_three_bottom = false;
+var got_three_left = false;
+var got_four_root = false;
+var got_body = false;
+var did_finish_five = false;
+var did_finish_six = false;
+var got_before = false;
+var got_after = false;
+
+// Flush layout to guarantee consistent transitions.
+document.body.getBoundingClientRect();
+
+document.documentElement.addEventListener("transitionend",
+ function(event) {
+ if (event.target == $("one")) {
+ ok(!got_one_root, "transitionend on one on root");
+ is(event.propertyName, "border-right-color",
+ "propertyName for transitionend on one");
+ is(event.elapsedTime, 0.5,
+ "elapsedTime for transitionend on one");
+ is(cs("one").borderRightColor, "rgb(0, 255, 0)",
+ "computed style for transitionend on one");
+ got_one_root = true;
+ finished_test();
+ } else if (event.target == $("four")) {
+ ok(!got_four_root, "transitionend on four on root");
+ is(event.propertyName, "color",
+ "propertyName for transitionend on four");
+ // Reported time should (really?) be shortened by reversing.
+ ok(event.elapsedTime < 30,
+ "elapsedTime for transitionend on four");
+ is(cs("four").color, "rgb(0, 0, 255)",
+ "computed style for transitionend on four (end of reverse transition)");
+ got_four_root = true;
+ finished_test();
+ } else if (event.target == document.body) {
+ // A synthesized event.
+ ok(!got_body, "transitionend on body on root");
+ is(event.propertyName, "some-unknown-prop",
+ "propertyName for transitionend on body");
+ // Reported time should (really?) be shortened by reversing.
+ is(event.elapsedTime, 0.5,
+ "elapsedTime for transitionend on body");
+ got_body = true;
+ finished_test();
+ } else if (event.target == $("seven")) {
+ if (!got_before) {
+ got_before = true;
+ is(event.pseudoElement, "::before");
+ } else {
+ ok(!got_after, "transitionend on #seven::after");
+ got_after = true;
+ is(event.pseudoElement, "::after");
+ }
+ is(event.propertyName, "color");
+ is(event.isTrusted, true);
+ finished_test();
+ } else {
+ if ((event.target == $("five") && did_finish_five) ||
+ (event.target == $("six") && did_finish_six)) {
+ todo(false,
+ "it seems that transitionstart and transitionend had been " +
+ "processed in the same frame");
+ return;
+ }
+ ok(false,
+ "unexpected event on " + event.target.nodeName +
+ " element with id '" + event.target.id + "' " +
+ "elapsedTime=" + event.elapsedTime +
+ " propertyName='" + event.propertyName + "'");
+ }
+ });
+
+$("one").addEventListener("transitionend",
+ function(event) {
+ is(event.propertyName, "color", "unexpected " +
+ "property name for transitionend on one on target");
+ ok(!got_one_target,
+ "transitionend on one on target (color)");
+ got_one_target = true;
+ event.stopPropagation();
+ is(event.elapsedTime, 0.5,
+ "elapsedTime for transitionend on one");
+ is(cs("one").getPropertyValue(event.propertyName), "rgb(0, 255, 0)",
+ "computed style of " + event.propertyName + " for transitionend on one");
+ finished_test();
+ });
+
+started_test(); // color on #one
+$("one").style.color = "lime";
+
+
+$("two").addEventListener("transitionend",
+ function(event) {
+ event.stopPropagation();
+
+ ok(!got_two_target, "transitionend on two on target");
+ is(event.propertyName, "margin-left",
+ "propertyName for transitionend on two");
+ is(event.elapsedTime, 1,
+ "elapsedTime for transitionend on two");
+ is(event.bubbles, true,
+ "transitionend events should bubble");
+ is(event.cancelable, false,
+ "transitionend events should not be cancelable");
+ is(cs("two").marginLeft, "10px",
+ "computed style for transitionend on two");
+ got_two_target = true;
+ finished_test();
+ });
+
+started_test(); // #two
+$("two").className = "bar";
+
+$("three").addEventListener("transitionend",
+ function(event) {
+ event.stopPropagation();
+
+ switch (event.propertyName) {
+ case "margin-top":
+ ok(!got_three_top, "should only get margin-top once");
+ got_three_top = true;
+ break;
+ case "margin-right":
+ ok(!got_three_right, "should only get margin-right once");
+ got_three_right = true;
+ break;
+ case "margin-bottom":
+ ok(!got_three_bottom, "should only get margin-bottom once");
+ got_three_bottom = true;
+ break;
+ case "margin-left":
+ ok(!got_three_left, "should only get margin-left once");
+ got_three_left = true;
+ break;
+ default:
+ ok(false, "unexpected property name " + event.propertyName +
+ " for transitionend on three");
+ }
+ is(event.elapsedTime, 0.5,
+ "elapsedTime for transitionend on three");
+ is(cs("three").getPropertyValue(event.propertyName), "10px",
+ "computed style for transitionend on three");
+ finished_test();
+ }, true);
+
+started_test(); // margin-top on #three
+started_test(); // margin-right on #three
+started_test(); // margin-bottom on #three
+started_test(); // margin-left on #three
+$("three").className = "bar";
+
+// We reverse the transition on four, and we should only get an event
+// at the end of the second transition.
+started_test(); // #four (listener on root)
+$("four").style.color = "lime";
+
+// We cancel the transition on five by changing 'transition-property',
+// and should thus get no event.
+$("five").style.color = "lime";
+
+// We cancel the transition on six by changing 'transition-duration' and
+// then changing the value, so we should get no event.
+$("six").style.color = "lime";
+
+started_test(); // #seven::before (listener on root)
+started_test(); // #seven::after (listener on root)
+$("seven").setAttribute("foo", "bar");
+
+$("five").addEventListener("transitionstart", function() {
+ if (cs("five").color == "rgb(0, 255, 0)") {
+ // The transition has finished already.
+ did_finish_five = true;
+ }
+ $("five").style.transitionProperty = "margin-left";
+});
+
+$("six").addEventListener("transitionstart", function() {
+ if (cs("six").color == "rgb(0, 255, 0)") {
+ // The transition has finished already.
+ did_finish_six = true;
+ }
+ $("six").style.transitionDuration = "0s";
+ $("six").style.transitionDelay = "0s";
+ $("six").style.color = "blue";
+});
+
+function poll_start_reversal() {
+ if (cs("four").color != "rgb(0, 0, 255)") {
+ // The forward transition has started.
+ $("four").style.color = "blue";
+ } else {
+ // The forward transition has not started yet.
+ setTimeout(poll_start_reversal, 20);
+ }
+}
+setTimeout(poll_start_reversal, 200);
+
+// And make our own event to dispatch to the body.
+started_test(); // synthesized event to body (listener on root)
+
+var e = new TransitionEvent("transitionend",
+ {
+ bubbles: true,
+ cancelable: true,
+ propertyName: "some-unknown-prop",
+ elapsedTime: 0.5,
+ pseudoElement: "pseudo"
+ });
+is(e.bubbles, true);
+is(e.cancelable, true);
+is(e.propertyName, "some-unknown-prop");
+is(e.elapsedTime, 0.5);
+is(e.pseudoElement, "pseudo");
+is(e.isTrusted, false)
+
+document.body.dispatchEvent(e);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_per_property.html b/layout/style/test/test_transitions_per_property.html
new file mode 100644
index 0000000000..058152adfb
--- /dev/null
+++ b/layout/style/test/test_transitions_per_property.html
@@ -0,0 +1,3245 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <meta charset=utf-8>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <script type="text/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ #display > p { margin-top: 0; margin-bottom: 0; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>
+
+<!--
+ fixed-height container so percentage heights compute to different
+ (i.e., nonzero) values
+ fixed-width container so that percentages for margin-top and
+ margin-bottom are all relative to the same size container (rather than
+ one that depends on whether we're tall enough to need a scrollbar)
+
+ Use a 20px font size and line-height so that percentage line-height
+ and vertical-align doesn't accumulate rounding error.
+ -->
+<div style="height: 50px; width: 300px; font-size: 20px; line-height: 20px">
+
+<div id="display">
+</div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/* eslint no-shadow: ["error", {"allow": ["prop", "div"]}] */
+/* eslint-disable dot-notation */
+
+
+/** Test for Bug 435441 **/
+
+SimpleTest.requestLongerTimeout(2);
+SimpleTest.waitForExplicitFinish();
+
+function has_num(str)
+{
+ return !!String(str).match(/^([\d.]+)/);
+}
+
+function any_unit_to_num(str)
+{
+ return Number(String(str).match(/^([\d.]+)/)[1]);
+}
+
+var FUNC_NEGATIVE = "cubic-bezier(0.25, -2, 0.75, 1)";
+var FUNC_OVERONE = "cubic-bezier(0.25, 0, 0.75, 3)";
+
+var supported_properties = {
+ "aspect-ratio" : [ test_aspect_ratio_transition ],
+ "border-bottom-left-radius": [ test_radius_transition ],
+ "border-bottom-right-radius": [ test_radius_transition ],
+ "border-top-left-radius": [ test_radius_transition ],
+ "border-top-right-radius": [ test_radius_transition ],
+ "border-start-start-radius": [ test_radius_transition ],
+ "border-start-end-radius": [ test_radius_transition ],
+ "border-end-start-radius": [ test_radius_transition ],
+ "border-end-end-radius": [ test_radius_transition ],
+ "-moz-box-flex": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition,
+ test_float_zeroToOne_clamped ],
+ "box-shadow": [ test_shadow_transition ],
+ "column-count": [ test_pos_integer_or_auto_transition,
+ test_integer_at_least_one_clamping ],
+ "column-rule-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "column-rule-width": [ test_length_transition,
+ test_length_clamped ],
+ "column-width": [ test_length_transition,
+ test_length_clamped ],
+ "cx": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "cy": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "background-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "background-position": [ test_background_position_transition,
+ test_length_percent_pair_unclamped ],
+ "background-position-x": [ test_background_position_coord_transition,
+ test_length_transition,
+ test_percent_transition,
+ // FIXME: We don't currently test clamping,
+ // since background-position-x uses calc() as
+ // an intermediate form.
+ /* test_length_percent_pair_unclamped */ ],
+ "background-position-y": [ test_background_position_coord_transition,
+ test_length_transition,
+ test_percent_transition,
+ // FIXME: We don't currently test clamping,
+ // since background-position-y uses calc() as
+ // an intermediate form.
+ /* test_length_percent_pair_unclamped */ ],
+ "background-size": [ test_background_size_transition,
+ test_length_percent_pair_clamped ],
+ "border-bottom-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "border-bottom-width": [ test_length_transition,
+ test_length_clamped ],
+ "border-left-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "border-left-width": [ test_length_transition,
+ test_length_clamped ],
+ "border-right-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "border-right-width": [ test_length_transition,
+ test_length_clamped ],
+ "border-spacing": [ test_length_pair_transition,
+ test_length_pair_transition_clamped ],
+ "border-top-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "border-top-width": [ test_length_transition,
+ test_length_clamped ],
+ "bottom": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "accent-color": [ test_color_transition,
+ test_currentcolor_transition,
+ test_auto_color_transition ],
+ "caret-color": [ test_color_transition,
+ test_currentcolor_transition,
+ test_auto_color_transition ],
+ "clip": [ test_rect_transition ],
+ "clip-path": [ test_basic_shape_or_url_transition,
+ test_path_function ],
+ "color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "d": [ test_path_function ],
+ "fill": [ test_color_transition,
+ test_currentcolor_transition ],
+ "fill-opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "filter" : [ test_filter_transition ],
+ "flex-basis": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ test_flex_basis_content_transition ],
+ "flex-grow": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition ],
+ "flex-shrink": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition ],
+ "flood-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "flood-opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "font-size": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "font-size-adjust": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition,
+ /* FIXME: font-size-adjust treats zero specially */
+ /* test_float_zeroToOne_clamped */ ],
+ "font-stretch": [ test_percent_transition, test_percent_clamped ],
+ "font-weight": [ test_font_weight ],
+ "column-gap": [ test_grid_gap ],
+ "row-gap": [ test_grid_gap ],
+ "height": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "left": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "letter-spacing": [ test_length_transition, test_length_unclamped ],
+ "lighting-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ // NOTE: when calc() is supported on 'line-height', we should add
+ // test_length_percent_calc_transition.
+ "line-height": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "margin-bottom": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "margin-left": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "margin-right": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "margin-top": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "mask-position": [ test_background_position_transition,
+ test_length_percent_pair_unclamped ],
+ "mask-position-x": [ test_background_position_coord_transition,
+ test_length_transition,
+ test_percent_transition,
+ // FIXME: We don't currently test clamping,
+ // since background-position-x uses calc() as
+ // an intermediate form.
+ /* test_length_percent_pair_unclamped */ ],
+ "mask-position-y": [ test_background_position_coord_transition,
+ test_length_transition,
+ test_percent_transition,
+ // FIXME: We don't currently test clamping,
+ // since background-position-y uses calc() as
+ // an intermediate form.
+ /* test_length_percent_pair_unclamped */ ],
+ "mask-size": [ test_background_size_transition,
+ test_length_percent_pair_clamped ],
+ "max-height": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "max-width": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "min-height": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "min-width": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "object-position": [ test_background_position_transition ],
+ "overflow-clip-margin": [ test_length_transition ],
+ "opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "order": [ test_integer_transition ],
+ "outline-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "outline-offset": [ test_length_transition, test_length_unclamped ],
+ "outline-width": [ test_length_transition, test_length_clamped ],
+ "padding-bottom": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "padding-left": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "padding-right": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "padding-top": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "perspective": [ test_length_transition ],
+ "perspective-origin": [ test_length_pair_transition,
+ test_length_percent_pair_transition,
+ test_length_percent_pair_unclamped ],
+ "right": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "r": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "rx": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "ry": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "shape-image-threshold": [ test_float_zeroToOne_transition,
+ // shape-image-threshold (like opacity) is
+ // clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "shape-margin": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped ],
+ "shape-outside": [ test_basic_shape_or_url_transition ],
+ "stop-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "stop-opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "stroke": [ test_color_transition,
+ test_currentcolor_transition ],
+ "stroke-dasharray": [ test_dasharray_transition ],
+ "stroke-dashoffset": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped, ],
+ "stroke-miterlimit": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition,
+ test_float_aboveZero_clamped ],
+ "stroke-opacity" : [ test_float_zeroToOne_transition,
+ // opacity is clamped in computed style
+ // (not parsing/interpolation)
+ test_float_zeroToOne_clamped ],
+ "stroke-width": [ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped, ],
+ "tab-size": [ test_float_zeroToOne_transition,
+ test_float_aboveOne_transition, test_length_clamped ],
+ "text-decoration": [ test_color_shorthand_transition,
+ test_currentcolor_shorthand_transition ],
+ "text-decoration-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "text-emphasis-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "text-indent": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "text-shadow": [ test_shadow_transition ],
+ "top": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "transform": [ test_transform_transition ],
+ "transform-origin": [ test_length_pair_transition,
+ test_length_percent_pair_transition,
+ test_length_percent_pair_unclamped ],
+ "vertical-align": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "visibility": [ test_visibility_transition ],
+ "width": [ test_length_transition, test_percent_transition,
+ test_length_percent_calc_transition,
+ test_length_clamped, test_percent_clamped ],
+ "word-spacing": [ test_length_transition, test_length_unclamped ],
+ "x": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "y": [ test_length_transition, test_percent_transition,
+ test_length_unclamped, test_percent_unclamped ],
+ "z-index": [ test_integer_transition, test_pos_integer_or_auto_transition ],
+ "-webkit-line-clamp": [ test_pos_integer_or_none_transition ],
+ "-webkit-text-fill-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "-webkit-text-stroke-color": [ test_color_transition,
+ test_currentcolor_transition ],
+ "text-underline-offset": [ test_length_transition ],
+ "text-decoration-thickness": [ test_length_transition ],
+ "scroll-margin-top": [
+ test_length_transition,
+ ],
+ "scroll-margin-right": [
+ test_length_transition,
+ ],
+ "scroll-margin-bottom": [
+ test_length_transition,
+ ],
+ "scroll-margin-left": [
+ test_length_transition,
+ ],
+ "scroll-padding-top": [
+ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ ],
+ "scroll-padding-right": [
+ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ ],
+ "scroll-padding-bottom": [
+ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ ],
+ "scroll-padding-left": [
+ test_length_transition, test_percent_transition,
+ test_length_clamped, test_percent_clamped,
+ ],
+ "scrollbar-color": [ test_scrollbar_color_transition ],
+};
+
+if (IsCSSPropertyPrefEnabled("layout.css.backdrop-filter.enabled")) {
+ supported_properties["backdrop-filter"] = [ test_filter_transition ];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) {
+ supported_properties["font-variation-settings"] = [ test_font_variations_transition ];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.individual-transform.enabled")) {
+ supported_properties["rotate"] = [ test_rotate_transition ];
+ supported_properties["scale"] = [ test_scale_transition ];
+ supported_properties["translate"] = [ test_translate_transition ];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) {
+ supported_properties["contain-intrinsic-width"] = [ test_length_transition, test_auto_with_length_transition ];
+ supported_properties["contain-intrinsic-height"] = supported_properties["contain-intrinsic-width"];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.content-visibility.enabled")) {
+ supported_properties["content-visibility"] = [test_content_visibility_transition];
+}
+
+if (IsCSSPropertyPrefEnabled("layout.css.zoom.enabled")) {
+ Object.assign(supported_properties, {
+ "zoom": [ test_number_transition, test_percent_transition ],
+ });
+}
+
+// For properties which are well-tested by web-platform-tests, we don't need to
+// test animations/transitions again on them.
+var skipped_transitionable_properties = [
+ "border-image-outset",
+ "border-image-slice",
+ "border-image-width",
+ "font-style", // Tests being added in https://github.com/web-platform-tests/wpt/pull/37570
+ "grid-template-columns",
+ "grid-template-rows",
+ "offset-path",
+ "offset-distance",
+ "offset-rotate",
+ "offset-anchor",
+ "offset-position",
+]
+
+// Logical properties.
+for (const logical_side of ["inline-start", "inline-end", "block-start", "block-end"]) {
+ supported_properties["border-" + logical_side + "-color"] = supported_properties["border-top-color"];
+ supported_properties["border-" + logical_side + "-width"] = supported_properties["border-top-width"];
+ supported_properties["margin-" + logical_side] = supported_properties["margin-top"];
+ supported_properties["padding-" + logical_side] = supported_properties["padding-top"];
+ supported_properties["inset-" + logical_side] = supported_properties["top"];
+ supported_properties["scroll-margin-" + logical_side] = supported_properties["scroll-margin-top"];
+ supported_properties["scroll-padding-" + logical_side] = supported_properties["scroll-padding-top"];
+}
+
+for (const logical_size of ["inline", "block"]) {
+ supported_properties[logical_size + "-size"] = supported_properties["width"];
+ supported_properties["min-" + logical_size + "-size"] = supported_properties["min-width"];
+ supported_properties["max-" + logical_size + "-size"] = supported_properties["max-width"];
+ if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) {
+ supported_properties["contain-intrinsic-" + logical_size + "-size"] = supported_properties["contain-intrinsic-width"];
+ }
+}
+
+var div = document.getElementById("display");
+var cs = getComputedStyle(div, "");
+var winUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function computeMatrix(v) {
+ div.style.setProperty("transform", v, "");
+ var result = cs.getPropertyValue("transform");
+ div.style.removeProperty("transform");
+ return result;
+}
+var c_rot_15 = computeMatrix("rotate(15deg)");
+is(c_rot_15.substring(0,6), "matrix", "should compute to matrix value");
+var c_rot_60 = computeMatrix("rotate(60deg)");
+is(c_rot_60.substring(0,6), "matrix", "should compute to matrix value");
+
+var transformTests = [
+ // rotate
+ { start: 'none', end: 'rotate(60deg)',
+ expected_uncomputed: 'rotate(15deg)',
+ expected: c_rot_15 },
+ { start: 'rotate(0)', end: 'rotate(60deg)',
+ expected_uncomputed: 'rotate(15deg)',
+ expected: c_rot_15 },
+ { start: 'rotate(0deg)', end: 'rotate(60deg)',
+ expected_uncomputed: 'rotate(15deg)',
+ expected: c_rot_15 },
+ { start: 'none', end: c_rot_60,
+ expected: c_rot_15 },
+ { start: 'none', end: 'rotate(360deg)',
+ expected_uncomputed: 'rotate(90deg)',
+ expected: computeMatrix('rotate(90deg)') },
+ { start: 'none', end: 'rotatez(360deg)',
+ expected_uncomputed: 'rotate(90deg)',
+ expected: computeMatrix('rotate(90deg)') },
+ { start: 'none', end: 'rotate(720deg)',
+ expected_uncomputed: 'rotate(180deg)',
+ expected: computeMatrix('rotate(180deg)') },
+ { start: 'none', end: 'rotate(720deg)',
+ expected_uncomputed: 'rotatez(180deg)',
+ expected: computeMatrix('rotate(180deg)') },
+ { start: 'none', end: 'rotate(1080deg)',
+ expected_uncomputed: 'rotate(270deg)',
+ expected: computeMatrix('rotate(270deg)') },
+ { start: 'none', end: 'rotate(1080deg)',
+ expected_uncomputed: 'rotate(270deg)',
+ expected: computeMatrix('rotatez(270deg)') },
+ { start: 'none', end: 'rotate(1440deg)',
+ expected_uncomputed: 'rotate(360deg)',
+ expected: computeMatrix('scale(1)'),
+ round_error_ok: true },
+ { start: 'none', end: 'rotatey(60deg)',
+ expected_uncomputed: 'rotatey(15deg)',
+ expected: computeMatrix('rotatey(15deg)') },
+ { start: 'none', end: 'rotatey(720deg)',
+ expected_uncomputed: 'rotatey(180deg)',
+ expected: computeMatrix('rotatey(180deg)') },
+ { start: 'none', end: 'rotatex(60deg)',
+ expected_uncomputed: 'rotatex(15deg)',
+ expected: computeMatrix('rotatex(15deg)') },
+ { start: 'none', end: 'rotatex(720deg)',
+ expected_uncomputed: 'rotatex(180deg)',
+ expected: computeMatrix('rotatex(180deg)') },
+
+ // translate
+ { start: 'translate(20px)', end: 'none',
+ expected_uncomputed: 'translate(15px)',
+ expected: 'matrix(1, 0, 0, 1, 15, 0)' },
+ { start: 'translate(20px, 12px)', end: 'none',
+ expected_uncomputed: 'translate(15px, 9px)',
+ expected: 'matrix(1, 0, 0, 1, 15, 9)' },
+ { start: 'translateX(-20px)', end: 'none',
+ expected_uncomputed: 'translateX(-15px)',
+ expected: 'matrix(1, 0, 0, 1, -15, 0)' },
+ { start: 'translateY(-40px)', end: 'none',
+ expected_uncomputed: 'translateY(-30px)',
+ expected: 'matrix(1, 0, 0, 1, 0, -30)' },
+ { start: 'translateZ(40px)', end: 'none',
+ expected_uncomputed: 'translateZ(30px)',
+ expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 30, 1)' },
+ { start: 'none', end: 'translate3D(40px, 60px, -40px)',
+ expected_uncomputed: 'translate3D(10px, 15px, -10px)',
+ expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 15, -10, 1)' },
+ // percentages are relative to 300px (width) and 50px (height)
+ // per the prerequisites in property_database.js
+ { start: 'translate(20%)', end: 'none',
+ expected_uncomputed: 'translate(15%)',
+ expected: 'matrix(1, 0, 0, 1, 45, 0)',
+ round_error_ok: true },
+ { start: 'translate(20%, 12%)', end: 'none',
+ expected_uncomputed: 'translate(15%, 9%)',
+ expected: 'matrix(1, 0, 0, 1, 45, 4.5)',
+ round_error_ok: true },
+ { start: 'translateX(-20%)', end: 'none',
+ expected_uncomputed: 'translateX(-15%)',
+ expected: 'matrix(1, 0, 0, 1, -45, 0)',
+ round_error_ok: true },
+ { start: 'translateY(-40%)', end: 'none',
+ expected_uncomputed: 'translateY(-30%)',
+ expected: 'matrix(1, 0, 0, 1, 0, -15)',
+ round_error_ok: true },
+ { start: 'none', end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
+ expected_uncomputed: 'rotate(22.5deg) translate(5%, 5%) rotate(-22.5deg)',
+ round_error_ok: true },
+ { start: 'none', end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)',
+ expected_uncomputed: 'rotate(-22.5deg) translate(5%, 5%) rotate(22.5deg)',
+ round_error_ok: true },
+ // test percent translation using matrix decomposition
+ { start: 'matrix(1, 0, 0, 1, 0, 0)',
+ end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
+ expected: 'matrix(1, 0, 0, 1, -2.5, 15)',
+ round_error_ok: true },
+ { start: 'matrix(1, 0, 0, 1, 0, 0)',
+ end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)',
+ expected: 'matrix(1, 0, 0, 1, 2.5, -15)',
+ round_error_ok: true },
+ // test calc() in translate
+ // Note that font-size: is 20px, and that percentages are relative
+ // to 300px (width) and 50px (height) per the prerequisites in
+ // property_database.js
+ { start: 'translateX(20%)', /* 60px */
+ end: 'translateX(calc(10% + 1em))', /* 30px + 20px = 50px */
+ expected_uncomputed: 'translateX(calc(17.5% + 0.25em))',
+ expected: 'matrix(1, 0, 0, 1, 57.5, 0)' },
+ { start: 'translate(calc(0.75 * 3em + 1.5 * 10%), calc(0.5 * 5em + 0.5 * 8%))', /* 90px, 52px */
+ end: 'rotate(90deg) translateY(20%) rotate(90deg) translateY(calc(10% + 0.5em)) rotate(180deg)', /* -10px, -15px */
+ expected: 'matrix(1, 0, 0, 1, 65, 35.25)' },
+
+ // scale
+ { start: 'scale(2)', end: 'none',
+ expected_uncomputed: 'scale(1.75)',
+ expected: 'matrix(1.75, 0, 0, 1.75, 0, 0)' },
+ { start: 'none', end: 'scale(0.4)',
+ expected_uncomputed: 'scale(0.85)',
+ expected: 'matrix(0.85, 0, 0, 0.85, 0, 0)',
+ round_error_ok: true },
+ { start: 'scale(2)', end: 'scale(-2)',
+ expected_uncomputed: 'scale(1)',
+ expected: 'matrix(1, 0, 0, 1, 0, 0)' },
+ { start: 'scale(2)', end: 'scale(-6)',
+ expected_uncomputed: 'scale(0)',
+ expected: 'matrix(0, 0, 0, 0, 0, 0)' },
+ { start: 'scale(2, 0.4)', end: 'none',
+ expected_uncomputed: 'scale(1.75, 0.55)',
+ expected: 'matrix(1.75, 0, 0, 0.55, 0, 0)',
+ round_error_ok: true },
+ { start: 'scaleX(3)', end: 'none',
+ expected_uncomputed: 'scaleX(2.5)',
+ expected: 'matrix(2.5, 0, 0, 1, 0, 0)' },
+ { start: 'scaleY(5)', end: 'none',
+ expected_uncomputed: 'scaleY(4)',
+ expected: 'matrix(1, 0, 0, 4, 0, 0)' },
+ { start: 'scaleZ(5)', end: 'none',
+ expected_uncomputed: 'scaleZ(4)',
+ expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1)' },
+ { start: 'none', end: 'scale3D(5, 5, 5)',
+ expected_uncomputed: 'scale3D(2, 2, 2)',
+ expected: 'matrix3d(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1)' },
+
+ // skew
+ { start: 'skewX(45deg)', end: 'none',
+ expected_uncomputed: 'skewX(33.75deg)' },
+ { start: 'skewY(45deg)', end: 'none',
+ expected_uncomputed: 'skewY(33.75deg)' },
+ { start: 'skew(45deg)', end: 'none',
+ expected_uncomputed: 'skew(33.75deg)' },
+ { start: 'skew(45deg, 45deg)', end: 'none',
+ expected_uncomputed: 'skew(33.75deg, 33.75deg)' },
+ { start: 'skewX(45deg)', end: 'skewX(-45deg)',
+ expected_uncomputed: 'skewX(22.5deg)' },
+ { start: 'skewX(0)', end: 'skewX(-45deg)',
+ expected_uncomputed: 'skewX(-11.25deg)' },
+ { start: 'skewY(45deg)', end: 'skewY(-45deg)',
+ expected_uncomputed: 'skewY(22.5deg)' },
+
+ // matrix : skewX
+ { start: 'matrix(1, 0, 3, 1, 0, 0)', end: 'none',
+ expected: 'matrix(1, 0, ' + 3 * 0.75 + ', 1, 0, 0)',
+ round_error_ok: true },
+ { start: 'skewX(0)', end: 'skewX(-45deg) translate(0)',
+ expected_uncomputed: 'skewX(-11.25deg) translate(0)' },
+ // matrix : rotate
+ { start: 'rotate(-30deg)', end: 'matrix(0, 1, -1, 0, 0, 0)',
+ expected: 'matrix(1, 0, 0, 1, 0, 0)',
+ round_error_ok: true },
+ { start: 'rotate(-30deg) translateX(0)',
+ end: 'translateX(0) rotate(-90deg)',
+ expected: computeMatrix('rotate(-45deg)'),
+ round_error_ok: true },
+ // extended shorter transform list
+ { start: 'skewY(60deg)', end: 'skewY(-60deg) translateX(0)',
+ expected_uncomputed: 'skewY(30deg) translateX(0)' },
+
+
+ // matrix decomposition
+
+ // Four pairs of the same matrix expressed different ways.
+ { start: 'matrix(-1, 0, 0, -1, 0, 0)', /* rotate(180deg) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(135deg)') },
+ { start: 'scale(-1)', end: 'none',
+ expected_uncomputed: 'scale(-0.5)',
+ expected: 'matrix(-0.5, 0, 0, -0.5, 0, 0)' },
+ { start: 'rotate(180deg)', end: 'none',
+ expected_uncomputed: 'rotate(135deg)' },
+ { start: 'rotate(-180deg)', end: 'none',
+ expected_uncomputed: 'rotate(-135deg)',
+ expected: computeMatrix('rotate(225deg)') },
+
+ // matrix followed by scale
+ { start: 'matrix(2, 0, 0, 2, 10, 20) scale(2)',
+ end: 'none',
+ expected: 'matrix(3.0625, 0, 0, 3.0625, 7.5, 15)' },
+
+ // ... and a bunch of similar possibilities. The spec isn't settled
+ // here; there are multiple options. See:
+ // http://lists.w3.org/Archives/Public/www-style/2010Jun/0602.html
+ { start: 'matrix(-1, 0, 0, 1, 0, 0)', /* scaleX(-1) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('scaleX(-0.5)') },
+
+ { start: 'matrix(1, 0, 0, -1, 0, 0)', /* rotate(-180deg) scaleX(-1) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(-135deg) scaleX(-0.5)') },
+
+ { start: 'matrix(0, 1, 1, 0, 0, 0)', /* rotate(-90deg) scaleX(-1) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(-67.5deg) scaleX(-0.5)') },
+
+ { start: 'matrix(0, -1, 1, 0, 0, 0)', /* rotate(-90deg) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(-67.5deg)') },
+
+ { start: 'matrix(0, 1, -1, 0, 0, 0)', /* rotate(90deg) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(67.5deg)') },
+
+ { start: 'matrix(0, -1, -1, 0, 0, 0)', /* rotate(90deg) scaleX(-1) */
+ end: 'matrix(1, 0, 0, 1, 0, 0)',
+ expected: computeMatrix('rotate(67.5deg) scaleX(-0.5)') },
+
+ // Similar decomposition tests, but with skewX. I checked visually
+ // that the sign of the skew was correct by checking visually that
+ // the animations in
+ // https://dbaron.org/css/test/2010/transition-negative-determinant
+ // don't flip when they finish, and then wrote tests corresponding
+ // to the current code's behavior.
+ // ... start with four with positive determinants
+ { start: 'none',
+ end: 'matrix(1, 0, 1.5, 1, 0, 0)',
+ /* skewX(atan(1.5)) */
+ expected: 'matrix(1, 0, ' + 1.5 * 0.25 + ', 1, 0, 0)',
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(-1, 0, 2, -1, 0, 0)',
+ /* rotate(180deg) skewX(atan(-2)) */
+ expected: computeMatrix('rotate(45deg) matrix(1, 0, ' + -2 * 0.25 + ', 1, 0, 0)'),
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(0, -1, 1, -3, 0, 0)',
+ /* rotate(-90deg) skewX(atan(3)) */
+ expected: computeMatrix('rotate(-22.5deg) matrix(1, 0, ' + 3 * 0.25 + ', 1, 0, 0)'),
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(0, 1, -1, 4, 0, 0)',
+ /* rotate(90deg) skewX(atan(4)) */
+ expected: computeMatrix('rotate(22.5deg) matrix(1, 0, ' + 4 * 0.25 + ', 1, 0, 0)'),
+ round_error_ok: true },
+ // and then four with negative determinants
+ { start: 'none',
+ end: 'matrix(1, 0, 1, -1, 0, 0)',
+ /* rotate(-180deg) skewX(atan(-1)) scaleX(-1) */
+ expected: computeMatrix('rotate(-45deg) matrix(1, 0, ' + -1 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(-1, 0, -1, 1, 0, 0)',
+ /* skewX(atan(-1)) scaleX(-1) */
+ expected: computeMatrix('matrix(1, 0, ' + -1 * 0.25 + ', 1, 0, 0) scaleX(0.5)') },
+ { start: 'none',
+ end: 'matrix(0, 1, 1, -2, 0, 0)',
+ /* rotate(-90deg) skewX(atan(2)) scaleX(-1) */
+ expected: computeMatrix('rotate(-22.5deg) matrix(1, 0, ' + 2 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
+ round_error_ok: true },
+ { start: 'none',
+ end: 'matrix(0, -1, -1, 0.5, 0, 0)',
+ /* rotate(90deg) skewX(atan(0.5)) scaleX(-1) */
+ expected: computeMatrix('rotate(22.5deg) matrix(1, 0, ' + 0.5 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
+ round_error_ok: true },
+
+ // lists
+ { start: 'translate(10px) skewY(45deg)',
+ end: 'translate(30px) skewY(-45deg)',
+ expected_uncomputed: 'translate(15px) skewY(22.5deg)' },
+ { start: 'skewY(45deg) rotate(90deg)',
+ end: 'skewY(-45deg) rotate(90deg)',
+ expected_uncomputed: 'skewY(22.5deg) rotate(90deg)' },
+ { start: 'skewX(45deg) rotate(90deg)',
+ end: 'skewX(-45deg) rotate(90deg)',
+ expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' },
+
+ // extended lists
+ { start: 'skewY(45deg) rotate(90deg) translate(0)',
+ end: 'skewY(-45deg) rotate(90deg)',
+ expected_uncomputed: 'skewY(22.5deg) rotate(90deg) translate(0)' },
+ { start: 'skewX(-60deg) rotate(90deg) translate(0)',
+ end: 'skewX(60deg) rotate(90deg)',
+ expected_uncomputed: 'skewX(-30deg) rotate(90deg) translate(0)' },
+];
+
+// We intentionally use a non-default reference-box so we always serialize it.
+// Therefore, we can reuse these tests for clip-path and shape-outside.
+// Bug 1313619: Add some tests for two basic shapes with an explicit
+// reference-box and a default one, for each property (because they use
+// different default reference-box).
+const basicShapesTests = [
+ { start: "none", end: "none",
+ expected: ["none"] },
+ // none to shape
+ { start: "none",
+ end: "circle(500px at 500px 500px) content-box",
+ expected: ["circle", ["500px at 500px 500px"], "content-box"]
+ },
+ { start: "none",
+ end: "ellipse(500px 500px at 500px 500px) content-box",
+ expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"]
+ },
+ { start: "none",
+ end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
+ expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "content-box"]
+ },
+ { start: "none",
+ end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
+ expected: ["inset", ["500px round 500px"], "content-box"]
+ },
+ // matching functions
+ { start: "circle(100px)", end: "circle(500px)",
+ expected: ["circle", ["200px"]] },
+ { start: "ellipse(100px 100px)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["200px 200px"]] },
+ { start: "circle(100px at 100px 100px) content-box",
+ end: "circle(500px at 500px 500px) content-box",
+ expected: ["circle", ["200px at 200px 200px"], "content-box"]
+ },
+ { start: "ellipse(100px 100px at 100px 100px) content-box",
+ end: "ellipse(500px 500px at 500px 500px) content-box",
+ expected: ["ellipse", ["200px 200px at 200px 200px"], "content-box"]
+ },
+ { start: "polygon(evenodd, 100px 100px, 100px 100px) content-box",
+ end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
+ expected: ["polygon", ["evenodd, 200px 200px, 200px 200px"], "content-box"]
+ },
+ { start: "inset(100px 100px 100px 100px round 100px 100px) content-box",
+ end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
+ expected: ["inset", ["200px round 200px"], "content-box"]
+ },
+ // matching functions percentage
+ { start: "circle(100%)", end: "circle(500%)",
+ expected: ["circle", ["200%"]] },
+ { start: "ellipse(100% 100%)", end: "ellipse(500% 500%)",
+ expected: ["ellipse", ["200% 200%"]] },
+ { start: "circle(100% at 100% 100%) content-box",
+ end: "circle(500% at 500% 500%) content-box",
+ expected: ["circle", ["200% at 200% 200%"], "content-box"]
+ },
+ { start: "ellipse(100% 100% at 100% 100%) content-box",
+ end: "ellipse(500% 500% at 500% 500%) content-box",
+ expected: ["ellipse", ["200% 200% at 200% 200%"], "content-box"]
+ },
+ { start: "polygon(evenodd, 100% 100%, 100% 100%) content-box",
+ end: "polygon(evenodd, 500% 500%, 500% 500%) content-box",
+ expected: ["polygon", ["evenodd, 200% 200%, 200% 200%"], "content-box"]
+ },
+ { start: "inset(100% 100% 100% 100% round 100% 100%) content-box",
+ end: "inset(500% 500% 500% 500% round 500% 500%) content-box",
+ expected: ["inset", ["200% round 200%"], "content-box"] },
+ // matching functions with calc() values
+ { start: "circle(calc(80px + 20px))", end: "circle(calc(200px + 300px))",
+ expected: ["circle", ["200px"]] },
+ { start: "circle(calc(80% + 20%))", end: "circle(calc(200% + 300%))",
+ expected: ["circle", ["200%"]] },
+ { start: "circle(calc(10px + 20%))", end: "circle(calc(50px + 40%))",
+ expected: ["circle", ["calc(25% + 20px)"]] },
+ // matching functions with interpolation between percentage/pixel values
+ { start: "circle(20px)", end: "circle(100%)",
+ expected: ["circle", ["calc(25% + 15px)"]] },
+ { start: "ellipse(100% 100px at 8px 20%) content-box",
+ end: "ellipse(40px 4% at 80% 60px) content-box",
+ expected: ["ellipse", ["calc(75% + 10px) calc(1% + 75px) at " +
+ "calc(20% + 6px) calc(15% + 15px)"],
+ "content-box"] },
+ // no interpolation for keywords
+ { start: "circle()", end: "circle(50px)",
+ expected: ["circle", ["50px"]] },
+ { start: "circle(closest-side)", end: "circle(500px)",
+ expected: ["circle", ["500px"]] },
+ { start: "circle(farthest-side)", end: "circle(500px)",
+ expected: ["circle", ["500px"]] },
+ { start: "circle(500px)", end: "circle(farthest-side)",
+ expected: ["circle", ["farthest-side"]]},
+ { start: "circle(500px)", end: "circle(closest-side)",
+ expected: ["circle", [""]]},
+ { start: "ellipse()", end: "ellipse(50px 50px)",
+ expected: ["ellipse", ["50px 50px"]] },
+ { start: "ellipse(closest-side closest-side)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px"]] },
+ { start: "ellipse(farthest-side closest-side)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px"]] },
+ { start: "ellipse(farthest-side farthest-side)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px"]] },
+ { start: "ellipse(500px 500px)", end: "ellipse(farthest-side farthest-side)",
+ expected: ["ellipse", ["farthest-side farthest-side"]] },
+ { start: "ellipse(500px 500px)", end: "ellipse(closest-side closest-side)",
+ expected: ["ellipse", [""]] },
+ // mismatching boxes
+ { start: "circle(100px at 100px 100px) border-box",
+ end: "circle(500px at 500px 500px) content-box",
+ expected: ["circle", ["500px at 500px 500px"], "content-box"]
+ },
+ { start: "ellipse(100px 100px at 100px 100px) border-box",
+ end: "ellipse(500px 500px at 500px 500px) content-box",
+ expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"]
+ },
+ { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box",
+ end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
+ expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "content-box"]
+ },
+ { start: "inset(100px 100px 100px 100px round 100px 100px) border-box",
+ end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
+ expected: ["inset", ["500px round 500px"], "content-box"]
+ },
+ // mismatching functions
+ { start: "circle(100px at 100px 100px) content-box",
+ end: "ellipse(500px 500px at 500px 500px) content-box",
+ expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"]
+ },
+ { start: "inset(0px round 20px)", end: "ellipse(500px 500px)",
+ expected: ["ellipse", ["500px 500px"]]
+ },
+ // shape to reference box
+ { start: "circle(20px)", end: "content-box", expected: ["content-box"] },
+ { start: "content-box", end: "circle(20px)", expected: ["circle", ["20px"]] },
+ // url to shape
+ { start: "circle(20px)", end: "url(http://localhost/a.png)", expected: ["url", ["\"http://localhost/a.png\""]] },
+ { start: "url(http://localhost/a.png)", end: "circle(20px)", expected: ["circle", ["20px"]] },
+ // url to none
+ { start: "none", end: "url(http://localhost/a.png)", expected: ["url", ["\"http://localhost/a.png\""]] },
+ { start: "http://localhost/a.png", end: "none", expected: ["none"] },
+];
+
+const basicShapesWithFragmentUrlTests = [
+ // Fragment url to shape
+ { start: "circle(20px)", end: "url('#a')", expected: ["url", ["\"#a\""]] },
+ { start: "url('#a')", end: "circle(20px)", expected: ["circle", ["20px"]] },
+ // Fragment url to none
+ { start: "none", end: "url('#a')", expected: ["url", ["\"#a\""]] },
+ { start: "url('#a')", end: "none", expected: ["none"] },
+];
+
+// We have a lot of tests in web-platform-tests already, so here we only test
+// basic interpolation cases.
+const pathFunctionTests = [
+ { start: "none", end: "none",
+ expected: ["none"] },
+ // none to path
+ { start: "none",
+ end: "path('M 100 100')",
+ expected: ["path", '"M 100 100"']
+ },
+ // path to none
+ { start: "path('M 100 100')",
+ end: "none",
+ expected: ["none"]
+ },
+ // mismatch
+ {
+ start: "path('M 0 0 H 100 H 200')",
+ end: "path('M 0 0 H 500')",
+ expected: ["path", '"M 0 0 H 500"']
+ },
+ {
+ start: "path('M 0 0 V 100')",
+ end: "path('M 0 0 H 500')",
+ expected: ["path", '"M 0 0 H 500"']
+ },
+ // match
+ {
+ start: "path('M 100 100')",
+ end: "path('M 100 500')",
+ expected: ["path", '"M 100 200"']
+ },
+ {
+ start: "path('M 10 10 L 100 100')",
+ end: "path('M 10 10 L 100 500')",
+ expected: ["path", '"M 10 10 L 100 200"']
+ },
+ {
+ start: "path('M 10 10 H 100')",
+ end: "path('M 10 10 H 500')",
+ expected: ["path", '"M 10 10 H 200"']
+ },
+ {
+ start: "path('M 10 10 V 100')",
+ end: "path('M 10 10 V 500')",
+ expected: ["path", '"M 10 10 V 200"']
+ },
+ {
+ start: "path('M 10 10 C 32 42 52 62 120 2200')",
+ end: "path('M 10 10 C 40 50 60 70 200 3000')",
+ expected: ["path", '"M 10 10 C 34 44 54 64 140 2400"']
+ },
+ {
+ start: "path('M 10 10 S 45 67 89 123')",
+ end: "path('M 10 10 S 61 51 113 99')",
+ expected: ["path", '"M 10 10 S 49 63 95 117"']
+ },
+ {
+ start: "path('M 10 10 Q 32 42 120 2200')",
+ end: "path('M 10 10 Q 40 50 200 3000')",
+ expected: ["path", '"M 10 10 Q 34 44 140 2400"']
+ },
+ {
+ start: "path('M 10 10 T 100 200')",
+ end: "path('M 10 10 T 500 280')",
+ expected: ["path", '"M 10 10 T 200 220"']
+ },
+ {
+ start: "path('M 10 10 A 10 20 30 0 1 140 450')",
+ end: "path('M 10 10 A 50 60 70 0 1 380 290')",
+ expected: ["path", '"M 10 10 A 20 30 40 0 1 200 410"']
+ },
+ {
+ start: "path('M 10 10 A 10 20 30 1 0 140 450')",
+ end: "path('M 10 10 A 50 60 70 0 1 380 290')",
+ expected: ["path", '"M 10 10 A 20 30 40 1 0 200 410"']
+ },
+ // mix relative and absolute coordinates
+ {
+ start: "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')",
+ // =="path('M 10 20 H 40 V 80 H 50 V 70 L 160 130')"
+ end: "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')",
+ expected: ["path", '"M 40 50 H 60 V 100 H 70 V 90 L 170 140"']
+ },
+];
+
+const clipPathPathFunctionTests = [
+ // match fill-rule
+ {
+ start: "path(nonzero, 'M 100 100')",
+ end: "path(nonzero, 'M 100 500')",
+ expected: ["path", '"M 100 200"']
+ },
+ {
+ start: "path(evenodd, 'M 100 100')",
+ end: "path(evenodd, 'M 100 500')",
+ expected: ["path", 'evenodd, "M 100 200"']
+ },
+ // mismatch fill-rule
+ {
+ start: "path(nonzero, 'M 100 100')",
+ end: "path(evenodd, 'M 100 500')",
+ expected: ["path", 'evenodd, "M 100 500"']
+ },
+];
+
+var filterTests = [
+ { start: "none", end: "none",
+ expected: ["none"] },
+ // function from none (number/length)
+ { start: "none", end: "brightness(0.5)",
+ expected: ["brightness", 0.875] },
+ { start: "none", end: "contrast(0.5)",
+ expected: ["contrast", 0.875] },
+ { start: "none", end: "grayscale(0.5)",
+ expected: ["grayscale", 0.125] },
+ { start: "none", end: "invert(0.5)",
+ expected: ["invert", 0.125] },
+ { start: "none", end: "opacity(0.5)",
+ expected: ["opacity", 0.875] },
+ { start: "none", end: "saturate(0.5)",
+ expected: ["saturate", 0.875] },
+ { start: "none", end: "sepia(0.5)",
+ expected: ["sepia", 0.125] },
+ { start: "none", end: "blur(50px)",
+ expected: ["blur", 12.5] },
+ // function to none (number/length)
+ { start: "brightness(0.5)", end: "none",
+ expected: ["brightness", 0.625] },
+ { start: "contrast(0.5)", end: "none",
+ expected: ["contrast", 0.625] },
+ { start: "grayscale(0.5)", end: "none",
+ expected: ["grayscale", 0.375] },
+ { start: "invert(0.5)", end: "none",
+ expected: ["invert", 0.375] },
+ { start: "opacity(0.5)", end: "none",
+ expected: ["opacity", 0.625] },
+ { start: "saturate(0.5)", end: "none",
+ expected: ["saturate", 0.625] },
+ { start: "sepia(0.5)", end: "none",
+ expected: ["sepia", 0.375] },
+ { start: "blur(50px)", end: "none",
+ expected: ["blur", 37.5] },
+ // function to same function (number/length)
+ { start: "brightness(0.25)", end: "brightness(0.75)",
+ expected: ["brightness", 0.375] },
+ { start: "contrast(0.25)", end: "contrast(0.75)",
+ expected: ["contrast", 0.375] },
+ { start: "grayscale(0.25)", end: "grayscale(0.75)",
+ expected: ["grayscale", 0.375] },
+ { start: "invert(0.25)", end: "invert(0.75)",
+ expected: ["invert", 0.375] },
+ { start: "opacity(0.25)", end: "opacity(0.75)",
+ expected: ["opacity", 0.375] },
+ { start: "saturate(0.25)", end: "saturate(0.75)",
+ expected: ["saturate", 0.375] },
+ { start: "sepia(0.25)", end: "sepia(0.75)",
+ expected: ["sepia", 0.375] },
+ { start: "blur(25px)", end: "blur(75px)",
+ expected: ["blur", 37.5] },
+ // function to same function (percent)
+ { start: "brightness(25%)", end: "brightness(75%)",
+ expected: ["brightness", 0.375] },
+ { start: "contrast(25%)", end: "contrast(75%)",
+ expected: ["contrast", 0.375] },
+ { start: "grayscale(25%)", end: "grayscale(75%)",
+ expected: ["grayscale", 0.375] },
+ { start: "invert(25%)", end: "invert(75%)",
+ expected: ["invert", 0.375] },
+ { start: "opacity(25%)", end: "opacity(75%)",
+ expected: ["opacity", 0.375] },
+ { start: "saturate(25%)", end: "saturate(75%)",
+ expected: ["saturate", 0.375] },
+ { start: "sepia(25%)", end: "sepia(75%)",
+ expected: ["sepia", 0.375] },
+ // function to same function (percent, number/length)
+ { start: "brightness(0.25)", end: "brightness(75%)",
+ expected: ["brightness", 0.375] },
+ { start: "contrast(25%)", end: "contrast(0.75)",
+ expected: ["contrast", 0.375] },
+ // hue-rotate with different angle values
+ { start: "hue-rotate(0deg)", end: "hue-rotate(720deg)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0rad)", end: "hue-rotate("+4*Math.PI+"rad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0grad)", end: "hue-rotate(800grad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0turn)", end: "hue-rotate(2turn)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0deg)", end: "hue-rotate("+4*Math.PI+"rad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0turn)", end: "hue-rotate(800grad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0grad)", end: "hue-rotate("+4*Math.PI+"rad)",
+ expected: ["hue-rotate", "180deg"] },
+ { start: "hue-rotate(0grad)", end: "hue-rotate(0turn)",
+ expected: ["hue-rotate", "0deg"] },
+ // multiple matching functions, same length
+ { start: "contrast(25%) brightness(0.25) blur(25px) sepia(75%)",
+ end: "contrast(75%) brightness(0.75) blur(75px) sepia(25%)",
+ expected: ["contrast", 0.375, "brightness", 0.375, "blur", 37.5, "sepia", 0.625] },
+ { start: "invert(25%) brightness(0.25) blur(25px) invert(50%) brightness(0.5) blur(50px)",
+ end: "invert(75%) brightness(0.75) blur(75px)",
+ expected: ["invert", 0.375, "brightness", 0.375, "blur", 37.5, "invert", 0.375, "brightness", 0.625, "blur", 37.5] },
+ // multiple matching functions, different length
+ { start: "contrast(25%) brightness(0.5) blur(50px)",
+ end: "contrast(75%)",
+ expected: ["contrast", 0.375, "brightness", 0.625, "blur", 37.5] },
+ // mismatching filter functions
+ { start: "contrast(0%)", end: "blur(10px)",
+ expected: ["blur", 10] },
+ // not supported interpolations
+ { start: "none", end: "url('#b')",
+ expected: ["url", "\"#b\""] },
+ { start: "url('#a')", end: "none",
+ expected: ["none"] },
+ { start: "url('#a')", end: "url('#b')",
+ expected: ["url", "\"#b\""] },
+ { start: "url('#a')", end: "blur(10px)",
+ expected: ["blur", 10] },
+ { start: "blur(10px)", end: "url('#a')",
+ expected: ["url", "\"#a\""] },
+ { start: "blur(0px) url('#a')", end: "blur(20px)",
+ expected: ["blur", 20] },
+ { start: "blur(0px)", end: "blur(20px) url('#a')",
+ expected: ["blur", 20, "url", "\"#a\""] },
+ { start: "contrast(0.25) brightness(0.25) blur(25px)",
+ end: "contrast(0.75) url('#a')",
+ expected: ["contrast", 0.75, "url", "\"#a\""] },
+ { start: "contrast(0.25) brightness(0.25) blur(75px)",
+ end: "brightness(0.75) contrast(0.75) blur(25px)",
+ expected: ["brightness", 0.75, "contrast", 0.75, "blur", 25] },
+ { start: "contrast(0.25) brightness(0.25) blur(25px)",
+ end: "contrast(0.75) brightness(0.75) contrast(0.75)",
+ expected: ["contrast", 0.75, "brightness", 0.75, "contrast", 0.75] },
+ // drop-shadow animation
+ { start: "none",
+ end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)",
+ expected: ["drop-shadow", "rgba(0, 0, 0, 0.25) 1px 1px 0px"] },
+ { start: "drop-shadow(rgb(0, 0, 0) 0px 0px 0px)",
+ end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)",
+ expected: ["drop-shadow", "rgb(0, 0, 0) 1px 1px 0px"] },
+ { start: "drop-shadow(#038000 4px 4px)",
+ end: "drop-shadow(8px 8px 8px red)",
+ expected: ["drop-shadow", "rgb(66, 96, 0) 5px 5px 2px"] },
+ { start: "blur(25px) drop-shadow(8px 8px)",
+ end: "blur(75px)",
+ expected: ["blur", 37.5, "drop-shadow", "rgba(0, 0, 0, 0.75) 6px 6px 0px"] },
+ { start: "blur(75px)",
+ end: "blur(25px) drop-shadow(8px 8px)",
+ expected: ["blur", 62.5, "drop-shadow", "rgba(0, 0, 0, 0.25) 2px 2px 0px"] },
+ { start: "drop-shadow(2px 2px blue)",
+ end: "none",
+ expected: ["drop-shadow", "rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px"] },
+];
+
+var prop;
+for (prop in supported_properties) {
+ // Test that prop is in the property database.
+ ok(prop in gCSSProperties, "property " + prop + " in gCSSProperties");
+
+ // Test that the entry has at least one test function.
+ ok(supported_properties[prop].length > 0,
+ "property " + prop + " must have at least one test function");
+}
+
+// Return a consistent sampling of |count| values out of |array|.
+function sample_array(array, count) {
+ if (count <= 0) {
+ ok(false, "unexpected count");
+ return [];
+ }
+ var ratio = array.length / count;
+ if (ratio <= 1) {
+ return array;
+ }
+ var result = new Array(count);
+ for (let i = 0; i < count; ++i) {
+ result[i] = array[Math.floor(i * ratio)];
+ }
+ return result;
+}
+
+// Test that transitions don't do anything (i.e., aren't supported) on
+// the properties not in our test list above (and not transition
+// properties themselves).
+for (prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if (!(prop in supported_properties) &&
+ !skipped_transitionable_properties.includes(prop) &&
+ info.type != CSS_TYPE_TRUE_SHORTHAND &&
+ info.type != CSS_TYPE_LEGACY_SHORTHAND &&
+ !("alias_for" in info) &&
+ !prop.match(/^transition-/) &&
+ prop != "mask") {
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ div.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+
+ var all_values = info.initial_values.concat(info.other_values);
+
+ if (all_values.length > 50) {
+ // Since we're using an O(N^2) algorithm here, reduce the list of
+ // values that we want to test. (This test is really only testing
+ // that somebody didn't make a property animatable without
+ // modifying this test. The odds of somebody doing that without
+ // making at least one of the many pairs of values we have left
+ // animatable seems pretty low, at least relative to the chance
+ // that any pair of the values listed in property_database.js is
+ // animatable.)
+ //
+ // That said, we still try to use all of the start of the list on
+ // the assumption that the more basic values are likely to be at
+ // the beginning of the list.
+ all_values = [].concat(info.initial_values.slice(0,2),
+ sample_array(info.initial_values.slice(2), 6),
+ info.other_values.slice(0, 10),
+ sample_array(info.other_values.slice(10), 40));
+ }
+
+ var all_computed = [];
+ for (var idx in all_values) {
+ let val = all_values[idx];
+ div.style.setProperty(prop, val, "");
+ all_computed.push(cs.getPropertyValue(prop));
+ }
+ div.style.removeProperty(prop);
+
+ div.style.setProperty("transition", prop + " 20s linear", "");
+ for (let i = 0; i < all_values.length; ++i) {
+ for (let j = i + 1; j < all_values.length; ++j) {
+ div.style.setProperty(prop, all_values[i], "");
+ is(cs.getPropertyValue(prop), all_computed[i],
+ "transitions not supported for property " + prop +
+ " value " + all_values[i]);
+ div.style.setProperty(prop, all_values[j], "");
+ is(cs.getPropertyValue(prop), all_computed[j],
+ "transitions not supported for property " + prop +
+ " value " + all_values[j]);
+ }
+ }
+
+ div.style.removeProperty("transition");
+ div.style.removeProperty(prop);
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ div.style.removeProperty(prereq);
+ }
+ }
+ }
+}
+
+// Do 4-second linear transitions with -1 second transition delay and
+// linear timing function so that we can expect the transition to be
+// one quarter of the way through the value space right after changing
+// the property.
+div.style.setProperty("transition-duration", "4s", "");
+div.style.setProperty("transition-delay", "-1s", "");
+div.style.setProperty("transition-timing-function", "linear", "");
+for (prop in supported_properties) {
+ var tinfo = supported_properties[prop];
+ var info = gCSSProperties[prop];
+
+ isnot(info.type, CSS_TYPE_TRUE_SHORTHAND,
+ prop + " must not be a shorthand");
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ // We don't want the 19px font-size prereq of line-height, since we
+ // want to leave it 20px.
+ if (prop != "line-height" || prereq != "font-size") {
+ div.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+ }
+
+ for (var idx in tinfo) {
+ tinfo[idx](prop);
+ }
+
+ // Make sure to unset the property and stop transitions on it.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.removeProperty(prop);
+ cs.getPropertyValue(prop);
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ div.style.removeProperty(prereq);
+ }
+ }
+}
+div.style.removeProperty("transition");
+
+function get_distance(prop, v1, v2)
+{
+ return SpecialPowers.DOMWindowUtils
+ .computeAnimationDistance(div, prop, v1, v2);
+}
+
+function check_distance(prop, start, quarter, end)
+{
+ var sq = get_distance(prop, start, quarter);
+ var se = get_distance(prop, start, end);
+ var qe = get_distance(prop, quarter, end);
+
+ ok(Math.abs((sq * 4 - se) / se) < 0.0001, "property '" + prop + "': distance " + sq + " from start '" + start + "' to quarter '" + quarter + "' should be quarter distance " + se + " from start '" + start + "' to end '" + end + "'");
+ ok(Math.abs((qe * 4 - se * 3) / se) < 0.0001, "property '" + prop + "': distance " + qe + " from quarter '" + quarter + "' to end '" + end + "' should be three quarters distance " + se + " from start '" + start + "' to end '" + end + "'");
+}
+
+function test_length_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px", "");
+ is(cs.getPropertyValue(prop), "4px",
+ "length-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px", "");
+ is(cs.getPropertyValue(prop), "6px",
+ "length-valued property " + prop + ": interpolation of lengths");
+ check_distance(prop, "4px", "6px", "12px");
+}
+
+function test_length_clamped(prop) {
+ test_length_clamped_or_unclamped(prop, true);
+}
+
+function test_length_unclamped(prop) {
+ test_length_clamped_or_unclamped(prop, false);
+}
+
+function test_length_clamped_or_unclamped(prop, is_clamped) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px", "");
+ let zero_val = cs.getPropertyValue(prop); // Flushes
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100px", "");
+ (is_clamped ? is : isnot)(cs.getPropertyValue(prop), zero_val,
+ "length-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+// Test transition to/from the special 'flex-basis: content' keyword.
+function test_flex_basis_content_transition(prop) {
+ is(prop, "flex-basis", "this test function should only be called for 'flex-basis'");
+
+ // Test transition from length to 'content':
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "8px", "");
+ is(cs.getPropertyValue(prop), "8px",
+ "property " + prop + ": computed value before transition to 'content'");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "content", "");
+ is(cs.getPropertyValue(prop), "content",
+ "property " + prop + ": transition to 'content' (should be discrete)");
+
+ // Test transition from 'content' to length:
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "content", "");
+ is(cs.getPropertyValue(prop), "content",
+ "property " + prop + ": computed value before transition from 'content'");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "6px", "");
+ is(cs.getPropertyValue(prop), "6px",
+ "property " + prop + ": transition from 'content' (should be discrete)");
+}
+
+// Test using float values in the range [0, 1] (e.g. opacity)
+function test_float_zeroToOne_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0.3", "");
+ is(cs.getPropertyValue(prop), "0.3",
+ "float-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "0.8", "");
+ is(cs.getPropertyValue(prop), "0.425",
+ "float-valued property " + prop + ": interpolation of floats");
+ check_distance(prop, "0.3", "0.425", "0.8");
+}
+
+function test_float_zeroToOne_clamped(prop) {
+ test_float_zeroToOne_clamped_or_unclamped(prop, true);
+}
+function test_float_zeroToOne_unclamped(prop) {
+ test_float_zeroToOne_clamped_or_unclamped(prop, false);
+}
+
+function test_float_zeroToOne_clamped_or_unclamped(prop, is_clamped) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0", "");
+ is(cs.getPropertyValue(prop), "0",
+ "float-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "1", "");
+ (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0",
+ "float-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+// Test using float values in the range [1, infinity)
+function test_float_aboveOne_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "1", "");
+ is(cs.getPropertyValue(prop), "1",
+ "float-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "2.1", "");
+ is(cs.getPropertyValue(prop), "1.275",
+ "float-valued property " + prop + ": interpolation of floats");
+ check_distance(prop, "1", "1.275", "2.1");
+}
+
+function test_float_aboveZero_clamped(prop) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0", "");
+ is(cs.getPropertyValue(prop), "0",
+ "float-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "5", "");
+ is(cs.getPropertyValue(prop), "0",
+ "float-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_percent_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "25%", "");
+ var av = cs.getPropertyValue(prop);
+ var a = any_unit_to_num(av);
+ div.style.setProperty(prop, "75%", "");
+ var bv = cs.getPropertyValue(prop);
+ var b = any_unit_to_num(bv);
+ isnot(b, a, "different percentages (" + av + " and " + bv +
+ ") should be different for " + prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "25%", "");
+ var res = cs.getPropertyValue(prop);
+ is(any_unit_to_num(res) * 4, 3 * b + a,
+ "percent-valued property " + prop + ": interpolation of percents: " +
+ res + " should be a quarter of the way between " + bv + " and " + av);
+ ok(has_num(res),
+ "percent-valued property " + prop + ": percent computes to number");
+ check_distance(prop, "25%", "37.5%", "75%");
+}
+
+function test_percent_clamped(prop) {
+ test_percent_clamped_or_unclamped(prop, true);
+}
+
+function test_percent_unclamped(prop) {
+ test_percent_clamped_or_unclamped(prop, false);
+}
+
+function test_percent_clamped_or_unclamped(prop, is_clamped) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0%", "");
+ var zero_val = cs.getPropertyValue(prop); // flushes too
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "150%", "");
+ (is_clamped ? is : isnot)(cs.getPropertyValue(prop), zero_val,
+ "percent-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+// FIXME: This doesn't deal well with properties for which the resolved value
+// is not the used value, like stroke-dashoffset or stroke-width.
+function test_length_percent_calc_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0%", "");
+ var av = cs.getPropertyValue(prop);
+ var a = any_unit_to_num(av);
+ div.style.setProperty(prop, "100%", "");
+ var bv = cs.getPropertyValue(prop);
+ var b = any_unit_to_num(bv);
+ div.style.setProperty(prop, "100px", "");
+ var cv = cs.getPropertyValue(prop);
+ var c = any_unit_to_num(cv);
+ isnot(b, a, "different percentages (" + av + " and " + bv +
+ ") should be different for " + prop);
+
+ div.style.setProperty(prop, "50%", "");
+ var v1v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v1v) * 2, a + b,
+ "computed value before transition for " + prop + ": '" +
+ v1v + "' should be halfway " +
+ "between '" + av + "' + and '" + bv + "'.");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "200px", "");
+ var v2v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v2v) * 8, 5*a + 3*b + 4*c,
+ "interpolation between length and percent for " + prop + ": '"
+ + v2v + "'");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(25% + 100px)", "");
+ v1v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v1v) * 4, b + 4*c,
+ "computed value before transition for " + prop + ": '" + v1v + "'");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "75%", "");
+ v2v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v2v) * 8, 5*a + 3*b + 6*c,
+ "interpolation between calc() and percent for " + prop + ": '" +
+ v2v + "'");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "150px", "");
+ v1v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v1v) * 2, c * 3,
+ "computed value before transition for " + prop + ": '" + v1v + "'");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(50% + 50px)", "");
+ v2v = cs.getPropertyValue(prop);
+ is(any_unit_to_num(v2v) * 8, 7 * a + b + 10*c,
+ "interpolation between length and calc() for " + prop + ": '" +
+ v2v + "'");
+
+ check_distance(prop, "50%", "calc(37.5% + 50px)", "200px");
+ check_distance(prop, "calc(25% + 100px)", "calc(37.5% + 75px)",
+ "75%");
+ check_distance(prop, "150px", "calc(125px + 12.5%)",
+ "calc(50% + 50px)");
+}
+
+// This can deal well with properties for which the computed value
+// is not the used value, e.g. translate.
+function test_calc_wrapped_calc_transition(prop) {
+ // Test interpolation that computes to calc() (transition from % to px)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "20%", "");
+ is(cs.getPropertyValue(prop), "20%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 3px)",
+ "property " + prop + ": interpolation that computes to calc()");
+
+ check_distance(prop, "20%", "calc(15% + 3px)", "12px");
+
+ // Test interpolation that computes to calc() (transition from px to %)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "12px", "");
+ is(cs.getPropertyValue(prop), "12px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "20%", "");
+ is(cs.getPropertyValue(prop), "calc(5% + 9px)",
+ "property " + prop + ": interpolation that computes to calc()");
+
+ check_distance(prop, "12px", "calc(5% + 9px)", "20%");
+
+ // Test interpolation between calc() and non-calc()
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(40px + 10%)", "");
+ is(cs.getPropertyValue(prop), "calc(10% + 40px)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "30%", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 30px)",
+ "property " + prop + ": interpolation between calc() and non-calc()");
+
+ check_distance(prop, "calc(40px + 10%)", "calc(30px + 15%)", "30%");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "16px", "");
+ is(cs.getPropertyValue(prop), "16px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(8px + 60%)", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 14px)",
+ "property " + prop + ": interpolation between calc() and non-calc()");
+
+ check_distance(prop, "16px", "calc(14px + 15%)", "calc(8px + 60%)");
+}
+
+function test_number_transition(prop) {
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '10';
+ is(cs[prop], '10',
+ `number property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '50';
+ is(cs[prop], '20', `number property ${prop}: interpolation of numbers`);
+ check_distance(prop, '10', '20', '50');
+}
+
+function test_angle_transition(prop) {
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '45deg';
+ is(cs[prop], '45deg',
+ `angle property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '145deg';
+ is(cs[prop], '70deg',
+ `angle property ${prop}: interpolation of angles`);
+ check_distance(prop, '45deg', '70deg', '145deg');
+}
+
+function get_color_options(options) {
+ let {
+ get_color = x => x,
+ set_color = x => x,
+ is_shorthand = false,
+ } = options;
+ return { get_color, set_color, is_shorthand };
+}
+
+function test_color_transition(prop, options={}) {
+ let { get_color, set_color, is_shorthand } = get_color_options(options);
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, set_color("rgb(255, 28, 0)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(255, 28, 0)",
+ "color-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color("rgb(75, 84, 128)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(210, 42, 32)",
+ "color-valued property " + prop + ": interpolation of colors");
+
+ if (!is_shorthand) {
+ check_distance(prop, set_color("rgb(255, 28, 0)"),
+ set_color("rgb(210, 42, 32)"),
+ set_color("rgb(75, 84, 128)"));
+ }
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, set_color("rgb(0, 255, 0)"), "");
+ var color = get_color(cs.getPropertyValue(prop));
+ var vals = color.match(/rgb\(([^, ]*), ([^, ]*), ([^, ]*)\)/);
+ is(vals.length, 4,
+ "color-valued property " + prop + ": flush before clamping test (length)");
+ is(vals[1], "0",
+ "color-valued property " + prop + ": flush before clamping test (red)");
+ is(vals[2], "255",
+ "color-valued property " + prop + ": flush before clamping test (green)");
+ is(vals[3], "0",
+ "color-valued property " + prop + ": flush before clamping test (blue)");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color("rgb(255, 0, 128)"), "");
+ // FIXME: Once we support non-sRGB colors, these tests will need fixing.
+ color = get_color(cs.getPropertyValue(prop));
+ vals = color.match(/rgb\(([^, ]*), ([^, ]*), ([^, ]*)\)/);
+ is(vals.length, 4,
+ "color-valued property " + prop + ": clamping of negatives (length)");
+ is(vals[1], "0",
+ "color-valued property " + prop + ": clamping of negatives (red)");
+ is(vals[2], "255",
+ "color-valued property " + prop + ": clamping of above-range (green)");
+ is(vals[3], "0",
+ "color-valued property " + prop + ": clamping of negatives (blue)");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_currentcolor_transition(prop, options={}) {
+ let { get_color, set_color } = get_color_options(options);
+
+ const msg_prefix = `color-valued property ${prop}: `;
+ div.style.setProperty("transition-property", "none", "");
+ (prop == "color" ? div.parentNode : div).style.
+ setProperty("color", "rgb(128, 0, 0)", "");
+ div.style.setProperty(prop, set_color("rgb(0, 0, 128)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(0, 0, 128)",
+ msg_prefix + "computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color("currentcolor"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(32, 0, 96)",
+ msg_prefix + "interpolation of rgb color and currentcolor");
+
+ if (prop != "color") {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty("color", "rgb(128, 0, 0)", "");
+ div.style.setProperty(prop, set_color("rgb(0, 0, 128)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(0, 0, 128)",
+ msg_prefix + "computed value before transition");
+ div.style.setProperty("transition-property", `color, ${prop}`, "");
+ div.style.setProperty("color", "rgb(0, 128, 0)", "");
+ div.style.setProperty(prop, set_color("currentcolor"), "");
+ is(cs.getPropertyValue("color"), "rgb(96, 32, 0)",
+ "interpolation of rgb color property");
+ is(get_color(cs.getPropertyValue(prop)), "rgb(24, 8, 96)",
+ msg_prefix + "interpolation of rgb color and interpolated currentcolor");
+ }
+
+ div.style.setProperty("transition-property", "none", "");
+ (prop == "color" ? div.parentNode : div).style.
+ setProperty("color", "rgba(128, 0, 0, 0.6)", "");
+ div.style.setProperty(prop, set_color("rgba(0, 0, 128, 0.8)"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgba(0, 0, 128, 0.8)",
+ msg_prefix + "computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color("currentcolor"), "");
+ is(get_color(cs.getPropertyValue(prop)), "rgba(26, 0, 102, 0.75)",
+ msg_prefix + "interpolation of rgba color and currentcolor");
+
+ // It is not possible to check distance, because there is a hidden
+ // dimension for ratio of currentcolor.
+
+ (prop == "color" ? div.parentNode : div).style.removeProperty("color");
+}
+
+function test_auto_color_transition(prop, options={}) {
+ let { get_color, set_color } = get_color_options(options);
+
+ const msg_prefix = `color-valued property ${prop}: `;
+ const test_color = "rgb(51, 102, 153)";
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "auto", "");
+ if (prop == "scrollbar-color") {
+ is(cs.getPropertyValue(prop), "auto",
+ msg_prefix + "auto should not be resolved to rgb color");
+ } else {
+ let used_value_of_auto = get_color(cs.getPropertyValue(prop));
+ isnot(used_value_of_auto, test_color,
+ msg_prefix + "ensure used auto value is different than our test color");
+ }
+
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, set_color(test_color), "");
+ is(get_color(cs.getPropertyValue(prop)), test_color,
+ msg_prefix + "not interpolatable between auto and rgb color");
+}
+
+function get_color_from_shorthand_value(value) {
+ var m = value.match(/rgba?\([^, ]*, [^, ]*, [^, ]*(?:, [^, ]*)?\)/);
+ isnot(m, null, "shorthand property value should contain color");
+ return m[0];
+}
+
+function test_color_shorthand_transition(prop) {
+ test_color_transition(prop, {
+ get_color: get_color_from_shorthand_value,
+ is_shorthand: true,
+ });
+}
+
+function test_currentcolor_shorthand_transition(prop) {
+ test_currentcolor_transition(prop, {
+ get_color: get_color_from_shorthand_value,
+ is_shorthand: true,
+ });
+}
+
+function test_scrollbar_color_transition(prop) {
+ function split_colors(value) {
+ const colors = value.match(/^(rgba?\(.+?\)) (rgba?\(.+?\))$/);
+ isnot(colors, null, "scrollbar-color should consist of two colors");
+ return { thumb: colors[1], track: colors[2] };
+ }
+ const TEST_FUNCS = [
+ test_color_transition,
+ test_currentcolor_transition,
+ test_auto_color_transition,
+ ];
+ for (let test_func of TEST_FUNCS) {
+ test_func(prop, {
+ get_color: value => split_colors(value).thumb,
+ set_color: value => value + " blue",
+ });
+ test_func(prop, {
+ get_color: value => split_colors(value).track,
+ set_color: value => "blue " + value,
+ });
+ }
+}
+
+function test_shape_or_url_equals(computedValStr, expected)
+{
+ // Check simple case "none"
+ if (computedValStr == "none" && computedValStr == expected[0]) {
+ return true;
+ }
+ // We will update the expected list in this function for checking the result,
+ // so we clone it first to avoid affecting the input parameter.
+ var expectedList = expected.slice();
+
+ var start = String(computedValStr);
+
+ var regBox = /\s*(content\-box|padding\-box|border\-box|margin\-box|view\-box|stroke\-box|fill\-box)/
+ var matches = computedValStr.split(regBox);
+ var expectRefBox = typeof expectedList[expectedList.length - 1] == "string" &&
+ expectedList[expectedList.length - 1].match(regBox) !== null;
+
+ // Found a reference box? Format: "shape()" or "shape() reference-box"
+ if (matches.length > 1) {
+ // Our split() did actually split the string, which means computedValStr
+ // contains a reference box. That reference box should be at the end,
+ // which means split() will have produced an empty string as the final
+ // entry in |matches|. Let's first ditch that empty string.
+ var trailingJunk = matches.pop();
+ is(trailingJunk, "", "reference box shouldn't have anything after it");
+
+ // Do we expect a reference box?
+ if (!expectRefBox) {
+ ok(false, "unexpected reference box found");
+ matches.pop(); // Get rid of it, so we can test the rest...
+ } else {
+ is(matches.pop(), expectedList.pop(), "Reference boxes should match");
+ }
+ } else {
+ // No reference box found. Did we expect one?
+ if (expectRefBox) {
+ ok(false, "expected reference box");
+ return false;
+ }
+ }
+ computedValStr = matches[0];
+ if (expectedList.length == 0) {
+ if (computedValStr == "") {
+ return true;
+ }
+ ok(false, "expected basic shape");
+ return false;
+ }
+
+ // The regular expression does not filter out the last parenthesis.
+ // Remove last character for now.
+ is(computedValStr.substring(computedValStr.length - 1, computedValStr.length),
+ ')', "Function should have close-paren");
+ computedValStr = computedValStr.substring(0, computedValStr.length - 1);
+
+ var regShape = /\)*\s*(circle|ellipse|polygon|inset|url)\(/
+ matches = computedValStr.split(regShape);
+ // First item must be empty. All other items are of functionName, functionValue.
+ if (!matches || matches.shift() != "") {
+ ok(false, "invalid value or unknown shape function");
+ return false;
+ }
+
+ // Check argument values.
+ if (matches[1] != expectedList[1]) {
+ ok(false, "function parameters mismatch");
+ return false;
+ }
+
+ return true;
+}
+
+function test_path_function_equals(computedValStr, expectedList)
+{
+ // Check simple case "none"
+ if (expectedList.length === 1 && computedValStr === expectedList[0]) {
+ return true;
+ }
+
+ var regex = /([a-z]+)\((.*)\)/;
+ matches = computedValStr.match(regex)
+ if (!matches || matches[0] != computedValStr) {
+ ok(false, "Invalid function value");
+ return false;
+ }
+
+ // Bug 1480665: Support ray() for motion path. For now, only path(...) is
+ // acceptable.
+ if (matches[1] != "path") {
+ ok(false, "Only support path function");
+ return false;
+ }
+
+ // Check argument values.
+ if (matches[2] != expectedList[1]) {
+ ok(false, "Function parameters mismatch");
+ return false;
+ }
+
+ return true;
+}
+
+function filter_function_list_equals(computedValStr, expectedList)
+{
+ // Check simple case "none"
+ if (computedValStr == "none" && computedValStr == expectedList[0]) {
+ return true;
+ }
+
+ // The regular expression does not filter out the last parenthesis.
+ // Remove last character for now.
+ is(computedValStr.substring(computedValStr.length - 1, computedValStr.length),
+ ')', "Last character should be close-paren");
+ computedValStr = computedValStr.substring(0, computedValStr.length - 1);
+
+ var reg = /\)*\s*(blur|brightness|contrast|grayscale|hue\-rotate|invert|opacity|saturate|sepia|drop\-shadow|url)\(/
+ var matches = computedValStr.split(reg);
+ // First item must be empty. All other items are of functionName, functionValue.
+ if (!matches || matches.shift() != "") {
+ ok(false, "computed style of 'filter' isn't in the format we expect");
+ return false;
+ }
+
+ // Odd items are the function name, even items the function value.
+ if (!matches.length || matches.length % 2 ||
+ expectedList.length != matches.length) {
+ ok(false, "computed style of 'filter' isn't in the format we expect");
+ return false;
+ }
+ for (let i = 0; i < matches.length; i += 2) {
+ var functionName = matches[i];
+ var functionValue = matches[i+1];
+ var expected = expectedList[i+1]
+ var tolerance = 0;
+ // Check if we have the expected function.
+ if (functionName != expectedList[i]) {
+ return false;
+ }
+ if (functionName == "blur") {
+ // Last two characters must be "px".
+ if (functionValue.search("px") != functionValue.length - 2) {
+ return false;
+ }
+ functionValue = functionValue.substring(0, functionValue.length - 2);
+ } else if (functionName == "hue-rotate") {
+ // Just check for string equality.
+ return functionValue == expected;
+ } else if (functionName == "drop-shadow" || functionName == "url") {
+ if (functionValue != expected) {
+ return false;
+ }
+ continue;
+ }
+ // Check if string is not a number or difference is not in tolerance level.
+ if (isNaN(functionValue) ||
+ Math.abs(parseFloat(functionValue) - expected) > tolerance) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function test_basic_shape_or_url_transition(prop) {
+ let tests = basicShapesTests;
+ if (prop === "clip-path") {
+ // Clip-path won't resolve fragment URLs.
+ tests = tests.concat(basicShapesWithFragmentUrlTests);
+ }
+
+ for (let test of tests) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, test.start, "");
+ cs.getPropertyValue(prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, test.end, "");
+ var actual = cs.getPropertyValue(prop);
+ ok(test_shape_or_url_equals(actual, test.expected),
+ prop + " property is " + actual + " expected values of " +
+ test.expected);
+ }
+}
+
+function test_path_function(prop) {
+ let tests = pathFunctionTests;
+ if (prop === "clip-path") {
+ // The syntax of path() in clip-path has fill-rule, so we have to test more.
+ tests = tests.concat(clipPathPathFunctionTests);
+ }
+
+ for (const test of tests) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, test.start, "");
+ cs.getPropertyValue(prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, test.end, "");
+ const actual = cs.getPropertyValue(prop);
+ ok(test_path_function_equals(actual, test.expected),
+ prop + " property is " + actual + " expected values of " +
+ test.expected[0] + "(" + test.expected[1] + ")");
+ }
+}
+
+function test_filter_transition(prop) {
+ for (let i in filterTests) {
+ var test = filterTests[i];
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, test.start, "");
+ cs.getPropertyValue(prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, test.end, "");
+ var actual = cs.getPropertyValue(prop);
+ ok(filter_function_list_equals(actual, test.expected),
+ "Filter property is " + actual + " expected values of " +
+ test.expected);
+ }
+}
+
+function test_shadow_transition(prop) {
+ var origTimingFunc = div.style.getPropertyValue("transition-timing-function");
+ var spreadStr = (prop == "box-shadow") ? " 0px" : "";
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "4px 8px 3px red", "");
+ is(cs.getPropertyValue(prop), "rgba(255, 0, 0, 0.25) 1px 2px 0.75px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation of shadows");
+ check_distance(prop, "none", "rgba(255, 0, 0, 0.25) 1px 2px 0.75px",
+ "4px 8px 3px red");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "#038000 4px 4px, 2px 2px blue", "");
+ is(cs.getPropertyValue(prop), "rgb(3, 128, 0) 4px 4px 0px" + spreadStr + ", rgb(0, 0, 255) 2px 2px 0px" + spreadStr,
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "8px 8px 8px red", "");
+ is(cs.getPropertyValue(prop), "rgb(66, 96, 0) 5px 5px 2px" + spreadStr + ", rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation of shadows");
+ check_distance(prop, "#038000 4px 4px, 2px 2px blue",
+ "rgb(66, 96, 0) 5px 5px 2px, rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px",
+ "8px 8px 8px red");
+
+ if (prop == "box-shadow") {
+ div.style.setProperty(prop, "8px 8px 8px red inset", "");
+ is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px inset",
+ "shadow-valued property " + prop + ": non-interpolable cases");
+ div.style.setProperty(prop, "8px 8px 8px 8px red inset", "");
+ is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 2px inset",
+ "shadow-valued property " + prop + ": interpolation of spread");
+ // Leave in same state whether in the |if| or not.
+ div.style.setProperty(prop, "8px 8px 8px red", "");
+ is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px",
+ "shadow-valued property " + prop + ": non-interpolable cases");
+ check_distance(prop, "8px 8px 8px red inset",
+ "rgb(255, 0, 0) 8px 8px 8px 2px inset",
+ "8px 8px 8px 8px red inset");
+ }
+
+ // Transition beween values with color and without color.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty("color", "rgb(3, 0, 0)", "");
+ div.style.setProperty(prop, "2px 2px 2px", "");
+ is(cs.getPropertyValue(prop), "rgb(3, 0, 0) 2px 2px 2px" + spreadStr,
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "8px 8px 8px red", "");
+ is(cs.getPropertyValue(prop), "rgb(66, 0, 0) 3.5px 3.5px 3.5px" + spreadStr,
+ "shadow-valued property " + prop +
+ ": interpolation values with/without color");
+
+ // Transition beween values without color.
+ var defaultColor = cs.getPropertyValue("color") + " ";
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "2px 2px 2px", "");
+ is(cs.getPropertyValue(prop), defaultColor + "2px 2px 2px" + spreadStr,
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "6px 14px 10px", "");
+ is(cs.getPropertyValue(prop), defaultColor + "3px 5px 4px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation without color");
+ check_distance(prop, "2px 2px 2px", "3px 5px 4px", "6px 14px 10px");
+
+ // Transition between values with currentcolor transitioning.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty("color", "rgb(0, 255, 0)", "");
+ div.style.setProperty(prop, "2px 2px 2px", "");
+ is(cs.getPropertyValue(prop), "rgb(0, 255, 0) 2px 2px 2px" + spreadStr,
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", "color, " + prop, "");
+ div.style.setProperty("color", "rgb(0, 0, 255)", "");
+ div.style.setProperty(prop, "6px 10px 14px red", "");
+ is(cs.getPropertyValue(prop), "rgb(64, 143, 48) 3px 4px 5px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation with interpolating" +
+ "currentcolor");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0px 0px black", "");
+ is(cs.getPropertyValue(prop), "rgb(0, 0, 0) 0px 0px 0px" + spreadStr,
+ "shadow-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "10px 10px 10px black", "");
+ var vals = cs.getPropertyValue(prop).split(" ");
+ is(vals.length, 6 + (prop == "box-shadow"), "unexpected number of values");
+ is(vals.slice(0, 3).join(" "), "rgb(0, 0, 0)",
+ "shadow-valued property " + prop + " (color): clamping of negatives");
+ isnot(vals[3], "0px",
+ "shadow-valued property " + prop + " (x): clamping of negatives");
+ isnot(vals[4], "0px",
+ "shadow-valued property " + prop + " (y): clamping of negatives");
+ is(vals[5], "0px",
+ "shadow-valued property " + prop + " (radius): clamping of negatives");
+ if (prop == "box-shadow") {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0px 0px 0px black", "");
+ is(cs.getPropertyValue(prop), "rgb(0, 0, 0) 0px 0px 0px 0px",
+ "shadow-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "10px 10px 10px 10px black", "");
+ var vals = cs.getPropertyValue(prop).split(" ");
+ is(vals.length, 7, "unexpected number of values");
+ is(vals.slice(0, 3).join(" "), "rgb(0, 0, 0)",
+ "shadow-valued property " + prop + " (color): clamping of negatives");
+ isnot(vals[3], "0px",
+ "shadow-valued property " + prop + " (x): clamping of negatives");
+ isnot(vals[4], "0px",
+ "shadow-valued property " + prop + " (y): clamping of negatives");
+ is(vals[5], "0px",
+ "shadow-valued property " + prop + " (radius): clamping of negatives");
+ isnot(vals[6], "0px",
+ "shadow-valued property " + prop + " (spread): clamping of negatives");
+ }
+
+ // A test case that timing function produces values greater than 1.0.
+ div.style.setProperty("transition-timing-function",
+ // This function produces 1.2989961788069297 at 25%.
+ "cubic-bezier(0, 1.5, 0, 1.5)", "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "shadow-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "0px 0px 0px rgba(100, 100, 100, 0.5)", "");
+ // The alpha value, 0.5 * 1.2989961788069297 * 255, is 165.622012798, and then
+ // converted to 0.649.
+ is(cs.getPropertyValue(prop), "rgba(100, 100, 100, 0.649) 0px 0px 0px" + spreadStr,
+ "shadow-valued property " + prop + ": interpolation of shadows with " +
+ "timing function which produces values greater than 1.0");
+
+ div.style.setProperty("transition-timing-function", origTimingFunc, "");
+}
+
+function test_dasharray_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "3", "");
+ is(cs.getPropertyValue(prop), "3px",
+ "dasharray-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "15px", "");
+ is(cs.getPropertyValue(prop), "6px",
+ "dasharray-valued property " + prop + ": interpolation of dasharray");
+ check_distance(prop, "3", "6", "15px");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "dasharray-valued property " + prop + ": non-interpolability of none");
+ div.style.setProperty(prop, "6,8px,4,4", "");
+ is(cs.getPropertyValue(prop), "6px, 8px, 4px, 4px",
+ "dasharray-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty(prop, "14, 12,16,16px", "");
+ is(cs.getPropertyValue(prop), "8px, 9px, 7px, 7px",
+ "dasharray-valued property " + prop + ": interpolation of dasharray");
+ check_distance(prop, "6,8px,4,4", "8,9,7,7", "14, 12,16,16px");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "dasharray-valued property " + prop + ": non-interpolability of none");
+ div.style.setProperty(prop, "8,16,4", "");
+ is(cs.getPropertyValue(prop), "8px, 16px, 4px",
+ "dasharray-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty(prop, "4,8,12,16", "");
+ is(cs.getPropertyValue(prop), "7px, 14px, 6px, 10px, 13px, 5px, 9px, 16px, 4px, 8px, 15px, 7px",
+ "dasharray-valued property " + prop + ": interpolation of dasharray");
+ check_distance(prop, "8,16,4", "7, 14, 6, 10, 13, 5, 9, 16, 4, 8, 15, 7",
+ "4,8,12,16");
+ div.style.setProperty(prop, "2,50%,6,10", "");
+ is(cs.getPropertyValue(prop),
+ "5.75px, calc(12.5% + 10.5px), 6px, 10px, 10.25px, calc(12.5% + 3.75px), 8.25px, 14.5px, 3.5px, calc(12.5% + 6px), 12.75px, 7.75px",
+ "dasharray-valued property " + prop + ": interpolability of mixed units");
+ div.style.setProperty(prop, "none", "");
+ is(cs.getPropertyValue(prop), "none",
+ "dasharray-valued property " + prop + ": non-interpolability of none");
+ div.style.setProperty(prop, "2,50%,6,10", "");
+ is(cs.getPropertyValue(prop), "2px, 50%, 6px, 10px",
+ "dasharray-valued property " + prop + ": non-interpolability of none");
+ div.style.setProperty(prop, "6,30%,2,2", "");
+ is(cs.getPropertyValue(prop), "3px, 45%, 5px, 8px",
+ "dasharray-valued property " + prop + ": interpolation of dasharray");
+ check_distance(prop, "2,50%,6,10", "3, 45%, 5, 8", "6,30%,2,2");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0,0%", "");
+ is(cs.getPropertyValue(prop), "0px, 0%",
+ "dasharray-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "5px, 25%", "");
+ is(cs.getPropertyValue(prop), "0px, 0%",
+ "dasharray-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_radius_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+
+ // FIXME: Test a square for now, since we haven't updated to the spec
+ // for vertical components being relative to the height.
+ // Note: We use powers of two here so the floating-point math comes out
+ // nicely.
+ div.style.setProperty("width", "256px", "");
+ div.style.setProperty("height", "256px", "");
+ div.style.setProperty("border", "none", "");
+ div.style.setProperty("padding", "0", "");
+
+ div.style.setProperty(prop, "3px", "");
+ is(cs.getPropertyValue(prop), "3px",
+ "radius-valued property " + prop +
+ ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "15px", "");
+ is(cs.getPropertyValue(prop), "6px",
+ "radius-valued property " + prop + ": interpolation of radius");
+ check_distance(prop, "3px", "6px", "15px");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "12.5%", "");
+ is(cs.getPropertyValue(prop), "12.5%",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "25%", "");
+ is(cs.getPropertyValue(prop), "15.625%",
+ "radius-valued property " + prop + ": interpolation of radius");
+ check_distance(prop, "12.5%", "15.625%", "25%");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "3px 8px", "");
+ is(cs.getPropertyValue(prop), "3px 8px",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "15px 12px", "");
+ is(cs.getPropertyValue(prop), "6px 9px",
+ "radius-valued property " + prop + ": interpolation of radius");
+ check_distance(prop, "3px 8px", "6px 9px", "15px 12px");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "12.5% 6.25%", "");
+ is(cs.getPropertyValue(prop), "12.5% 6.25%",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "25%", "");
+ is(cs.getPropertyValue(prop), "15.625% 10.9375%",
+ "radius-valued property " + prop + ": interpolation of radius");
+ check_distance(prop, "12.5% 6.25%", "15.625% 10.9375%", "25%");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "6.25% 12.5%", "");
+ is(cs.getPropertyValue(prop), "6.25% 12.5%",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "64px 16px", "");
+ is(cs.getPropertyValue(prop), "calc(4.6875% + 16px) calc(9.375% + 4px)",
+ "radius-valued property " + prop + ": interpolation of radius with mixed units");
+ check_distance(prop, "6.25% 12.5%",
+ "calc(4.6875% + 16px) calc(9.375% + 4px)",
+ "64px 16px");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(5px) 10px", "");
+ is(cs.getPropertyValue(prop), "5px 10px",
+ "radius-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(25px) calc(50px)", "");
+ is(cs.getPropertyValue(prop), "10px 20px",
+ "radius-valued property " + prop + ": interpolation of radius with calc() units");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0px", "");
+ is(cs.getPropertyValue(prop), "0px",
+ "radius-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "10px 20px", "");
+ is(cs.getPropertyValue(prop), "0px",
+ "radius-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+
+ div.style.removeProperty("width");
+ div.style.removeProperty("height");
+ div.style.removeProperty("border");
+ div.style.removeProperty("padding");
+}
+
+function test_integer_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4", "");
+ is(cs.getPropertyValue(prop), "4",
+ "integer-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "-14", "");
+ is(cs.getPropertyValue(prop), "0",
+ "integer-valued property " + prop + ": interpolation of integers");
+ check_distance(prop, "6", "1", "-14");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "-4", "");
+ is(cs.getPropertyValue(prop), "-4",
+ "integer-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "8", "");
+ is(cs.getPropertyValue(prop), "-1",
+ "integer-valued property " + prop + ": interpolation of integers");
+ check_distance(prop, "-4", "-1", "8");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0", "");
+ is(cs.getPropertyValue(prop), "0",
+ "integer-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100", "");
+ isnot(cs.getPropertyValue(prop), "0",
+ "integer-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_font_weight(prop) {
+ is(prop, "font-weight", "only designed for one property");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "normal", "");
+ is(cs.getPropertyValue(prop), "400",
+ "font-weight property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "900", "");
+ is(cs.getPropertyValue(prop), "525",
+ "font-weight property " + prop + ": interpolation of font-weights");
+ check_distance(prop, "400", "500", "800");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "900", "");
+ is(cs.getPropertyValue(prop), "900",
+ "font-weight property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100", "");
+ is(cs.getPropertyValue(prop), "700",
+ "font-weight property " + prop + ": interpolation of font-weights");
+ check_distance(prop, "900", "700", "100");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "1", "");
+ is(cs.getPropertyValue(prop), "1",
+ "font-weight property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "1000", "");
+ is(cs.getPropertyValue(prop), "1",
+ "font-weight property " + prop + ": clamping of values");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "1000", "");
+ is(cs.getPropertyValue(prop), "1000",
+ "font-weight property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "1", "");
+ is(cs.getPropertyValue(prop), "1000",
+ "font-weight property " + prop + ": clamping of values");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_grid_gap(prop) {
+ test_length_transition(prop);
+ test_length_clamped(prop);
+ test_percent_transition(prop);
+ test_percent_clamped(prop);
+}
+
+function test_pos_integer_or_keyword_transition(prop, keyword) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4", "");
+ is(cs.getPropertyValue(prop), "4",
+ "integer-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "11", "");
+ is(cs.getPropertyValue(prop), "6",
+ "integer-valued property " + prop + ": interpolation of integers");
+ check_distance(prop, "4", "6", "12");
+ div.style.setProperty(prop, keyword, "");
+ is(cs.getPropertyValue(prop), keyword,
+ "integer-valued property " + prop + ": " + keyword + " not interpolable");
+ div.style.setProperty(prop, "8", "");
+ is(cs.getPropertyValue(prop), "8",
+ "integer-valued property " + prop + ": computed value before transition");
+ div.style.setProperty(prop, "4", "");
+ is(cs.getPropertyValue(prop), "7",
+ "integer-valued property " + prop + ": interpolation of integers");
+ check_distance(prop, "8", "7", "4");
+}
+
+function test_pos_integer_or_auto_transition(prop) {
+ return test_pos_integer_or_keyword_transition(prop, "auto");
+}
+
+function test_pos_integer_or_none_transition(prop) {
+ return test_pos_integer_or_keyword_transition(prop, "none");
+}
+
+function test_integer_at_least_one_clamping(prop) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "1", "");
+ is(cs.getPropertyValue(prop), "1",
+ "integer-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "5", "");
+ is(cs.getPropertyValue(prop), "1",
+ "integer-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_length_pair_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px 6px", "");
+ is(cs.getPropertyValue(prop), "4px 6px",
+ "length-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 10px", "");
+ is(cs.getPropertyValue(prop), "6px 7px",
+ "length-valued property " + prop + ": interpolation of lengths");
+ check_distance(prop, "4px 6px", "6px 7px", "12px 10px");
+}
+
+function test_length_pair_transition_clamped(prop) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0px", "");
+ is(cs.getPropertyValue(prop), "0px 0px",
+ "length-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "30px 50px", "");
+ is(cs.getPropertyValue(prop), "0px 0px",
+ "length-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_length_percent_pair_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px 50%", "");
+ is(cs.getPropertyValue(prop), "4px 5px",
+ "length-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 70%", "");
+ is(cs.getPropertyValue(prop), "6px 5.5px",
+ "length-valued property " + prop + ": interpolation of lengths");
+ check_distance(prop, "4px 50%", "6px 55%", "12px 70%");
+}
+
+function test_length_percent_pair_clamped(prop) {
+ test_length_percent_pair_clamped_or_unclamped(prop, true);
+}
+
+function test_length_percent_pair_unclamped(prop) {
+ test_length_percent_pair_clamped_or_unclamped(prop, false);
+}
+
+function test_length_percent_pair_clamped_or_unclamped(prop, is_clamped) {
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0px 0%", "");
+ var is_zero = function(val) {
+ if (prop == "transform-origin" || prop == "perspective-origin") {
+ // These two properties resolve percentages to pixels.
+ return val == "0px 0px";
+ }
+ return val == "0px 0%";
+ }
+ ok(is_zero(cs.getPropertyValue(prop)),
+ "length+percent-valued property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "30px 25%", "");
+ is(is_zero(cs.getPropertyValue(prop)), is_clamped,
+ "length+percent-valued property " + prop + ": clamping of negatives");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_rect_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "rect(4px, 16px, 12px, 6px)", "");
+ is(cs.getPropertyValue(prop), "rect(4px, 16px, 12px, 6px)",
+ "rect-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "rect(0px, 4px, 4px, 2px)", "");
+ is(cs.getPropertyValue(prop), "rect(3px, 13px, 10px, 5px)",
+ "rect-valued property " + prop + ": interpolation of rects");
+ check_distance(prop, "rect(4px, 16px, 12px, 6px)",
+ "rect(3px, 13px, 10px, 5px)",
+ "rect(0px, 4px, 4px, 2px)");
+ div.style.setProperty(prop, "rect(0px, 6px, 4px, auto)", "");
+ is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)",
+ "rect-valued property " + prop + ": can't interpolate auto components");
+ div.style.setProperty(prop, "rect(0px, 6px, 4px, 2px)", "");
+ div.style.setProperty(prop, "auto", "");
+ is(cs.getPropertyValue(prop), "auto",
+ "rect-valued property " + prop + ": can't interpolate auto components");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "rect(-10px, 30px, 0px, 0px)", "");
+ var vals = cs.getPropertyValue(prop).match(/rect\(([^, ]*), ([^, ]*), ([^, ]*), ([^, ]*)\)/);
+ is(vals.length, 5,
+ "rect-valued property " + prop + ": flush before clamping test (length)");
+ is(vals[1], "-10px",
+ "rect-valued property " + prop + ": flush before clamping test (top)");
+ is(vals[2], "30px",
+ "rect-valued property " + prop + ": flush before clamping test (right)");
+ is(vals[3], "0px",
+ "rect-valued property " + prop + ": flush before clamping test (bottom)");
+ is(vals[4], "0px",
+ "rect-valued property " + prop + ": flush before clamping test (left)");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "rect(0px, 40px, 10px, 10px)", "");
+ vals = cs.getPropertyValue(prop).match(/rect\(([^, ]*), ([^, ]*), ([^, ]*), ([^, ]*)\)/);
+ is(vals.length, 5,
+ "rect-valued property " + prop + ": clamping of negatives (length)");
+ isnot(vals[1], "-10px",
+ "rect-valued property " + prop + ": clamping of negatives (top)");
+ isnot(vals[2], "30px",
+ "rect-valued property " + prop + ": clamping of negatives (right)");
+ isnot(vals[3], "0px",
+ "rect-valued property " + prop + ": clamping of negatives (bottom)");
+ isnot(vals[4], "0px",
+ "rect-valued property " + prop + ": clamping of negatives (left)");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function do_test(prop, from_value, to_value, interp_value) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, from_value, "");
+ is(cs.getPropertyValue(prop), from_value,
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, to_value, "");
+ is(cs.getPropertyValue(prop), interp_value,
+ "property " + prop + ": interpolation of " + prop);
+}
+
+function do_negative_test(prop, from_value, to_value, interpolable) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, from_value, "");
+ is(cs.getPropertyValue(prop), from_value,
+ "property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, to_value, "");
+ is(cs.getPropertyValue(prop), interpolable ? from_value : to_value,
+ "property " + prop + ": clamping of negatives");
+}
+
+function do_overone_test(prop, from_value, to_value) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, from_value, "");
+ is(cs.getPropertyValue(prop), from_value,
+ "property " + prop + ": flush before clamping test");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, to_value, "");
+ is(cs.getPropertyValue(prop), to_value,
+ "property " + prop + ": clamping of over-ones");
+}
+
+function test_visibility_transition(prop) {
+ do_test(prop, "visible", "hidden", "visible");
+ do_test(prop, "hidden", "visible", "visible");
+ do_test(prop, "hidden", "collapse", "collapse"); /* not interpolable */
+ do_test(prop, "collapse", "hidden", "hidden"); /* not interpolable */
+ do_test(prop, "visible", "collapse", "visible");
+ do_test(prop, "collapse", "visible", "visible");
+
+ isnot(get_distance(prop, "visible", "hidden"), 0,
+ "distance between visible and hidden should not be zero");
+ isnot(get_distance(prop, "visible", "collapse"), 0,
+ "distance between visible and collapse should not be zero");
+ is(get_distance(prop, "visible", "visible"), 0,
+ "distance between visible and visible should be zero");
+ is(get_distance(prop, "hidden", "hidden"), 0,
+ "distance between hidden and hidden should be zero");
+ is(get_distance(prop, "collapse", "collapse"), 0,
+ "distance between collapse and collapse should be zero");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ do_negative_test(prop, "visible", "hidden", true);
+ do_negative_test(prop, "hidden", "visible", true);
+ do_negative_test(prop, "hidden", "collapse", false);
+ do_negative_test(prop, "collapse", "hidden", false);
+ do_negative_test(prop, "visible", "collapse", true);
+ do_negative_test(prop, "collapse", "visible", true);
+
+ div.style.setProperty("transition-delay", "-3s", "");
+ div.style.setProperty("transition-timing-function", FUNC_OVERONE, "");
+ do_overone_test(prop, "visible", "hidden");
+ do_overone_test(prop, "hidden", "visible");
+ do_overone_test(prop, "hidden", "collapse");
+ do_overone_test(prop, "collapse", "hidden");
+ do_overone_test(prop, "visible", "collapse");
+ do_overone_test(prop, "collapse", "visible");
+
+ div.style.setProperty("transition-delay", "-1s", "");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_content_visibility_transition(prop) {
+ do_test(prop, "visible", "hidden", "visible");
+ do_test(prop, "hidden", "visible", "visible");
+ do_test(prop, "hidden", "auto", "auto");
+ do_test(prop, "auto", "hidden", "auto");
+ do_test(prop, "visible", "auto", "auto"); /* not interpolable */
+ do_test(prop, "auto", "visible", "visible"); /* not interpolable */
+
+ isnot(get_distance(prop, "visible", "hidden"), 0,
+ "distance between visible and hidden should not be zero");
+ isnot(get_distance(prop, "auto", "hidden"), 0,
+ "distance between auto and hidden should not be zero");
+ is(get_distance(prop, "visible", "visible"), 0,
+ "distance between visible and visible should be zero");
+ is(get_distance(prop, "hidden", "hidden"), 0,
+ "distance between hidden and hidden should be zero");
+ is(get_distance(prop, "auto", "auto"), 0,
+ "distance between auto and auto should be zero");
+
+ div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
+ do_negative_test(prop, "visible", "hidden", true);
+ do_negative_test(prop, "hidden", "visible", true);
+ do_negative_test(prop, "hidden", "auto", true);
+ do_negative_test(prop, "auto", "hidden", true);
+ do_negative_test(prop, "visible", "auto", false);
+ do_negative_test(prop, "auto", "visible", false);
+
+ div.style.setProperty("transition-delay", "-3s", "");
+ div.style.setProperty("transition-timing-function", FUNC_OVERONE, "");
+ do_overone_test(prop, "visible", "hidden");
+ do_overone_test(prop, "hidden", "visible");
+ do_overone_test(prop, "hidden", "auto");
+ do_overone_test(prop, "auto", "hidden");
+ do_overone_test(prop, "visible", "auto");
+ do_overone_test(prop, "auto", "visible");
+
+ div.style.setProperty("transition-delay", "-1s", "");
+ div.style.setProperty("transition-timing-function", "linear", "");
+}
+
+function test_background_size_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "50% 80%", "");
+ is(cs.getPropertyValue(prop), "50% 80%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "100% 100%", "");
+ is(cs.getPropertyValue(prop), "62.5% 85%",
+ "property " + prop + ": interpolation of percents");
+ check_distance(prop, "50% 80%", "62.5% 85%", "100% 100%");
+ div.style.setProperty(prop, "contain", "");
+ is(cs.getPropertyValue(prop), "contain",
+ "property " + prop + ": can't interpolate 'contain'");
+ test_background_position_size_common(prop, true, true);
+}
+
+function test_background_position_transition(prop) {
+ var doesPropTakeListValues = (prop == "background-position") ||
+ (prop == "mask-position");
+ var doesPropHaveDistanceComputation = (prop != "background-position") &&
+ (prop != "mask-position");
+
+ // Test interpolation between edge keywords, and between edge keyword and a
+ // percent value. (Note: edge keywords are really aliases for percent vals.)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "center 80%", "");
+ is(cs.getPropertyValue(prop), "50% 80%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "bottom right", "");
+ is(cs.getPropertyValue(prop), "62.5% 85%",
+ "property " + prop + ": interpolation of edge keywords & percents");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "center 80%", "62.5% 85%", "bottom right");
+ }
+
+ // Test interpolation between edge keyword *with an offset* and non-keyword
+ // values.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "right 20px bottom 30%", "");
+ is(cs.getPropertyValue(prop), "calc(100% - 20px) 70%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(40px + 20%) calc(12px + 30%)", "");
+ is(cs.getPropertyValue(prop), "calc(80% - 5px) calc(60% + 3px)",
+ "property " + prop + ": interpolation of edge keywords w/ offsets & calc");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "right 20px bottom 30%",
+ "calc(-5px + 80%) calc(3px + 60%)",
+ "calc(40px + 20%) calc(12px + 30%)");
+ }
+
+ test_background_position_size_common(prop, doesPropTakeListValues,
+ doesPropHaveDistanceComputation);
+}
+
+function test_background_position_coord_transition(prop) {
+ var endEdge = prop.endsWith("-x") ? "right" : "bottom";
+
+ // Test interpolation between edge keywords, and between edge keyword and a
+ // percent value. (Note: edge keywords are really aliases for percent vals.)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "center", "");
+ is(cs.getPropertyValue(prop), "50%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, endEdge, "");
+ is(cs.getPropertyValue(prop), "62.5%",
+ "property " + prop + ": interpolation of edge keywords & percents");
+ check_distance(prop, "center", "62.5%", endEdge);
+
+ // Test interpolation between edge keyword *with an offset* and non-keyword
+ // values.
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, `${endEdge} 20px`, "");
+ is(cs.getPropertyValue(prop), "calc(100% - 20px)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "calc(40px + 20%)", "");
+ is(cs.getPropertyValue(prop), "calc(80% - 5px)",
+ "property " + prop + ": interpolation of edge keywords w/ offsets & calc");
+ check_distance(prop, `${endEdge} 20px`,
+ "calc(-5px + 80%)",
+ "calc(40px + 20%)");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px, 50px, 30px", "");
+ is(cs.getPropertyValue(prop), "10px, 50px, 30px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px, 70px, 30px", "");
+ is(cs.getPropertyValue(prop), "20px, 55px, 30px",
+ "property " + prop + ": interpolation of lists of lengths");
+ check_distance(prop, "10px, 50px, 30px",
+ "20px, 55px, 30px",
+ "50px, 70px, 30px");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px, 50%, 30%, 5px", "");
+ is(cs.getPropertyValue(prop), "10px, 50%, 30%, 5px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px, 70%, 30%, 25px", "");
+ is(cs.getPropertyValue(prop), "20px, 55%, 30%, 10px",
+ "property " + prop + ": interpolation of lists of lengths and percents");
+ check_distance(prop, "10px, 50%, 30%, 5px",
+ "20px, 55%, 30%, 10px",
+ "50px, 70%, 30%, 25px");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "20%, 8px", "");
+ is(cs.getPropertyValue(prop), "20%, 8px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px, 40%", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 3px), calc(10% + 6px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ check_distance(prop, "20%, 8px",
+ "calc(3px + 15%), calc(6px + 10%)",
+ "12px, 40%");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(20% + 40px), 8px, calc(20px + 12%)", "");
+ is(cs.getPropertyValue(prop), "calc(20% + 40px), 8px, calc(12% + 20px)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px, calc(20%), calc(8px + 20%)", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 33px), calc(5% + 6px), calc(14% + 17px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ check_distance(prop, "calc(20% + 40px), 8px, calc(20px + 12%)",
+ "calc(33px + 15%), calc(6px + 5%), calc(17px + 14%)",
+ "12px, calc(20%), calc(8px + 20%)");
+}
+
+/**
+ * Common tests for 'background-position', 'background-size', and other
+ * properties that take CSS value-type 'position' or 'bg-size'.
+ *
+ * @arg prop The name of the property
+ * @arg doesPropTakeListValues
+ * If false, the property is assumed to just take a single 'position' or
+ * 'bg-size' value. If true, the property is assumed to also accept
+ * comma-separated list of such values.
+ */
+function test_background_position_size_common(prop, doesPropTakeListValues,
+ doesPropHaveDistanceComputation) {
+ // Test non-list values
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "40% 0%", "");
+ is(cs.getPropertyValue(prop), "40% 0%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "0% 0%", "");
+ is(cs.getPropertyValue(prop), "30% 0%",
+ "property " + prop + ": interpolation of percentages");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "40% 0%", "30% 0%", "0% 0%");
+ }
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "0% 40%", "");
+ is(cs.getPropertyValue(prop), "0% 40%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "0% 0%", "");
+ is(cs.getPropertyValue(prop), "0% 30%",
+ "property " + prop + ": interpolation of percentages");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "0% 40%", "0% 30%", "0% 0%");
+ }
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px 40px", "");
+ is(cs.getPropertyValue(prop), "10px 40px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px 0", "");
+ is(cs.getPropertyValue(prop), "20px 30px",
+ "property " + prop + ": interpolation of lengths");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "10px 40px", "20px 30px", "50px 0");
+ }
+
+ // Test interpolation that computes to to calc() (transition from % to px)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "20% 40%", "");
+ is(cs.getPropertyValue(prop), "20% 40%",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 20px", "");
+ is(cs.getPropertyValue(prop),
+ "calc(15% + 3px) calc(30% + 5px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "20% 40%",
+ "calc(3px + 15%) calc(5px + 30%)",
+ "12px 20px");
+ }
+
+ // Test interpolation that computes to to calc() (transition from px to %)
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "12px 20px", "");
+ is(cs.getPropertyValue(prop), "12px 20px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "20% 40%", "");
+ is(cs.getPropertyValue(prop),
+ "calc(5% + 9px) calc(10% + 15px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "12px 20px",
+ "calc(9px + 5%) calc(15px + 10%)",
+ "20% 40%");
+ }
+
+ // Test interpolation between calc() and non-calc()
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(40px + 10%) 16px", "");
+ is(cs.getPropertyValue(prop), "calc(10% + 40px) 16px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "30% calc(8px + 60%)", "");
+ is(cs.getPropertyValue(prop), "calc(15% + 30px) calc(15% + 14px)",
+ "property " + prop + ": interpolation between calc() and non-calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "calc(40px + 10%) 16px",
+ "calc(30px + 15%) calc(14px + 15%)",
+ "30% calc(8px + 60%)");
+ }
+
+ // Test list values, if appropriate
+ if (doesPropTakeListValues) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px 40px, 50px 50px, 30px 20px", "");
+ is(cs.getPropertyValue(prop), "10px 40px, 50px 50px, 30px 20px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px 20px, 70px 50px, 30px 40px", "");
+ is(cs.getPropertyValue(prop), "20px 35px, 55px 50px, 30px 25px",
+ "property " + prop + ": interpolation of lists of lengths");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "10px 40px, 50px 50px, 30px 20px",
+ "20px 35px, 55px 50px, 30px 25px",
+ "50px 20px, 70px 50px, 30px 40px");
+ }
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "10px 40%, 50% 50px, 30% 20%, 5px 10px", "");
+ is(cs.getPropertyValue(prop), "10px 40%, 50% 50px, 30% 20%, 5px 10px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "50px 20%, 70% 50px, 30% 40%, 25px 50px", "");
+ is(cs.getPropertyValue(prop), "20px 35%, 55% 50px, 30% 25%, 10px 20px",
+ "property " + prop + ": interpolation of lists of lengths and percents");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "10px 40%, 50% 50px, 30% 20%, 5px 10px",
+ "20px 35%, 55% 50px, 30% 25%, 10px 20px",
+ "50px 20%, 70% 50px, 30% 40%, 25px 50px");
+ }
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "20% 40%, 8px 12px", "");
+ is(cs.getPropertyValue(prop), "20% 40%, 8px 12px",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 20px, 40% 16%", "");
+ is(cs.getPropertyValue(prop),
+ "calc(15% + 3px) calc(30% + 5px), calc(10% + 6px) calc(4% + 9px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "20% 40%, 8px 12px",
+ "calc(3px + 15%) calc(5px + 30%), calc(6px + 10%) calc(9px + 4%)",
+ "12px 20px, 40% 16%");
+ }
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)", "");
+ is(cs.getPropertyValue(prop),
+ "calc(20% + 40px) calc(40% + 40px), 8px 12%, calc(12% + 20px) calc(8% + 24px)",
+ "property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)", "");
+ is(cs.getPropertyValue(prop),
+ "calc(15% + 33px) calc(35% + 30px), calc(5% + 6px) calc(24% + 4px), calc(14% + 17px) calc(10% + 28px)",
+ "property " + prop + ": interpolation that computes to calc()");
+ if (doesPropHaveDistanceComputation) {
+ check_distance(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)",
+ "calc(33px + 15%) calc(30px + 35%), calc(6px + 5%) calc(4px + 24%), calc(17px + 14%) calc(28px + 10%)",
+ "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)");
+ }
+ }
+}
+
+function test_transform_transition(prop) {
+ is(prop, "transform", "Unexpected transform property! Test needs to be fixed");
+ var matrix_re = /^matrix\(([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*)\)$/;
+ for (let i in transformTests) {
+ var test = transformTests[i];
+ if (!("expected" in test)) {
+ var v = test.expected_uncomputed;
+ if (v.match(matrix_re) && !test.force_compute) {
+ test.expected = v;
+ } else {
+ test.expected = computeMatrix(v);
+ }
+ }
+ }
+
+ for (let i in transformTests) {
+ var test = transformTests[i];
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, test.start, "");
+ cs.getPropertyValue(prop);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, test.end, "");
+ var actual = cs.getPropertyValue(prop);
+ if (!test.round_error_ok || actual == test.expected) {
+ // In most cases, we'll get an exact match, but in some cases
+ // there can be a small amount of rounding error.
+ is(actual, test.expected,
+ "interpolation of transitions: " + test.start + " to " + test.end);
+ } else {
+ function s(mat) {
+ return mat.match(matrix_re).slice(1,7);
+ }
+ var pass = true;
+ var actual_split = s(actual);
+ var expected_split = s(test.expected);
+ for (let j = 0; j < 6; ++j) {
+ // Allow differences of 1 at the sixth decimal place, and allow
+ // a drop extra for floating point error from the subtraction.
+ if (Math.abs(Number(actual_split[j]) - Number(expected_split[j])) >
+ 0.0000011) {
+ pass = false;
+ }
+ }
+ ok(pass,
+ "interpolation of transitions: " + test.start + " to " + test.end +
+ ": " + actual + " should approximately equal " + test.expected);
+ }
+ }
+
+ // FIXME: should perhaps test that no clamping occurs
+
+ runOMTATest(runAsyncTests, SimpleTest.finish);
+}
+
+function test_rotate_transition(prop) {
+ // One value: <angle>
+ test_angle_transition(prop);
+
+ // With axis: <number> <number> <number> <angle>
+ //
+ // We don't test for interpolation of the numbers here since it's quite
+ // complicated and this is tested by the web-platform tests for this property.
+ // Now that we have web-platform tests for animation properties the main
+ // purpose of the tests in this file is to check that transitions run on the
+ // properties we expect them to.
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '0 1 0 45deg';
+ is(cs[prop], 'y 45deg',
+ `rotate property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '0 1 0 145deg';
+ is(cs[prop], 'y 70deg',
+ `rotate property ${prop}: interpolation of angles`);
+ check_distance(prop, '0 1 0 45deg', '0 1 0 70deg', '0 1 0 145deg');
+}
+
+function test_scale_transition(prop) {
+ // One value: <number>
+ test_number_transition(prop);
+
+ // Two values: <number> <number>
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '10 20';
+ is(cs[prop], '10 20',
+ `number property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '50 60';
+ is(cs[prop], '20 30', `number property ${prop}: interpolation of numbers`);
+ check_distance(prop, '10 20', '20 30', '50 60');
+
+ // Three values: <number> <number> <number>
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '10 20 30';
+ is(cs[prop], '10 20 30',
+ `number property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '50 60 70';
+ is(cs[prop], '20 30 40', `number property ${prop}: interpolation of numbers`);
+ check_distance(prop, '10 20 30', '20 30 40', '50 60 70');
+}
+
+function test_translate_transition(prop) {
+ // One value: <length-percentage>
+ test_length_transition(prop);
+ test_length_unclamped(prop);
+ test_percent_transition(prop);
+ test_percent_unclamped(prop);
+ test_calc_wrapped_calc_transition(prop);
+
+ // Two values: <length-percentage> <length-percentage>
+ // Note: Cannot use test_length_percent_pair_transition(prop) because we
+ // don't resolve the percentage.
+ test_length_pair_transition(prop);
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px 50%", "");
+ is(cs.getPropertyValue(prop), "4px 50%",
+ `length-valued property ${prop}: computed value before transition`);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "12px 70%", "");
+ is(cs.getPropertyValue(prop), "6px 55%",
+ `length-valued property ${prop}: interpolation of lengths`);
+ check_distance(prop, "4px 50%", "6px 55%", "12px 70%");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "4px 50%", "");
+ is(cs.getPropertyValue(prop), "4px 50%",
+ `length-valued property ${prop}: computed value before transition`);
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "20% 20px", "");
+ is(cs.getPropertyValue(prop), "calc(5% + 3px) calc(37.5% + 5px)",
+ `length-valued property ${prop}: interpolation of lengths`);
+ check_distance(prop, "4px 50%", "calc(5% + 3px) calc(37.5% + 5px)",
+ "20% 20px");
+ // We can't use test_length_percent_pair_unclamped here since
+ // it assumes that "0px 0px" is serialized as "0px 0px" but
+ // translate should serialize it as "0px".
+
+ // Three values: <length-percentage> <length-percentage> <length>
+ div.style.transitionProperty = 'none';
+ div.style[prop] = '10px 200% 30px';
+ is(cs[prop], '10px 200% 30px',
+ `translate property ${prop}: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = '50px 600% 70px';
+ is(cs[prop], '20px 300% 40px',
+ `translate property ${prop}: interpolation of three values`);
+ check_distance(prop, '10px 20px 30px', '20px 30px 40px', '50px 60px 70px');
+}
+
+function test_font_variations_transition(prop) {
+ is(prop, "font-variation-settings", "only designed for one property");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "\"wght\" 0, \"wdth\" 1.5", "");
+ // Note that computed-style returns the tags in sorted order.
+ is(cs.getPropertyValue(prop), "\"wdth\" 1.5, \"wght\" 0",
+ "font-variation-settings property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "\"wght\" 2, \"wdth\" 0.5", "");
+ is(cs.getPropertyValue(prop), "\"wdth\" 1.25, \"wght\" 0.5",
+ "font-variation-settings property " + prop + ": interpolation of font-variation-settings");
+ check_distance(prop, "\"wght\" 0, \"wdth\" 1.5", "\"wght\" 0.5, \"wdth\" 1.25", "\"wght\" 2, \"wdth\" 0.5");
+
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "\"wght\" 2, \"wdth\" 0.5", "");
+ is(cs.getPropertyValue(prop), "\"wdth\" 0.5, \"wght\" 2",
+ "font-variation-settings property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "\"wght\" 0, \"wdth\" 1.5", "");
+ is(cs.getPropertyValue(prop), "\"wdth\" 0.75, \"wght\" 1.5",
+ "font-variation-settings property " + prop + ": interpolation of font-variation-settings");
+ check_distance(prop, "\"wght\" 2, \"wdth\" 0.5", "\"wght\" 1.5, \"wdth\" 0.75", "\"wght\" 0, \"wdth\" 1.5");
+}
+
+function test_aspect_ratio_transition(prop) {
+ [
+ // No transition between auto and <ratio>.
+ { start: "auto", end: "1 / 1",
+ expected: "1 / 1" },
+ // No transition between auto && <ratio> and <ratio>.
+ { start: "auto 1 / 1", end: "1 / 1",
+ expected: "1 / 1" },
+ // No transition between auto && <ratio> and auto.
+ { start: "auto 1 / 1", end: "auto",
+ expected: "auto" },
+ { start: "1 / 2", end: "8 / 1",
+ expected: "1 / 1" },
+ { start: "auto 1 / 2", end: "auto 8 / 1",
+ expected: "auto 1 / 1" },
+ ].forEach(test => {
+ div.style.transitionProperty = 'none';
+ div.style[prop] = test.start;
+ is(cs[prop], test.start,
+ `aspect-ratio: computed value before transition`);
+ div.style.transitionProperty = prop;
+ div.style[prop] = test.end;
+ is(cs[prop], test.expected,
+ `aspect-ratio: interpolation of aspect-ratio`);
+ // We check distance only if there is a transition.
+ if (test.end != test.expected) {
+ check_distance(prop, test.start, test.expected, test.end);
+ }
+ });
+}
+
+function test_auto_with_length_transition(prop) {
+ div.style.setProperty("transition-property", "none", "");
+ div.style.setProperty(prop, "auto 4px", "");
+ is(cs.getPropertyValue(prop), "auto 4px",
+ "auto+length-valued property " + prop + ": computed value before transition");
+ div.style.setProperty("transition-property", prop, "");
+ div.style.setProperty(prop, "auto 12px", "");
+ is(cs.getPropertyValue(prop), "auto 6px",
+ "auto+length-valued property " + prop + ": interpolation of lengths");
+ check_distance(prop, "auto 4px", "auto 6px", "auto 12px");
+}
+
+var OMTAdiv;
+var OMTACs;
+
+function prepareForOMTATest() {
+ if (OMTAdiv) {
+ OMTAdiv.remove();
+ }
+ OMTAdiv = document.createElement("div");
+ OMTAdiv.style = "height:100px; width:100px; background-color:blue;";
+ OMTAdiv.style.setProperty("transition-duration", "300s", "");
+ OMTAdiv.style.setProperty("transition-timing-function", "linear", "");
+ document.body.appendChild(OMTAdiv);
+
+ OMTACs = getComputedStyle(OMTAdiv, "");
+}
+
+function runAsyncTests() {
+ // These tests check the value on the compositor 2/3rds of the way through
+ // the transition.
+ // For the transform tests we simply compare the value on the compositor
+ // with the computed value, but for the opacity test we check the absolute
+ // value as well.
+ addAsyncTransformTests();
+ addAsyncOpacityTest();
+ addAsyncDelayTest();
+
+ runAllAsyncAnimTests().then(function() {
+ OMTAdiv.style.removeProperty("transition");
+ SimpleTest.finish();
+ });
+}
+
+function addAsyncTransformTests() {
+ transformTests.forEach(function(test) {
+ addAsyncAnimTest(function () { return runTransformTest(test); } );
+ });
+}
+
+async function runTransformTest(test) {
+ prepareForOMTATest();
+
+ OMTAdiv.style.setProperty("transition-property", "none", "");
+ OMTAdiv.style.setProperty("transform", test.start, "");
+ OMTACs.getPropertyValue("transform");
+ OMTAdiv.style.setProperty("transition-property", "transform", "");
+ OMTAdiv.style.setProperty("transform", test.end, "");
+ OMTACs.getPropertyValue("transform");
+ await waitForPaints();
+
+ // If the start value produced a non-invertible matrix the layer won't be
+ // created yet so we need to force an extra sample.
+ if (!isTransformInvertible(test.start)) {
+ winUtils.advanceTimeAndRefresh(100000);
+ await waitForPaints();
+ winUtils.advanceTimeAndRefresh(100000);
+ await waitForPaints();
+ } else {
+ winUtils.advanceTimeAndRefresh(200000);
+ await waitForPaints();
+ }
+
+ omta_is_approx(OMTAdiv, "transform", OMTACs.getPropertyValue("transform"),
+ 0.0001, RunningOn.Compositor,
+ "compositor transform transition " +
+ "from '" + test.start + "' " +
+ "to '" + test.end + "' " +
+ "at 2/3rds duration matches computed style");
+}
+
+function addAsyncOpacityTest() {
+ addAsyncAnimTest(async function() {
+ prepareForOMTATest();
+
+ OMTAdiv.style.setProperty("transition-property", "none", "");
+ OMTAdiv.style.setProperty("opacity", 0, "");
+ OMTACs.getPropertyValue("opacity");
+ OMTAdiv.style.setProperty("transition-property", "opacity", "");
+ OMTAdiv.style.setProperty("opacity", 1, "");
+ OMTACs.getPropertyValue("opacity");
+
+ await waitForPaints();
+
+ winUtils.advanceTimeAndRefresh(200000);
+
+ omta_is_approx(OMTAdiv, "opacity", 2/3, 0.00001, RunningOn.Compositor,
+ "compositor opacity transition at 2/3rds duration");
+ });
+}
+
+function addAsyncDelayTest() {
+ addAsyncAnimTest(async function() {
+ prepareForOMTATest();
+
+ OMTAdiv.style.setProperty("transition-property", "none", "");
+ OMTAdiv.style.setProperty("transition-delay", "100s", "");
+ OMTAdiv.style.setProperty("transition-duration", "200s", "");
+ OMTAdiv.style.setProperty("transform", "", "");
+ OMTACs.getPropertyValue("transform");
+ OMTAdiv.style.setProperty("transition-property", "transform", "");
+ OMTAdiv.style.setProperty("transform", "translate(100px)", "");
+ OMTACs.getPropertyValue("transform");
+
+ winUtils.advanceTimeAndRefresh(200000);
+ await waitForPaints();
+
+ omta_is_approx(OMTAdiv, "transform", { tx: 50 }, 0.0001,
+ RunningOn.Compositor,
+ "compositor transform transition with delay at 1/2"
+ + " duration");
+ });
+}
+
+function isTransformInvertible(transformStr) {
+ var computedStr = transformStrToComputedStr(transformStr);
+ if (!transformStr)
+ return false;
+ var matrix = convertTo3dMatrix(computedStr);
+ if (matrix === null)
+ return false;
+ return isInvertible(matrix);
+}
+
+function transformStrToComputedStr(transformStr) {
+ var div = document.createElement("div");
+ div.style.transform = transformStr;
+ return window.getComputedStyle(div).transform;
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_replacement_on_busy_frame.html b/layout/style/test/test_transitions_replacement_on_busy_frame.html
new file mode 100644
index 0000000000..527c98ae85
--- /dev/null
+++ b/layout/style/test/test_transitions_replacement_on_busy_frame.html
@@ -0,0 +1,100 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1167519
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for bug 1167519</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style>
+ #target {
+ height: 100px;
+ width: 100px;
+ background: green;
+ transition: transform 100s linear;
+ }
+ </style>
+</head>
+<body>
+<div id="target"></div>
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
+const omtaEnabled =
+ SpecialPowers.DOMWindowUtils.layerManagerRemote &&
+ SpecialPowers.getBoolPref(OMTAPrefKey);
+
+window.addEventListener('load', async function() {
+ if (!omtaEnabled) {
+ ok(true, 'Skipping the test since OMTA is disabled');
+ SimpleTest.finish();
+ return;
+ }
+
+ const div = document.getElementById('target');
+
+ // Start first transition
+ div.style.transform = 'translateX(300px)';
+ const firstTransition = div.getAnimations()[0];
+
+ // Wait for first transition to start running on the main thread and
+ // compositor.
+ await firstTransition.ready;
+ await waitForPaints();
+
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ // Start second transition
+ div.style.transform = 'translateX(600px)';
+ const secondTransition = div.getAnimations()[0];
+
+ const originalProperties = SpecialPowers.wrap(
+ secondTransition.effect
+ ).getProperties();
+ const previousPropertyValue = originalProperties[0].values[0].value;
+ const previousKeyframeValue = secondTransition.effect.getKeyframes()[0]
+ .transform;
+
+ // Tie up main thread for 300ms. In the meantime, the first transition
+ // will continue running on the compositor. If we don't update the start
+ // point of the second transition, it will appear to jump when it starts.
+ const startTime = performance.now();
+ while (performance.now() - startTime < 300);
+
+ // Ensure that our paint process has been done.
+ //
+ // Note that requestAnimationFrame is not suitable here since on Android
+ // there is a case where the paint process has not completed even when the
+ // requestAnimationFrame callback is run (and it is during the paint
+ // process that we update the transition start point).
+ await waitForPaints();
+
+ const updatedProperties = SpecialPowers.wrap(
+ secondTransition.effect
+ ).getProperties();
+ const currentPropertyValue = updatedProperties[0].values[0].value;
+ isnot(
+ currentPropertyValue,
+ previousPropertyValue,
+ 'From value of transition is updated since the moment when ' +
+ 'it was generated'
+ );
+ isnot(
+ secondTransition.effect.getKeyframes()[0].transform,
+ previousKeyframeValue,
+ 'Keyframe value of transition is updated since the moment when ' +
+ 'it was generated'
+ );
+ SimpleTest.finish();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_replacement_with_setKeyframes.html b/layout/style/test/test_transitions_replacement_with_setKeyframes.html
new file mode 100644
index 0000000000..85e9e40127
--- /dev/null
+++ b/layout/style/test/test_transitions_replacement_with_setKeyframes.html
@@ -0,0 +1,88 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1292001
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for bug 1292001</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <style>
+ #target {
+ height: 100px;
+ width: 100px;
+ background: green;
+ transition: transform 100s linear;
+ }
+ </style>
+</head>
+<body>
+<div id="target"></div>
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
+const omtaEnabled =
+ SpecialPowers.DOMWindowUtils.layerManagerRemote &&
+ SpecialPowers.getBoolPref(OMTAPrefKey);
+
+window.addEventListener('load', async function() {
+ if (!omtaEnabled) {
+ ok(true, 'Skipping the test since OMTA is disabled');
+ SimpleTest.finish();
+ return;
+ }
+
+ const div = document.getElementById('target');
+
+ // Start first transition
+ div.style.transform = 'translateX(300px)';
+ const firstTransition = div.getAnimations()[0];
+
+ // But then change its keyframes to something completely different.
+ firstTransition.effect.setKeyframes({ 'opacity': ['0', '1'] });
+
+ // Wait for the transition to start running on the main thread and
+ // compositor.
+ await firstTransition.ready;
+ await waitForPaints();
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ // Start second transition
+ div.style.transform = 'translateX(600px)';
+ const secondTransition = div.getAnimations()[0];
+
+ const previousKeyframeValue = secondTransition.effect.getKeyframes()[0]
+ .transform;
+
+ // Tie up main thread for 300ms. In the meantime, the first transition
+ // will continue running on the compositor. If we don't update the start
+ // point of the second transition, it will appear to jump when it starts.
+ const startTime = performance.now();
+ while (performance.now() - startTime < 300);
+
+ // Ensure that our paint process has been done.
+ //
+ // (See explanation in test_transitions_replacement_on_busy_frame.html for
+ // why we don't use requestAnimationFrame here.)
+ await waitForPaints();
+
+ // Now check that the keyframes are NOT updated.
+ is(
+ secondTransition.effect.getKeyframes()[0].transform,
+ previousKeyframeValue,
+ 'Keyframe value of transition is NOT updated since the moment when ' +
+ 'it was generated'
+ );
+
+ SimpleTest.finish();
+});
+
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_transitions_step_functions.html b/layout/style/test/test_transitions_step_functions.html
new file mode 100644
index 0000000000..c0205a8d2f
--- /dev/null
+++ b/layout/style/test/test_transitions_step_functions.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435441
+-->
+<head>
+ <title>Test for Bug 435441</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ p.transition {
+ transition: margin-top 100s linear;
+ }
+
+ </style>
+</head>
+<body>
+<div id="display">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for transition step functions **/
+
+var display = document.getElementById("display");
+
+function run_test(tf, percent, value)
+{
+ var p = document.createElement("p");
+ p.className = "transition";
+ p.style.marginTop = "0px";
+ // be this percent of the way through a 100s transition
+ p.style.transitionDelay = (percent * -100) + "s";
+ p.style.transitionTimingFunction = tf;
+ display.appendChild(p);
+ var cs = getComputedStyle(p, "");
+ var flush1 = cs.marginTop;
+
+ p.style.marginTop = "1000px";
+ var result = px_to_num(cs.marginTop) / 1000
+
+ is(result, value, 100 * percent + "% of the way through " + tf);
+
+ display.removeChild(p);
+}
+
+run_test("step-start", 0, 1);
+run_test("step-start", 0.001, 1);
+run_test("step-start", 0.999, 1);
+run_test("step-start", 1, 1);
+run_test("step-end", 0, 0);
+run_test("step-end", 0.001, 0);
+run_test("step-end", 0.999, 0);
+run_test("step-end", 1, 1);
+
+run_test("steps(2)", 0.00, 0.0);
+run_test("steps(2)", 0.49, 0.0);
+run_test("steps(2)", 0.50, 0.5);
+run_test("steps(2)", 0.99, 0.5);
+run_test("steps(2)", 1.00, 1.0);
+
+run_test("steps(2, end)", 0.00, 0.0);
+run_test("steps(2, end)", 0.49, 0.0);
+run_test("steps(2, end)", 0.50, 0.5);
+run_test("steps(2, end)", 0.99, 0.5);
+run_test("steps(2, end)", 1.00, 1.0);
+
+run_test("steps(2, start)", 0.00, 0.5);
+run_test("steps(2, start)", 0.49, 0.5);
+run_test("steps(2, start)", 0.50, 1.0);
+run_test("steps(2, start)", 0.99, 1.0);
+run_test("steps(2, start)", 1.00, 1.0);
+
+run_test("steps(8,end)", 0.00, 0.0);
+run_test("steps(8,end)", 0.10, 0.0);
+run_test("steps(8,end)", 0.20, 0.125);
+run_test("steps(8,end)", 0.30, 0.25);
+run_test("steps(8,end)", 0.40, 0.375);
+run_test("steps(8,end)", 0.49, 0.375);
+run_test("steps(8,end)", 0.50, 0.5);
+run_test("steps(8,end)", 0.60, 0.5);
+run_test("steps(8,end)", 0.70, 0.625);
+run_test("steps(8,end)", 0.80, 0.75);
+run_test("steps(8,end)", 0.90, 0.875);
+run_test("steps(8,end)", 1.00, 1.0);
+
+// steps(_, jump-*)
+run_test("steps(2, jump-start)", 0.00, 0.5);
+run_test("steps(2, jump-start)", 0.49, 0.5);
+run_test("steps(2, jump-start)", 0.50, 1.0);
+run_test("steps(2, jump-start)", 0.99, 1.0);
+run_test("steps(2, jump-start)", 1.00, 1.0);
+
+run_test("steps(2, jump-end)", 0.00, 0.0);
+run_test("steps(2, jump-end)", 0.49, 0.0);
+run_test("steps(2, jump-end)", 0.50, 0.5);
+run_test("steps(2, jump-end)", 0.99, 0.5);
+run_test("steps(2, jump-end)", 1.00, 1.0);
+
+run_test("steps(1, jump-both)", 0.00, 0.5);
+run_test("steps(1, jump-both)", 0.10, 0.5);
+run_test("steps(1, jump-both)", 0.99, 0.5);
+run_test("steps(1, jump-both)", 1.00, 1.0);
+
+run_test("steps(3, jump-both)", 0.00, 0.25);
+run_test("steps(3, jump-both)", 0.33, 0.25);
+run_test("steps(3, jump-both)", 0.34, 0.5);
+run_test("steps(3, jump-both)", 0.66, 0.5);
+run_test("steps(3, jump-both)", 0.67, 0.75);
+run_test("steps(3, jump-both)", 0.99, 0.75);
+run_test("steps(3, jump-both)", 1.00, 1.0);
+
+run_test("steps(2, jump-none)", 0.00, 0.0);
+run_test("steps(2, jump-none)", 0.49, 0.0);
+run_test("steps(2, jump-none)", 0.50, 1.0);
+run_test("steps(2, jump-none)", 1.00, 1.0);
+
+run_test("steps(3, jump-none)", 0.00, 0.0);
+run_test("steps(3, jump-none)", 0.33, 0.0);
+run_test("steps(3, jump-none)", 0.34, 0.5);
+run_test("steps(3, jump-none)", 0.66, 0.5);
+run_test("steps(3, jump-none)", 0.67, 1.0);
+run_test("steps(3, jump-none)", 1.00, 1.0);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_unclosed_parentheses.html b/layout/style/test/test_unclosed_parentheses.html
new file mode 100644
index 0000000000..7e8052892c
--- /dev/null
+++ b/layout/style/test/test_unclosed_parentheses.html
@@ -0,0 +1,262 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575672
+-->
+<head>
+ <title>Test for Bug 575672</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style type="text/css" id="style"></style>
+ <style type="text/css">
+ #display { position: relative }
+ </style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=575672">Mozilla Bug 575672</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for unclosed parentheses in CSS values. **/
+
+// Each of the following semicolon-terminated @-rules should have a
+// single missing ')' in the value.
+var semirules = [
+ "@import (",
+ "@import url(",
+ "@import url(foo",
+ "@import url('foo'",
+ "@import foo(",
+];
+
+// Each of the following declarations should have a single missing ')'
+// in the value.
+var declarations = [
+ "content: url(",
+ "content: url( ",
+ "content: url(http://www.foo.com",
+ "content: url('http://www.foo.com'",
+ "content: foobar(",
+ "content: foobar( ",
+ "content: foobar(http://www.foo.com",
+ "content: foobar('http://www.foo.com'",
+ "color: url(",
+ "color: url( ",
+ "color: url(http://www.foo.com",
+ "color: url('http://www.foo.com'",
+ "background-image: linear-gradient(",
+ "background-image: linear-gradient( ",
+ "background-image: linear-gradient(to",
+ "background-image: linear-gradient(to top",
+ "background-image: linear-gradient(to top left",
+ "background-image: linear-gradient(to top left,",
+ "background-image: repeating-linear-gradient(to top left, red, blue",
+ "background-image: linear-gradient(to top left, red, yellow, blue",
+ "background-image: linear-gradient(to top left, red 1px, yellow 5px, blue 10px",
+ "background-image: linear-gradient(to top left, red, yellow, rgb(0, 0, 255)",
+ "background-image: linear-gradient(red, blue",
+ "background-image: linear-gradient(red, yellow, blue",
+ "background-image: linear-gradient(red 1px, yellow 5px, blue 10px",
+ "background-image: linear-gradient(red, yellow, rgb(0, 0, 255)",
+ "background-image: radial-gradient(",
+ "background-image: radial-gradient( ",
+ "background-image: radial-gradient(at",
+ "background-image: radial-gradient(at ",
+ "background-image: radial-gradient(at center",
+ "background-image: radial-gradient(at center,",
+ "background-image: radial-gradient(at center ",
+ "background-image: radial-gradient(closest-corner",
+ "background-image: radial-gradient(farthest-side ",
+ "background-image: radial-gradient(closest-corner ellipse",
+ "background-image: radial-gradient(farthest-side circle ",
+ "background-image: radial-gradient(closest-corner ellipse at",
+ "background-image: radial-gradient(farthest-side circle at ",
+ "background-image: radial-gradient(closest-corner ellipse at center",
+ "background-image: radial-gradient(farthest-side circle at center ",
+ "background-image: radial-gradient(50px",
+ "background-image: radial-gradient(50px,",
+ "background-image: radial-gradient(50px ",
+ "background-image: radial-gradient(50px at",
+ "background-image: radial-gradient(50px at ",
+ "background-image: radial-gradient(50px at center",
+ "background-image: radial-gradient(50px at center ",
+ "background-image: radial-gradient(50px at center,",
+ "background-image: radial-gradient(50px 50px",
+ "background-image: radial-gradient(50px 50px,",
+ "background-image: radial-gradient(50px 50px ",
+ "background-image: radial-gradient(50px 50px at",
+ "background-image: radial-gradient(50px 50px at ",
+ "background-image: radial-gradient(50px 50px at center",
+ "background-image: radial-gradient(50px 50px at center ",
+ "background-image: radial-gradient(50px 50px at center,",
+ "background-image: radial-gradient(50px 50px at center, red, blue",
+ "background-image: radial-gradient(ellipse at",
+ "background-image: radial-gradient(ellipse at ",
+ "background-image: radial-gradient(circle",
+ "background-image: radial-gradient(circle ",
+ "background-image: radial-gradient(circle closest-corner",
+ "background-image: radial-gradient(circle farthest-side ",
+ "background-image: radial-gradient(ellipse closest-corner at center",
+ "background-image: radial-gradient(ellipse farthest-side at center,",
+ "background-image: radial-gradient(circle at center",
+ "background-image: radial-gradient(circle at center,",
+ "background-image: radial-gradient(circle at center ",
+ "background-image: radial-gradient(circle at 50px center",
+ "background-image: radial-gradient(circle at 50px center ",
+ "background-image: radial-gradient(ellipse 50px",
+ "background-image: radial-gradient(ellipse 50px ",
+ "background-image: radial-gradient(ellipse 50px 50px",
+ "background-image: radial-gradient(ellipse 50px 50px,",
+ "background-image: radial-gradient(ellipse 50px 50px ",
+ "background-image: radial-gradient(ellipse 50px 50px at",
+ "background-image: radial-gradient(ellipse 50px 50px at ",
+ "background-image: radial-gradient(ellipse 50px 50px at center",
+ "background-image: radial-gradient(ellipse 50px 50px at center ",
+ "background-image: radial-gradient(ellipse 50px 50px at center,",
+ "background-image: radial-gradient(ellipse 50px 50px at center, red, blue",
+ "background-image: radial-gradient(at top left, red, blue",
+ "background-image: radial-gradient(farthest-corner, red, blue",
+ "background-image: radial-gradient(ellipse closest-corner, red, hsl(240, 50%, 50%)",
+ "background-image: radial-gradient(farthest-side circle, red, blue",
+ "background-image: repeating-radial-gradient(50%",
+ "background-image: repeating-radial-gradient(50% ",
+ "background-image: repeating-radial-gradient(50% 50%",
+ "background-image: repeating-radial-gradient(50% 50%,",
+ "background-image: repeating-radial-gradient(50% 50%, red, blue",
+ "background-image: repeating-radial-gradient(circle, red, blue",
+ "color: rgb(",
+ "color: rgb( ",
+ "color: rgb(128, 0",
+ "color: rgb(128, 0, 128",
+ "color: rgb(128, 0, 128, 128",
+ "color: rgba(",
+ "color: rgba( ",
+ "color: rgba(128, 0",
+ "color: rgba(128, 0, 128",
+ "color: rgba(128, 0, 128, 1",
+ "color: rgba(128, 0, 128, 1, 1",
+ "color: hsl(",
+ "color: hsl( ",
+ "color: hsl(240, 50%",
+ "color: hsl(240, 50%, 50%",
+ "color: hsl(240, 50%, 50%, 50%",
+ "color: hsla(",
+ "color: hsla( ",
+ "color: hsla(240, 50%",
+ "color: hsla(240, 50%, 50%",
+ "color: hsla(240, 50%, 50%, 1",
+ "color: hsla(240, 50%, 50%, 1, 1",
+ "content: counter(",
+ "content: counter( ",
+ "content: counter(foo",
+ "content: counter(foo ",
+ "content: counter(foo,",
+ "content: counter(foo, ",
+ "content: counter(foo, upper-roman",
+ "content: counter(foo, upper-roman ",
+ "content: counter(foo, upper-roman,",
+ "content: counter(foo, upper-roman, ",
+ "content: counters(",
+ "content: counters( ",
+ "content: counters(foo, ','",
+ "content: counters(foo, ',' ",
+ "content: counters(foo, ',',",
+ "content: counters(foo, ',', ",
+ "content: counters(foo, ',', upper-roman",
+ "content: counters(foo, ',', upper-roman ",
+ "content: counters(foo, ',', upper-roman,",
+ "content: counters(foo, ',', upper-roman, ",
+ "content: attr(",
+ "content: attr( ",
+ "content: attr(href",
+ "content: attr(href ",
+ "content: attr(html",
+ "content: attr(html ",
+ "content: attr(html|",
+ "content: attr(html| ",
+ "content: attr(html|href",
+ "content: attr(html|href ",
+ "content: attr(|",
+ "content: attr(| ",
+ "content: attr(|href",
+ "content: attr(|href ",
+ "transition-timing-function: cubic-bezier(",
+ "transition-timing-function: cubic-bezier( ",
+ "transition-timing-function: cubic-bezier(0, 0, 1",
+ "transition-timing-function: cubic-bezier(0, 0, 1 ",
+ "transition-timing-function: cubic-bezier(0, 0, 1,",
+ "transition-timing-function: cubic-bezier(0, 0, 1, ",
+ "transition-timing-function: cubic-bezier(0, 0, 1, 1",
+ "transition-timing-function: cubic-bezier(0, 0, 1, 1 ",
+ "transition-timing-function: cubic-bezier(0, 0, 1, 1,",
+ "transition-timing-function: cubic-bezier(0, 0, 1, 1, ",
+ "border-top-width: calc(",
+ "border-top-width: calc( ",
+ "border-top-width: calc(2em",
+ "border-top-width: calc(2em ",
+ "border-top-width: calc(2em +",
+ "border-top-width: calc(2em + ",
+ "border-top-width: calc(2em *",
+ "border-top-width: calc(2em * ",
+ "border-top-width: calc((2em)",
+ "border-top-width: calc((2em) ",
+];
+
+var selectors = [
+ ":not(",
+ ":not( ",
+ ":not(-",
+ ":not(- ",
+ ":not(>",
+ ":not(> ",
+ ":not(div p",
+ ":not(div p ",
+ ":not(div >",
+ ":not(div > ",
+];
+
+var textNode = document.createTextNode("");
+document.getElementById("style").appendChild(textNode);
+var cs = getComputedStyle(document.getElementById("display"), "");
+
+for (var i = 0; i < semirules.length; ++i) {
+ var sheet = semirules[i] +
+ "p#display { color: red } ) ; p { color: green; z-index: " + (i + 1) + " }";
+ textNode.data = sheet;
+ is(cs.color, "rgb(0, 128, 0)",
+ "color for rule '" + semirules[i] + "'");
+ is(cs.zIndex, String(i + 1),
+ "z-index for rule '" + semirules[i] + "'");
+}
+
+for (var i = 0; i < declarations.length; ++i) {
+ var sheet = "@namespace html url(http://www.w3.org/1999/xhtml);\n" +
+ "#display { color: green; " + declarations[i] +
+ " x x x x x x x ; color: red; ) ; z-index: " + (i + 1) + " }";
+ textNode.data = sheet;
+ is(cs.color, "rgb(0, 128, 0)",
+ "color for declaration '" + declarations[i] + "'");
+ is(cs.zIndex, String(i + 1),
+ "z-index for declaration '" + declarations[i] + "'");
+}
+
+for (var i = 0; i < selectors.length; ++i) {
+ var sheet = "@namespace html url(http://www.w3.org/1999/xhtml);\n" +
+ "#display { color: green } " +
+ selectors[i] + " x x x x x x x , #display { color: red } #display { color: red } ) , #display { color: red } " +
+ "#display { z-index: " + (i + 1) + " }";
+ textNode.data = sheet;
+ is(cs.color, "rgb(0, 128, 0)",
+ "color for selector '" + selectors[i] + "'");
+ is(cs.zIndex, String(i + 1),
+ "z-index for selector '" + selectors[i] + "'");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_unicode_range_loading.html b/layout/style/test/test_unicode_range_loading.html
new file mode 100644
index 0000000000..43622e2ae5
--- /dev/null
+++ b/layout/style/test/test_unicode_range_loading.html
@@ -0,0 +1,366 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>unicode-range load tests using font loading api</title>
+ <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com">
+ <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#unicode-range-desc" />
+ <link rel="help" href="http://dev.w3.org/csswg/css-font-loading/" />
+ <meta name="assert" content="unicode-range descriptor defines precisely which fonts should be loaded" />
+ <script type="text/javascript" src="/resources/testharness.js"></script>
+ <script type="text/javascript" src="/resources/testharnessreport.js"></script>
+ <style type="text/css">
+ </style>
+</head>
+<body>
+<div id="log"></div>
+<pre id="display"></pre>
+<style id="testfonts"></style>
+<style id="teststyle"></style>
+<div id="testcontent"></div>
+
+<script>
+
+const kSheetFonts = 1;
+const kSheetStyles = 2;
+
+const redSquDataURL = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' width='100%' height='100%'><rect fill='red' x='0' y='0' width='10' height='10'/></svg>";
+
+var unicodeRangeTests = [
+ { test: "simple load sanity check, unused fonts not loaded",
+ fonts: [{ family: "a", src: "markA", descriptors: { }, loaded: false}],
+ content: "AAA", style: { "font-family": "unused" } },
+ { test: "simple load sanity check, font for a used character loaded",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true}],
+ content: "AAA" },
+ { test: "simple load sanity check, font for an unused character not loaded",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false}],
+ content: "BBB" },
+ { test: "simple load sanity check, with two fonts only font for used character loaded A",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}],
+ content: "AAA" },
+ { test: "simple load sanity check, with two fonts only font for used character loaded B",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}],
+ content: "BBB" },
+ { test: "simple load sanity check, two fonts but neither supports characters used",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}],
+ content: "CCC" },
+ { test: "simple load sanity check, two fonts and both are used",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}],
+ content: "ABC" },
+ { test: "simple load sanity check, one with Han ranges",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+3???,u+5???,u+7???,u+8???" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}],
+ content: "納豆嫌い" },
+ { test: "simple load sanity check, two fonts with different styles A",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { weight: "bold", unicodeRange: "u+42" }, loaded: false}],
+ content: "ABC" },
+ { test: "simple load sanity check, two fonts with different styles B",
+ fonts: [{ family: "a", src: "markA", descriptors: { weight: "bold", unicodeRange: "u+41" }, loaded: false},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}],
+ content: "ABC" },
+ { test: "multiple fonts with overlapping ranges, all with default ranges, only last one supports character used",
+ fonts: [{ family: "a", src: "markC", descriptors: { }, loaded: true},
+ { family: "a", src: "markA", descriptors: { }, loaded: true},
+ { family: "a", src: "markB", descriptors: { }, loaded: true}],
+ content: "CCC" },
+ { test: "multiple fonts with overlapping ranges, all with default ranges, first one supports character used",
+ fonts: [{ family: "a", src: "markB", descriptors: { }, loaded: false},
+ { family: "a", src: "markA", descriptors: { }, loaded: false},
+ { family: "a", src: "markC", descriptors: { }, loaded: true}],
+ content: "CCC" },
+ { test: "multiple fonts with overlapping ranges, one with default value in the fallback position",
+ fonts: [{ family: "a", src: "markC", descriptors: { }, loaded: true},
+ { family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}],
+ content: "ABC" },
+ { test: "multiple fonts with overlapping ranges, one with default value in the primary use position, fallback to one",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false},
+ { family: "a", src: "markC", descriptors: { }, loaded: true}],
+ content: "AAA" },
+ { test: "multiple fonts with overlapping ranges, one with default value in the primary use position, fallback to two",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true},
+ { family: "a", src: "markC", descriptors: { }, loaded: true}],
+ content: "ABC" },
+ { test: "multiple fonts with overlapping ranges, one with default value in the primary use position, no fallback",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false},
+ { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false},
+ { family: "a", src: "markC", descriptors: { }, loaded: true}],
+ content: "CCC" },
+ { test: "metrics only case, ex-sized image, single font with space in range",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+0??" }, loaded: true}],
+ content: "<img style='width: 2ex' src=\"" + redSquDataURL + "\">" },
+ { test: "metrics only case, ex-sized image, single font with space outside range",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+1??" }, loaded: false}],
+ content: "<img style='width: 2ex' src=\"" + redSquDataURL + "\">" },
+ { test: "metrics only case, ch-sized image, single font with space in range",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+0??" }, loaded: true}],
+ content: "<img style='width: 2ch' src=\"" + redSquDataURL + "\">" },
+ { test: "metrics only case, ch-sized image, single font with space outside range",
+ fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+1??" }, loaded: false}],
+ content: "<img style='width: 2ch' src=\"" + redSquDataURL + "\">" },
+];
+
+// map font loading descriptor names to @font-face rule descriptor names
+var mapDescriptorNames = {
+ style: "font-style",
+ weight: "font-weight",
+ stretch: "font-stretch",
+ unicodeRange: "unicode-range",
+ variant: "font-variant",
+ featureSettings: "font-feature-settings"
+};
+
+var kBaseFontURL;
+if ("SpecialPowers" in window) {
+ kBaseFontURL = "";
+} else {
+ kBaseFontURL = "fonts/";
+}
+
+var mapFontURLs = {
+ markA: "url(" + kBaseFontURL + "markA.woff" + ")",
+ markB: "url(" + kBaseFontURL + "markB.woff" + ")",
+ markC: "url(" + kBaseFontURL + "markC.woff" + ")",
+ markD: "url(" + kBaseFontURL + "markD.woff" + ")",
+
+ /* twourl versions include a bogus url followed by a valid url */
+ markAtwourl: "url(" + kBaseFontURL + "bogus-markA.woff" + "), url(" + kBaseFontURL + "markA.woff" + ")",
+ markBtwourl: "url(" + kBaseFontURL + "bogus-markB.woff" + "), url(" + kBaseFontURL + "markB.woff" + ")",
+ markCtwourl: "url(" + kBaseFontURL + "bogus-markC.woff" + "), url(" + kBaseFontURL + "markC.woff" + ")",
+ markDtwourl: "url(" + kBaseFontURL + "bogus-markD.woff" + "), url(" + kBaseFontURL + "markD.woff" + ")",
+
+ /* localfont versions include a bogus local ref followed by a valid url */
+ markAlocalfirst: "local(bogus-markA), url(" + kBaseFontURL + "markA.woff" + ")",
+ markBlocalfirst: "local(bogus-markB), url(" + kBaseFontURL + "markB.woff" + ")",
+ markClocalfirst: "local(bogus-markC), url(" + kBaseFontURL + "markC.woff" + ")",
+ markDlocalfirst: "local(bogus-markD), url(" + kBaseFontURL + "markD.woff" + ")",
+};
+
+function familyName(name, i) {
+ return "test" + i + "-" + name;
+}
+
+function fontFaceRule(name, fontdata, ft) {
+ var desc = [];
+ desc.push("font-family: " + name);
+ var srckey = fontdata.src + ft;
+ desc.push("src: " + mapFontURLs[srckey]);
+ for (var d in fontdata.descriptors) {
+ desc.push(mapDescriptorNames[d] + ": " + fontdata.descriptors[d]);
+ }
+ return "@font-face { " + desc.join(";") + " }";
+}
+
+function clearRules(sheetIndex) {
+ var sheet = document.styleSheets[sheetIndex];
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+}
+
+function clearAllRulesAndFonts() {
+ clearRules(kSheetFonts);
+ clearRules(kSheetStyles);
+ document.fonts.clear();
+}
+
+function addStyleRulesAndText(testdata, i) {
+ // add style rules for testcontent
+ var sheet = document.styleSheets[kSheetStyles];
+ while(sheet.cssRules.length > 0) {
+ sheet.deleteRule(0);
+ }
+ var rule = [];
+ var family = familyName(testdata.fonts[0].family, i);
+ rule.push("#testcontent { font-family: " + family);
+ if ("style" in testdata) {
+ for (s in testdata.style) {
+ rule.push(s + ": " + testdata.style[s]);
+ }
+ }
+ rule.push("}");
+ sheet.insertRule(rule.join("; "), 0);
+
+ var content = document.getElementById("testcontent");
+ content.innerHTML = testdata.content;
+ content.offsetHeight;
+}
+
+// work arounds
+function getFonts() {
+ if ("forEach" in document.fonts) {
+ return document.fonts;
+ }
+ return Array.from(document.fonts);
+}
+
+function getSize() {
+ if ("size" in document.fonts) {
+ return document.fonts.size;
+ }
+ return getFonts().length;
+}
+
+function getReady() {
+ if (typeof(document.fonts.ready) == "function") {
+ return document.fonts.ready();
+ }
+ return document.fonts.ready;
+}
+
+function setTimeoutPromise(aDelay) {
+ return new Promise(function(aResolve, aReject) {
+ setTimeout(aResolve, aDelay);
+ });
+}
+
+function addFontFaceRules(testdata, i, ft) {
+ var sheet = document.styleSheets[kSheetFonts];
+ var createdFonts = [];
+ testdata.fonts.forEach(function(f) {
+ var n = sheet.cssRules.length;
+ var fn = familyName(f.family, i);
+ sheet.insertRule(fontFaceRule(fn, f, ft), n);
+ var newfont;
+ var fonts = getFonts();
+ try {
+ fonts.forEach(function(font) { newfont = font; });
+ createdFonts.push({family: fn, data: f, font: newfont});
+ } catch (e) {
+ console.log(e);
+ }
+ });
+ return createdFonts;
+}
+
+function addDocumentFonts(testdata, i, ft) {
+ var createdFonts = [];
+ testdata.fonts.forEach(function(fd) {
+ var fn = familyName(fd.family, i);
+ var srckey = fd.src + ft;
+ var f = new FontFace(fn, mapFontURLs[srckey], fd.descriptors);
+ document.fonts.add(f);
+ createdFonts.push({family: fn, data: fd, font: f});
+ });
+ return createdFonts;
+}
+
+var q = Promise.resolve();
+
+function runTests() {
+ function setupTests() {
+ setup({explicit_done: true});
+ }
+
+ function checkFontsBeforeLoad(name, testdata, fd) {
+ test(function() {
+ assert_equals(document.fonts.status, "loaded", "before initializing test, no fonts should be loading - found: " + document.fonts.status);
+ var size = getSize();
+ assert_equals(size, testdata.fonts.length,
+ "fonts where not added to the font set object");
+ var i = 0;
+ fonts = getFonts();
+ fonts.forEach(function(ff) {
+ assert_equals(ff.status, "unloaded", "added fonts should be in unloaded state");
+ });
+ }, name + " before load");
+ }
+
+ function checkFontsAfterLoad(name, testdata, fd, afterTimeout) {
+ test(function() {
+ assert_equals(document.fonts.status, "loaded", "after ready promise resolved, no fonts should be loading");
+ var i = 0;
+ fd.forEach(function(f) {
+ assert_true(f.font instanceof FontFace, "font needs to be an instance of FontFace object");
+ if (f.data.loaded) {
+ assert_equals(f.font.status, "loaded", "font not loaded - font " + i + " " + f.data.src + " "
+ + JSON.stringify(f.data.descriptors) + " for content " + testdata.content);
+ } else {
+ assert_equals(f.font.status, "unloaded", "font loaded - font " + i + " " + f.data.src + " "
+ + JSON.stringify(f.data.descriptors) + " for content " + testdata.content);
+ }
+ i++;
+ });
+ }, name + " after load" + (afterTimeout ? " and timeout" : ""));
+ }
+
+ function testFontLoads(testdata, i, name, fd) {
+ checkFontsBeforeLoad(name, testdata, fd);
+ addStyleRulesAndText(testdata, i);
+
+ var ready = getReady();
+ return ready.then(function() {
+ checkFontsAfterLoad(name, testdata, fd, false);
+ }).then(function() {
+ return setTimeoutPromise(0).then(function() {
+ checkFontsAfterLoad(name, testdata, fd, true);
+ });
+ }).then(function() {
+ var ar = getReady();
+ return ar.then(function() {
+ test(function() {
+ assert_equals(document.fonts.status, "loaded", "after ready promise fulfilled once, fontset should not be loading");
+ var fonts = getFonts();
+ fonts.forEach(function(f) {
+ assert_not_equals(f.status, "loading", "after ready promise fulfilled once, no font should be loading");
+ });
+ }, name + " test done check");
+ });
+ }).then(function() {
+ clearAllRulesAndFonts();
+ });
+ }
+
+ function testUnicodeRangeFontFace(testdata, i, ft) {
+ var name = "TEST " + i + " " + testdata.test + " (@font-face rules)" + (ft != "" ? " " + ft : ft);
+
+ var fd = addFontFaceRules(testdata, i, ft);
+ return testFontLoads(testdata, i, name, fd);
+ }
+
+ function testUnicodeRangeDocumentFonts(testdata, i, ft) {
+ var name = "TEST " + i + " " + testdata.test + " (document.fonts)" + (ft != "" ? " " + ft : ft);
+
+ var fd = addDocumentFonts(testdata, i, ft);
+ return testFontLoads(testdata, i, name, fd);
+ }
+
+ q = q.then(function() {
+ setupTests();
+ });
+
+ var fontTypes = ["", "twourl", "localfirst"];
+
+ unicodeRangeTests.forEach(function(testdata, i) {
+ fontTypes.forEach(function(ft) {
+ q = q.then(function() {
+ return testUnicodeRangeFontFace(testdata, i, ft);
+ }).then(function() {
+ return testUnicodeRangeDocumentFonts(testdata, i, ft);
+ });
+ });
+ });
+
+ q = q.then(function() {
+ done();
+ });
+}
+
+if ("fonts" in document) {
+ runTests();
+} else {
+ test(function() {
+ assert_true(true, "CSS Font Loading API is not enabled.");
+ }, "CSS Font Loading API is not enabled");
+}
+</script>
+</body>
+</html>
diff --git a/layout/style/test/test_units_angle.html b/layout/style/test/test_units_angle.html
new file mode 100644
index 0000000000..a4432b7650
--- /dev/null
+++ b/layout/style/test/test_units_angle.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for serialization and equivalence of angle units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for serialization and equivalence of angle units **/
+
+/**
+ * We test that for each of the following:
+ * + they reserialize to exactly what is given
+ * + if a mapping is provided, they compute to the same result as the mapping
+ */
+var tests = {
+ "45deg": "50grad",
+ "150grad": "135deg",
+ "1rad": null
+};
+
+var p = document.getElementById("display");
+
+for (var test in tests) {
+ p.setAttribute("style", "transform: rotate(" + test + ")");
+ is(p.style.getPropertyValue("transform"), "rotate(" + test + ")",
+ test + " serializes to exactly itself");
+ // We can't test any equivalence since we don't have any properties
+ // with angle values that we compute. (transform doesn't help.)
+/*
+ var equiv = tests[test];
+ if (equiv) {
+ var cm1 = getComputedStyle(p, "").elevation;
+ p.style.elevation = equiv;
+ var cm2 = getComputedStyle(p, "").elevation;
+ is(cm1, cm2, test + " should compute to the same as " + equiv);
+ }
+*/
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_units_frequency.html b/layout/style/test/test_units_frequency.html
new file mode 100644
index 0000000000..cb5c0de20d
--- /dev/null
+++ b/layout/style/test/test_units_frequency.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for serialization and equivalence of frequency units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for serialization and equivalence of frequency units **/
+
+/**
+ * We test that for each of the following:
+ * + they reserialize to exactly what is given
+ * + if a mapping is provided, they compute to the same result as the mapping
+ */
+var tests = {
+ "7kHz": "7000Hz",
+ "300Hz": "0.3khz"
+};
+
+var p = document.getElementById("display");
+
+for (var test in tests) {
+ // We can't test this because we no longer support any properties
+ // with frequency values.
+ todo(false, "no tests to run, for now");
+ /*
+ p.setAttribute("style", "pitch: " + test);
+ is(p.style.getPropertyValue("pitch"), test,
+ test + " serializes to exactly itself");
+ */
+ // We can't test any equivalence since we don't have any properties
+ // with frequency values that we compute.
+/*
+ var equiv = tests[test];
+ if (equiv) {
+ var cm1 = getComputedStyle(p, "").pitch;
+ p.style.pitch = equiv;
+ var cm2 = getComputedStyle(p, "").pitch;
+ is(cm1, cm2, test + " should compute to the same as " + equiv);
+ }
+*/
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_units_length.html b/layout/style/test/test_units_length.html
new file mode 100644
index 0000000000..623f13df33
--- /dev/null
+++ b/layout/style/test/test_units_length.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for serialization and equivalence of length units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for serialization and equivalence of length units **/
+
+/**
+ * We test that for each of the following:
+ * + they reserialize to exactly what is given
+ * + if a mapping is provided, they compute to the same result as the mapping
+ */
+var tests = {
+ "1in": "72pt",
+ "20mm": "2cm",
+ "2.54cm": "1in",
+ "36pt": "0.5in",
+ "4pc": "48pt",
+ "1em": null,
+ "3ex": null,
+ "57px": null,
+ "5rem": null
+};
+
+var p = document.getElementById("display");
+
+for (var test in tests) {
+ p.setAttribute("style", "margin-left: " + test);
+ is(p.style.getPropertyValue("margin-left"), test,
+ test + " serializes to exactly itself");
+ var equiv = tests[test];
+ if (equiv) {
+ var cm1 = getComputedStyle(p, "").marginLeft;
+ p.style.marginLeft = equiv;
+ var cm2 = getComputedStyle(p, "").marginLeft;
+
+ ok(Math.abs(parseFloat(cm1, 10) - parseFloat(cm2, 10)) <= 0.0001, test + " should compute to the same as " + equiv + ", modulo floating point math error");
+ }
+}
+
+// Bug 393910
+p.setAttribute("style", "margin-left: 0");
+is(p.style.getPropertyValue("margin-left"), "0px",
+ "0 serializes to 0px");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_units_time.html b/layout/style/test/test_units_time.html
new file mode 100644
index 0000000000..16211c0207
--- /dev/null
+++ b/layout/style/test/test_units_time.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for serialization and equivalence of time units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for serialization and equivalence of time units **/
+
+/**
+ * We test that for each of the following:
+ * + they reserialize to exactly what is given
+ * + if a mapping is provided, they compute to the same result as the mapping
+ */
+var tests = {
+ "3s": "3000ms",
+ "500ms": "0.5s"
+};
+
+var p = document.getElementById("display");
+
+for (var test in tests) {
+ p.setAttribute("style", "transition-duration: " + test);
+ is(p.style.getPropertyValue("transition-duration"), test,
+ test + " serializes to exactly itself");
+ var equiv = tests[test];
+ if (equiv) {
+ var cm1 = getComputedStyle(p, "").transitionDuration;
+ p.style.transitionDuration = equiv;
+ var cm2 = getComputedStyle(p, "").transitionDuration;
+ is(cm1, cm2, test + " should compute to the same as " + equiv);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_use_counters.html b/layout/style/test/test_use_counters.html
new file mode 100644
index 0000000000..0706c9702d
--- /dev/null
+++ b/layout/style/test/test_use_counters.html
@@ -0,0 +1,159 @@
+<!doctype html>
+<title>Test for Bug 1425700: CSS properties use-counters</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<body>
+<iframe id="iframe"></iframe>
+<iframe id="second-iframe"></iframe>
+<script>
+const iframe = document.getElementById("iframe");
+
+function iframe_reload(frame = iframe) {
+ return new Promise(resolve => {
+ frame.addEventListener("load", _ => resolve());
+ frame.contentWindow.location.reload();
+ });
+}
+
+function assert_recorded(win, recorded, properties, desc) {
+ const utils = SpecialPowers.getDOMWindowUtils(win);
+ isnot(properties.length, 0, "Sanity check");
+ for (const prop of properties) {
+ try {
+ is(utils.isCssPropertyRecordedInUseCounter(prop), recorded,
+ `${desc} - ${prop}`)
+ } catch(ex) {
+ ok(false, "Threw: " + prop);
+ }
+ }
+}
+
+// NOTE(emilio): This is no longer meaningful now we always record in the style
+// system itself, which is what this tests. But we could conceivably change
+// it so it doesn't hurt.
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ "set": [
+ ["layout.css.use-counters.enabled", true],
+ ["layout.css.use-counters-unimplemented.enabled", true]
+ ]
+ });
+});
+
+// TODO(emilio): Make work (and test) inline style and maybe even CSSOM and
+// such?
+//
+// Make sure that something on the lines of the following passes:
+//
+// element.style.webkitTransform = "rotate(1deg)"
+// assert_recorded(true, ["-webkit-transform"]);
+// assert_recorded(false, ["transform"]);
+//
+const IMPLEMENTED_PROPERTIES = {
+ description: "unimplemented properties",
+ css: `
+ * {
+ grid-gap: 1px; /* shorthand alias */
+ -webkit-background-size: 100px 100px; /* longhand alias */
+ transform-origin: top left; /* longhand */
+ background: green; /* shorthand */
+ }
+ `,
+ recorded: [
+ "grid-gap",
+ "-webkit-background-size",
+ "transform-origin",
+ "background",
+ ],
+ // Should only record the aliases, not the non-aliased property.
+ // Should only record shorthands, not the longhands it expands to.
+ not_recorded: [
+ "gap",
+ "background-size",
+ "-moz-transform-origin",
+ "-webkit-transform-origin",
+ "background-color",
+ ],
+};
+
+const UNIMPLEMENTED_PROPERTIES = {
+ description: "unimplemented properties",
+ css: `
+ * {
+ grid-gap: 1px; /* shorthand alias */
+ -webkit-background-size: 100px 100px; /* longhand alias */
+ transform-origin: top left; /* longhand */
+ background: green; /* shorthand */
+ -webkit-font-smoothing: auto; /* counted unknown */
+ }
+ `,
+ recorded: [
+ "grid-gap",
+ "-webkit-background-size",
+ "transform-origin",
+ "background",
+ "-webkit-font-smoothing",
+ ],
+ not_recorded: [
+ "size",
+ "speak",
+ ],
+};
+
+// Test on regular <style> elements.
+add_task(async () => {
+ for (let test of [IMPLEMENTED_PROPERTIES, UNIMPLEMENTED_PROPERTIES]) {
+ await iframe_reload();
+
+ const win = iframe.contentWindow;
+ const style = document.createElement('style');
+ style.textContent = test.css;
+
+ iframe.contentDocument.body.appendChild(style);
+
+ assert_recorded(win, true, test.recorded, `Test ${test.description} in <style> elements`);
+ assert_recorded(win, false, test.not_recorded, `Test ${test.description} in <style> elements`);
+ }
+});
+
+// Test on constructable stylesheets.
+add_task(async () => {
+ for (let test of [IMPLEMENTED_PROPERTIES, UNIMPLEMENTED_PROPERTIES]) {
+ for (let method of ["replace", "replaceSync"]) {
+ await iframe_reload();
+ const win = iframe.contentWindow;
+
+ const sheet = new win.CSSStyleSheet();
+ await sheet[method](test.css);
+
+ assert_recorded(win, true, test.recorded, `Test ${test.description} in constructed sheet`);
+ assert_recorded(win, false, test.not_recorded, `Test ${test.description} in constructed sheet`);
+ }
+ }
+});
+
+add_task(async () => {
+ // Test for <link rel="stylesheet">. One iteration for the uncached version, one for the cached one.
+ for (let test of [IMPLEMENTED_PROPERTIES, UNIMPLEMENTED_PROPERTIES]) {
+ const uri = "data:text/css," + encodeURIComponent(test.css);
+ for (let frame of [iframe, document.getElementById("second-iframe")]) {
+ await iframe_reload(frame);
+ const win = frame.contentWindow;
+ const doc = frame.contentDocument;
+
+ const link = doc.createElement("link");
+ link.rel = "stylesheet";
+ const linkLoaded = new Promise(resolve => {
+ link.onload = resolve;
+ });
+ link.href = uri;
+ doc.body.appendChild(link);
+ await linkLoaded;
+ assert_recorded(win, true, test.recorded, `Test ${test.description} in <link> ${frame.id}`);
+ assert_recorded(win, false, test.not_recorded, `Test ${test.description} in <link> ${frame.id}`);
+ }
+ }
+});
+
+</script>
+</body>
diff --git a/layout/style/test/test_user_sheet_shadow_dom.html b/layout/style/test/test_user_sheet_shadow_dom.html
new file mode 100644
index 0000000000..cd7a44b308
--- /dev/null
+++ b/layout/style/test/test_user_sheet_shadow_dom.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<title>Test for bug 1576229 - Nodes in Shadow DOM react properly to dynamic changes in user sheets</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<div></div>
+<span id="host" style="display: block"></span>
+<script>
+const gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService)
+
+const gSSService = SpecialPowers.Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(SpecialPowers.Ci.nsIStyleSheetService);
+
+const windowUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function loadUserSheet(style) {
+ const uri = gIOService.newURI("data:text/css," + style);
+ windowUtils.loadSheet(uri, windowUtils.USER_SHEET);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+onload = function() {
+ loadUserSheet(`
+ div {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+ }
+ .foo {
+ background-color: green;
+ }
+ `);
+ let host = document.querySelector("#host");
+ host.attachShadow({ mode: "open" }).innerHTML = `
+ <div></div>
+ `;
+ let light = document.querySelector('div');
+ let shadow = host.shadowRoot.querySelector('div');
+ is(getComputedStyle(light).backgroundColor, "rgb(255, 0, 0)", "User sheet works in light DOM");
+ is(getComputedStyle(shadow).backgroundColor, "rgb(255, 0, 0)", "User sheet works in shadow DOM");
+ light.classList.add("foo");
+ shadow.classList.add("foo");
+ is(getComputedStyle(light).backgroundColor, "rgb(0, 128, 0)", "Dynamic change for user sheet works in light DOM");
+ is(getComputedStyle(shadow).backgroundColor, "rgb(0, 128, 0)", "Dynamic change for user sheet works in shadow DOM");
+ SimpleTest.finish();
+}
+</script>
diff --git a/layout/style/test/test_value_cloning.html b/layout/style/test/test_value_cloning.html
new file mode 100644
index 0000000000..ceaa9d3c66
--- /dev/null
+++ b/layout/style/test/test_value_cloning.html
@@ -0,0 +1,181 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375363
+-->
+<head>
+ <title>Test for cloning of CSS property values (including 'inherit', 'initial' and 'unset')</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"><iframe id="iframe" src="about:blank"></iframe></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for cloning of CSS property values (including 'inherit', 'initial' and 'unset') **/
+var test_queue = [];
+var iframe = document.getElementById("iframe");
+
+SimpleTest.waitForExplicitFinish();
+
+for (var prop in gCSSProperties) {
+ let info = gCSSProperties[prop];
+
+ test_queue.push({ prop: prop, value: "inherit",
+ inherited_value: info.initial_values[0] });
+ test_queue.push({ prop: prop, value: "inherit",
+ inherited_value: info.other_values[0] });
+ test_queue.push({ prop: prop, value: "initial" });
+ if (info.inherited) {
+ test_queue.push({ prop: prop, value: "unset",
+ inherited_value: info.initial_values[0] });
+ test_queue.push({ prop: prop, value: "unset",
+ inherited_value: info.other_values[0] });
+ } else {
+ test_queue.push({ prop: prop, value: "unset" });
+ }
+ for (let idx in info.initial_values) {
+ test_queue.push({ prop: prop, value: info.initial_values[idx] });
+ }
+ for (let idx in info.other_values) {
+ test_queue.push({ prop: prop, value: info.other_values[idx] });
+ }
+}
+
+test_queue.reverse();
+
+doTest();
+
+function doTest()
+{
+ var sheet_data = "";
+
+ for (let idx = 0; idx < test_queue.length; ++idx) {
+ var current_item = test_queue[idx];
+
+ let info = gCSSProperties[current_item.prop];
+
+ sheet_data += "#parent"+idx+", #test"+idx+" { ";
+ for (var prereq in info.prereqs) {
+ sheet_data += prereq + ": " + info.prereqs[prereq] + ";";
+ }
+ sheet_data += " }";
+
+ sheet_data += "#parent"+idx+" { ";
+ if ("inherited_value" in current_item) {
+ sheet_data += current_item.prop + ": " + current_item.inherited_value;
+ }
+ sheet_data += "}";
+
+ sheet_data += "#test"+idx+" { ";
+ sheet_data += current_item.prop + ": " + current_item.value;
+ sheet_data += "}";
+ }
+
+ var sheet_url = "data:text/css," + escape(sheet_data);
+
+ var doc_data =
+ "<!DOCTYPE HTML>\n" +
+ "<link rel='stylesheet' type='text/css' href='" + sheet_url + "'>\n" +
+ "<link rel='stylesheet' type='text/css' href='" + sheet_url + "'>\n" +
+ "<body>\n";
+
+
+ for (let idx = 0; idx < test_queue.length; ++idx) {
+ var current_item = test_queue[idx];
+
+ if ("inherited_value" in current_item) {
+ doc_data += "<span id='parent"+idx+"'>";
+ }
+ doc_data += "<span id='test"+idx+"'></span>";
+ if ("inherited_value" in current_item) {
+ doc_data += "</span>";
+ }
+ }
+
+ var doc_url = "data:text/html," + escape(doc_data);
+ iframe.onload = iframe_loaded;
+ iframe.src = doc_url;
+}
+
+function iframe_loaded(event)
+{
+ if (event.target != iframe)
+ return;
+
+ var start_ser = [];
+ var start_compute = [];
+ var test_cs = [];
+ var wrappedFrame = SpecialPowers.wrap(iframe);
+ var ifdoc = wrappedFrame.contentDocument;
+ var ifwin = wrappedFrame.contentWindow;
+
+ for (let idx = 0; idx < test_queue.length; ++idx) {
+ var current_item = test_queue[idx];
+ var info = gCSSProperties[current_item.prop];
+
+ var test = ifdoc.getElementById("test" + idx);
+ var cur_cs = ifwin.getComputedStyle(test);
+ test_cs.push(cur_cs);
+ var cur_ser = ifdoc.styleSheets[0].cssRules[3*idx+2].style.getPropertyValue(current_item.prop);
+ if (cur_ser == "") {
+ isnot(cur_ser, "",
+ "serialization should be nonempty for " +
+ current_item.prop + ": " + current_item.value);
+ }
+ start_ser.push(cur_ser);
+
+ var cur_compute = get_computed_value(cur_cs, current_item.prop);
+ if (cur_compute == "") {
+ isnot(cur_compute, "",
+ "computed value should be nonempty for " +
+ current_item.prop + ": " + current_item.value);
+ }
+ start_compute.push(cur_compute);
+ }
+
+ // In case the above access didn't force a clone already (though it
+ // currently does), clone the second style sheet's inner and then
+ // remove the first.
+ ifdoc.styleSheets[1].insertRule("#nonexistent { color: red }", 0);
+ var firstlink = ifdoc.getElementsByTagName("link")[0];
+ firstlink.remove();
+
+ // Force a flush
+ ifdoc.body.style.display="none";
+ var ow = ifdoc.body.offsetWidth;
+ ifdoc.body.style.display="";
+
+ for (let idx = 0; idx < test_queue.length; ++idx) {
+ var current_item = test_queue[idx];
+ var info = gCSSProperties[current_item.prop];
+
+ var end_ser =
+ ifdoc.styleSheets[0].cssRules[3*idx+3].style.getPropertyValue(current_item.prop);
+ is(end_ser, start_ser[idx],
+ "serialization should match when cloning " +
+ current_item.prop + ": " + current_item.value);
+
+ var end_compute = get_computed_value(test_cs[idx], current_item.prop);
+ // Output computed values only when the test failed.
+ // Computed values may be very long.
+ if (end_compute == start_compute[idx]) {
+ ok(true,
+ "computed values should match when cloning " +
+ current_item.prop + ": " + current_item.value);
+ } else {
+ is(end_compute, start_compute[idx],
+ "computed values should match when cloning " +
+ current_item.prop + ": " + current_item.value);
+ }
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_value_computation.html b/layout/style/test/test_value_computation.html
new file mode 100644
index 0000000000..df38a24b9b
--- /dev/null
+++ b/layout/style/test/test_value_computation.html
@@ -0,0 +1,236 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for computation of values in property database</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <style type="text/css" id="stylesheet"></style>
+ <style type="text/css">
+ /* For 'width', 'height', etc., need a constant size container. */
+ #display { width: 500px; height: 200px }
+ </style>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(2);
+
+ var load_count = 0;
+ function load_done() {
+ if (++load_count == 3)
+ run_tests();
+ }
+ </script>
+</head>
+<body>
+<iframe id="unstyledn" src="unstyled.xml" height="10" width="10" onload="load_done()"></iframe>
+<iframe id="unstyledf" src="unstyled-frame.xml" height="10" width="10" onload="load_done()"></iframe>
+<p id="display"><span><span id="elementf"></span></span></p>
+<div id="content" style="display: none">
+
+<div><span id="elementn"></span></div>
+
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for computation of values in property database **/
+
+var gBadComputedNoFrame = {
+ // These are probably bogus tests...
+ "-moz-margin-end": [ "0%", "calc(0% + 0px)" ],
+ "-moz-margin-start": [ "0%", "calc(0% + 0px)" ],
+ "-moz-padding-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "-moz-padding-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "margin": [ "0% 0px 0em 0pt" ],
+ "margin-block-end": [ "0%", "calc(0% + 0px)" ],
+ "margin-block-start": [ "0%", "calc(0% + 0px)" ],
+ "margin-bottom": [ "0%", "calc(0% + 0px)" ],
+ "margin-inline-end": [ "0%", "calc(0% + 0px)" ],
+ "margin-inline-start": [ "0%", "calc(0% + 0px)" ],
+ "margin-left": [ "0%", "calc(0% + 0px)" ],
+ "margin-right": [ "0%", "calc(0% + 0px)" ],
+ "margin-top": [ "0%", "calc(0% + 0px)" ],
+ "padding": [ "0% 0px 0em 0pt", "calc(0px) calc(0em) calc(-2px) calc(-1%)" ],
+ "padding-block-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-block-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-bottom": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-inline-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-inline-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-left": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-right": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+ "padding-top": [ "0%", "calc(0% + 0px)", "calc(-1%)" ],
+};
+
+function xfail_value(property, value, is_initial, has_frame) {
+ if (!has_frame && (property in gBadComputedNoFrame) &&
+ gBadComputedNoFrame[property].includes(value))
+ return true;
+
+ return false;
+}
+
+var gSwapInitialWhenHaveFrame = {
+ // When there's a frame, '-moz-available' works out to the same as
+ // 'auto' given the prerequisites of only 'display: block'.
+ "width": [ "-moz-available" ],
+ // When there's a frame, these keywords works out to the same as the initial
+ // value, i.e. `auto`, given the prerequisites of only 'display: block'.
+ "height": [ "-moz-max-content", "-moz-min-content", "-moz-fit-content",
+ "-moz-available", "max-content", "min-content", "fit-content",
+ "fit-content(100px)", "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))" ],
+ "block-size": [ "-moz-max-content", "-moz-min-content", "-moz-fit-content",
+ "-moz-available", "max-content", "min-content", "fit-content",
+ "fit-content(100px)", "fit-content(10%)",
+ "fit-content(calc(3*25px + 50%))" ],
+};
+
+function swap_when_frame(property, value) {
+ return (property in gSwapInitialWhenHaveFrame) &&
+ gSwapInitialWhenHaveFrame[property].includes(value);
+}
+
+var gDisplayTree = document.getElementById("display");
+var gElementN = document.getElementById("elementn");
+var gElementF = document.getElementById("elementf");
+var gStyleSheet = document.getElementById("stylesheet").sheet;
+var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)];
+var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)];
+
+var gInitialValuesN;
+var gInitialValuesF;
+var gInitialPrereqsRuleN;
+var gInitialPrereqsRuleF;
+
+function setup_initial_values(id, ivalprop, prereqprop) {
+ var iframe = document.getElementById(id);
+ window[ivalprop] = iframe.contentWindow.getComputedStyle(
+ iframe.contentDocument.documentElement.firstChild);
+ var sheet = iframe.contentDocument.styleSheets[0];
+ // For 'width', 'height', etc., need a constant size container.
+ sheet.insertRule(":root { height: 200px; width: 500px }", sheet.cssRules.length);
+
+ window[prereqprop] = sheet.cssRules[sheet.insertRule(":root > * {}", sheet.cssRules.length)];
+}
+
+function test_value(property, val, is_initial)
+{
+ var info = gCSSProperties[property];
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gRule1.style.setProperty(prereq, prereqs[prereq], "");
+ gInitialPrereqsRuleN.style.setProperty(prereq, prereqs[prereq], "");
+ gInitialPrereqsRuleF.style.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+ if (info.inherited && is_initial) {
+ gElementN.parentNode.style.setProperty(property, info.other_values[0], "");
+ gElementF.parentNode.style.setProperty(property, info.other_values[0], "");
+ }
+
+ var initial_computed_n = get_computed_value(gInitialValuesN, property);
+ var initial_computed_f = get_computed_value(gInitialValuesF, property);
+ if (is_initial) {
+ gRule1.style.setProperty(property, info.other_values[0], "");
+ var other_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property);
+ var other_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property);
+ isnot(other_computed_n, initial_computed_n,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ isnot(other_computed_f, initial_computed_f,
+ "should be testing with values that compute to different things " +
+ "for '" + property + "'");
+ }
+ // It used to be important for values that are supposed to compute to the
+ // initial value (given the design of the old rule tree, nsRuleNode) that
+ // we're modifying the most specific rule that matches the element, and
+ // that we've already requested style while that rule was empty. This
+ // means we'd have a cached aStartStruct from the parent in the rule
+ // tree (caching the "other" value), so we'd make sure we don't get the
+ // initial value from the luck of default-initialization. This means
+ // that it would've been important that we set the prereqs on
+ // gRule1.style rather than on gElement.style.
+ //
+ // However, the rule tree no longer stores cached structs, and we only
+ // temporarily cache reset structs during a single restyle. So the
+ // particular failure mode this was designed to test for isn't as
+ // likely to eventuate.
+ gRule2.style.setProperty(property, val, "");
+ var val_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property);
+ var val_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property);
+ isnot(val_computed_n, "",
+ "should not get empty value for '" + property + ":" + val + "'");
+ isnot(val_computed_f, "",
+ "should not get empty value for '" + property + ":" + val + "'");
+ if (is_initial) {
+ (xfail_value(property, val, is_initial, false) ? todo_is : is)(
+ val_computed_n, initial_computed_n,
+ "should get initial value for '" + property + ":" + val + "'");
+ (xfail_value(property, val, is_initial, true) ? todo_is : is)(
+ val_computed_f, initial_computed_f,
+ "should get initial value for '" + property + ":" + val + "'");
+ } else {
+ (xfail_value(property, val, is_initial, false) ? todo_isnot : isnot)(
+ val_computed_n, initial_computed_n,
+ "should not get initial value for '" + property + ":" + val + "' on elementn.");
+ var swap = swap_when_frame(property, val);
+ (xfail_value(property, val, is_initial, true) ? todo_isnot : (swap ? is : isnot))(
+ val_computed_f, initial_computed_f,
+ "should " + (swap ? "" : "not ") +
+ "get initial value for '" + property + ":" + val + "' on elementf.");
+ }
+ if (is_initial)
+ gRule1.style.removeProperty(property);
+ gRule2.style.removeProperty(property);
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gRule1.style.removeProperty(prereq);
+ gInitialPrereqsRuleN.style.removeProperty(prereq);
+ gInitialPrereqsRuleF.style.removeProperty(prereq);
+ }
+ }
+ if (info.inherited && is_initial) {
+ gElementN.parentNode.style.removeProperty(property);
+ gElementF.parentNode.style.removeProperty(property);
+ }
+}
+
+function test_property(prop) {
+ var info = gCSSProperties[prop];
+ for (var idx in info.initial_values)
+ test_value(prop, info.initial_values[idx], true);
+ for (var idx in info.other_values)
+ test_value(prop, info.other_values[idx], false);
+}
+
+function run_tests() {
+ setup_initial_values("unstyledn", "gInitialValuesN", "gInitialPrereqsRuleN");
+ setup_initial_values("unstyledf", "gInitialValuesF", "gInitialPrereqsRuleF");
+ var props = [];
+ for (var prop in gCSSProperties)
+ props.push(prop);
+ props = props.reverse();
+ function do_one() {
+ if (props.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ test_property(props.pop());
+ SimpleTest.executeSoon(do_one);
+ }
+ SimpleTest.executeSoon(do_one);
+}
+
+load_done();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_value_storage.html b/layout/style/test/test_value_storage.html
new file mode 100644
index 0000000000..542cad91ed
--- /dev/null
+++ b/layout/style/test/test_value_storage.html
@@ -0,0 +1,365 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for parsing, storage, and serialization of CSS values</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="property_database.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css" id="prereqsheet">
+ #testnode {}
+ </style>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div id="testnode"></div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for parsing, storage, and serialization of CSS values **/
+
+/*
+ * The idempotence tests here deserve a little bit of explanation. What
+ * we're testing here are the following operations:
+ * parse: string -> CSS rule
+ * serialize: CSS rule -> string (normalization 1)
+ * (this actually has two variants that go through partly different
+ * codepaths, which we exercise with getPropertyValue and cssText)
+ * compute: CSS rule -> computed style
+ * cserialize: computed style -> string (normalization 2)
+ *
+ * Both serialize and cserialize do some normalization, so we can't test
+ * for pure round-tripping, and we also can't compare their output since
+ * they could normalize differently. (We might at some point in the
+ * future want to guarantee that any output of cserialize is
+ * untouched by going through parse+serialize, though.)
+ *
+ * So we test idempotence of parse + serialize by running the whole
+ * operation twice. Likewise for parse + compute + cserialize.
+ *
+ * Slightly more interestingly, we test that serialize + parse is the
+ * identity transform by comparing the output of parse + compute +
+ * cserialize to the output of parse + serialize + parse + compute +
+ * cserialize.
+ */
+
+var gSystemFont = [
+ "caption",
+ "icon",
+ "menu",
+ "message-box",
+ "small-caption",
+ "status-bar",
+ "-moz-button",
+ "-moz-pull-down-menu",
+ "-moz-list",
+ "-moz-field",
+];
+
+var gBadCompute = {
+ // output wrapped around to positive, in exponential notation
+ "-moz-box-ordinal-group": [ "-1", "-1000" ],
+};
+
+function xfail_compute(property, value)
+{
+ if (property in gBadCompute &&
+ gBadCompute[property].includes(value))
+ return true;
+
+ return false;
+}
+
+// constructed to map longhands ==> list of containing shorthands
+var gPropertyShorthands = {};
+
+var gElement = document.getElementById("testnode");
+var gDeclaration = gElement.style;
+var gComputedStyle = window.getComputedStyle(gElement);
+
+var gPrereqDeclaration =
+ document.getElementById("prereqsheet").sheet.cssRules[0].style;
+
+// On Android, avoid most 'TEST-PASS' logging by overriding
+// SimpleTest.is/isnot, to improve performance
+if (navigator.appVersion.includes("Android")) {
+ is = function is(a, b, name)
+ {
+ var pass = Object.is(a, b);
+ if (!pass)
+ SimpleTest.is(a, b, name);
+ }
+
+ isnot = function isnot(a, b, name)
+ {
+ var pass = !Object.is(a, b);
+ if (!pass)
+ SimpleTest.isnot(a, b, name);
+ }
+}
+
+// Returns true if propA and propB are equivalent, considering aliasing.
+// (i.e. if one is an alias of the other, or if they're both aliases of
+// the same 3rd property)
+function are_properties_aliased(propA, propB)
+{
+ // If either property is an alias, replace it with the property it aliases.
+ if ("alias_for" in gCSSProperties[propA]) {
+ propA = gCSSProperties[propA].alias_for;
+ }
+ if ("alias_for" in gCSSProperties[propB]) {
+ propB = gCSSProperties[propB].alias_for;
+ }
+
+ return propA == propB;
+}
+
+function test_property(property)
+{
+ var info = gCSSProperties[property];
+
+ // can all properties be removed from the style?
+ function test_remove_all_properties(propName, value) {
+ var i, p = [];
+ for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]);
+ for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]);
+ var errstr = "when setting property " + propName + " to " + value;
+ is(gDeclaration.length, 0, "unremovable properties " + errstr);
+ is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr);
+ }
+
+ function test_other_shorthands_empty(value, subprop) {
+ if (!(subprop in gPropertyShorthands)) return;
+ var shorthands = gPropertyShorthands[subprop];
+ for (idx in shorthands) {
+ var sh = shorthands[idx];
+ if (are_properties_aliased(sh, property)) {
+ continue;
+ }
+ is(gDeclaration.getPropertyValue(sh), "",
+ "setting '" + value + "' on '" + property + "' (for shorthand '" + sh + "')");
+ }
+ }
+
+ function test_value(value, resolved_value) {
+ var value_has_variable_reference = resolved_value != null;
+ var is_system_font = property == "font" && gSystemFont.includes(value);
+
+ var colon = ": ";
+ gDeclaration.setProperty(property, value, "");
+
+ var idx;
+
+ var step1val = gDeclaration.getPropertyValue(property);
+ var step1vals = [];
+ var step1ser = gDeclaration.cssText;
+ if ("subproperties" in info)
+ for (idx in info.subproperties)
+ step1vals.push(gDeclaration.getPropertyValue(info.subproperties[idx]));
+ var step1comp;
+ var step1comps = [];
+ if (info.type != CSS_TYPE_TRUE_SHORTHAND)
+ step1comp = gComputedStyle.getPropertyValue(property);
+ if ("subproperties" in info)
+ for (idx in info.subproperties)
+ step1comps.push(gComputedStyle.getPropertyValue(info.subproperties[idx]));
+
+ SimpleTest.isnot(step1val, "", "setting '" + value + "' on '" + property + "'");
+ if ("subproperties" in info &&
+ // System font doesn't produce meaningful value for subproperties.
+ !is_system_font)
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ if (value_has_variable_reference &&
+ (!info.alias_for || info.type == CSS_TYPE_TRUE_SHORTHAND ||
+ info.type == CSS_TYPE_LEGACY_SHORTHAND)) {
+ is(gDeclaration.getPropertyValue(subprop), "",
+ "setting '" + value + "' on '" + property + "' (for '" + subprop + "')");
+ test_other_shorthands_empty(value, subprop);
+ } else {
+ isnot(gDeclaration.getPropertyValue(subprop), "",
+ "setting '" + value + "' on '" + property + "' (for '" + subprop + "')");
+ }
+ }
+
+ // We don't care particularly about the whitespace or the placement of
+ // semicolons, but for simplicity we'll test the current behavior.
+ var expected_serialization = "";
+ if (step1val != "") {
+ if ("alias_for" in info) {
+ let newValue = info.legacy_mapping && info.legacy_mapping[step1val]
+ ? info.legacy_mapping[step1val] : step1val;
+ // FIXME(emilio): This is a bit unfortunate:
+ // https://github.com/w3c/csswg-drafts/issues/3332
+ if (info.type == CSS_TYPE_LEGACY_SHORTHAND && value_has_variable_reference)
+ newValue = "";
+ expected_serialization = info.alias_for + colon + newValue + ";";
+ } else if (info.type == CSS_TYPE_LEGACY_SHORTHAND) {
+ is(property, "zoom", "Zoom is a bit special because it never " +
+ "serializes as-is, we always serialize the longhands, " +
+ "but it doesn't just map to a single property " +
+ "(and thus we can't use the 'alias_for' mechanism)");
+ let transform = step1val == "1" ? "none" : "scale(" + step1val + ")";
+ let origin = step1val == "1" ? "50% 50% 0px" : "0px 0px 0px";
+ if (value_has_variable_reference) { // See above.
+ transform = "";
+ origin = "";
+ }
+ expected_serialization = "transform" + colon + transform + "; transform-origin" + colon + origin + ";";
+ } else {
+ expected_serialization = property + colon + step1val + ";";
+ }
+ }
+ is(step1ser, expected_serialization,
+ "serialization should match property value");
+
+ gDeclaration.removeProperty(property);
+ gDeclaration.setProperty(property, step1val, "");
+
+ is(gDeclaration.getPropertyValue(property), step1val,
+ "parse+serialize should be idempotent for '" +
+ property + colon + value + "'");
+ if (info.type != CSS_TYPE_TRUE_SHORTHAND) {
+ is(gComputedStyle.getPropertyValue(property), step1comp,
+ "serialize+parse should be identity transform for '" +
+ property + ": " + value + "'");
+ }
+
+ if ("subproperties" in info &&
+ // Using setProperty over subproperties is not sufficient for
+ // system fonts, since the shorthand does more than its parts.
+ !is_system_font &&
+ !value_has_variable_reference) {
+ gDeclaration.removeProperty(property);
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ gDeclaration.setProperty(subprop, step1vals[idx], "");
+ }
+
+ // Now that all the subprops are set, check their values. Note that we
+ // need this in a separate loop, in case parts of the shorthand affect
+ // the computed values of other parts.
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ is(gComputedStyle.getPropertyValue(subprop), step1comps[idx],
+ "serialize(" + subprop + ")+parse should be the identity " +
+ "transform for '" + property + ": " + value + "'");
+ }
+ is(gDeclaration.getPropertyValue(property), step1val,
+ "parse+split+serialize should be idempotent for '" +
+ property + colon + value + "'");
+ }
+
+ // FIXME(emilio): Why is mask special?
+ if (info.type != CSS_TYPE_TRUE_SHORTHAND &&
+ property != "mask") {
+ gDeclaration.removeProperty(property);
+ gDeclaration.setProperty(property, step1comp, "");
+ var func = xfail_compute(property, value) ? todo_is : is;
+ func(gComputedStyle.getPropertyValue(property), step1comp,
+ "parse+compute+serialize should be idempotent for '" +
+ property + ": " + value + "'");
+ }
+ if ("subproperties" in info && !is_system_font) {
+ gDeclaration.removeProperty(property);
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ gDeclaration.setProperty(subprop, step1comps[idx], "");
+ }
+
+ // Now that all the subprops are set, check their values. Note that we
+ // need this in a separate loop, in case parts of the shorthand affect
+ // the computed values of other parts.
+ for (idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ is(gComputedStyle.getPropertyValue(subprop), step1comps[idx],
+ "parse+compute+serialize(" + subprop + ") should be idempotent for '" +
+ property + ": " + value + "'");
+ }
+ }
+
+ // sanity check shorthands to make sure disabled props aren't exposed
+ if (info.type != CSS_TYPE_LONGHAND) {
+ gDeclaration.setProperty(property, value, "");
+ test_remove_all_properties(property, value);
+ }
+
+ gDeclaration.removeProperty(property);
+ }
+
+ function test_value_without_variable(value) {
+ test_value(value, null);
+ }
+
+ function test_value_with_variable(value) {
+ gPrereqDeclaration.setProperty("--a", value, "");
+ test_value("var(--a)", value);
+ gPrereqDeclaration.removeProperty("--a");
+ }
+
+ if ("prerequisites" in info) {
+ var prereqs = info.prerequisites;
+ for (var prereq in prereqs) {
+ gPrereqDeclaration.setProperty(prereq, prereqs[prereq], "");
+ }
+ }
+
+ var idx;
+ for (idx in info.initial_values) {
+ test_value_without_variable(info.initial_values[idx]);
+ test_value_with_variable(info.initial_values[idx]);
+ }
+ for (idx in info.other_values) {
+ test_value_without_variable(info.other_values[idx]);
+ test_value_with_variable(info.other_values[idx]);
+ }
+
+ if ("prerequisites" in info) {
+ for (var prereq in info.prerequisites) {
+ gPrereqDeclaration.removeProperty(prereq);
+ }
+ }
+
+}
+
+function runTest() {
+ // To avoid triggering the slow script dialog, we have to test one
+ // property at a time.
+ var props = [];
+ for (var prop in gCSSProperties) {
+ var info = gCSSProperties[prop];
+ if ("subproperties" in info) {
+ for (var idx in info.subproperties) {
+ var subprop = info.subproperties[idx];
+ if (!(subprop in gPropertyShorthands)) {
+ gPropertyShorthands[subprop] = [];
+ }
+ gPropertyShorthands[subprop].push(prop);
+ }
+ }
+ props.push(prop);
+ }
+ props = props.reverse();
+ function do_one() {
+ if (props.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ test_property(props.pop());
+ SimpleTest.executeSoon(do_one);
+ }
+ SimpleTest.executeSoon(do_one);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(7);
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_variable_serialization_computed.html b/layout/style/test/test_variable_serialization_computed.html
new file mode 100644
index 0000000000..2814e4ab93
--- /dev/null
+++ b/layout/style/test/test_variable_serialization_computed.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<title>Test serialization of computed CSS variable values</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+
+<div>
+ <span></span>
+</div>
+
+<script>
+// Each entry is an entire declaration followed by the property to check and
+// its expected computed value.
+var values = [
+ ["", "--z", "an-inherited-value"],
+ ["--a: ", "--a", ""],
+ ["--a: initial", "--a", ""],
+ ["--z: initial", "--z", ""],
+ ["--a: inherit", "--a", ""],
+ ["--z: inherit", "--z", "an-inherited-value"],
+ ["--a: unset", "--a", ""],
+ ["--z: unset", "--z", "an-inherited-value"],
+ ["--a: 1px", "--a", "1px"],
+ ["--a: var(--a)", "--a", ""],
+ ["--a: var(--b)", "--a", ""],
+ ["--a: var(--b); --b: 1px", "--a", "1px"],
+ ["--a: var(--b, 1px)", "--a", "1px"],
+ ["--a: var(--a, 1px)", "--a", ""],
+ ["--a: something 3px url(whereever) calc(var(--a) + 1px)", "--a", ""],
+ ["--a: something 3px url(whereever) calc(var(--b,1em) + 1px)", "--a", "something 3px url(whereever) calc(1em + 1px)"],
+ ["--a: var(--b, var(--c, var(--d, Black)))", "--a", "Black"],
+ ["--a: a var(--b) c; --b:b", "--a", "a b c"],
+ ["--a: a var(--b,b var(--c) d) e; --c:c", "--a", "a b c d e"],
+ ["--a: var(--b)red; --b:orange;", "--a", "orange/**/red"],
+ ["--a: var(--b)var(--c); --b:orange; --c:red;", "--a", "orange/**/red"],
+ ["--a: var(--b)var(--c,red); --b:orange;", "--a", "orange/**/red"],
+ ["--a: var(--b,orange)var(--c); --c:red;", "--a", "orange/**/red"],
+ ["--a: var(--b)-; --b:-;", "--a", "-/**/-"],
+ ["--a: var(--b)--; --b:-;", "--a", "-/**/--"],
+ ["--a: var(--b)--x; --b:-;", "--a", "-/**/--x"],
+ ["--a: var(--b)var(--c); --b:-; --c:-;", "--a", "-/**/-"],
+ ["--a: var(--b)var(--c); --b:--; --c:-;", "--a", "--/**/-"],
+ ["--a: var(--b)var(--c); --b:--x; --c:-;", "--a", "--x/**/-"],
+ ["counter-reset: var(--a)red; --a:orange;", "counter-reset", "orange 0 red 0"],
+ ["--a: var(--b)var(--c); --c:[c]; --b:('ab", "--a", "('ab')[c]"],
+ ["--a: '", "--a", "''"],
+ ["--a: '\\", "--a", "''"],
+ ["--a: \\", "--a", "\\\ufffd"],
+ ["--a: \"", "--a", "\"\""],
+ ["--a: \"\\", "--a", "\"\""],
+ ["--a: url(http://example.org/", "--a", "url(http://example.org/)"],
+ ["--a: url(http://example.org/\\", "--a", "url(http://example.org/\\\ufffd)"],
+ ["--a: url('http://example.org/", "--a", "url('http://example.org/')"],
+ ["--a: url('http://example.org/\\", "--a", "url('http://example.org/')"],
+ ["--a: url(\"http://example.org/", "--a", "url(\"http://example.org/\")"],
+ ["--a: url(\"http://example.org/\\", "--a", "url(\"http://example.org/\")"]
+];
+
+function runTest() {
+ var div = document.querySelector("div");
+ var span = document.querySelector("span");
+
+ div.setAttribute("style", "--z:an-inherited-value");
+
+ values.forEach(function(entry, i) {
+ var declaration = entry[0];
+ var property = entry[1];
+ var expected = entry[2];
+ span.setAttribute("style", declaration);
+ var cs = getComputedStyle(span, "");
+ is(cs.getPropertyValue(property), expected, `subtest #${i}: ${declaration}`);
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
diff --git a/layout/style/test/test_variable_serialization_specified.html b/layout/style/test/test_variable_serialization_specified.html
new file mode 100644
index 0000000000..cae8871cb2
--- /dev/null
+++ b/layout/style/test/test_variable_serialization_specified.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<title>Test serialization of specified CSS variable values</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+
+<style id=style1>#test { }</style>
+<style id=style2></style>
+
+<script>
+// Values that should be serialized back to the same string.
+var values_with_unchanged_specified_value_serialization = [
+ "var(--a)",
+ "var(--a)",
+ "var(--a) ",
+ "var( --a ) ",
+ "var(--a, )",
+ "var(--a,/**/a)",
+ "1px var(--a)",
+ "var(--a) 1px",
+ "something 3px url(whereever) calc(var(--a) + 1px)",
+ "var(--a)",
+ "var(--a)var(--b)",
+ "var(--a, var(--b, var(--c, black)))",
+ "var(--a) <!--",
+ "--> var(--a)",
+ "{ [ var(--a) ] }",
+ "[;] var(--a)",
+ "var(--a,(;))",
+ "VAR(--a)",
+ "var(--0)",
+ "var(--\\30)",
+ "var(--\\d800)",
+ "var(--\\ffffff)",
+];
+
+// Values that serialize differently, due to additional implied closing
+// characters at EOF.
+var values_with_changed_specified_value_serialization = [
+ ["var(--a", "var(--a)"],
+ ["var(--a , ", "var(--a , )"],
+ ["var(--a, ", "var(--a, )"],
+ ["var(--a, var(--b", "var(--a, var(--b))"],
+ ["var(--a /* unclosed comment", "var(--a /* unclosed comment*/)"],
+ ["var(--a /* unclosed comment *", "var(--a /* unclosed comment */)"],
+ ["[{(((var(--a", "[{(((var(--a))))}]"],
+ ["var(--a, \"unclosed string", "var(--a, \"unclosed string\")"],
+ ["var(--a, 'unclosed string", "var(--a, 'unclosed string')"],
+ ["var(--a) \"unclosed string\\", "var(--a) \"unclosed string\""],
+ ["var(--a) 'unclosed string\\", "var(--a) 'unclosed string'"],
+ ["var(--a) \\", "var(--a) \\\ufffd"],
+ ["var(--a) url(unclosedurl", "var(--a) url(unclosedurl)"],
+ ["var(--a) url('unclosedurl", "var(--a) url('unclosedurl')"],
+ ["var(--a) url(\"unclosedurl", "var(--a) url(\"unclosedurl\")"],
+ ["var(--a) url(unclosedurl\\", "var(--a) url(unclosedurl\\\ufffd)"],
+ ["var(--a) url('unclosedurl\\", "var(--a) url('unclosedurl')"],
+ ["var(--a) url(\"unclosedurl\\", "var(--a) url(\"unclosedurl\")"],
+];
+
+var style1 = document.getElementById("style1");
+var style2 = document.getElementById("style2");
+
+var decl = style1.sheet.cssRules[0].style;
+
+function test_specified_value_serialization(value, expected) {
+ // Test setting value on a custom property with setProperty.
+ decl.setProperty("--test", value, "");
+ is(decl.getPropertyValue("--test"), expected,
+ "value with identical serialization set on custom property with setProperty");
+
+ // Test setting value on a custom property via style sheet parsing.
+ style2.textContent = "#test { --test:" + value;
+ is(style2.sheet.cssRules[0].style.getPropertyValue("--test"), expected,
+ "value with identical serialization set on custom property via parsing");
+
+ // Test setting value on a non-custom longhand property with setProperty.
+ decl.setProperty("color", value, "");
+ is(decl.getPropertyValue("color"), expected,
+ "value with identical serialization set on non-custom longhand property with setProperty");
+
+ // Test setting value on a non-custom longhand property via style sheet parsing.
+ style2.textContent = "#test { color:" + value;
+ is(style2.sheet.cssRules[0].style.getPropertyValue("color"), expected,
+ "value with identical serialization set on non-custom longhand property via parsing");
+
+ // Test setting value on a non-custom shorthand property with setProperty.
+ decl.setProperty("margin", value, "");
+ is(decl.getPropertyValue("margin"), expected,
+ "value with identical serialization set on non-custom shorthand property with setProperty");
+
+ // Test setting value on a non-custom shorthand property via style sheet parsing.
+ style2.textContent = "#test { margin:" + value;
+ is(style2.sheet.cssRules[0].style.getPropertyValue("margin"), expected,
+ "value with identical serialization set on non-custom shorthand property via parsing");
+
+ // Clean up.
+ decl.removeProperty("--test");
+ decl.removeProperty("color");
+ decl.removeProperty("margin");
+}
+
+function runTest() {
+ values_with_unchanged_specified_value_serialization.forEach(function(value) {
+ test_specified_value_serialization(value, value);
+ });
+
+ values_with_changed_specified_value_serialization.forEach(function(pair) {
+ test_specified_value_serialization(pair[0], pair[1]);
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
diff --git a/layout/style/test/test_variables.html b/layout/style/test/test_variables.html
new file mode 100644
index 0000000000..e26dc5a0f7
--- /dev/null
+++ b/layout/style/test/test_variables.html
@@ -0,0 +1,129 @@
+<!DOCTYPE type>
+<title>Assorted CSS variable tests</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+
+<style id="test1">
+</style>
+
+<style id="test2">
+</style>
+
+<style id="test3">
+</style>
+
+<style id="test4">
+</style>
+
+<div id="t4"></div>
+
+<style id="test5">
+</style>
+
+<div id="t5"></div>
+
+<style id="test6">
+</style>
+
+<style id="test7">
+</style>
+
+<style id="test8">
+</style>
+
+<script>
+var tests = [
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=773296#c121
+ var test1 = document.getElementById("test1");
+ test1.textContent = "p { --a:123!important; }";
+ var declaration = test1.sheet.cssRules[0].style;
+ declaration.cssText;
+ declaration.setProperty("color", "black");
+ is(declaration.getPropertyValue("--a"), "123");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=773296#c121
+ var test2 = document.getElementById("test2");
+ test2.textContent = "p { --a: a !important; }";
+ var declaration = test2.sheet.cssRules[0].style;
+ is(declaration.getPropertyPriority("--a"), "important");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=955913
+ var test3 = document.getElementById("test3");
+ test3.textContent = "p { border-left-style: inset; padding: 1px; --decoration: line-through; }";
+ var declaration = test3.sheet.cssRules[0].style;
+ is(declaration[declaration.length - 1], "--decoration");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=959973
+ var test4 = document.getElementById("test4");
+ test4.textContent = "#t4 { background-image: var(--a); }";
+
+ var element = document.getElementById("t4");
+ var path = window.location.pathname;
+ var currentDir = path.substring(0, path.lastIndexOf('/'));
+ var imageURL = "http://mochi.test:8888" + currentDir + "/image.png";
+
+ is(window.getComputedStyle(element).getPropertyValue("background-image"), "url(\"" + imageURL +"\")");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1043713
+ var test5 = document.getElementById("test5");
+ test5.textContent = "#t5 { --SomeVariableName: a; }";
+
+ var declaration = test5.sheet.cssRules[0].style;
+ is(declaration.item(0), "--SomeVariableName", "custom property name returned by item() on style declaration");
+ is(declaration[0], "--SomeVariableName", "custom property name returned by indexed getter on style declaration");
+
+ var element = document.getElementById("t5");
+ var cs = window.getComputedStyle(element);
+
+ is(cs.item(cs.length - 1), "--SomeVariableName", "custom property name returned by item() on computed style");
+ is(cs[cs.length - 1], "--SomeVariableName", "custom property name returned by indexed getter on style declaration");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1154356
+ var test7 = document.getElementById("test7");
+ test7.textContent = "p { --weird\\;name: green; }";
+ is(test7.sheet.cssRules[0].style.cssText, "--weird\\;name: green;");
+ test7.textContent = "p { --0: green; }";
+ is(test7.sheet.cssRules[0].style.cssText, "--0: green;");
+ },
+
+ function() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1330172
+ var test8 = document.getElementById("test8");
+ test8.textContent = "p { --a:inHerit; }";
+ is(test8.sheet.cssRules[0].style.cssText, "--a: inherit;");
+ test8.textContent = "p { --b: initial!important; }";
+ is(test8.sheet.cssRules[0].style.cssText, "--b: initial !important;");
+ test8.textContent = "p { --c: UNSET !important }";
+ is(test8.sheet.cssRules[0].style.cssText, "--c: unset !important;");
+ },
+];
+
+function prepareTest() {
+ // Load an external style sheet for test 4.
+ var e = document.createElement("link");
+ e.addEventListener("load", runTest);
+ e.setAttribute("rel", "stylesheet");
+ e.setAttribute("href", "support/external-variable-url.css");
+ document.head.appendChild(e);
+}
+
+function runTest() {
+ tests.forEach(function(fn) { fn(); });
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+prepareTest();
+</script>
diff --git a/layout/style/test/test_variables_loop.html b/layout/style/test/test_variables_loop.html
new file mode 100644
index 0000000000..76e97e26f3
--- /dev/null
+++ b/layout/style/test/test_variables_loop.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>CSS variables loop resolving</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+<style id="test">
+ #outer {
+ --a: a;
+ --b: b;
+ --c: c;
+ --d: d;
+ --e: e;
+ }
+ #inner {
+ --a: var(--d, ad);
+ --b: var(--d, ad);
+ --c: var(--d, ad);
+ --d: var(--e, de);
+ --e: var(--a, ea) var(--b, eb) var(--c, ec);
+ }
+</style>
+<div id="outer">
+ <div id="inner"></div>
+</div>
+<script>
+let inner_cs = getComputedStyle(document.getElementById("inner"));
+for (let v of ['a', 'b', 'c', 'd', 'e']) {
+ is(inner_cs.getPropertyValue(`--${v}`), "",
+ `Variable --${v} should be eliminated`);
+}
+</script>
diff --git a/layout/style/test/test_variables_order.html b/layout/style/test/test_variables_order.html
new file mode 100644
index 0000000000..5adb9edf75
--- /dev/null
+++ b/layout/style/test/test_variables_order.html
@@ -0,0 +1,53 @@
+<!DOCTYPE type>
+<title>CSS variables order tests</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
+
+<style id="test">
+</style>
+
+<div id="t4"></div>
+
+<script>
+
+/*
+ * Although the spec does not enforce any specific order, Gecko and Servo
+ * implement a consistent ordering for CSSDeclaration objects in the DOM.
+ * CSSDeclarations expose property names as indexed properties, which need
+ * to be stable. This order is the order that properties are cascaded in.
+ *
+ * We have this test just to prevent regressions, rather than testing specific
+ * mandated behavior.
+ */
+
+function prepareTest() {
+ var e = document.createElement("link");
+ e.addEventListener("load", runTest);
+ e.setAttribute("rel", "stylesheet");
+ e.setAttribute("href", "support/external-variable-url.css");
+ document.head.appendChild(e);
+}
+
+function runTest() {
+ var test = document.getElementById("test");
+ test.textContent = "div { --SomeVariableName: a; }";
+
+ var declaration = test.sheet.cssRules[0].style;
+ is(declaration.item(0), "--SomeVariableName", "custom property name returned by item() on style declaration");
+ is(declaration[0], "--SomeVariableName", "custom property name returned by indexed getter on style declaration");
+
+ var element = document.getElementById("t4");
+ var cs = window.getComputedStyle(element);
+
+ ["--SomeVariableName", "--a"].forEach((varName, index) => {
+ is(cs.item(cs.length - (index + 1)), varName, "custom property name returned by item() on computed style");
+ is(cs[cs.length - (index + 1)], varName, "custom property name returned by indexed getter on style declaration");
+ });
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+prepareTest();
+</script>
diff --git a/layout/style/test/test_video_object_fit.html b/layout/style/test/test_video_object_fit.html
new file mode 100644
index 0000000000..e673ba204e
--- /dev/null
+++ b/layout/style/test/test_video_object_fit.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1065766
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1065766</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1065766">Mozilla Bug 1065766</a>
+<div id="content" style="display: none">
+ <video id="myVideo"></video>
+</div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/**
+ * Test for Bug 1065766
+ *
+ * This test verifies that <video> has 'object-fit:contain' by default, set via
+ * a UA stylesheet. (This is different from the property's initial value, which
+ * is "fill".)
+ *
+ * Spec reference:
+ * https://html.spec.whatwg.org/multipage/rendering.html#video-object-fit
+ */
+
+function checkStyle(elem, expectedVal, message) {
+ is(window.getComputedStyle(elem).objectFit, expectedVal, message);
+}
+
+function main() {
+ const videoElem = document.getElementById("myVideo");
+
+ checkStyle(videoElem, "contain",
+ "<video> should have 'object-fit:contain' by default");
+
+ // Make sure we can override this behavior (i.e. that the UA stylesheet
+ // doesn't use "!important" to make this style mandatory):
+ videoElem.style.objectFit = "cover";
+ checkStyle(videoElem, "cover",
+ "<video> should honor 'object-fit:cover' in inline style");
+}
+
+main();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_viewport_scrollbar_causing_reflow.html b/layout/style/test/test_viewport_scrollbar_causing_reflow.html
new file mode 100644
index 0000000000..ced14f352a
--- /dev/null
+++ b/layout/style/test/test_viewport_scrollbar_causing_reflow.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1367568
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1367568</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug 1367568</a>
+<div id="content">
+ <!-- Some fixed-width divs that we shouldn't have to reflow when the viewport
+ changes. More than 5 so that our leeway for scrollbar parts doesn't
+ accidentally cause the test to pass -->
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="width: 100px">fixed-width <div>(child)</div></div>
+ <div style="position: absolute; width: 150px">
+ abs-fixed-width
+ <div>(child)</div>
+ </div>
+</div>
+<pre id="test">
+<script>
+"use strict";
+
+/** Test for Bug 1367568 **/
+
+/**
+ * This test verifies that "overflow" changes on the <body> don't cause
+ * an unnecessarily large amount of reflow.
+ */
+
+// Vars used in setStyleAndMeasure that we really only have to look up once:
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function setStyleAndMeasure(initialStyle, finalStyle) {
+ is(document.body.style.length, 0,
+ "Bug in test - body should start with empty style");
+ let unusedVal = document.body.offsetHeight; // flush layout
+ let constructCount = gUtils.framesConstructed;
+
+ document.body.style = initialStyle;
+ unusedVal = document.body.offsetHeight; // flush layout
+ let reflowCountBeforeTweak = gUtils.framesReflowed;
+
+ document.body.style = finalStyle;
+ unusedVal = document.body.offsetHeight; // flush layout
+ let reflowCountAfterTweak = gUtils.framesReflowed;
+
+ // Clean up:
+ document.body.style = "";
+
+ is(gUtils.framesConstructed, constructCount,
+ "Style tweak shouldn't have triggered frame construction");
+
+ // ...and return the delta:
+ return reflowCountAfterTweak - reflowCountBeforeTweak;
+}
+
+function main() {
+ // First, we sanity-check that our measurement make sense -- if we leave
+ // styles unchanged, we should measure no frames being reflowed:
+ let count = setStyleAndMeasure("width: 50px; height: 80px",
+ "width: 50px; height: 80px");
+ is(count, 0,
+ "Shouldn't reflow anything when we leave 'width' & 'height' unchanged");
+
+ // Now: see how many frames are reflowed when the "width" & "height" change.
+ // We'll use this as the reference when measuring reflow counts for various
+ // changes to "overflow" below.
+ count = setStyleAndMeasure("width: 50px; height: 80px",
+ "width: 90px; height: 60px");
+ ok(count > 0,
+ "Should reflow some frames when 'width' & 'height' change");
+
+ const scrollbarsHaveButtons = navigator.platform.includes("Win");
+ // This is to allow for reflowing scrollbar parts themselves.
+ const scrollbarReflows = scrollbarsHaveButtons ? 5 : 2;
+ // Expected maximum number of frames reflowed for "overflow" changes
+ const expectedMax = count + scrollbarReflows;
+
+ // Shared ending for messages in all ok() checks below:
+ const messageSuffix =
+ " shouldn't be greater than count for tweaking width/height on body (" +
+ expectedMax + ")";
+
+ // OK, here is where the relevant tests actually begin!!
+ // See how many frames we reflow for various tweaks to "overflow" on
+ // the body -- we expect the count to be no larger than |expectedMax|.
+ count = setStyleAndMeasure("", "overflow: scroll");
+ ok(count <= expectedMax,
+ "Reflow count when setting 'overflow: scroll' on body (" + count + ")" +
+ messageSuffix);
+
+ count = setStyleAndMeasure("", "overflow: hidden");
+ ok(count <= expectedMax,
+ "Reflow count when setting 'overflow: hidden' on body (" + count + ")" +
+ messageSuffix);
+
+ // Test removal of "overflow: scroll":
+ count = setStyleAndMeasure("overflow: scroll", "");
+ ok(count <= expectedMax,
+ "Reflow count when removing 'overflow: scroll' from body (" + count + ")" +
+ messageSuffix);
+
+ count = setStyleAndMeasure("overflow: hidden", "");
+ ok(count <= expectedMax,
+ "Reflow count when removing 'overflow: hidden' from body (" + count + ")" +
+ messageSuffix);
+
+ // Test change between two non-'visible' overflow values:
+ count = setStyleAndMeasure("overflow: scroll", "overflow: hidden");
+ ok(count <= expectedMax,
+ "Reflow count when changing 'overflow' on body (" + count + ")" +
+ messageSuffix);
+}
+
+main();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_viewport_units.html b/layout/style/test/test_viewport_units.html
new file mode 100644
index 0000000000..fa7df88eb6
--- /dev/null
+++ b/layout/style/test/test_viewport_units.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=804970
+-->
+<head>
+ <title>Test for dynamic changes to CSS 'vh', 'vw', 'vmin', and 'vmax' units</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=804970">Mozilla Bug 804970</a>
+<iframe id="iframe" src="viewport_units_iframe.html"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for CSS vh/vw/vmin/vmax units **/
+
+function px_to_num(str)
+{
+ return Number(String(str).match(/^([\d.]+)px$/)[1]);
+}
+
+function width(elt)
+{
+ return px_to_num(elt.ownerDocument.defaultView.getComputedStyle(elt).width);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var iframe = document.getElementById("iframe");
+ var idoc = iframe.contentDocument;
+ var vh = idoc.getElementById("vh");
+ var vw = idoc.getElementById("vw");
+ var vmin = idoc.getElementById("vmin");
+ var vmax = idoc.getElementById("vmax");
+
+ iframe.style.width = "100px";
+ iframe.style.height = "250px";
+ is(width(vh), 250, "vh should be 250px");
+ is(width(vw), 100, "vw should be 100px");
+ is(width(vmin), 100, "vmin should be 100px");
+ is(width(vmax), 250, "vmax should be 250px");
+
+ iframe.style.width = "300px";
+ is(width(vh), 250, "vh should be 250px");
+ is(width(vw), 300, "vw should be 300px");
+ is(width(vmin), 250, "vmin should be 250px");
+ is(width(vmax), 300, "vmax should be 300px");
+
+ iframe.style.height = "200px";
+ is(width(vh), 200, "vh should be 200px");
+ is(width(vw), 300, "vw should be 300px");
+ is(width(vmin), 200, "vmin should be 200px");
+ is(width(vmax), 300, "vmax should be 300px");
+
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", run);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_image_loading.html b/layout/style/test/test_visited_image_loading.html
new file mode 100644
index 0000000000..09aae8e53c
--- /dev/null
+++ b/layout/style/test/test_visited_image_loading.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557287
+-->
+<head>
+ <title>Test for Bug 557287</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557287">Mozilla Bug 147777</a>
+<pre id="test">
+<script type="application/ecmascript" src="visited_image_loading.sjs?reset"></script>
+<script type="application/javascript">
+
+/** Test for Bug 557287 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var subdoc, subwin;
+
+window.addEventListener("load", run);
+
+function run()
+{
+ subwin = window.open("visited_image_loading_frame.html", "_blank");
+ subwin.addEventListener("load", function() {
+ subdoc = subwin.document;
+ setTimeout(check_link_styled, 50);
+ });
+}
+
+function visitedDependentComputedStyle(win, elem, property) {
+ return SpecialPowers.DOMWindowUtils
+ .getVisitedDependentComputedStyle(elem, "", property);
+}
+
+function check_link_styled()
+{
+ var vislink = subdoc.getElementById("visited");
+ var bgcolor =
+ visitedDependentComputedStyle(subwin, vislink, "background-color");
+ if (bgcolor == "rgb(128, 0, 128)") {
+ // We've done our async :visited processing and restyled accordingly.
+ // Make sure that we've actually painted before finishing the test.
+ subwin.addEventListener("MozAfterPaint", paint_listener);
+ // do something that forces a paint
+ subdoc.body.appendChild(subdoc.createTextNode("new text node"));
+ } else {
+ setTimeout(check_link_styled, 50);
+ }
+}
+
+function paint_listener(event)
+{
+ subwin.removeEventListener("MozAfterPaint", paint_listener);
+ var s = document.createElement("script");
+ s.src = "visited_image_loading.sjs?waitforresult";
+ document.body.appendChild(s);
+ subwin.close();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_image_loading_empty.html b/layout/style/test/test_visited_image_loading_empty.html
new file mode 100644
index 0000000000..6687254720
--- /dev/null
+++ b/layout/style/test/test_visited_image_loading_empty.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557287
+-->
+<head>
+ <title>Test for Bug 557287</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557287">Mozilla Bug 147777</a>
+<pre id="test">
+<script type="application/ecmascript" src="visited_image_loading.sjs?reset"></script>
+<script type="application/javascript">
+
+/** Test for Bug 557287 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var subdoc, subwin;
+
+window.addEventListener("load", run);
+
+function run()
+{
+ subwin = window.open("visited_image_loading_frame_empty.html", "_blank");
+ subwin.addEventListener("load", function() {
+ subdoc = subwin.document;
+ setTimeout(check_link_styled, 50);
+ });
+}
+
+function visitedDependentComputedStyle(win, elem, property) {
+ return SpecialPowers.DOMWindowUtils
+ .getVisitedDependentComputedStyle(elem, "", property);
+}
+
+function check_link_styled()
+{
+ var vislink = subdoc.getElementById("visited");
+ var bgcolor =
+ visitedDependentComputedStyle(subwin, vislink, "background-color");
+ if (bgcolor == "rgb(128, 0, 128)") {
+ // We've done our async :visited processing and restyled accordingly.
+ // Make sure that we've actually painted before finishing the test.
+ subwin.addEventListener("MozAfterPaint", paint_listener);
+ // do something that forces a paint
+ subdoc.body.appendChild(subdoc.createTextNode("new text node"));
+ } else {
+ setTimeout(check_link_styled, 50);
+ }
+}
+
+function paint_listener(event)
+{
+ subwin.removeEventListener("MozAfterPaint", paint_listener);
+ var s = document.createElement("script");
+ s.src = "visited_image_loading.sjs?waitforresult";
+ document.body.appendChild(s);
+ subwin.close();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_lying.html b/layout/style/test/test_visited_lying.html
new file mode 100644
index 0000000000..116d301cf1
--- /dev/null
+++ b/layout/style/test/test_visited_lying.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=147777
+-->
+<head>
+ <title>Test for Bug 147777</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=147777">Mozilla Bug 147777</a>
+<iframe id="iframe" src="visited-lying-inner.html" style="width: 20em; height: 5em"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 147777 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+window.addEventListener("load", start);
+
+var iframe;
+var visitedlink, unvisitedlink;
+var snapshot1;
+
+function start()
+{
+ // Our load event has fired, so we know our iframe is loaded.
+ iframe = document.getElementById("iframe");
+ visitedlink = iframe.contentDocument.getElementById("visitedlink");
+ unvisitedlink = iframe.contentDocument.getElementById("unvisitedlink");
+
+ // First, take a snapshot of it with both links unvisited.
+ snapshot1 = snapshotWindow(iframe.contentWindow, false);
+
+ // Then, change one of the links in the iframe to being visited.
+ visitedlink.href = top.location;
+
+ // Then, start polling to see when the history has updated the display.
+ setTimeout(poll_for_restyle, 100);
+}
+
+function poll_for_restyle()
+{
+ var snapshot2 = snapshotWindow(iframe.contentWindow, false);
+ var equal = compareSnapshots(snapshot1, snapshot2, true)[0];
+ if (equal) {
+ // keep polling
+ setTimeout(poll_for_restyle, 100);
+ } else {
+ // We now know that the link is visited, so we're ready to run
+ // tests.
+ run_tests();
+ }
+}
+
+function run_tests()
+{
+ // Test querySelector and querySelectorAll.
+ var subdoc = iframe.contentDocument;
+ is(subdoc.querySelector(":link"), unvisitedlink,
+ "first :link should be the unvisited link");
+ is(subdoc.querySelector(":visited"), null,
+ "querySelector should not find anything :visited");
+ var qsr = subdoc.querySelectorAll(":link");
+ is(qsr.length, 2, "querySelectorAll(:link) should find 2 results");
+ is(qsr[0], unvisitedlink, "querySelectorAll(:link)[0]");
+ is(qsr[1], visitedlink, "querySelectorAll(:link)[1]");
+ qsr = subdoc.querySelectorAll(":visited");
+ is(qsr.length, 0, "querySelectorAll(:visited) should find 0 results");
+
+ // Test getComputedStyle.
+ var subwin = iframe.contentWindow;
+ is(subwin.getComputedStyle(unvisitedlink).color, "rgb(0, 0, 255)",
+ "getComputedStyle on unvisited link should report color is blue");
+ is(subwin.getComputedStyle(visitedlink).color, "rgb(0, 0, 255)",
+ "getComputedStyle on visited link should report color is blue");
+
+ // Test matches.
+ is(unvisitedlink.matches(":link"), true,
+ "unvisited link matches :link");
+ is(visitedlink.matches(":link"), true,
+ "visited link matches :link");
+ is(unvisitedlink.matches(":visited"), false,
+ "unvisited link does not match :visited");
+ is(visitedlink.matches(":visited"), false,
+ "visited link does not match :visited");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_pref.html b/layout/style/test/test_visited_pref.html
new file mode 100644
index 0000000000..481a71bfef
--- /dev/null
+++ b/layout/style/test/test_visited_pref.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=147777
+-->
+<head>
+ <title>Test for visited link coloring pref Bug 147777</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+
+ :link { float: left; }
+
+ :visited { float: right; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=147777">Mozilla Bug 147777</a>
+<iframe id="iframe" src="visited-pref-iframe.html" style="width: 10em; height: 5em"></iframe>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 147777 **/
+
+function reinsert_node(e) {
+ var sib = e.nextSibling;
+ var par = e.parentNode;
+ par.removeChild(e);
+ par.insertBefore(e, sib);
+}
+
+function get_pref()
+{
+ return SpecialPowers.getBoolPref("layout.css.visited_links_enabled");
+}
+
+function snapshotsEqual(snap1, snap2)
+{
+ return compareSnapshots(snap1, snap2, true)[0];
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+window.addEventListener("load", step1);
+
+var iframe, subdoc, subwin;
+var link;
+var start;
+var timeout;
+
+var unvisref; // reference image for unvisited style
+
+function step1()
+{
+ is(get_pref(), true, "pref defaults to true");
+
+ iframe = document.getElementById("iframe");
+ subdoc = iframe.contentDocument;
+ subwin = iframe.contentWindow;
+ link = subdoc.getElementById("link");
+
+ unvisref = snapshotWindow(subwin, false);
+
+ // Now set the href of the link to a location that's actually visited.
+ link.href = top.location;
+
+ start = Date.now();
+
+ // And wait for the link to get restyled when the history lets us
+ // know it is (asynchronously).
+ setTimeout(poll_for_visited_style, 100);
+}
+
+function poll_for_visited_style()
+{
+ var snapshot = snapshotWindow(subwin, false);
+ if (snapshotsEqual(unvisref, snapshot)) {
+ // hasn't been styled yet
+ setTimeout(poll_for_visited_style, 100);
+
+ // If it never gets styled correctly, this test will fail because
+ // this loop will never complete.
+ } else {
+ var end = Date.now();
+ timeout = 3 * Math.max(end - start, 300);
+ SpecialPowers.pushPrefEnv({"set":[["layout.css.visited_links_enabled", false]]}, step2);
+ }
+}
+
+function step2()
+{
+ // we don't handle dynamic changes of this pref; it only takes effect
+ // when a new page loads
+ reinsert_node(link);
+
+ setTimeout(step3, timeout);
+}
+
+function step3()
+{
+ var snapshot = snapshotWindow(subwin, false);
+ ok(snapshotsEqual(unvisref, snapshot),
+ ":visited selector does not apply given false preference");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_visited_reftests.html b/layout/style/test/test_visited_reftests.html
new file mode 100644
index 0000000000..0353d948fc
--- /dev/null
+++ b/layout/style/test/test_visited_reftests.html
@@ -0,0 +1,210 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=147777
+-->
+<head>
+ <title>Test for Bug 147777</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=147777">Mozilla Bug 147777</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 147777 **/
+
+// Because link-coloring for visited links is asynchronous, running
+// reftests that involve link coloring requires that we poll for the
+// correct result until all links are styled correctly.
+
+// A requirement of these reftests is that the reference rendering is
+// styled correctly when loaded. We only poll for the tests.
+
+var gTests = [
+ // there's also an implicit "load visited-page.html" at the start,
+ // thanks to the code below.
+
+ // IMPORTANT NOTE: For these tests, the test and reference are not
+ // snapshotted in the same way. The REFERENCE (second file) is
+ // assumed to be complete when loaded, but we poll for visited link
+ // coloring on the TEST (first file) until the test passes.
+ "== pseudo-classes-02.svg pseudo-classes-02-ref.svg",
+ "needs-focus == caret-color-on-visited-1.html caret-color-on-visited-1-ref.html",
+ "!= color-on-link-1-ref.html color-on-visited-1-ref.html",
+ "== color-on-link-1.html color-on-link-1-ref.html",
+ "== color-on-link-before-1.html color-on-link-1-ref.html",
+ "== color-on-visited-1.html color-on-visited-1-ref.html",
+ "== color-on-visited-before-1.html color-on-visited-1-ref.html",
+ "== color-on-visited-text-1.html color-on-visited-text-1-ref.html",
+ "!= content-color-on-link-before-1-ref.html content-color-on-visited-before-1-ref.html",
+ "== content-color-on-link-before-1.html content-color-on-link-before-1-ref.html",
+ "== content-color-on-visited-before-1.html content-color-on-visited-before-1-ref.html",
+ "== content-on-link-before-1.html content-before-1-ref.html",
+ "== content-on-visited-before-1.html content-before-1-ref.html",
+ "== color-on-text-decoration-1.html color-on-text-decoration-1-ref.html",
+ "== color-on-bullets-1.html color-on-bullets-1-ref.html",
+ // NOTE: background-color is tested by all the selector tests (and
+ // also color-choice-1) and therefore doesn't have its own tests.
+ // FIXME: Maybe add a test for selection colors (foreground and
+ // background), if possible.
+ "== width-on-link-1.html width-1-ref.html",
+ "== width-on-visited-1.html width-1-ref.html",
+ "== border-1.html border-1-ref.html",
+ "== border-2a.html border-2-ref.html",
+ "== border-2b.html border-2-ref.html",
+ // FIXME: Commented out because of dynamic change handling bugs in
+ // border-collapse tables that mean we get an incorrect rendering when
+ // the asynchronous restyle-from-history arrives.
+ //"== border-collapse-1.html border-collapse-1-ref.html",
+ "== outline-1.html outline-1-ref.html",
+ "== column-rule-1.html column-rule-1-ref.html",
+ "!= column-rule-1.html column-rule-1-notref.html",
+ "== color-choice-1.html color-choice-1-ref.html",
+ "== selector-descendant-1.html selector-descendant-1-ref.html",
+ "== selector-descendant-2.xhtml selector-descendant-2-ref.xhtml",
+ "== selector-child-1.html selector-child-1-ref.html",
+ "== selector-child-2.xhtml selector-child-2-ref.xhtml",
+ "== selector-adj-sibling-1.html selector-adj-sibling-1-ref.html",
+ "== selector-adj-sibling-2.html selector-adj-sibling-2-ref.html",
+ "== selector-adj-sibling-3.xhtml selector-adj-sibling-3-ref.xhtml",
+ "== selector-any-sibling-1.html selector-any-sibling-1-ref.html",
+ "== selector-any-sibling-2.html selector-any-sibling-2-ref.html",
+ "== subject-of-selector-descendant-1.html subject-of-selector-1-ref.html",
+ "== subject-of-selector-descendant-2.xhtml subject-of-selector-descendant-2-ref.xhtml",
+ "== subject-of-selector-child-1.html subject-of-selector-1-ref.html",
+ "== subject-of-selector-adj-sibling-1.html subject-of-selector-1-ref.html",
+ "== subject-of-selector-any-sibling-1.html subject-of-selector-1-ref.html",
+ "== inherit-keyword-1.xhtml inherit-keyword-1-ref.html",
+ "== svg-image-visited-1a.html svg-image-visited-1-ref.html",
+ "== svg-image-visited-1b.html svg-image-visited-1-ref.html",
+ "== svg-image-visited-1c.html svg-image-visited-1-ref.html",
+ "== svg-image-visited-1d.html svg-image-visited-1-ref.html",
+ // FIXME: commented out because dynamic changes on the non-first-line
+ // part of the test don't work right when the link becomes visited.
+ //"== first-line-1.html first-line-1-ref.html",
+ "== white-to-transparent-1.html white-to-transparent-1-ref.html",
+ "== link-root-1.xhtml link-root-1-ref.xhtml",
+ "== mathml-links.html mathml-links-ref.html",
+ "== placeholder-1.html placeholder-1-ref.html",
+ "== visited-inherit-1.html visited-inherit-1-ref.html",
+ "== transition-on-visited.html transition-on-visited-ref.html",
+ "== logical-box-border-color-visited-link-001.html logical-box-border-color-visited-link-ref.html",
+ "== logical-box-border-color-visited-link-002.html logical-box-border-color-visited-link-ref.html",
+ "== logical-box-border-color-visited-link-003.html logical-box-border-color-visited-link-ref.html",
+ "== svg-paint-currentcolor-visited.svg svg-paint-currentcolor-visited-ref.svg",
+ "== variables-visited.html variables-visited-ref.html",
+];
+
+// We record the maximum number of times we had to look at a test before
+// it switched to the passing state (though we assume it's 10 to start
+// rather than 0 so that we have a reasonable default). Then we make a
+// test "time out" if it takes more than gTimeoutFactor times that
+// amount of time. This allows us to report a test failure rather than
+// making a test failure just show up as a timeout.
+var gMaxPassingTries = 10;
+var gTimeoutFactor = 10;
+
+function startIframe(url) {
+ return new Promise(resolve => {
+ var element = document.createElement("iframe");
+ element.addEventListener("load", () => {
+ element.contentDocument.fonts.ready.then(() => {
+ resolve(element.contentWindow);
+ });
+ }, {once: true});
+ // smaller than normal reftests, but enough for these
+ element.setAttribute("style", "width: 30em; height: 10em");
+ element.src = "css-visited/" + url;
+ document.body.appendChild(element);
+ });
+}
+
+async function runTests() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("async link coloring");
+ // Set caret to a known size, for tests of :visited caret-color styling
+ await SpecialPowers.pushPrefEnv({'set': [['ui.caretWidth', 16]]});
+ info("opening visited page");
+ let win = window.open("css-visited/visited-page.html", "_blank");
+ await new Promise(resolve => {
+ win.onload = resolve;
+ });
+ info("running tests");
+ await Promise.all(gTests.map(runTest));
+ win.close();
+ SimpleTest.finish();
+}
+
+function passes(equal, shot1, shot2)
+{
+ let [correct] = compareSnapshots(shot1, shot2, equal);
+ return correct;
+}
+
+function waitFor100msAndIdle() {
+ return new Promise(resolve => setTimeout(function() {
+ requestIdleCallback(resolve);
+ }, 100));
+}
+
+async function runTest(testLine) {
+ let splitData = testLine.split(" ");
+ let isEqual;
+ let needsFocus = false;
+ while (true) {
+ let op = splitData.shift();
+ if (op == "needs-focus") {
+ needsFocus = true;
+ } else if (op == "==" || op == "!=") {
+ isEqual = op == "==";
+ break;
+ } else {
+ ok(false, "Unknown syntax");
+ return;
+ }
+ }
+ let [testFile, refFile] = splitData;
+
+ let promiseTestWin = startIframe(testFile);
+ let promiseRefWin = startIframe(refFile);
+ let refSnapshot = snapshotWindow(await promiseRefWin);
+ let testWindow = await promiseTestWin;
+ // Always wait at least 100ms, so that any test that switches
+ // from passing to failing when the asynchronous link coloring
+ // happens should fail at least some of the time.
+ await waitFor100msAndIdle();
+
+ let tries;
+ let testSnapshot;
+ for (tries = 0; tries < gMaxPassingTries * gTimeoutFactor; ++tries) {
+ if (needsFocus) {
+ await SimpleTest.promiseFocus(testWindow, false);
+ }
+ testSnapshot = snapshotWindow(testWindow, true);
+ if (passes(isEqual, testSnapshot, refSnapshot)) {
+ if (tries > gMaxPassingTries) {
+ gMaxPassingTries = tries;
+ }
+ break;
+ }
+ // Links might not have been colored yet. Try again in 100ms.
+ await waitFor100msAndIdle();
+ }
+
+ let result = assertSnapshots(testSnapshot, refSnapshot,
+ isEqual, null, testFile, refFile);
+ if (!result) {
+ info(`Gave up after ${tries} tries, ` +
+ `maxp=${gMaxPassingTries}, fact=${gTimeoutFactor}`);
+ }
+}
+
+runTests();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_webkit_device_pixel_ratio.html b/layout/style/test/test_webkit_device_pixel_ratio.html
new file mode 100644
index 0000000000..69e50c58ff
--- /dev/null
+++ b/layout/style/test/test_webkit_device_pixel_ratio.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1176968
+-->
+<head>
+ <title>Test for Bug 1176968</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style>.zoom-test { visibility: hidden; }</style>
+ <style><!-- placeholder for dynamic additions --></style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1176968">Mozilla Bug 1176968</a>
+<div id="content" style="display: none">
+
+</div>
+<script type="text/javascript">
+</script>
+<pre id="test">
+<div id="zoom1" class="zoom-test"></div>
+<div id="zoom2" class="zoom-test"></div>
+<div id="zoom3" class="zoom-test"></div>
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 1176968 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ function zoom(factor) {
+ var previous = SpecialPowers.getFullZoom(window);
+ SpecialPowers.setFullZoom(window, factor);
+ return previous;
+ }
+
+ function isVisible(divName) {
+ return window.getComputedStyle(document.getElementById(divName)).visibility == "visible";
+ }
+
+ var screenPixelsPerCSSPixel = window.devicePixelRatio;
+ var baseRatio = 1.0 * screenPixelsPerCSSPixel;
+ var doubleRatio = 2.0 * screenPixelsPerCSSPixel;
+ var halfRatio = 0.5 * screenPixelsPerCSSPixel;
+ var styleElem = document.getElementsByTagName("style")[1];
+ styleElem.textContent =
+ ["@media all and (-webkit-device-pixel-ratio: " + baseRatio + ") {",
+ "#zoom1 { visibility: visible; }",
+ "}",
+ "@media all and (-webkit-device-pixel-ratio: " + doubleRatio + ") {",
+ "#zoom2 { visibility: visible; }",
+ "}",
+ "@media all and (-webkit-device-pixel-ratio: " + halfRatio + ") {",
+ "#zoom3 { visibility: visible; }",
+ "}"
+ ].join("\n");
+
+ ok(isVisible("zoom1"), "Base ratio rule should apply at base zoom level");
+ ok(!isVisible("zoom2") && !isVisible("zoom3"), "no other rules should apply");
+ var origZoom = zoom(2);
+ ok(isVisible("zoom2"), "Double ratio rule should apply at double zoom level");
+ ok(!isVisible("zoom1") && !isVisible("zoom3"), "no other rules should apply");
+ zoom(0.5);
+ ok(isVisible("zoom3"), "Half ratio rule should apply at half zoom level");
+ ok(!isVisible("zoom1") && !isVisible("zoom2"), "no other rules should apply");
+ zoom(origZoom);
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_webkit_flex_display.html b/layout/style/test/test_webkit_flex_display.html
new file mode 100644
index 0000000000..2f329ed67d
--- /dev/null
+++ b/layout/style/test/test_webkit_flex_display.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1274096
+-->
+<head>
+ <title>Test for Bug 1274096</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1274096">Mozilla Bug 1274096</a>
+<div id="content" style="display: none">
+ <div id="testElem"></div>
+</div>
+<script type="text/javascript">
+</script>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 1274096 **/
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+function runTest() {
+ testValue("display", "-webkit-flex", "flex");
+ testValue("display", "-webkit-inline-flex", "inline-flex");
+
+ SimpleTest.finish();
+}
+
+function testValue(propName, specifiedVal, serializedVal) {
+ var testElem = document.getElementById("testElem");
+ testElem.style[propName] = specifiedVal;
+
+ is(testElem.style[propName], serializedVal,
+ `CSS '${propName}:${specifiedVal} should serialize as '${serializedVal}'`);
+ is(window.getComputedStyle(testElem)[propName], serializedVal,
+ `CSS 'display:${specifiedVal} should compute to '${serializedVal}'`);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/unstyled-frame.css b/layout/style/test/unstyled-frame.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/layout/style/test/unstyled-frame.css
diff --git a/layout/style/test/unstyled-frame.xml b/layout/style/test/unstyled-frame.xml
new file mode 100644
index 0000000000..833b4f112f
--- /dev/null
+++ b/layout/style/test/unstyled-frame.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="unstyled-frame.css" type="text/css"?>
+<!-- The root element is forced to display:block, so look at its child -->
+<root><child/></root>
diff --git a/layout/style/test/unstyled.css b/layout/style/test/unstyled.css
new file mode 100644
index 0000000000..82767f9b2f
--- /dev/null
+++ b/layout/style/test/unstyled.css
@@ -0,0 +1,2 @@
+/* we're testing computed style on elements without frames */
+root { display: none }
diff --git a/layout/style/test/unstyled.xml b/layout/style/test/unstyled.xml
new file mode 100644
index 0000000000..86b7c54acd
--- /dev/null
+++ b/layout/style/test/unstyled.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="unstyled.css" type="text/css"?>
+<root><child/></root>
diff --git a/layout/style/test/viewport_units_iframe.html b/layout/style/test/viewport_units_iframe.html
new file mode 100644
index 0000000000..fd71a3cd3e
--- /dev/null
+++ b/layout/style/test/viewport_units_iframe.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<title>viewport units test</title>
+<div id="vh" style="width: 100vh"></div>
+<div id="vw" style="width: 100vw"></div>
+<div id="vmin" style="width: 100vmin"></div>
+<div id="vmax" style="width: 100vmax"></div>
diff --git a/layout/style/test/visited-lying-inner.html b/layout/style/test/visited-lying-inner.html
new file mode 100644
index 0000000000..ad1dac7587
--- /dev/null
+++ b/layout/style/test/visited-lying-inner.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<title>Test document for test_visited_lying.html</title>
+<style>
+:link { color: blue }
+:visited { color: purple }
+</style>
+<div><a id="unvisitedlink" href="http://www.example.com/url-that-was-never-visited">unvisited link</a></div>
+<div><a id="visitedlink" href="http://www.example.com/url-that-was-never-visited">visited link</a></div>
diff --git a/layout/style/test/visited-pref-iframe.html b/layout/style/test/visited-pref-iframe.html
new file mode 100644
index 0000000000..31da176e44
--- /dev/null
+++ b/layout/style/test/visited-pref-iframe.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<title>iframe for test_visited_pref.html</title>
+<style>
+:link { color: blue }
+:visited { color: purple }
+</style>
+<a href="http://www.example.com/url-that-has-not-been-visited" id="link">link</a>
diff --git a/layout/style/test/visited_image_loading.sjs b/layout/style/test/visited_image_loading.sjs
new file mode 100644
index 0000000000..79c03d7b54
--- /dev/null
+++ b/layout/style/test/visited_image_loading.sjs
@@ -0,0 +1,83 @@
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ var query = request.queryString;
+ switch (query) {
+ case "reset":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ setState("1l", "");
+ setState("1v", "");
+ setState("2l", "");
+ setState("2v", "");
+ break;
+ case "1l":
+ case "1v":
+ case "2l":
+ case "2v":
+ setState(query, getState(query) + "load");
+ response.setStatusLine("1.1", 302, "Found");
+ // redirect to a solid blue image
+ response.setHeader(
+ "Location",
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12NgYPgPAAEDAQDZqt2zAAAAAElFTkSuQmCC"
+ );
+ response.setHeader("Content-Type", "text/plain", false);
+ break;
+
+ case "waitforresult":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ response.write("var start = Date.now();\n");
+ // fall through!
+
+ case "waitforresult-internal":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ response.write(
+ "if ('" +
+ getState("1l") +
+ "' == 'load' && '" +
+ getState("1v") +
+ "' == '' && '" +
+ getState("2l") +
+ "' == 'load' && '" +
+ getState("2v") +
+ "' == '') { \n"
+ );
+ response.write("setTimeout(function() {\n");
+ response.write("var s = document.createElement('script');\n");
+ response.write("s.src = 'visited_image_loading.sjs?result';\n");
+ response.write("document.body.appendChild(s);");
+ response.write("}, Math.max(100, 2 * (Date.now() - start)));\n");
+ response.write("} else setTimeout(function() {\n");
+ response.write("var s = document.createElement('script');\n");
+ response.write(
+ "s.src = 'visited_image_loading.sjs?waitforresult-internal';\n"
+ );
+ response.write("document.body.appendChild(s);");
+ response.write("}, 10);\n");
+ break;
+
+ case "result":
+ response.setHeader("Content-Type", "application/ecmascript", false);
+ response.write(
+ "is('" +
+ getState("1l") +
+ "', 'load', 'image 1l should have been loaded once')\n"
+ );
+ response.write(
+ "is('" +
+ getState("1v") +
+ "', '', 'image 1v should not have been loaded')\n"
+ );
+ response.write(
+ "is('" +
+ getState("2l") +
+ "', 'load', 'image 2l should have been loaded once')\n"
+ );
+ response.write(
+ "is('" +
+ getState("2v") +
+ "', '', 'image 2v should not have been loaded')\n"
+ );
+ response.write("SimpleTest.finish()");
+ break;
+ }
+}
diff --git a/layout/style/test/visited_image_loading_frame.html b/layout/style/test/visited_image_loading_frame.html
new file mode 100644
index 0000000000..f919f5eb75
--- /dev/null
+++ b/layout/style/test/visited_image_loading_frame.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<title>Test for :visited image loading</title>
+<style type="text/css">
+
+:link { background-color: blue }
+:visited { background-color: purple }
+
+#link:link { background-image: url("visited_image_loading.sjs?1l"); }
+#link:visited { background-image: url("visited_image_loading.sjs?1v"); }
+#visited:link { background-image: url("visited_image_loading.sjs?2l"); }
+#visited:visited { background-image: url("visited_image_loading.sjs?2v"); }
+
+</style>
+<a id="link" href="do-not-visit-this-link.html">unvisited link</a>
+<a id="visited" href="visited_image_loading_frame.html">visited link</a>
diff --git a/layout/style/test/visited_image_loading_frame_empty.html b/layout/style/test/visited_image_loading_frame_empty.html
new file mode 100644
index 0000000000..21579bb9ca
--- /dev/null
+++ b/layout/style/test/visited_image_loading_frame_empty.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<title>Test for :visited image loading</title>
+<style type="text/css">
+
+:link { background-color: blue }
+:visited { background-color: purple }
+
+#link:link { background-image: url("visited_image_loading.sjs?1l"); }
+#link:visited { background-image: url("visited_image_loading.sjs?1v"); }
+#visited:link { background-image: url("visited_image_loading.sjs?2l"); }
+#visited:visited { background-image: url("visited_image_loading.sjs?2v"); }
+
+</style>
+<a id="link" href="do-not-visit-this-link.html"></a>
+<a id="visited" href="visited_image_loading_frame_empty.html"></a>