summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/rules/test
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/inspector/rules/test')
-rw-r--r--devtools/client/inspector/rules/test/.eslintrc.js6
-rw-r--r--devtools/client/inspector/rules/test/browser.ini221
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js44
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-property-cancel_01.js44
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-property-cancel_02.js34
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-property-cancel_03.js43
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-property-commented.js47
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-property-svg.js22
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-property_01.js32
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-property_02.js65
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js30
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-rule-button-state.js51
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-rule-edit-selector.js55
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js57
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-rule-namespace-elements.js41
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-rule-pseudo-class.js82
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js80
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-rule-with-menu.js42
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_add-rule.js47
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_authored.js49
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_authored_color.js67
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_authored_override.js53
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_blob_stylesheet.js20
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorUnit.js65
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js63
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js66
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js51
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-commit-on-ENTER.js61
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js77
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-on-tooltip.js46
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js124
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-release-outside-frame.js67
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js109
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_colorpicker-swatch-displayed.js73
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js139
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js123
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js102
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js129
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_completion-new-property_03.js47
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js73
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js131
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_completion-popup-hidden-after-navigation.js41
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_computed-lists_01.js47
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_computed-lists_02.js74
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_content_01.js51
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_content_02.js60
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js96
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js61
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-03.js118
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_copy_styles.js307
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_css-docs-tooltip_closes-on-escape.js51
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_cssom.js22
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_cubicbezier-appears-on-swatch-click.js70
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_cubicbezier-commit-on-ENTER.js66
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_cubicbezier-revert-on-ESC.js100
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_custom.js72
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_cycle-angle.js93
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_cycle-color.js120
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js49
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property-cancel.js46
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property-click.js61
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js92
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js89
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js280
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property-order.js89
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property-remove_01.js67
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property-remove_02.js67
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property-remove_03.js83
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property_01.js93
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property_02.js133
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property_03.js50
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property_04.js85
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property_05.js77
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property_06.js52
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property_07.js50
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property_08.js57
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-property_09.js69
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector-click-on-scrollbar.js88
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector-click.js63
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector-commit.js117
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_01.js62
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_02.js88
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_03.js48
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_04.js69
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_05.js78
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_06.js76
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_07.js62
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_08.js71
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_09.js110
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_10.js64
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-selector_11.js69
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_01.js107
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_02.js65
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_03.js69
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_04.js62
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_editable-field-focus_01.js94
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_editable-field-focus_02.js84
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_eyedropper.js123
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_filtereditor-appears-on-swatch-click.js34
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_filtereditor-commit-on-ENTER.js45
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_filtereditor-revert-on-ESC.js118
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js41
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js53
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js64
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js73
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js96
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_guessIndentation.js47
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_inherited-properties_01.js47
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_inherited-properties_02.js34
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_inherited-properties_03.js40
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_inline-source-map.js26
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_invalid-source-map.js44
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_invalid.js33
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_keybindings.js49
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_keyframeLineNumbers.js25
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js106
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js92
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_lineNumbers.js29
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_livepreview.js72
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_mark_overridden_01.js56
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_mark_overridden_02.js45
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_mark_overridden_03.js41
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_mark_overridden_04.js36
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_mark_overridden_05.js33
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_mark_overridden_06.js60
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_mark_overridden_07.js72
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_mathml-element.js53
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_media-queries.js26
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_multiple-properties-duplicates.js68
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_multiple-properties-priority.js47
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js62
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js71
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_multiple_properties_01.js53
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js54
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_original-source-link.js85
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js260
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js29
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js131
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_refresh-no-flicker.js39
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_01.js61
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js153
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_refresh-on-style-change.js38
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js156
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js93
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_03.js49
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_04.js63
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_expander.js92
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter-overridden-property.js74
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_01.js91
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_02.js32
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_03.js39
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_04.js76
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_05.js33
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_06.js27
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_07.js62
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_08.js53
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_09.js73
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_10.js84
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js83
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_search-filter_escape-keypress.js65
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js171
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_selector-highlighter-on-navigate.js38
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_selector-highlighter_01.js35
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_selector-highlighter_02.js78
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_selector-highlighter_03.js78
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_selector-highlighter_04.js53
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_selector_highlight.js144
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js182
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js130
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_strict-search-filter_02.js34
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_strict-search-filter_03.js44
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_style-editor-link.js203
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_urls-clickable.js70
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_user-agent-styles-uneditable.js58
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js183
-rw-r--r--devtools/client/inspector/rules/test/browser_rules_user-property-reset.js90
-rw-r--r--devtools/client/inspector/rules/test/doc_author-sheet.html39
-rw-r--r--devtools/client/inspector/rules/test/doc_blob_stylesheet.html39
-rw-r--r--devtools/client/inspector/rules/test/doc_content_stylesheet.html35
-rw-r--r--devtools/client/inspector/rules/test/doc_content_stylesheet_imported.css5
-rw-r--r--devtools/client/inspector/rules/test/doc_content_stylesheet_imported2.css3
-rw-r--r--devtools/client/inspector/rules/test/doc_content_stylesheet_linked.css3
-rw-r--r--devtools/client/inspector/rules/test/doc_content_stylesheet_script.css5
-rw-r--r--devtools/client/inspector/rules/test/doc_copystyles.css11
-rw-r--r--devtools/client/inspector/rules/test/doc_copystyles.html11
-rw-r--r--devtools/client/inspector/rules/test/doc_cssom.html22
-rw-r--r--devtools/client/inspector/rules/test/doc_custom.html33
-rw-r--r--devtools/client/inspector/rules/test/doc_filter.html13
-rw-r--r--devtools/client/inspector/rules/test/doc_frame_script.js113
-rw-r--r--devtools/client/inspector/rules/test/doc_inline_sourcemap.html18
-rw-r--r--devtools/client/inspector/rules/test/doc_invalid_sourcemap.css3
-rw-r--r--devtools/client/inspector/rules/test/doc_invalid_sourcemap.html11
-rw-r--r--devtools/client/inspector/rules/test/doc_keyframeLineNumbers.html45
-rw-r--r--devtools/client/inspector/rules/test/doc_keyframeanimation.css84
-rw-r--r--devtools/client/inspector/rules/test/doc_keyframeanimation.html13
-rw-r--r--devtools/client/inspector/rules/test/doc_media_queries.html24
-rw-r--r--devtools/client/inspector/rules/test/doc_pseudoelement.html131
-rw-r--r--devtools/client/inspector/rules/test/doc_ruleLineNumbers.html19
-rw-r--r--devtools/client/inspector/rules/test/doc_sourcemaps.css7
-rw-r--r--devtools/client/inspector/rules/test/doc_sourcemaps.css.map7
-rw-r--r--devtools/client/inspector/rules/test/doc_sourcemaps.html11
-rw-r--r--devtools/client/inspector/rules/test/doc_sourcemaps.scss10
-rw-r--r--devtools/client/inspector/rules/test/doc_style_editor_link.css3
-rw-r--r--devtools/client/inspector/rules/test/doc_test_image.pngbin0 -> 580 bytes
-rw-r--r--devtools/client/inspector/rules/test/doc_urls_clickable.css9
-rw-r--r--devtools/client/inspector/rules/test/doc_urls_clickable.html30
-rw-r--r--devtools/client/inspector/rules/test/head.js840
207 files changed, 14659 insertions, 0 deletions
diff --git a/devtools/client/inspector/rules/test/.eslintrc.js b/devtools/client/inspector/rules/test/.eslintrc.js
new file mode 100644
index 000000000..698ae9181
--- /dev/null
+++ b/devtools/client/inspector/rules/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/client/inspector/rules/test/browser.ini b/devtools/client/inspector/rules/test/browser.ini
new file mode 100644
index 000000000..2c11219fb
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -0,0 +1,221 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ doc_author-sheet.html
+ doc_blob_stylesheet.html
+ doc_content_stylesheet.html
+ doc_content_stylesheet_imported.css
+ doc_content_stylesheet_imported2.css
+ doc_content_stylesheet_linked.css
+ doc_content_stylesheet_script.css
+ doc_copystyles.css
+ doc_copystyles.html
+ doc_cssom.html
+ doc_custom.html
+ doc_filter.html
+ doc_frame_script.js
+ doc_inline_sourcemap.html
+ doc_invalid_sourcemap.css
+ doc_invalid_sourcemap.html
+ doc_keyframeanimation.css
+ doc_keyframeanimation.html
+ doc_keyframeLineNumbers.html
+ doc_media_queries.html
+ doc_pseudoelement.html
+ doc_ruleLineNumbers.html
+ doc_sourcemaps.css
+ doc_sourcemaps.css.map
+ doc_sourcemaps.html
+ doc_sourcemaps.scss
+ doc_style_editor_link.css
+ doc_test_image.png
+ doc_urls_clickable.css
+ doc_urls_clickable.html
+ head.js
+ !/devtools/client/commandline/test/helpers.js
+ !/devtools/client/framework/test/shared-head.js
+ !/devtools/client/inspector/test/head.js
+ !/devtools/client/inspector/test/shared-head.js
+ !/devtools/client/shared/test/test-actor.js
+ !/devtools/client/shared/test/test-actor-registry.js
+
+[browser_rules_add-property-and-reselect.js]
+[browser_rules_add-property-cancel_01.js]
+[browser_rules_add-property-cancel_02.js]
+[browser_rules_add-property-cancel_03.js]
+[browser_rules_add-property-commented.js]
+[browser_rules_add-property_01.js]
+[browser_rules_add-property_02.js]
+[browser_rules_add-property-svg.js]
+[browser_rules_add-rule-and-property.js]
+[browser_rules_add-rule-button-state.js]
+[browser_rules_add-rule-edit-selector.js]
+[browser_rules_add-rule-iframes.js]
+[browser_rules_add-rule-namespace-elements.js]
+[browser_rules_add-rule-pseudo-class.js]
+[browser_rules_add-rule-then-property-edit-selector.js]
+[browser_rules_add-rule-with-menu.js]
+[browser_rules_add-rule.js]
+[browser_rules_authored.js]
+[browser_rules_authored_color.js]
+[browser_rules_authored_override.js]
+[browser_rules_blob_stylesheet.js]
+[browser_rules_colorpicker-and-image-tooltip_01.js]
+[browser_rules_colorpicker-and-image-tooltip_02.js]
+[browser_rules_colorpicker-appears-on-swatch-click.js]
+[browser_rules_colorpicker-commit-on-ENTER.js]
+[browser_rules_colorpicker-edit-gradient.js]
+[browser_rules_colorpicker-hides-on-tooltip.js]
+[browser_rules_colorpicker-multiple-changes.js]
+[browser_rules_colorpicker-release-outside-frame.js]
+[browser_rules_colorpicker-revert-on-ESC.js]
+[browser_rules_colorpicker-swatch-displayed.js]
+[browser_rules_colorUnit.js]
+[browser_rules_completion-existing-property_01.js]
+[browser_rules_completion-existing-property_02.js]
+[browser_rules_completion-new-property_01.js]
+[browser_rules_completion-new-property_02.js]
+[browser_rules_completion-new-property_03.js]
+[browser_rules_completion-new-property_04.js]
+[browser_rules_completion-new-property_multiline.js]
+[browser_rules_computed-lists_01.js]
+[browser_rules_computed-lists_02.js]
+[browser_rules_completion-popup-hidden-after-navigation.js]
+[browser_rules_content_01.js]
+[browser_rules_content_02.js]
+skip-if = e10s && debug # Bug 1250058 - Docshell leak on debug e10s
+[browser_rules_context-menu-show-mdn-docs-01.js]
+[browser_rules_context-menu-show-mdn-docs-02.js]
+[browser_rules_context-menu-show-mdn-docs-03.js]
+[browser_rules_copy_styles.js]
+subsuite = clipboard
+[browser_rules_cssom.js]
+[browser_rules_cubicbezier-appears-on-swatch-click.js]
+[browser_rules_cubicbezier-commit-on-ENTER.js]
+[browser_rules_cubicbezier-revert-on-ESC.js]
+[browser_rules_custom.js]
+[browser_rules_cycle-angle.js]
+[browser_rules_cycle-color.js]
+[browser_rules_edit-display-grid-property.js]
+[browser_rules_edit-property-cancel.js]
+[browser_rules_edit-property-click.js]
+[browser_rules_edit-property-commit.js]
+[browser_rules_edit-property-computed.js]
+[browser_rules_edit-property-increments.js]
+[browser_rules_edit-property-order.js]
+[browser_rules_edit-property-remove_01.js]
+[browser_rules_edit-property-remove_02.js]
+[browser_rules_edit-property-remove_03.js]
+[browser_rules_edit-property_01.js]
+[browser_rules_edit-property_02.js]
+[browser_rules_edit-property_03.js]
+[browser_rules_edit-property_04.js]
+[browser_rules_edit-property_05.js]
+[browser_rules_edit-property_06.js]
+[browser_rules_edit-property_07.js]
+[browser_rules_edit-property_08.js]
+[browser_rules_edit-property_09.js]
+[browser_rules_edit-selector-click.js]
+[browser_rules_edit-selector-click-on-scrollbar.js]
+skip-if = os == "mac" # Bug 1245996 : click on scrollbar not working on OSX
+[browser_rules_edit-selector-commit.js]
+[browser_rules_edit-selector_01.js]
+[browser_rules_edit-selector_02.js]
+[browser_rules_edit-selector_03.js]
+[browser_rules_edit-selector_04.js]
+[browser_rules_edit-selector_05.js]
+[browser_rules_edit-selector_06.js]
+[browser_rules_edit-selector_07.js]
+[browser_rules_edit-selector_08.js]
+[browser_rules_edit-selector_09.js]
+[browser_rules_edit-selector_10.js]
+[browser_rules_edit-selector_11.js]
+[browser_rules_edit-value-after-name_01.js]
+[browser_rules_edit-value-after-name_02.js]
+[browser_rules_edit-value-after-name_03.js]
+[browser_rules_edit-value-after-name_04.js]
+[browser_rules_editable-field-focus_01.js]
+[browser_rules_editable-field-focus_02.js]
+[browser_rules_eyedropper.js]
+[browser_rules_filtereditor-appears-on-swatch-click.js]
+[browser_rules_filtereditor-commit-on-ENTER.js]
+[browser_rules_filtereditor-revert-on-ESC.js]
+skip-if = (os == "win" && debug) # bug 963492: win.
+[browser_rules_grid-highlighter-on-navigate.js]
+[browser_rules_grid-highlighter-on-reload.js]
+[browser_rules_grid-toggle_01.js]
+[browser_rules_grid-toggle_02.js]
+[browser_rules_grid-toggle_03.js]
+[browser_rules_guessIndentation.js]
+[browser_rules_inherited-properties_01.js]
+[browser_rules_inherited-properties_02.js]
+[browser_rules_inherited-properties_03.js]
+[browser_rules_inline-source-map.js]
+[browser_rules_invalid.js]
+[browser_rules_invalid-source-map.js]
+[browser_rules_keybindings.js]
+[browser_rules_keyframes-rule_01.js]
+[browser_rules_keyframes-rule_02.js]
+[browser_rules_keyframeLineNumbers.js]
+[browser_rules_lineNumbers.js]
+[browser_rules_livepreview.js]
+[browser_rules_mark_overridden_01.js]
+[browser_rules_mark_overridden_02.js]
+[browser_rules_mark_overridden_03.js]
+[browser_rules_mark_overridden_04.js]
+[browser_rules_mark_overridden_05.js]
+[browser_rules_mark_overridden_06.js]
+[browser_rules_mark_overridden_07.js]
+[browser_rules_mathml-element.js]
+[browser_rules_media-queries.js]
+[browser_rules_multiple-properties-duplicates.js]
+[browser_rules_multiple-properties-priority.js]
+[browser_rules_multiple-properties-unfinished_01.js]
+[browser_rules_multiple-properties-unfinished_02.js]
+[browser_rules_multiple_properties_01.js]
+[browser_rules_multiple_properties_02.js]
+[browser_rules_original-source-link.js]
+[browser_rules_pseudo-element_01.js]
+[browser_rules_pseudo-element_02.js]
+[browser_rules_pseudo_lock_options.js]
+[browser_rules_refresh-no-flicker.js]
+[browser_rules_refresh-on-attribute-change_01.js]
+[browser_rules_refresh-on-attribute-change_02.js]
+[browser_rules_refresh-on-style-change.js]
+[browser_rules_search-filter-computed-list_01.js]
+[browser_rules_search-filter-computed-list_02.js]
+[browser_rules_search-filter-computed-list_03.js]
+[browser_rules_search-filter-computed-list_04.js]
+[browser_rules_search-filter-computed-list_expander.js]
+[browser_rules_search-filter-overridden-property.js]
+[browser_rules_search-filter_01.js]
+[browser_rules_search-filter_02.js]
+[browser_rules_search-filter_03.js]
+[browser_rules_search-filter_04.js]
+[browser_rules_search-filter_05.js]
+[browser_rules_search-filter_06.js]
+[browser_rules_search-filter_07.js]
+[browser_rules_search-filter_08.js]
+[browser_rules_search-filter_09.js]
+[browser_rules_search-filter_10.js]
+[browser_rules_search-filter_context-menu.js]
+subsuite = clipboard
+[browser_rules_search-filter_escape-keypress.js]
+[browser_rules_select-and-copy-styles.js]
+subsuite = clipboard
+[browser_rules_selector-highlighter-on-navigate.js]
+[browser_rules_selector-highlighter_01.js]
+[browser_rules_selector-highlighter_02.js]
+[browser_rules_selector-highlighter_03.js]
+[browser_rules_selector-highlighter_04.js]
+[browser_rules_selector_highlight.js]
+[browser_rules_strict-search-filter-computed-list_01.js]
+[browser_rules_strict-search-filter_01.js]
+[browser_rules_strict-search-filter_02.js]
+[browser_rules_strict-search-filter_03.js]
+[browser_rules_style-editor-link.js]
+[browser_rules_urls-clickable.js]
+[browser_rules_user-agent-styles.js]
+[browser_rules_user-agent-styles-uneditable.js]
+[browser_rules_user-property-reset.js]
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js b/devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js
new file mode 100644
index 000000000..492739abe
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js
@@ -0,0 +1,44 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that adding properties to rules work and reselecting the element still
+// show them.
+
+const TEST_URI = URL_ROOT + "doc_content_stylesheet.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#target", inspector);
+
+ info("Setting a font-weight property on all rules");
+ yield setPropertyOnAllRules(view);
+
+ info("Reselecting the element");
+ yield selectNode("body", inspector);
+ yield selectNode("#target", inspector);
+
+ checkPropertyOnAllRules(view);
+});
+
+function* setPropertyOnAllRules(view) {
+ // Wait for the properties to be properly created on the backend and for the
+ // view to be updated.
+ let onRefreshed = view.once("ruleview-refreshed");
+ for (let rule of view._elementStyle.rules) {
+ rule.editor.addProperty("font-weight", "bold", "", true);
+ }
+ yield onRefreshed;
+}
+
+function checkPropertyOnAllRules(view) {
+ for (let rule of view._elementStyle.rules) {
+ let lastRule = rule.textProps[rule.textProps.length - 1];
+
+ is(lastRule.name, "font-weight", "Last rule name is font-weight");
+ is(lastRule.value, "bold", "Last rule value is bold");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_01.js b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_01.js
new file mode 100644
index 000000000..78b3a4c91
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_01.js
@@ -0,0 +1,44 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests adding a new property and escapes the new empty property name editor.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ background-color: blue;
+ }
+ .testclass {
+ background-color: green;
+ }
+ </style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let elementRuleEditor = getRuleViewRuleEditor(view, 0);
+ let editor = yield focusNewRuleViewProperty(elementRuleEditor);
+ is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
+ "The new property editor got focused");
+
+ info("Escape the new property editor");
+ let onBlur = once(editor.input, "blur");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ yield onBlur;
+
+ info("Checking the state of cancelling a new property name editor");
+ is(elementRuleEditor.rule.textProps.length, 0,
+ "Should have cancelled creating a new text property.");
+ ok(!elementRuleEditor.propertyList.hasChildNodes(),
+ "Should not have any properties.");
+
+ is(view.styleDocument.activeElement, view.styleDocument.body,
+ "Correct element has focus");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_02.js b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_02.js
new file mode 100644
index 000000000..7f4d1564c
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_02.js
@@ -0,0 +1,34 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests adding a new property and escapes the new empty property value editor.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Test creating a new property and escaping");
+ yield addProperty(view, 1, "color", "red", "VK_ESCAPE", false);
+
+ is(view.styleDocument.activeElement, view.styleDocument.body,
+ "Correct element has focus");
+
+ let elementRuleEditor = getRuleViewRuleEditor(view, 1);
+ is(elementRuleEditor.rule.textProps.length, 1,
+ "Removed the new text property.");
+ is(elementRuleEditor.propertyList.children.length, 1,
+ "Removed the property editor.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_03.js b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_03.js
new file mode 100644
index 000000000..4f8b42009
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_03.js
@@ -0,0 +1,43 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests adding a new property and escapes the property name editor with a
+// value.
+
+const TEST_URI = `
+ <style type='text/css'>
+ div {
+ background-color: blue;
+ }
+ </style>
+ <div>Test node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ // Add a property to the element's style declaration, add some text,
+ // then press escape.
+
+ let elementRuleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = yield focusNewRuleViewProperty(elementRuleEditor);
+
+ is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
+ "Next focused editor should be the new property editor.");
+
+ EventUtils.sendString("background", view.styleWindow);
+
+ let onBlur = once(editor.input, "blur");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield onBlur;
+
+ is(elementRuleEditor.rule.textProps.length, 1,
+ "Should have canceled creating a new text property.");
+ is(view.styleDocument.activeElement, view.styleDocument.body,
+ "Correct element has focus");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-commented.js b/devtools/client/inspector/rules/test/browser_rules_add-property-commented.js
new file mode 100644
index 000000000..eacf5db5a
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-commented.js
@@ -0,0 +1,47 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that commented properties can be added and are disabled.
+
+const TEST_URI = "<div id='testid'></div>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testCreateNewSetOfCommentedAndUncommentedProperties(view);
+});
+
+function* testCreateNewSetOfCommentedAndUncommentedProperties(view) {
+ info("Test creating a new set of commented and uncommented properties");
+
+ info("Focusing a new property name in the rule-view");
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let editor = yield focusEditableField(view, ruleEditor.closeBrace);
+ is(inplaceEditor(ruleEditor.newPropSpan), editor,
+ "The new property editor has focus");
+
+ info(
+ "Entering a commented property/value pair into the property name editor");
+ let input = editor.input;
+ input.value = `color: blue;
+ /* background-color: yellow; */
+ width: 200px;
+ height: 100px;
+ /* padding-bottom: 1px; */`;
+
+ info("Pressing return to commit and focus the new value field");
+ let onModifications = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onModifications;
+
+ let textProps = ruleEditor.rule.textProps;
+ ok(textProps[0].enabled, "The 'color' property is enabled.");
+ ok(!textProps[1].enabled, "The 'background-color' property is disabled.");
+ ok(textProps[2].enabled, "The 'width' property is enabled.");
+ ok(textProps[3].enabled, "The 'height' property is enabled.");
+ ok(!textProps[4].enabled, "The 'padding-bottom' property is disabled.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property-svg.js b/devtools/client/inspector/rules/test/browser_rules_add-property-svg.js
new file mode 100644
index 000000000..a53421db3
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-svg.js
@@ -0,0 +1,22 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests editing SVG styles using the rules view.
+
+var TEST_URL = "chrome://global/skin/icons/warning.svg";
+var TEST_SELECTOR = "path";
+
+add_task(function* () {
+ yield addTab(TEST_URL);
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(TEST_SELECTOR, inspector);
+
+ info("Test creating a new property");
+ yield addProperty(view, 0, "fill", "red");
+
+ is((yield getComputedStyleProperty(TEST_SELECTOR, null, "fill")),
+ "rgb(255, 0, 0)", "The fill was changed to red");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property_01.js b/devtools/client/inspector/rules/test/browser_rules_add-property_01.js
new file mode 100644
index 000000000..1d7068d54
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property_01.js
@@ -0,0 +1,32 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test adding an invalid property.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ }
+ .testclass {
+ background-color: green;
+ }
+ </style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Test creating a new property");
+ let textProp = yield addProperty(view, 0, "background-color", "#XYZ");
+
+ is(textProp.value, "#XYZ", "Text prop should have been changed.");
+ is(textProp.overridden, true, "Property should be overridden");
+ is(textProp.editor.isValid(), false, "#XYZ should not be a valid entry");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-property_02.js b/devtools/client/inspector/rules/test/browser_rules_add-property_02.js
new file mode 100644
index 000000000..6f6bef0f7
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property_02.js
@@ -0,0 +1,65 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test adding a valid property to a CSS rule, and navigating through the fields
+// by pressing ENTER.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ color: blue;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Focus the new property name field");
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+ let input = editor.input;
+
+ is(inplaceEditor(ruleEditor.newPropSpan), editor,
+ "Next focused editor should be the new property editor.");
+ ok(input.selectionStart === 0 && input.selectionEnd === input.value.length,
+ "Editor contents are selected.");
+
+ // Try clicking on the editor's input again, shouldn't cause trouble
+ // (see bug 761665).
+ EventUtils.synthesizeMouse(input, 1, 1, {}, view.styleWindow);
+ input.select();
+
+ info("Entering the property name");
+ editor.input.value = "background-color";
+
+ info("Pressing RETURN and waiting for the value field focus");
+ let onNameAdded = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+
+ yield onNameAdded;
+
+ editor = inplaceEditor(view.styleDocument.activeElement);
+
+ is(ruleEditor.rule.textProps.length, 2,
+ "Should have created a new text property.");
+ is(ruleEditor.propertyList.children.length, 2,
+ "Should have created a property editor.");
+ let textProp = ruleEditor.rule.textProps[1];
+ is(editor, inplaceEditor(textProp.editor.valueSpan),
+ "Should be editing the value span now.");
+
+ info("Entering the property value");
+ let onValueAdded = view.once("ruleview-changed");
+ editor.input.value = "purple";
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onValueAdded;
+
+ is(textProp.value, "purple", "Text prop should have been changed.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js
new file mode 100644
index 000000000..1cf04a275
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js
@@ -0,0 +1,30 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests adding a new rule and a new property in this rule.
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8,<div id='testid'>Styled Node</div>");
+ let {inspector, view} = yield openRuleView();
+
+ info("Selecting the test node");
+ yield selectNode("#testid", inspector);
+
+ info("Adding a new rule for this node and blurring the new selector field");
+ yield addNewRuleAndDismissEditor(inspector, view, "#testid", 1);
+
+ info("Adding a new property for this rule");
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ let onRuleViewChanged = view.once("ruleview-changed");
+ ruleEditor.addProperty("font-weight", "bold", "", true);
+ yield onRuleViewChanged;
+
+ let textProps = ruleEditor.rule.textProps;
+ let prop = textProps[textProps.length - 1];
+ is(prop.name, "font-weight", "The last property name is font-weight");
+ is(prop.value, "bold", "The last property value is bold");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-button-state.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-button-state.js
new file mode 100644
index 000000000..1441213b3
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-button-state.js
@@ -0,0 +1,51 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests if the `Add rule` button disables itself properly for non-element nodes
+// and anonymous element.
+
+const TEST_URI = `
+ <style type="text/css">
+ #pseudo::before {
+ content: "before";
+ }
+ </style>
+ <div id="pseudo"></div>
+ <div id="testid">Test Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield testDisabledButton(inspector, view);
+});
+
+function* testDisabledButton(inspector, view) {
+ let node = "#testid";
+
+ info("Selecting a real element");
+ yield selectNode(node, inspector);
+ ok(!view.addRuleButton.disabled, "Add rule button should be enabled");
+
+ info("Select a null element");
+ yield view.selectElement(null);
+ ok(view.addRuleButton.disabled, "Add rule button should be disabled");
+
+ info("Selecting a real element");
+ yield selectNode(node, inspector);
+ ok(!view.addRuleButton.disabled, "Add rule button should be enabled");
+
+ info("Selecting a pseudo element");
+ let pseudo = yield getNodeFront("#pseudo", inspector);
+ let children = yield inspector.walker.children(pseudo);
+ let before = children.nodes[0];
+ yield selectNode(before, inspector);
+ ok(view.addRuleButton.disabled, "Add rule button should be disabled");
+
+ info("Selecting a real element");
+ yield selectNode(node, inspector);
+ ok(!view.addRuleButton.disabled, "Add rule button should be enabled");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-edit-selector.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-edit-selector.js
new file mode 100644
index 000000000..b59f317a5
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-edit-selector.js
@@ -0,0 +1,55 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the behaviour of adding a new rule to the rule view and editing
+// its selector.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ text-align: center;
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+ <span>This is a span</span>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ yield addNewRule(inspector, view);
+ yield testEditSelector(view, "span");
+
+ info("Selecting the modified element with the new rule");
+ yield selectNode("span", inspector);
+ yield checkModifiedElement(view, "span");
+});
+
+function* testEditSelector(view, name) {
+ info("Test editing existing selector field");
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = idRuleEditor.selectorText.ownerDocument.activeElement;
+
+ info("Entering a new selector name and committing");
+ editor.value = name;
+
+ info("Waiting for rule view to update");
+ let onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+}
+
+function* checkModifiedElement(view, name) {
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js
new file mode 100644
index 000000000..7b0ba7812
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js
@@ -0,0 +1,57 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests adding a rule on elements nested in iframes.
+
+const TEST_URI =
+ `<div>outer</div>
+ <iframe id="frame1" src="data:text/html;charset=utf-8,<div>inner1</div>">
+ </iframe>
+ <iframe id="frame2" src="data:text/html;charset=utf-8,<div>inner2</div>">
+ </iframe>`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+ yield addNewRuleAndDismissEditor(inspector, view, "div", 1);
+ yield addNewProperty(view, 1, "color", "red");
+
+ let innerFrameDiv1 = yield getNodeFrontInFrame("div", "#frame1", inspector);
+ yield selectNode(innerFrameDiv1, inspector);
+ yield addNewRuleAndDismissEditor(inspector, view, "div", 1);
+ yield addNewProperty(view, 1, "color", "blue");
+
+ let innerFrameDiv2 = yield getNodeFrontInFrame("div", "#frame2", inspector);
+ yield selectNode(innerFrameDiv2, inspector);
+ yield addNewRuleAndDismissEditor(inspector, view, "div", 1);
+ yield addNewProperty(view, 1, "color", "green");
+});
+
+/**
+ * Add a new property in the rule at the provided index in the rule view.
+ *
+ * @param {RuleView} view
+ * @param {Number} index
+ * The index of the rule in which we should add a new property.
+ * @param {String} name
+ * The name of the new property.
+ * @param {String} value
+ * The value of the new property.
+ */
+function* addNewProperty(view, index, name, value) {
+ let idRuleEditor = getRuleViewRuleEditor(view, index);
+ info(`Adding new property "${name}: ${value};"`);
+
+ let onRuleViewChanged = view.once("ruleview-changed");
+ idRuleEditor.addProperty(name, value, "", true);
+ yield onRuleViewChanged;
+
+ let textProps = idRuleEditor.rule.textProps;
+ let lastProperty = textProps[textProps.length - 1];
+ is(lastProperty.name, name, "Last property has the expected name");
+ is(lastProperty.value, value, "Last property has the expected value");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-namespace-elements.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-namespace-elements.js
new file mode 100644
index 000000000..98e34e69f
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-namespace-elements.js
@@ -0,0 +1,41 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the behaviour of adding a new rule using the add rule button
+// on namespaced elements.
+
+const XHTML = `
+ <!DOCTYPE html>
+ <html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <body>
+ <svg:svg width="100" height="100">
+ <svg:clipPath>
+ <svg:rect x="0" y="0" width="10" height="5"></svg:rect>
+ </svg:clipPath>
+ <svg:circle cx="0" cy="0" r="5"></svg:circle>
+ </svg:svg>
+ </body>
+ </html>
+`;
+const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML);
+
+const TEST_DATA = [
+ { node: "clipPath", expected: "clipPath" },
+ { node: "rect", expected: "rect" },
+ { node: "circle", expected: "circle" }
+];
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+
+ for (let data of TEST_DATA) {
+ let {node, expected} = data;
+ yield selectNode(node, inspector);
+ yield addNewRuleAndDismissEditor(inspector, view, expected, 1);
+ }
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-pseudo-class.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-pseudo-class.js
new file mode 100644
index 000000000..39f773c13
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-pseudo-class.js
@@ -0,0 +1,82 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests adding a rule with pseudo class locks on.
+
+const TEST_URI = "<p id='element'>Test element</p>";
+
+const EXPECTED_SELECTOR = "#element";
+const TEST_DATA = [
+ [],
+ [":hover"],
+ [":hover", ":active"],
+ [":hover", ":active", ":focus"],
+ [":active"],
+ [":active", ":focus"],
+ [":focus"]
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#element", inspector);
+
+ for (let data of TEST_DATA) {
+ yield runTestData(inspector, view, data);
+ }
+});
+
+function* runTestData(inspector, view, pseudoClasses) {
+ yield setPseudoLocks(inspector, view, pseudoClasses);
+
+ let expected = EXPECTED_SELECTOR + pseudoClasses.join("");
+ yield addNewRuleAndDismissEditor(inspector, view, expected, 1);
+
+ yield resetPseudoLocks(inspector, view);
+}
+
+function* setPseudoLocks(inspector, view, pseudoClasses) {
+ if (pseudoClasses.length == 0) {
+ return;
+ }
+
+ for (let pseudoClass of pseudoClasses) {
+ switch (pseudoClass) {
+ case ":hover":
+ view.hoverCheckbox.click();
+ yield inspector.once("rule-view-refreshed");
+ break;
+ case ":active":
+ view.activeCheckbox.click();
+ yield inspector.once("rule-view-refreshed");
+ break;
+ case ":focus":
+ view.focusCheckbox.click();
+ yield inspector.once("rule-view-refreshed");
+ break;
+ }
+ }
+}
+
+function* resetPseudoLocks(inspector, view) {
+ if (!view.hoverCheckbox.checked &&
+ !view.activeCheckbox.checked &&
+ !view.focusCheckbox.checked) {
+ return;
+ }
+ if (view.hoverCheckbox.checked) {
+ view.hoverCheckbox.click();
+ yield inspector.once("rule-view-refreshed");
+ }
+ if (view.activeCheckbox.checked) {
+ view.activeCheckbox.click();
+ yield inspector.once("rule-view-refreshed");
+ }
+ if (view.focusCheckbox.checked) {
+ view.focusCheckbox.click();
+ yield inspector.once("rule-view-refreshed");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js
new file mode 100644
index 000000000..294eb67e4
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js
@@ -0,0 +1,80 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the behaviour of adding a new rule to the rule view, adding a new
+// property and editing the selector.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ text-align: center;
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+ <span>This is a span</span>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ yield addNewRuleAndDismissEditor(inspector, view, "#testid", 1);
+
+ info("Adding a new property to the new rule");
+ yield testAddingProperty(view, 1);
+
+ info("Editing existing selector field");
+ yield testEditSelector(view, "span");
+
+ info("Selecting the modified element");
+ yield selectNode("span", inspector);
+
+ info("Check new rule and property exist in the modified element");
+ yield checkModifiedElement(view, "span", 1);
+});
+
+function* testAddingProperty(view, index) {
+ let ruleEditor = getRuleViewRuleEditor(view, index);
+ ruleEditor.addProperty("font-weight", "bold", "", true);
+ let textProps = ruleEditor.rule.textProps;
+ let lastRule = textProps[textProps.length - 1];
+ is(lastRule.name, "font-weight", "Last rule name is font-weight");
+ is(lastRule.value, "bold", "Last rule value is bold");
+}
+
+function* testEditSelector(view, name) {
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, idRuleEditor.selectorText);
+
+ is(inplaceEditor(idRuleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Entering a new selector name: " + name);
+ editor.input.value = name;
+
+ info("Waiting for rule view to update");
+ let onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
+}
+
+function* checkModifiedElement(view, name, index) {
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, index);
+ let textProps = idRuleEditor.rule.textProps;
+ let lastRule = textProps[textProps.length - 1];
+ is(lastRule.name, "font-weight", "Last rule name is font-weight");
+ is(lastRule.value, "bold", "Last rule value is bold");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-with-menu.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-with-menu.js
new file mode 100644
index 000000000..976fc9643
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-with-menu.js
@@ -0,0 +1,42 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the a new CSS rule can be added using the context menu.
+
+const TEST_URI = '<div id="testid">Test Node</div>';
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("#testid", inspector);
+ yield addNewRuleFromContextMenu(inspector, view);
+ yield testNewRule(view);
+});
+
+function* addNewRuleFromContextMenu(inspector, view) {
+ info("Waiting for context menu to be shown");
+
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, view.element);
+ let menuitemAddRule = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.addNewRule"));
+
+ ok(menuitemAddRule.visible, "Add rule is visible");
+
+ info("Adding the new rule and expecting a ruleview-changed event");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ menuitemAddRule.click();
+ yield onRuleViewChanged;
+}
+
+function* testNewRule(view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = ruleEditor.selectorText.ownerDocument.activeElement;
+ is(editor.value, "#testid", "Selector editor value is as expected");
+
+ info("Escaping from the selector field the change");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule.js b/devtools/client/inspector/rules/test/browser_rules_add-rule.js
new file mode 100644
index 000000000..296105c85
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule.js
@@ -0,0 +1,47 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests adding a new rule using the add rule button.
+
+const TEST_URI = `
+ <style type="text/css">
+ .testclass {
+ text-align: center;
+ }
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>
+ <span class="testclass2">This is a span</span>
+ <span class="class1 class2">Multiple classes</span>
+ <span class="class3 class4">Multiple classes</span>
+ <p>Empty<p>
+ <h1 class="asd@@@@a!!!!:::@asd">Invalid characters in class</h1>
+ <h2 id="asd@@@a!!2a">Invalid characters in id</h2>
+ <svg viewBox="0 0 10 10">
+ <circle cx="5" cy="5" r="5" fill="blue"></circle>
+ </svg>
+`;
+
+const TEST_DATA = [
+ { node: "#testid", expected: "#testid" },
+ { node: ".testclass2", expected: ".testclass2" },
+ { node: ".class1.class2", expected: ".class1.class2" },
+ { node: ".class3.class4", expected: ".class3.class4" },
+ { node: "p", expected: "p" },
+ { node: "h1", expected: ".asd\\@\\@\\@\\@a\\!\\!\\!\\!\\:\\:\\:\\@asd" },
+ { node: "h2", expected: "#asd\\@\\@\\@a\\!\\!2a" },
+ { node: "circle", expected: "circle" }
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ for (let data of TEST_DATA) {
+ let {node, expected} = data;
+ yield selectNode(node, inspector);
+ yield addNewRuleAndDismissEditor(inspector, view, expected, 1);
+ }
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_authored.js b/devtools/client/inspector/rules/test/browser_rules_authored.js
new file mode 100644
index 000000000..cb0dd1186
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_authored.js
@@ -0,0 +1,49 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for as-authored styles.
+
+function* createTestContent(style) {
+ let html = `<style type="text/css">
+ ${style}
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>`;
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(html));
+
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ return view;
+}
+
+add_task(function* () {
+ let view = yield createTestContent("#testid {" +
+ // Invalid property.
+ " something: random;" +
+ // Invalid value.
+ " color: orang;" +
+ // Override.
+ " background-color: blue;" +
+ " background-color: #f0c;" +
+ "} ");
+
+ let elementStyle = view._elementStyle;
+
+ let expected = [
+ {name: "something", overridden: true},
+ {name: "color", overridden: true},
+ {name: "background-color", overridden: true},
+ {name: "background-color", overridden: false}
+ ];
+
+ let rule = elementStyle.rules[1];
+
+ for (let i = 0; i < expected.length; ++i) {
+ let prop = rule.textProps[i];
+ is(prop.name, expected[i].name, "test name for prop " + i);
+ is(prop.overridden, expected[i].overridden,
+ "test overridden for prop " + i);
+ }
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_authored_color.js b/devtools/client/inspector/rules/test/browser_rules_authored_color.js
new file mode 100644
index 000000000..4c5cab206
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_authored_color.js
@@ -0,0 +1,67 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for as-authored color styles.
+
+/**
+ * Array of test color objects:
+ * {String} name: name of the used & expected color format.
+ * {String} id: id of the element that will be created to test this color.
+ * {String} color: initial value of the color property applied to the test element.
+ * {String} result: expected value of the color property after edition.
+ */
+const colors = [
+ {name: "hex", id: "test1", color: "#f0c", result: "#0f0"},
+ {name: "rgb", id: "test2", color: "rgb(0,128,250)", result: "rgb(0, 255, 0)"},
+ // Test case preservation.
+ {name: "hex", id: "test3", color: "#F0C", result: "#0F0"},
+];
+
+add_task(function* () {
+ Services.prefs.setCharPref("devtools.defaultColorUnit", "authored");
+
+ let html = "";
+ for (let {color, id} of colors) {
+ html += `<div id="${id}" style="color: ${color}">Styled Node</div>`;
+ }
+
+ let tab = yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(html));
+
+ let {inspector, view} = yield openRuleView();
+
+ for (let color of colors) {
+ let cPicker = view.tooltips.colorPicker;
+ let selector = "#" + color.id;
+ yield selectNode(selector, inspector);
+
+ let swatch = getRuleViewProperty(view, "element", "color").valueSpan
+ .querySelector(".ruleview-colorswatch");
+ let onColorPickerReady = cPicker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], {
+ selector,
+ name: "color",
+ value: "rgb(0, 255, 0)"
+ });
+
+ let spectrum = cPicker.spectrum;
+ let onHidden = cPicker.tooltip.once("hidden");
+ // Validating the color change ends up updating the rule view twice
+ let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
+ focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
+ yield onHidden;
+ yield onRuleViewChanged;
+
+ is(getRuleViewPropertyValue(view, "element", "color"), color.result,
+ "changing the color preserved the unit for " + color.name);
+ }
+
+ let target = TargetFactory.forTab(tab);
+ yield gDevTools.closeToolbox(target);
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_authored_override.js b/devtools/client/inspector/rules/test/browser_rules_authored_override.js
new file mode 100644
index 000000000..7305e5712
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_authored_override.js
@@ -0,0 +1,53 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for as-authored styles.
+
+function* createTestContent(style) {
+ let html = `<style type="text/css">
+ ${style}
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>`;
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(html));
+
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ return view;
+}
+
+add_task(function* () {
+ let gradientText1 = "(orange, blue);";
+ let gradientText2 = "(pink, teal);";
+
+ let view =
+ yield createTestContent("#testid {" +
+ " background-image: linear-gradient" +
+ gradientText1 +
+ " background-image: -ms-linear-gradient" +
+ gradientText2 +
+ " background-image: linear-gradient" +
+ gradientText2 +
+ "} ");
+
+ let elementStyle = view._elementStyle;
+ let rule = elementStyle.rules[1];
+
+ // Initially the last property should be active.
+ for (let i = 0; i < 3; ++i) {
+ let prop = rule.textProps[i];
+ is(prop.name, "background-image", "check the property name");
+ is(prop.overridden, i !== 2, "check overridden for " + i);
+ }
+
+ yield togglePropStatus(view, rule.textProps[2]);
+
+ // Now the first property should be active.
+ for (let i = 0; i < 3; ++i) {
+ let prop = rule.textProps[i];
+ is(prop.overridden || !prop.enabled, i !== 0,
+ "post-change check overridden for " + i);
+ }
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_blob_stylesheet.js b/devtools/client/inspector/rules/test/browser_rules_blob_stylesheet.js
new file mode 100644
index 000000000..adc8eb2ee
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_blob_stylesheet.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule-view content is correct for stylesheet generated
+// with createObjectURL(cssBlob)
+const TEST_URL = URL_ROOT + "doc_blob_stylesheet.html";
+
+add_task(function* () {
+ yield addTab(TEST_URL);
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("h1", inspector);
+ is(view.element.querySelectorAll("#noResults").length, 0,
+ "The no-results element is not displayed");
+
+ is(view.element.querySelectorAll(".ruleview-rule").length, 2,
+ "There are 2 displayed rules");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorUnit.js b/devtools/client/inspector/rules/test/browser_rules_colorUnit.js
new file mode 100644
index 000000000..138f68365
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorUnit.js
@@ -0,0 +1,65 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that color selection respects the user pref.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ color: blue;
+ }
+ </style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function* () {
+ let TESTS = [
+ {name: "hex", result: "#0f0"},
+ {name: "rgb", result: "rgb(0, 255, 0)"}
+ ];
+
+ for (let {name, result} of TESTS) {
+ info("starting test for " + name);
+ Services.prefs.setCharPref("devtools.defaultColorUnit", name);
+
+ let tab = yield addTab("data:text/html;charset=utf-8," +
+ encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("#testid", inspector);
+ yield basicTest(view, name, result);
+
+ let target = TargetFactory.forTab(tab);
+ yield gDevTools.closeToolbox(target);
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function* basicTest(view, name, result) {
+ let cPicker = view.tooltips.colorPicker;
+ let swatch = getRuleViewProperty(view, "#testid", "color").valueSpan
+ .querySelector(".ruleview-colorswatch");
+ let onColorPickerReady = cPicker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], {
+ selector: "#testid",
+ name: "color",
+ value: "rgb(0, 255, 0)"
+ });
+
+ let spectrum = cPicker.spectrum;
+ let onHidden = cPicker.tooltip.once("hidden");
+ // Validating the color change ends up updating the rule view twice
+ let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
+ focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
+ yield onHidden;
+ yield onRuleViewChanged;
+
+ is(getRuleViewPropertyValue(view, "#testid", "color"), result,
+ "changing the color used the " + name + " unit");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js
new file mode 100644
index 000000000..a8d2fd5f1
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js
@@ -0,0 +1,63 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that after a color change, the image preview tooltip in the same
+// property is displayed and positioned correctly.
+// See bug 979292
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ background: url("chrome://global/skin/icons/warning-64.png"), linear-gradient(white, #F06 400px);
+ }
+ </style>
+ Testing the color picker tooltip!
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+ let value = getRuleViewProperty(view, "body", "background").valueSpan;
+ let swatch = value.querySelectorAll(".ruleview-colorswatch")[0];
+ let url = value.querySelector(".theme-link");
+ yield testImageTooltipAfterColorChange(swatch, url, view);
+});
+
+function* testImageTooltipAfterColorChange(swatch, url, ruleView) {
+ info("First, verify that the image preview tooltip works");
+ let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip,
+ url);
+ ok(anchor, "The image preview tooltip is shown on the url span");
+ is(anchor, url, "The anchor returned by the showOnHover callback is correct");
+
+ info("Open the color picker tooltip and change the color");
+ let picker = ruleView.tooltips.colorPicker;
+ let onColorPickerReady = picker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ yield simulateColorPickerChange(ruleView, picker, [0, 0, 0, 1], {
+ selector: "body",
+ name: "background-image",
+ value: 'url("chrome://global/skin/icons/warning-64.png"), linear-gradient(rgb(0, 0, 0), rgb(255, 0, 102) 400px)'
+ });
+
+ let spectrum = picker.spectrum;
+ let onHidden = picker.tooltip.once("hidden");
+ let onModifications = ruleView.once("ruleview-changed");
+ focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
+ yield onHidden;
+ yield onModifications;
+
+ info("Verify again that the image preview tooltip works");
+ // After a color change, the property is re-populated, we need to get the new
+ // dom node
+ url = getRuleViewProperty(ruleView, "body", "background").valueSpan
+ .querySelector(".theme-link");
+ anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url);
+ ok(anchor, "The image preview tooltip is shown on the url span");
+ is(anchor, url, "The anchor returned by the showOnHover callback is correct");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js
new file mode 100644
index 000000000..743ad5180
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js
@@ -0,0 +1,66 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that after a color change, opening another tooltip, like the image
+// preview doesn't revert the color change in the rule view.
+// This used to happen when the activeSwatch wasn't reset when the colorpicker
+// would hide.
+// See bug 979292
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ background: red url("chrome://global/skin/icons/warning-64.png")
+ no-repeat center center;
+ }
+ </style>
+ Testing the color picker tooltip!
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+ yield testColorChangeIsntRevertedWhenOtherTooltipIsShown(view);
+});
+
+function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) {
+ let swatch = getRuleViewProperty(ruleView, "body", "background").valueSpan
+ .querySelector(".ruleview-colorswatch");
+
+ info("Open the color picker tooltip and change the color");
+ let picker = ruleView.tooltips.colorPicker;
+ let onColorPickerReady = picker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ yield simulateColorPickerChange(ruleView, picker, [0, 0, 0, 1], {
+ selector: "body",
+ name: "background-color",
+ value: "rgb(0, 0, 0)"
+ });
+
+ let spectrum = picker.spectrum;
+
+ let onModifications = waitForNEvents(ruleView, "ruleview-changed", 2);
+ let onHidden = picker.tooltip.once("hidden");
+ focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
+ yield onHidden;
+ yield onModifications;
+
+ info("Open the image preview tooltip");
+ let value = getRuleViewProperty(ruleView, "body", "background").valueSpan;
+ let url = value.querySelector(".theme-link");
+ let onShown = ruleView.tooltips.previewTooltip.once("shown");
+ let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url);
+ ruleView.tooltips.previewTooltip.show(anchor);
+ yield onShown;
+
+ info("Image tooltip is shown, verify that the swatch is still correct");
+ swatch = value.querySelector(".ruleview-colorswatch");
+ is(swatch.style.backgroundColor, "black",
+ "The swatch's color is correct");
+ is(swatch.nextSibling.textContent, "black", "The color name is correct");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js
new file mode 100644
index 000000000..383ffed6c
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js
@@ -0,0 +1,51 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that color pickers appear when clicking on color swatches.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ color: red;
+ background-color: #ededed;
+ background-image: url(chrome://global/skin/icons/warning-64.png);
+ border: 2em solid rgba(120, 120, 120, .5);
+ }
+ </style>
+ Testing the color picker tooltip!
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+
+ let propertiesToTest = ["color", "background-color", "border"];
+
+ for (let property of propertiesToTest) {
+ info("Testing that the colorpicker appears on swatch click");
+ let value = getRuleViewProperty(view, "body", property).valueSpan;
+ let swatch = value.querySelector(".ruleview-colorswatch");
+ yield testColorPickerAppearsOnColorSwatchClick(view, swatch);
+ }
+});
+
+function* testColorPickerAppearsOnColorSwatchClick(view, swatch) {
+ let cPicker = view.tooltips.colorPicker;
+ ok(cPicker, "The rule-view has the expected colorPicker property");
+
+ let cPickerPanel = cPicker.tooltip.panel;
+ ok(cPickerPanel, "The XUL panel for the color picker exists");
+
+ let onColorPickerReady = cPicker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ ok(true, "The color picker was shown on click of the color swatch");
+ ok(!inplaceEditor(swatch.parentNode),
+ "The inplace editor wasn't shown as a result of the color swatch click");
+
+ yield hideTooltipAndWaitForRuleViewChanged(cPicker, view);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-commit-on-ENTER.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-commit-on-ENTER.js
new file mode 100644
index 000000000..129e8f245
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-commit-on-ENTER.js
@@ -0,0 +1,61 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that a color change in the color picker is committed when ENTER is
+// pressed.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ border: 2em solid rgba(120, 120, 120, .5);
+ }
+ </style>
+ Testing the color picker tooltip!
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+
+ let swatch = getRuleViewProperty(view, "body", "border").valueSpan
+ .querySelector(".ruleview-colorswatch");
+ yield testPressingEnterCommitsChanges(swatch, view);
+});
+
+function* testPressingEnterCommitsChanges(swatch, ruleView) {
+ let cPicker = ruleView.tooltips.colorPicker;
+
+ let onColorPickerReady = cPicker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ yield simulateColorPickerChange(ruleView, cPicker, [0, 255, 0, .5], {
+ selector: "body",
+ name: "border-left-color",
+ value: "rgba(0, 255, 0, 0.5)"
+ });
+
+ is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)",
+ "The color swatch's background was updated");
+ is(getRuleViewProperty(ruleView, "body", "border").valueSpan.textContent,
+ "2em solid rgba(0, 255, 0, 0.5)",
+ "The text of the border css property was updated");
+
+ let onModified = ruleView.once("ruleview-changed");
+ let spectrum = cPicker.spectrum;
+ let onHidden = cPicker.tooltip.once("hidden");
+ focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
+ yield onHidden;
+ yield onModified;
+
+ is((yield getComputedStyleProperty("body", null, "border-left-color")),
+ "rgba(0, 255, 0, 0.5)", "The element's border was kept after RETURN");
+ is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)",
+ "The color swatch's background was kept after RETURN");
+ is(getRuleViewProperty(ruleView, "body", "border").valueSpan.textContent,
+ "2em solid rgba(0, 255, 0, 0.5)",
+ "The text of the border css property was kept after RETURN");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js
new file mode 100644
index 000000000..71ceb14c3
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js
@@ -0,0 +1,77 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that changing a color in a gradient css declaration using the tooltip
+// color picker works.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ background-image: linear-gradient(to left, #f06 25%, #333 95%, #000 100%);
+ }
+ </style>
+ Updating a gradient declaration with the color picker tooltip
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+
+ info("Testing that the colors in gradient properties are parsed correctly");
+ testColorParsing(view);
+
+ info("Testing that changing one of the colors of a gradient property works");
+ yield testPickingNewColor(view);
+});
+
+function testColorParsing(view) {
+ let ruleEl = getRuleViewProperty(view, "body", "background-image");
+ ok(ruleEl, "The background-image gradient declaration was found");
+
+ let swatchEls = ruleEl.valueSpan.querySelectorAll(".ruleview-colorswatch");
+ ok(swatchEls, "The color swatch elements were found");
+ is(swatchEls.length, 3, "There are 3 color swatches");
+
+ let colorEls = ruleEl.valueSpan.querySelectorAll(".ruleview-color");
+ ok(colorEls, "The color elements were found");
+ is(colorEls.length, 3, "There are 3 color values");
+
+ let colors = ["#f06", "#333", "#000"];
+ for (let i = 0; i < colors.length; i++) {
+ is(colorEls[i].textContent, colors[i], "The right color value was found");
+ }
+}
+
+function* testPickingNewColor(view) {
+ // Grab the first color swatch and color in the gradient
+ let ruleEl = getRuleViewProperty(view, "body", "background-image");
+ let swatchEl = ruleEl.valueSpan.querySelector(".ruleview-colorswatch");
+ let colorEl = ruleEl.valueSpan.querySelector(".ruleview-color");
+
+ info("Get the color picker tooltip and clicking on the swatch to show it");
+ let cPicker = view.tooltips.colorPicker;
+ let onColorPickerReady = cPicker.once("ready");
+ swatchEl.click();
+ yield onColorPickerReady;
+
+ let change = {
+ selector: "body",
+ name: "background-image",
+ value: "linear-gradient(to left, rgb(1, 1, 1) 25%, " +
+ "rgb(51, 51, 51) 95%, rgb(0, 0, 0) 100%)"
+ };
+ yield simulateColorPickerChange(view, cPicker, [1, 1, 1, 1], change);
+
+ is(swatchEl.style.backgroundColor, "rgb(1, 1, 1)",
+ "The color swatch's background was updated");
+ is(colorEl.textContent, "#010101", "The color text was updated");
+ is((yield getComputedStyleProperty("body", null, "background-image")),
+ "linear-gradient(to left, rgb(1, 1, 1) 25%, rgb(51, 51, 51) 95%, " +
+ "rgb(0, 0, 0) 100%)",
+ "The gradient has been updated correctly");
+
+ yield hideTooltipAndWaitForRuleViewChanged(cPicker, view);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-on-tooltip.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-on-tooltip.js
new file mode 100644
index 000000000..b50c63605
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-on-tooltip.js
@@ -0,0 +1,46 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the color picker tooltip hides when an image tooltip appears.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ color: red;
+ background-color: #ededed;
+ background-image: url(chrome://global/skin/icons/warning-64.png);
+ border: 2em solid rgba(120, 120, 120, .5);
+ }
+ </style>
+ Testing the color picker tooltip!
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+
+ let swatch = getRuleViewProperty(view, "body", "color").valueSpan
+ .querySelector(".ruleview-colorswatch");
+
+ let bgImageSpan = getRuleViewProperty(view, "body", "background-image").valueSpan;
+ let uriSpan = bgImageSpan.querySelector(".theme-link");
+
+ let colorPicker = view.tooltips.colorPicker;
+ info("Showing the color picker tooltip by clicking on the color swatch");
+ let onColorPickerReady = colorPicker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ info("Now showing the image preview tooltip to hide the color picker");
+ let onHidden = colorPicker.tooltip.once("hidden");
+ // Hiding the color picker refreshes the value.
+ let onRuleViewChanged = view.once("ruleview-changed");
+ yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
+ yield onHidden;
+ yield onRuleViewChanged;
+
+ ok(true, "The color picker closed when the image preview tooltip appeared");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js
new file mode 100644
index 000000000..06fab72d6
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js
@@ -0,0 +1,124 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the color in the colorpicker tooltip can be changed several times.
+// without causing error in various cases:
+// - simple single-color property (color)
+// - color and image property (background-image)
+// - overridden property
+// See bug 979292 and bug 980225
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ color: green;
+ background: red url("chrome://global/skin/icons/warning-64.png")
+ no-repeat center center;
+ }
+ p {
+ color: blue;
+ }
+ </style>
+ <p>Testing the color picker tooltip!</p>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield testSimpleMultipleColorChanges(inspector, view);
+ yield testComplexMultipleColorChanges(inspector, view);
+ yield testOverriddenMultipleColorChanges(inspector, view);
+});
+
+function* testSimpleMultipleColorChanges(inspector, ruleView) {
+ yield selectNode("p", inspector);
+
+ info("Getting the <p> tag's color property");
+ let swatch = getRuleViewProperty(ruleView, "p", "color").valueSpan
+ .querySelector(".ruleview-colorswatch");
+
+ info("Opening the color picker");
+ let picker = ruleView.tooltips.colorPicker;
+ let onColorPickerReady = picker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ info("Changing the color several times");
+ let colors = [
+ {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"},
+ {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
+ {rgba: [200, 200, 200, 1], computed: "rgb(200, 200, 200)"}
+ ];
+ for (let {rgba, computed} of colors) {
+ yield simulateColorPickerChange(ruleView, picker, rgba, {
+ selector: "p",
+ name: "color",
+ value: computed
+ });
+ }
+}
+
+function* testComplexMultipleColorChanges(inspector, ruleView) {
+ yield selectNode("body", inspector);
+
+ info("Getting the <body> tag's color property");
+ let swatch = getRuleViewProperty(ruleView, "body", "background").valueSpan
+ .querySelector(".ruleview-colorswatch");
+
+ info("Opening the color picker");
+ let picker = ruleView.tooltips.colorPicker;
+ let onColorPickerReady = picker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ info("Changing the color several times");
+ let colors = [
+ {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"},
+ {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
+ {rgba: [200, 200, 200, 1], computed: "rgb(200, 200, 200)"}
+ ];
+ for (let {rgba, computed} of colors) {
+ yield simulateColorPickerChange(ruleView, picker, rgba, {
+ selector: "body",
+ name: "background-color",
+ value: computed
+ });
+ }
+
+ info("Closing the color picker");
+ yield hideTooltipAndWaitForRuleViewChanged(picker, ruleView);
+}
+
+function* testOverriddenMultipleColorChanges(inspector, ruleView) {
+ yield selectNode("p", inspector);
+
+ info("Getting the <body> tag's color property");
+ let swatch = getRuleViewProperty(ruleView, "body", "color").valueSpan
+ .querySelector(".ruleview-colorswatch");
+
+ info("Opening the color picker");
+ let picker = ruleView.tooltips.colorPicker;
+ let onColorPickerReady = picker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ info("Changing the color several times");
+ let colors = [
+ {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"},
+ {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
+ {rgba: [200, 200, 200, 1], computed: "rgb(200, 200, 200)"}
+ ];
+ for (let {rgba, computed} of colors) {
+ yield simulateColorPickerChange(ruleView, picker, rgba, {
+ selector: "body",
+ name: "color",
+ value: computed
+ });
+ is((yield getComputedStyleProperty("p", null, "color")),
+ "rgb(200, 200, 200)", "The color of the P tag is still correct");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-release-outside-frame.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-release-outside-frame.js
new file mode 100644
index 000000000..ef6ca02b1
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-release-outside-frame.js
@@ -0,0 +1,67 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that color pickers stops following the pointer if the pointer is
+// released outside the tooltip frame (bug 1160720).
+
+const TEST_URI = "<body style='color: red'>Test page for bug 1160720";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+
+ let cSwatch = getRuleViewProperty(view, "element", "color").valueSpan
+ .querySelector(".ruleview-colorswatch");
+
+ let picker = yield openColorPickerForSwatch(cSwatch, view);
+ let spectrum = picker.spectrum;
+ let change = spectrum.once("changed");
+
+ info("Pressing mouse down over color picker.");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.synthesizeMouseAtCenter(spectrum.dragger, {
+ type: "mousedown",
+ }, spectrum.dragger.ownerDocument.defaultView);
+ yield onRuleViewChanged;
+
+ let value = yield change;
+ info(`Color changed to ${value} on mousedown.`);
+
+ // If the mousemove below fails to detect that the button is no longer pressed
+ // the spectrum will update and emit changed event synchronously after calling
+ // synthesizeMouse so this handler is executed before the test ends.
+ spectrum.once("changed", (event, newValue) => {
+ is(newValue, value, "Value changed on mousemove without a button pressed.");
+ });
+
+ // Releasing the button pressed by mousedown above on top of a different frame
+ // does not make sense in this test as EventUtils doesn't preserve the context
+ // i.e. the buttons that were pressed down between events.
+
+ info("Moving mouse over color picker without any buttons pressed.");
+
+ EventUtils.synthesizeMouse(spectrum.dragger, 10, 10, {
+ // -1 = no buttons are pressed down
+ button: -1,
+ type: "mousemove",
+ }, spectrum.dragger.ownerDocument.defaultView);
+});
+
+function* openColorPickerForSwatch(swatch, view) {
+ let cPicker = view.tooltips.colorPicker;
+ ok(cPicker, "The rule-view has the expected colorPicker property");
+
+ let cPickerPanel = cPicker.tooltip.panel;
+ ok(cPickerPanel, "The XUL panel for the color picker exists");
+
+ let onColorPickerReady = cPicker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ ok(true, "The color picker was shown on click of the color swatch");
+
+ return cPicker;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js
new file mode 100644
index 000000000..e244d429c
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js
@@ -0,0 +1,109 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that a color change in the color picker is reverted when ESC is
+// pressed.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ background-color: #EDEDED;
+ }
+ </style>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+ yield testPressingEscapeRevertsChanges(view);
+ yield testPressingEscapeRevertsChangesAndDisables(view);
+});
+
+function* testPressingEscapeRevertsChanges(view) {
+ let {swatch, propEditor, cPicker} = yield openColorPickerAndSelectColor(view,
+ 1, 0, [0, 0, 0, 1], {
+ selector: "body",
+ name: "background-color",
+ value: "rgb(0, 0, 0)"
+ });
+
+ is(swatch.style.backgroundColor, "rgb(0, 0, 0)",
+ "The color swatch's background was updated");
+ is(propEditor.valueSpan.textContent, "#000",
+ "The text of the background-color css property was updated");
+
+ let spectrum = cPicker.spectrum;
+
+ info("Pressing ESCAPE to close the tooltip");
+ let onHidden = cPicker.tooltip.once("hidden");
+ let onModifications = view.once("ruleview-changed");
+ EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView);
+ yield onHidden;
+ yield onModifications;
+
+ yield waitForComputedStyleProperty("body", null, "background-color",
+ "rgb(237, 237, 237)");
+ is(propEditor.valueSpan.textContent, "#EDEDED",
+ "Got expected property value.");
+}
+
+function* testPressingEscapeRevertsChangesAndDisables(view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Disabling background-color property");
+ let textProp = ruleEditor.rule.textProps[0];
+ yield togglePropStatus(view, textProp);
+
+ ok(textProp.editor.element.classList.contains("ruleview-overridden"),
+ "property is overridden.");
+ is(textProp.editor.enable.style.visibility, "visible",
+ "property enable checkbox is visible.");
+ ok(!textProp.editor.enable.getAttribute("checked"),
+ "property enable checkbox is not checked.");
+ ok(!textProp.editor.prop.enabled,
+ "background-color property is disabled.");
+ let newValue = yield getRulePropertyValue("background-color");
+ is(newValue, "", "background-color should have been unset.");
+
+ let {cPicker} = yield openColorPickerAndSelectColor(view,
+ 1, 0, [0, 0, 0, 1]);
+
+ ok(!textProp.editor.element.classList.contains("ruleview-overridden"),
+ "property overridden is not displayed.");
+ is(textProp.editor.enable.style.visibility, "hidden",
+ "property enable checkbox is hidden.");
+
+ let spectrum = cPicker.spectrum;
+
+ info("Pressing ESCAPE to close the tooltip");
+ let onHidden = cPicker.tooltip.once("hidden");
+ let onModifications = view.once("ruleview-changed");
+ EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView);
+ yield onHidden;
+ yield onModifications;
+
+ ok(textProp.editor.element.classList.contains("ruleview-overridden"),
+ "property is overridden.");
+ is(textProp.editor.enable.style.visibility, "visible",
+ "property enable checkbox is visible.");
+ ok(!textProp.editor.enable.getAttribute("checked"),
+ "property enable checkbox is not checked.");
+ ok(!textProp.editor.prop.enabled,
+ "background-color property is disabled.");
+ newValue = yield getRulePropertyValue("background-color");
+ is(newValue, "", "background-color should have been unset.");
+ is(textProp.editor.valueSpan.textContent, "#EDEDED",
+ "Got expected property value.");
+}
+
+function* getRulePropertyValue(name) {
+ let propValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: name
+ });
+ return propValue;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_colorpicker-swatch-displayed.js b/devtools/client/inspector/rules/test/browser_rules_colorpicker-swatch-displayed.js
new file mode 100644
index 000000000..b06ff37df
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-swatch-displayed.js
@@ -0,0 +1,73 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that color swatches are displayed next to colors in the rule-view.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ color: red;
+ background-color: #ededed;
+ background-image: url(chrome://global/skin/icons/warning-64.png);
+ border: 2em solid rgba(120, 120, 120, .5);
+ }
+ * {
+ color: blue;
+ background: linear-gradient(
+ to right,
+ #f00,
+ #f008,
+ #00ff00,
+ #00ff0080,
+ rgb(31,170,217),
+ rgba(31,170,217,.5),
+ hsl(5, 5%, 5%),
+ hsla(5, 5%, 5%, 0.25),
+ #F00,
+ #F008,
+ #00FF00,
+ #00FF0080,
+ RGB(31,170,217),
+ RGBA(31,170,217,.5),
+ HSL(5, 5%, 5%),
+ HSLA(5, 5%, 5%, 0.25));
+ box-shadow: inset 0 0 2px 20px red, inset 0 0 2px 40px blue;
+ }
+ </style>
+ Testing the color picker tooltip!
+`;
+
+// Tests that properties in the rule-view contain color swatches.
+// Each entry in the test array should contain:
+// {
+// selector: the rule-view selector to look for the property in
+// propertyName: the property to test
+// nb: the number of color swatches this property should have
+// }
+const TESTS = [
+ {selector: "body", propertyName: "color", nb: 1},
+ {selector: "body", propertyName: "background-color", nb: 1},
+ {selector: "body", propertyName: "border", nb: 1},
+ {selector: "*", propertyName: "color", nb: 1},
+ {selector: "*", propertyName: "background", nb: 16},
+ {selector: "*", propertyName: "box-shadow", nb: 2},
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+
+ for (let {selector, propertyName, nb} of TESTS) {
+ info("Looking for color swatches in property " + propertyName +
+ " in selector " + selector);
+
+ let prop = getRuleViewProperty(view, selector, propertyName).valueSpan;
+ let swatches = prop.querySelectorAll(".ruleview-colorswatch");
+
+ ok(swatches.length, "Swatches found in the property");
+ is(swatches.length, nb, "Correct number of swatches found in the property");
+ }
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
new file mode 100644
index 000000000..566bae259
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
@@ -0,0 +1,139 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that CSS property names are autocompleted and cycled correctly when
+// editing an existing property in the rule view.
+
+// format :
+// [
+// what key to press,
+// expected input box value after keypress,
+// is the popup open,
+// is a suggestion selected in the popup,
+// ]
+
+const OPEN = true, SELECTED = true;
+var testData = [
+ ["VK_RIGHT", "font", !OPEN, !SELECTED],
+ ["-", "font-size", OPEN, SELECTED],
+ ["f", "font-family", OPEN, SELECTED],
+ ["VK_BACK_SPACE", "font-f", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "font-", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "font", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "fon", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "fo", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "f", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "", !OPEN, !SELECTED],
+ ["d", "display", OPEN, SELECTED],
+ ["VK_DOWN", "dominant-baseline", OPEN, SELECTED],
+ ["VK_DOWN", "direction", OPEN, SELECTED],
+ ["VK_DOWN", "display", OPEN, SELECTED],
+ ["VK_UP", "direction", OPEN, SELECTED],
+ ["VK_UP", "dominant-baseline", OPEN, SELECTED],
+ ["VK_UP", "display", OPEN, SELECTED],
+ ["VK_BACK_SPACE", "d", !OPEN, !SELECTED],
+ ["i", "display", OPEN, SELECTED],
+ ["s", "display", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "dis", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "di", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "d", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "", !OPEN, !SELECTED],
+ ["VK_HOME", "", !OPEN, !SELECTED],
+ ["VK_END", "", !OPEN, !SELECTED],
+ ["VK_PAGE_UP", "", !OPEN, !SELECTED],
+ ["VK_PAGE_DOWN", "", !OPEN, !SELECTED],
+ ["d", "display", OPEN, SELECTED],
+ ["VK_HOME", "display", !OPEN, !SELECTED],
+ ["VK_END", "display", !OPEN, !SELECTED],
+ // Press right key to ensure caret move to end of the input on Mac OS since
+ // Mac OS doesn't move caret after pressing HOME / END.
+ ["VK_RIGHT", "display", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "displa", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "displ", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "disp", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "dis", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "di", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "d", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "", !OPEN, !SELECTED],
+ ["f", "font-size", OPEN, SELECTED],
+ ["i", "filter", OPEN, SELECTED],
+ ["VK_LEFT", "filter", !OPEN, !SELECTED],
+ ["VK_LEFT", "filter", !OPEN, !SELECTED],
+ ["i", "fiilter", !OPEN, !SELECTED],
+ ["VK_ESCAPE", null, !OPEN, !SELECTED],
+];
+
+const TEST_URI = "<h1 style='font: 24px serif'>Header</h1>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {toolbox, inspector, view, testActor} = yield openRuleView();
+
+ info("Test autocompletion after 1st page load");
+ yield runAutocompletionTest(toolbox, inspector, view);
+
+ info("Test autocompletion after page navigation");
+ yield reloadPage(inspector, testActor);
+ yield runAutocompletionTest(toolbox, inspector, view);
+});
+
+function* runAutocompletionTest(toolbox, inspector, view) {
+ info("Selecting the test node");
+ yield selectNode("h1", inspector);
+
+ info("Focusing the css property editable field");
+ let propertyName = view.styleDocument.querySelectorAll(".ruleview-propertyname")[0];
+ let editor = yield focusEditableField(view, propertyName);
+
+ info("Starting to test for css property completion");
+ for (let i = 0; i < testData.length; i++) {
+ yield testCompletion(testData[i], editor, view);
+ }
+}
+
+function* testCompletion([key, completion, open, selected],
+ editor, view) {
+ info("Pressing key " + key);
+ info("Expecting " + completion);
+ info("Is popup opened: " + open);
+ info("Is item selected: " + selected);
+
+ // Listening for the right event that will tell us when the key has been
+ // entered and processed.
+ let onSuggest;
+ if (/(left|right|back_space|escape|home|end|page_up|page_down)/ig.test(key)) {
+ info("Adding event listener for " +
+ "left|right|back_space|escape|home|end|page_up|page_down keys");
+ onSuggest = once(editor.input, "keypress");
+ } else {
+ info("Waiting for after-suggest event on the editor");
+ onSuggest = editor.once("after-suggest");
+ }
+
+ // Also listening for popup opened/closed events if needed.
+ let popupEvent = open ? "popup-opened" : "popup-closed";
+ let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null;
+
+ info("Synthesizing key " + key);
+ EventUtils.synthesizeKey(key, {}, view.styleWindow);
+
+ // Flush the throttle for the preview text.
+ view.throttle.flush();
+
+ yield onSuggest;
+ yield onPopupEvent;
+
+ info("Checking the state");
+ if (completion !== null) {
+ is(editor.input.value, completion, "Correct value is autocompleted");
+ }
+ if (!open) {
+ ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
+ } else {
+ ok(editor.popup.isOpen, "Popup is open");
+ is(editor.popup.selectedIndex !== -1, selected, "An item is selected");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
new file mode 100644
index 000000000..fde8f5d12
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
@@ -0,0 +1,123 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that CSS property names and values are autocompleted and cycled
+// correctly when editing existing properties in the rule view.
+
+// format :
+// [
+// what key to press,
+// modifers,
+// expected input box value after keypress,
+// is the popup open,
+// is a suggestion selected in the popup,
+// expect ruleview-changed,
+// ]
+
+const OPEN = true, SELECTED = true, CHANGE = true;
+var testData = [
+ ["b", {}, "beige", OPEN, SELECTED, CHANGE],
+ ["l", {}, "black", OPEN, SELECTED, CHANGE],
+ ["VK_DOWN", {}, "blanchedalmond", OPEN, SELECTED, CHANGE],
+ ["VK_DOWN", {}, "blue", OPEN, SELECTED, CHANGE],
+ ["VK_RIGHT", {}, "blue", !OPEN, !SELECTED, !CHANGE],
+ [" ", {}, "blue aliceblue", OPEN, SELECTED, CHANGE],
+ ["!", {}, "blue !important", !OPEN, !SELECTED, CHANGE],
+ ["VK_BACK_SPACE", {}, "blue !", !OPEN, !SELECTED, CHANGE],
+ ["VK_BACK_SPACE", {}, "blue ", !OPEN, !SELECTED, CHANGE],
+ ["VK_BACK_SPACE", {}, "blue", !OPEN, !SELECTED, CHANGE],
+ ["VK_TAB", {shiftKey: true}, "color", !OPEN, !SELECTED, CHANGE],
+ ["VK_BACK_SPACE", {}, "", !OPEN, !SELECTED, !CHANGE],
+ ["d", {}, "display", OPEN, SELECTED, !CHANGE],
+ ["VK_TAB", {}, "blue", !OPEN, !SELECTED, CHANGE],
+ ["n", {}, "none", !OPEN, !SELECTED, CHANGE],
+ ["VK_RETURN", {}, null, !OPEN, !SELECTED, CHANGE]
+];
+
+const TEST_URI = "<h1 style='color: red'>Header</h1>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {toolbox, inspector, view, testActor} = yield openRuleView();
+
+ info("Test autocompletion after 1st page load");
+ yield runAutocompletionTest(toolbox, inspector, view);
+
+ info("Test autocompletion after page navigation");
+ yield reloadPage(inspector, testActor);
+ yield runAutocompletionTest(toolbox, inspector, view);
+});
+
+function* runAutocompletionTest(toolbox, inspector, view) {
+ info("Selecting the test node");
+ yield selectNode("h1", inspector);
+
+ let rule = getRuleViewRuleEditor(view, 0).rule;
+ let prop = rule.textProps[0];
+
+ info("Focusing the css property editable value");
+ let editor = yield focusEditableField(view, prop.editor.valueSpan);
+
+ info("Starting to test for css property completion");
+ for (let i = 0; i < testData.length; i++) {
+ // Re-define the editor at each iteration, because the focus may have moved
+ // from property to value and back
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ yield testCompletion(testData[i], editor, view);
+ }
+}
+
+function* testCompletion([key, modifiers, completion, open, selected, change],
+ editor, view) {
+ info("Pressing key " + key);
+ info("Expecting " + completion);
+ info("Is popup opened: " + open);
+ info("Is item selected: " + selected);
+
+ let onDone;
+ if (change) {
+ // If the key triggers a ruleview-changed, wait for that event, it will
+ // always be the last to be triggered and tells us when the preview has
+ // been done.
+ onDone = view.once("ruleview-changed");
+ } else {
+ // Otherwise, expect an after-suggest event (except if the popup gets
+ // closed).
+ onDone = key !== "VK_RIGHT" && key !== "VK_BACK_SPACE"
+ ? editor.once("after-suggest")
+ : null;
+ }
+
+ info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers));
+
+ // Also listening for popup opened/closed events if needed.
+ let popupEvent = open ? "popup-opened" : "popup-closed";
+ let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null;
+
+ EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
+
+ // Flush the throttle for the preview text.
+ view.throttle.flush();
+
+ yield onDone;
+ yield onPopupEvent;
+
+ // The key might have been a TAB or shift-TAB, in which case the editor will
+ // be a new one
+ editor = inplaceEditor(view.styleDocument.activeElement);
+
+ info("Checking the state");
+ if (completion !== null) {
+ is(editor.input.value, completion, "Correct value is autocompleted");
+ }
+
+ if (!open) {
+ ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
+ } else {
+ ok(editor.popup.isOpen, "Popup is open");
+ is(editor.popup.selectedIndex !== -1, selected, "An item is selected");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js
new file mode 100644
index 000000000..86ff9ca03
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js
@@ -0,0 +1,102 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that CSS property names are autocompleted and cycled correctly when
+// creating a new property in the rule view.
+
+// format :
+// [
+// what key to press,
+// expected input box value after keypress,
+// is the popup open,
+// is a suggestion selected in the popup,
+// ]
+const OPEN = true, SELECTED = true;
+var testData = [
+ ["d", "display", OPEN, SELECTED],
+ ["VK_DOWN", "dominant-baseline", OPEN, SELECTED],
+ ["VK_DOWN", "direction", OPEN, SELECTED],
+ ["VK_DOWN", "display", OPEN, SELECTED],
+ ["VK_UP", "direction", OPEN, SELECTED],
+ ["VK_UP", "dominant-baseline", OPEN, SELECTED],
+ ["VK_UP", "display", OPEN, SELECTED],
+ ["VK_BACK_SPACE", "d", !OPEN, !SELECTED],
+ ["i", "display", OPEN, SELECTED],
+ ["s", "display", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "dis", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "di", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "d", !OPEN, !SELECTED],
+ ["VK_BACK_SPACE", "", !OPEN, !SELECTED],
+ ["f", "font-size", OPEN, SELECTED],
+ ["i", "filter", OPEN, SELECTED],
+ ["VK_ESCAPE", null, !OPEN, !SELECTED],
+];
+
+const TEST_URI = "<h1 style='border: 1px solid red'>Header</h1>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {toolbox, inspector, view, testActor} = yield openRuleView();
+
+ info("Test autocompletion after 1st page load");
+ yield runAutocompletionTest(toolbox, inspector, view);
+
+ info("Test autocompletion after page navigation");
+ yield reloadPage(inspector, testActor);
+ yield runAutocompletionTest(toolbox, inspector, view);
+});
+
+function* runAutocompletionTest(toolbox, inspector, view) {
+ info("Selecting the test node");
+ yield selectNode("h1", inspector);
+
+ info("Focusing the css property editable field");
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+
+ info("Starting to test for css property completion");
+ for (let i = 0; i < testData.length; i++) {
+ yield testCompletion(testData[i], editor, view);
+ }
+}
+
+function* testCompletion([key, completion, open, isSelected], editor, view) {
+ info("Pressing key " + key);
+ info("Expecting " + completion);
+ info("Is popup opened: " + open);
+ info("Is item selected: " + isSelected);
+
+ let onSuggest;
+
+ if (/(right|back_space|escape)/ig.test(key)) {
+ info("Adding event listener for right|back_space|escape keys");
+ onSuggest = once(editor.input, "keypress");
+ } else {
+ info("Waiting for after-suggest event on the editor");
+ onSuggest = editor.once("after-suggest");
+ }
+
+ // Also listening for popup opened/closed events if needed.
+ let popupEvent = open ? "popup-opened" : "popup-closed";
+ let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null;
+
+ info("Synthesizing key " + key);
+ EventUtils.synthesizeKey(key, {}, view.styleWindow);
+
+ yield onSuggest;
+ yield onPopupEvent;
+
+ info("Checking the state");
+ if (completion !== null) {
+ is(editor.input.value, completion, "Correct value is autocompleted");
+ }
+ if (!open) {
+ ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
+ } else {
+ ok(editor.popup.isOpen, "Popup is open");
+ is(editor.popup.selectedIndex !== -1, isSelected, "An item is selected");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
new file mode 100644
index 000000000..d89e5129d
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
@@ -0,0 +1,129 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that CSS property names and values are autocompleted and cycled
+// correctly when editing new properties in the rule view.
+
+// format :
+// [
+// what key to press,
+// modifers,
+// expected input box value after keypress,
+// is the popup open,
+// is a suggestion selected in the popup,
+// expect ruleview-changed,
+// ]
+
+const OPEN = true, SELECTED = true, CHANGE = true;
+const testData = [
+ ["d", {}, "display", OPEN, SELECTED, !CHANGE],
+ ["VK_TAB", {}, "", OPEN, !SELECTED, CHANGE],
+ ["VK_DOWN", {}, "block", OPEN, SELECTED, CHANGE],
+ ["n", {}, "none", !OPEN, !SELECTED, CHANGE],
+ ["VK_TAB", {shiftKey: true}, "display", !OPEN, !SELECTED, CHANGE],
+ ["VK_BACK_SPACE", {}, "", !OPEN, !SELECTED, !CHANGE],
+ ["o", {}, "overflow", OPEN, SELECTED, !CHANGE],
+ ["u", {}, "outline", OPEN, SELECTED, !CHANGE],
+ ["VK_DOWN", {}, "outline-color", OPEN, SELECTED, !CHANGE],
+ ["VK_TAB", {}, "none", !OPEN, !SELECTED, CHANGE],
+ ["r", {}, "rebeccapurple", OPEN, SELECTED, CHANGE],
+ ["VK_DOWN", {}, "red", OPEN, SELECTED, CHANGE],
+ ["VK_DOWN", {}, "rgb", OPEN, SELECTED, CHANGE],
+ ["VK_DOWN", {}, "rgba", OPEN, SELECTED, CHANGE],
+ ["VK_DOWN", {}, "rosybrown", OPEN, SELECTED, CHANGE],
+ ["VK_DOWN", {}, "royalblue", OPEN, SELECTED, CHANGE],
+ ["VK_RIGHT", {}, "royalblue", !OPEN, !SELECTED, !CHANGE],
+ [" ", {}, "royalblue aliceblue", OPEN, SELECTED, CHANGE],
+ ["!", {}, "royalblue !important", !OPEN, !SELECTED, CHANGE],
+ ["VK_ESCAPE", {}, null, !OPEN, !SELECTED, CHANGE]
+];
+
+const TEST_URI = `
+ <style type="text/css">
+ h1 {
+ border: 1px solid red;
+ }
+ </style>
+ <h1>Test element</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {toolbox, inspector, view, testActor} = yield openRuleView();
+
+ info("Test autocompletion after 1st page load");
+ yield runAutocompletionTest(toolbox, inspector, view);
+
+ info("Test autocompletion after page navigation");
+ yield reloadPage(inspector, testActor);
+ yield runAutocompletionTest(toolbox, inspector, view);
+});
+
+function* runAutocompletionTest(toolbox, inspector, view) {
+ info("Selecting the test node");
+ yield selectNode("h1", inspector);
+
+ info("Focusing a new css property editable property");
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+
+ info("Starting to test for css property completion");
+ for (let i = 0; i < testData.length; i++) {
+ // Re-define the editor at each iteration, because the focus may have moved
+ // from property to value and back
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ yield testCompletion(testData[i], editor, view);
+ }
+}
+
+function* testCompletion([key, modifiers, completion, open, selected, change],
+ editor, view) {
+ info("Pressing key " + key);
+ info("Expecting " + completion);
+ info("Is popup opened: " + open);
+ info("Is item selected: " + selected);
+
+ let onDone;
+ if (change) {
+ // If the key triggers a ruleview-changed, wait for that event, it will
+ // always be the last to be triggered and tells us when the preview has
+ // been done.
+ onDone = view.once("ruleview-changed");
+ } else {
+ // Otherwise, expect an after-suggest event (except if the popup gets
+ // closed).
+ onDone = key !== "VK_RIGHT" && key !== "VK_BACK_SPACE"
+ ? editor.once("after-suggest")
+ : null;
+ }
+
+ // Also listening for popup opened/closed events if needed.
+ let popupEvent = open ? "popup-opened" : "popup-closed";
+ let onPopupEvent = editor.popup.isOpen !== open ? once(editor.popup, popupEvent) : null;
+
+ info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers));
+ EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
+
+ // Flush the throttle for the preview text.
+ view.throttle.flush();
+
+ yield onDone;
+ yield onPopupEvent;
+
+ info("Checking the state");
+ if (completion !== null) {
+ // The key might have been a TAB or shift-TAB, in which case the editor will
+ // be a new one
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ is(editor.input.value, completion, "Correct value is autocompleted");
+ }
+ if (!open) {
+ ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
+ } else {
+ ok(editor.popup.isOpen, "Popup is open");
+ is(editor.popup.selectedIndex !== -1, selected, "An item is selected");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_03.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_03.js
new file mode 100644
index 000000000..a5072429c
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_03.js
@@ -0,0 +1,47 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Regression test for a case where completing gave the wrong answer.
+// See bug 1179318.
+
+const TEST_URI = "<h1 style='color: red'>Header</h1>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {toolbox, inspector, view} = yield openRuleView();
+
+ info("Test autocompletion for background-color");
+ yield runAutocompletionTest(toolbox, inspector, view);
+});
+
+function* runAutocompletionTest(toolbox, inspector, view) {
+ info("Selecting the test node");
+ yield selectNode("h1", inspector);
+
+ info("Focusing the new property editable field");
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+
+ info("Sending \"background\" to the editable field");
+ for (let key of "background") {
+ let onSuggest = editor.once("after-suggest");
+ EventUtils.synthesizeKey(key, {}, view.styleWindow);
+ yield onSuggest;
+ }
+
+ const itemIndex = 4;
+
+ let bgcItem = editor.popup.getItemAtIndex(itemIndex);
+ is(bgcItem.label, "background-color",
+ "check the expected completion element");
+
+ editor.popup.selectedIndex = itemIndex;
+
+ let node = editor.popup._list.childNodes[itemIndex];
+ EventUtils.synthesizeMouseAtCenter(node, {}, editor.popup._window);
+
+ is(editor.input.value, "background-color", "Correct value is autocompleted");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js
new file mode 100644
index 000000000..e19794e1b
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js
@@ -0,0 +1,73 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a new property editor supports the following flow:
+// - type first character of property name
+// - select an autocomplete suggestion !!with a mouse click!!
+// - press RETURN to move to the property value
+// - blur the input to commit
+
+const TEST_URI = "<style>.title {color: red;}</style>" +
+ "<h1 class=title>Header</h1>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let { inspector, view} = yield openRuleView();
+
+ info("Selecting the test node");
+ yield selectNode("h1", inspector);
+
+ info("Focusing the new property editable field");
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+
+ info("Sending \"background\" to the editable field.");
+ for (let key of "background") {
+ let onSuggest = editor.once("after-suggest");
+ EventUtils.synthesizeKey(key, {}, view.styleWindow);
+ yield onSuggest;
+ }
+
+ const itemIndex = 4;
+ let bgcItem = editor.popup.getItemAtIndex(itemIndex);
+ is(bgcItem.label, "background-color",
+ "Check the expected completion element is background-color.");
+ editor.popup.selectedIndex = itemIndex;
+
+ info("Select the background-color suggestion with a mouse click.");
+ let onSuggest = editor.once("after-suggest");
+ let node = editor.popup.elements.get(bgcItem);
+ EventUtils.synthesizeMouseAtCenter(node, {}, editor.popup._window);
+
+ yield onSuggest;
+ is(editor.input.value, "background-color", "Correct value is autocompleted");
+
+ info("Press RETURN to move the focus to a property value editor.");
+ let onModifications = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+
+ yield onModifications;
+
+ // Getting the new value editor after focus
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ let textProp = ruleEditor.rule.textProps[1];
+
+ is(ruleEditor.rule.textProps.length, 2,
+ "Created a new text property.");
+ is(ruleEditor.propertyList.children.length, 2,
+ "Created a property editor.");
+ is(editor, inplaceEditor(textProp.editor.valueSpan),
+ "Editing the value span now.");
+
+ info("Entering a value and blurring the field to expect a rule change");
+ editor.input.value = "#F00";
+
+ onModifications = view.once("ruleview-changed");
+ editor.input.blur();
+ yield onModifications;
+
+ is(textProp.value, "#F00", "Text prop should have been changed.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js
new file mode 100644
index 000000000..ec939eafc
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js
@@ -0,0 +1,131 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the behaviour of the CSS autocomplete for CSS value displayed on
+// multiple lines. Expected behavior is:
+// - UP/DOWN should navigate in the input and not increment/decrement numbers
+// - typing a new value should still trigger the autocomplete
+// - UP/DOWN when the autocomplete popup is displayed should cycle through
+// suggestions
+
+const LONG_CSS_VALUE =
+ "transparent linear-gradient(0deg, blue 0%, white 5%, red 10%, blue 15%, " +
+ "white 20%, red 25%, blue 30%, white 35%, red 40%, blue 45%, white 50%, " +
+ "red 55%, blue 60%, white 65%, red 70%, blue 75%, white 80%, red 85%, " +
+ "blue 90%, white 95% ) repeat scroll 0% 0%";
+
+const EXPECTED_CSS_VALUE = LONG_CSS_VALUE.replace("95%", "95%, red");
+
+const TEST_URI =
+ `<style>
+ .title {
+ background: ${LONG_CSS_VALUE};
+ }
+ </style>
+ <h1 class=title>Header</h1>`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let { inspector, view} = yield openRuleView();
+
+ info("Selecting the test node");
+ yield selectNode("h1", inspector);
+
+ info("Focusing the property editable field");
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let prop = rule.textProps[0];
+
+ // Calculate offsets to click in the middle of the first box quad.
+ let rect = prop.editor.valueSpan.getBoundingClientRect();
+ let firstQuad = prop.editor.valueSpan.getBoxQuads()[0];
+ // For a multiline value, the first quad left edge is not aligned with the
+ // bounding rect left edge. The offsets expected by focusEditableField are
+ // relative to the bouding rectangle, so we need to translate the x-offset.
+ let x = firstQuad.bounds.left - rect.left + firstQuad.bounds.width / 2;
+ // The first quad top edge is aligned with the bounding top edge, no
+ // translation needed here.
+ let y = firstQuad.bounds.height / 2;
+
+ info("Focusing the css property editable value");
+ let editor = yield focusEditableField(view, prop.editor.valueSpan, x, y);
+
+ info("Moving the caret next to a number");
+ let pos = editor.input.value.indexOf("0deg") + 1;
+ editor.input.setSelectionRange(pos, pos);
+ is(editor.input.value[editor.input.selectionStart - 1], "0",
+ "Input caret is after a 0");
+
+ info("Check that UP/DOWN navigates in the input, even when next to a number");
+ EventUtils.synthesizeKey("VK_DOWN", {}, view.styleWindow);
+ ok(editor.input.selectionStart !== pos, "Input caret moved");
+ is(editor.input.value, LONG_CSS_VALUE, "Input value was not decremented.");
+
+ info("Move the caret to the end of the gradient definition.");
+ pos = editor.input.value.indexOf("95%") + 3;
+ editor.input.setSelectionRange(pos, pos);
+
+ info("Sending \", re\" to the editable field.");
+ for (let key of ", re") {
+ yield synthesizeKeyForAutocomplete(key, editor, view.styleWindow);
+ }
+
+ info("Check the autocomplete can still be displayed.");
+ ok(editor.popup && editor.popup.isOpen, "Autocomplete popup is displayed.");
+ is(editor.popup.selectedIndex, 0,
+ "Autocomplete has an item selected by default");
+
+ let item = editor.popup.getItemAtIndex(editor.popup.selectedIndex);
+ is(item.label, "rebeccapurple",
+ "Check autocomplete displays expected value.");
+
+ info("Check autocomplete suggestions can be cycled using UP/DOWN arrows.");
+
+ yield synthesizeKeyForAutocomplete("VK_DOWN", editor, view.styleWindow);
+ ok(editor.popup.selectedIndex, 1, "Using DOWN cycles autocomplete values.");
+ yield synthesizeKeyForAutocomplete("VK_DOWN", editor, view.styleWindow);
+ ok(editor.popup.selectedIndex, 2, "Using DOWN cycles autocomplete values.");
+ yield synthesizeKeyForAutocomplete("VK_UP", editor, view.styleWindow);
+ is(editor.popup.selectedIndex, 1, "Using UP cycles autocomplete values.");
+ item = editor.popup.getItemAtIndex(editor.popup.selectedIndex);
+ is(item.label, "red", "Check autocomplete displays expected value.");
+
+ info("Select the background-color suggestion with a mouse click.");
+ let onRuleviewChanged = view.once("ruleview-changed");
+ let onSuggest = editor.once("after-suggest");
+
+ let node = editor.popup._list.childNodes[editor.popup.selectedIndex];
+ EventUtils.synthesizeMouseAtCenter(node, {}, editor.popup._window);
+
+ view.throttle.flush();
+ yield onSuggest;
+ yield onRuleviewChanged;
+
+ is(editor.input.value, EXPECTED_CSS_VALUE,
+ "Input value correctly autocompleted");
+
+ info("Press ESCAPE to leave the input.");
+ onRuleviewChanged = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ yield onRuleviewChanged;
+});
+
+/**
+ * Send the provided key to the currently focused input of the provided window.
+ * Wait for the editor to emit "after-suggest" to make sure the autocompletion
+ * process is finished.
+ *
+ * @param {String} key
+ * The key to send to the input.
+ * @param {InplaceEditor} editor
+ * The inplace editor which owns the focused input.
+ * @param {Window} win
+ * Window in which the key event will be dispatched.
+ */
+function* synthesizeKeyForAutocomplete(key, editor, win) {
+ let onSuggest = editor.once("after-suggest");
+ EventUtils.synthesizeKey(key, {}, win);
+ yield onSuggest;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-popup-hidden-after-navigation.js b/devtools/client/inspector/rules/test/browser_rules_completion-popup-hidden-after-navigation.js
new file mode 100644
index 000000000..84f119606
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-popup-hidden-after-navigation.js
@@ -0,0 +1,41 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the ruleview autocomplete popup is hidden after page navigation.
+
+const TEST_URI = "<h1 style='font: 24px serif'></h1>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view, testActor} = yield openRuleView();
+
+ info("Test autocompletion popup is hidden after page navigation");
+
+ info("Selecting the test node");
+ yield selectNode("h1", inspector);
+
+ info("Focusing the css property editable field");
+ let propertyName = view.styleDocument
+ .querySelectorAll(".ruleview-propertyname")[0];
+ let editor = yield focusEditableField(view, propertyName);
+
+ info("Pressing key VK_DOWN");
+ let onSuggest = once(editor.input, "keypress");
+ let onPopupOpened = once(editor.popup, "popup-opened");
+
+ EventUtils.synthesizeKey("VK_DOWN", {}, view.styleWindow);
+
+ info("Waiting for autocomplete popup to be displayed");
+ yield onSuggest;
+ yield onPopupOpened;
+
+ ok(view.popup && view.popup.isOpen, "Popup should be opened");
+
+ info("Reloading the page");
+ yield reloadPage(inspector, testActor);
+
+ ok(!(view.popup && view.popup.isOpen), "Popup should be closed");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_computed-lists_01.js b/devtools/client/inspector/rules/test/browser_rules_computed-lists_01.js
new file mode 100644
index 000000000..5acebd562
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_computed-lists_01.js
@@ -0,0 +1,47 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view shows expanders for properties with computed lists.
+
+var TEST_URI = `
+ <style type="text/css">
+ #testid {
+ margin: 4px;
+ top: 0px;
+ }
+ </style>
+ <h1 id="testid">Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testExpandersShown(inspector, view);
+});
+
+function* testExpandersShown(inspector, view) {
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+
+ info("Check that the correct rules are visible");
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ is(rule.textProps[0].name, "margin", "First property is margin.");
+ is(rule.textProps[1].name, "top", "Second property is top.");
+
+ info("Check that the expanders are shown correctly");
+ is(rule.textProps[0].editor.expander.style.visibility, "visible",
+ "margin expander is visible.");
+ is(rule.textProps[1].editor.expander.style.visibility, "hidden",
+ "top expander is hidden.");
+ ok(!rule.textProps[0].editor.expander.hasAttribute("open"),
+ "margin computed list is closed.");
+ ok(!rule.textProps[1].editor.expander.hasAttribute("open"),
+ "top computed list is closed.");
+ ok(!rule.textProps[0].editor.computed.hasChildNodes(),
+ "margin computed list is empty before opening.");
+ ok(!rule.textProps[1].editor.computed.hasChildNodes(),
+ "top computed list is empty.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_computed-lists_02.js b/devtools/client/inspector/rules/test/browser_rules_computed-lists_02.js
new file mode 100644
index 000000000..d6dc82d5f
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_computed-lists_02.js
@@ -0,0 +1,74 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view computed lists can be expanded/collapsed,
+// and contain the right subproperties.
+
+var TEST_URI = `
+ <style type="text/css">
+ #testid {
+ margin: 0px 1px 2px 3px;
+ top: 0px;
+ }
+ </style>
+ <h1 id="testid">Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testComputedList(inspector, view);
+});
+
+function* testComputedList(inspector, view) {
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let propEditor = rule.textProps[0].editor;
+ let expander = propEditor.expander;
+
+ ok(!expander.hasAttribute("open"), "margin computed list is closed");
+
+ info("Opening the computed list of margin property");
+ expander.click();
+ ok(expander.hasAttribute("open"), "margin computed list is open");
+
+ let computed = propEditor.prop.computed;
+ let computedDom = propEditor.computed;
+ let propNames = [
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left"
+ ];
+
+ is(computed.length, propNames.length, "There should be 4 computed values");
+ is(computedDom.children.length, propNames.length,
+ "There should be 4 nodes in the DOM");
+
+ propNames.forEach((propName, i) => {
+ let propValue = i + "px";
+ is(computed[i].name, propName,
+ "Computed property #" + i + " has name " + propName);
+ is(computed[i].value, propValue,
+ "Computed property #" + i + " has value " + propValue);
+ is(computedDom.querySelectorAll(".ruleview-propertyname")[i].textContent,
+ propName,
+ "Computed property #" + i + " in DOM has correct name");
+ is(computedDom.querySelectorAll(".ruleview-propertyvalue")[i].textContent,
+ propValue,
+ "Computed property #" + i + " in DOM has correct value");
+ });
+
+ info("Closing the computed list of margin property");
+ expander.click();
+ ok(!expander.hasAttribute("open"), "margin computed list is closed");
+
+ info("Opening the computed list of margin property");
+ expander.click();
+ ok(expander.hasAttribute("open"), "margin computed list is open");
+ is(computed.length, propNames.length, "Still 4 computed values");
+ is(computedDom.children.length, propNames.length, "Still 4 nodes in the DOM");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_content_01.js b/devtools/client/inspector/rules/test/browser_rules_content_01.js
new file mode 100644
index 000000000..8695d9b8d
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_content_01.js
@@ -0,0 +1,51 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule-view content is correct
+
+const TEST_URI = `
+ <style type="text/css">
+ @media screen and (min-width: 10px) {
+ #testid {
+ background-color: blue;
+ }
+ }
+ .testclass, .unmatched {
+ background-color: green;
+ }
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>
+ <div id="testid2">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("#testid", inspector);
+ is(view.element.querySelectorAll("#ruleview-no-results").length, 0,
+ "After a highlight, no longer has a no-results element.");
+
+ yield clearCurrentNodeSelection(inspector);
+ is(view.element.querySelectorAll("#ruleview-no-results").length, 1,
+ "After highlighting null, has a no-results element again.");
+
+ yield selectNode("#testid", inspector);
+
+ let linkText = getRuleViewLinkTextByIndex(view, 1);
+ is(linkText, "inline:3 @screen and (min-width: 10px)",
+ "link text at index 1 contains media query text.");
+
+ linkText = getRuleViewLinkTextByIndex(view, 2);
+ is(linkText, "inline:7",
+ "link text at index 2 contains no media query text.");
+
+ let selector = getRuleViewRuleEditor(view, 2).selectorText;
+ is(selector.querySelector(".ruleview-selector-matched").textContent,
+ ".testclass", ".textclass should be matched.");
+ is(selector.querySelector(".ruleview-selector-unmatched").textContent,
+ ".unmatched", ".unmatched should not be matched.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_content_02.js b/devtools/client/inspector/rules/test/browser_rules_content_02.js
new file mode 100644
index 000000000..253f374b4
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_content_02.js
@@ -0,0 +1,60 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* globals getTestActorWithoutToolbox */
+"use strict";
+
+// Test the rule-view content when the inspector gets opened via the page
+// ctx-menu "inspect element"
+
+const CONTENT = `
+ <body style="color:red;">
+ <div style="color:blue;">
+ <p style="color:green;">
+ <span style="color:yellow;">test element</span>
+ </p>
+ </div>
+ </body>
+`;
+
+add_task(function* () {
+ let tab = yield addTab("data:text/html;charset=utf-8," + CONTENT);
+
+ let testActor = yield getTestActorWithoutToolbox(tab);
+ let inspector = yield clickOnInspectMenuItem(testActor, "span");
+
+ checkRuleViewContent(inspector.ruleview.view);
+});
+
+function checkRuleViewContent({styleDocument}) {
+ info("Making sure the rule-view contains the expected content");
+
+ let headers = [...styleDocument.querySelectorAll(".ruleview-header")];
+ is(headers.length, 3, "There are 3 headers for inherited rules");
+
+ is(headers[0].textContent,
+ STYLE_INSPECTOR_L10N.getFormatStr("rule.inheritedFrom", "p"),
+ "The first header is correct");
+ is(headers[1].textContent,
+ STYLE_INSPECTOR_L10N.getFormatStr("rule.inheritedFrom", "div"),
+ "The second header is correct");
+ is(headers[2].textContent,
+ STYLE_INSPECTOR_L10N.getFormatStr("rule.inheritedFrom", "body"),
+ "The third header is correct");
+
+ let rules = styleDocument.querySelectorAll(".ruleview-rule");
+ is(rules.length, 4, "There are 4 rules in the view");
+
+ for (let rule of rules) {
+ let selector = rule.querySelector(".ruleview-selectorcontainer");
+ is(selector.textContent, STYLE_INSPECTOR_L10N.getStr("rule.sourceElement"),
+ "The rule's selector is correct");
+
+ let propertyNames = [...rule.querySelectorAll(".ruleview-propertyname")];
+ is(propertyNames.length, 1, "There's only one property name, as expected");
+
+ let propertyValues = [...rule.querySelectorAll(".ruleview-propertyvalue")];
+ is(propertyValues.length, 1, "There's only one property value, as expected");
+ }
+}
+
diff --git a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js
new file mode 100644
index 000000000..b81bb8013
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js
@@ -0,0 +1,96 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests the code that integrates the Style Inspector's rule view
+ * with the MDN docs tooltip.
+ *
+ * If you display the context click on a property name in the rule view, you
+ * should see a menu item "Show MDN Docs". If you click that item, the MDN
+ * docs tooltip should be shown, containing docs from MDN for that property.
+ *
+ * This file tests that the context menu item is shown when it should be
+ * shown and hidden when it should be hidden.
+ */
+
+"use strict";
+
+/**
+ * The test document tries to confuse the context menu
+ * code by having a tag called "padding" and a property
+ * value called "margin".
+ */
+const TEST_URI = `
+ <html>
+ <head>
+ <style>
+ padding {font-family: margin;}
+ </style>
+ </head>
+
+ <body>
+ <padding>MDN tooltip testing</padding>
+ </body>
+ </html>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("padding", inspector);
+ yield testMdnContextMenuItemVisibility(view);
+});
+
+/**
+ * Tests that the MDN context menu item is shown when it should be,
+ * and hidden when it should be.
+ * - iterate through every node in the rule view
+ * - set that node as popupNode (the node that the context menu
+ * is shown for)
+ * - update the context menu's state
+ * - test that the MDN context menu item is hidden, or not,
+ * depending on popupNode
+ */
+function* testMdnContextMenuItemVisibility(view) {
+ info("Test that MDN context menu item is shown only when it should be.");
+
+ let root = rootElement(view);
+ for (let node of iterateNodes(root)) {
+ info("Setting " + node + " as popupNode");
+ info("Creating context menu with " + node + " as popupNode");
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, node);
+ let menuitemShowMdnDocs = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs"));
+
+ let isVisible = menuitemShowMdnDocs.visible;
+ let shouldBeVisible = isPropertyNameNode(node);
+ let message = shouldBeVisible ? "shown" : "hidden";
+ is(isVisible, shouldBeVisible,
+ "The MDN context menu item is " + message + " ; content : " +
+ node.textContent + " ; type : " + node.nodeType);
+ }
+}
+
+/**
+ * Check if a node is a property name.
+ */
+function isPropertyNameNode(node) {
+ return node.textContent === "font-family";
+}
+
+/**
+ * A generator that iterates recursively through all child nodes of baseNode.
+ */
+function* iterateNodes(baseNode) {
+ yield baseNode;
+
+ for (let child of baseNode.childNodes) {
+ yield* iterateNodes(child);
+ }
+}
+
+/**
+ * Returns the root element for the rule view.
+ */
+var rootElement = view => (view.element) ? view.element : view.styleDocument;
diff --git a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js
new file mode 100644
index 000000000..e0d08d28a
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js
@@ -0,0 +1,61 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests the code that integrates the Style Inspector's rule view
+ * with the MDN docs tooltip.
+ *
+ * If you display the context click on a property name in the rule view, you
+ * should see a menu item "Show MDN Docs". If you click that item, the MDN
+ * docs tooltip should be shown, containing docs from MDN for that property.
+ *
+ * This file tests that:
+ * - clicking the context menu item shows the tooltip
+ * - the tooltip content matches the property name for which the context menu was opened
+ */
+
+"use strict";
+
+const {setBaseCssDocsUrl} =
+ require("devtools/client/shared/widgets/MdnDocsWidget");
+
+const PROPERTYNAME = "color";
+
+const TEST_DOC = `
+ <html>
+ <body>
+ <div style="color: red">
+ Test "Show MDN Docs" context menu option
+ </div>
+ </body>
+ </html>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_DOC));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ setBaseCssDocsUrl(URL_ROOT);
+
+ info("Setting the popupNode for the MDN docs tooltip");
+
+ let {nameSpan} = getRuleViewProperty(view, "element", PROPERTYNAME);
+
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, nameSpan.firstChild);
+ let menuitemShowMdnDocs = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs"));
+
+ let cssDocs = view.tooltips.cssDocs;
+
+ info("Showing the MDN docs tooltip");
+ let onShown = cssDocs.tooltip.once("shown");
+ menuitemShowMdnDocs.click();
+ yield onShown;
+ ok(true, "The MDN docs tooltip was shown");
+
+ info("Quick check that the tooltip contents are set");
+ let h1 = cssDocs.tooltip.container.querySelector(".mdn-property-name");
+ is(h1.textContent, PROPERTYNAME, "The MDN docs tooltip h1 is correct");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-03.js b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-03.js
new file mode 100644
index 000000000..d1089fcf6
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-03.js
@@ -0,0 +1,118 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests the "devtools.inspector.mdnDocsTooltip.enabled" preference,
+ * that we use to enable/disable the MDN tooltip in the Inspector.
+ *
+ * The desired behavior is:
+ * - if the preference is true, show the "Show MDN Docs" context menu item
+ * - if the preference is false, don't show the item
+ * - listen for changes to the pref, so we can show/hide the item dynamically
+ */
+
+"use strict";
+
+const { PrefObserver } = require("devtools/client/styleeditor/utils");
+const PREF_ENABLE_MDN_DOCS_TOOLTIP =
+ "devtools.inspector.mdnDocsTooltip.enabled";
+const PROPERTY_NAME_CLASS = "ruleview-propertyname";
+
+const TEST_DOC = `
+ <html>
+ <body>
+ <div style="color: red">
+ Test the pref to enable/disable the "Show MDN Docs" context menu option
+ </div>
+ </body>
+ </html>
+`;
+
+add_task(function* () {
+ info("Ensure the pref is true to begin with");
+ let initial = Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP);
+ if (initial != true) {
+ setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, true);
+ }
+
+ yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_DOC));
+
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+ yield testMdnContextMenuItemVisibility(view, true);
+
+ yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, false);
+ yield testMdnContextMenuItemVisibility(view, false);
+
+ info("Close the Inspector");
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ yield gDevTools.closeToolbox(target);
+
+ ({inspector, view} = yield openRuleView());
+ yield selectNode("div", inspector);
+ yield testMdnContextMenuItemVisibility(view, false);
+
+ yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, true);
+ yield testMdnContextMenuItemVisibility(view, true);
+
+ info("Ensure the pref is reset to its initial value");
+ let eventual = Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP);
+ if (eventual != initial) {
+ setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, initial);
+ }
+});
+
+/**
+ * Set a boolean pref, and wait for the pref observer to
+ * trigger, so that code listening for the pref change
+ * has had a chance to update itself.
+ *
+ * @param pref {string} Name of the pref to change
+ * @param state {boolean} Desired value of the pref.
+ *
+ * Note that if the pref already has the value in `state`,
+ * then the prefObserver will not trigger. So you should only
+ * call this function if you know the pref's current value is
+ * not `state`.
+ */
+function* setBooleanPref(pref, state) {
+ let oncePrefChanged = defer();
+ let prefObserver = new PrefObserver("devtools.");
+ prefObserver.on(pref, oncePrefChanged.resolve);
+
+ info("Set the pref " + pref + " to: " + state);
+ Services.prefs.setBoolPref(pref, state);
+
+ info("Wait for prefObserver to call back so the UI can update");
+ yield oncePrefChanged.promise;
+ prefObserver.off(pref, oncePrefChanged.resolve);
+}
+
+/**
+ * Test whether the MDN tooltip context menu item is visible when it should be.
+ *
+ * @param view The rule view
+ * @param shouldBeVisible {boolean} Whether we expect the context
+ * menu item to be visible or not.
+ */
+function* testMdnContextMenuItemVisibility(view, shouldBeVisible) {
+ let message = shouldBeVisible ? "shown" : "hidden";
+ info("Test that MDN context menu item is " + message);
+
+ info("Set a CSS property name as popupNode");
+ let root = rootElement(view);
+ let node = root.querySelector("." + PROPERTY_NAME_CLASS).firstChild;
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, node);
+ let menuitemShowMdnDocs = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs"));
+
+ let isVisible = menuitemShowMdnDocs.visible;
+ is(isVisible, shouldBeVisible,
+ "The MDN context menu item is " + message);
+}
+
+/**
+ * Returns the root element for the rule view.
+ */
+var rootElement = view => (view.element) ? view.element : view.styleDocument;
diff --git a/devtools/client/inspector/rules/test/browser_rules_copy_styles.js b/devtools/client/inspector/rules/test/browser_rules_copy_styles.js
new file mode 100644
index 000000000..a6f991a60
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_copy_styles.js
@@ -0,0 +1,307 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests the behaviour of the copy styles context menu items in the rule
+ * view.
+ */
+
+const osString = Services.appinfo.OS;
+
+const TEST_URI = URL_ROOT + "doc_copystyles.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let { inspector, view } = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ let data = [
+ {
+ desc: "Test Copy Property Name",
+ node: ruleEditor.rule.textProps[0].editor.nameSpan,
+ menuItemLabel: "styleinspector.contextmenu.copyPropertyName",
+ expectedPattern: "color",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: true,
+ copyPropertyName: true,
+ copyPropertyValue: false,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ {
+ desc: "Test Copy Property Value",
+ node: ruleEditor.rule.textProps[2].editor.valueSpan,
+ menuItemLabel: "styleinspector.contextmenu.copyPropertyValue",
+ expectedPattern: "12px",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: true,
+ copyPropertyName: false,
+ copyPropertyValue: true,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ {
+ desc: "Test Copy Property Value with Priority",
+ node: ruleEditor.rule.textProps[3].editor.valueSpan,
+ menuItemLabel: "styleinspector.contextmenu.copyPropertyValue",
+ expectedPattern: "#00F !important",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: true,
+ copyPropertyName: false,
+ copyPropertyValue: true,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ {
+ desc: "Test Copy Property Declaration",
+ node: ruleEditor.rule.textProps[2].editor.nameSpan,
+ menuItemLabel: "styleinspector.contextmenu.copyPropertyDeclaration",
+ expectedPattern: "font-size: 12px;",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: true,
+ copyPropertyName: true,
+ copyPropertyValue: false,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ {
+ desc: "Test Copy Property Declaration with Priority",
+ node: ruleEditor.rule.textProps[3].editor.nameSpan,
+ menuItemLabel: "styleinspector.contextmenu.copyPropertyDeclaration",
+ expectedPattern: "border-color: #00F !important;",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: true,
+ copyPropertyName: true,
+ copyPropertyValue: false,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ {
+ desc: "Test Copy Rule",
+ node: ruleEditor.rule.textProps[2].editor.nameSpan,
+ menuItemLabel: "styleinspector.contextmenu.copyRule",
+ expectedPattern: "#testid {[\\r\\n]+" +
+ "\tcolor: #F00;[\\r\\n]+" +
+ "\tbackground-color: #00F;[\\r\\n]+" +
+ "\tfont-size: 12px;[\\r\\n]+" +
+ "\tborder-color: #00F !important;[\\r\\n]+" +
+ "\t--var: \"\\*/\";[\\r\\n]+" +
+ "}",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: true,
+ copyPropertyName: true,
+ copyPropertyValue: false,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ {
+ desc: "Test Copy Selector",
+ node: ruleEditor.selectorText,
+ menuItemLabel: "styleinspector.contextmenu.copySelector",
+ expectedPattern: "html, body, #testid",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: false,
+ copyPropertyName: false,
+ copyPropertyValue: false,
+ copySelector: true,
+ copyRule: true
+ }
+ },
+ {
+ desc: "Test Copy Location",
+ node: ruleEditor.source,
+ menuItemLabel: "styleinspector.contextmenu.copyLocation",
+ expectedPattern: "http://example.com/browser/devtools/client/" +
+ "inspector/rules/test/doc_copystyles.css",
+ visible: {
+ copyLocation: true,
+ copyPropertyDeclaration: false,
+ copyPropertyName: false,
+ copyPropertyValue: false,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ {
+ setup: function* () {
+ yield disableProperty(view, 0);
+ },
+ desc: "Test Copy Rule with Disabled Property",
+ node: ruleEditor.rule.textProps[2].editor.nameSpan,
+ menuItemLabel: "styleinspector.contextmenu.copyRule",
+ expectedPattern: "#testid {[\\r\\n]+" +
+ "\t\/\\* color: #F00; \\*\/[\\r\\n]+" +
+ "\tbackground-color: #00F;[\\r\\n]+" +
+ "\tfont-size: 12px;[\\r\\n]+" +
+ "\tborder-color: #00F !important;[\\r\\n]+" +
+ "\t--var: \"\\*/\";[\\r\\n]+" +
+ "}",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: true,
+ copyPropertyName: true,
+ copyPropertyValue: false,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ {
+ setup: function* () {
+ yield disableProperty(view, 4);
+ },
+ desc: "Test Copy Rule with Disabled Property with Comment",
+ node: ruleEditor.rule.textProps[2].editor.nameSpan,
+ menuItemLabel: "styleinspector.contextmenu.copyRule",
+ expectedPattern: "#testid {[\\r\\n]+" +
+ "\t\/\\* color: #F00; \\*\/[\\r\\n]+" +
+ "\tbackground-color: #00F;[\\r\\n]+" +
+ "\tfont-size: 12px;[\\r\\n]+" +
+ "\tborder-color: #00F !important;[\\r\\n]+" +
+ "\t/\\* --var: \"\\*\\\\\/\"; \\*\/[\\r\\n]+" +
+ "}",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: true,
+ copyPropertyName: true,
+ copyPropertyValue: false,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ {
+ desc: "Test Copy Property Declaration with Disabled Property",
+ node: ruleEditor.rule.textProps[0].editor.nameSpan,
+ menuItemLabel: "styleinspector.contextmenu.copyPropertyDeclaration",
+ expectedPattern: "\/\\* color: #F00; \\*\/",
+ visible: {
+ copyLocation: false,
+ copyPropertyDeclaration: true,
+ copyPropertyName: true,
+ copyPropertyValue: false,
+ copySelector: false,
+ copyRule: true
+ }
+ },
+ ];
+
+ for (let { setup, desc, node, menuItemLabel, expectedPattern, visible } of data) {
+ if (setup) {
+ yield setup();
+ }
+
+ info(desc);
+ yield checkCopyStyle(view, node, menuItemLabel, expectedPattern, visible);
+ }
+});
+
+function* checkCopyStyle(view, node, menuItemLabel, expectedPattern, visible) {
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, node);
+ let menuItem = allMenuItems.find(item =>
+ item.label === STYLE_INSPECTOR_L10N.getStr(menuItemLabel));
+ let menuitemCopy = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copy"));
+ let menuitemCopyLocation = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyLocation"));
+ let menuitemCopyPropertyDeclaration = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyPropertyDeclaration"));
+ let menuitemCopyPropertyName = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyPropertyName"));
+ let menuitemCopyPropertyValue = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyPropertyValue"));
+ let menuitemCopySelector = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copySelector"));
+ let menuitemCopyRule = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyRule"));
+
+ ok(menuitemCopy.disabled,
+ "Copy disabled is as expected: true");
+ ok(menuitemCopy.visible,
+ "Copy visible is as expected: true");
+
+ is(menuitemCopyLocation.visible,
+ visible.copyLocation,
+ "Copy Location visible attribute is as expected: " +
+ visible.copyLocation);
+
+ is(menuitemCopyPropertyDeclaration.visible,
+ visible.copyPropertyDeclaration,
+ "Copy Property Declaration visible attribute is as expected: " +
+ visible.copyPropertyDeclaration);
+
+ is(menuitemCopyPropertyName.visible,
+ visible.copyPropertyName,
+ "Copy Property Name visible attribute is as expected: " +
+ visible.copyPropertyName);
+
+ is(menuitemCopyPropertyValue.visible,
+ visible.copyPropertyValue,
+ "Copy Property Value visible attribute is as expected: " +
+ visible.copyPropertyValue);
+
+ is(menuitemCopySelector.visible,
+ visible.copySelector,
+ "Copy Selector visible attribute is as expected: " +
+ visible.copySelector);
+
+ is(menuitemCopyRule.visible,
+ visible.copyRule,
+ "Copy Rule visible attribute is as expected: " +
+ visible.copyRule);
+
+ try {
+ yield waitForClipboardPromise(() => menuItem.click(),
+ () => checkClipboardData(expectedPattern));
+ } catch (e) {
+ failedClipboard(expectedPattern);
+ }
+}
+
+function* disableProperty(view, index) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let textProp = ruleEditor.rule.textProps[index];
+ yield togglePropStatus(view, textProp);
+}
+
+function checkClipboardData(expectedPattern) {
+ let actual = SpecialPowers.getClipboardData("text/unicode");
+ let expectedRegExp = new RegExp(expectedPattern, "g");
+ return expectedRegExp.test(actual);
+}
+
+function failedClipboard(expectedPattern) {
+ // Format expected text for comparison
+ let terminator = osString == "WINNT" ? "\r\n" : "\n";
+ expectedPattern = expectedPattern.replace(/\[\\r\\n\][+*]/g, terminator);
+ expectedPattern = expectedPattern.replace(/\\\(/g, "(");
+ expectedPattern = expectedPattern.replace(/\\\)/g, ")");
+
+ let actual = SpecialPowers.getClipboardData("text/unicode");
+
+ // Trim the right hand side of our strings. This is because expectedPattern
+ // accounts for windows sometimes adding a newline to our copied data.
+ expectedPattern = expectedPattern.trimRight();
+ actual = actual.trimRight();
+
+ ok(false, "Clipboard text does not match expected " +
+ "results (escaped for accurate comparison):\n");
+ info("Actual: " + escape(actual));
+ info("Expected: " + escape(expectedPattern));
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_css-docs-tooltip_closes-on-escape.js b/devtools/client/inspector/rules/test/browser_rules_css-docs-tooltip_closes-on-escape.js
new file mode 100644
index 000000000..f386f45b4
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_css-docs-tooltip_closes-on-escape.js
@@ -0,0 +1,51 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the CssDocs tooltip of the ruleview can be closed when pressing the Escape
+ * key.
+ */
+
+"use strict";
+
+const {setBaseCssDocsUrl} =
+ require("devtools/client/shared/widgets/MdnDocsWidget");
+
+const PROPERTYNAME = "color";
+
+const TEST_URI = `
+ <html>
+ <body>
+ <div style="color: red">
+ Test "Show MDN Docs" closes on escape
+ </div>
+ </body>
+ </html>
+`;
+
+/**
+ * Test that the tooltip is hidden when we press Escape
+ */
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ setBaseCssDocsUrl(URL_ROOT);
+
+ info("Retrieve a valid anchor for the CssDocs tooltip");
+ let {nameSpan} = getRuleViewProperty(view, "element", PROPERTYNAME);
+
+ info("Showing the MDN docs tooltip");
+ let onShown = view.tooltips.cssDocs.tooltip.once("shown");
+ view.tooltips.cssDocs.show(nameSpan, PROPERTYNAME);
+ yield onShown;
+ ok(true, "The MDN docs tooltip was shown");
+
+ info("Simulate pressing the 'Escape' key");
+ let onHidden = view.tooltips.cssDocs.tooltip.once("hidden");
+ EventUtils.sendKey("escape");
+ yield onHidden;
+ ok(true, "The MDN docs tooltip was hidden on pressing 'escape'");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_cssom.js b/devtools/client/inspector/rules/test/browser_rules_cssom.js
new file mode 100644
index 000000000..d20e85192
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_cssom.js
@@ -0,0 +1,22 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test to ensure that CSSOM doesn't make the rule view blow up.
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1224121
+
+const TEST_URI = URL_ROOT + "doc_cssom.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#target", inspector);
+
+ let elementStyle = view._elementStyle;
+ let rule = elementStyle.rules[1];
+
+ is(rule.textProps.length, 1, "rule should have one property");
+ is(rule.textProps[0].name, "color", "the property should be 'color'");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_cubicbezier-appears-on-swatch-click.js b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-appears-on-swatch-click.js
new file mode 100644
index 000000000..18099894b
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-appears-on-swatch-click.js
@@ -0,0 +1,70 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that cubic-bezier pickers appear when clicking on cubic-bezier
+// swatches.
+
+const TEST_URI = `
+ <style type="text/css">
+ div {
+ animation: move 3s linear;
+ transition: top 4s cubic-bezier(.1, 1.45, 1, -1.2);
+ }
+ .test {
+ animation-timing-function: ease-in-out;
+ transition-timing-function: ease-out;
+ }
+ </style>
+ <div class="test">Testing the cubic-bezier tooltip!</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ let swatches = [];
+ swatches.push(
+ getRuleViewProperty(view, "div", "animation").valueSpan
+ .querySelector(".ruleview-bezierswatch")
+ );
+ swatches.push(
+ getRuleViewProperty(view, "div", "transition").valueSpan
+ .querySelector(".ruleview-bezierswatch")
+ );
+ swatches.push(
+ getRuleViewProperty(view, ".test", "animation-timing-function").valueSpan
+ .querySelector(".ruleview-bezierswatch")
+ );
+ swatches.push(
+ getRuleViewProperty(view, ".test", "transition-timing-function").valueSpan
+ .querySelector(".ruleview-bezierswatch")
+ );
+
+ for (let swatch of swatches) {
+ info("Testing that the cubic-bezier appears on cubicswatch click");
+ yield testAppears(view, swatch);
+ }
+});
+
+function* testAppears(view, swatch) {
+ ok(swatch, "The cubic-swatch exists");
+
+ let bezier = view.tooltips.cubicBezier;
+ ok(bezier, "The rule-view has the expected cubicBezier property");
+
+ let bezierPanel = bezier.tooltip.panel;
+ ok(bezierPanel, "The XUL panel for the cubic-bezier tooltip exists");
+
+ let onBezierWidgetReady = bezier.once("ready");
+ swatch.click();
+ yield onBezierWidgetReady;
+
+ ok(true, "The cubic-bezier tooltip was shown on click of the cibuc swatch");
+ ok(!inplaceEditor(swatch.parentNode),
+ "The inplace editor wasn't shown as a result of the cibuc swatch click");
+ yield hideTooltipAndWaitForRuleViewChanged(bezier, view);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_cubicbezier-commit-on-ENTER.js b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-commit-on-ENTER.js
new file mode 100644
index 000000000..5dc43d1c9
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-commit-on-ENTER.js
@@ -0,0 +1,66 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that a curve change in the cubic-bezier tooltip is committed when ENTER
+// is pressed.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ transition: top 2s linear;
+ }
+ </style>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+
+ info("Getting the bezier swatch element");
+ let swatch = getRuleViewProperty(view, "body", "transition").valueSpan
+ .querySelector(".ruleview-bezierswatch");
+
+ yield testPressingEnterCommitsChanges(swatch, view);
+});
+
+function* testPressingEnterCommitsChanges(swatch, ruleView) {
+ let bezierTooltip = ruleView.tooltips.cubicBezier;
+
+ info("Showing the tooltip");
+ let onBezierWidgetReady = bezierTooltip.once("ready");
+ swatch.click();
+ yield onBezierWidgetReady;
+
+ let widget = yield bezierTooltip.widget;
+ info("Simulating a change of curve in the widget");
+ widget.coordinates = [0.1, 2, 0.9, -1];
+ let expected = "cubic-bezier(0.1, 2, 0.9, -1)";
+
+ yield waitForSuccess(function* () {
+ let func = yield getComputedStyleProperty("body", null,
+ "transition-timing-function");
+ return func === expected;
+ }, "Waiting for the change to be previewed on the element");
+
+ ok(getRuleViewProperty(ruleView, "body", "transition").valueSpan.textContent
+ .indexOf("cubic-bezier(") !== -1,
+ "The text of the timing-function was updated");
+
+ info("Sending RETURN key within the tooltip document");
+ // Pressing RETURN ends up doing 2 rule-view updates, one for the preview and
+ // one for the commit when the tooltip closes.
+ let onRuleViewChanged = waitForNEvents(ruleView, "ruleview-changed", 2);
+ focusAndSendKey(widget.parent.ownerDocument.defaultView, "RETURN");
+ yield onRuleViewChanged;
+
+ let style = yield getComputedStyleProperty("body", null,
+ "transition-timing-function");
+ is(style, expected, "The element's timing-function was kept after RETURN");
+
+ let ruleViewStyle = getRuleViewProperty(ruleView, "body", "transition")
+ .valueSpan.textContent.indexOf("cubic-bezier(") !== -1;
+ ok(ruleViewStyle, "The text of the timing-function was kept after RETURN");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_cubicbezier-revert-on-ESC.js b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-revert-on-ESC.js
new file mode 100644
index 000000000..826d8a5aa
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-revert-on-ESC.js
@@ -0,0 +1,100 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that changes made to the cubic-bezier timing-function in the
+// cubic-bezier tooltip are reverted when ESC is pressed.
+
+const TEST_URI = `
+ <style type='text/css'>
+ body {
+ animation-timing-function: linear;
+ }
+ </style>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+ yield testPressingEscapeRevertsChanges(view);
+ yield testPressingEscapeRevertsChangesAndDisables(view);
+});
+
+function* testPressingEscapeRevertsChanges(view) {
+ let {propEditor} = yield openCubicBezierAndChangeCoords(view, 1, 0,
+ [0.1, 2, 0.9, -1], {
+ selector: "body",
+ name: "animation-timing-function",
+ value: "cubic-bezier(0.1, 2, 0.9, -1)"
+ });
+
+ is(propEditor.valueSpan.textContent, "cubic-bezier(.1,2,.9,-1)",
+ "Got expected property value.");
+
+ yield escapeTooltip(view);
+
+ yield waitForComputedStyleProperty("body", null, "animation-timing-function",
+ "linear");
+ is(propEditor.valueSpan.textContent, "linear",
+ "Got expected property value.");
+}
+
+function* testPressingEscapeRevertsChangesAndDisables(view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let textProp = ruleEditor.rule.textProps[0];
+ let propEditor = textProp.editor;
+
+ info("Disabling animation-timing-function property");
+ yield togglePropStatus(view, textProp);
+
+ ok(propEditor.element.classList.contains("ruleview-overridden"),
+ "property is overridden.");
+ is(propEditor.enable.style.visibility, "visible",
+ "property enable checkbox is visible.");
+ ok(!propEditor.enable.getAttribute("checked"),
+ "property enable checkbox is not checked.");
+ ok(!propEditor.prop.enabled,
+ "animation-timing-function property is disabled.");
+ let newValue = yield getRulePropertyValue("animation-timing-function");
+ is(newValue, "", "animation-timing-function should have been unset.");
+
+ yield openCubicBezierAndChangeCoords(view, 1, 0, [0.1, 2, 0.9, -1]);
+
+ yield escapeTooltip(view);
+
+ ok(propEditor.element.classList.contains("ruleview-overridden"),
+ "property is overridden.");
+ is(propEditor.enable.style.visibility, "visible",
+ "property enable checkbox is visible.");
+ ok(!propEditor.enable.getAttribute("checked"),
+ "property enable checkbox is not checked.");
+ ok(!propEditor.prop.enabled,
+ "animation-timing-function property is disabled.");
+ newValue = yield getRulePropertyValue("animation-timing-function");
+ is(newValue, "", "animation-timing-function should have been unset.");
+ is(propEditor.valueSpan.textContent, "linear",
+ "Got expected property value.");
+}
+
+function* getRulePropertyValue(name) {
+ let propValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: name
+ });
+ return propValue;
+}
+
+function* escapeTooltip(view) {
+ info("Pressing ESCAPE to close the tooltip");
+
+ let bezierTooltip = view.tooltips.cubicBezier;
+ let widget = yield bezierTooltip.widget;
+ let onHidden = bezierTooltip.tooltip.once("hidden");
+ let onModifications = view.once("ruleview-changed");
+ focusAndSendKey(widget.parent.ownerDocument.defaultView, "ESCAPE");
+ yield onHidden;
+ yield onModifications;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_custom.js b/devtools/client/inspector/rules/test/browser_rules_custom.js
new file mode 100644
index 000000000..7c941af6f
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_custom.js
@@ -0,0 +1,72 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = URL_ROOT + "doc_custom.html";
+
+// Tests the display of custom declarations in the rule-view.
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+
+ yield simpleCustomOverride(inspector, view);
+ yield importantCustomOverride(inspector, view);
+ yield disableCustomOverride(inspector, view);
+});
+
+function* simpleCustomOverride(inspector, view) {
+ yield selectNode("#testidSimple", inspector);
+
+ let idRule = getRuleViewRuleEditor(view, 1).rule;
+ let idRuleProp = idRule.textProps[0];
+
+ is(idRuleProp.name, "--background-color",
+ "First ID prop should be --background-color");
+ ok(!idRuleProp.overridden, "ID prop should not be overridden.");
+
+ let classRule = getRuleViewRuleEditor(view, 2).rule;
+ let classRuleProp = classRule.textProps[0];
+
+ is(classRuleProp.name, "--background-color",
+ "First class prop should be --background-color");
+ ok(classRuleProp.overridden, "Class property should be overridden.");
+
+ // Override --background-color by changing the element style.
+ let elementProp = yield addProperty(view, 0, "--background-color", "purple");
+
+ is(classRuleProp.name, "--background-color",
+ "First element prop should now be --background-color");
+ ok(!elementProp.overridden,
+ "Element style property should not be overridden");
+ ok(idRuleProp.overridden, "ID property should be overridden");
+ ok(classRuleProp.overridden, "Class property should be overridden");
+}
+
+function* importantCustomOverride(inspector, view) {
+ yield selectNode("#testidImportant", inspector);
+
+ let idRule = getRuleViewRuleEditor(view, 1).rule;
+ let idRuleProp = idRule.textProps[0];
+ ok(idRuleProp.overridden, "Not-important rule should be overridden.");
+
+ let classRule = getRuleViewRuleEditor(view, 2).rule;
+ let classRuleProp = classRule.textProps[0];
+ ok(!classRuleProp.overridden, "Important rule should not be overridden.");
+}
+
+function* disableCustomOverride(inspector, view) {
+ yield selectNode("#testidDisable", inspector);
+
+ let idRule = getRuleViewRuleEditor(view, 1).rule;
+ let idRuleProp = idRule.textProps[0];
+
+ yield togglePropStatus(view, idRuleProp);
+
+ let classRule = getRuleViewRuleEditor(view, 2).rule;
+ let classRuleProp = classRule.textProps[0];
+ ok(!classRuleProp.overridden,
+ "Class prop should not be overridden after id prop was disabled.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_cycle-angle.js b/devtools/client/inspector/rules/test/browser_rules_cycle-angle.js
new file mode 100644
index 000000000..fa135f937
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_cycle-angle.js
@@ -0,0 +1,93 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test cycling angle units in the rule view.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ image-orientation: 1turn;
+ }
+ div {
+ image-orientation: 180deg;
+ }
+ </style>
+ <body><div>Test</div>cycling angle units in the rule view!</body>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ let container = getRuleViewProperty(
+ view, "body", "image-orientation").valueSpan;
+ yield checkAngleCycling(container, view);
+ yield checkAngleCyclingPersist(inspector, view);
+});
+
+function* checkAngleCycling(container, view) {
+ let valueNode = container.querySelector(".ruleview-angle");
+ let win = view.styleWindow;
+
+ // turn
+ is(valueNode.textContent, "1turn", "Angle displayed as a turn value.");
+
+ let tests = [{
+ value: "360deg",
+ comment: "Angle displayed as a degree value."
+ }, {
+ value: `${Math.round(Math.PI * 2 * 10000) / 10000}rad`,
+ comment: "Angle displayed as a radian value."
+ }, {
+ value: "400grad",
+ comment: "Angle displayed as a gradian value."
+ }, {
+ value: "1turn",
+ comment: "Angle displayed as a turn value again."
+ }];
+
+ for (let test of tests) {
+ yield checkSwatchShiftClick(container, win, test.value, test.comment);
+ }
+}
+
+function* checkAngleCyclingPersist(inspector, view) {
+ yield selectNode("div", inspector);
+ let container = getRuleViewProperty(
+ view, "div", "image-orientation").valueSpan;
+ let valueNode = container.querySelector(".ruleview-angle");
+ let win = view.styleWindow;
+
+ is(valueNode.textContent, "180deg", "Angle displayed as a degree value.");
+
+ yield checkSwatchShiftClick(container, win,
+ `${Math.round(Math.PI * 10000) / 10000}rad`,
+ "Angle displayed as a radian value.");
+
+ // Select the body and reselect the div to see
+ // if the new angle unit persisted
+ yield selectNode("body", inspector);
+ yield selectNode("div", inspector);
+
+ // We have to query for the container and the swatch because
+ // they've been re-generated
+ container = getRuleViewProperty(view, "div", "image-orientation").valueSpan;
+ valueNode = container.querySelector(".ruleview-angle");
+ is(valueNode.textContent, `${Math.round(Math.PI * 10000) / 10000}rad`,
+ "Angle still displayed as a radian value.");
+}
+
+function* checkSwatchShiftClick(container, win, expectedValue, comment) {
+ let swatch = container.querySelector(".ruleview-angleswatch");
+ let valueNode = container.querySelector(".ruleview-angle");
+
+ let onUnitChange = swatch.once("unit-change");
+ EventUtils.synthesizeMouseAtCenter(swatch, {
+ type: "mousedown",
+ shiftKey: true
+ }, win);
+ yield onUnitChange;
+ is(valueNode.textContent, expectedValue, comment);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_cycle-color.js b/devtools/client/inspector/rules/test/browser_rules_cycle-color.js
new file mode 100644
index 000000000..e31ffa133
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_cycle-color.js
@@ -0,0 +1,120 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test cycling color types in the rule view.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ color: #f00;
+ }
+ span {
+ color: blue;
+ border-color: #ff000080;
+ }
+ </style>
+ <body><span>Test</span> cycling color types in the rule view!</body>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ let container = getRuleViewProperty(view, "body", "color").valueSpan;
+ yield checkColorCycling(container, view);
+ yield checkAlphaColorCycling(inspector, view);
+ yield checkColorCyclingPersist(inspector, view);
+});
+
+function* checkColorCycling(container, view) {
+ let valueNode = container.querySelector(".ruleview-color");
+ let win = view.styleWindow;
+
+ // Hex
+ is(valueNode.textContent, "#f00", "Color displayed as a hex value.");
+
+ let tests = [{
+ value: "hsl(0, 100%, 50%)",
+ comment: "Color displayed as an HSL value."
+ }, {
+ value: "rgb(255, 0, 0)",
+ comment: "Color displayed as an RGB value."
+ }, {
+ value: "red",
+ comment: "Color displayed as a color name."
+ }, {
+ value: "#f00",
+ comment: "Color displayed as an authored value."
+ }, {
+ value: "hsl(0, 100%, 50%)",
+ comment: "Color displayed as an HSL value again."
+ }];
+
+ for (let test of tests) {
+ yield checkSwatchShiftClick(container, win, test.value, test.comment);
+ }
+}
+
+function* checkAlphaColorCycling(inspector, view) {
+ yield selectNode("span", inspector);
+ let container = getRuleViewProperty(view, "span", "border-color").valueSpan;
+ let valueNode = container.querySelector(".ruleview-color");
+ let win = view.styleWindow;
+
+ is(valueNode.textContent, "#ff000080",
+ "Color displayed as an alpha hex value.");
+
+ let tests = [{
+ value: "hsla(0, 100%, 50%, 0.5)",
+ comment: "Color displayed as an HSLa value."
+ }, {
+ value: "rgba(255, 0, 0, 0.5)",
+ comment: "Color displayed as an RGBa value."
+ }, {
+ value: "#ff000080",
+ comment: "Color displayed as an alpha hex value again."
+ }];
+
+ for (let test of tests) {
+ yield checkSwatchShiftClick(container, win, test.value, test.comment);
+ }
+}
+
+function* checkColorCyclingPersist(inspector, view) {
+ yield selectNode("span", inspector);
+ let container = getRuleViewProperty(view, "span", "color").valueSpan;
+ let valueNode = container.querySelector(".ruleview-color");
+ let win = view.styleWindow;
+
+ is(valueNode.textContent, "blue", "Color displayed as a color name.");
+
+ yield checkSwatchShiftClick(container, win, "#00f",
+ "Color displayed as a hex value.");
+
+ // Select the body and reselect the span to see
+ // if the new color unit persisted
+ yield selectNode("body", inspector);
+ yield selectNode("span", inspector);
+
+ // We have to query for the container and the swatch because
+ // they've been re-generated
+ container = getRuleViewProperty(view, "span", "color").valueSpan;
+ valueNode = container.querySelector(".ruleview-color");
+ is(valueNode.textContent, "#00f",
+ "Color is still displayed as a hex value.");
+}
+
+function* checkSwatchShiftClick(container, win, expectedValue, comment) {
+ let swatch = container.querySelector(".ruleview-colorswatch");
+ let valueNode = container.querySelector(".ruleview-color");
+
+ let onUnitChange = swatch.once("unit-change");
+ EventUtils.synthesizeMouseAtCenter(swatch, {
+ type: "mousedown",
+ shiftKey: true
+ }, win);
+ yield onUnitChange;
+ is(valueNode.textContent, expectedValue, comment);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js b/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js
new file mode 100644
index 000000000..18522b527
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js
@@ -0,0 +1,49 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling the grid highlighter in the rule view and modifying the 'display: grid'
+// declaration.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #grid {
+ display: grid;
+ }
+ </style>
+ <div id="grid">
+ <div id="cell1">cell1</div>
+ <div id="cell2">cell2</div>
+ </div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ let highlighters = view.highlighters;
+
+ yield selectNode("#grid", inspector);
+ let container = getRuleViewProperty(view, "#grid", "display").valueSpan;
+ let gridToggle = container.querySelector(".ruleview-grid");
+
+ info("Toggling ON the CSS grid highlighter from the rule-view.");
+ let onHighlighterShown = highlighters.once("highlighter-shown");
+ gridToggle.click();
+ yield onHighlighterShown;
+
+ info("Edit the 'grid' property value to 'block'.");
+ let editor = yield focusEditableField(view, container);
+ let onHighlighterHidden = highlighters.once("highlighter-hidden");
+ let onDone = view.once("ruleview-changed");
+ editor.input.value = "block;";
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onHighlighterHidden;
+ yield onDone;
+
+ info("Check the grid highlighter and grid toggle button are hidden.");
+ gridToggle = container.querySelector(".ruleview-grid");
+ ok(!gridToggle, "Grid highlighter toggle is not visible.");
+ ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-cancel.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-cancel.js
new file mode 100644
index 000000000..af1a6fbc0
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-cancel.js
@@ -0,0 +1,46 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests editing a property name or value and escaping will revert the
+// changes and restore the original value.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: #00F;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+
+ yield focusEditableField(view, propEditor.nameSpan);
+ yield sendKeysAndWaitForFocus(view, ruleEditor.element,
+ ["DELETE", "ESCAPE"]);
+
+ is(propEditor.nameSpan.textContent, "background-color",
+ "'background-color' property name is correctly set.");
+ is((yield getComputedStyleProperty("#testid", null, "background-color")),
+ "rgb(0, 0, 255)", "#00F background color is set.");
+
+ yield focusEditableField(view, propEditor.valueSpan);
+ let onValueDeleted = view.once("ruleview-changed");
+ yield sendKeysAndWaitForFocus(view, ruleEditor.element,
+ ["DELETE", "ESCAPE"]);
+ yield onValueDeleted;
+
+ is(propEditor.valueSpan.textContent, "#00F",
+ "'#00F' property value is correctly set.");
+ is((yield getComputedStyleProperty("#testid", null, "background-color")),
+ "rgb(0, 0, 255)", "#00F background color is set.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-click.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-click.js
new file mode 100644
index 000000000..08a5ee786
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-click.js
@@ -0,0 +1,61 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the property name and value editors can be triggered when
+// clicking on the property-name, the property-value, the colon or semicolon.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ margin: 0;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testEditPropertyAndCancel(inspector, view);
+});
+
+function* testEditPropertyAndCancel(inspector, view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+
+ info("Test editor is created when clicking on property name");
+ yield focusEditableField(view, propEditor.nameSpan);
+ ok(propEditor.nameSpan.inplaceEditor, "Editor created for property name");
+ yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["ESCAPE"]);
+
+ info("Test editor is created when clicking on ':' next to property name");
+ let nameRect = propEditor.nameSpan.getBoundingClientRect();
+ yield focusEditableField(view, propEditor.nameSpan, nameRect.width + 1);
+ ok(propEditor.nameSpan.inplaceEditor, "Editor created for property name");
+ yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["ESCAPE"]);
+
+ info("Test editor is created when clicking on property value");
+ yield focusEditableField(view, propEditor.valueSpan);
+ ok(propEditor.valueSpan.inplaceEditor, "Editor created for property value");
+ // When cancelling a value edition, the text-property-editor will trigger
+ // a modification to make sure the property is back to its original value
+ // => need to wait on "ruleview-changed" to avoid unhandled promises
+ let onRuleviewChanged = view.once("ruleview-changed");
+ yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["ESCAPE"]);
+ yield onRuleviewChanged;
+
+ info("Test editor is created when clicking on ';' next to property value");
+ let valueRect = propEditor.valueSpan.getBoundingClientRect();
+ yield focusEditableField(view, propEditor.valueSpan, valueRect.width + 1);
+ ok(propEditor.valueSpan.inplaceEditor, "Editor created for property value");
+ // When cancelling a value edition, the text-property-editor will trigger
+ // a modification to make sure the property is back to its original value
+ // => need to wait on "ruleview-changed" to avoid unhandled promises
+ onRuleviewChanged = view.once("ruleview-changed");
+ yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["ESCAPE"]);
+ yield onRuleviewChanged;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js
new file mode 100644
index 000000000..8e16601c7
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js
@@ -0,0 +1,92 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test original value is correctly displayed when ESCaping out of the
+// inplace editor in the style inspector.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ color: #00F;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+// Test data format
+// {
+// value: what char sequence to type,
+// commitKey: what key to type to "commit" the change,
+// modifiers: commitKey modifiers,
+// expected: what value is expected as a result
+// }
+const testData = [
+ {
+ value: "red",
+ commitKey: "VK_ESCAPE",
+ modifiers: {},
+ expected: "#00F"
+ },
+ {
+ value: "red",
+ commitKey: "VK_RETURN",
+ modifiers: {},
+ expected: "red"
+ },
+ {
+ value: "invalid",
+ commitKey: "VK_RETURN",
+ modifiers: {},
+ expected: "invalid"
+ },
+ {
+ value: "blue",
+ commitKey: "VK_TAB", modifiers: {shiftKey: true},
+ expected: "blue"
+ }
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ for (let data of testData) {
+ yield runTestData(view, data);
+ }
+});
+
+function* runTestData(view, {value, commitKey, modifiers, expected}) {
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = idRuleEditor.rule.textProps[0].editor;
+
+ info("Focusing the inplace editor field");
+
+ let editor = yield focusEditableField(view, propEditor.valueSpan);
+ is(inplaceEditor(propEditor.valueSpan), editor,
+ "Focused editor should be the value span.");
+
+ info("Entering test data " + value);
+ let onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.sendString(value, view.styleWindow);
+ view.throttle.flush();
+ yield onRuleViewChanged;
+
+ info("Entering the commit key " + commitKey + " " + modifiers);
+ onRuleViewChanged = view.once("ruleview-changed");
+ let onBlur = once(editor.input, "blur");
+ EventUtils.synthesizeKey(commitKey, modifiers);
+ yield onBlur;
+ yield onRuleViewChanged;
+
+ if (commitKey === "VK_ESCAPE") {
+ is(propEditor.valueSpan.textContent, expected,
+ "Value is as expected: " + expected);
+ } else {
+ is(propEditor.valueSpan.textContent, expected,
+ "Value is as expected: " + expected);
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js
new file mode 100644
index 000000000..ee0a1fa74
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js
@@ -0,0 +1,89 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the computed values of a style (the shorthand expansion) are
+// properly updated after the style is changed.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ padding: 10px;
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield editAndCheck(view);
+});
+
+function* editAndCheck(view) {
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let prop = idRuleEditor.rule.textProps[0];
+ let propEditor = prop.editor;
+ let newPaddingValue = "20px";
+
+ info("Focusing the inplace editor field");
+ let editor = yield focusEditableField(view, propEditor.valueSpan);
+ is(inplaceEditor(propEditor.valueSpan), editor,
+ "Focused editor should be the value span.");
+
+ let onPropertyChange = waitForComputedStyleProperty("#testid", null,
+ "padding-top", newPaddingValue);
+ let onRefreshAfterPreview = once(view, "ruleview-changed");
+
+ info("Entering a new value");
+ EventUtils.sendString(newPaddingValue, view.styleWindow);
+
+ info("Waiting for the throttled previewValue to apply the " +
+ "changes to document");
+
+ view.throttle.flush();
+ yield onPropertyChange;
+
+ info("Waiting for ruleview-refreshed after previewValue was applied.");
+ yield onRefreshAfterPreview;
+
+ let onBlur = once(editor.input, "blur");
+
+ info("Entering the commit key and finishing edit");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ info("Waiting for blur on the field");
+ yield onBlur;
+
+ info("Waiting for the style changes to be applied");
+ yield once(view, "ruleview-changed");
+
+ let computed = prop.computed;
+ let propNames = [
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left"
+ ];
+
+ is(computed.length, propNames.length, "There should be 4 computed values");
+ propNames.forEach((propName, i) => {
+ is(computed[i].name, propName,
+ "Computed property #" + i + " has name " + propName);
+ is(computed[i].value, newPaddingValue,
+ "Computed value of " + propName + " is as expected");
+ });
+
+ propEditor.expander.click();
+ let computedDom = propEditor.computed;
+ is(computedDom.children.length, propNames.length,
+ "There should be 4 nodes in the DOM");
+ propNames.forEach((propName, i) => {
+ is(computedDom.getElementsByClassName("ruleview-propertyvalue")[i]
+ .textContent, newPaddingValue,
+ "Computed value of " + propName + " in DOM is as expected");
+ });
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
new file mode 100644
index 000000000..ca63cedcc
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
@@ -0,0 +1,280 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that increasing/decreasing values in rule view using
+// arrow keys works correctly.
+
+// Bug 1275446 - This test happen to hit the default timeout on linux32
+requestLongerTimeout(2);
+
+const TEST_URI = `
+ <style>
+ #test {
+ margin-top: 0px;
+ padding-top: 0px;
+ color: #000000;
+ background-color: #000000;
+ background: none;
+ transition: initial;
+ z-index: 0;
+ }
+ </style>
+ <div id="test"></div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#test", inspector);
+
+ yield testMarginIncrements(view);
+ yield testVariousUnitIncrements(view);
+ yield testHexIncrements(view);
+ yield testAlphaHexIncrements(view);
+ yield testRgbIncrements(view);
+ yield testShorthandIncrements(view);
+ yield testOddCases(view);
+ yield testZeroValueIncrements(view);
+});
+
+function* testMarginIncrements(view) {
+ info("Testing keyboard increments on the margin property");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let marginPropEditor = idRuleEditor.rule.textProps[0].editor;
+
+ yield runIncrementTest(marginPropEditor, view, {
+ 1: {alt: true, start: "0px", end: "0.1px", selectAll: true},
+ 2: {start: "0px", end: "1px", selectAll: true},
+ 3: {shift: true, start: "0px", end: "10px", selectAll: true},
+ 4: {down: true, alt: true, start: "0.1px", end: "0px", selectAll: true},
+ 5: {down: true, start: "0px", end: "-1px", selectAll: true},
+ 6: {down: true, shift: true, start: "0px", end: "-10px", selectAll: true},
+ 7: {pageUp: true, shift: true, start: "0px", end: "100px", selectAll: true},
+ 8: {pageDown: true, shift: true, start: "0px", end: "-100px",
+ selectAll: true},
+ 9: {start: "0", end: "1px", selectAll: true},
+ 10: {down: true, start: "0", end: "-1px", selectAll: true},
+ });
+}
+
+function* testVariousUnitIncrements(view) {
+ info("Testing keyboard increments on values with various units");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let paddingPropEditor = idRuleEditor.rule.textProps[1].editor;
+
+ yield runIncrementTest(paddingPropEditor, view, {
+ 1: {start: "0px", end: "1px", selectAll: true},
+ 2: {start: "0pt", end: "1pt", selectAll: true},
+ 3: {start: "0pc", end: "1pc", selectAll: true},
+ 4: {start: "0em", end: "1em", selectAll: true},
+ 5: {start: "0%", end: "1%", selectAll: true},
+ 6: {start: "0in", end: "1in", selectAll: true},
+ 7: {start: "0cm", end: "1cm", selectAll: true},
+ 8: {start: "0mm", end: "1mm", selectAll: true},
+ 9: {start: "0ex", end: "1ex", selectAll: true},
+ 10: {start: "0", end: "1px", selectAll: true},
+ 11: {down: true, start: "0", end: "-1px", selectAll: true},
+ });
+}
+
+function* testHexIncrements(view) {
+ info("Testing keyboard increments with hex colors");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let hexColorPropEditor = idRuleEditor.rule.textProps[2].editor;
+
+ yield runIncrementTest(hexColorPropEditor, view, {
+ 1: {start: "#CCCCCC", end: "#CDCDCD", selectAll: true},
+ 2: {shift: true, start: "#CCCCCC", end: "#DCDCDC", selectAll: true},
+ 3: {start: "#CCCCCC", end: "#CDCCCC", selection: [1, 3]},
+ 4: {shift: true, start: "#CCCCCC", end: "#DCCCCC", selection: [1, 3]},
+ 5: {start: "#FFFFFF", end: "#FFFFFF", selectAll: true},
+ 6: {down: true, shift: true, start: "#000000", end: "#000000",
+ selectAll: true}
+ });
+}
+
+function* testAlphaHexIncrements(view) {
+ info("Testing keyboard increments with alpha hex colors");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let hexColorPropEditor = idRuleEditor.rule.textProps[2].editor;
+
+ yield runIncrementTest(hexColorPropEditor, view, {
+ 1: {start: "#CCCCCCAA", end: "#CDCDCDAB", selectAll: true},
+ 2: {shift: true, start: "#CCCCCCAA", end: "#DCDCDCBA", selectAll: true},
+ 3: {start: "#CCCCCCAA", end: "#CDCCCCAA", selection: [1, 3]},
+ 4: {shift: true, start: "#CCCCCCAA", end: "#DCCCCCAA", selection: [1, 3]},
+ 5: {start: "#FFFFFFFF", end: "#FFFFFFFF", selectAll: true},
+ 6: {down: true, shift: true, start: "#00000000", end: "#00000000",
+ selectAll: true}
+ });
+}
+
+function* testRgbIncrements(view) {
+ info("Testing keyboard increments with rgb colors");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let rgbColorPropEditor = idRuleEditor.rule.textProps[3].editor;
+
+ yield runIncrementTest(rgbColorPropEditor, view, {
+ 1: {start: "rgb(0,0,0)", end: "rgb(0,1,0)", selection: [6, 7]},
+ 2: {shift: true, start: "rgb(0,0,0)", end: "rgb(0,10,0)",
+ selection: [6, 7]},
+ 3: {start: "rgb(0,255,0)", end: "rgb(0,255,0)", selection: [6, 9]},
+ 4: {shift: true, start: "rgb(0,250,0)", end: "rgb(0,255,0)",
+ selection: [6, 9]},
+ 5: {down: true, start: "rgb(0,0,0)", end: "rgb(0,0,0)", selection: [6, 7]},
+ 6: {down: true, shift: true, start: "rgb(0,5,0)", end: "rgb(0,0,0)",
+ selection: [6, 7]}
+ });
+}
+
+function* testShorthandIncrements(view) {
+ info("Testing keyboard increments within shorthand values");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let paddingPropEditor = idRuleEditor.rule.textProps[1].editor;
+
+ yield runIncrementTest(paddingPropEditor, view, {
+ 1: {start: "0px 0px 0px 0px", end: "0px 1px 0px 0px", selection: [4, 7]},
+ 2: {shift: true, start: "0px 0px 0px 0px", end: "0px 10px 0px 0px",
+ selection: [4, 7]},
+ 3: {start: "0px 0px 0px 0px", end: "1px 0px 0px 0px", selectAll: true},
+ 4: {shift: true, start: "0px 0px 0px 0px", end: "10px 0px 0px 0px",
+ selectAll: true},
+ 5: {down: true, start: "0px 0px 0px 0px", end: "0px 0px -1px 0px",
+ selection: [8, 11]},
+ 6: {down: true, shift: true, start: "0px 0px 0px 0px",
+ end: "-10px 0px 0px 0px", selectAll: true},
+ 7: {up: true, start: "0.1em .1em 0em 0em", end: "0.1em 1.1em 0em 0em",
+ selection: [6, 9]},
+ 8: {up: true, alt: true, start: "0.1em .9em 0em 0em",
+ end: "0.1em 1em 0em 0em", selection: [6, 9]},
+ 9: {up: true, shift: true, start: "0.2em .2em 0em 0em",
+ end: "0.2em 10.2em 0em 0em", selection: [6, 9]}
+ });
+}
+
+function* testOddCases(view) {
+ info("Testing some more odd cases");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let marginPropEditor = idRuleEditor.rule.textProps[0].editor;
+
+ yield runIncrementTest(marginPropEditor, view, {
+ 1: {start: "98.7%", end: "99.7%", selection: [3, 3]},
+ 2: {alt: true, start: "98.7%", end: "98.8%", selection: [3, 3]},
+ 3: {start: "0", end: "1px"},
+ 4: {down: true, start: "0", end: "-1px"},
+ 5: {start: "'a=-1'", end: "'a=0'", selection: [4, 4]},
+ 6: {start: "0 -1px", end: "0 0px", selection: [2, 2]},
+ 7: {start: "url(-1)", end: "url(-1)", selection: [4, 4]},
+ 8: {start: "url('test1.1.png')", end: "url('test1.2.png')",
+ selection: [11, 11]},
+ 9: {start: "url('test1.png')", end: "url('test2.png')", selection: [9, 9]},
+ 10: {shift: true, start: "url('test1.1.png')", end: "url('test11.1.png')",
+ selection: [9, 9]},
+ 11: {down: true, start: "url('test-1.png')", end: "url('test-2.png')",
+ selection: [9, 11]},
+ 12: {start: "url('test1.1.png')", end: "url('test1.2.png')",
+ selection: [11, 12]},
+ 13: {down: true, alt: true, start: "url('test-0.png')",
+ end: "url('test--0.1.png')", selection: [10, 11]},
+ 14: {alt: true, start: "url('test--0.1.png')", end: "url('test-0.png')",
+ selection: [10, 14]}
+ });
+}
+
+function* testZeroValueIncrements(view) {
+ info("Testing a valid unit is added when incrementing from 0");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+ let backgroundPropEditor = idRuleEditor.rule.textProps[4].editor;
+ yield runIncrementTest(backgroundPropEditor, view, {
+ 1: { start: "url(test-0.png) no-repeat 0 0",
+ end: "url(test-0.png) no-repeat 1px 0", selection: [26, 26] },
+ 2: { start: "url(test-0.png) no-repeat 0 0",
+ end: "url(test-0.png) no-repeat 0 1px", selection: [28, 28] },
+ 3: { start: "url(test-0.png) no-repeat center/0",
+ end: "url(test-0.png) no-repeat center/1px", selection: [34, 34] },
+ 4: { start: "url(test-0.png) no-repeat 0 0",
+ end: "url(test-1.png) no-repeat 0 0", selection: [10, 10] },
+ 5: { start: "linear-gradient(0, red 0, blue 0)",
+ end: "linear-gradient(1deg, red 0, blue 0)", selection: [17, 17] },
+ 6: { start: "linear-gradient(1deg, red 0, blue 0)",
+ end: "linear-gradient(1deg, red 1px, blue 0)", selection: [27, 27] },
+ 7: { start: "linear-gradient(1deg, red 0, blue 0)",
+ end: "linear-gradient(1deg, red 0, blue 1px)", selection: [35, 35] },
+ });
+
+ let transitionPropEditor = idRuleEditor.rule.textProps[5].editor;
+ yield runIncrementTest(transitionPropEditor, view, {
+ 1: { start: "all 0 ease-out", end: "all 1s ease-out", selection: [5, 5] },
+ 2: { start: "margin 4s, color 0",
+ end: "margin 4s, color 1s", selection: [18, 18] },
+ });
+
+ let zIndexPropEditor = idRuleEditor.rule.textProps[6].editor;
+ yield runIncrementTest(zIndexPropEditor, view, {
+ 1: {start: "0", end: "1", selection: [1, 1]},
+ });
+}
+
+function* runIncrementTest(propertyEditor, view, tests) {
+ let editor = yield focusEditableField(view, propertyEditor.valueSpan);
+
+ for (let test in tests) {
+ yield testIncrement(editor, tests[test], view, propertyEditor);
+ }
+
+ // Blur the field to put back the UI in its initial state (and avoid pending
+ // requests when the test ends).
+ let onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ view.throttle.flush();
+ yield onRuleViewChanged;
+}
+
+function* testIncrement(editor, options, view) {
+ editor.input.value = options.start;
+ let input = editor.input;
+
+ if (options.selectAll) {
+ input.select();
+ } else if (options.selection) {
+ input.setSelectionRange(options.selection[0], options.selection[1]);
+ }
+
+ is(input.value, options.start, "Value initialized at " + options.start);
+
+ let onRuleViewChanged = view.once("ruleview-changed");
+ let onKeyUp = once(input, "keyup");
+
+ let key;
+ key = options.down ? "VK_DOWN" : "VK_UP";
+ if (options.pageDown) {
+ key = "VK_PAGE_DOWN";
+ } else if (options.pageUp) {
+ key = "VK_PAGE_UP";
+ }
+
+ EventUtils.synthesizeKey(key, {altKey: options.alt, shiftKey: options.shift},
+ view.styleWindow);
+
+ yield onKeyUp;
+
+ // Only expect a change if the value actually changed!
+ if (options.start !== options.end) {
+ view.throttle.flush();
+ yield onRuleViewChanged;
+ }
+
+ is(input.value, options.end, "Value changed to " + options.end);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js
new file mode 100644
index 000000000..b4a86c194
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js
@@ -0,0 +1,89 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Checking properties orders and overrides in the rule-view.
+
+const TEST_URI = "<style>#testid {}</style><div id='testid'>Styled Node</div>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let elementStyle = view._elementStyle;
+ let elementRule = elementStyle.rules[1];
+
+ info("Checking rules insertion order and checking the applied style");
+ let firstProp = yield addProperty(view, 1, "background-color", "green");
+ let secondProp = yield addProperty(view, 1, "background-color", "blue");
+
+ is(elementRule.textProps[0], firstProp,
+ "Rules should be in addition order.");
+ is(elementRule.textProps[1], secondProp,
+ "Rules should be in addition order.");
+
+ // rgb(0, 0, 255) = blue
+ is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+ "Second property should have been used.");
+
+ info("Removing the second property and checking the applied style again");
+ yield removeProperty(view, secondProp);
+ // rgb(0, 128, 0) = green
+ is((yield getValue("#testid", "background-color")), "rgb(0, 128, 0)",
+ "After deleting second property, first should be used.");
+
+ info("Creating a new second property and checking that the insertion order " +
+ "is still the same");
+
+ secondProp = yield addProperty(view, 1, "background-color", "blue");
+
+ is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+ "New property should be used.");
+ is(elementRule.textProps[0], firstProp,
+ "Rules shouldn't have switched places.");
+ is(elementRule.textProps[1], secondProp,
+ "Rules shouldn't have switched places.");
+
+ info("Disabling the second property and checking the applied style");
+ yield togglePropStatus(view, secondProp);
+
+ is((yield getValue("#testid", "background-color")), "rgb(0, 128, 0)",
+ "After disabling second property, first value should be used");
+
+ info("Disabling the first property too and checking the applied style");
+ yield togglePropStatus(view, firstProp);
+
+ is((yield getValue("#testid", "background-color")), "transparent",
+ "After disabling both properties, value should be empty.");
+
+ info("Re-enabling the second propertyt and checking the applied style");
+ yield togglePropStatus(view, secondProp);
+
+ is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+ "Value should be set correctly after re-enabling");
+
+ info("Re-enabling the first property and checking the insertion order " +
+ "is still respected");
+ yield togglePropStatus(view, firstProp);
+
+ is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+ "Re-enabling an earlier property shouldn't make it override " +
+ "a later property.");
+ is(elementRule.textProps[0], firstProp,
+ "Rules shouldn't have switched places.");
+ is(elementRule.textProps[1], secondProp,
+ "Rules shouldn't have switched places.");
+ info("Modifying the first property and checking the applied style");
+ yield setProperty(view, firstProp, "purple");
+
+ is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+ "Modifying an earlier property shouldn't override a later property.");
+});
+
+function* getValue(selector, propName) {
+ let value = yield getComputedStyleProperty(selector, null, propName);
+ return value;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_01.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_01.js
new file mode 100644
index 000000000..0aed2f5c8
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_01.js
@@ -0,0 +1,67 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests removing a property by clearing the property name and pressing the
+// return key, and checks if the focus is moved to the appropriate editable
+// field.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: #00F;
+ color: #00F;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Getting the first property in the #testid rule");
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let prop = rule.textProps[0];
+
+ info("Deleting the name of that property to remove the property");
+ yield removeProperty(view, prop, false);
+
+ let newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "background-color"
+ });
+ is(newValue, "", "background-color should have been unset.");
+
+ info("Getting the new first property in the rule");
+ prop = rule.textProps[0];
+
+ let editor = inplaceEditor(view.styleDocument.activeElement);
+ is(inplaceEditor(prop.editor.nameSpan), editor,
+ "Focus should have moved to the next property name");
+
+ info("Deleting the name of that property to remove the property");
+ view.styleDocument.activeElement.blur();
+ yield removeProperty(view, prop, false);
+
+ newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "color"
+ });
+ is(newValue, "", "color should have been unset.");
+
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ is(inplaceEditor(rule.editor.newPropSpan), editor,
+ "Focus should have moved to the new property span");
+ is(rule.textProps.length, 0,
+ "All properties should have been removed.");
+ is(rule.editor.propertyList.children.length, 1,
+ "Should have the new property span.");
+
+ view.styleDocument.activeElement.blur();
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_02.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_02.js
new file mode 100644
index 000000000..5690e7c2d
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_02.js
@@ -0,0 +1,67 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests removing a property by clearing the property value and pressing the
+// return key, and checks if the focus is moved to the appropriate editable
+// field.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: #00F;
+ color: #00F;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Getting the first property in the rule");
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let prop = rule.textProps[0];
+
+ info("Clearing the property value");
+ yield setProperty(view, prop, null, false);
+
+ let newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "background-color"
+ });
+ is(newValue, "", "background-color should have been unset.");
+
+ info("Getting the new first property in the rule");
+ prop = rule.textProps[0];
+
+ let editor = inplaceEditor(view.styleDocument.activeElement);
+ is(inplaceEditor(prop.editor.nameSpan), editor,
+ "Focus should have moved to the next property name");
+ view.styleDocument.activeElement.blur();
+
+ info("Clearing the property value");
+ yield setProperty(view, prop, null, false);
+
+ newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "color"
+ });
+ is(newValue, "", "color should have been unset.");
+
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ is(inplaceEditor(rule.editor.newPropSpan), editor,
+ "Focus should have moved to the new property span");
+ is(rule.textProps.length, 0,
+ "All properties should have been removed.");
+ is(rule.editor.propertyList.children.length, 1,
+ "Should have the new property span.");
+
+ view.styleDocument.activeElement.blur();
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_03.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_03.js
new file mode 100644
index 000000000..21a1063c2
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_03.js
@@ -0,0 +1,83 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests removing a property by clearing the property name and pressing shift
+// and tab keys, and checks if the focus is moved to the appropriate editable
+// field.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: #00F;
+ color: #00F;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Getting the second property in the rule");
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let prop = rule.textProps[1];
+
+ info("Clearing the property value and pressing shift-tab");
+ let editor = yield focusEditableField(view, prop.editor.valueSpan);
+ let onValueDone = view.once("ruleview-changed");
+ editor.input.value = "";
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow);
+ yield onValueDone;
+
+ let newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "color"
+ });
+ is(newValue, "", "color should have been unset.");
+ is(prop.editor.valueSpan.textContent, "",
+ "'' property value is correctly set.");
+
+ info("Pressing shift-tab again to focus the previous property value");
+ let onValueFocused = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow);
+ yield onValueFocused;
+
+ info("Getting the first property in the rule");
+ prop = rule.textProps[0];
+
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ is(inplaceEditor(prop.editor.valueSpan), editor,
+ "Focus should have moved to the previous property value");
+
+ info("Pressing shift-tab again to focus the property name");
+ let onNameFocused = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow);
+ yield onNameFocused;
+
+ info("Removing the name and pressing shift-tab to focus the selector");
+ let onNameDeleted = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow);
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow);
+ yield onNameDeleted;
+
+ newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "background-color"
+ });
+ is(newValue, "", "background-color should have been unset.");
+
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ is(inplaceEditor(rule.editor.selectorText), editor,
+ "Focus should have moved to the selector text.");
+ is(rule.textProps.length, 0,
+ "All properties should have been removed.");
+ ok(!rule.editor.propertyList.hasChildNodes(),
+ "Should not have any properties.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_01.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_01.js
new file mode 100644
index 000000000..6f4c49e20
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_01.js
@@ -0,0 +1,93 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing adding new properties via the inplace-editors in the rule
+// view.
+// FIXME: some of the inplace-editor focus/blur/commit/revert stuff
+// should be factored out in head.js
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ color: red;
+ background-color: blue;
+ }
+ .testclass, .unmatched {
+ background-color: green;
+ }
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>
+ <div id="testid2">Styled Node</div>
+`;
+
+var BACKGROUND_IMAGE_URL = 'url("' + URL_ROOT + 'doc_test_image.png")';
+
+var TEST_DATA = [
+ { name: "border-color", value: "red", isValid: true },
+ { name: "background-image", value: BACKGROUND_IMAGE_URL, isValid: true },
+ { name: "border", value: "solid 1px foo", isValid: false },
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ for (let {name, value, isValid} of TEST_DATA) {
+ yield testEditProperty(view, rule, name, value, isValid);
+ }
+});
+
+function* testEditProperty(view, rule, name, value, isValid) {
+ info("Test editing existing property name/value fields");
+
+ let doc = rule.editor.doc;
+ let prop = rule.textProps[0];
+
+ info("Focusing an existing property name in the rule-view");
+ let editor = yield focusEditableField(view, prop.editor.nameSpan, 32, 1);
+
+ is(inplaceEditor(prop.editor.nameSpan), editor,
+ "The property name editor got focused");
+ let input = editor.input;
+
+ info("Entering a new property name, including : to commit and " +
+ "focus the value");
+ let onValueFocus = once(rule.editor.element, "focus", true);
+ let onNameDone = view.once("ruleview-changed");
+ EventUtils.sendString(name + ":", doc.defaultView);
+ yield onValueFocus;
+ yield onNameDone;
+
+ // Getting the value editor after focus
+ editor = inplaceEditor(doc.activeElement);
+ input = editor.input;
+ is(inplaceEditor(prop.editor.valueSpan), editor, "Focus moved to the value.");
+
+ info("Entering a new value, including ; to commit and blur the value");
+ let onValueDone = view.once("ruleview-changed");
+ let onBlur = once(input, "blur");
+ EventUtils.sendString(value + ";", doc.defaultView);
+ yield onBlur;
+ yield onValueDone;
+
+ is(prop.editor.isValid(), isValid,
+ value + " is " + isValid ? "valid" : "invalid");
+
+ info("Checking that the style property was changed on the content page");
+ let propValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name
+ });
+
+ if (isValid) {
+ is(propValue, value, name + " should have been set.");
+ } else {
+ isnot(propValue, value, name + " shouldn't have been set.");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
new file mode 100644
index 000000000..7e6315236
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
@@ -0,0 +1,133 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test several types of rule-view property edition
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ background-color: blue;
+ }
+ .testclass, .unmatched {
+ background-color: green;
+ }
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>
+ <div id="testid2">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ yield testEditProperty(inspector, view);
+ yield testDisableProperty(inspector, view);
+ yield testPropertyStillMarkedDirty(inspector, view);
+});
+
+function* testEditProperty(inspector, ruleView) {
+ let idRule = getRuleViewRuleEditor(ruleView, 1).rule;
+ let prop = idRule.textProps[0];
+
+ let editor = yield focusEditableField(ruleView, prop.editor.nameSpan);
+ let input = editor.input;
+ is(inplaceEditor(prop.editor.nameSpan), editor,
+ "Next focused editor should be the name editor.");
+
+ ok(input.selectionStart === 0 && input.selectionEnd === input.value.length,
+ "Editor contents are selected.");
+
+ // Try clicking on the editor's input again, shouldn't cause trouble
+ // (see bug 761665).
+ EventUtils.synthesizeMouse(input, 1, 1, {}, ruleView.styleWindow);
+ input.select();
+
+ info("Entering property name \"border-color\" followed by a colon to " +
+ "focus the value");
+ let onNameDone = ruleView.once("ruleview-changed");
+ let onFocus = once(idRule.editor.element, "focus", true);
+ EventUtils.sendString("border-color:", ruleView.styleWindow);
+ yield onFocus;
+ yield onNameDone;
+
+ info("Verifying that the focused field is the valueSpan");
+ editor = inplaceEditor(ruleView.styleDocument.activeElement);
+ input = editor.input;
+ is(inplaceEditor(prop.editor.valueSpan), editor,
+ "Focus should have moved to the value.");
+ ok(input.selectionStart === 0 && input.selectionEnd === input.value.length,
+ "Editor contents are selected.");
+
+ info("Entering a value following by a semi-colon to commit it");
+ let onBlur = once(editor.input, "blur");
+ // Use sendChar() to pass each character as a string so that we can test
+ // prop.editor.warning.hidden after each character.
+ for (let ch of "red;") {
+ let onPreviewDone = ruleView.once("ruleview-changed");
+ EventUtils.sendChar(ch, ruleView.styleWindow);
+ ruleView.throttle.flush();
+ yield onPreviewDone;
+ is(prop.editor.warning.hidden, true,
+ "warning triangle is hidden or shown as appropriate");
+ }
+ yield onBlur;
+
+ let newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "border-color"
+ });
+ is(newValue, "red", "border-color should have been set.");
+
+ ruleView.styleDocument.activeElement.blur();
+ yield addProperty(ruleView, 1, "color", "red", ";");
+
+ let props = ruleView.element.querySelectorAll(".ruleview-property");
+ for (let i = 0; i < props.length; i++) {
+ is(props[i].hasAttribute("dirty"), i <= 1,
+ "props[" + i + "] marked dirty as appropriate");
+ }
+}
+
+function* testDisableProperty(inspector, ruleView) {
+ let idRule = getRuleViewRuleEditor(ruleView, 1).rule;
+ let prop = idRule.textProps[0];
+
+ info("Disabling a property");
+ yield togglePropStatus(ruleView, prop);
+
+ let newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "border-color"
+ });
+ is(newValue, "", "Border-color should have been unset.");
+
+ info("Enabling the property again");
+ yield togglePropStatus(ruleView, prop);
+
+ newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "border-color"
+ });
+ is(newValue, "red", "Border-color should have been reset.");
+}
+
+function* testPropertyStillMarkedDirty(inspector, ruleView) {
+ // Select an unstyled node.
+ yield selectNode("#testid2", inspector);
+
+ // Select the original node again.
+ yield selectNode("#testid", inspector);
+
+ let props = ruleView.element.querySelectorAll(".ruleview-property");
+ for (let i = 0; i < props.length; i++) {
+ is(props[i].hasAttribute("dirty"), i <= 1,
+ "props[" + i + "] marked dirty as appropriate");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_03.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_03.js
new file mode 100644
index 000000000..a5771b41e
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_03.js
@@ -0,0 +1,50 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that emptying out an existing value removes the property and
+// doesn't cause any other issues. See also Bug 1150780.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ color: red;
+ background-color: blue;
+ font-size: 12px;
+ }
+ .testclass, .unmatched {
+ background-color: green;
+ }
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>
+ <div id="testid2">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[1].editor;
+
+ yield focusEditableField(view, propEditor.valueSpan);
+
+ info("Deleting all the text out of a value field");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ yield sendKeysAndWaitForFocus(view, ruleEditor.element,
+ ["DELETE", "RETURN"]);
+ yield onRuleViewChanged;
+
+ info("Pressing enter a couple times to cycle through editors");
+ yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["RETURN"]);
+ onRuleViewChanged = view.once("ruleview-changed");
+ yield sendKeysAndWaitForFocus(view, ruleEditor.element, ["RETURN"]);
+ yield onRuleViewChanged;
+
+ isnot(ruleEditor.rule.textProps[1].editor.nameSpan.style.display, "none",
+ "The name span is visible");
+ is(ruleEditor.rule.textProps.length, 2, "Correct number of props");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_04.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_04.js
new file mode 100644
index 000000000..7460db4cd
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_04.js
@@ -0,0 +1,85 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that a disabled property remains disabled when the escaping out of
+// the property editor.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let prop = rule.textProps[0];
+
+ info("Disabling a property");
+ yield togglePropStatus(view, prop);
+
+ let newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "background-color"
+ });
+ is(newValue, "", "background-color should have been unset.");
+
+ yield testEditDisableProperty(view, rule, prop, "name", "VK_ESCAPE");
+ yield testEditDisableProperty(view, rule, prop, "value", "VK_ESCAPE");
+ yield testEditDisableProperty(view, rule, prop, "value", "VK_TAB");
+ yield testEditDisableProperty(view, rule, prop, "value", "VK_RETURN");
+});
+
+function* testEditDisableProperty(view, rule, prop, fieldType, commitKey) {
+ let field = fieldType === "name" ? prop.editor.nameSpan
+ : prop.editor.valueSpan;
+
+ let editor = yield focusEditableField(view, field);
+
+ ok(!prop.editor.element.classList.contains("ruleview-overridden"),
+ "property is not overridden.");
+ is(prop.editor.enable.style.visibility, "hidden",
+ "property enable checkbox is hidden.");
+
+ let newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "background-color"
+ });
+ is(newValue, "", "background-color should remain unset.");
+
+ let onChangeDone;
+ if (fieldType === "value") {
+ onChangeDone = view.once("ruleview-changed");
+ }
+
+ let onBlur = once(editor.input, "blur");
+ EventUtils.synthesizeKey(commitKey, {}, view.styleWindow);
+ yield onBlur;
+ yield onChangeDone;
+
+ ok(!prop.enabled, "property is disabled.");
+ ok(prop.editor.element.classList.contains("ruleview-overridden"),
+ "property is overridden.");
+ is(prop.editor.enable.style.visibility, "visible",
+ "property enable checkbox is visible.");
+ ok(!prop.editor.enable.getAttribute("checked"),
+ "property enable checkbox is not checked.");
+
+ newValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: "background-color"
+ });
+ is(newValue, "", "background-color should remain unset.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_05.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_05.js
new file mode 100644
index 000000000..3d37c81d5
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_05.js
@@ -0,0 +1,77 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that a disabled property is re-enabled if the property name or value is
+// modified
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let prop = rule.textProps[0];
+
+ info("Disabling background-color property");
+ yield togglePropStatus(view, prop);
+
+ let newValue = yield getRulePropertyValue("background-color");
+ is(newValue, "", "background-color should have been unset.");
+
+ info("Entering a new property name, including : to commit and " +
+ "focus the value");
+
+ yield focusEditableField(view, prop.editor.nameSpan);
+ let onNameDone = view.once("ruleview-changed");
+ EventUtils.sendString("border-color:", view.styleWindow);
+ yield onNameDone;
+
+ info("Escape editing the property value");
+ let onValueDone = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ yield onValueDone;
+
+ newValue = yield getRulePropertyValue("border-color");
+ is(newValue, "blue", "border-color should have been set.");
+
+ ok(prop.enabled, "border-color property is enabled.");
+ ok(!prop.editor.element.classList.contains("ruleview-overridden"),
+ "border-color is not overridden");
+
+ info("Disabling border-color property");
+ yield togglePropStatus(view, prop);
+
+ newValue = yield getRulePropertyValue("border-color");
+ is(newValue, "", "border-color should have been unset.");
+
+ info("Enter a new property value for the border-color property");
+ yield setProperty(view, prop, "red");
+
+ newValue = yield getRulePropertyValue("border-color");
+ is(newValue, "red", "new border-color should have been set.");
+
+ ok(prop.enabled, "border-color property is enabled.");
+ ok(!prop.editor.element.classList.contains("ruleview-overridden"),
+ "border-color is not overridden");
+});
+
+function* getRulePropertyValue(name) {
+ let propValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: name
+ });
+ return propValue;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_06.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_06.js
new file mode 100644
index 000000000..95211f1d0
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_06.js
@@ -0,0 +1,52 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that editing a property's priority is behaving correctly, and disabling
+// and editing the property will re-enable the property.
+
+const TEST_URI = `
+ <style type='text/css'>
+ body {
+ background-color: green !important;
+ }
+ body {
+ background-color: red;
+ }
+ </style>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("body", inspector);
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let prop = rule.textProps[0];
+
+ is((yield getComputedStyleProperty("body", null, "background-color")),
+ "rgb(0, 128, 0)", "green background color is set.");
+
+ yield setProperty(view, prop, "red !important");
+
+ is(prop.editor.valueSpan.textContent, "red !important",
+ "'red !important' property value is correctly set.");
+ is((yield getComputedStyleProperty("body", null, "background-color")),
+ "rgb(255, 0, 0)", "red background color is set.");
+
+ info("Disabling red background color property");
+ yield togglePropStatus(view, prop);
+
+ is((yield getComputedStyleProperty("body", null, "background-color")),
+ "rgb(0, 128, 0)", "green background color is set.");
+
+ yield setProperty(view, prop, "red");
+
+ is(prop.editor.valueSpan.textContent, "red",
+ "'red' property value is correctly set.");
+ ok(prop.enabled, "red background-color property is enabled.");
+ is((yield getComputedStyleProperty("body", null, "background-color")),
+ "rgb(0, 128, 0)", "green background color is set.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_07.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_07.js
new file mode 100644
index 000000000..40314819f
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_07.js
@@ -0,0 +1,50 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that adding multiple values will enable the property even if the
+// property does not change, and that the extra values are added correctly.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: #f00;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let prop = rule.textProps[0];
+
+ info("Disabling red background color property");
+ yield togglePropStatus(view, prop);
+ ok(!prop.enabled, "red background-color property is disabled.");
+
+ let editor = yield focusEditableField(view, prop.editor.valueSpan);
+ let onDone = view.once("ruleview-changed");
+ editor.input.value = "red; color: red;";
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onDone;
+
+ is(prop.editor.valueSpan.textContent, "red",
+ "'red' property value is correctly set.");
+ ok(prop.enabled, "red background-color property is enabled.");
+ is((yield getComputedStyleProperty("#testid", null, "background-color")),
+ "rgb(255, 0, 0)", "red background color is set.");
+
+ let propEditor = rule.textProps[1].editor;
+ is(propEditor.nameSpan.textContent, "color",
+ "new 'color' property name is correctly set.");
+ is(propEditor.valueSpan.textContent, "red",
+ "new 'red' property value is correctly set.");
+ is((yield getComputedStyleProperty("#testid", null, "color")),
+ "rgb(255, 0, 0)", "red color is set.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_08.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_08.js
new file mode 100644
index 000000000..1becd40d9
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_08.js
@@ -0,0 +1,57 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that renaming a property works.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ color: #FFF;
+ }
+ </style>
+ <div style='color: red' id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Get the color property editor");
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+ is(ruleEditor.rule.textProps[0].name, "color");
+
+ info("Focus the property name field");
+ yield focusEditableField(ruleEditor.ruleView, propEditor.nameSpan, 32, 1);
+
+ info("Rename the property to background-color");
+ // Expect 3 events: the value editor being focused, the ruleview-changed event
+ // which signals that the new value has been previewed (fires once when the
+ // value gets focused), and the markupmutation event since we're modifying an
+ // inline style.
+ let onValueFocus = once(ruleEditor.element, "focus", true);
+ let onRuleViewChanged = ruleEditor.ruleView.once("ruleview-changed");
+ let onMutation = inspector.once("markupmutation");
+ EventUtils.sendString("background-color:", ruleEditor.doc.defaultView);
+ yield onValueFocus;
+ yield onRuleViewChanged;
+ yield onMutation;
+
+ is(ruleEditor.rule.textProps[0].name, "background-color");
+ yield waitForComputedStyleProperty("#testid", null, "background-color",
+ "rgb(255, 0, 0)");
+
+ is((yield getComputedStyleProperty("#testid", null, "color")),
+ "rgb(255, 255, 255)", "color is white");
+
+ // The value field is still focused. Blur it now and wait for the
+ // ruleview-changed event to avoid pending requests.
+ onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield onRuleViewChanged;
+});
+
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_09.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_09.js
new file mode 100644
index 000000000..51f714021
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_09.js
@@ -0,0 +1,69 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a newProperty editor is only created if no other editor was
+// previously displayed.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testClickOnEmptyAreaToCloseEditor(inspector, view);
+});
+
+function synthesizeMouseOnEmptyArea(ruleEditor, view) {
+ // any text property editor will do
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+ let valueContainer = propEditor.valueContainer;
+ let valueRect = valueContainer.getBoundingClientRect();
+ // click right next to the ";" at the end of valueContainer
+ EventUtils.synthesizeMouse(valueContainer, valueRect.width + 1, 1, {},
+ view.styleWindow);
+}
+
+function* testClickOnEmptyAreaToCloseEditor(inspector, view) {
+ // Start at the beginning: start to add a rule to the element's style
+ // declaration, add some text, then press escape.
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+
+ info("Create a property value editor");
+ let editor = yield focusEditableField(view, propEditor.valueSpan);
+ ok(editor.input, "The inplace-editor field is ready");
+
+ info("Close the property value editor by clicking on an empty area " +
+ "in the rule editor");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ let onBlur = once(editor.input, "blur");
+ synthesizeMouseOnEmptyArea(ruleEditor, view);
+ yield onBlur;
+ yield onRuleViewChanged;
+ ok(!view.isEditing, "No inplace editor should be displayed in the ruleview");
+
+ info("Create new newProperty editor by clicking again on the empty area");
+ let onFocus = once(ruleEditor.element, "focus", true);
+ synthesizeMouseOnEmptyArea(ruleEditor, view);
+ yield onFocus;
+ editor = inplaceEditor(ruleEditor.element.ownerDocument.activeElement);
+ is(inplaceEditor(ruleEditor.newPropSpan), editor,
+ "New property editor was created");
+
+ info("Close the newProperty editor by clicking again on the empty area");
+ onBlur = once(editor.input, "blur");
+ synthesizeMouseOnEmptyArea(ruleEditor, view);
+ yield onBlur;
+
+ ok(!view.isEditing, "No inplace editor should be displayed in the ruleview");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector-click-on-scrollbar.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector-click-on-scrollbar.js
new file mode 100644
index 000000000..1846df60d
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector-click-on-scrollbar.js
@@ -0,0 +1,88 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing ruleview inplace-editor is not blurred when clicking on the ruleview
+// container scrollbar.
+
+const TEST_URI = `
+ <style type="text/css">
+ div.testclass {
+ color: black;
+ }
+ .a {
+ color: #aaa;
+ }
+ .b {
+ color: #bbb;
+ }
+ .c {
+ color: #ccc;
+ }
+ .d {
+ color: #ddd;
+ }
+ .e {
+ color: #eee;
+ }
+ .f {
+ color: #fff;
+ }
+ </style>
+ <div class="testclass a b c d e f">Styled Node</div>
+`;
+
+add_task(function* () {
+ info("Toolbox height should be small enough to force scrollbars to appear");
+ yield new Promise(done => {
+ let options = {"set": [
+ ["devtools.toolbox.footer.height", 200],
+ ]};
+ SpecialPowers.pushPrefEnv(options, done);
+ });
+
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".testclass", inspector);
+
+ info("Check we have an overflow on the ruleview container.");
+ let container = view.element;
+ let hasScrollbar = container.offsetHeight < container.scrollHeight;
+ ok(hasScrollbar, "The rule view container should have a vertical scrollbar.");
+
+ info("Focusing an existing selector name in the rule-view.");
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+ is(inplaceEditor(ruleEditor.selectorText), editor,
+ "The selector editor is focused.");
+
+ info("Click on the scrollbar element.");
+ yield clickOnRuleviewScrollbar(view);
+
+ is(editor.input, view.styleDocument.activeElement,
+ "The editor input should still be focused.");
+
+ info("Check a new value can still be committed in the editable field");
+ let newValue = ".testclass.a.b.c.d.e.f";
+ let onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Enter new value and commit.");
+ editor.input.value = newValue;
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+ ok(getRuleViewRule(view, newValue), "Rule with '" + newValue + " 'exists.");
+});
+
+function* clickOnRuleviewScrollbar(view) {
+ let container = view.element.parentNode;
+ let onScroll = once(container, "scroll");
+ let rect = container.getBoundingClientRect();
+ // click 5 pixels before the bottom-right corner should hit the scrollbar
+ EventUtils.synthesizeMouse(container, rect.width - 5, rect.height - 5,
+ {}, view.styleWindow);
+ yield onScroll;
+
+ ok(true, "The rule view container scrolled after clicking on the scrollbar.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector-click.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector-click.js
new file mode 100644
index 000000000..7a3b6d467
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector-click.js
@@ -0,0 +1,63 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing selector inplace-editor remains available and focused after clicking
+// in its input.
+
+const TEST_URI = `
+ <style type="text/css">
+ .testclass {
+ text-align: center;
+ }
+ </style>
+ <div class="testclass">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".testclass", inspector);
+ yield testClickOnSelectorEditorInput(view);
+});
+
+function* testClickOnSelectorEditorInput(view) {
+ info("Test clicking inside the selector editor input");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+ let editorInput = editor.input;
+ is(inplaceEditor(ruleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Click inside the editor input");
+ let onClick = once(editorInput, "click");
+ EventUtils.synthesizeMouse(editor.input, 2, 1, {}, view.styleWindow);
+ yield onClick;
+ is(editor.input, view.styleDocument.activeElement,
+ "The editor input should still be focused");
+ ok(!ruleEditor.newPropSpan, "No newProperty editor was created");
+
+ info("Doubleclick inside the editor input");
+ let onDoubleClick = once(editorInput, "dblclick");
+ EventUtils.synthesizeMouse(editor.input, 2, 1, { clickCount: 2 },
+ view.styleWindow);
+ yield onDoubleClick;
+ is(editor.input, view.styleDocument.activeElement,
+ "The editor input should still be focused");
+ ok(!ruleEditor.newPropSpan, "No newProperty editor was created");
+
+ info("Click outside the editor input");
+ let onBlur = once(editorInput, "blur");
+ let rect = editorInput.getBoundingClientRect();
+ EventUtils.synthesizeMouse(editorInput, rect.width + 5, rect.height / 2, {},
+ view.styleWindow);
+ yield onBlur;
+
+ isnot(editorInput, view.styleDocument.activeElement,
+ "The editor input should no longer be focused");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector-commit.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector-commit.js
new file mode 100644
index 000000000..f7058371f
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector-commit.js
@@ -0,0 +1,117 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test selector value is correctly displayed when committing the inplace editor
+// with ENTER, ESC, SHIFT+TAB and TAB
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid1 {
+ text-align: center;
+ }
+ #testid2 {
+ text-align: center;
+ }
+ #testid3 {
+ }
+ </style>
+ <div id='testid1'>Styled Node</div>
+ <div id='testid2'>Styled Node</div>
+ <div id='testid3'>Styled Node</div>
+`;
+
+const TEST_DATA = [
+ {
+ node: "#testid1",
+ value: ".testclass",
+ commitKey: "VK_ESCAPE",
+ modifiers: {},
+ expected: "#testid1",
+
+ },
+ {
+ node: "#testid1",
+ value: ".testclass1",
+ commitKey: "VK_RETURN",
+ modifiers: {},
+ expected: ".testclass1"
+ },
+ {
+ node: "#testid2",
+ value: ".testclass2",
+ commitKey: "VK_TAB",
+ modifiers: {},
+ expected: ".testclass2"
+ },
+ {
+ node: "#testid3",
+ value: ".testclass3",
+ commitKey: "VK_TAB",
+ modifiers: {shiftKey: true},
+ expected: ".testclass3"
+ }
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let { inspector, view } = yield openRuleView();
+
+ for (let data of TEST_DATA) {
+ yield runTestData(inspector, view, data);
+ }
+});
+
+function* runTestData(inspector, view, data) {
+ let {node, value, commitKey, modifiers, expected} = data;
+
+ info("Updating " + node + " to " + value + " and committing with " +
+ commitKey + ". Expecting: " + expected);
+
+ info("Selecting the test element");
+ yield selectNode(node, inspector);
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, idRuleEditor.selectorText);
+ is(inplaceEditor(idRuleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Enter the new selector value: " + value);
+ editor.input.value = value;
+
+ info("Entering the commit key " + commitKey + " " + modifiers);
+ EventUtils.synthesizeKey(commitKey, modifiers);
+
+ let activeElement = view.styleDocument.activeElement;
+
+ if (commitKey === "VK_ESCAPE") {
+ is(idRuleEditor.rule.selectorText, expected,
+ "Value is as expected: " + expected);
+ is(idRuleEditor.isEditing, false, "Selector is not being edited.");
+ is(idRuleEditor.selectorText, activeElement,
+ "Focus is on selector span.");
+ return;
+ }
+
+ yield once(view, "ruleview-changed");
+
+ ok(getRuleViewRule(view, expected),
+ "Rule with " + expected + " selector exists.");
+
+ if (modifiers.shiftKey) {
+ idRuleEditor = getRuleViewRuleEditor(view, 0);
+ }
+
+ let rule = idRuleEditor.rule;
+ if (rule.textProps.length > 0) {
+ is(inplaceEditor(rule.textProps[0].editor.nameSpan).input, activeElement,
+ "Focus is on the first property name span.");
+ } else {
+ is(inplaceEditor(idRuleEditor.newPropSpan).input, activeElement,
+ "Focus is on the new property span.");
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_01.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_01.js
new file mode 100644
index 000000000..af228094b
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_01.js
@@ -0,0 +1,62 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing selector inplace-editor behaviors in the rule-view
+
+const TEST_URI = `
+ <style type="text/css">
+ .testclass {
+ text-align: center;
+ }
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>
+ <span>This is a span</span>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ info("Selecting the test element");
+ yield selectNode("#testid", inspector);
+ yield testEditSelector(view, "span");
+
+ info("Selecting the modified element with the new rule");
+ yield selectNode("span", inspector);
+ yield checkModifiedElement(view, "span");
+});
+
+function* testEditSelector(view, name) {
+ info("Test editing existing selector fields");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, idRuleEditor.selectorText);
+
+ is(inplaceEditor(idRuleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Entering a new selector name and committing");
+ editor.input.value = name;
+
+ info("Waiting for rule view to update");
+ let onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+ ok(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"),
+ "Rule with " + name + " does not match the current element.");
+}
+
+function* checkModifiedElement(view, name) {
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_02.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_02.js
new file mode 100644
index 000000000..503f91efa
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_02.js
@@ -0,0 +1,88 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing selector inplace-editor behaviors in the rule-view with pseudo
+// classes.
+
+const TEST_URI = `
+ <style type="text/css">
+ .testclass {
+ text-align: center;
+ }
+ #testid3::first-letter {
+ text-decoration: "italic"
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+ <span class="testclass">This is a span</span>
+ <div class="testclass2">A</div>
+ <div id="testid3">B</div>
+`;
+
+const PSEUDO_PREF = "devtools.inspector.show_pseudo_elements";
+
+add_task(function* () {
+ // Expand the pseudo-elements section by default.
+ Services.prefs.setBoolPref(PSEUDO_PREF, true);
+
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ info("Selecting the test element");
+ yield selectNode(".testclass", inspector);
+ yield testEditSelector(view, "div:nth-child(1)");
+
+ info("Selecting the modified element");
+ yield selectNode("#testid", inspector);
+ yield checkModifiedElement(view, "div:nth-child(1)");
+
+ info("Selecting the test element");
+ yield selectNode("#testid3", inspector);
+ yield testEditSelector(view, ".testclass2::first-letter");
+
+ info("Selecting the modified element");
+ yield selectNode(".testclass2", inspector);
+ yield checkModifiedElement(view, ".testclass2::first-letter");
+
+ // Reset the pseudo-elements section pref to its default value.
+ Services.prefs.clearUserPref(PSEUDO_PREF);
+});
+
+function* testEditSelector(view, name) {
+ info("Test editing existing selector fields");
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 1) ||
+ getRuleViewRuleEditor(view, 1, 0);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, idRuleEditor.selectorText);
+
+ is(inplaceEditor(idRuleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Entering a new selector name: " + name);
+ editor.input.value = name;
+
+ info("Waiting for rule view to update");
+ let onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ is(view._elementStyle.rules.length, 2, "Should have 2 rule.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+
+ let newRuleEditor = getRuleViewRuleEditor(view, 1) ||
+ getRuleViewRuleEditor(view, 1, 0);
+ ok(newRuleEditor.element.getAttribute("unmatched"),
+ "Rule with " + name + " does not match the current element.");
+}
+
+function* checkModifiedElement(view, name) {
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_03.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_03.js
new file mode 100644
index 000000000..c6834f6ee
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_03.js
@@ -0,0 +1,48 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing selector inplace-editor behaviors in the rule-view with invalid
+// selectors
+
+const TEST_URI = `
+ <style type="text/css">
+ .testclass {
+ text-align: center;
+ }
+ </style>
+ <div class="testclass">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".testclass", inspector);
+ yield testEditSelector(view, "asd@:::!");
+});
+
+function* testEditSelector(view, name) {
+ info("Test editing existing selector fields");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ is(inplaceEditor(ruleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Entering a new selector name and committing");
+ editor.input.value = name;
+ let onRuleViewChanged = once(view, "ruleview-invalid-selector");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ is(getRuleViewRule(view, name), undefined,
+ "Rule with " + name + " selector should not exist.");
+ ok(getRuleViewRule(view, ".testclass"),
+ "Rule with .testclass selector exists.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_04.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_04.js
new file mode 100644
index 000000000..09b6ad841
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_04.js
@@ -0,0 +1,69 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the selector highlighter is removed when modifying a selector and
+// the selector highlighter works for the newly added unmatched rule.
+
+const TEST_URI = `
+ <style type="text/css">
+ p {
+ background: red;
+ }
+ </style>
+ <p>Test the selector highlighter</p>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("p", inspector);
+
+ ok(!view.selectorHighlighter,
+ "No selectorhighlighter exist in the rule-view");
+
+ yield testSelectorHighlight(view, "p");
+ yield testEditSelector(view, "body");
+ yield testSelectorHighlight(view, "body");
+});
+
+function* testSelectorHighlight(view, name) {
+ info("Test creating selector highlighter");
+
+ info("Clicking on a selector icon");
+ let icon = getRuleViewSelectorHighlighterIcon(view, name);
+
+ let onToggled = view.once("ruleview-selectorhighlighter-toggled");
+ EventUtils.synthesizeMouseAtCenter(icon, {}, view.styleWindow);
+ let isVisible = yield onToggled;
+
+ ok(view.selectorHighlighter, "The selectorhighlighter instance was created");
+ ok(isVisible, "The toggle event says the highlighter is visible");
+}
+
+function* testEditSelector(view, name) {
+ info("Test editing existing selector fields");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ is(inplaceEditor(ruleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Waiting for rule view to update");
+ let onToggled = view.once("ruleview-selectorhighlighter-toggled");
+
+ info("Entering a new selector name and committing");
+ editor.input.value = name;
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ let isVisible = yield onToggled;
+
+ ok(!view.highlighters.selectorHighlighterShown,
+ "The selectorHighlighterShown instance was removed");
+ ok(!isVisible, "The toggle event says the highlighter is not visible");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_05.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_05.js
new file mode 100644
index 000000000..cd996b4b0
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_05.js
@@ -0,0 +1,78 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that adding a new property of an unmatched rule works properly.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ }
+ .testclass {
+ background-color: white;
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+ <span class="testclass">This is a span</span>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ info("Selecting the test element");
+ yield selectNode("#testid", inspector);
+ yield testEditSelector(view, "span");
+ yield testAddProperty(view);
+
+ info("Selecting the modified element with the new rule");
+ yield selectNode("span", inspector);
+ yield checkModifiedElement(view, "span");
+});
+
+function* testEditSelector(view, name) {
+ info("Test editing existing selector fields");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ is(inplaceEditor(ruleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Entering a new selector name and committing");
+ editor.input.value = name;
+
+ info("Waiting for rule view to update");
+ let onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+ ok(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"),
+ "Rule with " + name + " does not match the current element.");
+
+ // Escape the new property editor after editing the selector
+ let onBlur = once(view.styleDocument.activeElement, "blur");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ yield onBlur;
+}
+
+function* checkModifiedElement(view, name) {
+ is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+}
+
+function* testAddProperty(view) {
+ info("Test creating a new property");
+ let textProp = yield addProperty(view, 1, "text-align", "center");
+
+ is(textProp.value, "center", "Text prop should have been changed.");
+ ok(!textProp.overridden, "Property should not be overridden");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_06.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_06.js
new file mode 100644
index 000000000..7d782a309
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_06.js
@@ -0,0 +1,76 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Testing selector inplace-editor behaviors in the rule-view with unmatched
+// selectors
+
+const TEST_URI = `
+ <style type="text/css">
+ .testclass {
+ text-align: center;
+ }
+ div {
+ }
+ </style>
+ <div class="testclass">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".testclass", inspector);
+ yield testEditClassSelector(view);
+ yield testEditDivSelector(view);
+});
+
+function* testEditClassSelector(view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ editor.input.value = "body";
+ let onRuleViewChanged = once(view, "ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ // Get the new rule editor that replaced the original
+ ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+
+ info("Check that the correct rules are visible");
+ is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
+ ok(ruleEditor.element.getAttribute("unmatched"), "Rule editor is unmatched.");
+ is(getRuleViewRule(view, ".testclass"), undefined,
+ "Rule with .testclass selector should not exist.");
+ ok(getRuleViewRule(view, "body"),
+ "Rule with body selector exists.");
+ is(inplaceEditor(propEditor.nameSpan),
+ inplaceEditor(view.styleDocument.activeElement),
+ "Focus should have moved to the property name.");
+}
+
+function* testEditDivSelector(view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 2);
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ editor.input.value = "asdf";
+ let onRuleViewChanged = once(view, "ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ // Get the new rule editor that replaced the original
+ ruleEditor = getRuleViewRuleEditor(view, 2);
+
+ info("Check that the correct rules are visible");
+ is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
+ ok(ruleEditor.element.getAttribute("unmatched"), "Rule editor is unmatched.");
+ is(getRuleViewRule(view, "div"), undefined,
+ "Rule with div selector should not exist.");
+ ok(getRuleViewRule(view, "asdf"),
+ "Rule with asdf selector exists.");
+ is(inplaceEditor(ruleEditor.newPropSpan),
+ inplaceEditor(view.styleDocument.activeElement),
+ "Focus should have moved to the property name.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_07.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_07.js
new file mode 100644
index 000000000..81c7aad72
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_07.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view overridden search filter does not appear for an
+// unmatched rule.
+
+const TEST_URI = `
+ <style type="text/css">
+ div {
+ height: 0px;
+ }
+ #testid {
+ height: 1px;
+ }
+ .testclass {
+ height: 10px;
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+ <span class="testclass">This is a span</span>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("#testid", inspector);
+ yield testEditSelector(view, "span");
+});
+
+function* testEditSelector(view, name) {
+ info("Test editing existing selector fields");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ is(inplaceEditor(ruleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Entering a new selector name and committing");
+ editor.input.value = name;
+
+ info("Entering the commit key");
+ let onRuleViewChanged = once(view, "ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ // Get the new rule editor that replaced the original
+ ruleEditor = getRuleViewRuleEditor(view, 1);
+ let rule = ruleEditor.rule;
+ let textPropEditor = rule.textProps[0].editor;
+
+ is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+ ok(ruleEditor.element.getAttribute("unmatched"),
+ "Rule with " + name + " does not match the current element.");
+ ok(textPropEditor.filterProperty.hidden, "Overridden search is hidden.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_08.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_08.js
new file mode 100644
index 000000000..33382e0de
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_08.js
@@ -0,0 +1,71 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that reverting a selector edit does the right thing.
+// Bug 1241046.
+
+const TEST_URI = `
+ <style type="text/css">
+ span {
+ color: chartreuse;
+ }
+ </style>
+ <span>
+ <div id="testid" class="testclass">Styled Node</div>
+ </span>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ info("Selecting the test element");
+ yield selectNode("#testid", inspector);
+
+ let idRuleEditor = getRuleViewRuleEditor(view, 2);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, idRuleEditor.selectorText);
+
+ is(inplaceEditor(idRuleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Entering a new selector name and committing");
+ editor.input.value = "pre";
+
+ info("Waiting for rule view to update");
+ let onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ info("Re-focusing the selector name in the rule-view");
+ idRuleEditor = getRuleViewRuleEditor(view, 2);
+ editor = yield focusEditableField(view, idRuleEditor.selectorText);
+
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ ok(getRuleViewRule(view, "pre"), "Rule with pre selector exists.");
+ is(getRuleViewRuleEditor(view, 2).element.getAttribute("unmatched"),
+ "true",
+ "Rule with pre does not match the current element.");
+
+ // Now change it back.
+ info("Re-entering original selector name and committing");
+ editor.input.value = "span";
+
+ info("Waiting for rule view to update");
+ onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ ok(getRuleViewRule(view, "span"), "Rule with span selector exists.");
+ is(getRuleViewRuleEditor(view, 2).element.getAttribute("unmatched"),
+ "false", "Rule with span matches the current element.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_09.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_09.js
new file mode 100644
index 000000000..a18ddc5ef
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_09.js
@@ -0,0 +1,110 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that editing a selector to an unmatched rule does set up the correct
+// property on the rule, and that settings property in said rule does not
+// lead to overriding properties from matched rules.
+// Test that having a rule with both matched and unmatched selectors does work
+// correctly.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ color: black;
+ }
+ .testclass {
+ background-color: white;
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+ <span class="testclass">This is a span</span>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("#testid", inspector);
+ yield testEditSelector(view, "span");
+ yield testAddImportantProperty(view);
+ yield testAddMatchedRule(view, "span, div");
+});
+
+function* testEditSelector(view, name) {
+ info("Test editing existing selector fields");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ is(inplaceEditor(ruleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Entering a new selector name and committing");
+ editor.input.value = name;
+
+ info("Waiting for rule view to update");
+ let onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
+ ok(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"),
+ "Rule with " + name + " does not match the current element.");
+
+ // Escape the new property editor after editing the selector
+ let onBlur = once(view.styleDocument.activeElement, "blur");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ yield onBlur;
+}
+
+function* testAddImportantProperty(view) {
+ info("Test creating a new property with !important");
+ let textProp = yield addProperty(view, 1, "color", "red !important");
+
+ is(textProp.value, "red", "Text prop should have been changed.");
+ is(textProp.priority, "important",
+ "Text prop has an \"important\" priority.");
+ ok(!textProp.overridden, "Property should not be overridden");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let prop = ruleEditor.rule.textProps[0];
+ ok(!prop.overridden,
+ "Existing property on matched rule should not be overridden");
+}
+
+function* testAddMatchedRule(view, name) {
+ info("Test adding a matching selector");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ is(inplaceEditor(ruleEditor.selectorText), editor,
+ "The selector editor got focused");
+
+ info("Entering a new selector name and committing");
+ editor.input.value = name;
+
+ info("Waiting for rule view to update");
+ let onRuleViewChanged = once(view, "ruleview-changed");
+
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ is(getRuleViewRuleEditor(view, 1).element.getAttribute("unmatched"), "false",
+ "Rule with " + name + " does match the current element.");
+
+ // Escape the new property editor after editing the selector
+ let onBlur = once(view.styleDocument.activeElement, "blur");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ yield onBlur;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_10.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_10.js
new file mode 100644
index 000000000..d878dd516
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_10.js
@@ -0,0 +1,64 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Regression test for bug 1293616: make sure that editing a selector
+// keeps the rule in the proper position.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid span, #testid p {
+ background: aqua;
+ }
+ span {
+ background: fuchsia;
+ }
+ </style>
+ <div id="testid">
+ <span class="pickme">
+ Styled Node
+ </span>
+ </div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".pickme", inspector);
+ yield testEditSelector(view);
+});
+
+function* testEditSelector(view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ editor.input.value = "#testid span";
+ let onRuleViewChanged = once(view, "ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ // Escape the new property editor after editing the selector
+ let onBlur = once(view.styleDocument.activeElement, "blur");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ yield onBlur;
+
+ // Get the new rule editor that replaced the original
+ ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Check that the correct rules are visible");
+ is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
+ is(ruleEditor.element.getAttribute("unmatched"), "false", "Rule editor is matched.");
+
+ let props = ruleEditor.rule.textProps;
+ is(props.length, 1, "Rule has correct number of properties");
+ is(props[0].name, "background", "Found background property");
+ ok(!props[0].overridden, "Background property is not overridden");
+
+ ruleEditor = getRuleViewRuleEditor(view, 2);
+ props = ruleEditor.rule.textProps;
+ is(props.length, 1, "Rule has correct number of properties");
+ is(props[0].name, "background", "Found background property");
+ ok(props[0].overridden, "Background property is overridden");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector_11.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector_11.js
new file mode 100644
index 000000000..9a1bdc8fa
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_11.js
@@ -0,0 +1,69 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Regression test for bug 1293616, where editing a selector should
+// change the relative priority of the rule.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ background: aqua;
+ }
+ .pickme {
+ background: seagreen;
+ }
+ span {
+ background: fuchsia;
+ }
+ </style>
+ <div>
+ <span id="testid" class="pickme">
+ Styled Node
+ </span>
+ </div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".pickme", inspector);
+ yield testEditSelector(view);
+});
+
+function* testEditSelector(view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ editor.input.value = ".pickme";
+ let onRuleViewChanged = once(view, "ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ // Escape the new property editor after editing the selector
+ let onBlur = once(view.styleDocument.activeElement, "blur");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ yield onBlur;
+
+ // Get the new rule editor that replaced the original
+ ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ info("Check that the correct rules are visible");
+ is(view._elementStyle.rules.length, 4, "Should have 4 rules.");
+ is(ruleEditor.element.getAttribute("unmatched"), "false", "Rule editor is matched.");
+
+ let props = ruleEditor.rule.textProps;
+ is(props.length, 1, "Rule has correct number of properties");
+ is(props[0].name, "background", "Found background property");
+ is(props[0].value, "aqua", "Background property is aqua");
+ ok(props[0].overridden, "Background property is overridden");
+
+ ruleEditor = getRuleViewRuleEditor(view, 2);
+ props = ruleEditor.rule.textProps;
+ is(props.length, 1, "Rule has correct number of properties");
+ is(props[0].name, "background", "Found background property");
+ is(props[0].value, "seagreen", "Background property is seagreen");
+ ok(!props[0].overridden, "Background property is not overridden");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_01.js b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_01.js
new file mode 100644
index 000000000..dbf59cba9
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_01.js
@@ -0,0 +1,107 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that clicking on swatch-preceeded value while editing the property name
+// will result in editing the property value. Also tests that the value span is updated
+// only if the property name has changed. See also Bug 1248274.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ color: red;
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("#testid", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+
+ yield testColorValueSpanClickWithoutNameChange(propEditor, view);
+ yield testColorValueSpanClickAfterNameChange(propEditor, view);
+});
+
+function* testColorValueSpanClickWithoutNameChange(propEditor, view) {
+ info("Test click on color span while focusing property name editor");
+ let colorSpan = propEditor.valueSpan.querySelector(".ruleview-color");
+
+ info("Focus the color name span");
+ yield focusEditableField(view, propEditor.nameSpan);
+ let editor = inplaceEditor(propEditor.doc.activeElement);
+
+ // We add a click event to make sure the color span won't be cleared
+ // on nameSpan blur (which would lead to the click event not being triggered)
+ let onColorSpanClick = once(colorSpan, "click");
+
+ // The property-value-updated is emitted when the valueSpan markup is being
+ // re-populated, which should not be the case when not modifying the property name
+ let onPropertyValueUpdated = function () {
+ ok(false, "The \"property-value-updated\" should not be emitted");
+ };
+ view.on("property-value-updated", onPropertyValueUpdated);
+
+ info("blur propEditor.nameSpan by clicking on the color span");
+ EventUtils.synthesizeMouse(colorSpan, 1, 1, {}, propEditor.doc.defaultView);
+
+ info("wait for the click event on the color span");
+ yield onColorSpanClick;
+ ok(true, "Expected click event was emitted");
+
+ editor = inplaceEditor(propEditor.doc.activeElement);
+ is(inplaceEditor(propEditor.valueSpan), editor,
+ "The property value editor got focused");
+
+ // We remove this listener in order to not cause unwanted conflict in the next test
+ view.off("property-value-updated", onPropertyValueUpdated);
+
+ info("blur valueSpan editor to trigger ruleview-changed event and prevent " +
+ "having pending request");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ editor.input.blur();
+ yield onRuleViewChanged;
+}
+
+function* testColorValueSpanClickAfterNameChange(propEditor, view) {
+ info("Test click on color span after property name change");
+ let colorSpan = propEditor.valueSpan.querySelector(".ruleview-color");
+
+ info("Focus the color name span");
+ yield focusEditableField(view, propEditor.nameSpan);
+ let editor = inplaceEditor(propEditor.doc.activeElement);
+
+ info("Modify the property to border-color to trigger the " +
+ "property-value-updated event");
+ editor.input.value = "border-color";
+
+ let onRuleViewChanged = view.once("ruleview-changed");
+ let onPropertyValueUpdate = view.once("property-value-updated");
+
+ info("blur propEditor.nameSpan by clicking on the color span");
+ EventUtils.synthesizeMouse(colorSpan, 1, 1, {}, propEditor.doc.defaultView);
+
+ info("wait for ruleview-changed event to be triggered to prevent pending requests");
+ yield onRuleViewChanged;
+
+ info("wait for the property value to be updated");
+ yield onPropertyValueUpdate;
+ ok(true, "Expected \"property-value-updated\" event was emitted");
+
+ editor = inplaceEditor(propEditor.doc.activeElement);
+ is(inplaceEditor(propEditor.valueSpan), editor,
+ "The property value editor got focused");
+
+ info("blur valueSpan editor to trigger ruleview-changed event and prevent " +
+ "having pending request");
+ onRuleViewChanged = view.once("ruleview-changed");
+ editor.input.blur();
+ yield onRuleViewChanged;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_02.js b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_02.js
new file mode 100644
index 000000000..372ed7477
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_02.js
@@ -0,0 +1,65 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that hitting shift + click on color swatch while editing the property
+// name will only change the color unit and not lead to edit the property value.
+// See also Bug 1248274.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ color: red;
+ background: linear-gradient(
+ 90deg,
+ rgb(183,222,237),
+ rgb(33,180,226),
+ rgb(31,170,217),
+ rgba(200,170,140,0.5));
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ info("Test shift + click on color swatch while editing property name");
+
+ yield selectNode("#testid", inspector);
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[1].editor;
+ let swatchSpan = propEditor.valueSpan.querySelectorAll(".ruleview-colorswatch")[2];
+
+ info("Focus the background name span");
+ yield focusEditableField(view, propEditor.nameSpan);
+ let editor = inplaceEditor(propEditor.doc.activeElement);
+
+ info("Modify the property to background-image to trigger the " +
+ "property-value-updated event");
+ editor.input.value = "background-image";
+
+ let onPropertyValueUpdate = view.once("property-value-updated");
+ let onSwatchUnitChange = swatchSpan.once("unit-change");
+ let onRuleViewChanged = view.once("ruleview-changed");
+
+ info("blur propEditor.nameSpan by clicking on the color swatch");
+ EventUtils.synthesizeMouseAtCenter(swatchSpan, {shiftKey: true},
+ propEditor.doc.defaultView);
+
+ info("wait for ruleview-changed event to be triggered to prevent pending requests");
+ yield onRuleViewChanged;
+
+ info("wait for the color unit to change");
+ yield onSwatchUnitChange;
+ ok(true, "the color unit was changed");
+
+ info("wait for the property value to be updated");
+ yield onPropertyValueUpdate;
+
+ ok(!inplaceEditor(propEditor.valueSpan), "The inplace editor wasn't shown " +
+ "as a result of the color swatch shift + click");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_03.js b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_03.js
new file mode 100644
index 000000000..041a45a3e
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_03.js
@@ -0,0 +1,69 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that clicking on color swatch while editing the property name
+// will show the color tooltip with the correct value. See also Bug 1248274.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ color: red;
+ background: linear-gradient(
+ 90deg,
+ rgb(183,222,237),
+ rgb(33,180,226),
+ rgb(31,170,217),
+ rgba(200,170,140,0.5));
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ info("Test click on color swatch while editing property name");
+
+ yield selectNode("#testid", inspector);
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[1].editor;
+ let swatchSpan = propEditor.valueSpan.querySelectorAll(
+ ".ruleview-colorswatch")[3];
+ let colorPicker = view.tooltips.colorPicker;
+
+ info("Focus the background name span");
+ yield focusEditableField(view, propEditor.nameSpan);
+ let editor = inplaceEditor(propEditor.doc.activeElement);
+
+ info("Modify the background property to background-image to trigger the " +
+ "property-value-updated event");
+ editor.input.value = "background-image";
+
+ let onRuleViewChanged = view.once("ruleview-changed");
+ let onPropertyValueUpdate = view.once("property-value-updated");
+ let onReady = colorPicker.once("ready");
+
+ info("blur propEditor.nameSpan by clicking on the color swatch");
+ EventUtils.synthesizeMouseAtCenter(swatchSpan, {},
+ propEditor.doc.defaultView);
+
+ info("wait for ruleview-changed event to be triggered to prevent pending requests");
+ yield onRuleViewChanged;
+
+ info("wait for the property value to be updated");
+ yield onPropertyValueUpdate;
+
+ info("wait for the color picker to be shown");
+ yield onReady;
+
+ ok(true, "The color picker was shown on click of the color swatch");
+ ok(!inplaceEditor(propEditor.valueSpan),
+ "The inplace editor wasn't shown as a result of the color swatch click");
+
+ let spectrum = colorPicker.spectrum;
+ is(spectrum.rgb, "200,170,140,0.5", "The correct color picker was shown");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_04.js b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_04.js
new file mode 100644
index 000000000..fa4d8e6e2
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-value-after-name_04.js
@@ -0,0 +1,62 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that clicking on a property's value URL while editing the property name
+// will open the link in a new tab. See also Bug 1248274.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ background: url("chrome://global/skin/icons/warning-64.png"), linear-gradient(white, #F06 400px);
+ }
+ </style>
+ <div id="testid">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ info("Test click on background-image url while editing property name");
+
+ yield selectNode("#testid", inspector);
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+ let anchor = propEditor.valueSpan.querySelector(".ruleview-propertyvalue .theme-link");
+
+ info("Focus the background name span");
+ yield focusEditableField(view, propEditor.nameSpan);
+ let editor = inplaceEditor(propEditor.doc.activeElement);
+
+ info("Modify the property to background to trigger the " +
+ "property-value-updated event");
+ editor.input.value = "background-image";
+
+ let onRuleViewChanged = view.once("ruleview-changed");
+ let onPropertyValueUpdate = view.once("property-value-updated");
+ let onTabOpened = waitForTab();
+
+ info("blur propEditor.nameSpan by clicking on the link");
+ // The url can be wrapped across multiple lines, and so we click the lower left corner
+ // of the anchor to make sure to target the link.
+ let rect = anchor.getBoundingClientRect();
+ EventUtils.synthesizeMouse(anchor, 2, rect.height - 2, {}, propEditor.doc.defaultView);
+
+ info("wait for ruleview-changed event to be triggered to prevent pending requests");
+ yield onRuleViewChanged;
+
+ info("wait for the property value to be updated");
+ yield onPropertyValueUpdate;
+
+ info("wait for the image to be open in a new tab");
+ let tab = yield onTabOpened;
+ ok(true, "A new tab opened");
+
+ is(tab.linkedBrowser.currentURI.spec, anchor.href,
+ "The URL for the new tab is correct");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_01.js b/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_01.js
new file mode 100644
index 000000000..c9c7cd3d2
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_01.js
@@ -0,0 +1,94 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the correct editable fields are focused when tabbing and entering
+// through the rule view.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ color: red;
+ margin: 0;
+ padding: 0;
+ }
+ div {
+ border-color: red
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testEditableFieldFocus(inspector, view, "VK_RETURN");
+ yield testEditableFieldFocus(inspector, view, "VK_TAB");
+});
+
+function* testEditableFieldFocus(inspector, view, commitKey) {
+ info("Click on the selector of the inline style ('element')");
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let onFocus = once(ruleEditor.element, "focus", true);
+ ruleEditor.selectorText.click();
+ yield onFocus;
+ assertEditor(view, ruleEditor.newPropSpan,
+ "Focus should be in the element property span");
+
+ info("Focus the next field with " + commitKey);
+ ruleEditor = getRuleViewRuleEditor(view, 1);
+ yield focusNextEditableField(view, ruleEditor, commitKey);
+ assertEditor(view, ruleEditor.selectorText,
+ "Focus should have moved to the next rule selector");
+
+ for (let i = 0; i < ruleEditor.rule.textProps.length; i++) {
+ let textProp = ruleEditor.rule.textProps[i];
+ let propEditor = textProp.editor;
+
+ info("Focus the next field with " + commitKey);
+ // Expect a ruleview-changed event if we are moving from a property value
+ // to the next property name (which occurs after the first iteration, as for
+ // i=0, the previous field is the selector).
+ let onRuleViewChanged = i > 0 ? view.once("ruleview-changed") : null;
+ yield focusNextEditableField(view, ruleEditor, commitKey);
+ yield onRuleViewChanged;
+ assertEditor(view, propEditor.nameSpan,
+ "Focus should have moved to the property name");
+
+ info("Focus the next field with " + commitKey);
+ yield focusNextEditableField(view, ruleEditor, commitKey);
+ assertEditor(view, propEditor.valueSpan,
+ "Focus should have moved to the property value");
+ }
+
+ // Expect a ruleview-changed event again as we're bluring a property value.
+ let onRuleViewChanged = view.once("ruleview-changed");
+ yield focusNextEditableField(view, ruleEditor, commitKey);
+ yield onRuleViewChanged;
+ assertEditor(view, ruleEditor.newPropSpan,
+ "Focus should have moved to the new property span");
+
+ ruleEditor = getRuleViewRuleEditor(view, 2);
+
+ yield focusNextEditableField(view, ruleEditor, commitKey);
+ assertEditor(view, ruleEditor.selectorText,
+ "Focus should have moved to the next rule selector");
+
+ info("Blur the selector field");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+}
+
+function* focusNextEditableField(view, ruleEditor, commitKey) {
+ let onFocus = once(ruleEditor.element, "focus", true);
+ EventUtils.synthesizeKey(commitKey, {}, view.styleWindow);
+ yield onFocus;
+}
+
+function assertEditor(view, element, message) {
+ let editor = inplaceEditor(view.styleDocument.activeElement);
+ is(inplaceEditor(element), editor, message);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_02.js b/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_02.js
new file mode 100644
index 000000000..13ad221f0
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_02.js
@@ -0,0 +1,84 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the correct editable fields are focused when shift tabbing
+// through the rule view.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ color: red;
+ margin: 0;
+ padding: 0;
+ }
+ div {
+ border-color: red
+ }
+ </style>
+ <div id='testid'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testEditableFieldFocus(inspector, view, "VK_TAB", { shiftKey: true });
+});
+
+function* testEditableFieldFocus(inspector, view, commitKey, options = {}) {
+ let ruleEditor = getRuleViewRuleEditor(view, 2);
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+ is(inplaceEditor(ruleEditor.selectorText), editor,
+ "Focus should be in the 'div' rule selector");
+
+ ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ yield focusNextField(view, ruleEditor, commitKey, options);
+ assertEditor(view, ruleEditor.newPropSpan,
+ "Focus should have moved to the new property span");
+
+ for (let textProp of ruleEditor.rule.textProps.slice(0).reverse()) {
+ let propEditor = textProp.editor;
+
+ yield focusNextField(view, ruleEditor, commitKey, options);
+ yield assertEditor(view, propEditor.valueSpan,
+ "Focus should have moved to the property value");
+
+ yield focusNextFieldAndExpectChange(view, ruleEditor, commitKey, options);
+ yield assertEditor(view, propEditor.nameSpan,
+ "Focus should have moved to the property name");
+ }
+
+ ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ yield focusNextField(view, ruleEditor, commitKey, options);
+ yield assertEditor(view, ruleEditor.selectorText,
+ "Focus should have moved to the '#testid' rule selector");
+
+ ruleEditor = getRuleViewRuleEditor(view, 0);
+
+ yield focusNextField(view, ruleEditor, commitKey, options);
+ assertEditor(view, ruleEditor.newPropSpan,
+ "Focus should have moved to the new property span");
+}
+
+function* focusNextFieldAndExpectChange(view, ruleEditor, commitKey, options) {
+ let onRuleViewChanged = view.once("ruleview-changed");
+ yield focusNextField(view, ruleEditor, commitKey, options);
+ yield onRuleViewChanged;
+}
+
+function* focusNextField(view, ruleEditor, commitKey, options) {
+ let onFocus = once(ruleEditor.element, "focus", true);
+ EventUtils.synthesizeKey(commitKey, options, view.styleWindow);
+ yield onFocus;
+}
+
+function* assertEditor(view, element, message) {
+ let editor = inplaceEditor(view.styleDocument.activeElement);
+ is(inplaceEditor(element), editor, message);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_eyedropper.js b/devtools/client/inspector/rules/test/browser_rules_eyedropper.js
new file mode 100644
index 000000000..0762066e3
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_eyedropper.js
@@ -0,0 +1,123 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test opening the eyedropper from the color picker. Pressing escape to close it, and
+// clicking the page to select a color.
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ background-color: white;
+ padding: 0px
+ }
+
+ #div1 {
+ background-color: #ff5;
+ width: 20px;
+ height: 20px;
+ }
+
+ #div2 {
+ margin-left: 20px;
+ width: 20px;
+ height: 20px;
+ background-color: #f09;
+ }
+ </style>
+ <body><div id="div1"></div><div id="div2"></div></body>
+`;
+
+// #f09
+const ORIGINAL_COLOR = "rgb(255, 0, 153)";
+// #ff5
+const EXPECTED_COLOR = "rgb(255, 255, 85)";
+
+add_task(function* () {
+ info("Add the test tab, open the rule-view and select the test node");
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {testActor, inspector, view} = yield openRuleView();
+ yield selectNode("#div2", inspector);
+
+ info("Get the background-color property from the rule-view");
+ let property = getRuleViewProperty(view, "#div2", "background-color");
+ let swatch = property.valueSpan.querySelector(".ruleview-colorswatch");
+ ok(swatch, "Color swatch is displayed for the bg-color property");
+
+ info("Open the eyedropper from the colorpicker tooltip");
+ yield openEyedropper(view, swatch);
+
+ let tooltip = view.tooltips.colorPicker.tooltip;
+ ok(!tooltip.isVisible(), "color picker tooltip is closed after opening eyedropper");
+
+ info("Test that pressing escape dismisses the eyedropper");
+ yield testESC(swatch, inspector, testActor);
+
+ info("Open the eyedropper again");
+ yield openEyedropper(view, swatch);
+
+ info("Test that a color can be selected with the eyedropper");
+ yield testSelect(view, swatch, inspector, testActor);
+
+ let onHidden = tooltip.once("hidden");
+ tooltip.hide();
+ yield onHidden;
+ ok(!tooltip.isVisible(), "color picker tooltip is closed");
+
+ yield waitForTick();
+});
+
+function* testESC(swatch, inspector, testActor) {
+ info("Press escape");
+ let onCanceled = new Promise(resolve => {
+ inspector.inspector.once("color-pick-canceled", resolve);
+ });
+ yield testActor.synthesizeKey({key: "VK_ESCAPE", options: {}});
+ yield onCanceled;
+
+ let color = swatch.style.backgroundColor;
+ is(color, ORIGINAL_COLOR, "swatch didn't change after pressing ESC");
+}
+
+function* testSelect(view, swatch, inspector, testActor) {
+ info("Click at x:10px y:10px");
+ let onPicked = new Promise(resolve => {
+ inspector.inspector.once("color-picked", resolve);
+ });
+ // The change to the content is done async after rule view change
+ let onRuleViewChanged = view.once("ruleview-changed");
+
+ yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10,
+ options: {type: "mousemove"}});
+ yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10,
+ options: {type: "mousedown"}});
+ yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10,
+ options: {type: "mouseup"}});
+
+ yield onPicked;
+ yield onRuleViewChanged;
+
+ let color = swatch.style.backgroundColor;
+ is(color, EXPECTED_COLOR, "swatch changed colors");
+
+ is((yield getComputedStyleProperty("div", null, "background-color")),
+ EXPECTED_COLOR,
+ "div's color set to body color after dropper");
+}
+
+function* openEyedropper(view, swatch) {
+ let tooltip = view.tooltips.colorPicker.tooltip;
+
+ info("Click on the swatch");
+ let onColorPickerReady = view.tooltips.colorPicker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ let dropperButton = tooltip.doc.querySelector("#eyedropper-button");
+
+ info("Click on the eyedropper icon");
+ let onOpened = tooltip.once("eyedropper-opened");
+ dropperButton.click();
+ yield onOpened;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_filtereditor-appears-on-swatch-click.js b/devtools/client/inspector/rules/test/browser_rules_filtereditor-appears-on-swatch-click.js
new file mode 100644
index 000000000..21eeebb36
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_filtereditor-appears-on-swatch-click.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the that Filter Editor Tooltip opens by clicking on filter swatches
+
+const TEST_URL = URL_ROOT + "doc_filter.html";
+
+add_task(function* () {
+ yield addTab(TEST_URL);
+
+ let {view} = yield openRuleView();
+
+ info("Getting the filter swatch element");
+ let swatch = getRuleViewProperty(view, "body", "filter").valueSpan
+ .querySelector(".ruleview-filterswatch");
+
+ let filterTooltip = view.tooltips.filterEditor;
+ // Clicking on a cssfilter swatch sets the current filter value in the tooltip
+ // which, in turn, makes the FilterWidget emit an "updated" event that causes
+ // the rule-view to refresh. So we must wait for the ruleview-changed event.
+ let onRuleViewChanged = view.once("ruleview-changed");
+ swatch.click();
+ yield onRuleViewChanged;
+
+ ok(true, "The shown event was emitted after clicking on swatch");
+ ok(!inplaceEditor(swatch.parentNode),
+ "The inplace editor wasn't shown as a result of the filter swatch click");
+
+ yield hideTooltipAndWaitForRuleViewChanged(filterTooltip, view);
+
+ yield waitForTick();
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_filtereditor-commit-on-ENTER.js b/devtools/client/inspector/rules/test/browser_rules_filtereditor-commit-on-ENTER.js
new file mode 100644
index 000000000..127a20843
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_filtereditor-commit-on-ENTER.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the Filter Editor Tooltip committing changes on ENTER
+
+const TEST_URL = URL_ROOT + "doc_filter.html";
+
+add_task(function* () {
+ yield addTab(TEST_URL);
+ let {view} = yield openRuleView();
+
+ info("Get the filter swatch element");
+ let swatch = getRuleViewProperty(view, "body", "filter").valueSpan
+ .querySelector(".ruleview-filterswatch");
+
+ info("Click on the filter swatch element");
+ // Clicking on a cssfilter swatch sets the current filter value in the tooltip
+ // which, in turn, makes the FilterWidget emit an "updated" event that causes
+ // the rule-view to refresh. So we must wait for the ruleview-changed event.
+ let onRuleViewChanged = view.once("ruleview-changed");
+ swatch.click();
+ yield onRuleViewChanged;
+
+ info("Get the cssfilter widget instance");
+ let filterTooltip = view.tooltips.filterEditor;
+ let widget = filterTooltip.widget;
+
+ info("Set a new value in the cssfilter widget");
+ onRuleViewChanged = view.once("ruleview-changed");
+ widget.setCssValue("blur(2px)");
+ yield waitForComputedStyleProperty("body", null, "filter", "blur(2px)");
+ yield onRuleViewChanged;
+ ok(true, "Changes previewed on the element");
+
+ info("Press RETURN to commit changes");
+ // Pressing return in the cssfilter tooltip triggeres 2 ruleview-changed
+ onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
+ EventUtils.sendKey("RETURN", widget.styleWindow);
+ yield onRuleViewChanged;
+
+ is((yield getComputedStyleProperty("body", null, "filter")), "blur(2px)",
+ "The elemenet's filter was kept after RETURN");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_filtereditor-revert-on-ESC.js b/devtools/client/inspector/rules/test/browser_rules_filtereditor-revert-on-ESC.js
new file mode 100644
index 000000000..0302f40a9
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_filtereditor-revert-on-ESC.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that changes made to the Filter Editor Tooltip are reverted when
+// ESC is pressed
+
+const TEST_URL = URL_ROOT + "doc_filter.html";
+
+add_task(function* () {
+ yield addTab(TEST_URL);
+ let {view} = yield openRuleView();
+ yield testPressingEscapeRevertsChanges(view);
+ yield testPressingEscapeRevertsChangesAndDisables(view);
+});
+
+function* testPressingEscapeRevertsChanges(view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+ let swatch = propEditor.valueSpan.querySelector(".ruleview-filterswatch");
+
+ yield clickOnFilterSwatch(swatch, view);
+ yield setValueInFilterWidget("blur(2px)", view);
+
+ yield waitForComputedStyleProperty("body", null, "filter", "blur(2px)");
+ is(propEditor.valueSpan.textContent, "blur(2px)",
+ "Got expected property value.");
+
+ yield pressEscapeToCloseTooltip(view);
+
+ yield waitForComputedStyleProperty("body", null, "filter",
+ "blur(2px) contrast(2)");
+ is(propEditor.valueSpan.textContent, "blur(2px) contrast(2)",
+ "Got expected property value.");
+}
+
+function* testPressingEscapeRevertsChangesAndDisables(view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+
+ info("Disabling filter property");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ propEditor.enable.click();
+ yield onRuleViewChanged;
+
+ ok(propEditor.element.classList.contains("ruleview-overridden"),
+ "property is overridden.");
+ is(propEditor.enable.style.visibility, "visible",
+ "property enable checkbox is visible.");
+ ok(!propEditor.enable.getAttribute("checked"),
+ "property enable checkbox is not checked.");
+ ok(!propEditor.prop.enabled,
+ "filter property is disabled.");
+ let newValue = yield getRulePropertyValue("filter");
+ is(newValue, "", "filter should have been unset.");
+
+ let swatch = propEditor.valueSpan.querySelector(".ruleview-filterswatch");
+ yield clickOnFilterSwatch(swatch, view);
+
+ ok(!propEditor.element.classList.contains("ruleview-overridden"),
+ "property overridden is not displayed.");
+ is(propEditor.enable.style.visibility, "hidden",
+ "property enable checkbox is hidden.");
+
+ yield setValueInFilterWidget("blur(2px)", view);
+ yield pressEscapeToCloseTooltip(view);
+
+ ok(propEditor.element.classList.contains("ruleview-overridden"),
+ "property is overridden.");
+ is(propEditor.enable.style.visibility, "visible",
+ "property enable checkbox is visible.");
+ ok(!propEditor.enable.getAttribute("checked"),
+ "property enable checkbox is not checked.");
+ ok(!propEditor.prop.enabled, "filter property is disabled.");
+ newValue = yield getRulePropertyValue("filter");
+ is(newValue, "", "filter should have been unset.");
+ is(propEditor.valueSpan.textContent, "blur(2px) contrast(2)",
+ "Got expected property value.");
+}
+
+function* getRulePropertyValue(name) {
+ let propValue = yield executeInContent("Test:GetRulePropertyValue", {
+ styleSheetIndex: 0,
+ ruleIndex: 0,
+ name: name
+ });
+ return propValue;
+}
+
+function* clickOnFilterSwatch(swatch, view) {
+ info("Clicking on a css filter swatch to open the tooltip");
+
+ // Clicking on a cssfilter swatch sets the current filter value in the tooltip
+ // which, in turn, makes the FilterWidget emit an "updated" event that causes
+ // the rule-view to refresh. So we must wait for the ruleview-changed event.
+ let onRuleViewChanged = view.once("ruleview-changed");
+ swatch.click();
+ yield onRuleViewChanged;
+}
+
+function* setValueInFilterWidget(value, view) {
+ info("Setting the CSS filter value in the tooltip");
+
+ let filterTooltip = view.tooltips.filterEditor;
+ let onRuleViewChanged = view.once("ruleview-changed");
+ filterTooltip.widget.setCssValue(value);
+ yield onRuleViewChanged;
+}
+
+function* pressEscapeToCloseTooltip(view) {
+ info("Pressing ESCAPE to close the tooltip");
+
+ let filterTooltip = view.tooltips.filterEditor;
+ let onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.sendKey("ESCAPE", filterTooltip.widget.styleWindow);
+ yield onRuleViewChanged;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js
new file mode 100644
index 000000000..617eb00da
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js
@@ -0,0 +1,41 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that grid highlighter is hidden on page navigation.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #grid {
+ display: grid;
+ }
+ </style>
+ <div id="grid">
+ <div id="cell1">cell1</div>
+ <div id="cell2">cell2</div>
+ </div>
+`;
+
+const TEST_URI_2 = "data:text/html,<html><body>test</body></html>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ let highlighters = view.highlighters;
+
+ yield selectNode("#grid", inspector);
+ let container = getRuleViewProperty(view, "#grid", "display").valueSpan;
+ let gridToggle = container.querySelector(".ruleview-grid");
+
+ info("Toggling ON the CSS grid highlighter from the rule-view.");
+ let onHighlighterShown = highlighters.once("highlighter-shown");
+ gridToggle.click();
+ yield onHighlighterShown;
+
+ ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+
+ yield navigateTo(inspector, TEST_URI_2);
+ ok(!highlighters.gridHighlighterShown, "CSS grid highlighter is hidden.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js
new file mode 100644
index 000000000..a6780a94a
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-reload.js
@@ -0,0 +1,53 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a grid highlighter showing grid gaps can be displayed after reloading the
+// page (Bug 1342051).
+
+const TEST_URI = `
+ <style type='text/css'>
+ #grid {
+ display: grid;
+ grid-gap: 10px;
+ }
+ </style>
+ <div id="grid">
+ <div id="cell1">cell1</div>
+ <div id="cell2">cell2</div>
+ </div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+
+ info("Check that the grid highlighter can be displayed");
+ yield checkGridHighlighter();
+
+ info("Close the toolbox before reloading the tab");
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ yield gDevTools.closeToolbox(target);
+
+ yield refreshTab(gBrowser.selectedTab);
+
+ info("Check that the grid highlighter can be displayed after reloading the page");
+ yield checkGridHighlighter();
+});
+
+function* checkGridHighlighter() {
+ let {inspector, view} = yield openRuleView();
+ let {highlighters} = view;
+
+ yield selectNode("#grid", inspector);
+ let container = getRuleViewProperty(view, "#grid", "display").valueSpan;
+ let gridToggle = container.querySelector(".ruleview-grid");
+
+ info("Toggling ON the CSS grid highlighter from the rule-view.");
+ let onHighlighterShown = highlighters.once("highlighter-shown");
+ gridToggle.click();
+ yield onHighlighterShown;
+
+ ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js
new file mode 100644
index 000000000..04534522b
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js
@@ -0,0 +1,64 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling the grid highlighter in the rule view and the display of the
+// grid highlighter.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #grid {
+ display: grid;
+ }
+ </style>
+ <div id="grid">
+ <div id="cell1">cell1</div>
+ <div id="cell2">cell2</div>
+ </div>
+`;
+
+const HIGHLIGHTER_TYPE = "CssGridHighlighter";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ let highlighters = view.highlighters;
+
+ yield selectNode("#grid", inspector);
+ let container = getRuleViewProperty(view, "#grid", "display").valueSpan;
+ let gridToggle = container.querySelector(".ruleview-grid");
+
+ info("Checking the initial state of the CSS grid toggle in the rule-view.");
+ ok(gridToggle, "Grid highlighter toggle is visible.");
+ ok(!gridToggle.classList.contains("active"),
+ "Grid highlighter toggle button is not active.");
+ ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
+ "No CSS grid highlighter exists in the rule-view.");
+ ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+
+ info("Toggling ON the CSS grid highlighter from the rule-view.");
+ let onHighlighterShown = highlighters.once("highlighter-shown");
+ gridToggle.click();
+ yield onHighlighterShown;
+
+ info("Checking the CSS grid highlighter is created and toggle button is active in " +
+ "the rule-view.");
+ ok(gridToggle.classList.contains("active"),
+ "Grid highlighter toggle is active.");
+ ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
+ "CSS grid highlighter created in the rule-view.");
+ ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+
+ info("Toggling OFF the CSS grid highlighter from the rule-view.");
+ let onHighlighterHidden = highlighters.once("highlighter-hidden");
+ gridToggle.click();
+ yield onHighlighterHidden;
+
+ info("Checking the CSS grid highlighter is not shown and toggle button is not active " +
+ "in the rule-view.");
+ ok(!gridToggle.classList.contains("active"),
+ "Grid highlighter toggle button is not active.");
+ ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js
new file mode 100644
index 000000000..5c339e892
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js
@@ -0,0 +1,73 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling the grid highlighter in the rule view from an overridden 'display: grid'
+// declaration.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #grid {
+ display: grid;
+ }
+ div, ul {
+ display: grid;
+ }
+ </style>
+ <ul id="grid">
+ <li id="cell1">cell1</li>
+ <li id="cell2">cell2</li>
+ </ul>
+`;
+
+const HIGHLIGHTER_TYPE = "CssGridHighlighter";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ let highlighters = view.highlighters;
+
+ yield selectNode("#grid", inspector);
+ let container = getRuleViewProperty(view, "#grid", "display").valueSpan;
+ let gridToggle = container.querySelector(".ruleview-grid");
+ let overriddenContainer = getRuleViewProperty(view, "div, ul", "display").valueSpan;
+ let overriddenGridToggle = overriddenContainer.querySelector(".ruleview-grid");
+
+ info("Checking the initial state of the CSS grid toggle in the rule-view.");
+ ok(gridToggle && overriddenGridToggle, "Grid highlighter toggles are visible.");
+ ok(!gridToggle.classList.contains("active") &&
+ !overriddenGridToggle.classList.contains("active"),
+ "Grid highlighter toggle buttons are not active.");
+ ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
+ "No CSS grid highlighter exists in the rule-view.");
+ ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+
+ info("Toggling ON the CSS grid highlighter from the overridden rule in the rule-view.");
+ let onHighlighterShown = highlighters.once("highlighter-shown");
+ overriddenGridToggle.click();
+ yield onHighlighterShown;
+
+ info("Checking the CSS grid highlighter is created and toggle buttons are active in " +
+ "the rule-view.");
+ ok(gridToggle.classList.contains("active") &&
+ overriddenGridToggle.classList.contains("active"),
+ "Grid highlighter toggle is active.");
+ ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
+ "CSS grid highlighter created in the rule-view.");
+ ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+
+ info("Toggling off the CSS grid highlighter from the normal grid declaration in the " +
+ "rule-view.");
+ let onHighlighterHidden = highlighters.once("highlighter-hidden");
+ gridToggle.click();
+ yield onHighlighterHidden;
+
+ info("Checking the CSS grid highlighter is not shown and toggle buttons are not " +
+ "active in the rule-view.");
+ ok(!gridToggle.classList.contains("active") &&
+ !overriddenGridToggle.classList.contains("active"),
+ "Grid highlighter toggle buttons are not active.");
+ ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js
new file mode 100644
index 000000000..a908d6a97
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js
@@ -0,0 +1,96 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling the grid highlighter in the rule view with multiple grids in the page.
+
+const TEST_URI = `
+ <style type='text/css'>
+ .grid {
+ display: grid;
+ }
+ </style>
+ <div id="grid1" class="grid">
+ <div class="cell1">cell1</div>
+ <div class="cell2">cell2</div>
+ </div>
+ <div id="grid2" class="grid">
+ <div class="cell1">cell1</div>
+ <div class="cell2">cell2</div>
+ </div>
+`;
+
+const HIGHLIGHTER_TYPE = "CssGridHighlighter";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ let highlighters = view.highlighters;
+
+ info("Selecting the first grid container.");
+ yield selectNode("#grid1", inspector);
+ let container = getRuleViewProperty(view, ".grid", "display").valueSpan;
+ let gridToggle = container.querySelector(".ruleview-grid");
+
+ info("Checking the state of the CSS grid toggle for the first grid container in the " +
+ "rule-view.");
+ ok(gridToggle, "Grid highlighter toggle is visible.");
+ ok(!gridToggle.classList.contains("active"),
+ "Grid highlighter toggle button is not active.");
+ ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
+ "No CSS grid highlighter exists in the rule-view.");
+ ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+
+ info("Toggling ON the CSS grid highlighter for the first grid container from the " +
+ "rule-view.");
+ let onHighlighterShown = highlighters.once("highlighter-shown");
+ gridToggle.click();
+ yield onHighlighterShown;
+
+ info("Checking the CSS grid highlighter is created and toggle button is active in " +
+ "the rule-view.");
+ ok(gridToggle.classList.contains("active"),
+ "Grid highlighter toggle is active.");
+ ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
+ "CSS grid highlighter created in the rule-view.");
+ ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+
+ info("Selecting the second grid container.");
+ yield selectNode("#grid2", inspector);
+ let firstGridHighterShown = highlighters.gridHighlighterShown;
+ container = getRuleViewProperty(view, ".grid", "display").valueSpan;
+ gridToggle = container.querySelector(".ruleview-grid");
+
+ info("Checking the state of the CSS grid toggle for the second grid container in the " +
+ "rule-view.");
+ ok(gridToggle, "Grid highlighter toggle is visible.");
+ ok(!gridToggle.classList.contains("active"),
+ "Grid highlighter toggle button is not active.");
+ ok(highlighters.gridHighlighterShown, "CSS grid highlighter is still shown.");
+
+ info("Toggling ON the CSS grid highlighter for the second grid container from the " +
+ "rule-view.");
+ onHighlighterShown = highlighters.once("highlighter-shown");
+ gridToggle.click();
+ yield onHighlighterShown;
+
+ info("Checking the CSS grid highlighter is created for the second grid container and " +
+ "toggle button is active in the rule-view.");
+ ok(gridToggle.classList.contains("active"),
+ "Grid highlighter toggle is active.");
+ ok(highlighters.gridHighlighterShown != firstGridHighterShown,
+ "Grid highlighter for the second grid container is shown.");
+
+ info("Selecting the first grid container.");
+ yield selectNode("#grid1", inspector);
+ container = getRuleViewProperty(view, ".grid", "display").valueSpan;
+ gridToggle = container.querySelector(".ruleview-grid");
+
+ info("Checking the state of the CSS grid toggle for the first grid container in the " +
+ "rule-view.");
+ ok(gridToggle, "Grid highlighter toggle is visible.");
+ ok(!gridToggle.classList.contains("active"),
+ "Grid highlighter toggle button is not active.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_guessIndentation.js b/devtools/client/inspector/rules/test/browser_rules_guessIndentation.js
new file mode 100644
index 000000000..ba2a1d7fb
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_guessIndentation.js
@@ -0,0 +1,47 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that we can guess indentation from a style sheet, not just a
+// rule.
+
+// Use a weird indentation depth to avoid accidental success.
+const TEST_URI = `
+ <style type='text/css'>
+div {
+ background-color: blue;
+}
+
+* {
+}
+</style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+const expectedText = `
+div {
+ background-color: blue;
+}
+
+* {
+ color: chartreuse;
+}
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {toolbox, inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Add a new property in the rule-view");
+ yield addProperty(view, 2, "color", "chartreuse");
+
+ info("Switch to the style-editor");
+ let { UI } = yield toolbox.selectTool("styleeditor");
+
+ let styleEditor = yield UI.editors[0].getSourceEditor();
+ let text = styleEditor.sourceEditor.getText();
+ is(text, expectedText, "style inspector changes are synced");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_inherited-properties_01.js b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_01.js
new file mode 100644
index 000000000..d1f6d7f45
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_01.js
@@ -0,0 +1,47 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that inherited properties appear for a nested element in the
+// rule view.
+
+const TEST_URI = `
+ <style type="text/css">
+ #test2 {
+ background-color: green;
+ color: purple;
+ }
+ </style>
+ <div id="test2"><div id="test1">Styled Node</div></div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#test1", inspector);
+ yield simpleInherit(inspector, view);
+});
+
+function* simpleInherit(inspector, view) {
+ let elementStyle = view._elementStyle;
+ is(elementStyle.rules.length, 2, "Should have 2 rules.");
+
+ let elementRule = elementStyle.rules[0];
+ ok(!elementRule.inherited,
+ "Element style attribute should not consider itself inherited.");
+
+ let inheritRule = elementStyle.rules[1];
+ is(inheritRule.selectorText, "#test2",
+ "Inherited rule should be the one that includes inheritable properties.");
+ ok(!!inheritRule.inherited, "Rule should consider itself inherited.");
+ is(inheritRule.textProps.length, 2,
+ "Rule should have two styles");
+ let bgcProp = inheritRule.textProps[0];
+ is(bgcProp.name, "background-color",
+ "background-color property should exist");
+ ok(bgcProp.invisible, "background-color property should be invisible");
+ let inheritProp = inheritRule.textProps[1];
+ is(inheritProp.name, "color", "color should have been inherited.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_inherited-properties_02.js b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_02.js
new file mode 100644
index 000000000..db9662eee
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_02.js
@@ -0,0 +1,34 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that no inherited properties appear when the property does not apply
+// to the nested element.
+
+const TEST_URI = `
+ <style type="text/css">
+ #test2 {
+ background-color: green;
+ }
+ </style>
+ <div id="test2"><div id="test1">Styled Node</div></div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#test1", inspector);
+ yield emptyInherit(inspector, view);
+});
+
+function* emptyInherit(inspector, view) {
+ // No inheritable styles, this rule shouldn't show up.
+ let elementStyle = view._elementStyle;
+ is(elementStyle.rules.length, 1, "Should have 1 rule.");
+
+ let elementRule = elementStyle.rules[0];
+ ok(!elementRule.inherited,
+ "Element style attribute should not consider itself inherited.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_inherited-properties_03.js b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_03.js
new file mode 100644
index 000000000..d6075f6f4
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_03.js
@@ -0,0 +1,40 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that inline inherited properties appear in the nested element.
+
+var {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
+
+const TEST_URI = `
+ <div id="test2" style="color: red">
+ <div id="test1">Styled Node</div>
+ </div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#test1", inspector);
+ yield elementStyleInherit(inspector, view);
+});
+
+function* elementStyleInherit(inspector, view) {
+ let elementStyle = view._elementStyle;
+ is(elementStyle.rules.length, 2, "Should have 2 rules.");
+
+ let elementRule = elementStyle.rules[0];
+ ok(!elementRule.inherited,
+ "Element style attribute should not consider itself inherited.");
+
+ let inheritRule = elementStyle.rules[1];
+ is(inheritRule.domRule.type, ELEMENT_STYLE,
+ "Inherited rule should be an element style, not a rule.");
+ ok(!!inheritRule.inherited, "Rule should consider itself inherited.");
+ is(inheritRule.textProps.length, 1,
+ "Should only display one inherited style");
+ let inheritProp = inheritRule.textProps[0];
+ is(inheritProp.name, "color", "color should have been inherited.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_inline-source-map.js b/devtools/client/inspector/rules/test/browser_rules_inline-source-map.js
new file mode 100644
index 000000000..05109d8c6
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_inline-source-map.js
@@ -0,0 +1,26 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that when a source map comment appears in an inline stylesheet, the
+// rule-view still appears correctly.
+// Bug 1255787.
+
+const TESTCASE_URI = URL_ROOT + "doc_inline_sourcemap.html";
+const PREF = "devtools.styleeditor.source-maps-enabled";
+
+add_task(function* () {
+ Services.prefs.setBoolPref(PREF, true);
+
+ yield addTab(TESTCASE_URI);
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("div", inspector);
+
+ let ruleEl = getRuleViewRule(view, "div");
+ ok(ruleEl, "The 'div' rule exists in the rule-view");
+
+ Services.prefs.clearUserPref(PREF);
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_invalid-source-map.js b/devtools/client/inspector/rules/test/browser_rules_invalid-source-map.js
new file mode 100644
index 000000000..825f48a96
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_invalid-source-map.js
@@ -0,0 +1,44 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that when a source map is missing/invalid, the rule view still loads
+// correctly.
+
+const TESTCASE_URI = URL_ROOT + "doc_invalid_sourcemap.html";
+const PREF = "devtools.styleeditor.source-maps-enabled";
+const CSS_LOC = "doc_invalid_sourcemap.css:1";
+
+add_task(function* () {
+ Services.prefs.setBoolPref(PREF, true);
+
+ yield addTab(TESTCASE_URI);
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("div", inspector);
+
+ let ruleEl = getRuleViewRule(view, "div");
+ ok(ruleEl, "The 'div' rule exists in the rule-view");
+
+ let prop = getRuleViewProperty(view, "div", "color");
+ ok(prop, "The 'color' property exists in this rule");
+
+ let value = getRuleViewPropertyValue(view, "div", "color");
+ is(value, "gold", "The 'color' property has the right value");
+
+ yield verifyLinkText(view, CSS_LOC);
+
+ Services.prefs.clearUserPref(PREF);
+});
+
+function verifyLinkText(view, text) {
+ info("Verifying that the rule-view stylesheet link is " + text);
+ let label = getRuleViewLinkByIndex(view, 1)
+ .querySelector(".ruleview-rule-source-label");
+ return waitForSuccess(
+ () => label.textContent == text,
+ "Link text changed to display correct location: " + text
+ );
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_invalid.js b/devtools/client/inspector/rules/test/browser_rules_invalid.js
new file mode 100644
index 000000000..e664f68ac
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_invalid.js
@@ -0,0 +1,33 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that an invalid property still lets us display the rule view
+// Bug 1235603.
+
+const TEST_URI = `
+ <style>
+ div {
+ background: #fff;
+ font-family: sans-serif;
+ url(display-table.min.htc);
+ }
+ </style>
+ <body>
+ <div id="testid" class="testclass">Styled Node</div>
+ </body>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
+ // Have to actually get the rule in order to ensure that the
+ // elements were created.
+ ok(getRuleViewRule(view, "div"), "Rule with div selector exists");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_keybindings.js b/devtools/client/inspector/rules/test/browser_rules_keybindings.js
new file mode 100644
index 000000000..84fdeff85
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_keybindings.js
@@ -0,0 +1,49 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that focus doesn't leave the style editor when adding a property
+// (bug 719916)
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8,<h1>Some header text</h1>");
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("h1", inspector);
+
+ info("Getting the ruleclose brace element");
+ let brace = view.styleDocument.querySelector(".ruleview-ruleclose");
+
+ info("Focus the new property editable field to create a color property");
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+ editor.input.value = "color";
+
+ info("Typing ENTER to focus the next field: property value");
+ let onFocus = once(brace.parentNode, "focus", true);
+ let onRuleViewChanged = view.once("ruleview-changed");
+
+ EventUtils.sendKey("return");
+
+ yield onFocus;
+ yield onRuleViewChanged;
+ ok(true, "The value field was focused");
+
+ info("Entering a property value");
+ editor = getCurrentInplaceEditor(view);
+ editor.input.value = "green";
+
+ info("Typing ENTER again should focus a new property name");
+ onFocus = once(brace.parentNode, "focus", true);
+ onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.sendKey("return");
+ yield onFocus;
+ yield onRuleViewChanged;
+ ok(true, "The new property name field was focused");
+ getCurrentInplaceEditor(view).input.blur();
+});
+
+function getCurrentInplaceEditor(view) {
+ return inplaceEditor(view.styleDocument.activeElement);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_keyframeLineNumbers.js b/devtools/client/inspector/rules/test/browser_rules_keyframeLineNumbers.js
new file mode 100644
index 000000000..ebbde08ac
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_keyframeLineNumbers.js
@@ -0,0 +1,25 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that editing a rule will update the line numbers of subsequent
+// rules in the rule view.
+
+const TESTCASE_URI = URL_ROOT + "doc_keyframeLineNumbers.html";
+
+add_task(function* () {
+ yield addTab(TESTCASE_URI);
+ let { inspector, view } = yield openRuleView();
+ yield selectNode("#outer", inspector);
+
+ info("Insert a new property, which will affect the line numbers");
+ yield addProperty(view, 1, "font-size", "72px");
+
+ yield selectNode("#inner", inspector);
+
+ let value = getRuleViewLinkTextByIndex(view, 3);
+ // Note that this is relative to the <style>.
+ is(value.slice(-3), ":27", "rule line number is 27");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js
new file mode 100644
index 000000000..8d4b436c5
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js
@@ -0,0 +1,106 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that keyframe rules and gutters are displayed correctly in the
+// rule view.
+
+const TEST_URI = URL_ROOT + "doc_keyframeanimation.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield testPacman(inspector, view);
+ yield testBoxy(inspector, view);
+ yield testMoxy(inspector, view);
+});
+
+function* testPacman(inspector, view) {
+ info("Test content and gutter in the keyframes rule of #pacman");
+
+ yield assertKeyframeRules("#pacman", inspector, view, {
+ elementRulesNb: 2,
+ keyframeRulesNb: 2,
+ keyframesRules: ["pacman", "pacman"],
+ keyframeRules: ["100%", "100%"]
+ });
+
+ assertGutters(view, {
+ guttersNbs: 2,
+ gutterHeading: ["Keyframes pacman", "Keyframes pacman"]
+ });
+}
+
+function* testBoxy(inspector, view) {
+ info("Test content and gutter in the keyframes rule of #boxy");
+
+ yield assertKeyframeRules("#boxy", inspector, view, {
+ elementRulesNb: 3,
+ keyframeRulesNb: 3,
+ keyframesRules: ["boxy", "boxy", "boxy"],
+ keyframeRules: ["10%", "20%", "100%"]
+ });
+
+ assertGutters(view, {
+ guttersNbs: 1,
+ gutterHeading: ["Keyframes boxy"]
+ });
+}
+
+function* testMoxy(inspector, view) {
+ info("Test content and gutter in the keyframes rule of #moxy");
+
+ yield assertKeyframeRules("#moxy", inspector, view, {
+ elementRulesNb: 3,
+ keyframeRulesNb: 4,
+ keyframesRules: ["boxy", "boxy", "boxy", "moxy"],
+ keyframeRules: ["10%", "20%", "100%", "100%"]
+ });
+
+ assertGutters(view, {
+ guttersNbs: 2,
+ gutterHeading: ["Keyframes boxy", "Keyframes moxy"]
+ });
+}
+
+function* assertKeyframeRules(selector, inspector, view, expected) {
+ yield selectNode(selector, inspector);
+ let elementStyle = view._elementStyle;
+
+ let rules = {
+ elementRules: elementStyle.rules.filter(rule => !rule.keyframes),
+ keyframeRules: elementStyle.rules.filter(rule => rule.keyframes)
+ };
+
+ is(rules.elementRules.length, expected.elementRulesNb, selector +
+ " has the correct number of non keyframe element rules");
+ is(rules.keyframeRules.length, expected.keyframeRulesNb, selector +
+ " has the correct number of keyframe rules");
+
+ let i = 0;
+ for (let keyframeRule of rules.keyframeRules) {
+ ok(keyframeRule.keyframes.name == expected.keyframesRules[i],
+ keyframeRule.keyframes.name + " has the correct keyframes name");
+ ok(keyframeRule.domRule.keyText == expected.keyframeRules[i],
+ keyframeRule.domRule.keyText + " selector heading is correct");
+ i++;
+ }
+}
+
+function assertGutters(view, expected) {
+ let gutters = view.element.querySelectorAll(".theme-gutter");
+
+ is(gutters.length, expected.guttersNbs,
+ "There are " + gutters.length + " gutter headings");
+
+ let i = 0;
+ for (let gutter of gutters) {
+ is(gutter.textContent, expected.gutterHeading[i],
+ "Correct " + gutter.textContent + " gutter headings");
+ i++;
+ }
+
+ return gutters;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js
new file mode 100644
index 000000000..b7652ecaa
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js
@@ -0,0 +1,92 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that verifies the content of the keyframes rule and property changes
+// to keyframe rules.
+
+const TEST_URI = URL_ROOT + "doc_keyframeanimation.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield testPacman(inspector, view);
+ yield testBoxy(inspector, view);
+});
+
+function* testPacman(inspector, view) {
+ info("Test content in the keyframes rule of #pacman");
+
+ let rules = yield getKeyframeRules("#pacman", inspector, view);
+
+ info("Test text properties for Keyframes #pacman");
+
+ is(convertTextPropsToString(rules.keyframeRules[0].textProps),
+ "left: 750px",
+ "Keyframe pacman (100%) property is correct"
+ );
+
+ // Dynamic changes test disabled because of Bug 1050940
+ // If this part of the test is ever enabled again, it should be changed to
+ // use addProperty (in head.js) and stop using _applyingModifications
+
+ // info("Test dynamic changes to keyframe rule for #pacman");
+
+ // let defaultView = element.ownerDocument.defaultView;
+ // let ruleEditor = view.element.children[5].childNodes[0]._ruleEditor;
+ // ruleEditor.addProperty("opacity", "0", true);
+
+ // yield ruleEditor._applyingModifications;
+ // yield once(element, "animationend");
+
+ // is
+ // (
+ // convertTextPropsToString(rules.keyframeRules[1].textProps),
+ // "left: 750px; opacity: 0",
+ // "Keyframe pacman (100%) property is correct"
+ // );
+
+ // is(defaultView.getComputedStyle(element).getPropertyValue("opacity"), "0",
+ // "Added opacity property should have been used.");
+}
+
+function* testBoxy(inspector, view) {
+ info("Test content in the keyframes rule of #boxy");
+
+ let rules = yield getKeyframeRules("#boxy", inspector, view);
+
+ info("Test text properties for Keyframes #boxy");
+
+ is(convertTextPropsToString(rules.keyframeRules[0].textProps),
+ "background-color: blue",
+ "Keyframe boxy (10%) property is correct"
+ );
+
+ is(convertTextPropsToString(rules.keyframeRules[1].textProps),
+ "background-color: green",
+ "Keyframe boxy (20%) property is correct"
+ );
+
+ is(convertTextPropsToString(rules.keyframeRules[2].textProps),
+ "opacity: 0",
+ "Keyframe boxy (100%) property is correct"
+ );
+}
+
+function convertTextPropsToString(textProps) {
+ return textProps.map(t => t.name + ": " + t.value).join("; ");
+}
+
+function* getKeyframeRules(selector, inspector, view) {
+ yield selectNode(selector, inspector);
+ let elementStyle = view._elementStyle;
+
+ let rules = {
+ elementRules: elementStyle.rules.filter(rule => !rule.keyframes),
+ keyframeRules: elementStyle.rules.filter(rule => rule.keyframes)
+ };
+
+ return rules;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_lineNumbers.js b/devtools/client/inspector/rules/test/browser_rules_lineNumbers.js
new file mode 100644
index 000000000..3b09209f5
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_lineNumbers.js
@@ -0,0 +1,29 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that editing a rule will update the line numbers of subsequent
+// rules in the rule view.
+
+const TESTCASE_URI = URL_ROOT + "doc_ruleLineNumbers.html";
+
+add_task(function* () {
+ yield addTab(TESTCASE_URI);
+ let { inspector, view } = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let bodyRuleEditor = getRuleViewRuleEditor(view, 3);
+ let value = getRuleViewLinkTextByIndex(view, 2);
+ // Note that this is relative to the <style>.
+ is(value.slice(-2), ":6", "initial rule line number is 6");
+
+ let onLocationChanged = once(bodyRuleEditor.rule.domRule, "location-changed");
+ yield addProperty(view, 1, "font-size", "23px");
+ yield onLocationChanged;
+
+ let newBodyTitle = getRuleViewLinkTextByIndex(view, 2);
+ // Note that this is relative to the <style>.
+ is(newBodyTitle.slice(-2), ":7", "updated rule line number is 7");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_livepreview.js b/devtools/client/inspector/rules/test/browser_rules_livepreview.js
new file mode 100644
index 000000000..1f1302a70
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_livepreview.js
@@ -0,0 +1,72 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that changes are previewed when editing a property value.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ display:block;
+ }
+ </style>
+ <div id="testid">Styled Node</div><span>inline element</span>
+`;
+
+// Format
+// {
+// value : what to type in the field
+// expected : expected computed style on the targeted element
+// }
+const TEST_DATA = [
+ {value: "inline", expected: "inline"},
+ {value: "inline-block", expected: "inline-block"},
+
+ // Invalid property values should not apply, and should fall back to default
+ {value: "red", expected: "block"},
+ {value: "something", expected: "block"},
+
+ {escape: true, value: "inline", expected: "block"}
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ for (let data of TEST_DATA) {
+ yield testLivePreviewData(data, view, "#testid");
+ }
+});
+
+function* testLivePreviewData(data, ruleView, selector) {
+ let rule = getRuleViewRuleEditor(ruleView, 1).rule;
+ let propEditor = rule.textProps[0].editor;
+
+ info("Focusing the property value inplace-editor");
+ let editor = yield focusEditableField(ruleView, propEditor.valueSpan);
+ is(inplaceEditor(propEditor.valueSpan), editor,
+ "The focused editor is the value");
+
+ info("Entering value in the editor: " + data.value);
+ let onPreviewDone = ruleView.once("ruleview-changed");
+ EventUtils.sendString(data.value, ruleView.styleWindow);
+ ruleView.throttle.flush();
+ yield onPreviewDone;
+
+ let onValueDone = ruleView.once("ruleview-changed");
+ if (data.escape) {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ } else {
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ yield onValueDone;
+
+ // While the editor is still focused in, the display should have
+ // changed already
+ is((yield getComputedStyleProperty(selector, null, "display")),
+ data.expected,
+ "Element should be previewed as " + data.expected);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_01.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_01.js
new file mode 100644
index 000000000..ab10fadfe
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_01.js
@@ -0,0 +1,56 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view marks overridden rules correctly based on the
+// specificity of the rule.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ }
+ .testclass {
+ background-color: green;
+ }
+ </style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let idRule = getRuleViewRuleEditor(view, 1).rule;
+ let idProp = idRule.textProps[0];
+ is(idProp.name, "background-color",
+ "First ID property should be background-color");
+ is(idProp.value, "blue", "First ID property value should be blue");
+ ok(!idProp.overridden, "ID prop should not be overridden.");
+ ok(!idProp.editor.element.classList.contains("ruleview-overridden"),
+ "ID property editor should not have ruleview-overridden class");
+
+ let classRule = getRuleViewRuleEditor(view, 2).rule;
+ let classProp = classRule.textProps[0];
+ is(classProp.name, "background-color",
+ "First class prop should be background-color");
+ is(classProp.value, "green", "First class property value should be green");
+ ok(classProp.overridden, "Class property should be overridden.");
+ ok(classProp.editor.element.classList.contains("ruleview-overridden"),
+ "Class property editor should have ruleview-overridden class");
+
+ // Override background-color by changing the element style.
+ let elementProp = yield addProperty(view, 0, "background-color", "purple");
+
+ ok(!elementProp.overridden,
+ "Element style property should not be overridden");
+ ok(idProp.overridden, "ID property should be overridden");
+ ok(idProp.editor.element.classList.contains("ruleview-overridden"),
+ "ID property editor should have ruleview-overridden class");
+ ok(classProp.overridden, "Class property should be overridden");
+ ok(classProp.editor.element.classList.contains("ruleview-overridden"),
+ "Class property editor should have ruleview-overridden class");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_02.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_02.js
new file mode 100644
index 000000000..c71fc7211
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_02.js
@@ -0,0 +1,45 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view marks overridden rules correctly for short hand
+// properties and the computed list properties
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ margin-left: 1px;
+ }
+ .testclass {
+ margin: 2px;
+ }
+ </style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testMarkOverridden(inspector, view);
+});
+
+function* testMarkOverridden(inspector, view) {
+ let elementStyle = view._elementStyle;
+
+ let classRule = elementStyle.rules[2];
+ let classProp = classRule.textProps[0];
+ ok(!classProp.overridden,
+ "Class prop shouldn't be overridden, some props are still being used.");
+
+ for (let computed of classProp.computed) {
+ if (computed.name.indexOf("margin-left") == 0) {
+ ok(computed.overridden, "margin-left props should be overridden.");
+ } else {
+ ok(!computed.overridden,
+ "Non-margin-left props should not be overridden.");
+ }
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_03.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_03.js
new file mode 100644
index 000000000..b99bab8b4
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_03.js
@@ -0,0 +1,41 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view marks overridden rules correctly based on the
+// priority for the rule
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ }
+ .testclass {
+ background-color: green !important;
+ }
+ </style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let idRule = getRuleViewRuleEditor(view, 1).rule;
+ let idProp = idRule.textProps[0];
+ ok(idProp.overridden, "Not-important rule should be overridden.");
+
+ let classRule = getRuleViewRuleEditor(view, 2).rule;
+ let classProp = classRule.textProps[0];
+ ok(!classProp.overridden, "Important rule should not be overridden.");
+
+ ok(idProp.overridden, "ID property should be overridden.");
+
+ // FIXME: re-enable these 2 assertions when bug 1247737 is fixed.
+ // let elementProp = yield addProperty(view, 0, "background-color", "purple");
+ // ok(!elementProp.overridden, "New important prop should not be overriden.");
+ // ok(classProp.overridden, "Class property should be overridden.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_04.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_04.js
new file mode 100644
index 000000000..fbce1ebf4
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_04.js
@@ -0,0 +1,36 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view marks overridden rules correctly if a property gets
+// disabled
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: blue;
+ }
+ .testclass {
+ background-color: green;
+ }
+ </style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let idRule = getRuleViewRuleEditor(view, 1).rule;
+ let idProp = idRule.textProps[0];
+
+ yield togglePropStatus(view, idProp);
+
+ let classRule = getRuleViewRuleEditor(view, 2).rule;
+ let classProp = classRule.textProps[0];
+ ok(!classProp.overridden,
+ "Class prop should not be overridden after id prop was disabled.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_05.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_05.js
new file mode 100644
index 000000000..11ecd72ff
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_05.js
@@ -0,0 +1,33 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view marks overridden rules correctly based on the
+// order of the property.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ background-color: green;
+ }
+ </style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+
+ yield addProperty(view, 1, "background-color", "red");
+
+ let firstProp = rule.textProps[0];
+ let secondProp = rule.textProps[1];
+
+ ok(firstProp.overridden, "First property should be overridden.");
+ ok(!secondProp.overridden, "Second property should not be overridden.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_06.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_06.js
new file mode 100644
index 000000000..c2e71fe49
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_06.js
@@ -0,0 +1,60 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view marks overridden rules correctly after
+// editing the selector.
+
+const TEST_URI = `
+ <style type='text/css'>
+ div {
+ background-color: blue;
+ background-color: chartreuse;
+ }
+ </style>
+ <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testMarkOverridden(inspector, view);
+});
+
+function* testMarkOverridden(inspector, view) {
+ let elementStyle = view._elementStyle;
+ let rule = elementStyle.rules[1];
+ checkProperties(rule);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ info("Focusing an existing selector name in the rule-view");
+ let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+ info("Entering a new selector name and committing");
+ editor.input.value = "div[class]";
+
+ let onRuleViewChanged = once(view, "ruleview-changed");
+ info("Entering the commit key");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ view.searchField.focus();
+ checkProperties(rule);
+}
+
+// A helper to perform a repeated set of checks.
+function checkProperties(rule) {
+ let prop = rule.textProps[0];
+ is(prop.name, "background-color",
+ "First property should be background-color");
+ is(prop.value, "blue", "First property value should be blue");
+ ok(prop.overridden, "prop should be overridden.");
+ prop = rule.textProps[1];
+ is(prop.name, "background-color",
+ "Second property should be background-color");
+ is(prop.value, "chartreuse", "First property value should be chartreuse");
+ ok(!prop.overridden, "prop should not be overridden.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_07.js b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_07.js
new file mode 100644
index 000000000..9480ddd47
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_07.js
@@ -0,0 +1,72 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view marks overridden rules correctly based on the
+// specificity of the rule.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ margin-left: 23px;
+ }
+
+ div {
+ margin-right: 23px;
+ margin-left: 1px !important;
+ }
+
+ body {
+ margin-right: 1px !important;
+ font-size: 79px;
+ }
+
+ span {
+ font-size: 12px;
+ }
+ </style>
+ <body>
+ <span>
+ <div id='testid' class='testclass'>Styled Node</div>
+ </span>
+ </body>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testMarkOverridden(inspector, view);
+});
+
+function* testMarkOverridden(inspector, view) {
+ let elementStyle = view._elementStyle;
+
+ let RESULTS = [
+ // We skip the first element
+ [],
+ [{name: "margin-left", value: "23px", overridden: true}],
+ [{name: "margin-right", value: "23px", overridden: false},
+ {name: "margin-left", value: "1px", overridden: false}],
+ [{name: "font-size", value: "12px", overridden: false}],
+ [{name: "margin-right", value: "1px", overridden: true},
+ {name: "font-size", value: "79px", overridden: true}]
+ ];
+
+ for (let i = 1; i < RESULTS.length; ++i) {
+ let idRule = elementStyle.rules[i];
+
+ for (let propIndex in RESULTS[i]) {
+ let expected = RESULTS[i][propIndex];
+ let prop = idRule.textProps[propIndex];
+
+ info("Checking rule " + i + ", property " + propIndex);
+
+ is(prop.name, expected.name, "check property name");
+ is(prop.value, expected.value, "check property value");
+ is(prop.overridden, expected.overridden, "check property overridden");
+ }
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_mathml-element.js b/devtools/client/inspector/rules/test/browser_rules_mathml-element.js
new file mode 100644
index 000000000..f8a1e8572
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_mathml-element.js
@@ -0,0 +1,53 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule-view displays correctly on MathML elements.
+
+const TEST_URI = `
+ <div>
+ <math xmlns=\http://www.w3.org/1998/Math/MathML\>
+ <mfrac>
+ <msubsup>
+ <mi>a</mi>
+ <mi>i</mi>
+ <mi>j</mi>
+ </msubsup>
+ <msub>
+ <mi>x</mi>
+ <mn>0</mn>
+ </msub>
+ </mfrac>
+ </math>
+ </div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ info("Select the DIV node and verify the rule-view shows rules");
+ yield selectNode("div", inspector);
+ ok(view.element.querySelectorAll(".ruleview-rule").length,
+ "The rule-view shows rules for the div element");
+
+ info("Select various MathML nodes and verify the rule-view is empty");
+ yield selectNode("math", inspector);
+ ok(!view.element.querySelectorAll(".ruleview-rule").length,
+ "The rule-view is empty for the math element");
+
+ yield selectNode("msubsup", inspector);
+ ok(!view.element.querySelectorAll(".ruleview-rule").length,
+ "The rule-view is empty for the msubsup element");
+
+ yield selectNode("mn", inspector);
+ ok(!view.element.querySelectorAll(".ruleview-rule").length,
+ "The rule-view is empty for the mn element");
+
+ info("Select again the DIV node and verify the rule-view shows rules");
+ yield selectNode("div", inspector);
+ ok(view.element.querySelectorAll(".ruleview-rule").length,
+ "The rule-view shows rules for the div element");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_media-queries.js b/devtools/client/inspector/rules/test/browser_rules_media-queries.js
new file mode 100644
index 000000000..57ab19163
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_media-queries.js
@@ -0,0 +1,26 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that we correctly display appropriate media query titles in the
+// rule view.
+
+const TEST_URI = URL_ROOT + "doc_media_queries.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ let elementStyle = view._elementStyle;
+
+ let inline = STYLE_INSPECTOR_L10N.getStr("rule.sourceInline");
+
+ is(elementStyle.rules.length, 3, "Should have 3 rules.");
+ is(elementStyle.rules[0].title, inline, "check rule 0 title");
+ is(elementStyle.rules[1].title, inline +
+ ":9 @media screen and (min-width: 1px)", "check rule 1 title");
+ is(elementStyle.rules[2].title, inline + ":2", "check rule 2 title");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-duplicates.js b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-duplicates.js
new file mode 100644
index 000000000..c820dd73f
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-duplicates.js
@@ -0,0 +1,68 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule-view behaves correctly when entering mutliple and/or
+// unfinished properties/values in inplace-editors
+
+const TEST_URI = "<div>Test Element</div>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ // Note that we wait for a markup mutation here because this new rule will end
+ // up creating a style attribute on the node shown in the markup-view.
+ // (we also wait for the rule-view to refresh).
+ let onMutation = inspector.once("markupmutation");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ yield createNewRuleViewProperty(ruleEditor,
+ "color:red;color:orange;color:yellow;color:green;color:blue;color:indigo;" +
+ "color:violet;");
+ yield onMutation;
+ yield onRuleViewChanged;
+
+ is(ruleEditor.rule.textProps.length, 7,
+ "Should have created new text properties.");
+ is(ruleEditor.propertyList.children.length, 8,
+ "Should have created new property editors.");
+
+ is(ruleEditor.rule.textProps[0].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[0].value, "red",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[1].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[1].value, "orange",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[2].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[2].value, "yellow",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[3].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[3].value, "green",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[4].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[4].value, "blue",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[5].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[5].value, "indigo",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[6].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[6].value, "violet",
+ "Should have correct property value");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-priority.js b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-priority.js
new file mode 100644
index 000000000..f7d98b768
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-priority.js
@@ -0,0 +1,47 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule-view behaves correctly when entering mutliple and/or
+// unfinished properties/values in inplace-editors.
+
+const TEST_URI = "<div>Test Element</div>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ // Note that we wait for a markup mutation here because this new rule will end
+ // up creating a style attribute on the node shown in the markup-view.
+ // (we also wait for the rule-view to refresh).
+ let onMutation = inspector.once("markupmutation");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ yield createNewRuleViewProperty(ruleEditor,
+ "color:red;width:100px;height: 100px;");
+ yield onMutation;
+ yield onRuleViewChanged;
+
+ is(ruleEditor.rule.textProps.length, 3,
+ "Should have created new text properties.");
+ is(ruleEditor.propertyList.children.length, 4,
+ "Should have created new property editors.");
+
+ is(ruleEditor.rule.textProps[0].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[0].value, "red",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[1].name, "width",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[1].value, "100px",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[2].name, "height",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[2].value, "100px",
+ "Should have correct property value");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js
new file mode 100644
index 000000000..deaf16029
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_01.js
@@ -0,0 +1,62 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule-view behaves correctly when entering multiple and/or
+// unfinished properties/values in inplace-editors
+
+const TEST_URI = "<div>Test Element</div>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+ yield testCreateNewMultiUnfinished(inspector, view);
+});
+
+function* testCreateNewMultiUnfinished(inspector, view) {
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let onMutation = inspector.once("markupmutation");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ yield createNewRuleViewProperty(ruleEditor,
+ "color:blue;background : orange ; text-align:center; border-color: ");
+ yield onMutation;
+ yield onRuleViewChanged;
+
+ is(ruleEditor.rule.textProps.length, 4,
+ "Should have created new text properties.");
+ is(ruleEditor.propertyList.children.length, 4,
+ "Should have created property editors.");
+
+ EventUtils.sendString("red", view.styleWindow);
+ onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onRuleViewChanged;
+
+ is(ruleEditor.rule.textProps.length, 4,
+ "Should have the same number of text properties.");
+ is(ruleEditor.propertyList.children.length, 5,
+ "Should have added the changed value editor.");
+
+ is(ruleEditor.rule.textProps[0].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[0].value, "blue",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[1].name, "background",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[1].value, "orange",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[2].name, "text-align",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[2].value, "center",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[3].name, "border-color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[3].value, "red",
+ "Should have correct property value");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js
new file mode 100644
index 000000000..dd1360b96
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js
@@ -0,0 +1,71 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule-view behaves correctly when entering mutliple and/or
+// unfinished properties/values in inplace-editors
+
+const TEST_URI = "<div>Test Element</div>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ // Turn off throttling, which can cause intermittents. Throttling is used by
+ // the TextPropertyEditor.
+ view.throttle = () => {};
+
+ yield selectNode("div", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ // Note that we wait for a markup mutation here because this new rule will end
+ // up creating a style attribute on the node shown in the markup-view.
+ // (we also wait for the rule-view to refresh).
+ let onMutation = inspector.once("markupmutation");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ yield createNewRuleViewProperty(ruleEditor, "width: 100px; heig");
+ yield onMutation;
+ yield onRuleViewChanged;
+
+ is(ruleEditor.rule.textProps.length, 2,
+ "Should have created a new text property.");
+ is(ruleEditor.propertyList.children.length, 2,
+ "Should have created a property editor.");
+
+ // Value is focused, lets add multiple rules here and make sure they get added
+ onMutation = inspector.once("markupmutation");
+ onRuleViewChanged = view.once("ruleview-changed");
+ let valueEditor = ruleEditor.propertyList.children[1]
+ .querySelector(".styleinspector-propertyeditor");
+ valueEditor.value = "10px;background:orangered;color: black;";
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onMutation;
+ yield onRuleViewChanged;
+
+ is(ruleEditor.rule.textProps.length, 4,
+ "Should have added the changed value.");
+ is(ruleEditor.propertyList.children.length, 5,
+ "Should have added the changed value editor.");
+
+ is(ruleEditor.rule.textProps[0].name, "width",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[0].value, "100px",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[1].name, "heig",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[1].value, "10px",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[2].name, "background",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[2].value, "orangered",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[3].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[3].value, "black",
+ "Should have correct property value");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple_properties_01.js b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_01.js
new file mode 100644
index 000000000..2801df652
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_01.js
@@ -0,0 +1,53 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule-view behaves correctly when entering mutliple and/or
+// unfinished properties/values in inplace-editors.
+
+const TEST_URI = "<div>Test Element</div>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ // Note that we wait for a markup mutation here because this new rule will end
+ // up creating a style attribute on the node shown in the markup-view.
+ // (we also wait for the rule-view to refresh).
+ let onMutation = inspector.once("markupmutation");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ yield createNewRuleViewProperty(ruleEditor,
+ "color:blue;background : orange ; text-align:center; " +
+ "border-color: green;");
+ yield onMutation;
+ yield onRuleViewChanged;
+
+ is(ruleEditor.rule.textProps.length, 4,
+ "Should have created a new text property.");
+ is(ruleEditor.propertyList.children.length, 5,
+ "Should have created a new property editor.");
+
+ is(ruleEditor.rule.textProps[0].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[0].value, "blue",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[1].name, "background",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[1].value, "orange",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[2].name, "text-align",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[2].value, "center",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[3].name, "border-color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[3].value, "green",
+ "Should have correct property value");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js
new file mode 100644
index 000000000..ce6f1909f
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple_properties_02.js
@@ -0,0 +1,54 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule-view behaves correctly when entering mutliple and/or
+// unfinished properties/values in inplace-editors
+
+const TEST_URI = "<div>Test Element</div>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let onDone = view.once("ruleview-changed");
+ yield createNewRuleViewProperty(ruleEditor, "width:");
+ yield onDone;
+
+ is(ruleEditor.rule.textProps.length, 1,
+ "Should have created a new text property.");
+ is(ruleEditor.propertyList.children.length, 1,
+ "Should have created a property editor.");
+
+ // Value is focused, lets add multiple rules here and make sure they get added
+ onDone = view.once("ruleview-changed");
+ let onMutation = inspector.once("markupmutation");
+ let input = view.styleDocument.activeElement;
+ input.value = "height: 10px;color:blue";
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onMutation;
+ yield onDone;
+
+ is(ruleEditor.rule.textProps.length, 2,
+ "Should have added the changed value.");
+ is(ruleEditor.propertyList.children.length, 3,
+ "Should have added the changed value editor.");
+
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+ is(ruleEditor.propertyList.children.length, 2,
+ "Should have removed the value editor.");
+
+ is(ruleEditor.rule.textProps[0].name, "width",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[0].value, "height: 10px",
+ "Should have correct property value");
+
+ is(ruleEditor.rule.textProps[1].name, "color",
+ "Should have correct property name");
+ is(ruleEditor.rule.textProps[1].value, "blue",
+ "Should have correct property value");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_original-source-link.js b/devtools/client/inspector/rules/test/browser_rules_original-source-link.js
new file mode 100644
index 000000000..09dad9a86
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_original-source-link.js
@@ -0,0 +1,85 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the stylesheet links in the rule view are correct when source maps
+// are involved.
+
+const TESTCASE_URI = URL_ROOT + "doc_sourcemaps.html";
+const PREF = "devtools.styleeditor.source-maps-enabled";
+const SCSS_LOC = "doc_sourcemaps.scss:4";
+const CSS_LOC = "doc_sourcemaps.css:1";
+
+add_task(function* () {
+ info("Setting the " + PREF + " pref to true");
+ Services.prefs.setBoolPref(PREF, true);
+
+ yield addTab(TESTCASE_URI);
+ let {toolbox, inspector, view} = yield openRuleView();
+
+ info("Selecting the test node");
+ yield selectNode("div", inspector);
+
+ yield verifyLinkText(SCSS_LOC, view);
+
+ info("Setting the " + PREF + " pref to false");
+ Services.prefs.setBoolPref(PREF, false);
+ yield verifyLinkText(CSS_LOC, view);
+
+ info("Setting the " + PREF + " pref to true again");
+ Services.prefs.setBoolPref(PREF, true);
+
+ yield testClickingLink(toolbox, view);
+ yield checkDisplayedStylesheet(toolbox);
+
+ info("Clearing the " + PREF + " pref");
+ Services.prefs.clearUserPref(PREF);
+});
+
+function* testClickingLink(toolbox, view) {
+ info("Listening for switch to the style editor");
+ let onStyleEditorReady = toolbox.once("styleeditor-ready");
+
+ info("Finding the stylesheet link and clicking it");
+ let link = getRuleViewLinkByIndex(view, 1);
+ link.scrollIntoView();
+ link.click();
+ yield onStyleEditorReady;
+}
+
+function checkDisplayedStylesheet(toolbox) {
+ let def = defer();
+
+ let panel = toolbox.getCurrentPanel();
+ panel.UI.on("editor-selected", (event, editor) => {
+ // The style editor selects the first sheet at first load before
+ // selecting the desired sheet.
+ if (editor.styleSheet.href.endsWith("scss")) {
+ info("Original source editor selected");
+ editor.getSourceEditor().then(editorSelected)
+ .then(def.resolve, def.reject);
+ }
+ });
+
+ return def.promise;
+}
+
+function editorSelected(editor) {
+ let href = editor.styleSheet.href;
+ ok(href.endsWith("doc_sourcemaps.scss"),
+ "selected stylesheet is correct one");
+
+ let {line} = editor.sourceEditor.getCursor();
+ is(line, 3, "cursor is at correct line number in original source");
+}
+
+function verifyLinkText(text, view) {
+ info("Verifying that the rule-view stylesheet link is " + text);
+ let label = getRuleViewLinkByIndex(view, 1)
+ .querySelector(".ruleview-rule-source-label");
+ return waitForSuccess(function* () {
+ return label.textContent == text;
+ }, "Link text changed to display correct location: " + text);
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js
new file mode 100644
index 000000000..e98b5437c
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js
@@ -0,0 +1,260 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that pseudoelements are displayed correctly in the rule view
+
+const TEST_URI = URL_ROOT + "doc_pseudoelement.html";
+const PSEUDO_PREF = "devtools.inspector.show_pseudo_elements";
+
+add_task(function* () {
+ yield pushPref(PSEUDO_PREF, true);
+
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+
+ yield testTopLeft(inspector, view);
+ yield testTopRight(inspector, view);
+ yield testBottomRight(inspector, view);
+ yield testBottomLeft(inspector, view);
+ yield testParagraph(inspector, view);
+ yield testBody(inspector, view);
+});
+
+function* testTopLeft(inspector, view) {
+ let id = "#topleft";
+ let rules = yield assertPseudoElementRulesNumbers(id,
+ inspector, view, {
+ elementRulesNb: 4,
+ firstLineRulesNb: 2,
+ firstLetterRulesNb: 1,
+ selectionRulesNb: 0,
+ afterRulesNb: 1,
+ beforeRulesNb: 2
+ }
+ );
+
+ let gutters = assertGutters(view);
+
+ info("Make sure that clicking on the twisty hides pseudo elements");
+ let expander = gutters[0].querySelector(".ruleview-expander");
+ ok(!view.element.children[1].hidden, "Pseudo Elements are expanded");
+
+ expander.click();
+ ok(view.element.children[1].hidden,
+ "Pseudo Elements are collapsed by twisty");
+
+ expander.click();
+ ok(!view.element.children[1].hidden, "Pseudo Elements are expanded again");
+
+ info("Make sure that dblclicking on the header container also toggles " +
+ "the pseudo elements");
+ EventUtils.synthesizeMouseAtCenter(gutters[0], {clickCount: 2},
+ view.styleWindow);
+ ok(view.element.children[1].hidden,
+ "Pseudo Elements are collapsed by dblclicking");
+
+ let elementRuleView = getRuleViewRuleEditor(view, 3);
+
+ let elementFirstLineRule = rules.firstLineRules[0];
+ let elementFirstLineRuleView =
+ [...view.element.children[1].children].filter(e => {
+ return e._ruleEditor && e._ruleEditor.rule === elementFirstLineRule;
+ })[0]._ruleEditor;
+
+ is(convertTextPropsToString(elementFirstLineRule.textProps),
+ "color: orange",
+ "TopLeft firstLine properties are correct");
+
+ let onAdded = view.once("ruleview-changed");
+ let firstProp = elementFirstLineRuleView.addProperty("background-color",
+ "rgb(0, 255, 0)", "", true);
+ yield onAdded;
+
+ onAdded = view.once("ruleview-changed");
+ let secondProp = elementFirstLineRuleView.addProperty("font-style",
+ "italic", "", true);
+ yield onAdded;
+
+ is(firstProp,
+ elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 2],
+ "First added property is on back of array");
+ is(secondProp,
+ elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 1],
+ "Second added property is on back of array");
+
+ is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
+ "rgb(0, 255, 0)", "Added property should have been used.");
+ is((yield getComputedStyleProperty(id, ":first-line", "font-style")),
+ "italic", "Added property should have been used.");
+ is((yield getComputedStyleProperty(id, null, "text-decoration")),
+ "none", "Added property should not apply to element");
+
+ yield togglePropStatus(view, firstProp);
+
+ is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
+ "rgb(255, 0, 0)", "Disabled property should now have been used.");
+ is((yield getComputedStyleProperty(id, null, "background-color")),
+ "rgb(221, 221, 221)", "Added property should not apply to element");
+
+ yield togglePropStatus(view, firstProp);
+
+ is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
+ "rgb(0, 255, 0)", "Added property should have been used.");
+ is((yield getComputedStyleProperty(id, null, "text-decoration")),
+ "none", "Added property should not apply to element");
+
+ onAdded = view.once("ruleview-changed");
+ firstProp = elementRuleView.addProperty("background-color",
+ "rgb(0, 0, 255)", "", true);
+ yield onAdded;
+
+ is((yield getComputedStyleProperty(id, null, "background-color")),
+ "rgb(0, 0, 255)", "Added property should have been used.");
+ is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
+ "rgb(0, 255, 0)", "Added prop does not apply to pseudo");
+}
+
+function* testTopRight(inspector, view) {
+ yield assertPseudoElementRulesNumbers("#topright", inspector, view, {
+ elementRulesNb: 4,
+ firstLineRulesNb: 1,
+ firstLetterRulesNb: 1,
+ selectionRulesNb: 0,
+ beforeRulesNb: 2,
+ afterRulesNb: 1
+ });
+
+ let gutters = assertGutters(view);
+
+ let expander = gutters[0].querySelector(".ruleview-expander");
+ ok(!view.element.firstChild.classList.contains("show-expandable-container"),
+ "Pseudo Elements remain collapsed after switching element");
+
+ expander.scrollIntoView();
+ expander.click();
+ ok(!view.element.children[1].hidden,
+ "Pseudo Elements are shown again after clicking twisty");
+}
+
+function* testBottomRight(inspector, view) {
+ yield assertPseudoElementRulesNumbers("#bottomright", inspector, view, {
+ elementRulesNb: 4,
+ firstLineRulesNb: 1,
+ firstLetterRulesNb: 1,
+ selectionRulesNb: 0,
+ beforeRulesNb: 3,
+ afterRulesNb: 1
+ });
+}
+
+function* testBottomLeft(inspector, view) {
+ yield assertPseudoElementRulesNumbers("#bottomleft", inspector, view, {
+ elementRulesNb: 4,
+ firstLineRulesNb: 1,
+ firstLetterRulesNb: 1,
+ selectionRulesNb: 0,
+ beforeRulesNb: 2,
+ afterRulesNb: 1
+ });
+}
+
+function* testParagraph(inspector, view) {
+ let rules =
+ yield assertPseudoElementRulesNumbers("#bottomleft p", inspector, view, {
+ elementRulesNb: 3,
+ firstLineRulesNb: 1,
+ firstLetterRulesNb: 1,
+ selectionRulesNb: 1,
+ beforeRulesNb: 0,
+ afterRulesNb: 0
+ });
+
+ assertGutters(view);
+
+ let elementFirstLineRule = rules.firstLineRules[0];
+ is(convertTextPropsToString(elementFirstLineRule.textProps),
+ "background: blue",
+ "Paragraph first-line properties are correct");
+
+ let elementFirstLetterRule = rules.firstLetterRules[0];
+ is(convertTextPropsToString(elementFirstLetterRule.textProps),
+ "color: red; font-size: 130%",
+ "Paragraph first-letter properties are correct");
+
+ let elementSelectionRule = rules.selectionRules[0];
+ is(convertTextPropsToString(elementSelectionRule.textProps),
+ "color: white; background: black",
+ "Paragraph first-letter properties are correct");
+}
+
+function* testBody(inspector, view) {
+ yield testNode("body", inspector, view);
+
+ let gutters = getGutters(view);
+ is(gutters.length, 0, "There are no gutter headings");
+}
+
+function convertTextPropsToString(textProps) {
+ return textProps.map(t => t.name + ": " + t.value).join("; ");
+}
+
+function* testNode(selector, inspector, view) {
+ yield selectNode(selector, inspector);
+ let elementStyle = view._elementStyle;
+ return elementStyle;
+}
+
+function* assertPseudoElementRulesNumbers(selector, inspector, view, ruleNbs) {
+ let elementStyle = yield testNode(selector, inspector, view);
+
+ let rules = {
+ elementRules: elementStyle.rules.filter(rule => !rule.pseudoElement),
+ firstLineRules: elementStyle.rules.filter(rule =>
+ rule.pseudoElement === ":first-line"),
+ firstLetterRules: elementStyle.rules.filter(rule =>
+ rule.pseudoElement === ":first-letter"),
+ selectionRules: elementStyle.rules.filter(rule =>
+ rule.pseudoElement === ":-moz-selection"),
+ beforeRules: elementStyle.rules.filter(rule =>
+ rule.pseudoElement === ":before"),
+ afterRules: elementStyle.rules.filter(rule =>
+ rule.pseudoElement === ":after"),
+ };
+
+ is(rules.elementRules.length, ruleNbs.elementRulesNb,
+ selector + " has the correct number of non pseudo element rules");
+ is(rules.firstLineRules.length, ruleNbs.firstLineRulesNb,
+ selector + " has the correct number of :first-line rules");
+ is(rules.firstLetterRules.length, ruleNbs.firstLetterRulesNb,
+ selector + " has the correct number of :first-letter rules");
+ is(rules.selectionRules.length, ruleNbs.selectionRulesNb,
+ selector + " has the correct number of :selection rules");
+ is(rules.beforeRules.length, ruleNbs.beforeRulesNb,
+ selector + " has the correct number of :before rules");
+ is(rules.afterRules.length, ruleNbs.afterRulesNb,
+ selector + " has the correct number of :after rules");
+
+ return rules;
+}
+
+function getGutters(view) {
+ return view.element.querySelectorAll(".theme-gutter");
+}
+
+function assertGutters(view) {
+ let gutters = getGutters(view);
+
+ is(gutters.length, 3,
+ "There are 3 gutter headings");
+ is(gutters[0].textContent, "Pseudo-elements",
+ "Gutter heading is correct");
+ is(gutters[1].textContent, "This Element",
+ "Gutter heading is correct");
+ is(gutters[2].textContent, "Inherited from body",
+ "Gutter heading is correct");
+
+ return gutters;
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js
new file mode 100644
index 000000000..f69c328db
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js
@@ -0,0 +1,29 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that pseudoelements are displayed correctly in the markup view.
+
+const TEST_URI = URL_ROOT + "doc_pseudoelement.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector} = yield openRuleView();
+
+ let node = yield getNodeFront("#topleft", inspector);
+ let children = yield inspector.markup.walker.children(node);
+
+ is(children.nodes.length, 3, "Element has correct number of children");
+
+ let beforeElement = children.nodes[0];
+ is(beforeElement.tagName, "_moz_generated_content_before",
+ "tag name is correct");
+ yield selectNode(beforeElement, inspector);
+
+ let afterElement = children.nodes[children.nodes.length - 1];
+ is(afterElement.tagName, "_moz_generated_content_after",
+ "tag name is correct");
+ yield selectNode(afterElement, inspector);
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js b/devtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js
new file mode 100644
index 000000000..d795ba5f3
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js
@@ -0,0 +1,131 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view pseudo lock options work properly.
+
+const TEST_URI = `
+ <style type='text/css'>
+ div {
+ color: red;
+ }
+ div:hover {
+ color: blue;
+ }
+ div:active {
+ color: yellow;
+ }
+ div:focus {
+ color: green;
+ }
+ </style>
+ <div>test div</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ yield assertPseudoPanelClosed(view);
+
+ info("Toggle the pseudo class panel open");
+ view.pseudoClassToggle.click();
+ yield assertPseudoPanelOpened(view);
+
+ info("Toggle each pseudo lock and check that the pseudo lock is added");
+ yield togglePseudoClass(inspector, view.hoverCheckbox);
+ yield assertPseudoAdded(inspector, view, ":hover", 3, 1);
+ yield togglePseudoClass(inspector, view.hoverCheckbox);
+ yield assertPseudoRemoved(inspector, view, 2);
+
+ yield togglePseudoClass(inspector, view.activeCheckbox);
+ yield assertPseudoAdded(inspector, view, ":active", 3, 1);
+ yield togglePseudoClass(inspector, view.activeCheckbox);
+ yield assertPseudoRemoved(inspector, view, 2);
+
+ yield togglePseudoClass(inspector, view.focusCheckbox);
+ yield assertPseudoAdded(inspector, view, ":focus", 3, 1);
+ yield togglePseudoClass(inspector, view.focusCheckbox);
+ yield assertPseudoRemoved(inspector, view, 2);
+
+ info("Toggle all pseudo lock and check that the pseudo lock is added");
+ yield togglePseudoClass(inspector, view.hoverCheckbox);
+ yield togglePseudoClass(inspector, view.activeCheckbox);
+ yield togglePseudoClass(inspector, view.focusCheckbox);
+ yield assertPseudoAdded(inspector, view, ":focus", 5, 1);
+ yield assertPseudoAdded(inspector, view, ":active", 5, 2);
+ yield assertPseudoAdded(inspector, view, ":hover", 5, 3);
+ yield togglePseudoClass(inspector, view.hoverCheckbox);
+ yield togglePseudoClass(inspector, view.activeCheckbox);
+ yield togglePseudoClass(inspector, view.focusCheckbox);
+ yield assertPseudoRemoved(inspector, view, 2);
+
+ info("Select a null element");
+ yield view.selectElement(null);
+ ok(!view.hoverCheckbox.checked && view.hoverCheckbox.disabled,
+ ":hover checkbox is unchecked and disabled");
+ ok(!view.activeCheckbox.checked && view.activeCheckbox.disabled,
+ ":active checkbox is unchecked and disabled");
+ ok(!view.focusCheckbox.checked && view.focusCheckbox.disabled,
+ ":focus checkbox is unchecked and disabled");
+
+ info("Toggle the pseudo class panel close");
+ view.pseudoClassToggle.click();
+ yield assertPseudoPanelClosed(view);
+});
+
+function* togglePseudoClass(inspector, pseudoClassOption) {
+ info("Toggle the pseudoclass, wait for it to be applied");
+ let onRefresh = inspector.once("rule-view-refreshed");
+ pseudoClassOption.click();
+ yield onRefresh;
+}
+
+function* assertPseudoAdded(inspector, view, pseudoClass, numRules,
+ childIndex) {
+ info("Check that the ruleview contains the pseudo-class rule");
+ is(view.element.children.length, numRules,
+ "Should have " + numRules + " rules.");
+ is(getRuleViewRuleEditor(view, childIndex).rule.selectorText,
+ "div" + pseudoClass, "rule view is showing " + pseudoClass + " rule");
+}
+
+function* assertPseudoRemoved(inspector, view, numRules) {
+ info("Check that the ruleview no longer contains the pseudo-class rule");
+ is(view.element.children.length, numRules,
+ "Should have " + numRules + " rules.");
+ is(getRuleViewRuleEditor(view, 1).rule.selectorText, "div",
+ "Second rule is div");
+}
+
+function* assertPseudoPanelOpened(view) {
+ info("Check the opened state of the pseudo class panel");
+
+ ok(!view.pseudoClassPanel.hidden, "Pseudo Class Panel Opened");
+ ok(!view.hoverCheckbox.disabled, ":hover checkbox is not disabled");
+ ok(!view.activeCheckbox.disabled, ":active checkbox is not disabled");
+ ok(!view.focusCheckbox.disabled, ":focus checkbox is not disabled");
+
+ is(view.hoverCheckbox.getAttribute("tabindex"), "0",
+ ":hover checkbox has a tabindex of 0");
+ is(view.activeCheckbox.getAttribute("tabindex"), "0",
+ ":active checkbox has a tabindex of 0");
+ is(view.focusCheckbox.getAttribute("tabindex"), "0",
+ ":focus checkbox has a tabindex of 0");
+}
+
+function* assertPseudoPanelClosed(view) {
+ info("Check the closed state of the pseudo clas panel");
+
+ ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Hidden");
+
+ is(view.hoverCheckbox.getAttribute("tabindex"), "-1",
+ ":hover checkbox has a tabindex of -1");
+ is(view.activeCheckbox.getAttribute("tabindex"), "-1",
+ ":active checkbox has a tabindex of -1");
+ is(view.focusCheckbox.getAttribute("tabindex"), "-1",
+ ":focus checkbox has a tabindex of -1");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-no-flicker.js b/devtools/client/inspector/rules/test/browser_rules_refresh-no-flicker.js
new file mode 100644
index 000000000..25ea3d972
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_refresh-no-flicker.js
@@ -0,0 +1,39 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule view does not go blank while selecting a new node.
+
+const TESTCASE_URI = "data:text/html;charset=utf-8," +
+ "<div id=\"testdiv\" style=\"font-size:10px;\">" +
+ "Test div!</div>";
+
+add_task(function* () {
+ yield addTab(TESTCASE_URI);
+
+ info("Opening the rule view and selecting the test node");
+ let {inspector, view} = yield openRuleView();
+ let testdiv = yield getNodeFront("#testdiv", inspector);
+ yield selectNode(testdiv, inspector);
+
+ let htmlBefore = view.element.innerHTML;
+ ok(htmlBefore.indexOf("font-size") > -1,
+ "The rule view should contain a font-size property.");
+
+ // Do the selectNode call manually, because otherwise it's hard to guarantee
+ // that we can make the below checks at a reasonable time.
+ info("refreshing the node");
+ let p = view.selectElement(testdiv, true);
+ is(view.element.innerHTML, htmlBefore,
+ "The rule view is unchanged during selection.");
+ ok(view.element.classList.contains("non-interactive"),
+ "The rule view is marked non-interactive.");
+ yield p;
+
+ info("node refreshed");
+ ok(!view.element.classList.contains("non-interactive"),
+ "The rule view is marked interactive again.");
+});
+
diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_01.js b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_01.js
new file mode 100644
index 000000000..381a6bda2
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_01.js
@@ -0,0 +1,61 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that changing the current element's attributes refreshes the rule-view
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ background-color: blue;
+ }
+ .testclass {
+ background-color: green;
+ }
+ </style>
+ <div id="testid" class="testclass" style="margin-top: 1px; padding-top: 5px;">
+ Styled Node
+ </div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view, testActor} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Checking that the rule-view has the element, #testid and " +
+ ".testclass selectors");
+ checkRuleViewContent(view, ["element", "#testid", ".testclass"]);
+
+ info("Changing the node's ID attribute and waiting for the " +
+ "rule-view refresh");
+ let ruleViewRefreshed = inspector.once("rule-view-refreshed");
+ yield testActor.setAttribute("#testid", "id", "differentid");
+ yield ruleViewRefreshed;
+
+ info("Checking that the rule-view doesn't have the #testid selector anymore");
+ checkRuleViewContent(view, ["element", ".testclass"]);
+
+ info("Reverting the ID attribute change");
+ ruleViewRefreshed = inspector.once("rule-view-refreshed");
+ yield testActor.setAttribute("#differentid", "id", "testid");
+ yield ruleViewRefreshed;
+
+ info("Checking that the rule-view has all the selectors again");
+ checkRuleViewContent(view, ["element", "#testid", ".testclass"]);
+});
+
+function checkRuleViewContent(view, expectedSelectors) {
+ let selectors = view.styleDocument
+ .querySelectorAll(".ruleview-selectorcontainer");
+
+ is(selectors.length, expectedSelectors.length,
+ expectedSelectors.length + " selectors are displayed");
+
+ for (let i = 0; i < expectedSelectors.length; i++) {
+ is(selectors[i].textContent.indexOf(expectedSelectors[i]), 0,
+ "Selector " + (i + 1) + " is " + expectedSelectors[i]);
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
new file mode 100644
index 000000000..6ee385faa
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
@@ -0,0 +1,153 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that changing the current element's style attribute refreshes the
+// rule-view
+
+const TEST_URI = `
+ <div id="testid" class="testclass" style="margin-top: 1px; padding-top: 5px;">
+ Styled Node
+ </div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view, testActor} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ yield testPropertyChanges(inspector, view);
+ yield testPropertyChange0(inspector, view, "#testid", testActor);
+ yield testPropertyChange1(inspector, view, "#testid", testActor);
+ yield testPropertyChange2(inspector, view, "#testid", testActor);
+ yield testPropertyChange3(inspector, view, "#testid", testActor);
+ yield testPropertyChange4(inspector, view, "#testid", testActor);
+ yield testPropertyChange5(inspector, view, "#testid", testActor);
+ yield testPropertyChange6(inspector, view, "#testid", testActor);
+});
+
+function* testPropertyChanges(inspector, ruleView) {
+ info("Adding a second margin-top value in the element selector");
+ let ruleEditor = ruleView._elementStyle.rules[0].editor;
+ let onRefreshed = inspector.once("rule-view-refreshed");
+ ruleEditor.addProperty("margin-top", "5px", "", true);
+ yield onRefreshed;
+
+ let rule = ruleView._elementStyle.rules[0];
+ validateTextProp(rule.textProps[0], false, "margin-top", "1px",
+ "Original margin property active");
+}
+
+function* testPropertyChange0(inspector, ruleView, selector, testActor) {
+ yield changeElementStyle(selector, "margin-top: 1px; padding-top: 5px",
+ inspector, testActor);
+
+ let rule = ruleView._elementStyle.rules[0];
+ is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+ "Correct number of properties");
+ validateTextProp(rule.textProps[0], true, "margin-top", "1px",
+ "First margin property re-enabled");
+ validateTextProp(rule.textProps[2], false, "margin-top", "5px",
+ "Second margin property disabled");
+}
+
+function* testPropertyChange1(inspector, ruleView, selector, testActor) {
+ info("Now set it back to 5px, the 5px value should be re-enabled.");
+ yield changeElementStyle(selector, "margin-top: 5px; padding-top: 5px;",
+ inspector, testActor);
+
+ let rule = ruleView._elementStyle.rules[0];
+ is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+ "Correct number of properties");
+ validateTextProp(rule.textProps[0], false, "margin-top", "1px",
+ "First margin property re-enabled");
+ validateTextProp(rule.textProps[2], true, "margin-top", "5px",
+ "Second margin property disabled");
+}
+
+function* testPropertyChange2(inspector, ruleView, selector, testActor) {
+ info("Set the margin property to a value that doesn't exist in the editor.");
+ info("Should reuse the currently-enabled element (the second one.)");
+ yield changeElementStyle(selector, "margin-top: 15px; padding-top: 5px;",
+ inspector, testActor);
+
+ let rule = ruleView._elementStyle.rules[0];
+ is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+ "Correct number of properties");
+ validateTextProp(rule.textProps[0], false, "margin-top", "1px",
+ "First margin property re-enabled");
+ validateTextProp(rule.textProps[2], true, "margin-top", "15px",
+ "Second margin property disabled");
+}
+
+function* testPropertyChange3(inspector, ruleView, selector, testActor) {
+ info("Remove the padding-top attribute. Should disable the padding " +
+ "property but not remove it.");
+ yield changeElementStyle(selector, "margin-top: 5px;", inspector, testActor);
+
+ let rule = ruleView._elementStyle.rules[0];
+ is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+ "Correct number of properties");
+ validateTextProp(rule.textProps[1], false, "padding-top", "5px",
+ "Padding property disabled");
+}
+
+function* testPropertyChange4(inspector, ruleView, selector, testActor) {
+ info("Put the padding-top attribute back in, should re-enable the " +
+ "padding property.");
+ yield changeElementStyle(selector, "margin-top: 5px; padding-top: 25px",
+ inspector, testActor);
+
+ let rule = ruleView._elementStyle.rules[0];
+ is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+ "Correct number of properties");
+ validateTextProp(rule.textProps[1], true, "padding-top", "25px",
+ "Padding property enabled");
+}
+
+function* testPropertyChange5(inspector, ruleView, selector, testActor) {
+ info("Add an entirely new property");
+ yield changeElementStyle(selector,
+ "margin-top: 5px; padding-top: 25px; padding-left: 20px;",
+ inspector, testActor);
+
+ let rule = ruleView._elementStyle.rules[0];
+ is(rule.editor.element.querySelectorAll(".ruleview-property").length, 4,
+ "Added a property");
+ validateTextProp(rule.textProps[3], true, "padding-left", "20px",
+ "Padding property enabled");
+}
+
+function* testPropertyChange6(inspector, ruleView, selector, testActor) {
+ info("Add an entirely new property again");
+ yield changeElementStyle(selector, "background: red " +
+ "url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%",
+ inspector, testActor);
+
+ let rule = ruleView._elementStyle.rules[0];
+ is(rule.editor.element.querySelectorAll(".ruleview-property").length, 5,
+ "Added a property");
+ validateTextProp(rule.textProps[4], true, "background",
+ "red url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%",
+ "shortcut property correctly set");
+}
+
+function* changeElementStyle(selector, style, inspector, testActor) {
+ let onRefreshed = inspector.once("rule-view-refreshed");
+ yield testActor.setAttribute(selector, "style", style);
+ yield onRefreshed;
+}
+
+function validateTextProp(prop, enabled, name, value, desc) {
+ is(prop.enabled, enabled, desc + ": enabled.");
+ is(prop.name, name, desc + ": name.");
+ is(prop.value, value, desc + ": value.");
+
+ is(prop.editor.enable.hasAttribute("checked"), enabled,
+ desc + ": enabled checkbox.");
+ is(prop.editor.nameSpan.textContent, name, desc + ": name span.");
+ is(prop.editor.valueSpan.textContent,
+ value, desc + ": value span.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-on-style-change.js b/devtools/client/inspector/rules/test/browser_rules_refresh-on-style-change.js
new file mode 100644
index 000000000..81ff9d4d5
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-style-change.js
@@ -0,0 +1,38 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule view refreshes when the current node has its style
+// changed
+
+const TEST_URI = "<div id='testdiv' style='font-size: 10px;''>Test div!</div>";
+
+add_task(function* () {
+ Services.prefs.setCharPref("devtools.defaultColorUnit", "name");
+
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view, testActor} = yield openRuleView();
+ yield selectNode("#testdiv", inspector);
+
+ let fontSize = getRuleViewPropertyValue(view, "element", "font-size");
+ is(fontSize, "10px", "The rule view shows the right font-size");
+
+ info("Changing the node's style and waiting for the update");
+ let onUpdated = inspector.once("rule-view-refreshed");
+ yield testActor.setAttribute("#testdiv", "style",
+ "font-size: 3em; color: lightgoldenrodyellow; " +
+ "text-align: right; text-transform: uppercase");
+ yield onUpdated;
+
+ let textAlign = getRuleViewPropertyValue(view, "element", "text-align");
+ is(textAlign, "right", "The rule view shows the new text align.");
+ let color = getRuleViewPropertyValue(view, "element", "color");
+ is(color, "lightgoldenrodyellow", "The rule view shows the new color.");
+ fontSize = getRuleViewPropertyValue(view, "element", "font-size");
+ is(fontSize, "3em", "The rule view shows the new font size.");
+ let textTransform = getRuleViewPropertyValue(view, "element",
+ "text-transform");
+ is(textTransform, "uppercase", "The rule view shows the new text transform.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js
new file mode 100644
index 000000000..f4c47bba0
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js
@@ -0,0 +1,156 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter and clear button works properly in
+// the computed list.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ margin: 4px 0px;
+ }
+ .testclass {
+ background-color: red;
+ }
+ </style>
+ <h1 id="testid" class="testclass">Styled Node</h1>
+`;
+
+const TEST_DATA = [
+ {
+ desc: "Tests that the search filter works properly in the computed list " +
+ "for property names",
+ search: "margin",
+ isExpanderOpen: false,
+ isFilterOpen: false,
+ isMarginHighlighted: true,
+ isMarginTopHighlighted: true,
+ isMarginRightHighlighted: true,
+ isMarginBottomHighlighted: true,
+ isMarginLeftHighlighted: true
+ },
+ {
+ desc: "Tests that the search filter works properly in the computed list " +
+ "for property values",
+ search: "0px",
+ isExpanderOpen: false,
+ isFilterOpen: false,
+ isMarginHighlighted: true,
+ isMarginTopHighlighted: false,
+ isMarginRightHighlighted: true,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: true
+ },
+ {
+ desc: "Tests that the search filter works properly in the computed list " +
+ "for property line input",
+ search: "margin-top:4px",
+ isExpanderOpen: true,
+ isFilterOpen: true,
+ isMarginHighlighted: false,
+ isMarginTopHighlighted: true,
+ isMarginRightHighlighted: false,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: false
+ },
+ {
+ desc: "Tests that the search filter works properly in the computed list " +
+ "for parsed name",
+ search: "margin-top:",
+ isExpanderOpen: true,
+ isFilterOpen: true,
+ isMarginHighlighted: false,
+ isMarginTopHighlighted: true,
+ isMarginRightHighlighted: false,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: false
+ },
+ {
+ desc: "Tests that the search filter works properly in the computed list " +
+ "for parsed property value",
+ search: ":4px",
+ isExpanderOpen: false,
+ isFilterOpen: false,
+ isMarginHighlighted: true,
+ isMarginTopHighlighted: true,
+ isMarginRightHighlighted: false,
+ isMarginBottomHighlighted: true,
+ isMarginLeftHighlighted: false
+ }
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ for (let data of TEST_DATA) {
+ info(data.desc);
+ yield setSearchFilter(view, data.search);
+ yield checkRules(view, data);
+ yield clearSearchAndCheckRules(view);
+ }
+}
+
+function* checkRules(view, data) {
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let textPropEditor = rule.textProps[0].editor;
+ let computed = textPropEditor.computed;
+
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ is(!!textPropEditor.expander.getAttribute("open"), data.isExpanderOpen,
+ "Got correct expander state.");
+ is(computed.hasAttribute("filter-open"), data.isFilterOpen,
+ "Got correct expanded state for margin computed list.");
+ is(textPropEditor.container.classList.contains("ruleview-highlight"),
+ data.isMarginHighlighted,
+ "Got correct highlight for margin text property.");
+
+ is(computed.children[0].classList.contains("ruleview-highlight"),
+ data.isMarginTopHighlighted,
+ "Got correct highlight for margin-top computed property.");
+ is(computed.children[1].classList.contains("ruleview-highlight"),
+ data.isMarginRightHighlighted,
+ "Got correct highlight for margin-right computed property.");
+ is(computed.children[2].classList.contains("ruleview-highlight"),
+ data.isMarginBottomHighlighted,
+ "Got correct highlight for margin-bottom computed property.");
+ is(computed.children[3].classList.contains("ruleview-highlight"),
+ data.isMarginLeftHighlighted,
+ "Got correct highlight for margin-left computed property.");
+}
+
+function* clearSearchAndCheckRules(view) {
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+ let searchClearButton = view.searchClearButton;
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let textPropEditor = rule.textProps[0].editor;
+ let computed = textPropEditor.computed;
+
+ info("Clearing the search filter");
+ EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
+ yield view.inspector.once("ruleview-filtered");
+
+ info("Check the search filter is cleared and no rules are highlighted");
+ is(view.element.children.length, 3, "Should have 3 rules.");
+ ok(!searchField.value, "Search filter is cleared");
+ ok(!view.styleDocument.querySelectorAll(".ruleview-highlight").length,
+ "No rules are higlighted");
+
+ ok(!textPropEditor.expander.getAttribute("open"), "Expander is closed.");
+ ok(!computed.hasAttribute("filter-open"),
+ "margin computed list is closed.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js
new file mode 100644
index 000000000..911f09ff3
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js
@@ -0,0 +1,93 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly in the computed list
+// when modifying the existing search filter value
+
+const SEARCH = "margin-";
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ margin: 4px 0px;
+ }
+ .testclass {
+ background-color: red;
+ }
+ </style>
+ <h1 id="testid" class="testclass">Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+ yield testRemoveTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let ruleEditor = rule.textProps[0].editor;
+ let computed = ruleEditor.computed;
+
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(ruleEditor.expander.getAttribute("open"), "Expander is open.");
+ ok(!ruleEditor.container.classList.contains("ruleview-highlight"),
+ "margin text property is not highlighted.");
+ ok(computed.hasAttribute("filter-open"), "margin computed list is open.");
+
+ ok(computed.children[0].classList.contains("ruleview-highlight"),
+ "margin-top computed property is correctly highlighted.");
+ ok(computed.children[1].classList.contains("ruleview-highlight"),
+ "margin-right computed property is correctly highlighted.");
+ ok(computed.children[2].classList.contains("ruleview-highlight"),
+ "margin-bottom computed property is correctly highlighted.");
+ ok(computed.children[3].classList.contains("ruleview-highlight"),
+ "margin-left computed property is correctly highlighted.");
+}
+
+function* testRemoveTextInFilter(inspector, view) {
+ info("Press backspace and set filter text to \"margin\"");
+
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+
+ searchField.focus();
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win);
+ yield inspector.once("ruleview-filtered");
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let ruleEditor = rule.textProps[0].editor;
+ let computed = ruleEditor.computed;
+
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(!ruleEditor.expander.getAttribute("open"), "Expander is closed.");
+ ok(ruleEditor.container.classList.contains("ruleview-highlight"),
+ "margin text property is correctly highlighted.");
+ ok(!computed.hasAttribute("filter-open"), "margin computed list is closed.");
+
+ ok(computed.children[0].classList.contains("ruleview-highlight"),
+ "margin-top computed property is correctly highlighted.");
+ ok(computed.children[1].classList.contains("ruleview-highlight"),
+ "margin-right computed property is correctly highlighted.");
+ ok(computed.children[2].classList.contains("ruleview-highlight"),
+ "margin-bottom computed property is correctly highlighted.");
+ ok(computed.children[3].classList.contains("ruleview-highlight"),
+ "margin-left computed property is correctly highlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_03.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_03.js
new file mode 100644
index 000000000..1d8063419
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_03.js
@@ -0,0 +1,49 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly in the computed list
+// for color values.
+
+// The color format here is chosen to match the default returned by
+// CssColor.toString.
+const SEARCH = "background-color: rgb(243, 243, 243)";
+
+const TEST_URI = `
+ <style type="text/css">
+ .testclass {
+ background: rgb(243, 243, 243) none repeat scroll 0% 0%;
+ }
+ </style>
+ <div class="testclass">Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".testclass", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let ruleEditor = rule.textProps[0].editor;
+ let computed = ruleEditor.computed;
+
+ is(rule.selectorText, ".testclass", "Second rule is .testclass.");
+ ok(ruleEditor.expander.getAttribute("open"), "Expander is open.");
+ ok(!ruleEditor.container.classList.contains("ruleview-highlight"),
+ "background property is not highlighted.");
+ ok(computed.hasAttribute("filter-open"), "background computed list is open.");
+ ok(computed.children[0].classList.contains("ruleview-highlight"),
+ "background-color computed property is highlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_04.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_04.js
new file mode 100644
index 000000000..05b8b01eb
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_04.js
@@ -0,0 +1,63 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly in the computed list
+// for newly modified property values.
+
+const SEARCH = "0px";
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ margin: 4px;
+ top: 0px;
+ }
+ </style>
+ <h1 id='testid'>Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testModifyPropertyValueFilter(inspector, view);
+});
+
+function* testModifyPropertyValueFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let propEditor = rule.textProps[0].editor;
+ let computed = propEditor.computed;
+ let editor = yield focusEditableField(view, propEditor.valueSpan);
+
+ info("Check that the correct rules are visible");
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(!propEditor.container.classList.contains("ruleview-highlight"),
+ "margin text property is not highlighted.");
+ ok(rule.textProps[1].editor.container.classList
+ .contains("ruleview-highlight"),
+ "top text property is correctly highlighted.");
+
+ let onBlur = once(editor.input, "blur");
+ let onModification = view.once("ruleview-changed");
+ EventUtils.sendString("4px 0px", view.styleWindow);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onBlur;
+ yield onModification;
+
+ ok(propEditor.container.classList.contains("ruleview-highlight"),
+ "margin text property is correctly highlighted.");
+ ok(!computed.hasAttribute("filter-open"), "margin computed list is closed.");
+ ok(!computed.children[0].classList.contains("ruleview-highlight"),
+ "margin-top computed property is not highlighted.");
+ ok(computed.children[1].classList.contains("ruleview-highlight"),
+ "margin-right computed property is correctly highlighted.");
+ ok(!computed.children[2].classList.contains("ruleview-highlight"),
+ "margin-bottom computed property is not highlighted.");
+ ok(computed.children[3].classList.contains("ruleview-highlight"),
+ "margin-left computed property is correctly highlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_expander.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_expander.js
new file mode 100644
index 000000000..c8b1e0869
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_expander.js
@@ -0,0 +1,92 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the expanded computed list for a property remains open after
+// clearing the rule view search filter.
+
+const SEARCH = "0px";
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ margin: 4px 0px;
+ }
+ .testclass {
+ background-color: red;
+ }
+ </style>
+ <h1 id="testid" class="testclass">Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testOpenExpanderAndAddTextInFilter(inspector, view);
+ yield testClearSearchFilter(inspector, view);
+});
+
+function* testOpenExpanderAndAddTextInFilter(inspector, view) {
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let ruleEditor = rule.textProps[0].editor;
+ let computed = ruleEditor.computed;
+
+ info("Opening the computed list of margin property");
+ ruleEditor.expander.click();
+
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(ruleEditor.expander.getAttribute("open"), "Expander is open.");
+ ok(ruleEditor.container.classList.contains("ruleview-highlight"),
+ "margin text property is correctly highlighted.");
+ ok(!computed.hasAttribute("filter-open"),
+ "margin computed list does not contain filter-open class.");
+ ok(computed.hasAttribute("user-open"),
+ "margin computed list contains user-open attribute.");
+
+ ok(!computed.children[0].classList.contains("ruleview-highlight"),
+ "margin-top computed property is not highlighted.");
+ ok(computed.children[1].classList.contains("ruleview-highlight"),
+ "margin-right computed property is correctly highlighted.");
+ ok(!computed.children[2].classList.contains("ruleview-highlight"),
+ "margin-bottom computed property is not highlighted.");
+ ok(computed.children[3].classList.contains("ruleview-highlight"),
+ "margin-left computed property is correctly highlighted.");
+}
+
+function* testClearSearchFilter(inspector, view) {
+ info("Clearing the search filter");
+
+ let searchField = view.searchField;
+ let searchClearButton = view.searchClearButton;
+ let onRuleViewFiltered = inspector.once("ruleview-filtered");
+
+ EventUtils.synthesizeMouseAtCenter(searchClearButton, {},
+ view.styleWindow);
+
+ yield onRuleViewFiltered;
+
+ info("Check the search filter is cleared and no rules are highlighted");
+ is(view.element.children.length, 3, "Should have 3 rules.");
+ ok(!searchField.value, "Search filter is cleared");
+ ok(!view.styleDocument.querySelectorAll(".ruleview-highlight").length,
+ "No rules are higlighted");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1).rule.textProps[0].editor;
+ let computed = ruleEditor.computed;
+
+ ok(ruleEditor.expander.getAttribute("open"), "Expander is open.");
+ ok(!computed.hasAttribute("filter-open"),
+ "margin computed list does not contain filter-open class.");
+ ok(computed.hasAttribute("user-open"),
+ "margin computed list contains user-open attribute.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-overridden-property.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-overridden-property.js
new file mode 100644
index 000000000..3e634b76e
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-overridden-property.js
@@ -0,0 +1,74 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view overriden search filter works properly for
+// overridden properties.
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ width: 100%;
+ }
+ h1 {
+ width: 50%;
+ }
+ </style>
+ <h1 id='testid' class='testclass'>Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testFilterOverriddenProperty(inspector, view);
+});
+
+function* testFilterOverriddenProperty(inspector, ruleView) {
+ info("Check that the correct rules are visible");
+ is(ruleView.element.children.length, 3, "Should have 3 rules.");
+
+ let rule = getRuleViewRuleEditor(ruleView, 1).rule;
+ let textPropEditor = rule.textProps[0].editor;
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(!textPropEditor.element.classList.contains("ruleview-overridden"),
+ "width property is not overridden.");
+ ok(textPropEditor.filterProperty.hidden,
+ "Overridden search button is hidden.");
+
+ rule = getRuleViewRuleEditor(ruleView, 2).rule;
+ textPropEditor = rule.textProps[0].editor;
+ is(rule.selectorText, "h1", "Third rule is h1.");
+ ok(textPropEditor.element.classList.contains("ruleview-overridden"),
+ "width property is overridden.");
+ ok(!textPropEditor.filterProperty.hidden,
+ "Overridden search button is not hidden.");
+
+ let searchField = ruleView.searchField;
+ let onRuleViewFiltered = inspector.once("ruleview-filtered");
+
+ info("Click the overridden search");
+ textPropEditor.filterProperty.click();
+ yield onRuleViewFiltered;
+
+ info("Check that the overridden search is applied");
+ is(searchField.value, "`width`", "The search field value is width.");
+
+ rule = getRuleViewRuleEditor(ruleView, 1).rule;
+ textPropEditor = rule.textProps[0].editor;
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(textPropEditor.container.classList.contains("ruleview-highlight"),
+ "width property is correctly highlighted.");
+
+ rule = getRuleViewRuleEditor(ruleView, 2).rule;
+ textPropEditor = rule.textProps[0].editor;
+ is(rule.selectorText, "h1", "Third rule is h1.");
+ ok(textPropEditor.container.classList.contains("ruleview-highlight"),
+ "width property is correctly highlighted.");
+ ok(textPropEditor.element.classList.contains("ruleview-overridden"),
+ "width property is overridden.");
+ ok(!textPropEditor.filterProperty.hidden,
+ "Overridden search button is not hidden.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js
new file mode 100644
index 000000000..4dd1c951d
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js
@@ -0,0 +1,91 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter and clear button works properly.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid, h1 {
+ background-color: #00F !important;
+ }
+ .testclass {
+ width: 100%;
+ }
+ </style>
+ <h1 id="testid" class="testclass">Styled Node</h1>
+`;
+
+const TEST_DATA = [
+ {
+ desc: "Tests that the search filter works properly for property names",
+ search: "color"
+ },
+ {
+ desc: "Tests that the search filter works properly for property values",
+ search: "00F"
+ },
+ {
+ desc: "Tests that the search filter works properly for property line input",
+ search: "background-color:#00F"
+ },
+ {
+ desc: "Tests that the search filter works properly for parsed property " +
+ "names",
+ search: "background:"
+ },
+ {
+ desc: "Tests that the search filter works properly for parsed property " +
+ "values",
+ search: ":00F"
+ },
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ for (let data of TEST_DATA) {
+ info(data.desc);
+ yield setSearchFilter(view, data.search);
+ yield checkRules(view);
+ yield clearSearchAndCheckRules(view);
+ }
+}
+
+function* checkRules(view) {
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+
+ is(rule.selectorText, "#testid, h1", "Second rule is #testid, h1.");
+ ok(rule.textProps[0].editor.container.classList
+ .contains("ruleview-highlight"),
+ "background-color text property is correctly highlighted.");
+}
+
+function* clearSearchAndCheckRules(view) {
+ let doc = view.styleDocument;
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+ let searchClearButton = view.searchClearButton;
+
+ info("Clearing the search filter");
+ EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
+ yield view.inspector.once("ruleview-filtered");
+
+ info("Check the search filter is cleared and no rules are highlighted");
+ is(view.element.children.length, 3, "Should have 3 rules.");
+ ok(!searchField.value, "Search filter is cleared.");
+ ok(!doc.querySelectorAll(".ruleview-highlight").length,
+ "No rules are higlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_02.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_02.js
new file mode 100644
index 000000000..c23e7be62
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_02.js
@@ -0,0 +1,32 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly for keyframe rule
+// selectors.
+
+const SEARCH = "20%";
+const TEST_URI = URL_ROOT + "doc_keyframeanimation.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#boxy", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 2, 0);
+
+ is(ruleEditor.rule.domRule.keyText, "20%", "Second rule is 20%.");
+ ok(ruleEditor.selectorText.classList.contains("ruleview-highlight"),
+ "20% selector is highlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_03.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_03.js
new file mode 100644
index 000000000..89280f0eb
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_03.js
@@ -0,0 +1,39 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly for inline styles.
+
+const SEARCH = "color";
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ width: 100%;
+ }
+ </style>
+ <div id="testid" style="background-color:aliceblue">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 1, "Should have 1 rule.");
+
+ let rule = getRuleViewRuleEditor(view, 0).rule;
+
+ is(rule.selectorText, "element", "First rule is inline element.");
+ ok(rule.textProps[0].editor.container.classList
+ .contains("ruleview-highlight"),
+ "background-color text property is correctly highlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_04.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_04.js
new file mode 100644
index 000000000..5804d74ac
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_04.js
@@ -0,0 +1,76 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly when modifying the
+// existing search filter value.
+
+const SEARCH = "00F";
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ background-color: #00F;
+ }
+ .testclass {
+ width: 100%;
+ }
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+ yield testRemoveTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(rule.textProps[0].editor.container.classList
+ .contains("ruleview-highlight"),
+ "background-color text property is correctly highlighted.");
+}
+
+function* testRemoveTextInFilter(inspector, view) {
+ info("Press backspace and set filter text to \"00\"");
+
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+
+ searchField.focus();
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win);
+ yield inspector.once("ruleview-filtered");
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 3, "Should have 3 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(rule.textProps[0].editor.container.classList
+ .contains("ruleview-highlight"),
+ "background-color text property is correctly highlighted.");
+
+ rule = getRuleViewRuleEditor(view, 2).rule;
+
+ is(rule.selectorText, ".testclass", "Second rule is .testclass.");
+ ok(rule.textProps[0].editor.container.classList
+ .contains("ruleview-highlight"),
+ "width text property is correctly highlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_05.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_05.js
new file mode 100644
index 000000000..9388dd47e
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_05.js
@@ -0,0 +1,33 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly for stylesheet source.
+
+const SEARCH = "doc_urls_clickable.css";
+const TEST_URI = URL_ROOT + "doc_urls_clickable.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".relative1", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let source = rule.textProps[0].editor.ruleEditor.source;
+
+ is(rule.selectorText, ".relative1", "Second rule is .relative1.");
+ ok(source.classList.contains("ruleview-highlight"),
+ "stylesheet source is correctly highlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_06.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_06.js
new file mode 100644
index 000000000..67b02ab73
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_06.js
@@ -0,0 +1,27 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter does not highlight the source with
+// input that could be parsed as a property line.
+
+const SEARCH = "doc_urls_clickable.css: url";
+const TEST_URI = URL_ROOT + "doc_urls_clickable.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".relative1", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 1, "Should have 1 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_07.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_07.js
new file mode 100644
index 000000000..16b047d8d
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_07.js
@@ -0,0 +1,62 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly for newly modified
+// property name.
+
+const SEARCH = "e";
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ width: 100%;
+ height: 50%;
+ }
+ </style>
+ <h1 id='testid'>Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Enter the test value in the search filter");
+ yield setSearchFilter(view, SEARCH);
+
+ info("Focus the width property name");
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let rule = ruleEditor.rule;
+ let propEditor = rule.textProps[0].editor;
+ yield focusEditableField(view, propEditor.nameSpan);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(!propEditor.container.classList.contains("ruleview-highlight"),
+ "width text property is not highlighted.");
+ ok(rule.textProps[1].editor.container.classList
+ .contains("ruleview-highlight"),
+ "height text property is correctly highlighted.");
+
+ info("Change the width property to margin-left");
+ EventUtils.sendString("margin-left", view.styleWindow);
+
+ info("Submit the change");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ ok(propEditor.container.classList.contains("ruleview-highlight"),
+ "margin-left text property is correctly highlighted.");
+
+ // After pressing return on the property name, the value has been focused
+ // automatically. Blur it now and wait for the rule-view to refresh to avoid
+ // pending requests.
+ onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield onRuleViewChanged;
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js
new file mode 100644
index 000000000..1a3c0de59
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js
@@ -0,0 +1,53 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly for newly modified
+// property value.
+
+const SEARCH = "100%";
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ width: 100%;
+ height: 50%;
+ }
+ </style>
+ <h1 id='testid'>Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Enter the test value in the search filter");
+ yield setSearchFilter(view, SEARCH);
+
+ info("Focus the height property value");
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let rule = ruleEditor.rule;
+ let propEditor = rule.textProps[1].editor;
+ yield focusEditableField(view, propEditor.valueSpan);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(rule.textProps[0].editor.container.classList
+ .contains("ruleview-highlight"),
+ "width text property is correctly highlighted.");
+ ok(!propEditor.container.classList.contains("ruleview-highlight"),
+ "height text property is not highlighted.");
+
+ info("Change the height property value to 100%");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.sendString("100%", view.styleWindow);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield onRuleViewChanged;
+
+ ok(propEditor.container.classList.contains("ruleview-highlight"),
+ "height text property is correctly highlighted.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
new file mode 100644
index 000000000..620e5d336
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
@@ -0,0 +1,73 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly for newly added
+// property.
+
+const SEARCH = "100%";
+
+const TEST_URI = `
+ <style type='text/css'>
+ #testid {
+ width: 100%;
+ height: 50%;
+ }
+ </style>
+ <h1 id='testid'>Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+
+ info("Enter the test value in the search filter");
+ yield setSearchFilter(view, SEARCH);
+
+ info("Start entering a new property in the rule");
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+ let rule = ruleEditor.rule;
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(rule.textProps[0].editor.container.classList
+ .contains("ruleview-highlight"),
+ "width text property is correctly highlighted.");
+ ok(!rule.textProps[1].editor.container.classList
+ .contains("ruleview-highlight"),
+ "height text property is not highlighted.");
+
+ info("Test creating a new property");
+
+ info("Entering margin-left in the property name editor");
+ // Changing the value doesn't cause a rule-view refresh, no need to wait for
+ // ruleview-changed here.
+ editor.input.value = "margin-left";
+
+ info("Pressing return to commit and focus the new value field");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onRuleViewChanged;
+
+ // Getting the new value editor after focus
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ let propEditor = ruleEditor.rule.textProps[2].editor;
+
+ info("Entering a value and bluring the field to expect a rule change");
+ onRuleViewChanged = view.once("ruleview-changed");
+ editor.input.value = "100%";
+ view.throttle.flush();
+ yield onRuleViewChanged;
+
+ onRuleViewChanged = view.once("ruleview-changed");
+ editor.input.blur();
+ yield onRuleViewChanged;
+
+ ok(propEditor.container.classList.contains("ruleview-highlight"),
+ "margin-left text property is correctly highlighted.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js
new file mode 100644
index 000000000..ac336591d
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js
@@ -0,0 +1,84 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter works properly for rule selectors.
+
+const TEST_URI = `
+ <style type="text/css">
+ html, body, div {
+ background-color: #00F;
+ }
+ #testid {
+ width: 100%;
+ }
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>
+`;
+
+const TEST_DATA = [
+ {
+ desc: "Tests that the search filter works properly for a single rule " +
+ "selector",
+ search: "#test",
+ selectorText: "#testid",
+ index: 0
+ },
+ {
+ desc: "Tests that the search filter works properly for multiple rule " +
+ "selectors",
+ search: "body",
+ selectorText: "html, body, div",
+ index: 2
+ }
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ for (let data of TEST_DATA) {
+ info(data.desc);
+ yield setSearchFilter(view, data.search);
+ yield checkRules(view, data);
+ yield clearSearchAndCheckRules(view);
+ }
+}
+
+function* checkRules(view, data) {
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ is(ruleEditor.rule.selectorText, data.selectorText,
+ "Second rule is " + data.selectorText + ".");
+ ok(ruleEditor.selectorText.children[data.index].classList
+ .contains("ruleview-highlight"),
+ data.selectorText + " selector is highlighted.");
+}
+
+function* clearSearchAndCheckRules(view) {
+ let doc = view.styleDocument;
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+ let searchClearButton = view.searchClearButton;
+
+ info("Clearing the search filter");
+ EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
+ yield view.inspector.once("ruleview-filtered");
+
+ info("Check the search filter is cleared and no rules are highlighted");
+ is(view.element.children.length, 3, "Should have 3 rules.");
+ ok(!searchField.value, "Search filter is cleared.");
+ ok(!doc.querySelectorAll(".ruleview-highlight").length,
+ "No rules are higlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js
new file mode 100644
index 000000000..349f1b9b3
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js
@@ -0,0 +1,83 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test rule view search filter context menu works properly.
+
+const TEST_INPUT = "h1";
+const TEST_URI = "<h1>test filter context menu</h1>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {toolbox, inspector, view} = yield openRuleView();
+ yield selectNode("h1", inspector);
+
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+ let searchContextMenu = toolbox.textBoxContextMenuPopup;
+ ok(searchContextMenu,
+ "The search filter context menu is loaded in the rule view");
+
+ let cmdUndo = searchContextMenu.querySelector("[command=cmd_undo]");
+ let cmdDelete = searchContextMenu.querySelector("[command=cmd_delete]");
+ let cmdSelectAll = searchContextMenu.querySelector("[command=cmd_selectAll]");
+ let cmdCut = searchContextMenu.querySelector("[command=cmd_cut]");
+ let cmdCopy = searchContextMenu.querySelector("[command=cmd_copy]");
+ let cmdPaste = searchContextMenu.querySelector("[command=cmd_paste]");
+
+ info("Opening context menu");
+
+ emptyClipboard();
+
+ let onFocus = once(searchField, "focus");
+ searchField.focus();
+ yield onFocus;
+
+ let onContextMenuPopup = once(searchContextMenu, "popupshowing");
+ EventUtils.synthesizeMouse(searchField, 2, 2,
+ {type: "contextmenu", button: 2}, win);
+ yield onContextMenuPopup;
+
+ is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
+ is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
+ is(cmdSelectAll.getAttribute("disabled"), "true", "cmdSelectAll is disabled");
+
+ // Cut/Copy items are enabled in context menu even if there
+ // is no selection. See also Bug 1303033
+ is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
+ is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
+
+ if (isWindows()) {
+ // emptyClipboard only works on Windows (666254), assert paste only for this OS.
+ is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
+ }
+
+ info("Closing context menu");
+ let onContextMenuHidden = once(searchContextMenu, "popuphidden");
+ searchContextMenu.hidePopup();
+ yield onContextMenuHidden;
+
+ info("Copy text in search field using the context menu");
+ searchField.value = TEST_INPUT;
+ searchField.select();
+ EventUtils.synthesizeMouse(searchField, 2, 2,
+ {type: "contextmenu", button: 2}, win);
+ yield onContextMenuPopup;
+ yield waitForClipboardPromise(() => cmdCopy.click(), TEST_INPUT);
+ searchContextMenu.hidePopup();
+ yield onContextMenuHidden;
+
+ info("Reopen context menu and check command properties");
+ EventUtils.synthesizeMouse(searchField, 2, 2,
+ {type: "contextmenu", button: 2}, win);
+ yield onContextMenuPopup;
+
+ is(cmdUndo.getAttribute("disabled"), "", "cmdUndo is enabled");
+ is(cmdDelete.getAttribute("disabled"), "", "cmdDelete is enabled");
+ is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
+ is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
+ is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
+ is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_escape-keypress.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_escape-keypress.js
new file mode 100644
index 000000000..21848dce8
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_escape-keypress.js
@@ -0,0 +1,65 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter escape keypress will clear the search
+// field.
+
+const SEARCH = "00F";
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ background-color: #00F;
+ }
+ .testclass {
+ width: 100%;
+ }
+ </style>
+ <div id="testid" class="testclass">Styled Node</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+ yield testEscapeKeypress(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(rule.textProps[0].editor.container.classList
+ .contains("ruleview-highlight"),
+ "background-color text property is correctly highlighted.");
+}
+
+function* testEscapeKeypress(inspector, view) {
+ info("Pressing the escape key on search filter");
+
+ let doc = view.styleDocument;
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+ let onRuleViewFiltered = inspector.once("ruleview-filtered");
+
+ searchField.focus();
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ yield onRuleViewFiltered;
+
+ info("Check the search filter is cleared and no rules are highlighted");
+ is(view.element.children.length, 3, "Should have 3 rules.");
+ ok(!searchField.value, "Search filter is cleared");
+ ok(!doc.querySelectorAll(".ruleview-highlight").length,
+ "No rules are higlighted");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js b/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js
new file mode 100644
index 000000000..b3f4ef364
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js
@@ -0,0 +1,171 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that properties can be selected and copied from the rule view
+
+const osString = Services.appinfo.OS;
+
+const TEST_URI = `
+ <style type="text/css">
+ html {
+ color: #000000;
+ }
+ span {
+ font-variant: small-caps; color: #000000;
+ }
+ .nomatches {
+ color: #ff0000;
+ }
+ </style>
+ <div id="first" style="margin: 10em;
+ font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">
+ <h1>Some header text</h1>
+ <p id="salutation" style="font-size: 12pt">hi.</p>
+ <p id="body" style="font-size: 12pt">I am a test-case. This text exists
+ solely to provide some things to <span style="color: yellow">
+ highlight</span> and <span style="font-weight: bold">count</span>
+ style list-items in the box at right. If you are reading this,
+ you should go do something else instead. Maybe read a book. Or better
+ yet, write some test-cases for another bit of code.
+ <span style="font-style: italic">some text</span></p>
+ <p id="closing">more text</p>
+ <p>even more text</p>
+ </div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+ yield checkCopySelection(view);
+ yield checkSelectAll(view);
+ yield checkCopyEditorValue(view);
+});
+
+function* checkCopySelection(view) {
+ info("Testing selection copy");
+
+ let contentDoc = view.styleDocument;
+ let win = view.styleWindow;
+ let prop = contentDoc.querySelector(".ruleview-property");
+ let values = contentDoc.querySelectorAll(".ruleview-propertyvaluecontainer");
+
+ let range = contentDoc.createRange();
+ range.setStart(prop, 0);
+ range.setEnd(values[4], 2);
+ win.getSelection().addRange(range);
+ info("Checking that _Copy() returns the correct clipboard value");
+
+ let expectedPattern = " margin: 10em;[\\r\\n]+" +
+ " font-size: 14pt;[\\r\\n]+" +
+ " font-family: helvetica, sans-serif;[\\r\\n]+" +
+ " color: #AAA;[\\r\\n]+" +
+ "}[\\r\\n]+" +
+ "html {[\\r\\n]+" +
+ " color: #000000;[\\r\\n]*";
+
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, prop);
+ let menuitemCopy = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copy"));
+
+ ok(menuitemCopy.visible,
+ "Copy menu item is displayed as expected");
+
+ try {
+ yield waitForClipboardPromise(() => menuitemCopy.click(),
+ () => checkClipboardData(expectedPattern));
+ } catch (e) {
+ failedClipboard(expectedPattern);
+ }
+}
+
+function* checkSelectAll(view) {
+ info("Testing select-all copy");
+
+ let contentDoc = view.styleDocument;
+ let prop = contentDoc.querySelector(".ruleview-property");
+
+ info("Checking that _SelectAll() then copy returns the correct " +
+ "clipboard value");
+ view._contextmenu._onSelectAll();
+ let expectedPattern = "element {[\\r\\n]+" +
+ " margin: 10em;[\\r\\n]+" +
+ " font-size: 14pt;[\\r\\n]+" +
+ " font-family: helvetica, sans-serif;[\\r\\n]+" +
+ " color: #AAA;[\\r\\n]+" +
+ "}[\\r\\n]+" +
+ "html {[\\r\\n]+" +
+ " color: #000000;[\\r\\n]+" +
+ "}[\\r\\n]*";
+
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, prop);
+ let menuitemCopy = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copy"));
+
+ ok(menuitemCopy.visible,
+ "Copy menu item is displayed as expected");
+
+ try {
+ yield waitForClipboardPromise(() => menuitemCopy.click(),
+ () => checkClipboardData(expectedPattern));
+ } catch (e) {
+ failedClipboard(expectedPattern);
+ }
+}
+
+function* checkCopyEditorValue(view) {
+ info("Testing CSS property editor value copy");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 0);
+ let propEditor = ruleEditor.rule.textProps[0].editor;
+
+ let editor = yield focusEditableField(view, propEditor.valueSpan);
+
+ info("Checking that copying a css property value editor returns the correct" +
+ " clipboard value");
+
+ let expectedPattern = "10em";
+
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, editor.input);
+ let menuitemCopy = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copy"));
+
+ ok(menuitemCopy.visible,
+ "Copy menu item is displayed as expected");
+
+ try {
+ yield waitForClipboardPromise(() => menuitemCopy.click(),
+ () => checkClipboardData(expectedPattern));
+ } catch (e) {
+ failedClipboard(expectedPattern);
+ }
+}
+
+function checkClipboardData(expectedPattern) {
+ let actual = SpecialPowers.getClipboardData("text/unicode");
+ let expectedRegExp = new RegExp(expectedPattern, "g");
+ return expectedRegExp.test(actual);
+}
+
+function failedClipboard(expectedPattern) {
+ // Format expected text for comparison
+ let terminator = osString == "WINNT" ? "\r\n" : "\n";
+ expectedPattern = expectedPattern.replace(/\[\\r\\n\][+*]/g, terminator);
+ expectedPattern = expectedPattern.replace(/\\\(/g, "(");
+ expectedPattern = expectedPattern.replace(/\\\)/g, ")");
+
+ let actual = SpecialPowers.getClipboardData("text/unicode");
+
+ // Trim the right hand side of our strings. This is because expectedPattern
+ // accounts for windows sometimes adding a newline to our copied data.
+ expectedPattern = expectedPattern.trimRight();
+ actual = actual.trimRight();
+
+ dump("TEST-UNEXPECTED-FAIL | Clipboard text does not match expected ... " +
+ "results (escaped for accurate comparison):\n");
+ info("Actual: " + escape(actual));
+ info("Expected: " + escape(expectedPattern));
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter-on-navigate.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter-on-navigate.js
new file mode 100644
index 000000000..54e25c399
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter-on-navigate.js
@@ -0,0 +1,38 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the selector highlighter is hidden on page navigation.
+
+const TEST_URI = `
+ <style type="text/css">
+ body, p, td {
+ background: red;
+ }
+ </style>
+ Test the selector highlighter
+`;
+
+const TEST_URI_2 = "data:text/html,<html><body>test</body></html>";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ let highlighters = view.highlighters;
+
+ info("Clicking on a selector icon");
+ let icon = getRuleViewSelectorHighlighterIcon(view, "body, p, td");
+
+ let onToggled = view.once("ruleview-selectorhighlighter-toggled");
+ EventUtils.synthesizeMouseAtCenter(icon, {}, view.styleWindow);
+ let isVisible = yield onToggled;
+
+ ok(highlighters.selectorHighlighterShown, "The selectorHighlighterShown is set.");
+ ok(view.selectorHighlighter, "The selectorhighlighter instance was created");
+ ok(isVisible, "The toggle event says the highlighter is visible");
+
+ yield navigateTo(inspector, TEST_URI_2);
+ ok(!highlighters.selectorHighlighterShown, "The selectorHighlighterShown is unset.");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_01.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_01.js
new file mode 100644
index 000000000..4c8853e02
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_01.js
@@ -0,0 +1,35 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the selector highlighter is created when clicking on a selector
+// icon in the rule view.
+
+const TEST_URI = `
+ <style type="text/css">
+ body, p, td {
+ background: red;
+ }
+ </style>
+ Test the selector highlighter
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {view} = yield openRuleView();
+
+ ok(!view.selectorHighlighter,
+ "No selectorhighlighter exist in the rule-view");
+
+ info("Clicking on a selector icon");
+ let icon = getRuleViewSelectorHighlighterIcon(view, "body, p, td");
+
+ let onToggled = view.once("ruleview-selectorhighlighter-toggled");
+ EventUtils.synthesizeMouseAtCenter(icon, {}, view.styleWindow);
+ let isVisible = yield onToggled;
+
+ ok(view.selectorHighlighter, "The selectorhighlighter instance was created");
+ ok(isVisible, "The toggle event says the highlighter is visible");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_02.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_02.js
new file mode 100644
index 000000000..33f73e587
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_02.js
@@ -0,0 +1,78 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the selector highlighter is shown when clicking on a selector icon
+// in the rule-view
+
+// Note that in this test, we mock the highlighter front, merely testing the
+// behavior of the style-inspector UI for now
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ background: red;
+ }
+ p {
+ color: white;
+ }
+ </style>
+ <p>Testing the selector highlighter</p>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ // Mock the highlighter front to get the reference of the NodeFront
+ let HighlighterFront = {
+ isShown: false,
+ nodeFront: null,
+ options: null,
+ show: function (nodeFront, options) {
+ this.nodeFront = nodeFront;
+ this.options = options;
+ this.isShown = true;
+ },
+ hide: function () {
+ this.nodeFront = null;
+ this.options = null;
+ this.isShown = false;
+ }
+ };
+
+ // Inject the mock highlighter in the rule-view
+ view.selectorHighlighter = HighlighterFront;
+
+ let icon = getRuleViewSelectorHighlighterIcon(view, "body");
+
+ info("Checking that the HighlighterFront's show/hide methods are called");
+
+ info("Clicking once on the body selector highlighter icon");
+ yield clickSelectorIcon(icon, view);
+ ok(HighlighterFront.isShown, "The highlighter is shown");
+
+ info("Clicking once again on the body selector highlighter icon");
+ yield clickSelectorIcon(icon, view);
+ ok(!HighlighterFront.isShown, "The highlighter is hidden");
+
+ info("Checking that the right NodeFront reference and options are passed");
+ yield selectNode("p", inspector);
+ icon = getRuleViewSelectorHighlighterIcon(view, "p");
+
+ yield clickSelectorIcon(icon, view);
+ is(HighlighterFront.nodeFront.tagName, "P",
+ "The right NodeFront is passed to the highlighter (1)");
+ is(HighlighterFront.options.selector, "p",
+ "The right selector option is passed to the highlighter (1)");
+
+ yield selectNode("body", inspector);
+ icon = getRuleViewSelectorHighlighterIcon(view, "body");
+ yield clickSelectorIcon(icon, view);
+ is(HighlighterFront.nodeFront.tagName, "BODY",
+ "The right NodeFront is passed to the highlighter (2)");
+ is(HighlighterFront.options.selector, "body",
+ "The right selector option is passed to the highlighter (2)");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_03.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_03.js
new file mode 100644
index 000000000..1ffbac012
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_03.js
@@ -0,0 +1,78 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the selector highlighter toggling mechanism works correctly.
+
+// Note that in this test, we mock the highlighter front, merely testing the
+// behavior of the style-inspector UI for now
+
+const TEST_URI = `
+ <style type="text/css">
+ div {text-decoration: underline;}
+ .node-1 {color: red;}
+ .node-2 {color: green;}
+ </style>
+ <div class="node-1">Node 1</div>
+ <div class="node-2">Node 2</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ // Mock the highlighter front.
+ let HighlighterFront = {
+ isShown: false,
+ show: function () {
+ this.isShown = true;
+ },
+ hide: function () {
+ this.isShown = false;
+ }
+ };
+
+ // Inject the mock highlighter in the rule-view
+ view.selectorHighlighter = HighlighterFront;
+
+ info("Select .node-1 and click on the .node-1 selector icon");
+ yield selectNode(".node-1", inspector);
+ let icon = getRuleViewSelectorHighlighterIcon(view, ".node-1");
+ yield clickSelectorIcon(icon, view);
+ ok(HighlighterFront.isShown, "The highlighter is shown");
+
+ info("With .node-1 still selected, click again on the .node-1 selector icon");
+ yield clickSelectorIcon(icon, view);
+ ok(!HighlighterFront.isShown, "The highlighter is now hidden");
+
+ info("With .node-1 still selected, click on the div selector icon");
+ icon = getRuleViewSelectorHighlighterIcon(view, "div");
+ yield clickSelectorIcon(icon, view);
+ ok(HighlighterFront.isShown, "The highlighter is shown again");
+
+ info("With .node-1 still selected, click again on the .node-1 selector icon");
+ icon = getRuleViewSelectorHighlighterIcon(view, ".node-1");
+ yield clickSelectorIcon(icon, view);
+ ok(HighlighterFront.isShown,
+ "The highlighter is shown again since the clicked selector was different");
+
+ info("Selecting .node-2");
+ yield selectNode(".node-2", inspector);
+ ok(HighlighterFront.isShown,
+ "The highlighter is still shown after selection");
+
+ info("With .node-2 selected, click on the div selector icon");
+ icon = getRuleViewSelectorHighlighterIcon(view, "div");
+ yield clickSelectorIcon(icon, view);
+ ok(HighlighterFront.isShown,
+ "The highlighter is shown still since the selected was different");
+
+ info("Switching back to .node-1 and clicking on the div selector");
+ yield selectNode(".node-1", inspector);
+ icon = getRuleViewSelectorHighlighterIcon(view, "div");
+ yield clickSelectorIcon(icon, view);
+ ok(!HighlighterFront.isShown,
+ "The highlighter is hidden now that the same selector was clicked");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_04.js b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_04.js
new file mode 100644
index 000000000..b770f8127
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_selector-highlighter_04.js
@@ -0,0 +1,53 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the selector highlighter is shown when clicking on a selector icon
+// for the 'element {}' rule
+
+// Note that in this test, we mock the highlighter front, merely testing the
+// behavior of the style-inspector UI for now
+
+const TEST_URI = `
+<p>Testing the selector highlighter for the 'element {}' rule</p>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ // Mock the highlighter front to get the reference of the NodeFront
+ let HighlighterFront = {
+ isShown: false,
+ nodeFront: null,
+ options: null,
+ show: function (nodeFront, options) {
+ this.nodeFront = nodeFront;
+ this.options = options;
+ this.isShown = true;
+ },
+ hide: function () {
+ this.nodeFront = null;
+ this.options = null;
+ this.isShown = false;
+ }
+ };
+ // Inject the mock highlighter in the rule-view
+ view.selectorHighlighter = HighlighterFront;
+
+ info("Checking that the right NodeFront reference and options are passed");
+ yield selectNode("p", inspector);
+ let icon = getRuleViewSelectorHighlighterIcon(view, "element");
+
+ yield clickSelectorIcon(icon, view);
+ is(HighlighterFront.nodeFront.tagName, "P",
+ "The right NodeFront is passed to the highlighter (1)");
+ is(HighlighterFront.options.selector, "body > p:nth-child(1)",
+ "The right selector option is passed to the highlighter (1)");
+ ok(HighlighterFront.isShown, "The toggle event says the highlighter is visible");
+
+ yield clickSelectorIcon(icon, view);
+ ok(!HighlighterFront.isShown, "The toggle event says the highlighter is not visible");
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_selector_highlight.js b/devtools/client/inspector/rules/test/browser_rules_selector_highlight.js
new file mode 100644
index 000000000..91422d57a
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_selector_highlight.js
@@ -0,0 +1,144 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view selector text is highlighted correctly according
+// to the components of the selector.
+
+const TEST_URI = [
+ "<style type='text/css'>",
+ " h1 {}",
+ " h1#testid {}",
+ " h1 + p {}",
+ " div[hidden=\"true\"] {}",
+ " div[title=\"test\"][checked=true] {}",
+ " p:empty {}",
+ " p:lang(en) {}",
+ " .testclass:active {}",
+ " .testclass:focus {}",
+ " .testclass:hover {}",
+ "</style>",
+ "<h1>Styled Node</h1>",
+ "<p>Paragraph</p>",
+ "<h1 id=\"testid\">Styled Node</h1>",
+ "<div hidden=\"true\"></div>",
+ "<div title=\"test\" checked=\"true\"></div>",
+ "<p></p>",
+ "<p lang=\"en\">Paragraph<p>",
+ "<div class=\"testclass\">Styled Node</div>"
+].join("\n");
+
+const SELECTOR_ATTRIBUTE = "ruleview-selector-attribute";
+const SELECTOR_ELEMENT = "ruleview-selector";
+const SELECTOR_PSEUDO_CLASS = "ruleview-selector-pseudo-class";
+const SELECTOR_PSEUDO_CLASS_LOCK = "ruleview-selector-pseudo-class-lock";
+
+const TEST_DATA = [
+ {
+ node: "h1",
+ expected: [
+ { value: "h1", class: SELECTOR_ELEMENT }
+ ]
+ },
+ {
+ node: "h1 + p",
+ expected: [
+ { value: "h1 + p", class: SELECTOR_ELEMENT }
+ ]
+ },
+ {
+ node: "h1#testid",
+ expected: [
+ { value: "h1#testid", class: SELECTOR_ELEMENT }
+ ]
+ },
+ {
+ node: "div[hidden='true']",
+ expected: [
+ { value: "div", class: SELECTOR_ELEMENT },
+ { value: "[hidden=\"true\"]", class: SELECTOR_ATTRIBUTE }
+ ]
+ },
+ {
+ node: "div[title=\"test\"][checked=\"true\"]",
+ expected: [
+ { value: "div", class: SELECTOR_ELEMENT },
+ { value: "[title=\"test\"]", class: SELECTOR_ATTRIBUTE },
+ { value: "[checked=\"true\"]", class: SELECTOR_ATTRIBUTE }
+ ]
+ },
+ {
+ node: "p:empty",
+ expected: [
+ { value: "p", class: SELECTOR_ELEMENT },
+ { value: ":empty", class: SELECTOR_PSEUDO_CLASS }
+ ]
+ },
+ {
+ node: "p:lang(en)",
+ expected: [
+ { value: "p", class: SELECTOR_ELEMENT },
+ { value: ":lang(en)", class: SELECTOR_PSEUDO_CLASS }
+ ]
+ },
+ {
+ node: ".testclass",
+ pseudoClass: ":active",
+ expected: [
+ { value: ".testclass", class: SELECTOR_ELEMENT },
+ { value: ":active", class: SELECTOR_PSEUDO_CLASS_LOCK }
+ ]
+ },
+ {
+ node: ".testclass",
+ pseudoClass: ":focus",
+ expected: [
+ { value: ".testclass", class: SELECTOR_ELEMENT },
+ { value: ":focus", class: SELECTOR_PSEUDO_CLASS_LOCK }
+ ]
+ },
+ {
+ node: ".testclass",
+ pseudoClass: ":hover",
+ expected: [
+ { value: ".testclass", class: SELECTOR_ELEMENT },
+ { value: ":hover", class: SELECTOR_PSEUDO_CLASS_LOCK }
+ ]
+ },
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ for (let {node, pseudoClass, expected} of TEST_DATA) {
+ yield selectNode(node, inspector);
+
+ if (pseudoClass) {
+ let onRefresh = inspector.once("rule-view-refreshed");
+ inspector.togglePseudoClass(pseudoClass);
+ yield onRefresh;
+ }
+
+ let selectorContainer =
+ getRuleViewRuleEditor(view, 1).selectorText.firstChild;
+
+ if (selectorContainer.children.length === expected.length) {
+ for (let i = 0; i < expected.length; i++) {
+ is(expected[i].value, selectorContainer.children[i].textContent,
+ "Got expected selector value: " + expected[i].value + " == " +
+ selectorContainer.children[i].textContent);
+ is(expected[i].class, selectorContainer.children[i].className,
+ "Got expected class name: " + expected[i].class + " == " +
+ selectorContainer.children[i].className);
+ }
+ } else {
+ for (let selector of selectorContainer.children) {
+ info("Actual selector components: { value: " + selector.textContent +
+ ", class: " + selector.className + " }\n");
+ }
+ }
+ }
+});
diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js
new file mode 100644
index 000000000..dea9fff32
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js
@@ -0,0 +1,182 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view strict search filter and clear button works properly
+// in the computed list
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ margin: 4px 0px 10px 44px;
+ }
+ .testclass {
+ background-color: red;
+ }
+ </style>
+ <h1 id="testid" class="testclass">Styled Node</h1>
+`;
+
+const TEST_DATA = [
+ {
+ desc: "Tests that the strict search filter works properly in the " +
+ "computed list for property names",
+ search: "`margin-left`",
+ isExpanderOpen: true,
+ isFilterOpen: true,
+ isMarginHighlighted: false,
+ isMarginTopHighlighted: false,
+ isMarginRightHighlighted: false,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: true
+ },
+ {
+ desc: "Tests that the strict search filter works properly in the " +
+ "computed list for property values",
+ search: "`0px`",
+ isExpanderOpen: true,
+ isFilterOpen: true,
+ isMarginHighlighted: false,
+ isMarginTopHighlighted: false,
+ isMarginRightHighlighted: true,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: false
+ },
+ {
+ desc: "Tests that the strict search filter works properly in the " +
+ "computed list for parsed property names",
+ search: "`margin-left`:",
+ isExpanderOpen: true,
+ isFilterOpen: true,
+ isMarginHighlighted: false,
+ isMarginTopHighlighted: false,
+ isMarginRightHighlighted: false,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: true
+ },
+ {
+ desc: "Tests that the strict search filter works properly in the " +
+ "computed list for parsed property values",
+ search: ":`4px`",
+ isExpanderOpen: true,
+ isFilterOpen: true,
+ isMarginHighlighted: false,
+ isMarginTopHighlighted: true,
+ isMarginRightHighlighted: false,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: false
+ },
+ {
+ desc: "Tests that the strict search filter works properly in the " +
+ "computed list for property line input",
+ search: "`margin-top`:`4px`",
+ isExpanderOpen: true,
+ isFilterOpen: true,
+ isMarginHighlighted: false,
+ isMarginTopHighlighted: true,
+ isMarginRightHighlighted: false,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: false
+ },
+ {
+ desc: "Tests that the strict search filter works properly in the " +
+ "computed list for a parsed strict property name and non-strict " +
+ "property value",
+ search: "`margin-top`:4px",
+ isExpanderOpen: true,
+ isFilterOpen: true,
+ isMarginHighlighted: false,
+ isMarginTopHighlighted: true,
+ isMarginRightHighlighted: false,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: false
+ },
+ {
+ desc: "Tests that the strict search filter works properly in the " +
+ "computed list for a parsed strict property value and non-strict " +
+ "property name",
+ search: "i:`4px`",
+ isExpanderOpen: true,
+ isFilterOpen: true,
+ isMarginHighlighted: false,
+ isMarginTopHighlighted: true,
+ isMarginRightHighlighted: false,
+ isMarginBottomHighlighted: false,
+ isMarginLeftHighlighted: false
+ },
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ for (let data of TEST_DATA) {
+ info(data.desc);
+ yield setSearchFilter(view, data.search);
+ yield checkRules(view, data);
+ yield clearSearchAndCheckRules(view);
+ }
+}
+
+function* checkRules(view, data) {
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let textPropEditor = rule.textProps[0].editor;
+ let computed = textPropEditor.computed;
+
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ is(!!textPropEditor.expander.getAttribute("open"), data.isExpanderOpen,
+ "Got correct expander state.");
+ is(computed.hasAttribute("filter-open"), data.isFilterOpen,
+ "Got correct expanded state for margin computed list.");
+ is(textPropEditor.container.classList.contains("ruleview-highlight"),
+ data.isMarginHighlighted,
+ "Got correct highlight for margin text property.");
+
+ is(computed.children[0].classList.contains("ruleview-highlight"),
+ data.isMarginTopHighlighted,
+ "Got correct highlight for margin-top computed property.");
+ is(computed.children[1].classList.contains("ruleview-highlight"),
+ data.isMarginRightHighlighted,
+ "Got correct highlight for margin-right computed property.");
+ is(computed.children[2].classList.contains("ruleview-highlight"),
+ data.isMarginBottomHighlighted,
+ "Got correct highlight for margin-bottom computed property.");
+ is(computed.children[3].classList.contains("ruleview-highlight"),
+ data.isMarginLeftHighlighted,
+ "Got correct highlight for margin-left computed property.");
+}
+
+function* clearSearchAndCheckRules(view) {
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+ let searchClearButton = view.searchClearButton;
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let textPropEditor = rule.textProps[0].editor;
+ let computed = textPropEditor.computed;
+
+ info("Clearing the search filter");
+ EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
+ yield view.inspector.once("ruleview-filtered");
+
+ info("Check the search filter is cleared and no rules are highlighted");
+ is(view.element.children.length, 3, "Should have 3 rules.");
+ ok(!searchField.value, "Search filter is cleared");
+ ok(!view.styleDocument.querySelectorAll(".ruleview-highlight").length,
+ "No rules are higlighted");
+
+ ok(!textPropEditor.expander.getAttribute("open"), "Expander is closed.");
+ ok(!computed.hasAttribute("filter-open"),
+ "margin computed list is closed.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js
new file mode 100644
index 000000000..50948e174
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js
@@ -0,0 +1,130 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view strict search filter works properly for property
+// names.
+
+const TEST_URI = `
+ <style type="text/css">
+ #testid {
+ width: 2%;
+ color: red;
+ }
+ .testclass {
+ width: 22%;
+ background-color: #00F;
+ }
+ </style>
+ <h1 id="testid" class="testclass">Styled Node</h1>
+`;
+
+const TEST_DATA = [
+ {
+ desc: "Tests that the strict search filter works properly for property " +
+ "names",
+ search: "`color`",
+ ruleCount: 2,
+ propertyIndex: 1
+ },
+ {
+ desc: "Tests that the strict search filter works properly for property " +
+ "values",
+ search: "`2%`",
+ ruleCount: 2,
+ propertyIndex: 0
+ },
+ {
+ desc: "Tests that the strict search filter works properly for parsed " +
+ "property names",
+ search: "`color`:",
+ ruleCount: 2,
+ propertyIndex: 1
+ },
+ {
+ desc: "Tests that the strict search filter works properly for parsed " +
+ "property values",
+ search: ":`2%`",
+ ruleCount: 2,
+ propertyIndex: 0
+ },
+ {
+ desc: "Tests that the strict search filter works properly for property " +
+ "line input",
+ search: "`width`:`2%`",
+ ruleCount: 2,
+ propertyIndex: 0
+ },
+ {
+ desc: "Tests that the search filter works properly for a parsed strict " +
+ "property name and non-strict property value.",
+ search: "`width`:2%",
+ ruleCount: 3,
+ propertyIndex: 0
+ },
+ {
+ desc: "Tests that the search filter works properly for a parsed strict " +
+ "property value and non-strict property name.",
+ search: "i:`2%`",
+ ruleCount: 2,
+ propertyIndex: 0
+ }
+];
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ for (let data of TEST_DATA) {
+ info(data.desc);
+ yield setSearchFilter(view, data.search);
+ yield checkRules(view, data);
+ yield clearSearchAndCheckRules(view);
+ }
+}
+
+function* checkRules(view, data) {
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, data.ruleCount,
+ "Should have " + data.ruleCount + " rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+
+ is(rule.selectorText, "#testid", "Second rule is #testid.");
+ ok(rule.textProps[data.propertyIndex].editor.container.classList
+ .contains("ruleview-highlight"),
+ "Text property is correctly highlighted.");
+
+ if (data.ruleCount > 2) {
+ rule = getRuleViewRuleEditor(view, 2).rule;
+ is(rule.selectorText, ".testclass", "Third rule is .testclass.");
+ ok(rule.textProps[data.propertyIndex].editor.container.classList
+ .contains("ruleview-highlight"),
+ "Text property is correctly highlighted.");
+ }
+}
+
+function* clearSearchAndCheckRules(view) {
+ let doc = view.styleDocument;
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+ let searchClearButton = view.searchClearButton;
+
+ info("Clearing the search filter");
+ EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
+ yield view.inspector.once("ruleview-filtered");
+
+ info("Check the search filter is cleared and no rules are highlighted");
+ is(view.element.children.length, 3, "Should have 3 rules.");
+ ok(!searchField.value, "Search filter is cleared.");
+ ok(!doc.querySelectorAll(".ruleview-highlight").length,
+ "No rules are higlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_02.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_02.js
new file mode 100644
index 000000000..0c76f0518
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_02.js
@@ -0,0 +1,34 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view strict search filter works properly for stylesheet
+// source.
+
+const SEARCH = "`doc_urls_clickable.css:1`";
+const TEST_URI = URL_ROOT + "doc_urls_clickable.html";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".relative1", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let rule = getRuleViewRuleEditor(view, 1).rule;
+ let source = rule.textProps[0].editor.ruleEditor.source;
+
+ is(rule.selectorText, ".relative1", "Second rule is .relative1.");
+ ok(source.classList.contains("ruleview-highlight"),
+ "stylesheet source is correctly highlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_03.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_03.js
new file mode 100644
index 000000000..0326b0e9c
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_03.js
@@ -0,0 +1,44 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view strict search filter works properly for selector
+// values.
+
+const SEARCH = "`.testclass`";
+
+const TEST_URI = `
+ <style type="text/css">
+ .testclass1 {
+ background-color: #00F;
+ }
+ .testclass {
+ color: red;
+ }
+ </style>
+ <h1 id="testid" class="testclass testclass1">Styled Node</h1>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testid", inspector);
+ yield testAddTextInFilter(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, view) {
+ yield setSearchFilter(view, SEARCH);
+
+ info("Check that the correct rules are visible");
+ is(view.element.children.length, 2, "Should have 2 rules.");
+ is(getRuleViewRuleEditor(view, 0).rule.selectorText, "element",
+ "First rule is inline element.");
+
+ let ruleEditor = getRuleViewRuleEditor(view, 1);
+
+ is(ruleEditor.rule.selectorText, ".testclass", "Second rule is .testclass.");
+ ok(ruleEditor.selectorText.children[0].classList
+ .contains("ruleview-highlight"), ".testclass selector is highlighted.");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js b/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js
new file mode 100644
index 000000000..927deb8ce
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js
@@ -0,0 +1,203 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// FIXME: Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+// Test the links from the rule-view to the styleeditor
+
+const STYLESHEET_URL = "data:text/css," + encodeURIComponent(
+ ["#first {",
+ "color: blue",
+ "}"].join("\n"));
+
+const EXTERNAL_STYLESHEET_FILE_NAME = "doc_style_editor_link.css";
+const EXTERNAL_STYLESHEET_URL = URL_ROOT + EXTERNAL_STYLESHEET_FILE_NAME;
+
+const DOCUMENT_URL = "data:text/html;charset=utf-8," + encodeURIComponent(`
+ <html>
+ <head>
+ <title>Rule view style editor link test</title>
+ <style type="text/css">
+ html { color: #000000; }
+ div { font-variant: small-caps; color: #000000; }
+ .nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em;
+ font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">
+ </style>
+ <style>
+ div { font-weight: bold; }
+ </style>
+ <link rel="stylesheet" type="text/css" href="${STYLESHEET_URL}">
+ <link rel="stylesheet" type="text/css" href="${EXTERNAL_STYLESHEET_URL}">
+ </head>
+ <body>
+ <h1>Some header text</h1>
+ <p id="salutation" style="font-size: 12pt">hi.</p>
+ <p id="body" style="font-size: 12pt">I am a test-case. This text exists
+ solely to provide some things to
+ <span style="color: yellow" class="highlight">
+ highlight</span> and <span style="font-weight: bold">count</span>
+ style list-items in the box at right. If you are reading this,
+ you should go do something else instead. Maybe read a book. Or better
+ yet, write some test-cases for another bit of code.
+ <span style="font-style: italic">some text</span></p>
+ <p id="closing">more text</p>
+ <p>even more text</p>
+ </div>
+ </body>
+ </html>
+`);
+
+add_task(function* () {
+ yield addTab(DOCUMENT_URL);
+ let {toolbox, inspector, view, testActor} = yield openRuleView();
+ yield selectNode("div", inspector);
+
+ yield testInlineStyle(view);
+ yield testFirstInlineStyleSheet(view, toolbox, testActor);
+ yield testSecondInlineStyleSheet(view, toolbox, testActor);
+ yield testExternalStyleSheet(view, toolbox, testActor);
+ yield testDisabledStyleEditor(view, toolbox);
+});
+
+function* testInlineStyle(view) {
+ info("Testing inline style");
+
+ let onTab = waitForTab();
+ info("Clicking on the first link in the rule-view");
+ clickLinkByIndex(view, 0);
+
+ let tab = yield onTab;
+
+ let tabURI = tab.linkedBrowser.documentURI.spec;
+ ok(tabURI.startsWith("view-source:"), "View source tab is open");
+ info("Closing tab");
+ gBrowser.removeTab(tab);
+}
+
+function* testFirstInlineStyleSheet(view, toolbox, testActor) {
+ info("Testing inline stylesheet");
+
+ info("Listening for toolbox switch to the styleeditor");
+ let onSwitch = waitForStyleEditor(toolbox);
+
+ info("Clicking an inline stylesheet");
+ clickLinkByIndex(view, 4);
+ let editor = yield onSwitch;
+
+ ok(true, "Switched to the style-editor panel in the toolbox");
+
+ yield validateStyleEditorSheet(editor, 0, testActor);
+}
+
+function* testSecondInlineStyleSheet(view, toolbox, testActor) {
+ info("Testing second inline stylesheet");
+
+ info("Waiting for the stylesheet editor to be selected");
+ let panel = toolbox.getCurrentPanel();
+ let onSelected = panel.UI.once("editor-selected");
+
+ info("Switching back to the inspector panel in the toolbox");
+ yield toolbox.selectTool("inspector");
+
+ info("Clicking on second inline stylesheet link");
+ testRuleViewLinkLabel(view);
+ clickLinkByIndex(view, 3);
+ let editor = yield onSelected;
+
+ is(toolbox.currentToolId, "styleeditor",
+ "The style editor is selected again");
+ yield validateStyleEditorSheet(editor, 1, testActor);
+}
+
+function* testExternalStyleSheet(view, toolbox, testActor) {
+ info("Testing external stylesheet");
+
+ info("Waiting for the stylesheet editor to be selected");
+ let panel = toolbox.getCurrentPanel();
+ let onSelected = panel.UI.once("editor-selected");
+
+ info("Switching back to the inspector panel in the toolbox");
+ yield toolbox.selectTool("inspector");
+
+ info("Clicking on an external stylesheet link");
+ testRuleViewLinkLabel(view);
+ clickLinkByIndex(view, 1);
+ let editor = yield onSelected;
+
+ is(toolbox.currentToolId, "styleeditor",
+ "The style editor is selected again");
+ yield validateStyleEditorSheet(editor, 2, testActor);
+}
+
+function* validateStyleEditorSheet(editor, expectedSheetIndex, testActor) {
+ info("validating style editor stylesheet");
+ is(editor.styleSheet.styleSheetIndex, expectedSheetIndex,
+ "loaded stylesheet index matches document stylesheet");
+
+ let href = editor.styleSheet.href || editor.styleSheet.nodeHref;
+
+ let expectedHref = yield testActor.eval(
+ `content.document.styleSheets[${expectedSheetIndex}].href ||
+ content.document.location.href`);
+
+ is(href, expectedHref, "loaded stylesheet href matches document stylesheet");
+}
+
+function* testDisabledStyleEditor(view, toolbox) {
+ info("Testing with the style editor disabled");
+
+ info("Switching to the inspector panel in the toolbox");
+ yield toolbox.selectTool("inspector");
+
+ info("Disabling the style editor");
+ Services.prefs.setBoolPref("devtools.styleeditor.enabled", false);
+ gDevTools.emit("tool-unregistered", "styleeditor");
+
+ info("Clicking on a link");
+ testUnselectableRuleViewLink(view, 1);
+ clickLinkByIndex(view, 1);
+
+ is(toolbox.currentToolId, "inspector", "The click should have no effect");
+
+ info("Enabling the style editor");
+ Services.prefs.setBoolPref("devtools.styleeditor.enabled", true);
+ gDevTools.emit("tool-registered", "styleeditor");
+
+ info("Clicking on a link");
+ let onStyleEditorSelected = toolbox.once("styleeditor-selected");
+ clickLinkByIndex(view, 1);
+ yield onStyleEditorSelected;
+ is(toolbox.currentToolId, "styleeditor", "Style Editor should be selected");
+
+ Services.prefs.clearUserPref("devtools.styleeditor.enabled");
+}
+
+function testRuleViewLinkLabel(view) {
+ let link = getRuleViewLinkByIndex(view, 2);
+ let labelElem = link.querySelector(".ruleview-rule-source-label");
+ let value = labelElem.textContent;
+ let tooltipText = labelElem.getAttribute("title");
+
+ is(value, EXTERNAL_STYLESHEET_FILE_NAME + ":1",
+ "rule view stylesheet display value matches filename and line number");
+ is(tooltipText, EXTERNAL_STYLESHEET_URL + ":1",
+ "rule view stylesheet tooltip text matches the full URI path");
+}
+
+function testUnselectableRuleViewLink(view, index) {
+ let link = getRuleViewLinkByIndex(view, index);
+ let unselectable = link.hasAttribute("unselectable");
+
+ ok(unselectable, "Rule view is unselectable");
+}
+
+function clickLinkByIndex(view, index) {
+ let link = getRuleViewLinkByIndex(view, index);
+ link.scrollIntoView();
+ link.click();
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_urls-clickable.js b/devtools/client/inspector/rules/test/browser_rules_urls-clickable.js
new file mode 100644
index 000000000..fb1211e3c
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_urls-clickable.js
@@ -0,0 +1,70 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests to make sure that URLs are clickable in the rule view
+
+const TEST_URI = URL_ROOT + "doc_urls_clickable.html";
+const TEST_IMAGE = URL_ROOT + "doc_test_image.png";
+const BASE_64_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAA" +
+ "FCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAA" +
+ "BJRU5ErkJggg==";
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+ yield selectNodes(inspector, view);
+});
+
+function* selectNodes(inspector, ruleView) {
+ let relative1 = ".relative1";
+ let relative2 = ".relative2";
+ let absolute = ".absolute";
+ let inline = ".inline";
+ let base64 = ".base64";
+ let noimage = ".noimage";
+ let inlineresolved = ".inline-resolved";
+
+ yield selectNode(relative1, inspector);
+ let relativeLink = ruleView.styleDocument
+ .querySelector(".ruleview-propertyvaluecontainer a");
+ ok(relativeLink, "Link exists for relative1 node");
+ is(relativeLink.getAttribute("href"), TEST_IMAGE, "href matches");
+
+ yield selectNode(relative2, inspector);
+ relativeLink = ruleView.styleDocument
+ .querySelector(".ruleview-propertyvaluecontainer a");
+ ok(relativeLink, "Link exists for relative2 node");
+ is(relativeLink.getAttribute("href"), TEST_IMAGE, "href matches");
+
+ yield selectNode(absolute, inspector);
+ let absoluteLink = ruleView.styleDocument
+ .querySelector(".ruleview-propertyvaluecontainer a");
+ ok(absoluteLink, "Link exists for absolute node");
+ is(absoluteLink.getAttribute("href"), TEST_IMAGE, "href matches");
+
+ yield selectNode(inline, inspector);
+ let inlineLink = ruleView.styleDocument
+ .querySelector(".ruleview-propertyvaluecontainer a");
+ ok(inlineLink, "Link exists for inline node");
+ is(inlineLink.getAttribute("href"), TEST_IMAGE, "href matches");
+
+ yield selectNode(base64, inspector);
+ let base64Link = ruleView.styleDocument
+ .querySelector(".ruleview-propertyvaluecontainer a");
+ ok(base64Link, "Link exists for base64 node");
+ is(base64Link.getAttribute("href"), BASE_64_URL, "href matches");
+
+ yield selectNode(inlineresolved, inspector);
+ let inlineResolvedLink = ruleView.styleDocument
+ .querySelector(".ruleview-propertyvaluecontainer a");
+ ok(inlineResolvedLink, "Link exists for style tag node");
+ is(inlineResolvedLink.getAttribute("href"), TEST_IMAGE, "href matches");
+
+ yield selectNode(noimage, inspector);
+ let noimageLink = ruleView.styleDocument
+ .querySelector(".ruleview-propertyvaluecontainer a");
+ ok(!noimageLink, "There is no link for the node with no background image");
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_user-agent-styles-uneditable.js b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles-uneditable.js
new file mode 100644
index 000000000..e1bafff9b
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles-uneditable.js
@@ -0,0 +1,58 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that user agent styles are never editable via
+// the UI
+
+const TEST_URI = `
+ <blockquote type=cite>
+ <pre _moz_quote=true>
+ inspect <a href='foo' style='color:orange'>user agent</a> styles
+ </pre>
+ </blockquote>
+`;
+
+var PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
+
+add_task(function* () {
+ info("Starting the test with the pref set to true before toolbox is opened");
+ Services.prefs.setBoolPref(PREF_UA_STYLES, true);
+
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield userAgentStylesUneditable(inspector, view);
+
+ info("Resetting " + PREF_UA_STYLES);
+ Services.prefs.clearUserPref(PREF_UA_STYLES);
+});
+
+function* userAgentStylesUneditable(inspector, view) {
+ info("Making sure that UI is not editable for user agent styles");
+
+ yield selectNode("a", inspector);
+ let uaRules = view._elementStyle.rules.filter(rule=>!rule.editor.isEditable);
+
+ for (let rule of uaRules) {
+ ok(rule.editor.element.hasAttribute("uneditable"),
+ "UA rules have uneditable attribute");
+
+ let firstProp = rule.textProps.filter(p => !p.invisible)[0];
+
+ ok(!firstProp.editor.nameSpan._editable,
+ "nameSpan is not editable");
+ ok(!firstProp.editor.valueSpan._editable,
+ "valueSpan is not editable");
+ ok(!rule.editor.closeBrace._editable, "closeBrace is not editable");
+
+ let colorswatch = rule.editor.element
+ .querySelector(".ruleview-colorswatch");
+ if (colorswatch) {
+ ok(!view.tooltips.colorPicker.swatches.has(colorswatch),
+ "The swatch is not editable");
+ }
+ }
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js
new file mode 100644
index 000000000..6852e3c03
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js
@@ -0,0 +1,183 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that user agent styles are inspectable via rule view if
+// it is preffed on.
+
+var PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
+const { PrefObserver } = require("devtools/client/styleeditor/utils");
+
+const TEST_URI = URL_ROOT + "doc_author-sheet.html";
+
+const TEST_DATA = [
+ {
+ selector: "blockquote",
+ numUserRules: 1,
+ numUARules: 0
+ },
+ {
+ selector: "pre",
+ numUserRules: 1,
+ numUARules: 0
+ },
+ {
+ selector: "input[type=range]",
+ numUserRules: 1,
+ numUARules: 0
+ },
+ {
+ selector: "input[type=number]",
+ numUserRules: 1,
+ numUARules: 0
+ },
+ {
+ selector: "input[type=color]",
+ numUserRules: 1,
+ numUARules: 0
+ },
+ {
+ selector: "input[type=text]",
+ numUserRules: 1,
+ numUARules: 0
+ },
+ {
+ selector: "progress",
+ numUserRules: 1,
+ numUARules: 0
+ },
+ // Note that some tests below assume that the "a" selector is the
+ // last test in TEST_DATA.
+ {
+ selector: "a",
+ numUserRules: 3,
+ numUARules: 0
+ }
+];
+
+add_task(function* () {
+ requestLongerTimeout(4);
+
+ info("Starting the test with the pref set to true before toolbox is opened");
+ yield setUserAgentStylesPref(true);
+
+ yield addTab(TEST_URI);
+ let {inspector, view} = yield openRuleView();
+
+ info("Making sure that UA styles are visible on initial load");
+ yield userAgentStylesVisible(inspector, view);
+
+ info("Making sure that setting the pref to false hides UA styles");
+ yield setUserAgentStylesPref(false);
+ yield userAgentStylesNotVisible(inspector, view);
+
+ info("Making sure that resetting the pref to true shows UA styles again");
+ yield setUserAgentStylesPref(true);
+ yield userAgentStylesVisible(inspector, view);
+
+ info("Resetting " + PREF_UA_STYLES);
+ Services.prefs.clearUserPref(PREF_UA_STYLES);
+});
+
+function* setUserAgentStylesPref(val) {
+ info("Setting the pref " + PREF_UA_STYLES + " to: " + val);
+
+ // Reset the pref and wait for PrefObserver to callback so UI
+ // has a chance to get updated.
+ let oncePrefChanged = defer();
+ let prefObserver = new PrefObserver("devtools.");
+ prefObserver.on(PREF_UA_STYLES, oncePrefChanged.resolve);
+ Services.prefs.setBoolPref(PREF_UA_STYLES, val);
+ yield oncePrefChanged.promise;
+ prefObserver.off(PREF_UA_STYLES, oncePrefChanged.resolve);
+}
+
+function* userAgentStylesVisible(inspector, view) {
+ info("Making sure that user agent styles are currently visible");
+
+ let userRules;
+ let uaRules;
+
+ for (let data of TEST_DATA) {
+ yield selectNode(data.selector, inspector);
+ yield compareAppliedStylesWithUI(inspector, view, "ua");
+
+ userRules = view._elementStyle.rules.filter(rule=>rule.editor.isEditable);
+ uaRules = view._elementStyle.rules.filter(rule=>!rule.editor.isEditable);
+ is(userRules.length, data.numUserRules, "Correct number of user rules");
+ ok(uaRules.length > data.numUARules, "Has UA rules");
+ }
+
+ ok(userRules.some(rule => rule.matchedSelectors.length === 1),
+ "There is an inline style for element in user styles");
+
+ // These tests rely on the "a" selector being the last test in
+ // TEST_DATA.
+ ok(uaRules.some(rule => {
+ return rule.matchedSelectors.indexOf(":any-link") !== -1;
+ }), "There is a rule for :any-link");
+ ok(uaRules.some(rule => {
+ return rule.matchedSelectors.indexOf("*|*:link") !== -1;
+ }), "There is a rule for *|*:link");
+ ok(uaRules.some(rule => {
+ return rule.matchedSelectors.length === 1;
+ }), "Inline styles for ua styles");
+}
+
+function* userAgentStylesNotVisible(inspector, view) {
+ info("Making sure that user agent styles are not currently visible");
+
+ let userRules;
+ let uaRules;
+
+ for (let data of TEST_DATA) {
+ yield selectNode(data.selector, inspector);
+ yield compareAppliedStylesWithUI(inspector, view);
+
+ userRules = view._elementStyle.rules.filter(rule=>rule.editor.isEditable);
+ uaRules = view._elementStyle.rules.filter(rule=>!rule.editor.isEditable);
+ is(userRules.length, data.numUserRules, "Correct number of user rules");
+ is(uaRules.length, data.numUARules, "No UA rules");
+ }
+}
+
+function* compareAppliedStylesWithUI(inspector, view, filter) {
+ info("Making sure that UI is consistent with pageStyle.getApplied");
+
+ let entries = yield inspector.pageStyle.getApplied(
+ inspector.selection.nodeFront,
+ {
+ inherited: true,
+ matchedSelectors: true,
+ filter: filter
+ }
+ );
+
+ // We may see multiple entries that map to a given rule; filter the
+ // duplicates here to match what the UI does.
+ let entryMap = new Map();
+ for (let entry of entries) {
+ entryMap.set(entry.rule, entry);
+ }
+ entries = [...entryMap.values()];
+
+ let elementStyle = view._elementStyle;
+ is(elementStyle.rules.length, entries.length,
+ "Should have correct number of rules (" + entries.length + ")");
+
+ entries = entries.sort((a, b) => {
+ return (a.pseudoElement || "z") > (b.pseudoElement || "z");
+ });
+
+ entries.forEach((entry, i) => {
+ let elementStyleRule = elementStyle.rules[i];
+ is(elementStyleRule.inherited, entry.inherited,
+ "Same inherited (" + entry.inherited + ")");
+ is(elementStyleRule.isSystem, entry.isSystem,
+ "Same isSystem (" + entry.isSystem + ")");
+ is(elementStyleRule.editor.isEditable, !entry.isSystem,
+ "Editor isEditable opposite of UA (" + entry.isSystem + ")");
+ });
+}
diff --git a/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js b/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js
new file mode 100644
index 000000000..62b1d927c
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js
@@ -0,0 +1,90 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that user set style properties can be changed from the markup-view and
+// don't survive page reload
+
+const TEST_URI = `
+ <p id='id1' style='width:200px;'>element 1</p>
+ <p id='id2' style='width:100px;'>element 2</p>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view, testActor} = yield openRuleView();
+
+ yield selectNode("#id1", inspector);
+ yield modifyRuleViewWidth("300px", view, inspector);
+ yield assertRuleAndMarkupViewWidth("id1", "300px", view, inspector);
+
+ yield selectNode("#id2", inspector);
+ yield assertRuleAndMarkupViewWidth("id2", "100px", view, inspector);
+ yield modifyRuleViewWidth("50px", view, inspector);
+ yield assertRuleAndMarkupViewWidth("id2", "50px", view, inspector);
+
+ yield reloadPage(inspector, testActor);
+
+ yield selectNode("#id1", inspector);
+ yield assertRuleAndMarkupViewWidth("id1", "200px", view, inspector);
+ yield selectNode("#id2", inspector);
+ yield assertRuleAndMarkupViewWidth("id2", "100px", view, inspector);
+});
+
+function getStyleRule(ruleView) {
+ return ruleView.styleDocument.querySelector(".ruleview-rule");
+}
+
+function* modifyRuleViewWidth(value, ruleView, inspector) {
+ info("Getting the property value element");
+ let valueSpan = getStyleRule(ruleView)
+ .querySelector(".ruleview-propertyvalue");
+
+ info("Focusing the property value to set it to edit mode");
+ let editor = yield focusEditableField(ruleView, valueSpan.parentNode);
+
+ ok(editor.input, "The inplace-editor field is ready");
+ info("Setting the new value");
+ editor.input.value = value;
+
+ info("Pressing return and waiting for the field to blur and for the " +
+ "markup-view to show the mutation");
+ let onBlur = once(editor.input, "blur", true);
+ let onStyleChanged = waitForStyleModification(inspector);
+ EventUtils.sendKey("return");
+ yield onBlur;
+ yield onStyleChanged;
+
+ info("Escaping out of the new property field that has been created after " +
+ "the value was edited");
+ let onNewFieldBlur = once(ruleView.styleDocument.activeElement, "blur", true);
+ EventUtils.sendKey("escape");
+ yield onNewFieldBlur;
+}
+
+function* getContainerStyleAttrValue(id, {walker, markup}) {
+ let front = yield walker.querySelector(walker.rootNode, "#" + id);
+ let container = markup.getContainer(front);
+
+ let attrIndex = 0;
+ for (let attrName of container.elt.querySelectorAll(".attr-name")) {
+ if (attrName.textContent === "style") {
+ return container.elt.querySelectorAll(".attr-value")[attrIndex];
+ }
+ attrIndex++;
+ }
+ return undefined;
+}
+
+function* assertRuleAndMarkupViewWidth(id, value, ruleView, inspector) {
+ let valueSpan = getStyleRule(ruleView)
+ .querySelector(".ruleview-propertyvalue");
+ is(valueSpan.textContent, value,
+ "Rule-view style width is " + value + " as expected");
+
+ let attr = yield getContainerStyleAttrValue(id, inspector);
+ is(attr.textContent.replace(/\s/g, ""),
+ "width:" + value + ";", "Markup-view style attribute width is " + value);
+}
diff --git a/devtools/client/inspector/rules/test/doc_author-sheet.html b/devtools/client/inspector/rules/test/doc_author-sheet.html
new file mode 100644
index 000000000..f8c2eadd5
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_author-sheet.html
@@ -0,0 +1,39 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <title>authored sheet test</title>
+
+ <style>
+ pre a {
+ color: orange;
+ }
+ </style>
+
+ <script>
+ "use strict";
+ var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+
+ var style = "data:text/css,a { background-color: seagreen; }";
+ var uri = gIOService.newURI(style, null, null);
+ var windowUtils = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+ windowUtils.loadSheet(uri, windowUtils.AUTHOR_SHEET);
+ </script>
+
+</head>
+<body>
+ <input type=text placeholder=test></input>
+ <input type=color></input>
+ <input type=range></input>
+ <input type=number></input>
+ <progress></progress>
+ <blockquote type=cite>
+ <pre _moz_quote=true>
+ inspect <a href="foo">user agent</a> styles
+ </pre>
+ </blockquote>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_blob_stylesheet.html b/devtools/client/inspector/rules/test/doc_blob_stylesheet.html
new file mode 100644
index 000000000..c9973993b
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_blob_stylesheet.html
@@ -0,0 +1,39 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+</html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Blob stylesheet sourcemap</title>
+</head>
+<body>
+<h1>Test</h1>
+<script>
+"use strict";
+
+var cssContent = `body {
+ background-color: black;
+}
+body > h1 {
+ color: white;
+}
+` +
+"/*# sourceMappingURL=data:application/json;base64,ewoidmVyc2lvbiI6IDMsCiJtYX" +
+"BwaW5ncyI6ICJBQUFBLElBQUs7RUFDSCxnQkFBZ0IsRUFBRSxLQUFLOztBQUN2QixTQUFPO0VBQ0" +
+"wsS0FBSyxFQUFFLEtBQUsiLAoic291cmNlcyI6IFsidGVzdC5zY3NzIl0sCiJzb3VyY2VzQ29udG" +
+"VudCI6IFsiYm9keSB7XG4gIGJhY2tncm91bmQtY29sb3I6IGJsYWNrO1xuICAmID4gaDEge1xuIC" +
+"AgIGNvbG9yOiB3aGl0ZTsgIFxuICB9XG59XG4iXSwKIm5hbWVzIjogW10sCiJmaWxlIjogInRlc3" +
+"QuY3NzIgp9Cg== */";
+var cssBlob = new Blob([cssContent], {type: "text/css"});
+var url = URL.createObjectURL(cssBlob);
+
+var head = document.querySelector("head");
+var link = document.createElement("link");
+link.rel = "stylesheet";
+link.type = "text/css";
+link.href = url;
+head.appendChild(link);
+</script>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet.html b/devtools/client/inspector/rules/test/doc_content_stylesheet.html
new file mode 100644
index 000000000..3ea65f606
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_content_stylesheet.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+ <title>test</title>
+
+ <link href="./doc_content_stylesheet_linked.css" rel="stylesheet" type="text/css">
+
+ <script>
+ /* eslint no-unused-vars: [2, {"vars": "local"}] */
+ "use strict";
+ // Load script.css
+ function loadCSS() {
+ let link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.type = "text/css";
+ link.href = "./doc_content_stylesheet_script.css";
+ document.getElementsByTagName("head")[0].appendChild(link);
+ }
+ </script>
+
+ <style>
+ table {
+ border: 1px solid #000;
+ }
+ </style>
+</head>
+<body onload="loadCSS();">
+ <table id="target">
+ <tr>
+ <td>
+ <h3>Simple test</h3>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet_imported.css b/devtools/client/inspector/rules/test/doc_content_stylesheet_imported.css
new file mode 100644
index 000000000..ea1a3d986
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_content_stylesheet_imported.css
@@ -0,0 +1,5 @@
+@import url("./doc_content_stylesheet_imported2.css");
+
+#target {
+ text-decoration: underline;
+}
diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet_imported2.css b/devtools/client/inspector/rules/test/doc_content_stylesheet_imported2.css
new file mode 100644
index 000000000..77c73299e
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_content_stylesheet_imported2.css
@@ -0,0 +1,3 @@
+#target {
+ text-decoration: underline;
+}
diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet_linked.css b/devtools/client/inspector/rules/test/doc_content_stylesheet_linked.css
new file mode 100644
index 000000000..712ba78fb
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_content_stylesheet_linked.css
@@ -0,0 +1,3 @@
+table {
+ border-collapse: collapse;
+}
diff --git a/devtools/client/inspector/rules/test/doc_content_stylesheet_script.css b/devtools/client/inspector/rules/test/doc_content_stylesheet_script.css
new file mode 100644
index 000000000..5aa5e2c6c
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_content_stylesheet_script.css
@@ -0,0 +1,5 @@
+@import url("./doc_content_stylesheet_imported.css");
+
+table {
+ opacity: 1;
+}
diff --git a/devtools/client/inspector/rules/test/doc_copystyles.css b/devtools/client/inspector/rules/test/doc_copystyles.css
new file mode 100644
index 000000000..83f0c87b1
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_copystyles.css
@@ -0,0 +1,11 @@
+/* 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, body, #testid {
+ color: #F00;
+ background-color: #00F;
+ font-size: 12px;
+ border-color: #00F !important;
+ --var: "*/";
+}
diff --git a/devtools/client/inspector/rules/test/doc_copystyles.html b/devtools/client/inspector/rules/test/doc_copystyles.html
new file mode 100644
index 000000000..da1b4c0b3
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_copystyles.html
@@ -0,0 +1,11 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+ <head>
+ <title>Test case for copying stylesheet in rule-view</title>
+ <link rel="stylesheet" type="text/css" href="doc_copystyles.css"/>
+ </head>
+ <body>
+ <div id='testid'>Styled Node</div>
+ </body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_cssom.html b/devtools/client/inspector/rules/test/doc_cssom.html
new file mode 100644
index 000000000..28de66d7d
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_cssom.html
@@ -0,0 +1,22 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <title>CSSOM test</title>
+
+ <script>
+ "use strict";
+ window.onload = function () {
+ let x = document.styleSheets[0];
+ x.insertRule("div { color: seagreen; }", 1);
+ };
+ </script>
+
+ <style>
+ span { }
+ </style>
+</head>
+<body>
+ <div id="target"> the ocean </div>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_custom.html b/devtools/client/inspector/rules/test/doc_custom.html
new file mode 100644
index 000000000..09bf501d5
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_custom.html
@@ -0,0 +1,33 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+ <head>
+ <style>
+ #testidSimple {
+ --background-color: blue;
+ }
+ .testclassSimple {
+ --background-color: green;
+ }
+
+ .testclassImportant {
+ --background-color: green !important;
+ }
+ #testidImportant {
+ --background-color: blue;
+ }
+
+ #testidDisable {
+ --background-color: blue;
+ }
+ .testclassDisable {
+ --background-color: green;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="testidSimple" class="testclassSimple">Styled Node</div>
+ <div id="testidImportant" class="testclassImportant">Styled Node</div>
+ <div id="testidDisable" class="testclassDisable">Styled Node</div>
+ </body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_filter.html b/devtools/client/inspector/rules/test/doc_filter.html
new file mode 100644
index 000000000..cb2df9feb
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_filter.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<!-- 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>Bug 1055181 - CSS Filter Editor Widget</title>
+ <style>
+ body {
+ filter: blur(2px) contrast(2);
+ }
+ </style>
+</head>
diff --git a/devtools/client/inspector/rules/test/doc_frame_script.js b/devtools/client/inspector/rules/test/doc_frame_script.js
new file mode 100644
index 000000000..88da043f1
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_frame_script.js
@@ -0,0 +1,113 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* globals addMessageListener, sendAsyncMessage */
+
+"use strict";
+
+// A helper frame-script for brower/devtools/styleinspector tests.
+//
+// Most listeners in the script expect "Test:"-namespaced messages from chrome,
+// then execute code upon receiving, and immediately send back a message.
+// This is so that chrome test code can execute code in content and wait for a
+// response this way:
+// let response = yield executeInContent(browser, "Test:msgName", data, true);
+// The response message should have the same name "Test:msgName"
+//
+// Some listeners do not send a response message back.
+
+var {utils: Cu} = Components;
+
+var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var defer = require("devtools/shared/defer");
+
+/**
+ * Get a value for a given property name in a css rule in a stylesheet, given
+ * their indexes
+ * @param {Object} data Expects a data object with the following properties
+ * - {Number} styleSheetIndex
+ * - {Number} ruleIndex
+ * - {String} name
+ * @return {String} The value, if found, null otherwise
+ */
+addMessageListener("Test:GetRulePropertyValue", function (msg) {
+ let {name, styleSheetIndex, ruleIndex} = msg.data;
+ let value = null;
+
+ dumpn("Getting the value for property name " + name + " in sheet " +
+ styleSheetIndex + " and rule " + ruleIndex);
+
+ let sheet = content.document.styleSheets[styleSheetIndex];
+ if (sheet) {
+ let rule = sheet.cssRules[ruleIndex];
+ if (rule) {
+ value = rule.style.getPropertyValue(name);
+ }
+ }
+
+ sendAsyncMessage("Test:GetRulePropertyValue", value);
+});
+
+/**
+ * Get the property value from the computed style for an element.
+ * @param {Object} data Expects a data object with the following properties
+ * - {String} selector: The selector used to obtain the element.
+ * - {String} pseudo: pseudo id to query, or null.
+ * - {String} name: name of the property
+ * @return {String} The value, if found, null otherwise
+ */
+addMessageListener("Test:GetComputedStylePropertyValue", function (msg) {
+ let {selector, pseudo, name} = msg.data;
+ let element = content.document.querySelector(selector);
+ let value = content.document.defaultView.getComputedStyle(element, pseudo)
+ .getPropertyValue(name);
+ sendAsyncMessage("Test:GetComputedStylePropertyValue", value);
+});
+
+/**
+ * Wait the property value from the computed style for an element and
+ * compare it with the expected value
+ * @param {Object} data Expects a data object with the following properties
+ * - {String} selector: The selector used to obtain the element.
+ * - {String} pseudo: pseudo id to query, or null.
+ * - {String} name: name of the property
+ * - {String} expected: the expected value for property
+ */
+addMessageListener("Test:WaitForComputedStylePropertyValue", function (msg) {
+ let {selector, pseudo, name, expected} = msg.data;
+ let element = content.document.querySelector(selector);
+ waitForSuccess(() => {
+ let value = content.document.defaultView.getComputedStyle(element, pseudo)
+ .getPropertyValue(name);
+
+ return value === expected;
+ }).then(() => {
+ sendAsyncMessage("Test:WaitForComputedStylePropertyValue");
+ });
+});
+
+var dumpn = msg => dump(msg + "\n");
+
+/**
+ * Polls a given function waiting for it to return true.
+ *
+ * @param {Function} validatorFn A validator function that returns a boolean.
+ * This is called every few milliseconds to check if the result is true. When
+ * it is true, the promise resolves.
+ * @return a promise that resolves when the function returned true or rejects
+ * if the timeout is reached
+ */
+function waitForSuccess(validatorFn) {
+ let def = defer();
+
+ function wait(fn) {
+ if (fn()) {
+ def.resolve();
+ } else {
+ setTimeout(() => wait(fn), 200);
+ }
+ }
+ wait(validatorFn);
+
+ return def.promise;
+}
diff --git a/devtools/client/inspector/rules/test/doc_inline_sourcemap.html b/devtools/client/inspector/rules/test/doc_inline_sourcemap.html
new file mode 100644
index 000000000..cb107d424
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_inline_sourcemap.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+ <title>CSS source maps in inline stylesheets</title>
+</head>
+<body>
+ <div>CSS source maps in inline stylesheets</div>
+ <style>
+div {
+ color: #ff0066; }
+
+span {
+ background-color: #EEE; }
+
+/*# sourceMappingURL=doc_sourcemaps.css.map */
+ </style>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_invalid_sourcemap.css b/devtools/client/inspector/rules/test/doc_invalid_sourcemap.css
new file mode 100644
index 000000000..ff96a6b54
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_invalid_sourcemap.css
@@ -0,0 +1,3 @@
+div { color: gold; }
+
+/*# sourceMappingURL=this-source-map-does-not-exist.css.map */ \ No newline at end of file
diff --git a/devtools/client/inspector/rules/test/doc_invalid_sourcemap.html b/devtools/client/inspector/rules/test/doc_invalid_sourcemap.html
new file mode 100644
index 000000000..2e6422bec
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_invalid_sourcemap.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Invalid source map</title>
+ <link rel="stylesheet" type="text/css" href="doc_invalid_sourcemap.css">
+</head>
+<body>
+ <div>invalid source map</div>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_keyframeLineNumbers.html b/devtools/client/inspector/rules/test/doc_keyframeLineNumbers.html
new file mode 100644
index 000000000..8fce04584
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_keyframeLineNumbers.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>keyframe line numbers test</title>
+ <style type="text/css">
+div {
+ animation-duration: 1s;
+ animation-iteration-count: infinite;
+ animation-direction: alternate;
+ animation-name: CC;
+}
+
+span {
+ animation-duration: 3s;
+ animation-iteration-count: infinite;
+ animation-direction: alternate;
+ animation-name: DD;
+}
+
+@keyframes CC {
+ from {
+ background: #ffffff;
+ }
+ to {
+ background: #f0c;
+ }
+}
+
+@keyframes DD {
+ from {
+ background: seagreen;
+ }
+ to {
+ background: chartreuse;
+ }
+}
+ </style>
+</head>
+<body>
+ <div id="outer">
+ <span id="inner">lizards</div>
+ </div>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_keyframeanimation.css b/devtools/client/inspector/rules/test/doc_keyframeanimation.css
new file mode 100644
index 000000000..64582ed35
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_keyframeanimation.css
@@ -0,0 +1,84 @@
+/* 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/. */
+
+.box {
+ height: 50px;
+ width: 50px;
+}
+
+.circle {
+ width: 20px;
+ height: 20px;
+ border-radius: 10px;
+ background-color: #FFCB01;
+}
+
+#pacman {
+ width: 0px;
+ height: 0px;
+ border-right: 60px solid transparent;
+ border-top: 60px solid #FFCB01;
+ border-left: 60px solid #FFCB01;
+ border-bottom: 60px solid #FFCB01;
+ border-top-left-radius: 60px;
+ border-bottom-left-radius: 60px;
+ border-top-right-radius: 60px;
+ border-bottom-right-radius: 60px;
+ top: 120px;
+ left: 150px;
+ position: absolute;
+ animation-name: pacman;
+ animation-fill-mode: forwards;
+ animation-timing-function: linear;
+ animation-duration: 15s;
+}
+
+#boxy {
+ top: 170px;
+ left: 450px;
+ position: absolute;
+ animation: 4s linear 0s normal none infinite boxy;
+}
+
+
+#moxy {
+ animation-name: moxy, boxy;
+ animation-delay: 3.5s;
+ animation-duration: 2s;
+ top: 170px;
+ left: 650px;
+ position: absolute;
+}
+
+@-moz-keyframes pacman {
+ 100% {
+ left: 750px;
+ }
+}
+
+@keyframes pacman {
+ 100% {
+ left: 750px;
+ }
+}
+
+@keyframes boxy {
+ 10% {
+ background-color: blue;
+ }
+
+ 20% {
+ background-color: green;
+ }
+
+ 100% {
+ opacity: 0;
+ }
+}
+
+@keyframes moxy {
+ to {
+ opacity: 0;
+ }
+}
diff --git a/devtools/client/inspector/rules/test/doc_keyframeanimation.html b/devtools/client/inspector/rules/test/doc_keyframeanimation.html
new file mode 100644
index 000000000..4e02c32f0
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_keyframeanimation.html
@@ -0,0 +1,13 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+ <head>
+ <title>test case for keyframes rule in rule-view</title>
+ <link rel="stylesheet" type="text/css" href="doc_keyframeanimation.css"/>
+ </head>
+ <body>
+ <div id="pacman"></div>
+ <div id="boxy" class="circle"></div>
+ <div id="moxy" class="circle"></div>
+ </body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_media_queries.html b/devtools/client/inspector/rules/test/doc_media_queries.html
new file mode 100644
index 000000000..1adb8bc7a
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_media_queries.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+ <title>test</title>
+ <script type="application/javascript;version=1.7">
+
+ </script>
+ <style>
+ div {
+ width: 1000px;
+ height: 100px;
+ background-color: #f00;
+ }
+
+ @media screen and (min-width: 1px) {
+ div {
+ width: 200px;
+ }
+ }
+ </style>
+</head>
+<body>
+<div></div>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_pseudoelement.html b/devtools/client/inspector/rules/test/doc_pseudoelement.html
new file mode 100644
index 000000000..6145d4bf1
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_pseudoelement.html
@@ -0,0 +1,131 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+ <head>
+ <style>
+
+body {
+ color: #333;
+}
+
+.box {
+ float:left;
+ width: 128px;
+ height: 128px;
+ background: #ddd;
+ padding: 32px;
+ margin: 32px;
+ position:relative;
+}
+
+.box:first-line {
+ color: orange;
+ background: red;
+}
+
+.box:first-letter {
+ color: green;
+}
+
+* {
+ cursor: default;
+}
+
+nothing {
+ cursor: pointer;
+}
+
+p::-moz-selection {
+ color: white;
+ background: black;
+}
+p::selection {
+ color: white;
+ background: black;
+}
+
+p:first-line {
+ background: blue;
+}
+p:first-letter {
+ color: red;
+ font-size: 130%;
+}
+
+.box:before {
+ background: green;
+ content: " ";
+ position: absolute;
+ height:32px;
+ width:32px;
+}
+
+.box:after {
+ background: red;
+ content: " ";
+ position: absolute;
+ border-radius: 50%;
+ height:32px;
+ width:32px;
+ top: 50%;
+ left: 50%;
+ margin-top: -16px;
+ margin-left: -16px;
+}
+
+.topleft:before {
+ top:0;
+ left:0;
+}
+
+.topleft:first-line {
+ color: orange;
+}
+.topleft::selection {
+ color: orange;
+}
+
+.topright:before {
+ top:0;
+ right:0;
+}
+
+.bottomright:before {
+ bottom:10px;
+ right:10px;
+ color: red;
+}
+
+.bottomright:before {
+ bottom:0;
+ right:0;
+}
+
+.bottomleft:before {
+ bottom:0;
+ left:0;
+}
+
+ </style>
+ </head>
+ <body>
+ <h1>ruleview pseudoelement($("test"));</h1>
+
+ <div id="topleft" class="box topleft">
+ <p>Top Left<br />Position</p>
+ </div>
+
+ <div id="topright" class="box topright">
+ <p>Top Right<br />Position</p>
+ </div>
+
+ <div id="bottomright" class="box bottomright">
+ <p>Bottom Right<br />Position</p>
+ </div>
+
+ <div id="bottomleft" class="box bottomleft">
+ <p>Bottom Left<br />Position</p>
+ </div>
+
+ </body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_ruleLineNumbers.html b/devtools/client/inspector/rules/test/doc_ruleLineNumbers.html
new file mode 100644
index 000000000..5a157f384
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_ruleLineNumbers.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>simple testcase</title>
+ <style type="text/css">
+ #testid {
+ background-color: seagreen;
+ }
+
+ body {
+ color: chartreuse;
+ }
+ </style>
+</head>
+<body>
+ <div id="testid">simple testcase</div>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_sourcemaps.css b/devtools/client/inspector/rules/test/doc_sourcemaps.css
new file mode 100644
index 000000000..a9b437a40
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_sourcemaps.css
@@ -0,0 +1,7 @@
+div {
+ color: #ff0066; }
+
+span {
+ background-color: #EEE; }
+
+/*# sourceMappingURL=doc_sourcemaps.css.map */ \ No newline at end of file
diff --git a/devtools/client/inspector/rules/test/doc_sourcemaps.css.map b/devtools/client/inspector/rules/test/doc_sourcemaps.css.map
new file mode 100644
index 000000000..0f7486fd9
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_sourcemaps.css.map
@@ -0,0 +1,7 @@
+{
+"version": 3,
+"mappings": "AAGA,GAAI;EACF,KAAK,EAHU,OAAI;;AAMrB,IAAK;EACH,gBAAgB,EAAE,IAAI",
+"sources": ["doc_sourcemaps.scss"],
+"names": [],
+"file": "doc_sourcemaps.css"
+}
diff --git a/devtools/client/inspector/rules/test/doc_sourcemaps.html b/devtools/client/inspector/rules/test/doc_sourcemaps.html
new file mode 100644
index 000000000..0014e55fe
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_sourcemaps.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+<head>
+ <title>testcase for testing CSS source maps</title>
+ <link rel="stylesheet" type="text/css" href="simple.css"/>
+ <link rel="stylesheet" type="text/css" href="doc_sourcemaps.css"/>
+</head>
+<body>
+ <div>source maps <span>testcase</span></div>
+</body>
+</html>
diff --git a/devtools/client/inspector/rules/test/doc_sourcemaps.scss b/devtools/client/inspector/rules/test/doc_sourcemaps.scss
new file mode 100644
index 000000000..0ff6c471b
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_sourcemaps.scss
@@ -0,0 +1,10 @@
+
+$paulrougetpink: #f06;
+
+div {
+ color: $paulrougetpink;
+}
+
+span {
+ background-color: #EEE;
+} \ No newline at end of file
diff --git a/devtools/client/inspector/rules/test/doc_style_editor_link.css b/devtools/client/inspector/rules/test/doc_style_editor_link.css
new file mode 100644
index 000000000..e49e1f587
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_style_editor_link.css
@@ -0,0 +1,3 @@
+div {
+ opacity: 1;
+} \ No newline at end of file
diff --git a/devtools/client/inspector/rules/test/doc_test_image.png b/devtools/client/inspector/rules/test/doc_test_image.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_test_image.png
Binary files differ
diff --git a/devtools/client/inspector/rules/test/doc_urls_clickable.css b/devtools/client/inspector/rules/test/doc_urls_clickable.css
new file mode 100644
index 000000000..04315b2c3
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_urls_clickable.css
@@ -0,0 +1,9 @@
+.relative1 {
+ background-image: url(./doc_test_image.png);
+}
+.absolute {
+ background: url("http://example.com/browser/devtools/client/inspector/rules/test/doc_test_image.png");
+}
+.base64 {
+ background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==');
+}
diff --git a/devtools/client/inspector/rules/test/doc_urls_clickable.html b/devtools/client/inspector/rules/test/doc_urls_clickable.html
new file mode 100644
index 000000000..b0265a703
--- /dev/null
+++ b/devtools/client/inspector/rules/test/doc_urls_clickable.html
@@ -0,0 +1,30 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+ <head>
+
+ <link href="./doc_urls_clickable.css" rel="stylesheet" type="text/css">
+
+ <style>
+ .relative2 {
+ background-image: url(doc_test_image.png);
+ }
+ </style>
+ </head>
+ <body>
+
+ <div class="relative1">Background image #1 with relative path (loaded from external css)</div>
+
+ <div class="relative2">Background image #2 with relative path (loaded from style tag)</div>
+
+ <div class="absolute">Background image with absolute path (loaded from external css)</div>
+
+ <div class="base64">Background image with base64 url (loaded from external css)</div>
+
+ <div class="inline" style="background: url(doc_test_image.png);">Background image with relative path (loaded from style attribute)</div>
+
+ <div class="inline-resolved" style="background-image: url(./doc_test_image.png)">Background image with resolved relative path (loaded from style attribute)</div>
+
+ <div class="noimage">No background image :(</div>
+ </body>
+</html>
diff --git a/devtools/client/inspector/rules/test/head.js b/devtools/client/inspector/rules/test/head.js
new file mode 100644
index 000000000..5e5ede09b
--- /dev/null
+++ b/devtools/client/inspector/rules/test/head.js
@@ -0,0 +1,840 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../test/head.js */
+"use strict";
+
+// Import the inspector's head.js first (which itself imports shared-head.js).
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
+ this);
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.defaultColorUnit");
+});
+
+var {getInplaceEditorForSpan: inplaceEditor} =
+ require("devtools/client/shared/inplace-editor");
+
+const ROOT_TEST_DIR = getRootDirectory(gTestPath);
+const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
+
+const STYLE_INSPECTOR_L10N
+ = new LocalizationHelper("devtools/shared/locales/styleinspector.properties");
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.defaultColorUnit");
+});
+
+/**
+ * The rule-view tests rely on a frame-script to be injected in the content test
+ * page. So override the shared-head's addTab to load the frame script after the
+ * tab was added.
+ * FIXME: Refactor the rule-view tests to use the testActor instead of a frame
+ * script, so they can run on remote targets too.
+ */
+var _addTab = addTab;
+addTab = function (url) {
+ return _addTab(url).then(tab => {
+ info("Loading the helper frame script " + FRAME_SCRIPT_URL);
+ let browser = tab.linkedBrowser;
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
+ return tab;
+ });
+};
+
+/**
+ * Wait for a content -> chrome message on the message manager (the window
+ * messagemanager is used).
+ *
+ * @param {String} name
+ * The message name
+ * @return {Promise} A promise that resolves to the response data when the
+ * message has been received
+ */
+function waitForContentMessage(name) {
+ info("Expecting message " + name + " from content");
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ let def = defer();
+ mm.addMessageListener(name, function onMessage(msg) {
+ mm.removeMessageListener(name, onMessage);
+ def.resolve(msg.data);
+ });
+ return def.promise;
+}
+
+/**
+ * Send an async message to the frame script (chrome -> content) and wait for a
+ * response message with the same name (content -> chrome).
+ *
+ * @param {String} name
+ * The message name. Should be one of the messages defined
+ * in doc_frame_script.js
+ * @param {Object} data
+ * Optional data to send along
+ * @param {Object} objects
+ * Optional CPOW objects to send along
+ * @param {Boolean} expectResponse
+ * If set to false, don't wait for a response with the same name
+ * from the content script. Defaults to true.
+ * @return {Promise} Resolves to the response data if a response is expected,
+ * immediately resolves otherwise
+ */
+function executeInContent(name, data = {}, objects = {},
+ expectResponse = true) {
+ info("Sending message " + name + " to content");
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ mm.sendAsyncMessage(name, data, objects);
+ if (expectResponse) {
+ return waitForContentMessage(name);
+ }
+
+ return promise.resolve();
+}
+
+/**
+ * Send an async message to the frame script and get back the requested
+ * computed style property.
+ *
+ * @param {String} selector
+ * The selector used to obtain the element.
+ * @param {String} pseudo
+ * pseudo id to query, or null.
+ * @param {String} name
+ * name of the property.
+ */
+function* getComputedStyleProperty(selector, pseudo, propName) {
+ return yield executeInContent("Test:GetComputedStylePropertyValue",
+ {selector,
+ pseudo,
+ name: propName});
+}
+
+/**
+ * Get an element's inline style property value.
+ * @param {TestActor} testActor
+ * @param {String} selector
+ * The selector used to obtain the element.
+ * @param {String} name
+ * name of the property.
+ */
+function getStyle(testActor, selector, propName) {
+ return testActor.eval(`
+ content.document.querySelector("${selector}")
+ .style.getPropertyValue("${propName}");
+ `);
+}
+
+/**
+ * Send an async message to the frame script and wait until the requested
+ * computed style property has the expected value.
+ *
+ * @param {String} selector
+ * The selector used to obtain the element.
+ * @param {String} pseudo
+ * pseudo id to query, or null.
+ * @param {String} prop
+ * name of the property.
+ * @param {String} expected
+ * expected value of property
+ * @param {String} name
+ * the name used in test message
+ */
+function* waitForComputedStyleProperty(selector, pseudo, name, expected) {
+ return yield executeInContent("Test:WaitForComputedStylePropertyValue",
+ {selector,
+ pseudo,
+ expected,
+ name});
+}
+
+/**
+ * Given an inplace editable element, click to switch it to edit mode, wait for
+ * focus
+ *
+ * @return a promise that resolves to the inplace-editor element when ready
+ */
+var focusEditableField = Task.async(function* (ruleView, editable, xOffset = 1,
+ yOffset = 1, options = {}) {
+ let onFocus = once(editable.parentNode, "focus", true);
+ info("Clicking on editable field to turn to edit mode");
+ EventUtils.synthesizeMouse(editable, xOffset, yOffset, options,
+ editable.ownerDocument.defaultView);
+ yield onFocus;
+
+ info("Editable field gained focus, returning the input field now");
+ let onEdit = inplaceEditor(editable.ownerDocument.activeElement);
+
+ return onEdit;
+});
+
+/**
+ * When a tooltip is closed, this ends up "commiting" the value changed within
+ * the tooltip (e.g. the color in case of a colorpicker) which, in turn, ends up
+ * setting the value of the corresponding css property in the rule-view.
+ * Use this function to close the tooltip and make sure the test waits for the
+ * ruleview-changed event.
+ * @param {SwatchBasedEditorTooltip} editorTooltip
+ * @param {CSSRuleView} view
+ */
+function* hideTooltipAndWaitForRuleViewChanged(editorTooltip, view) {
+ let onModified = view.once("ruleview-changed");
+ let onHidden = editorTooltip.tooltip.once("hidden");
+ editorTooltip.hide();
+ yield onModified;
+ yield onHidden;
+}
+
+/**
+ * Polls a given generator function waiting for it to return true.
+ *
+ * @param {Function} validatorFn
+ * A validator generator function that returns a boolean.
+ * This is called every few milliseconds to check if the result is true.
+ * When it is true, the promise resolves.
+ * @param {String} name
+ * Optional name of the test. This is used to generate
+ * the success and failure messages.
+ * @return a promise that resolves when the function returned true or rejects
+ * if the timeout is reached
+ */
+var waitForSuccess = Task.async(function* (validatorFn, desc = "untitled") {
+ let i = 0;
+ while (true) {
+ info("Checking: " + desc);
+ if (yield validatorFn()) {
+ ok(true, "Success: " + desc);
+ break;
+ }
+ i++;
+ if (i > 10) {
+ ok(false, "Failure: " + desc);
+ break;
+ }
+ yield new Promise(r => setTimeout(r, 200));
+ }
+});
+
+/**
+ * Get the DOMNode for a css rule in the rule-view that corresponds to the given
+ * selector
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view for which the rule
+ * object is wanted
+ * @return {DOMNode}
+ */
+function getRuleViewRule(view, selectorText) {
+ let rule;
+ for (let r of view.styleDocument.querySelectorAll(".ruleview-rule")) {
+ let selector = r.querySelector(".ruleview-selectorcontainer, " +
+ ".ruleview-selector-matched");
+ if (selector && selector.textContent === selectorText) {
+ rule = r;
+ break;
+ }
+ }
+
+ return rule;
+}
+
+/**
+ * Get references to the name and value span nodes corresponding to a given
+ * selector and property name in the rule-view
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view to look for the property in
+ * @param {String} propertyName
+ * The name of the property
+ * @return {Object} An object like {nameSpan: DOMNode, valueSpan: DOMNode}
+ */
+function getRuleViewProperty(view, selectorText, propertyName) {
+ let prop;
+
+ let rule = getRuleViewRule(view, selectorText);
+ if (rule) {
+ // Look for the propertyName in that rule element
+ for (let p of rule.querySelectorAll(".ruleview-property")) {
+ let nameSpan = p.querySelector(".ruleview-propertyname");
+ let valueSpan = p.querySelector(".ruleview-propertyvalue");
+
+ if (nameSpan.textContent === propertyName) {
+ prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+ break;
+ }
+ }
+ }
+ return prop;
+}
+
+/**
+ * Get the text value of the property corresponding to a given selector and name
+ * in the rule-view
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view to look for the property in
+ * @param {String} propertyName
+ * The name of the property
+ * @return {String} The property value
+ */
+function getRuleViewPropertyValue(view, selectorText, propertyName) {
+ return getRuleViewProperty(view, selectorText, propertyName)
+ .valueSpan.textContent;
+}
+
+/**
+ * Get a reference to the selector DOM element corresponding to a given selector
+ * in the rule-view
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view to look for
+ * @return {DOMNode} The selector DOM element
+ */
+function getRuleViewSelector(view, selectorText) {
+ let rule = getRuleViewRule(view, selectorText);
+ return rule.querySelector(".ruleview-selector, .ruleview-selector-matched");
+}
+
+/**
+ * Get a reference to the selectorhighlighter icon DOM element corresponding to
+ * a given selector in the rule-view
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view to look for
+ * @return {DOMNode} The selectorhighlighter icon DOM element
+ */
+function getRuleViewSelectorHighlighterIcon(view, selectorText) {
+ let rule = getRuleViewRule(view, selectorText);
+ return rule.querySelector(".ruleview-selectorhighlighter");
+}
+
+/**
+ * Simulate a color change in a given color picker tooltip, and optionally wait
+ * for a given element in the page to have its style changed as a result.
+ * Note that this function assumes that the colorpicker popup is already open
+ * and it won't close it after having selected the new color.
+ *
+ * @param {RuleView} ruleView
+ * The related rule view instance
+ * @param {SwatchColorPickerTooltip} colorPicker
+ * @param {Array} newRgba
+ * The new color to be set [r, g, b, a]
+ * @param {Object} expectedChange
+ * Optional object that needs the following props:
+ * - {String} selector The selector to the element in the page that
+ * will have its style changed.
+ * - {String} name The style name that will be changed
+ * - {String} value The expected style value
+ * The style will be checked like so: getComputedStyle(element)[name] === value
+ */
+var simulateColorPickerChange = Task.async(function* (ruleView, colorPicker,
+ newRgba, expectedChange) {
+ let onComputedStyleChanged;
+ if (expectedChange) {
+ let {selector, name, value} = expectedChange;
+ onComputedStyleChanged = waitForComputedStyleProperty(selector, null, name, value);
+ }
+ let onRuleViewChanged = ruleView.once("ruleview-changed");
+ info("Getting the spectrum colorpicker object");
+ let spectrum = colorPicker.spectrum;
+ info("Setting the new color");
+ spectrum.rgb = newRgba;
+ info("Applying the change");
+ spectrum.updateUI();
+ spectrum.onChange();
+ info("Waiting for rule-view to update");
+ yield onRuleViewChanged;
+
+ if (expectedChange) {
+ info("Waiting for the style to be applied on the page");
+ yield onComputedStyleChanged;
+ }
+});
+
+/**
+ * Open the color picker popup for a given property in a given rule and
+ * simulate a color change. Optionally wait for a given element in the page to
+ * have its style changed as a result.
+ *
+ * @param {RuleView} view
+ * The related rule view instance
+ * @param {Number} ruleIndex
+ * Which rule to target in the rule view
+ * @param {Number} propIndex
+ * Which property to target in the rule
+ * @param {Array} newRgba
+ * The new color to be set [r, g, b, a]
+ * @param {Object} expectedChange
+ * Optional object that needs the following props:
+ * - {String} selector The selector to the element in the page that
+ * will have its style changed.
+ * - {String} name The style name that will be changed
+ * - {String} value The expected style value
+ * The style will be checked like so: getComputedStyle(element)[name] === value
+ */
+var openColorPickerAndSelectColor = Task.async(function* (view, ruleIndex,
+ propIndex, newRgba, expectedChange) {
+ let ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+ let propEditor = ruleEditor.rule.textProps[propIndex].editor;
+ let swatch = propEditor.valueSpan.querySelector(".ruleview-colorswatch");
+ let cPicker = view.tooltips.colorPicker;
+
+ info("Opening the colorpicker by clicking the color swatch");
+ let onColorPickerReady = cPicker.once("ready");
+ swatch.click();
+ yield onColorPickerReady;
+
+ yield simulateColorPickerChange(view, cPicker, newRgba, expectedChange);
+
+ return {propEditor, swatch, cPicker};
+});
+
+/**
+ * Open the cubicbezier popup for a given property in a given rule and
+ * simulate a curve change. Optionally wait for a given element in the page to
+ * have its style changed as a result.
+ *
+ * @param {RuleView} view
+ * The related rule view instance
+ * @param {Number} ruleIndex
+ * Which rule to target in the rule view
+ * @param {Number} propIndex
+ * Which property to target in the rule
+ * @param {Array} coords
+ * The new coordinates to be used, e.g. [0.1, 2, 0.9, -1]
+ * @param {Object} expectedChange
+ * Optional object that needs the following props:
+ * - {String} selector The selector to the element in the page that
+ * will have its style changed.
+ * - {String} name The style name that will be changed
+ * - {String} value The expected style value
+ * The style will be checked like so: getComputedStyle(element)[name] === value
+ */
+var openCubicBezierAndChangeCoords = Task.async(function* (view, ruleIndex,
+ propIndex, coords, expectedChange) {
+ let ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+ let propEditor = ruleEditor.rule.textProps[propIndex].editor;
+ let swatch = propEditor.valueSpan.querySelector(".ruleview-bezierswatch");
+ let bezierTooltip = view.tooltips.cubicBezier;
+
+ info("Opening the cubicBezier by clicking the swatch");
+ let onBezierWidgetReady = bezierTooltip.once("ready");
+ swatch.click();
+ yield onBezierWidgetReady;
+
+ let widget = yield bezierTooltip.widget;
+
+ info("Simulating a change of curve in the widget");
+ let onRuleViewChanged = view.once("ruleview-changed");
+ widget.coordinates = coords;
+ yield onRuleViewChanged;
+
+ if (expectedChange) {
+ info("Waiting for the style to be applied on the page");
+ let {selector, name, value} = expectedChange;
+ yield waitForComputedStyleProperty(selector, null, name, value);
+ }
+
+ return {propEditor, swatch, bezierTooltip};
+});
+
+/**
+ * Get a rule-link from the rule-view given its index
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {Number} index
+ * The index of the link to get
+ * @return {DOMNode} The link if any at this index
+ */
+function getRuleViewLinkByIndex(view, index) {
+ let links = view.styleDocument.querySelectorAll(".ruleview-rule-source");
+ return links[index];
+}
+
+/**
+ * Get rule-link text from the rule-view given its index
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {Number} index
+ * The index of the link to get
+ * @return {String} The string at this index
+ */
+function getRuleViewLinkTextByIndex(view, index) {
+ let link = getRuleViewLinkByIndex(view, index);
+ return link.querySelector(".ruleview-rule-source-label").textContent;
+}
+
+/**
+ * Simulate adding a new property in an existing rule in the rule-view.
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {Number} ruleIndex
+ * The index of the rule to use. Note that if ruleIndex is 0, you might
+ * want to also listen to markupmutation events in your test since
+ * that's going to change the style attribute of the selected node.
+ * @param {String} name
+ * The name for the new property
+ * @param {String} value
+ * The value for the new property
+ * @param {String} commitValueWith
+ * Which key should be used to commit the new value. VK_RETURN is used by
+ * default, but tests might want to use another key to test cancelling
+ * for exemple.
+ * @param {Boolean} blurNewProperty
+ * After the new value has been added, a new property would have been
+ * focused. This parameter is true by default, and that causes the new
+ * property to be blurred. Set to false if you don't want this.
+ * @return {TextProperty} The instance of the TextProperty that was added
+ */
+var addProperty = Task.async(function* (view, ruleIndex, name, value,
+ commitValueWith = "VK_RETURN",
+ blurNewProperty = true) {
+ info("Adding new property " + name + ":" + value + " to rule " + ruleIndex);
+
+ let ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+ let numOfProps = ruleEditor.rule.textProps.length;
+
+ info("Adding name " + name);
+ editor.input.value = name;
+ let onNameAdded = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onNameAdded;
+
+ // Focus has moved to the value inplace-editor automatically.
+ editor = inplaceEditor(view.styleDocument.activeElement);
+ let textProps = ruleEditor.rule.textProps;
+ let textProp = textProps[textProps.length - 1];
+
+ is(ruleEditor.rule.textProps.length, numOfProps + 1,
+ "A new test property was added");
+ is(editor, inplaceEditor(textProp.editor.valueSpan),
+ "The inplace editor appeared for the value");
+
+ info("Adding value " + value);
+ // Setting the input value schedules a preview to be shown in 10ms which
+ // triggers a ruleview-changed event (see bug 1209295).
+ let onPreview = view.once("ruleview-changed");
+ editor.input.value = value;
+ view.throttle.flush();
+ yield onPreview;
+
+ let onValueAdded = view.once("ruleview-changed");
+ EventUtils.synthesizeKey(commitValueWith, {}, view.styleWindow);
+ yield onValueAdded;
+
+ if (blurNewProperty) {
+ view.styleDocument.activeElement.blur();
+ }
+
+ return textProp;
+});
+
+/**
+ * Simulate changing the value of a property in a rule in the rule-view.
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {TextProperty} textProp
+ * The instance of the TextProperty to be changed
+ * @param {String} value
+ * The new value to be used. If null is passed, then the value will be
+ * deleted
+ * @param {Boolean} blurNewProperty
+ * After the value has been changed, a new property would have been
+ * focused. This parameter is true by default, and that causes the new
+ * property to be blurred. Set to false if you don't want this.
+ */
+var setProperty = Task.async(function* (view, textProp, value,
+ blurNewProperty = true) {
+ yield focusEditableField(view, textProp.editor.valueSpan);
+
+ let onPreview = view.once("ruleview-changed");
+ if (value === null) {
+ EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow);
+ } else {
+ EventUtils.sendString(value, view.styleWindow);
+ }
+ view.throttle.flush();
+ yield onPreview;
+
+ let onValueDone = view.once("ruleview-changed");
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onValueDone;
+
+ if (blurNewProperty) {
+ view.styleDocument.activeElement.blur();
+ }
+});
+
+/**
+ * Simulate removing a property from an existing rule in the rule-view.
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {TextProperty} textProp
+ * The instance of the TextProperty to be removed
+ * @param {Boolean} blurNewProperty
+ * After the property has been removed, a new property would have been
+ * focused. This parameter is true by default, and that causes the new
+ * property to be blurred. Set to false if you don't want this.
+ */
+var removeProperty = Task.async(function* (view, textProp,
+ blurNewProperty = true) {
+ yield focusEditableField(view, textProp.editor.nameSpan);
+
+ let onModifications = view.once("ruleview-changed");
+ info("Deleting the property name now");
+ EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow);
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+ yield onModifications;
+
+ if (blurNewProperty) {
+ view.styleDocument.activeElement.blur();
+ }
+});
+
+/**
+ * Simulate clicking the enable/disable checkbox next to a property in a rule.
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {TextProperty} textProp
+ * The instance of the TextProperty to be enabled/disabled
+ */
+var togglePropStatus = Task.async(function* (view, textProp) {
+ let onRuleViewRefreshed = view.once("ruleview-changed");
+ textProp.editor.enable.click();
+ yield onRuleViewRefreshed;
+});
+
+/**
+ * Click on a rule-view's close brace to focus a new property name editor
+ *
+ * @param {RuleEditor} ruleEditor
+ * An instance of RuleEditor that will receive the new property
+ * @return a promise that resolves to the newly created editor when ready and
+ * focused
+ */
+var focusNewRuleViewProperty = Task.async(function* (ruleEditor) {
+ info("Clicking on a close ruleEditor brace to start editing a new property");
+
+ // Use bottom alignment to avoid scrolling out of the parent element area.
+ ruleEditor.closeBrace.scrollIntoView(false);
+ let editor = yield focusEditableField(ruleEditor.ruleView,
+ ruleEditor.closeBrace);
+
+ is(inplaceEditor(ruleEditor.newPropSpan), editor,
+ "Focused editor is the new property editor.");
+
+ return editor;
+});
+
+/**
+ * Create a new property name in the rule-view, focusing a new property editor
+ * by clicking on the close brace, and then entering the given text.
+ * Keep in mind that the rule-view knows how to handle strings with multiple
+ * properties, so the input text may be like: "p1:v1;p2:v2;p3:v3".
+ *
+ * @param {RuleEditor} ruleEditor
+ * The instance of RuleEditor that will receive the new property(ies)
+ * @param {String} inputValue
+ * The text to be entered in the new property name field
+ * @return a promise that resolves when the new property name has been entered
+ * and once the value field is focused
+ */
+var createNewRuleViewProperty = Task.async(function* (ruleEditor, inputValue) {
+ info("Creating a new property editor");
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+
+ info("Entering the value " + inputValue);
+ editor.input.value = inputValue;
+
+ info("Submitting the new value and waiting for value field focus");
+ let onFocus = once(ruleEditor.element, "focus", true);
+ EventUtils.synthesizeKey("VK_RETURN", {},
+ ruleEditor.element.ownerDocument.defaultView);
+ yield onFocus;
+});
+
+/**
+ * Set the search value for the rule-view filter styles search box.
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} searchValue
+ * The filter search value
+ * @return a promise that resolves when the rule-view is filtered for the
+ * search term
+ */
+var setSearchFilter = Task.async(function* (view, searchValue) {
+ info("Setting filter text to \"" + searchValue + "\"");
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+ searchField.focus();
+ synthesizeKeys(searchValue, win);
+ yield view.inspector.once("ruleview-filtered");
+});
+
+/**
+ * Reload the current page and wait for the inspector to be initialized after
+ * the navigation
+ *
+ * @param {InspectorPanel} inspector
+ * The instance of InspectorPanel currently loaded in the toolbox
+ * @param {TestActor} testActor
+ * The current instance of the TestActor
+ */
+function* reloadPage(inspector, testActor) {
+ let onNewRoot = inspector.once("new-root");
+ yield testActor.reload();
+ yield onNewRoot;
+ yield inspector.markup._waitForChildren();
+}
+
+/**
+ * Create a new rule by clicking on the "add rule" button.
+ * This will leave the selector inplace-editor active.
+ *
+ * @param {InspectorPanel} inspector
+ * The instance of InspectorPanel currently loaded in the toolbox
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @return a promise that resolves after the rule has been added
+ */
+function* addNewRule(inspector, view) {
+ info("Adding the new rule using the button");
+ view.addRuleButton.click();
+
+ info("Waiting for rule view to change");
+ yield view.once("ruleview-changed");
+}
+
+/**
+ * Create a new rule by clicking on the "add rule" button, dismiss the editor field and
+ * verify that the selector is correct.
+ *
+ * @param {InspectorPanel} inspector
+ * The instance of InspectorPanel currently loaded in the toolbox
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} expectedSelector
+ * The value we expect the selector to have
+ * @param {Number} expectedIndex
+ * The index we expect the rule to have in the rule-view
+ * @return a promise that resolves after the rule has been added
+ */
+function* addNewRuleAndDismissEditor(inspector, view, expectedSelector, expectedIndex) {
+ yield addNewRule(inspector, view);
+
+ info("Getting the new rule at index " + expectedIndex);
+ let ruleEditor = getRuleViewRuleEditor(view, expectedIndex);
+ let editor = ruleEditor.selectorText.ownerDocument.activeElement;
+ is(editor.value, expectedSelector,
+ "The editor for the new selector has the correct value: " + expectedSelector);
+
+ info("Pressing escape to leave the editor");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+
+ is(ruleEditor.selectorText.textContent, expectedSelector,
+ "The new selector has the correct text: " + expectedSelector);
+}
+
+/**
+ * Simulate a sequence of non-character keys (return, escape, tab) and wait for
+ * a given element to receive the focus.
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {DOMNode} element
+ * The element that should be focused
+ * @param {Array} keys
+ * Array of non-character keys, the part that comes after "DOM_VK_" eg.
+ * "RETURN", "ESCAPE"
+ * @return a promise that resolves after the element received the focus
+ */
+function* sendKeysAndWaitForFocus(view, element, keys) {
+ let onFocus = once(element, "focus", true);
+ for (let key of keys) {
+ EventUtils.sendKey(key, view.styleWindow);
+ }
+ yield onFocus;
+}
+
+/**
+ * Open the style editor context menu and return all of it's items in a flat array
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @return An array of MenuItems
+ */
+function openStyleContextMenuAndGetAllItems(view, target) {
+ let menu = view._contextmenu._openMenu({target: target});
+
+ // Flatten all menu items into a single array to make searching through it easier
+ let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
+ if (item.submenu) {
+ return addItem(item.submenu.items);
+ }
+ return item;
+ }));
+
+ return allItems;
+}
+
+/**
+ * Wait for a markupmutation event on the inspector that is for a style modification.
+ * @param {InspectorPanel} inspector
+ * @return {Promise}
+ */
+function waitForStyleModification(inspector) {
+ return new Promise(function (resolve) {
+ function checkForStyleModification(name, mutations) {
+ for (let mutation of mutations) {
+ if (mutation.type === "attributes" && mutation.attributeName === "style") {
+ inspector.off("markupmutation", checkForStyleModification);
+ resolve();
+ return;
+ }
+ }
+ }
+ inspector.on("markupmutation", checkForStyleModification);
+ });
+}
+
+/**
+ * Click on the selector icon
+ * @param {DOMNode} icon
+ * @param {CSSRuleView} view
+ */
+function* clickSelectorIcon(icon, view) {
+ let onToggled = view.once("ruleview-selectorhighlighter-toggled");
+ EventUtils.synthesizeMouseAtCenter(icon, {}, view.styleWindow);
+ yield onToggled;
+}
+
+/**
+ * Make sure window is properly focused before sending a key event.
+ * @param {Window} win
+ * @param {Event} key
+ */
+function focusAndSendKey(win, key) {
+ win.document.documentElement.focus();
+ EventUtils.sendKey(key, win);
+}