From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- devtools/client/styleeditor/test/autocomplete.html | 23 + devtools/client/styleeditor/test/browser.toml | 227 + .../test/browser_styleeditor_add_stylesheet.js | 36 + .../test/browser_styleeditor_at_rules_sidebar.js | 340 ++ .../browser_styleeditor_autocomplete-disabled.js | 42 + .../test/browser_styleeditor_autocomplete.js | 284 ++ .../styleeditor/test/browser_styleeditor_bom.js | 37 + ...itor_bug_1247083_inline_stylesheet_numbering.js | 104 + ...tyleeditor_bug_1405342_serviceworker_iframes.js | 27 + .../test/browser_styleeditor_bug_740541_iframes.js | 107 + .../browser_styleeditor_bug_851132_middle_click.js | 63 + .../test/browser_styleeditor_bug_870339.js | 49 + .../test/browser_styleeditor_copyurl.js | 43 + .../test/browser_styleeditor_enabled.js | 135 + .../browser_styleeditor_fetch-from-netmonitor.js | 80 + .../test/browser_styleeditor_filesave.js | 93 + .../styleeditor/test/browser_styleeditor_filter.js | 343 ++ .../browser_styleeditor_fission_switch_target.js | 33 + .../test/browser_styleeditor_highlight-selector.js | 223 + .../styleeditor/test/browser_styleeditor_import.js | 56 + .../test/browser_styleeditor_import_rule.js | 32 + .../styleeditor/test/browser_styleeditor_init.js | 52 + .../browser_styleeditor_inline_friendly_names.js | 103 + .../test/browser_styleeditor_loading.js | 36 + .../browser_styleeditor_loading_with_containers.js | 76 + .../browser_styleeditor_media_sidebar_links.js | 165 + ...browser_styleeditor_media_sidebar_sourcemaps.js | 62 + .../test/browser_styleeditor_missing_stylesheet.js | 37 + .../test/browser_styleeditor_navigate.js | 31 + .../styleeditor/test/browser_styleeditor_new.js | 106 + .../test/browser_styleeditor_nostyle.js | 56 + .../test/browser_styleeditor_opentab.js | 133 + .../styleeditor/test/browser_styleeditor_pretty.js | 112 + .../browser_styleeditor_private_perwindowpb.js | 76 + .../styleeditor/test/browser_styleeditor_reload.js | 42 + .../test/browser_styleeditor_remove_stylesheet.js | 29 + .../test/browser_styleeditor_resize_performance.js | 63 + .../styleeditor/test/browser_styleeditor_scroll.js | 96 + .../test/browser_styleeditor_selectstylesheet.js | 25 + .../test/browser_styleeditor_sidebars.js | 67 + .../test/browser_styleeditor_sourcemap_chrome.js | 47 + .../test/browser_styleeditor_sourcemap_large.js | 34 + .../test/browser_styleeditor_sourcemap_watching.js | 157 + .../test/browser_styleeditor_sourcemaps.js | 152 + .../test/browser_styleeditor_sourcemaps_inline.js | 89 + .../test/browser_styleeditor_sv_keynav.js | 85 + .../test/browser_styleeditor_sv_resize.js | 53 + .../styleeditor/test/browser_styleeditor_sync.js | 73 + .../test/browser_styleeditor_syncAddProperty.js | 52 + .../test/browser_styleeditor_syncAddRule.js | 30 + .../test/browser_styleeditor_syncAlreadyOpen.js | 50 + .../test/browser_styleeditor_syncEditSelector.js | 38 + .../test/browser_styleeditor_syncIntoRuleView.js | 41 + .../test/browser_styleeditor_transition_rule.js | 51 + .../styleeditor/test/browser_styleeditor_xul.js | 23 + .../test/browser_toolbox_styleeditor.js | 104 + .../test/bug_1405342_serviceworker_iframes.html | 10 + devtools/client/styleeditor/test/doc_empty.html | 3 + .../test/doc_fetch_from_netmonitor.html | 13 + devtools/client/styleeditor/test/doc_long.css | 402 ++ .../client/styleeditor/test/doc_long_string.css | 43 + .../client/styleeditor/test/doc_short_string.css | 15 + .../styleeditor/test/doc_sourcemap_chrome.html | 11 + devtools/client/styleeditor/test/doc_xulpage.xhtml | 7 + devtools/client/styleeditor/test/four.html | 25 + devtools/client/styleeditor/test/head.js | 201 + .../styleeditor/test/iframe_service_worker.js | 12 + .../test/iframe_with_service_worker.html | 33 + devtools/client/styleeditor/test/import.css | 8 + devtools/client/styleeditor/test/import.html | 11 + devtools/client/styleeditor/test/import2.css | 8 + devtools/client/styleeditor/test/inline-1.html | 19 + devtools/client/styleeditor/test/inline-2.html | 19 + devtools/client/styleeditor/test/longload.html | 29 + devtools/client/styleeditor/test/longname.html | 12 + .../test/many-media-rules-sourcemaps/index.html | 11 + .../sourcemap/sourcemap-css/sourcemaps.css | 201 + .../sourcemap/sourcemap-css/sourcemaps.css.map | 10 + .../sourcemap/sourcemap-sass/_partial.scss | 25 + .../sourcemap/sourcemap-sass/sourcemaps.scss | 27 + .../styleeditor/test/media-rules-sourcemaps.html | 12 + devtools/client/styleeditor/test/media-rules.css | 29 + devtools/client/styleeditor/test/media-rules.html | 48 + devtools/client/styleeditor/test/media-small.css | 4 + devtools/client/styleeditor/test/media.html | 10 + devtools/client/styleeditor/test/minified.html | 16 + devtools/client/styleeditor/test/missing.html | 11 + devtools/client/styleeditor/test/nostyle.html | 5 + devtools/client/styleeditor/test/pretty.css | 2 + .../client/styleeditor/test/resources_inpage.jsi | 12 + .../client/styleeditor/test/resources_inpage1.css | 11 + .../client/styleeditor/test/resources_inpage2.css | 11 + .../styleeditor/test/selector-highlighter.html | 1 + devtools/client/styleeditor/test/simple.css | 7 + devtools/client/styleeditor/test/simple.css.gz | Bin 0 -> 166 bytes .../client/styleeditor/test/simple.css.gz^headers^ | 4 + devtools/client/styleeditor/test/simple.gz.html | 23 + devtools/client/styleeditor/test/simple.html | 24 + .../styleeditor/test/sjs_huge-css-server.sjs | 19 + .../styleeditor/test/sourcemap-css/contained.css | 4 + .../styleeditor/test/sourcemap-css/media-rules.css | 8 + .../test/sourcemap-css/media-rules.css.map | 6 + .../styleeditor/test/sourcemap-css/sourcemaps.css | 7 + .../test/sourcemap-css/sourcemaps.css.map | 6 + .../test/sourcemap-css/sourcemaps.css.map^headers^ | 2 + .../test/sourcemap-css/sourcemaps_chrome.css | 7 + .../test/sourcemap-css/test-bootstrap-scss.css | 4513 ++++++++++++++++++++ .../styleeditor/test/sourcemap-css/test-stylus.css | 7 + .../test/sourcemap-sass/media-rules.scss | 11 + .../test/sourcemap-sass/sourcemaps.scss | 10 + .../test/sourcemap-sass/sourcemaps.scss^headers^ | 2 + .../test/sourcemap-styl/test-stylus.styl | 7 + .../client/styleeditor/test/sourcemaps-inline.html | 17 + .../client/styleeditor/test/sourcemaps-large.html | 11 + .../styleeditor/test/sourcemaps-watching.html | 11 + devtools/client/styleeditor/test/sourcemaps.html | 13 + devtools/client/styleeditor/test/sync.html | 20 + devtools/client/styleeditor/test/sync_with_csp.css | 10 + .../client/styleeditor/test/sync_with_csp.html | 12 + devtools/client/styleeditor/test/test_private.css | 3 + devtools/client/styleeditor/test/test_private.html | 7 + devtools/client/styleeditor/test/utf-16.css | Bin 0 -> 156 bytes ...yveryverylongnamethatcanbreakthestyleeditor.css | 7 + 123 files changed, 10928 insertions(+) create mode 100644 devtools/client/styleeditor/test/autocomplete.html create mode 100644 devtools/client/styleeditor/test/browser.toml create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_add_stylesheet.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_at_rules_sidebar.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_autocomplete-disabled.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_autocomplete.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_bom.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_bug_1247083_inline_stylesheet_numbering.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_bug_1405342_serviceworker_iframes.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_bug_740541_iframes.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_bug_870339.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_copyurl.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_enabled.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_fetch-from-netmonitor.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_filesave.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_filter.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_fission_switch_target.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_import.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_import_rule.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_init.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_inline_friendly_names.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_loading.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_loading_with_containers.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_missing_stylesheet.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_navigate.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_new.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_nostyle.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_opentab.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_pretty.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_private_perwindowpb.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_reload.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_remove_stylesheet.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_resize_performance.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_scroll.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_selectstylesheet.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_sidebars.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_sourcemap_chrome.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_sourcemap_large.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_sourcemap_watching.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_sourcemaps.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_sourcemaps_inline.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_sv_keynav.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_sv_resize.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_sync.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_syncAddProperty.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_syncAddRule.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_syncAlreadyOpen.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_syncEditSelector.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_syncIntoRuleView.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_transition_rule.js create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_xul.js create mode 100644 devtools/client/styleeditor/test/browser_toolbox_styleeditor.js create mode 100644 devtools/client/styleeditor/test/bug_1405342_serviceworker_iframes.html create mode 100644 devtools/client/styleeditor/test/doc_empty.html create mode 100644 devtools/client/styleeditor/test/doc_fetch_from_netmonitor.html create mode 100644 devtools/client/styleeditor/test/doc_long.css create mode 100644 devtools/client/styleeditor/test/doc_long_string.css create mode 100644 devtools/client/styleeditor/test/doc_short_string.css create mode 100644 devtools/client/styleeditor/test/doc_sourcemap_chrome.html create mode 100644 devtools/client/styleeditor/test/doc_xulpage.xhtml create mode 100644 devtools/client/styleeditor/test/four.html create mode 100644 devtools/client/styleeditor/test/head.js create mode 100644 devtools/client/styleeditor/test/iframe_service_worker.js create mode 100644 devtools/client/styleeditor/test/iframe_with_service_worker.html create mode 100644 devtools/client/styleeditor/test/import.css create mode 100644 devtools/client/styleeditor/test/import.html create mode 100644 devtools/client/styleeditor/test/import2.css create mode 100644 devtools/client/styleeditor/test/inline-1.html create mode 100644 devtools/client/styleeditor/test/inline-2.html create mode 100644 devtools/client/styleeditor/test/longload.html create mode 100644 devtools/client/styleeditor/test/longname.html create mode 100644 devtools/client/styleeditor/test/many-media-rules-sourcemaps/index.html create mode 100644 devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css create mode 100644 devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css.map create mode 100644 devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-sass/_partial.scss create mode 100644 devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-sass/sourcemaps.scss create mode 100644 devtools/client/styleeditor/test/media-rules-sourcemaps.html create mode 100644 devtools/client/styleeditor/test/media-rules.css create mode 100644 devtools/client/styleeditor/test/media-rules.html create mode 100644 devtools/client/styleeditor/test/media-small.css create mode 100644 devtools/client/styleeditor/test/media.html create mode 100644 devtools/client/styleeditor/test/minified.html create mode 100644 devtools/client/styleeditor/test/missing.html create mode 100644 devtools/client/styleeditor/test/nostyle.html create mode 100644 devtools/client/styleeditor/test/pretty.css create mode 100644 devtools/client/styleeditor/test/resources_inpage.jsi create mode 100644 devtools/client/styleeditor/test/resources_inpage1.css create mode 100644 devtools/client/styleeditor/test/resources_inpage2.css create mode 100644 devtools/client/styleeditor/test/selector-highlighter.html create mode 100644 devtools/client/styleeditor/test/simple.css create mode 100644 devtools/client/styleeditor/test/simple.css.gz create mode 100644 devtools/client/styleeditor/test/simple.css.gz^headers^ create mode 100644 devtools/client/styleeditor/test/simple.gz.html create mode 100644 devtools/client/styleeditor/test/simple.html create mode 100644 devtools/client/styleeditor/test/sjs_huge-css-server.sjs create mode 100644 devtools/client/styleeditor/test/sourcemap-css/contained.css create mode 100644 devtools/client/styleeditor/test/sourcemap-css/media-rules.css create mode 100644 devtools/client/styleeditor/test/sourcemap-css/media-rules.css.map create mode 100644 devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css create mode 100644 devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css.map create mode 100644 devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css.map^headers^ create mode 100644 devtools/client/styleeditor/test/sourcemap-css/sourcemaps_chrome.css create mode 100644 devtools/client/styleeditor/test/sourcemap-css/test-bootstrap-scss.css create mode 100644 devtools/client/styleeditor/test/sourcemap-css/test-stylus.css create mode 100644 devtools/client/styleeditor/test/sourcemap-sass/media-rules.scss create mode 100644 devtools/client/styleeditor/test/sourcemap-sass/sourcemaps.scss create mode 100644 devtools/client/styleeditor/test/sourcemap-sass/sourcemaps.scss^headers^ create mode 100644 devtools/client/styleeditor/test/sourcemap-styl/test-stylus.styl create mode 100644 devtools/client/styleeditor/test/sourcemaps-inline.html create mode 100644 devtools/client/styleeditor/test/sourcemaps-large.html create mode 100644 devtools/client/styleeditor/test/sourcemaps-watching.html create mode 100644 devtools/client/styleeditor/test/sourcemaps.html create mode 100644 devtools/client/styleeditor/test/sync.html create mode 100644 devtools/client/styleeditor/test/sync_with_csp.css create mode 100644 devtools/client/styleeditor/test/sync_with_csp.html create mode 100644 devtools/client/styleeditor/test/test_private.css create mode 100644 devtools/client/styleeditor/test/test_private.html create mode 100644 devtools/client/styleeditor/test/utf-16.css create mode 100644 devtools/client/styleeditor/test/veryveryverylongnamethatcanbreakthestyleeditor.css (limited to 'devtools/client/styleeditor/test') diff --git a/devtools/client/styleeditor/test/autocomplete.html b/devtools/client/styleeditor/test/autocomplete.html new file mode 100644 index 0000000000..801eb4d4b9 --- /dev/null +++ b/devtools/client/styleeditor/test/autocomplete.html @@ -0,0 +1,23 @@ + + + + testcase for autocomplete testing + + + + +
parent child
+ + diff --git a/devtools/client/styleeditor/test/browser.toml b/devtools/client/styleeditor/test/browser.toml new file mode 100644 index 0000000000..34cf130826 --- /dev/null +++ b/devtools/client/styleeditor/test/browser.toml @@ -0,0 +1,227 @@ +[DEFAULT] +tags = "devtools" +subsuite = "devtools" +support-files = [ + "autocomplete.html", + "bug_1405342_serviceworker_iframes.html", + "four.html", + "head.js", + "iframe_with_service_worker.html", + "iframe_service_worker.js", + "import.css", + "import.html", + "import2.css", + "inline-1.html", + "inline-2.html", + "longload.html", + "longname.html", + "many-media-rules-sourcemaps/index.html", + "many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css", + "many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css.map", + "many-media-rules-sourcemaps/sourcemap/sourcemap-sass/_partial.scss", + "many-media-rules-sourcemaps/sourcemap/sourcemap-sass/sourcemaps.scss", + "media-small.css", + "media.html", + "media-rules.html", + "media-rules.css", + "media-rules-sourcemaps.html", + "minified.html", + "missing.html", + "nostyle.html", + "pretty.css", + "resources_inpage.jsi", + "resources_inpage1.css", + "resources_inpage2.css", + "selector-highlighter.html", + "simple.css", + "simple.css.gz", + "simple.css.gz^headers^", + "simple.gz.html", + "simple.html", + "sjs_huge-css-server.sjs", + "sourcemap-css/contained.css", + "sourcemap-css/sourcemaps.css", + "sourcemap-css/sourcemaps_chrome.css", + "sourcemap-css/sourcemaps.css.map", + "sourcemap-css/sourcemaps.css.map^headers^", # add nosniff header to test against Bug 1330383 + "sourcemap-css/media-rules.css", + "sourcemap-css/media-rules.css.map", + "sourcemap-css/test-bootstrap-scss.css", + "sourcemap-css/test-stylus.css", + "sourcemap-sass/sourcemaps.scss", + "sourcemap-sass/sourcemaps.scss^headers^", # add nosniff header to test against Bug 1330383 + "sourcemap-sass/media-rules.scss", + "sourcemap-styl/test-stylus.styl", + "sourcemaps.html", + "sourcemaps-inline.html", + "sourcemaps-large.html", + "sourcemaps-watching.html", + "test_private.css", + "test_private.html", + "doc_empty.html", + "doc_fetch_from_netmonitor.html", + "doc_long_string.css", + "doc_long.css", + "doc_short_string.css", + "doc_sourcemap_chrome.html", + "doc_xulpage.xhtml", + "sync.html", + "sync_with_csp.css", + "sync_with_csp.html", + "utf-16.css", + "veryveryverylongnamethatcanbreakthestyleeditor.css", + "!/devtools/client/framework/browser-toolbox/test/helpers-browser-toolbox.js", + "!/devtools/client/inspector/shared/test/head.js", + "!/devtools/client/inspector/test/head.js", + "!/devtools/client/inspector/test/shared-head.js", + "!/devtools/client/shared/test/shared-head.js", + "!/devtools/client/shared/test/telemetry-test-helpers.js", + "!/devtools/client/shared/test/highlighter-test-actor.js", +] + +["browser_styleeditor_add_stylesheet.js"] + +["browser_styleeditor_at_rules_sidebar.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_autocomplete-disabled.js"] + +["browser_styleeditor_autocomplete.js"] + +["browser_styleeditor_bom.js"] + +["browser_styleeditor_bug_740541_iframes.js"] + +["browser_styleeditor_bug_851132_middle_click.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_bug_870339.js"] + +["browser_styleeditor_bug_1247083_inline_stylesheet_numbering.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_bug_1405342_serviceworker_iframes.js"] +skip-if = [ + "!debug && os == 'win'", + "os == 'linux' && os_version == '18.04'", #bug 1424914 +] + +["browser_styleeditor_copyurl.js"] + +["browser_styleeditor_enabled.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_fetch-from-netmonitor.js"] +skip-if = [ + "http3", # Bug 1829298 + "http2", +] + +["browser_styleeditor_filesave.js"] +skip-if = [ + "http3", # Bug 1829298 + "http2", +] + +["browser_styleeditor_filter.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_fission_switch_target.js"] + +["browser_styleeditor_highlight-selector.js"] + +["browser_styleeditor_import.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_import_rule.js"] + +["browser_styleeditor_init.js"] + +["browser_styleeditor_inline_friendly_names.js"] + +["browser_styleeditor_loading.js"] +skip-if = [ + "http3", # Bug 1829298 + "http2", +] + +["browser_styleeditor_loading_with_containers.js"] + +["browser_styleeditor_media_sidebar_links.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_media_sidebar_sourcemaps.js"] +skip-if = ["a11y_checks"] # Bug 1858041 and 1849028 intermittent a11y_checks results (fails on Try, passes on Autoland) + +["browser_styleeditor_missing_stylesheet.js"] + +["browser_styleeditor_navigate.js"] + +["browser_styleeditor_new.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_nostyle.js"] + +["browser_styleeditor_opentab.js"] + +["browser_styleeditor_pretty.js"] + +["browser_styleeditor_private_perwindowpb.js"] + +["browser_styleeditor_reload.js"] + +["browser_styleeditor_remove_stylesheet.js"] + +["browser_styleeditor_resize_performance.js"] + +["browser_styleeditor_scroll.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_selectstylesheet.js"] + +["browser_styleeditor_sidebars.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_sourcemap_chrome.js"] + +["browser_styleeditor_sourcemap_large.js"] +skip-if = ["a11y_checks"] # Bug 1858041 and 1849028 intermittent a11y_checks results (fails on Try, passes on Autoland) + +["browser_styleeditor_sourcemap_watching.js"] +skip-if = [ + "http3", # Bug 1829298 + "http2", + "a11y_checks", # Bug 1858041 and 1849028 intermittent a11y_checks results (fails on Try, passes on Autoland) +] + +["browser_styleeditor_sourcemaps.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_sourcemaps_inline.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_sv_keynav.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_sv_resize.js"] + +["browser_styleeditor_sync.js"] +skip-if = ["a11y_checks && debug"] # Bugs 1849028 and 1858041 for causing intermittent test results + +["browser_styleeditor_syncAddProperty.js"] + +["browser_styleeditor_syncAddRule.js"] + +["browser_styleeditor_syncAlreadyOpen.js"] + +["browser_styleeditor_syncEditSelector.js"] + +["browser_styleeditor_syncIntoRuleView.js"] + +["browser_styleeditor_transition_rule.js"] +fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled + +["browser_styleeditor_xul.js"] + +["browser_toolbox_styleeditor.js"] +skip-if = ["asan"] # Bug 1591064 diff --git a/devtools/client/styleeditor/test/browser_styleeditor_add_stylesheet.js b/devtools/client/styleeditor/test/browser_styleeditor_add_stylesheet.js new file mode 100644 index 0000000000..794de2c328 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_add_stylesheet.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that a newly-added style sheet shows up in the style editor. + +const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 2, "Two sheets present after load."); + + // We have to wait for the length to change, because we might still + // be seeing events from the initial open. + const added = new Promise(resolve => { + const handler = () => { + if (ui.editors.length === 3) { + ui.off("editor-added", handler); + resolve(); + } + }; + ui.on("editor-added", handler); + }); + + info("Adding a style sheet"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + const document = content.document; + const style = document.createElement("style"); + style.appendChild(document.createTextNode("div { background: #f06; }")); + document.head.appendChild(style); + }); + await added; + + is(ui.editors.length, 3, "Three sheets present after new style sheet"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_at_rules_sidebar.js b/devtools/client/styleeditor/test/browser_styleeditor_at_rules_sidebar.js new file mode 100644 index 0000000000..a0a9bc93fd --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_at_rules_sidebar.js @@ -0,0 +1,340 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// https rather than chrome to improve coverage +const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html"; +const SIDEBAR_PREF = "devtools.styleeditor.showAtRulesSidebar"; + +const RESIZE_W = 300; +const RESIZE_H = 450; +const LABELS = [ + "not all", + "all", + "(max-width: 550px)", + "(min-height: 300px) and (max-height: 320px)", + "(max-width: 750px)", + "", + "print", +]; +const LINE_NOS = [1, 7, 19, 25, 31, 34, 39]; +const NEW_RULE = ` + @media (max-width: 750px) { + div { + color: blue; + @layer { + border-color: tomato; + } + } + + @media print { + body { + filter: grayscale(100%); + } + } + }`; + +waitForExplicitFinish(); + +add_task(async function () { + await pushPref("layout.css.container-queries.enabled", true); + + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 4, "correct number of editors"); + + info("Test first plain css editor"); + const plainEditor = ui.editors[0]; + await openEditor(plainEditor); + testPlainEditor(plainEditor); + + info("Test editor for inline sheet with @media rules"); + const inlineMediaEditor = ui.editors[3]; + await openEditor(inlineMediaEditor); + await testInlineMediaEditor(ui, inlineMediaEditor); + + info("Test editor with @media rules"); + const mediaEditor = ui.editors[1]; + await openEditor(mediaEditor); + await testMediaEditor(ui, mediaEditor); + + info("Test that sidebar hides when flipping pref"); + await testShowHide(ui, mediaEditor); + + info("Test adding a rule updates the list"); + await testMediaRuleAdded(ui, mediaEditor); + + info("Test resizing and seeing @media matching state change"); + const originalWidth = window.outerWidth; + const originalHeight = window.outerHeight; + + const onMatchesChange = ui.once("at-rules-list-changed"); + window.resizeTo(RESIZE_W, RESIZE_H); + await onMatchesChange; + + testMediaMatchChanged(mediaEditor); + + window.resizeTo(originalWidth, originalHeight); +}); + +function testPlainEditor(editor) { + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + is(sidebar.hidden, true, "sidebar is hidden on editor without @media"); +} + +async function testInlineMediaEditor(ui, editor) { + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + is(sidebar.hidden, false, "sidebar is showing on editor with @media"); + + const entries = sidebar.querySelectorAll(".at-rule-label"); + is(entries.length, 6, "6 @media rules displayed in sidebar"); + + await testRule({ + ui, + editor, + rule: entries[0], + conditionText: "screen", + matches: true, + line: 2, + type: "media", + }); + + await testRule({ + ui, + editor, + rule: entries[1], + conditionText: "(display: flex)", + line: 7, + type: "support", + }); + + await testRule({ + ui, + editor, + rule: entries[2], + conditionText: "(1px < height < 10000px)", + matches: true, + line: 8, + type: "media", + }); + + await testRule({ + ui, + editor, + rule: entries[3], + conditionText: "", + line: 16, + type: "layer", + layerName: "myLayer", + }); + + await testRule({ + ui, + editor, + rule: entries[4], + conditionText: "(min-width: 1px)", + line: 17, + type: "container", + }); + + await testRule({ + ui, + editor, + rule: entries[5], + conditionText: "selector(&)", + line: 21, + type: "support", + }); +} + +async function testMediaEditor(ui, editor) { + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + is(sidebar.hidden, false, "sidebar is showing on editor with @media"); + + const entries = [...sidebar.querySelectorAll(".at-rule-label")]; + is(entries.length, 4, "four @media rules displayed in sidebar"); + + await testRule({ + ui, + editor, + rule: entries[0], + conditionText: LABELS[0], + matches: false, + line: LINE_NOS[0], + }); + await testRule({ + ui, + editor, + rule: entries[1], + conditionText: LABELS[1], + matches: true, + line: LINE_NOS[1], + }); + await testRule({ + ui, + editor, + rule: entries[2], + conditionText: LABELS[2], + matches: false, + line: LINE_NOS[2], + }); + await testRule({ + ui, + editor, + rule: entries[3], + conditionText: LABELS[3], + matches: false, + line: LINE_NOS[3], + }); +} + +function testMediaMatchChanged(editor) { + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + + const cond = sidebar.querySelectorAll(".at-rule-condition")[2]; + is( + cond.textContent, + "(max-width: 550px)", + "third rule condition text is correct" + ); + ok( + !cond.classList.contains("media-condition-unmatched"), + "media rule is now matched after resizing" + ); +} + +async function testShowHide(ui, editor) { + let sidebarChange = ui.once("at-rules-list-changed"); + Services.prefs.setBoolPref(SIDEBAR_PREF, false); + await sidebarChange; + + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + is(sidebar.hidden, true, "sidebar is hidden after flipping pref"); + + sidebarChange = ui.once("at-rules-list-changed"); + Services.prefs.clearUserPref(SIDEBAR_PREF); + await sidebarChange; + + is(sidebar.hidden, false, "sidebar is showing after flipping pref back"); +} + +async function testMediaRuleAdded(ui, editor) { + await editor.getSourceEditor(); + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + is( + sidebar.querySelectorAll(".at-rule-label").length, + 4, + "4 @media rules after changing text" + ); + + let text = editor.sourceEditor.getText(); + text += NEW_RULE; + + const listChange = ui.once("at-rules-list-changed"); + editor.sourceEditor.setText(text); + await listChange; + + const entries = [...sidebar.querySelectorAll(".at-rule-label")]; + is(entries.length, 7, "7 @media rules after changing text"); + + await testRule({ + ui, + editor, + rule: entries[4], + conditionText: LABELS[4], + matches: false, + line: LINE_NOS[4], + }); + + await testRule({ + ui, + editor, + rule: entries[5], + type: "layer", + conditionText: LABELS[5], + line: LINE_NOS[5], + }); + + await testRule({ + ui, + editor, + rule: entries[6], + conditionText: LABELS[6], + matches: false, + line: LINE_NOS[6], + }); +} + +/** + * Run assertion on given rule + * + * @param {Object} options + * @param {StyleEditorUI} options.ui + * @param {StyleSheetEditor} options.editor: The editor the rule is displayed in + * @param {Element} options.rule: The rule element in the media sidebar + * @param {String} options.conditionText: media query condition text + * @param {Boolean} options.matches: Whether or not the document matches the rule + * @param {String} options.layerName: Optional name of the @layer + * @param {Number} options.line: Line of the rule + * @param {String} options.type: The type of the rule (container, layer, media, support ). + * Defaults to "media". + */ +async function testRule({ + ui, + editor, + rule, + conditionText, + matches, + layerName, + line, + type = "media", +}) { + const atTypeEl = rule.querySelector(".at-rule-type"); + is( + atTypeEl.textContent, + `@${type}\u00A0${layerName ? `${layerName}\u00A0` : ""}`, + "label for at-rule type is correct" + ); + + const cond = rule.querySelector(".at-rule-condition"); + is( + cond.textContent, + conditionText, + "condition label is correct for " + conditionText + ); + + if (type == "media") { + const matched = !cond.classList.contains("media-condition-unmatched"); + ok( + matches ? matched : !matched, + "media rule is " + (matches ? "matched" : "unmatched") + ); + } + + const ruleLine = rule.querySelector(".at-rule-line"); + is(ruleLine.textContent, ":" + line, "correct line number shown"); + + info( + "Check that clicking on the rule jumps to the expected position in the stylesheet" + ); + rule.click(); + await waitFor( + () => + ui.selectedEditor == editor && + editor.sourceEditor.getCursor().line == line - 1 + ); + ok(true, "Jumped to the expected location"); +} + +/* Helpers */ + +function openEditor(editor) { + getLinkFor(editor).click(); + + return editor.getSourceEditor(); +} + +function getLinkFor(editor) { + return editor.summary.querySelector(".stylesheet-name"); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_autocomplete-disabled.js b/devtools/client/styleeditor/test/browser_styleeditor_autocomplete-disabled.js new file mode 100644 index 0000000000..fd7d4969d0 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_autocomplete-disabled.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that autocomplete can be disabled. + +const TESTCASE_URI = TEST_BASE_HTTP + "autocomplete.html"; + +// Pref which decides if CSS autocompletion is enabled in Style Editor or not. +const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + const editor = await ui.editors[0].getSourceEditor(); + editor.sourceEditor.setOption("autocomplete", false); + + is( + editor.sourceEditor.getOption("autocomplete"), + false, + "Autocompletion option does not exist" + ); + ok( + !editor.sourceEditor.getAutocompletionPopup(), + "Autocompletion popup does not exist" + ); +}); + +add_task(async function () { + Services.prefs.setBoolPref(AUTOCOMPLETION_PREF, false); + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + const editor = await ui.editors[0].getSourceEditor(); + + is( + editor.sourceEditor.getOption("autocomplete"), + false, + "Autocompletion option does not exist" + ); +}); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref(AUTOCOMPLETION_PREF); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_autocomplete.js b/devtools/client/styleeditor/test/browser_styleeditor_autocomplete.js new file mode 100644 index 0000000000..b1377c62ab --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_autocomplete.js @@ -0,0 +1,284 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that autocompletion works as expected. + +const TESTCASE_URI = TEST_BASE_HTTP + "autocomplete.html"; +const MAX_SUGGESTIONS = 15; + +// Test cases to test that autocompletion works correctly when enabled. +// Format: +// [ +// key, +// { +// total: Number of suggestions in the popup (-1 if popup is closed), +// current: Index of selected suggestion, +// inserted: 1 to check whether the selected suggestion is inserted into the +// editor or not, +// entered: 1 if the suggestion is inserted and finalized +// } +// ] + +function getTestCases(cssProperties) { + const keywords = getCSSKeywords(cssProperties); + const getSuggestionNumberFor = suggestionNumberGetter(keywords); + + return [ + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["Ctrl+Space", { total: 1, current: 0 }], + ["VK_LEFT"], + ["VK_RIGHT"], + ["VK_DOWN"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["Ctrl+Space", { total: getSuggestionNumberFor("font"), current: 0 }], + ["VK_END"], + ["VK_RETURN"], + ["b", { total: getSuggestionNumberFor("b"), current: 0 }], + ["a", { total: getSuggestionNumberFor("ba"), current: 0 }], + [ + "VK_DOWN", + { total: getSuggestionNumberFor("ba"), current: 0, inserted: 1 }, + ], + [ + "VK_DOWN", + { total: getSuggestionNumberFor("ba"), current: 1, inserted: 1 }, + ], + [ + "VK_TAB", + { total: getSuggestionNumberFor("ba"), current: 2, inserted: 1 }, + ], + ["VK_RETURN", { current: 2, inserted: 1, entered: 1 }], + ["b", { total: getSuggestionNumberFor("background", "b"), current: 0 }], + ["l", { total: getSuggestionNumberFor("background", "bl"), current: 0 }], + [ + "VK_TAB", + { + total: getSuggestionNumberFor("background", "bl"), + current: 0, + inserted: 1, + }, + ], + [ + "VK_DOWN", + { + total: getSuggestionNumberFor("background", "bl"), + current: 1, + inserted: 1, + }, + ], + [ + "VK_UP", + { + total: getSuggestionNumberFor("background", "bl"), + current: 0, + inserted: 1, + }, + ], + [ + "VK_TAB", + { + total: getSuggestionNumberFor("background", "bl"), + current: 1, + inserted: 1, + }, + ], + [ + "VK_TAB", + { + total: getSuggestionNumberFor("background", "bl"), + current: 2, + inserted: 1, + }, + ], + [";"], + ["VK_RETURN"], + ["c", { total: getSuggestionNumberFor("c"), current: 0 }], + ["o", { total: getSuggestionNumberFor("co"), current: 0 }], + ["VK_RETURN", { current: 0, inserted: 1 }], + ["r", { total: getSuggestionNumberFor("color", "r"), current: 0 }], + ["VK_RETURN", { current: 0, inserted: 1 }], + [";"], + ["VK_LEFT"], + ["VK_RIGHT"], + ["VK_DOWN"], + ["VK_RETURN"], + ["b", { total: 2, current: 0 }], + ["u", { total: 1, current: 0 }], + ["VK_RETURN", { current: 0, inserted: 1 }], + ["{"], + ["VK_HOME"], + ["VK_DOWN"], + ["VK_DOWN"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["VK_RIGHT"], + ["Ctrl+Space", { total: 1, current: 0 }], + ]; +} + +add_task(async function () { + // We try to type "background" above, so backdrop-filter enabledness affects + // the expectations. Instead of branching on the test set the pref to true + // here as that is the end state, and it doesn't interact with the test in + // other ways. + await SpecialPowers.pushPrefEnv({ + set: [["layout.css.backdrop-filter.enabled", true]], + }); + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); + const { cssProperties } = ui; + const testCases = getTestCases(cssProperties); + + await ui.selectStyleSheet(ui.editors[1].styleSheet); + const editor = await ui.editors[1].getSourceEditor(); + + const sourceEditor = editor.sourceEditor; + const popup = sourceEditor.getAutocompletionPopup(); + + await SimpleTest.promiseFocus(panel.panelWindow); + + for (const index in testCases) { + await testState(testCases, index, sourceEditor, popup, panel.panelWindow); + await checkState(testCases, index, sourceEditor, popup); + } +}); + +function testState(testCases, index, sourceEditor, popup, panelWindow) { + let [key, details] = testCases[index]; + let entered; + if (details) { + entered = details.entered; + } + const mods = {}; + + info( + "pressing key " + + key + + " to get result: " + + JSON.stringify(testCases[index]) + + " for index " + + index + ); + + let evt = "after-suggest"; + + if (key == "Ctrl+Space") { + key = " "; + mods.ctrlKey = true; + } else if (key == "VK_RETURN" && entered) { + evt = "popup-hidden"; + } else if ( + /(left|right|return|home|end)/gi.test(key) || + (key == "VK_DOWN" && !popup.isOpen) + ) { + evt = "cursorActivity"; + } else if (key == "VK_TAB" || key == "VK_UP" || key == "VK_DOWN") { + evt = "suggestion-entered"; + } + + const ready = sourceEditor.once(evt); + EventUtils.synthesizeKey(key, mods, panelWindow); + + return ready; +} + +function checkState(testCases, index, sourceEditor, popup) { + return new Promise(resolve => { + executeSoon(() => { + let [, details] = testCases[index]; + details = details || {}; + const { total, current, inserted } = details; + + if (total != undefined) { + ok(popup.isOpen, "Popup is open for index " + index); + is( + total, + popup.itemCount, + "Correct total suggestions for index " + index + ); + is( + current, + popup.selectedIndex, + "Correct index is selected for index " + index + ); + if (inserted) { + const { text } = popup.getItemAtIndex(current); + const { line, ch } = sourceEditor.getCursor(); + const lineText = sourceEditor.getText(line); + is( + lineText.substring(ch - text.length, ch), + text, + "Current suggestion from the popup is inserted into the editor." + ); + } + } else { + ok(!popup.isOpen, "Popup is closed for index " + index); + if (inserted) { + const { text } = popup.getItemAtIndex(current); + const { line, ch } = sourceEditor.getCursor(); + const lineText = sourceEditor.getText(line); + is( + lineText.substring(ch - text.length, ch), + text, + "Current suggestion from the popup is inserted into the editor." + ); + } + } + resolve(); + }); + }); +} + +/** + * Returns a list of all property names and a map of property name vs possible + * CSS values provided by the Gecko engine. + * + * @return {Object} An object with following properties: + * - CSSProperties {Array} Array of string containing all the possible + * CSS property names. + * - CSSValues {Object|Map} A map where key is the property name and + * value is an array of string containing all the possible + * CSS values the property can have. + */ +function getCSSKeywords(cssProperties) { + const props = {}; + const propNames = cssProperties.getNames(); + propNames.forEach(prop => { + props[prop] = cssProperties.getValues(prop).sort(); + }); + return { + CSSValues: props, + CSSProperties: propNames.sort(), + }; +} + +/** + * Returns a function that returns the number of expected suggestions for the given + * property and value. If the value is not null, returns the number of values starting + * with `value`. Returns the number of properties starting with `property` otherwise. + */ +function suggestionNumberGetter({ CSSProperties, CSSValues }) { + return (property, value) => { + if (value == null) { + return CSSProperties.filter(prop => prop.startsWith(property)).slice( + 0, + MAX_SUGGESTIONS + ).length; + } + return CSSValues[property] + .filter(val => val.startsWith(value)) + .slice(0, MAX_SUGGESTIONS).length; + }; +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_bom.js b/devtools/client/styleeditor/test/browser_styleeditor_bom.js new file mode 100644 index 0000000000..863351a32c --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_bom.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const BOM_CSS = TEST_BASE_HTTPS + "utf-16.css"; +const DOCUMENT = + "data:text/html;charset=UTF-8," + + encodeURIComponent( + [ + "", + "", + " ", + " Bug 1301854", + ' ', + " ", + " ", + " ", + "", + ].join("\n") + ); + +const CONTENTS = + "// Note that this file must be utf-16 with a " + + "BOM for the test to make sense.\n"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(DOCUMENT); + + is(ui.editors.length, 1, "correct number of editors"); + + const editor = ui.editors[0]; + await editor.getSourceEditor(); + + const text = editor.sourceEditor.getText(); + is(text, CONTENTS, "editor contains expected text"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_bug_1247083_inline_stylesheet_numbering.js b/devtools/client/styleeditor/test/browser_styleeditor_bug_1247083_inline_stylesheet_numbering.js new file mode 100644 index 0000000000..1f40d44cc1 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_bug_1247083_inline_stylesheet_numbering.js @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that the displayed numbering of inline and user-created stylesheets are independent of their absolute index +// See bug 1247083. + +const SIMPLE = TEST_BASE_HTTP + "simple.css"; +const LONG = TEST_BASE_HTTP + "doc_long.css"; +const DOCUMENT_WITH_LONG_SHEET = + "data:text/html;charset=UTF-8," + + encodeURIComponent( + [ + "", + "", + " ", + " Style editor numbering test page", + + // first inline stylesheet + " ", + // first external stylesheet + ' ', + // second external stylesheet + ' ', + // second inline stylesheet + " ", + + " ", + " ", + " ", + "", + ].join("\n") + ); + +add_task(async function () { + info("Test that inline stylesheets are numbered correctly"); + const { ui } = await openStyleEditorForURL(DOCUMENT_WITH_LONG_SHEET); + + is(ui.editors.length, 4, "4 editors present."); + + const firstEditor = ui.editors[0]; + is( + firstEditor.styleSheetFriendlyIndex, + 0, + "1st inline stylesheet's index is 0" + ); + + is( + firstEditor.styleSheet.styleSheetIndex, + 0, + "1st inline stylesheet is also the first stylesheet declared" + ); + + is(firstEditor.styleSheet.ruleCount, 1, "1st inline stylesheet has 1 rule"); + + const secondEditor = ui.editors[3]; + is( + secondEditor.styleSheetFriendlyIndex, + 1, + "2nd inline stylesheet's index is 1" + ); + + is( + secondEditor.styleSheet.styleSheetIndex, + 3, + "2nd inline stylesheet is the last stylesheet" + ); + + is(secondEditor.styleSheet.ruleCount, 2, "2nd inline stylesheet has 2 rules"); +}); + +add_task(async function () { + info("Test that user-created stylesheets are numbered correctly"); + const { panel, ui } = await openStyleEditorForURL(DOCUMENT_WITH_LONG_SHEET); + await createNewStyleSheet(ui, panel.panelWindow); + await createNewStyleSheet(ui, panel.panelWindow); + + is(ui.editors.length, 6, "6 editors present."); + + ok(ui.editors[4].isNew, "2nd to last editor is user-created"); + is( + ui.editors[4].styleSheetFriendlyIndex, + 0, + "2nd to last user created stylesheet's index is 0" + ); + + ok(ui.editors[5].isNew, "Last editor is user-created"); + is( + ui.editors[5].styleSheetFriendlyIndex, + 1, + "Last user created stylesheet's index is 1" + ); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_bug_1405342_serviceworker_iframes.js b/devtools/client/styleeditor/test/browser_styleeditor_bug_1405342_serviceworker_iframes.js new file mode 100644 index 0000000000..cccd92fedd --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_bug_1405342_serviceworker_iframes.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that sheets inside cross origin iframes, served from a service worker +// are correctly fetched via the service worker in the stylesheet editor. + +add_task(async function () { + const TEST_URL = + "https://test1.example.com/browser/devtools/client/styleeditor/test/bug_1405342_serviceworker_iframes.html"; + const { ui } = await openStyleEditorForURL(TEST_URL); + + if (ui.editors.length != 1) { + info("Stylesheet isn't available immediately, waiting for it"); + await ui.once("editor-added"); + } + is(ui.editors.length, 1, "Got the iframe stylesheet"); + + await ui.selectStyleSheet(ui.editors[0].styleSheet); + const editor = await ui.editors[0].getSourceEditor(); + const text = editor.sourceEditor.getText(); + is( + text, + "* { color: green; }", + "stylesheet content is the one served by the service worker" + ); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_bug_740541_iframes.js b/devtools/client/styleeditor/test/browser_styleeditor_bug_740541_iframes.js new file mode 100644 index 0000000000..9eaf0be0f2 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_bug_740541_iframes.js @@ -0,0 +1,107 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that sheets inside iframes are shown in the editor. + +add_task(async function () { + function makeStylesheet(selector) { + return ( + "data:text/css;charset=UTF-8," + encodeURIComponent(selector + " { }") + ); + } + + function makeDocument(stylesheets, framedDocuments) { + stylesheets = stylesheets || []; + framedDocuments = framedDocuments || []; + return ( + "data:text/html;charset=UTF-8," + + encodeURIComponent( + Array.prototype.concat + .call( + [ + "", + "", + "", + "Bug 740541", + ], + stylesheets.map(function (sheet) { + return ( + '' + ); + }), + ["", ""], + framedDocuments.map(function (doc) { + return ''; + }), + ["", ""] + ) + .join("\n") + ) + ); + } + + const DOCUMENT_WITH_INLINE_STYLE = + "data:text/html;charset=UTF-8," + + encodeURIComponent( + [ + "", + "", + " ", + " Bug 740541", + ' ", + " ", + " ", + " ", + " ", + ].join("\n") + ); + + const FOUR = TEST_BASE_HTTP + "four.html"; + + const SIMPLE = TEST_BASE_HTTP + "simple.css"; + + const SIMPLE_DOCUMENT = TEST_BASE_HTTP + "simple.html"; + + const TESTCASE_URI = makeDocument( + [makeStylesheet(".a")], + [ + makeDocument([], [FOUR, DOCUMENT_WITH_INLINE_STYLE]), + makeDocument( + [makeStylesheet(".b"), SIMPLE], + [makeDocument([makeStylesheet(".c")], [])] + ), + makeDocument([SIMPLE], []), + SIMPLE_DOCUMENT, + ] + ); + + const EXPECTED_STYLE_SHEET_COUNT = 12; + + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is( + ui.editors.length, + EXPECTED_STYLE_SHEET_COUNT, + "Got the expected number of style sheets." + ); + + // Verify that stylesheets are removed when their related target is destroyed + if (isFissionEnabled() || isEveryFrameTargetEnabled()) { + info("Remove all iframes"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + const iframes = content.document.querySelectorAll("iframe"); + for (const iframe of iframes) { + iframe.remove(); + } + }); + + await waitFor( + () => ui.editors.length == 1, + "Wait until all iframe stylesheets are removed and we only have the top document one" + ); + } +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js b/devtools/client/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js new file mode 100644 index 0000000000..ed0d5838b2 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that middle click on style sheet doesn't open index.xhtml in a new +// tab (bug 851132). + +const TESTCASE_URI = TEST_BASE_HTTP + "four.html"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + gBrowser.tabContainer.addEventListener("TabOpen", onTabAdded); + + await ui.editors[0].getSourceEditor(); + info("first editor selected"); + + await waitFor( + () => ui.editors[0].sourceEditor.hasFocus(), + "Wait until the initially selected editor grabs the focus" + ); + + info("Left-clicking on the second editor link."); + await clickOnStyleSheetLink(ui.editors[1], 0); + + info("Waiting for the second editor to be selected."); + const editor = await ui.once("editor-selected"); + + ok( + editor.sourceEditor.hasFocus(), + "Left mouse click gave second editor focus." + ); + + // middle mouse click should not open a new tab + info("Middle clicking on the third editor link."); + await clickOnStyleSheetLink(ui.editors[2], 1); +}); + +/** + * A helper that clicks on style sheet link in the sidebar. + * + * @param {StyleSheetEditor} editor + * The editor of which link should be clicked. + * @param {MouseEvent.button} button + * The button to click the link with. + */ +async function clickOnStyleSheetLink(editor, button) { + const window = editor._window; + const link = editor.summary.querySelector(".stylesheet-name"); + + info("Waiting for focus."); + await SimpleTest.promiseFocus(window); + + info("Pressing button " + button + " on style sheet name link."); + EventUtils.synthesizeMouseAtCenter(link, { button }, window); +} + +function onTabAdded() { + ok(false, "middle mouse click has opened a new tab"); +} + +registerCleanupFunction(function () { + gBrowser.tabContainer.removeEventListener("TabOpen", onTabAdded); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_bug_870339.js b/devtools/client/styleeditor/test/browser_styleeditor_bug_870339.js new file mode 100644 index 0000000000..926048223d --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_bug_870339.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const SIMPLE = TEST_BASE_HTTP + "simple.css"; +const DOCUMENT_WITH_ONE_STYLESHEET = + "data:text/html;charset=UTF-8," + + encodeURIComponent( + [ + "", + "", + " ", + " Bug 870339", + ' ', + " ", + " ", + " ", + "", + ].join("\n") + ); + +add_task(async function () { + const { ui } = await openStyleEditorForURL(DOCUMENT_WITH_ONE_STYLESHEET); + + // Spam the "devtools.source-map.client-service.enabled" pref observer callback (#onOrigSourcesPrefChanged) + // multiple times before the StyleEditorActor has a chance to respond to the first one. + const SPAM_COUNT = 2; + let prefValue = false; + for (let i = 0; i < SPAM_COUNT; ++i) { + pushPref("devtools.source-map.client-service.enabled", prefValue); + prefValue = !prefValue; + } + + // Wait for the StyleEditorActor to respond to each pref changes. + await new Promise(resolve => { + let loadCount = 0; + ui.on("stylesheets-refreshed", function onReset() { + ++loadCount; + if (loadCount == SPAM_COUNT) { + ui.off("stylesheets-refreshed", onReset); + // No matter how large SPAM_COUNT is, the number of style + // sheets should never be more than the number of style sheets + // in the document. + is(ui.editors.length, 1, "correct style sheet count"); + resolve(); + } + }); + }); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_copyurl.js b/devtools/client/styleeditor/test/browser_styleeditor_copyurl.js new file mode 100644 index 0000000000..8b2919a173 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_copyurl.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test to check the 'Copy URL' functionality in the context menu item for stylesheets. + +const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html"; + +add_task(async function () { + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); + + const doc = panel.panelWindow.document; + const contextMenu = getContextMenuElement(panel); + const copyUrlItem = doc.getElementById("context-copyurl"); + + const onContextMenuShown = new Promise(resolve => { + contextMenu.addEventListener("popupshown", resolve, { once: true }); + }); + + info("Right-click the first stylesheet editor."); + const editor = ui.editors[0]; + + is(editor.friendlyName, "simple.css", "editor is the expected one"); + + const stylesheetEl = editor.summary.querySelector(".stylesheet-name"); + await EventUtils.synthesizeMouseAtCenter( + stylesheetEl, + { button: 2, type: "contextmenu" }, + panel.panelWindow + ); + await onContextMenuShown; + + ok(!copyUrlItem.hidden, "Copy URL menu item should be showing."); + + info( + "Click on Copy URL menu item and wait for the URL to be copied to the clipboard." + ); + await waitForClipboardPromise( + () => contextMenu.activateItem(copyUrlItem), + `${TEST_BASE_HTTPS}simple.css` + ); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_enabled.js b/devtools/client/styleeditor/test/browser_styleeditor_enabled.js new file mode 100644 index 0000000000..7a902d2634 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_enabled.js @@ -0,0 +1,135 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that style sheets can be disabled and enabled. + +// https rather than chrome to improve coverage +const SIMPLE_URI = TEST_BASE_HTTPS + "simple.html"; +const LONGNAME_URI = TEST_BASE_HTTPS + "longname.html"; + +add_task(async function () { + const { panel, ui } = await openStyleEditorForURL(SIMPLE_URI); + const editor = await ui.editors[0].getSourceEditor(); + + const summary = editor.summary; + const stylesheetToggle = summary.querySelector(".stylesheet-toggle"); + ok(stylesheetToggle, "stylesheet toggle button exists"); + + is( + editor.styleSheet.disabled, + false, + "first stylesheet is initially enabled" + ); + + is( + summary.classList.contains("disabled"), + false, + "first stylesheet is initially enabled, UI does not have DISABLED class" + ); + + info("Disabling the first stylesheet."); + await toggleEnabled(editor, stylesheetToggle, panel.panelWindow); + + is(editor.styleSheet.disabled, true, "first stylesheet is now disabled"); + is( + summary.classList.contains("disabled"), + true, + "first stylesheet is now disabled, UI has DISABLED class" + ); + + info("Enabling the first stylesheet again."); + await toggleEnabled(editor, stylesheetToggle, panel.panelWindow); + + is( + editor.styleSheet.disabled, + false, + "first stylesheet is now enabled again" + ); + is( + summary.classList.contains("disabled"), + false, + "first stylesheet is now enabled again, UI does not have DISABLED class" + ); +}); + +// Check that stylesheets with long names do not prevent the toggle button +// from being visible. +add_task(async function testLongNameStylesheet() { + const { ui } = await openStyleEditorForURL(LONGNAME_URI); + + is(ui.editors.length, 2, "Expected 2 stylesheet editors"); + + // Test that the first editor, which should have a stylesheet with a short + // name. + let editor = ui.editors[0]; + let stylesheetToggle = editor.summary.querySelector(".stylesheet-toggle"); + is(editor.friendlyName, "simple.css"); + ok(stylesheetToggle, "stylesheet toggle button exists"); + Assert.greater(stylesheetToggle.getBoundingClientRect().width, 0); + Assert.greater(stylesheetToggle.getBoundingClientRect().height, 0); + + const expectedWidth = stylesheetToggle.getBoundingClientRect().width; + const expectedHeight = stylesheetToggle.getBoundingClientRect().height; + + // Test that the second editor, which should have a stylesheet with a long + // name. + editor = ui.editors[1]; + stylesheetToggle = editor.summary.querySelector(".stylesheet-toggle"); + is(editor.friendlyName, "veryveryverylongnamethatcanbreakthestyleeditor.css"); + ok(stylesheetToggle, "stylesheet toggle button exists"); + is(stylesheetToggle.getBoundingClientRect().width, expectedWidth); + is(stylesheetToggle.getBoundingClientRect().height, expectedHeight); +}); + +add_task(async function testSystemStylesheet() { + const { ui } = await openStyleEditorForURL("about:support"); + + const aboutSupportEditor = ui.editors.find( + editor => editor.friendlyName === "aboutSupport.css" + ); + ok(!!aboutSupportEditor, "Found the editor for aboutSupport.css"); + const aboutSupportToggle = + aboutSupportEditor.summary.querySelector(".stylesheet-toggle"); + ok(aboutSupportToggle, "enabled toggle button exists"); + ok(!aboutSupportToggle.disabled, "enabled toggle button is not disabled"); + is( + aboutSupportToggle.getAttribute("tooltiptext"), + "Toggle style sheet visibility" + ); + + const formsEditor = ui.editors.find( + editor => editor.friendlyName === "forms.css" + ); + ok(!!formsEditor, "Found the editor for forms.css"); + const formsToggle = formsEditor.summary.querySelector(".stylesheet-toggle"); + ok(formsToggle, "enabled toggle button exists"); + ok(formsToggle.disabled, "enabled toggle button is disabled"); + // For some unexplained reason, this is updated asynchronously + await waitFor( + () => + formsToggle.getAttribute("tooltiptext") == + "System style sheets can’t be disabled" + ); + is( + formsToggle.getAttribute("tooltiptext"), + "System style sheets can’t be disabled" + ); +}); + +async function toggleEnabled(editor, stylesheetToggle, panelWindow) { + const changed = editor.once("property-change"); + + info("Waiting for focus."); + await SimpleTest.promiseFocus(panelWindow); + + info("Clicking on the toggle."); + EventUtils.synthesizeMouseAtCenter(stylesheetToggle, {}, panelWindow); + + info("Waiting for stylesheet to be disabled."); + let property = await changed; + while (property !== "disabled") { + info("Ignoring property-change for '" + property + "'."); + property = await editor.once("property-change"); + } +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-netmonitor.js b/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-netmonitor.js new file mode 100644 index 0000000000..7169de8bf5 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-netmonitor.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// A test to ensure Style Editor only issues 1 request for each stylesheet (instead of 2) +// by using the cache on the platform. + +const EMPTY_TEST_URL = TEST_BASE_HTTPS + "doc_empty.html"; +const TEST_URL = TEST_BASE_HTTPS + "doc_fetch_from_netmonitor.html"; + +add_task(async function () { + info("Opening netmonitor"); + // Navigate first to an empty document in order to: + // * avoid introducing a cross process navigation when calling navigateTo() + // * properly wait for request updates when calling navigateTo, while showToolbox + // won't necessarily wait for all pending requests. (If we were loading TEST_URL + // in the tab, we might have pending updates in the netmonitor which won't be + // awaited for by showToolbox) + const tab = await addTab(EMPTY_TEST_URL); + const toolbox = await gDevTools.showToolboxForTab(tab, { + toolId: "netmonitor", + }); + const monitor = toolbox.getPanel("netmonitor"); + const { store, windowRequire } = monitor.panelWin; + const Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); + const { getSortedRequests } = windowRequire( + "devtools/client/netmonitor/src/selectors/index" + ); + + store.dispatch(Actions.batchEnable(false)); + + info("Navigating to test page"); + await navigateTo(TEST_URL); + + info("Opening Style Editor"); + const styleeditor = await toolbox.selectTool("styleeditor"); + const ui = styleeditor.UI; + + info("Waiting for the sources to be loaded."); + await ui.editors[0].getSourceEditor(); + await ui.selectStyleSheet(ui.editors[1].styleSheet); + await ui.editors[1].getSourceEditor(); + + // Wait till there is 4 requests in Netmonitor store. + await waitUntil(() => getSortedRequests(store.getState()).length == 4); + + info("Checking Netmonitor contents."); + const shortRequests = []; + const longRequests = []; + const hugeRequests = []; + for (const item of getSortedRequests(store.getState())) { + if (item.url.endsWith("doc_short_string.css")) { + shortRequests.push(item); + } + if (item.url.endsWith("doc_long_string.css")) { + longRequests.push(item); + } + if (item.url.endsWith("sjs_huge-css-server.sjs")) { + hugeRequests.push(item); + } + } + + is( + shortRequests.length, + 1, + "Got one request for doc_short_string.css after Style Editor was loaded." + ); + is( + longRequests.length, + 1, + "Got one request for doc_long_string.css after Style Editor was loaded." + ); + + is( + hugeRequests.length, + 1, + "Got one requests for sjs_huge-css-server.sjs after Style Editor was loaded." + ); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_filesave.js b/devtools/client/styleeditor/test/browser_styleeditor_filesave.js new file mode 100644 index 0000000000..d59137af25 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_filesave.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that 'Save' function works. + +const TESTCASE_URI_HTML = TEST_BASE_HTTP + "simple.html"; +const TESTCASE_URI_CSS = TEST_BASE_HTTP + "simple.css"; + +add_task(async function () { + const htmlFile = await copy(TESTCASE_URI_HTML, "simple.html"); + await copy(TESTCASE_URI_CSS, "simple.css"); + const uri = Services.io.newFileURI(htmlFile); + const filePath = uri.resolve(""); + + const { ui } = await openStyleEditorForURL(filePath); + + const editor = ui.editors[0]; + await editor.getSourceEditor(); + + info("Editing the style sheet."); + let dirty = editor.sourceEditor.once("dirty-change"); + const beginCursor = { line: 0, ch: 0 }; + editor.sourceEditor.replaceText("DIRTY TEXT", beginCursor, beginCursor); + + await dirty; + + is(editor.sourceEditor.isClean(), false, "Editor is dirty."); + ok( + editor.summary.classList.contains("unsaved"), + "Star icon is present in the corresponding summary." + ); + + info("Saving the changes."); + dirty = editor.sourceEditor.once("dirty-change"); + + editor.saveToFile(null, function (file) { + ok(file, "file should get saved directly when using a file:// URI"); + }); + + await dirty; + + is(editor.sourceEditor.isClean(), true, "Editor is clean."); + ok( + !editor.summary.classList.contains("unsaved"), + "Star icon is not present in the corresponding summary." + ); +}); + +function copy(srcChromeURL, destFileName) { + return new Promise(resolve => { + const destFile = new FileUtils.File( + PathUtils.join(PathUtils.profileDir, destFileName) + ); + write(read(srcChromeURL), destFile, resolve); + }); +} + +function read(srcChromeURL) { + const scriptableStream = Cc[ + "@mozilla.org/scriptableinputstream;1" + ].getService(Ci.nsIScriptableInputStream); + + const channel = NetUtil.newChannel({ + uri: srcChromeURL, + loadUsingSystemPrincipal: true, + }); + const input = channel.open(); + scriptableStream.init(input); + + let data = ""; + while (input.available()) { + data = data.concat(scriptableStream.read(input.available())); + } + scriptableStream.close(); + input.close(); + + return data; +} + +function write(data, file, callback) { + const istream = getInputStream(data); + const ostream = FileUtils.openSafeFileOutputStream(file); + + NetUtil.asyncCopy(istream, ostream, function (status) { + if (!Components.isSuccessCode(status)) { + info("Couldn't write to " + file.path); + return; + } + + callback(file); + }); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_filter.js b/devtools/client/styleeditor/test/browser_styleeditor_filter.js new file mode 100644 index 0000000000..c8472b5f40 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_filter.js @@ -0,0 +1,343 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(2); + +// Check that the stylesheets list can be filtered + +const INITIAL_INLINE_STYLE_SHEETS_COUNT = 100; + +const TEST_URI = + "data:text/html;charset=UTF-8," + + encodeURIComponent( + ` + + + + Test filter + + ${Array.from({ length: INITIAL_INLINE_STYLE_SHEETS_COUNT }) + .map((_, i) => ``) + .join("\n")} + + + + + + ` + ); + +add_task(async function () { + const { panel, ui } = await openStyleEditorForURL(TEST_URI); + const { panelWindow } = panel; + is( + ui.editors.length, + INITIAL_INLINE_STYLE_SHEETS_COUNT + 2, + "correct number of editors" + ); + + const doc = panel.panelWindow.document; + + const filterInput = doc.querySelector(".devtools-filterinput"); + const filterInputClearButton = doc.querySelector( + ".devtools-searchinput-clear" + ); + ok(filterInput, "There's a filter input"); + ok(filterInputClearButton, "There's a clear button next to the filter input"); + ok( + filterInputClearButton.hasAttribute("hidden"), + "The clear button is hidden by default" + ); + + const setFilterInputValue = value => { + // The keyboard shortcut focuses the input and select its content, so we should + // be able to type right-away. + synthesizeKeyShortcut("CmdOrCtrl+P"); + EventUtils.sendString(value); + }; + + info( + "Check that the list can be filtered with the stylesheet name, regardless of the casing" + ); + let onEditorSelected = ui.once("editor-selected"); + setFilterInputValue("PREttY"); + ok( + !filterInputClearButton.hasAttribute("hidden"), + "The clear button is visible when the input isn't empty" + ); + Assert.deepEqual( + getVisibleStyleSheetsNames(doc), + ["pretty.css"], + "Only pretty.css is now displayed" + ); + + await onEditorSelected; + is( + ui.selectedEditor, + ui.editors.at(-1), + "When the selected stylesheet is filtered out, the first visible one gets selected" + ); + is( + filterInput.ownerGlobal.document.activeElement, + filterInput, + "Even when a stylesheet was automatically opened, the filter input is still focused" + ); + ok(!ui.selectedEditor.sourceEditor.hasFocus(), "Editor doesn't have focus."); + + info( + "Clicking on the clear button should clear the input and unfilter the list" + ); + EventUtils.synthesizeMouseAtCenter( + filterInputClearButton, + {}, + panel.panelWindow + ); + is(filterInput.value, "", "input was cleared"); + ok(!isListFiltered(doc), "List isn't filtered anymore"); + ok( + filterInputClearButton.hasAttribute("hidden"), + "The clear button is hidden after clicking on it" + ); + + info("Check that the list can be filtered with name-less stylesheets"); + onEditorSelected = ui.once("editor-selected"); + setFilterInputValue("#1"); + Assert.deepEqual( + getVisibleStyleSheetsNames(doc), + [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ], + `List is showing inline stylesheets whose index start with "1"` + ); + await onEditorSelected; + is( + ui.selectedEditor, + ui.editors[1], + "The first visible stylesheet got selected" + ); + + info("Check that keyboard navigation still works when the list is filtered"); + // Move focus out of the input + EventUtils.synthesizeKey("VK_TAB", {}, panelWindow); + EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow); + + is( + panelWindow.document.activeElement.childNodes[0].value, + "", + "focus is on first inline stylesheet" + ); + + EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow); + is( + panelWindow.document.activeElement.childNodes[0].value, + "", + "focus is on inline stylesheet #10" + ); + + EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow); + is( + panelWindow.document.activeElement.childNodes[0].value, + "", + "focus is on inline stylesheet #11" + ); + + info( + "Check that when stylesheets are added in the page, they respect the filter state" + ); + let onEditorAdded = ui.once("editor-added"); + // Adding an inline stylesheet that will match the search + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + const document = content.document; + const style = document.createElement("style"); + style.appendChild(document.createTextNode(`/* inline 101 */`)); + document.head.appendChild(style); + }); + await onEditorAdded; + ok( + getVisibleStyleSheetsNames(doc).includes(""), + "New inline stylesheet is visible as it matches the search" + ); + + // Adding a stylesheet that won't match the search + onEditorAdded = ui.once("editor-added"); + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [TEST_BASE_HTTPS], + baseUrl => { + const document = content.document; + const link = document.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", "text/css"); + link.setAttribute("href", `${baseUrl}doc_short_string.css`); + document.head.appendChild(link); + } + ); + await onEditorAdded; + + ok( + !getVisibleStyleSheetsNames(doc).includes("doc_short_string.css"), + "doc_short_string.css is not visible as its name does not match the search" + ); + + info( + "Check that clicking on the Add New Stylesheet button clears the list and show the stylesheet" + ); + onEditorAdded = ui.once("editor-added"); + await createNewStyleSheet(ui, panel.panelWindow); + is(filterInput.value, "", "Filter input was cleared"); + + ok(!isListFiltered(doc), "List is not filtered anymore"); + is(ui.selectedEditor, ui.editors.at(-1), "The new stylesheet got selected"); + + info( + "Check that when no stylesheet matches the search, a class is added to the nav" + ); + setFilterInputValue("sync_with_csp"); + ok(navHasAllFilteredClass(panel), `"splitview-all-filtered" was added`); + ok( + filterInput + .closest(".devtools-searchbox") + .classList.contains("devtools-searchbox-no-match"), + `The searchbox has the "devtools-searchbox-no-match" class` + ); + + info( + "Check that adding a stylesheet matching the search remove the splitview-all-filtered class" + ); + onEditorAdded = ui.once("editor-added"); + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [TEST_BASE_HTTPS], + baseUrl => { + const document = content.document; + const link = document.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", "text/css"); + link.setAttribute("href", `${baseUrl}sync_with_csp.css`); + document.head.appendChild(link); + } + ); + await onEditorAdded; + ok(!navHasAllFilteredClass(panel), `"splitview-all-filtered" was removed`); + ok( + !filterInput + .closest(".devtools-searchbox") + .classList.contains("devtools-searchbox-no-match"), + `The searchbox does not have the "devtools-searchbox-no-match" class anymore` + ); + + info( + "Check that reloading the page when the filter don't match anything won't select anything" + ); + setFilterInputValue("XXXDONTMATCHANYTHING"); + ok(navHasAllFilteredClass(panel), `"splitview-all-filtered" was added`); + await reloadPageAndWaitForStyleSheets( + ui, + INITIAL_INLINE_STYLE_SHEETS_COUNT + 2 + ); + ok( + navHasAllFilteredClass(panel), + `"splitview-all-filtered" is still applied` + ); + is(getVisibleStyleSheets(doc).length, 0, "No stylesheets are displayed"); + is(ui.selectedEditor, null, "No editor was selected"); + + info( + "Check that reloading the page when the filter was matching elements keep the same state" + ); + onEditorSelected = ui.once("editor-selected"); + setFilterInputValue("pretty"); + await onEditorSelected; + Assert.deepEqual( + getVisibleStyleSheetsNames(doc), + ["pretty.css"], + "Only pretty.css is now displayed" + ); + + onEditorSelected = ui.once("editor-selected"); + await reloadPageAndWaitForStyleSheets( + ui, + INITIAL_INLINE_STYLE_SHEETS_COUNT + 2 + ); + await onEditorSelected; + Assert.deepEqual( + getVisibleStyleSheetsNames(doc), + ["pretty.css"], + "pretty.css is still the only stylesheet displayed" + ); + is( + ui.selectedEditor.friendlyName, + "pretty.css", + "pretty.css editor is active" + ); + + info("Check that clearing the input does show all the stylesheets"); + EventUtils.synthesizeMouseAtCenter( + filterInputClearButton, + {}, + panel.panelWindow + ); + ok(!isListFiltered(doc), "List is not filtered anymore"); + is( + ui.selectedEditor.friendlyName, + "pretty.css", + "pretty.css editor is still active" + ); +}); + +/** + * @param {StyleEditorPanel} panel + * @returns Boolean + */ +function navHasAllFilteredClass(panel) { + return panel.panelWindow.document + .querySelector(".splitview-nav") + .classList.contains("splitview-all-filtered"); +} + +/** + * Returns true if there's at least one stylesheet filtered out + * + * @param {Document} doc: StyleEditor document + * @returns Boolean + */ +function isListFiltered(doc) { + return !!doc.querySelectorAll("ol > li.splitview-filtered").length; +} + +/** + * Returns the list of stylesheet list elements. + * + * @param {Document} doc: StyleEditor document + * @returns Array + */ +function getVisibleStyleSheets(doc) { + return Array.from( + doc.querySelectorAll( + "ol > li:not(.splitview-filtered) .stylesheet-name label" + ) + ); +} + +/** + * Returns the list of stylesheet names visible in the style editor list. + * + * @param {Document} doc: StyleEditor document + * @returns Array + */ +function getVisibleStyleSheetsNames(doc) { + return getVisibleStyleSheets(doc).map(label => label.value); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_fission_switch_target.js b/devtools/client/styleeditor/test/browser_styleeditor_fission_switch_target.js new file mode 100644 index 0000000000..3f5bf34fd1 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_fission_switch_target.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test switching for the top-level target. + +const PARENT_PROCESS_URI = "about:robots"; +const CONTENT_PROCESS_URI = TEST_BASE_HTTPS + "simple.html"; + +add_task(async function () { + // We use about:robots, because this page will run in the parent process. + // Navigating from about:robots to a regular content page will always trigger a target + // switch, with or without fission. + + info("Open a page that runs in the parent process"); + const { ui } = await openStyleEditorForURL(PARENT_PROCESS_URI); + await waitUntil(() => ui.editors.length === 7); + ok(true, `Seven style sheets for ${PARENT_PROCESS_URI}`); + + info("Navigate to a page that runs in the child process"); + await navigateToAndWaitForStyleSheets(CONTENT_PROCESS_URI, ui, 2); + // We also have to wait for the toolbox to complete the target switching + // in order to avoid pending requests during test teardown. + ok( + ui.editors.every( + editor => editor._resource.nodeHref == CONTENT_PROCESS_URI + ), + `Two sheets present for ${CONTENT_PROCESS_URI}` + ); + + info("Wait until the editor is ready"); + await waitFor(() => ui.selectedEditor?.sourceEditor); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js b/devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js new file mode 100644 index 0000000000..a5ee01e438 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js @@ -0,0 +1,223 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that hovering over a simple selector in the style-editor requests the +// highlighting of the corresponding nodes, even in remote iframes. + +const REMOTE_IFRAME_URL = `https://example.org/document-builder.sjs?html= + +

highlighter test

`; +const TOP_LEVEL_URL = `https://example.com/document-builder.sjs?html= + +

highlighter test

+ `; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TOP_LEVEL_URL); + + info( + "Wait until both stylesheet are loaded and ready to handle mouse events" + ); + await waitFor(() => ui.editors.length == 2); + const topLevelStylesheetEditor = ui.editors.find(e => + e._resource.nodeHref.startsWith("https://example.com") + ); + const iframeStylesheetEditor = ui.editors.find(e => + e._resource.nodeHref.startsWith("https://example.org") + ); + + await ui.selectStyleSheet(topLevelStylesheetEditor.styleSheet); + await waitFor(() => topLevelStylesheetEditor.highlighter); + + info("Check that highlighting works on the top-level document"); + const topLevelHighlighterTestFront = + await topLevelStylesheetEditor._resource.targetFront.getFront( + "highlighterTest" + ); + topLevelHighlighterTestFront.highlighter = + topLevelStylesheetEditor.highlighter; + + info("Expecting a node-highlighted event"); + let onHighlighted = topLevelStylesheetEditor.once("node-highlighted"); + + info("Simulate a mousemove event on the h1 selector"); + // mousemove event listeners is set on editor.sourceEditor, which is not defined right away. + await waitFor(() => !!topLevelStylesheetEditor.sourceEditor); + let selectorEl = querySelectorCodeMirrorCssRuleSelectorToken( + topLevelStylesheetEditor + ); + EventUtils.synthesizeMouseAtCenter( + selectorEl, + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + await onHighlighted; + ok( + await topLevelHighlighterTestFront.isNodeRectHighlighted( + await getElementNodeRectWithinTarget(["h1"]) + ), + "The highlighter's outline corresponds to the h1 node" + ); + + info( + "Simulate a mousemove event on the property name to hide the highlighter" + ); + EventUtils.synthesizeMouseAtCenter( + querySelectorCodeMirrorCssPropertyNameToken(topLevelStylesheetEditor), + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + + await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown()); + let isVisible = await topLevelHighlighterTestFront.isHighlighting(); + is(isVisible, false, "The highlighter is now hidden"); + + // It looks like we need to show the same highlighter again to trigger Bug 1847747 + info("Show and hide the highlighter again"); + onHighlighted = topLevelStylesheetEditor.once("node-highlighted"); + EventUtils.synthesizeMouseAtCenter( + selectorEl, + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + await onHighlighted; + EventUtils.synthesizeMouseAtCenter( + querySelectorCodeMirrorCssPropertyNameToken(topLevelStylesheetEditor), + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + + await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown()); + // wait for a bit so the style editor would have had the time to process + // any unwanted stylesheets + await wait(1000); + ok( + !ui.editors.find(e => e._resource.href?.includes("highlighters.css")), + "highlighters.css isn't displayed in StyleEditor" + ); + is(ui.editors.length, 2, "No other stylesheet was displayed"); + + info("Check that highlighting works on the iframe document"); + await ui.selectStyleSheet(iframeStylesheetEditor.styleSheet); + await waitFor(() => iframeStylesheetEditor.highlighter); + + const iframeHighlighterTestFront = + await iframeStylesheetEditor._resource.targetFront.getFront( + "highlighterTest" + ); + iframeHighlighterTestFront.highlighter = iframeStylesheetEditor.highlighter; + + info("Expecting a node-highlighted event"); + onHighlighted = iframeStylesheetEditor.once("node-highlighted"); + + info("Simulate a mousemove event on the h2 selector"); + // mousemove event listeners is set on editor.sourceEditor, which is not defined right away. + await waitFor(() => !!iframeStylesheetEditor.sourceEditor); + selectorEl = querySelectorCodeMirrorCssRuleSelectorToken( + iframeStylesheetEditor + ); + EventUtils.synthesizeMouseAtCenter( + selectorEl, + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + await onHighlighted; + + isVisible = await iframeHighlighterTestFront.isHighlighting(); + ok(isVisible, "The highlighter is shown"); + ok( + await iframeHighlighterTestFront.isNodeRectHighlighted( + await getElementNodeRectWithinTarget(["iframe", "h2"]) + ), + "The highlighter's outline corresponds to the h2 node" + ); + + info("Simulate a mousemove event elsewhere in the editor"); + EventUtils.synthesizeMouseAtCenter( + querySelectorCodeMirrorCssPropertyNameToken(iframeStylesheetEditor), + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + + await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown()); + + isVisible = await iframeHighlighterTestFront.isHighlighting(); + is(isVisible, false, "The highlighter is now hidden"); +}); + +function querySelectorCodeMirrorCssRuleSelectorToken(stylesheetEditor) { + // CSS Rules selector (e.g. `h1`) are displayed in a .cm-tag span + return querySelectorCodeMirror(stylesheetEditor, ".cm-tag"); +} + +function querySelectorCodeMirrorCssPropertyNameToken(stylesheetEditor) { + // properties name (e.g. `color`) are displayed in a .cm-property span + return querySelectorCodeMirror(stylesheetEditor, ".cm-property"); +} + +function querySelectorCodeMirror(stylesheetEditor, selector) { + return stylesheetEditor.sourceEditor.codeMirror + .getWrapperElement() + .querySelector(selector); +} + +/** + * Return the bounds of the element matching the selector, relatively to the target bounds + * (e.g. if Fission is enabled, it's related to the iframe bound, if Fission is disabled, + * it's related to the top level document). + * + * @param {Array} selectors: Arrays of CSS selectors from the root document to the node. + * The last CSS selector of the array is for the node in its frame doc. + * The before-last CSS selector is for the frame in its parent frame, etc... + * Ex: ["frame.first-frame", ..., "frame.last-frame", ".target-node"] + * @returns {Object} with left/top/width/height properties representing the node bounds + */ +async function getElementNodeRectWithinTarget(selectors) { + // Retrieve the browsing context in which the element is + const inBCSelector = selectors.pop(); + const frameSelectors = selectors; + const bc = frameSelectors.length + ? await getBrowsingContextInFrames( + gBrowser.selectedBrowser.browsingContext, + frameSelectors + ) + : gBrowser.selectedBrowser.browsingContext; + + // Get the element bounds within the Firefox window + const elementBounds = await SpecialPowers.spawn( + bc, + [inBCSelector], + _selector => { + const el = content.document.querySelector(_selector); + const { left, top, width, height } = el + .getBoxQuadsFromWindowOrigin()[0] + .getBounds(); + return { left, top, width, height }; + } + ); + + // Then we need to offset the element bounds from a frame bounds + // When fission/EFT is enabled, the highlighter is only shown within the iframe bounds. + // So we only need to retrieve the element bounds within the iframe. + // Otherwise, we retrieve the top frame bounds + const relativeBrowsingContext = + isFissionEnabled() || isEveryFrameTargetEnabled() + ? bc + : gBrowser.selectedBrowser.browsingContext; + const relativeDocumentBounds = await SpecialPowers.spawn( + relativeBrowsingContext, + [], + () => + content.document.documentElement + .getBoxQuadsFromWindowOrigin()[0] + .getBounds() + ); + + // Adjust the element bounds based on the relative document bounds + elementBounds.left = elementBounds.left - relativeDocumentBounds.left; + elementBounds.top = elementBounds.top - relativeDocumentBounds.top; + + return elementBounds; +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_import.js b/devtools/client/styleeditor/test/browser_styleeditor_import.js new file mode 100644 index 0000000000..c630dcd508 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_import.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that the import button in the UI works. + +// http rather than chrome to improve coverage +const TESTCASE_URI = TEST_BASE_HTTP + "simple.html"; + +const FILENAME = "styleeditor-import-test.css"; +const SOURCE = "body{background:red;}"; + +add_task(async function () { + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); + + const added = ui.once("test:editor-updated"); + importSheet(ui, panel.panelWindow); + + info("Waiting for editor to be added for the imported sheet."); + const editor = await added; + + is( + editor.savedFile.leafName, + FILENAME, + "imported stylesheet will be saved directly into the same file" + ); + is( + editor.friendlyName, + FILENAME, + "imported stylesheet has the same name as the filename" + ); +}); + +function importSheet(ui, panelWindow) { + // create file to import first + const file = new FileUtils.File( + PathUtils.join(PathUtils.profileDir, FILENAME) + ); + const ostream = FileUtils.openSafeFileOutputStream(file); + const istream = getInputStream(SOURCE); + + NetUtil.asyncCopy(istream, ostream, function () { + FileUtils.closeSafeFileOutputStream(ostream); + + // click the import button now that the file to import is ready + ui._mockImportFile = file; + + waitForFocus(function () { + const document = panelWindow.document; + const importButton = document.querySelector(".style-editor-importButton"); + ok(importButton, "import button exists"); + + EventUtils.synthesizeMouseAtCenter(importButton, {}, panelWindow); + }, panelWindow); + }); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_import_rule.js b/devtools/client/styleeditor/test/browser_styleeditor_import_rule.js new file mode 100644 index 0000000000..1aba71634c --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_import_rule.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that style editor shows sheets loaded with @import rules. + +// http rather than chrome to improve coverage +const TESTCASE_URI = TEST_BASE_HTTPS + "import.html"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 3, "there are 3 stylesheets after loading @imports"); + + is( + ui.editors[0].styleSheet.href, + TEST_BASE_HTTPS + "simple.css", + "stylesheet 1 is simple.css" + ); + + is( + ui.editors[1].styleSheet.href, + TEST_BASE_HTTPS + "import.css", + "stylesheet 2 is import.css" + ); + + is( + ui.editors[2].styleSheet.href, + TEST_BASE_HTTPS + "import2.css", + "stylesheet 3 is import2.css" + ); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_init.js b/devtools/client/styleeditor/test/browser_styleeditor_init.js new file mode 100644 index 0000000000..2183495441 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_init.js @@ -0,0 +1,52 @@ +"use strict"; +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Checks that style editor contains correct stylesheets after initialization. + +const TESTCASE_URI = TEST_BASE_HTTP + "simple.html"; +const EXPECTED_SHEETS = [ + { + sheetIndex: 0, + name: /^simple.css$/, + rules: 1, + active: true, + }, + { + sheetIndex: 1, + name: /^<.*>$/, + rules: 3, + active: false, + }, +]; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 2, "The UI contains two style sheets."); + checkSheet(ui.editors[0], EXPECTED_SHEETS[0]); + checkSheet(ui.editors[1], EXPECTED_SHEETS[1]); +}); + +function checkSheet(editor, expected) { + is( + editor.styleSheet.styleSheetIndex, + expected.sheetIndex, + "Style sheet has correct index." + ); + + const summary = editor.summary; + const name = summary + .querySelector(".stylesheet-name > label") + .getAttribute("value"); + ok(expected.name.test(name), "The name '" + name + "' is correct."); + + const ruleCount = summary.querySelector(".stylesheet-rule-count").textContent; + is(parseInt(ruleCount, 10), expected.rules, "the rule count is correct"); + + is( + summary.classList.contains("splitview-active"), + expected.active, + "The active status for this sheet is correct." + ); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_inline_friendly_names.js b/devtools/client/styleeditor/test/browser_styleeditor_inline_friendly_names.js new file mode 100644 index 0000000000..0296801701 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_inline_friendly_names.js @@ -0,0 +1,103 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that inline style sheets get correct names if they are saved to disk and +// that those names survice a reload but not navigation to another page. + +const FIRST_TEST_PAGE = TEST_BASE_HTTPS + "inline-1.html"; +const SECOND_TEST_PAGE = TEST_BASE_HTTPS + "inline-2.html"; +const SAVE_PATH = "test.css"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(FIRST_TEST_PAGE); + + testIndentifierGeneration(ui); + + await saveFirstInlineStyleSheet(ui); + await testFriendlyNamesAfterSave(ui); + await reloadPageAndWaitForStyleSheets(ui, 2); + await testFriendlyNamesAfterSave(ui); + await navigateToAndWaitForStyleSheets(SECOND_TEST_PAGE, ui, 2); + await testFriendlyNamesAfterNavigation(ui); +}); + +function testIndentifierGeneration(ui) { + const fakeStyleSheetFile = { + href: "http://example.com/test.css", + nodeHref: "http://example.com/", + styleSheetIndex: 1, + }; + + const fakeInlineStyleSheet = { + href: null, + nodeHref: "http://example.com/", + styleSheetIndex: 2, + }; + + is( + ui.getStyleSheetIdentifier(fakeStyleSheetFile), + "http://example.com/test.css", + "URI is the identifier of style sheet file." + ); + + is( + ui.getStyleSheetIdentifier(fakeInlineStyleSheet), + "inline-2-at-http://example.com/", + "Inline sheets are identified by their page and position in the page." + ); +} + +function saveFirstInlineStyleSheet(ui) { + return new Promise(resolve => { + const editor = ui.editors[0]; + const destFile = new FileUtils.File( + PathUtils.join(PathUtils.profileDir, SAVE_PATH) + ); + + editor.saveToFile(destFile, function (file) { + ok(file, "File was correctly saved."); + resolve(); + }); + }); +} + +function testFriendlyNamesAfterSave(ui) { + const firstEditor = ui.editors[0]; + const secondEditor = ui.editors[1]; + + // The friendly name of first sheet should've been remembered, the second + // should not be the same (bug 969900). + is( + firstEditor.friendlyName, + SAVE_PATH, + "Friendly name is correct for the saved inline style sheet." + ); + isnot( + secondEditor.friendlyName, + SAVE_PATH, + "Friendly name for the second inline sheet isn't the same as the first." + ); + + return Promise.resolve(null); +} + +function testFriendlyNamesAfterNavigation(ui) { + const firstEditor = ui.editors[0]; + const secondEditor = ui.editors[1]; + + // Inline style sheets shouldn't have the name of previously saved file as the + // page is different. + isnot( + firstEditor.friendlyName, + SAVE_PATH, + "The first editor doesn't have the save path as a friendly name." + ); + isnot( + secondEditor.friendlyName, + SAVE_PATH, + "The second editor doesn't have the save path as a friendly name." + ); + + return Promise.resolve(null); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_loading.js b/devtools/client/styleeditor/test/browser_styleeditor_loading.js new file mode 100644 index 0000000000..1321d071be --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_loading.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that style editor loads correctly. + +const TESTCASE_URI = TEST_BASE_HTTPS + "longload.html"; + +add_task(async function () { + // launch Style Editor right when the tab is created (before load) + // this checks that the Style Editor still launches correctly when it is + // opened *while* the page is still loading. The Style Editor should not + // signal that it is loaded until the accompanying content page is loaded. + const tabAdded = addTab(TESTCASE_URI); + const tab = gBrowser.selectedTab; + const styleEditorLoaded = gDevTools.showToolboxForTab(tab, { + toolId: "styleeditor", + }); + + await Promise.all([tabAdded, styleEditorLoaded]); + + const toolbox = gDevTools.getToolboxForTab(tab); + const panel = toolbox.getPanel("styleeditor"); + const { panelWindow } = panel; + + ok( + !getRootElement(panel).classList.contains("loading"), + "style editor root element does not have 'loading' class name anymore" + ); + + let button = panelWindow.document.querySelector(".style-editor-newButton"); + ok(!button.hasAttribute("disabled"), "new style sheet button is enabled"); + + button = panelWindow.document.querySelector(".style-editor-importButton"); + ok(!button.hasAttribute("disabled"), "import button is enabled"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_loading_with_containers.js b/devtools/client/styleeditor/test/browser_styleeditor_loading_with_containers.js new file mode 100644 index 0000000000..7cb0d04c64 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_loading_with_containers.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the stylesheets can be loaded correctly with containers +// (bug 1282660). + +const TESTCASE_URI = TEST_BASE_HTTP + "simple.html"; +const EXPECTED_SHEETS = [ + { + sheetIndex: 0, + name: /^simple.css$/, + rules: 1, + active: true, + }, + { + sheetIndex: 1, + name: /^<.*>$/, + rules: 3, + active: false, + }, +]; + +add_task(async function () { + // Using the personal container. + const userContextId = 1; + const { tab } = await openTabInUserContext(TESTCASE_URI, userContextId); + const { ui } = await openStyleEditor(tab); + + is(ui.editors.length, 2, "The UI contains two style sheets."); + await checkSheet(ui.editors[0], EXPECTED_SHEETS[0]); + await checkSheet(ui.editors[1], EXPECTED_SHEETS[1]); +}); + +async function openTabInUserContext(uri, userContextId) { + // Open the tab in the correct userContextId. + const tab = BrowserTestUtils.addTab(gBrowser, uri, { userContextId }); + + // Select tab and make sure its browser is focused. + gBrowser.selectedTab = tab; + tab.ownerDocument.defaultView.focus(); + + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + return { tab, browser }; +} + +async function checkSheet(editor, expected) { + is( + editor.styleSheet.styleSheetIndex, + expected.sheetIndex, + "Style sheet has correct index." + ); + + const summary = editor.summary; + const name = summary + .querySelector(".stylesheet-name > label") + .getAttribute("value"); + ok(expected.name.test(name), "The name '" + name + "' is correct."); + + // The rule count is displayed via l10n.setArgs which only applies the value + // asynchronously, so wait for it to be applied. + await waitFor(() => { + const count = summary.querySelector(".stylesheet-rule-count").textContent; + return parseInt(count, 10) === expected.rules; + }); + const ruleCount = summary.querySelector(".stylesheet-rule-count").textContent; + is(parseInt(ruleCount, 10), expected.rules, "the rule count is correct"); + + is( + summary.classList.contains("splitview-active"), + expected.active, + "The active status for this sheet is correct." + ); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js new file mode 100644 index 0000000000..850c9bf686 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js @@ -0,0 +1,165 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* Tests responsive mode links for + * @media sidebar width and height related conditions */ + +loader.lazyRequireGetter( + this, + "ResponsiveUIManager", + "resource://devtools/client/responsive/manager.js" +); + +const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html"; +const responsiveModeToggleClass = ".media-responsive-mode-toggle"; + +add_task(async function () { + // ensure that original RDM size is big enough so it doesn't match the + // media queries by default. + await pushPref("devtools.responsive.viewport.width", 1000); + await pushPref("devtools.responsive.viewport.height", 1000); + + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + const editor = ui.editors[1]; + await openEditor(editor); + + const tab = gBrowser.selectedTab; + testNumberOfLinks(editor); + await testMediaLink(editor, tab, ui, 2, "width", 550); + await testMediaLink(editor, tab, ui, 3, "height", 300); + + const onMediaChange = waitForManyEvents(ui, 1000); + await closeRDM(tab); + + info("Wait for at-rules-list-changed events to settle on StyleEditorUI"); + await onMediaChange; + doFinalChecks(editor); +}); + +function testNumberOfLinks(editor) { + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + const conditions = sidebar.querySelectorAll(".at-rule-condition"); + + info("Testing if media rules have the appropriate number of links"); + ok( + !conditions[0].querySelector(responsiveModeToggleClass), + "There should be no links in the first media rule." + ); + ok( + !conditions[1].querySelector(responsiveModeToggleClass), + "There should be no links in the second media rule." + ); + ok( + conditions[2].querySelector(responsiveModeToggleClass), + "There should be 1 responsive mode link in the media rule" + ); + is( + conditions[3].querySelectorAll(responsiveModeToggleClass).length, + 2, + "There should be 2 responsive mode links in the media rule" + ); +} + +async function testMediaLink(editor, tab, ui, itemIndex, type, value) { + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + const conditions = sidebar.querySelectorAll(".at-rule-condition"); + const onRDMOpened = once(ui, "responsive-mode-opened"); + + ok( + sidebar + .querySelectorAll(".at-rule-condition") + [itemIndex].classList.contains("media-condition-unmatched"), + `The ${type} condition is not matched when not in responsive mode` + ); + + info("Launching responsive mode"); + conditions[itemIndex].querySelector(responsiveModeToggleClass).click(); + await onRDMOpened; + const rdmUI = ResponsiveUIManager.getResponsiveUIForTab(tab); + await waitForResizeTo(rdmUI, type, value); + rdmUI.transitionsEnabled = false; + + info("Wait for RDM ui to be fully loaded"); + await waitForRDMLoaded(rdmUI); + + // Ensure that the content has reflowed, which will ensure that all the + // element classes are reported correctly. + await promiseContentReflow(rdmUI); + + ok( + ResponsiveUIManager.isActiveForTab(tab), + "Responsive mode should be active." + ); + await waitFor(() => { + const el = sidebar.querySelectorAll(".at-rule-condition")[itemIndex]; + return !el.classList.contains("media-condition-unmatched"); + }); + ok(true, "media rule should now be matched after responsive mode is active"); + + const dimension = (await getSizing(rdmUI))[type]; + is(dimension, value, `${type} should be properly set.`); +} + +function doFinalChecks(editor) { + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + let conditions = sidebar.querySelectorAll(".at-rule-condition"); + conditions = sidebar.querySelectorAll(".at-rule-condition"); + ok( + conditions[2].classList.contains("media-condition-unmatched"), + "The width condition should now be unmatched" + ); + ok( + conditions[3].classList.contains("media-condition-unmatched"), + "The height condition should now be unmatched" + ); +} + +/* Helpers */ +function waitForResizeTo(rdmUI, type, value) { + return new Promise(resolve => { + const onResize = data => { + if (data[type] != value) { + return; + } + rdmUI.off("content-resize", onResize); + info(`Got content-resize to a ${type} of ${value}`); + resolve(); + }; + info(`Waiting for content-resize to a ${type} of ${value}`); + rdmUI.on("content-resize", onResize); + }); +} + +function promiseContentReflow(ui) { + return SpecialPowers.spawn(ui.getViewportBrowser(), [], async function () { + return new Promise(resolve => { + content.window.requestAnimationFrame(() => { + content.window.requestAnimationFrame(resolve); + }); + }); + }); +} + +async function getSizing(rdmUI) { + const browser = rdmUI.getViewportBrowser(); + const sizing = await SpecialPowers.spawn(browser, [], async function () { + return { + width: content.innerWidth, + height: content.innerHeight, + }; + }); + return sizing; +} + +function openEditor(editor) { + getLinkFor(editor).click(); + + return editor.getSourceEditor(); +} + +function getLinkFor(editor) { + return editor.summary.querySelector(".stylesheet-name"); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js new file mode 100644 index 0000000000..5086058402 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// https rather than chrome to improve coverage +const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules-sourcemaps.html"; +const MAP_PREF = "devtools.source-map.client-service.enabled"; + +const LABELS = [ + "screen and (max-width: 320px)", + "screen and (min-width: 1200px)", +]; +const LINE_NOS = [5, 8]; + +waitForExplicitFinish(); + +add_task(async function () { + Services.prefs.setBoolPref(MAP_PREF, true); + + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 1, "correct number of editors"); + + // Test editor with @media rules + const mediaEditor = ui.editors[0]; + await openEditor(mediaEditor); + testAtRulesEditor(mediaEditor); + + Services.prefs.clearUserPref(MAP_PREF); +}); + +function testAtRulesEditor(editor) { + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + is(sidebar.hidden, false, "sidebar is showing on editor with @media"); + + const entries = [...sidebar.querySelectorAll(".at-rule-label")]; + is(entries.length, 2, "two @media rules displayed in sidebar"); + + testRule(entries[0], LABELS[0], LINE_NOS[0]); + testRule(entries[1], LABELS[1], LINE_NOS[1]); +} + +function testRule(rule, text, lineno) { + const cond = rule.querySelector(".at-rule-condition"); + is(cond.textContent, text, "media label is correct for " + text); + + const line = rule.querySelector(".at-rule-line"); + is(line.textContent, ":" + lineno, "correct line number shown"); +} + +/* Helpers */ + +function openEditor(editor) { + getLinkFor(editor).click(); + + return editor.getSourceEditor(); +} + +function getLinkFor(editor) { + return editor.summary.querySelector(".stylesheet-name"); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_missing_stylesheet.js b/devtools/client/styleeditor/test/browser_styleeditor_missing_stylesheet.js new file mode 100644 index 0000000000..97e6b78b49 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_missing_stylesheet.js @@ -0,0 +1,37 @@ +"use strict"; +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Checks that the style editor manages to finalize its stylesheet loading phase +// even if one stylesheet is missing, and that an error message is displayed. + +const TESTCASE_URI = TEST_BASE + "missing.html"; + +add_task(async function () { + const { ui, toolbox, panel } = await openStyleEditorForURL(TESTCASE_URI); + + // Note that we're not testing for a specific number of stylesheet editors + // below because the test-page is loaded with chrome:// URL and, right now, + // that means UA stylesheets are shown. So we avoid hardcoding the number of + // stylesheets here. + await waitUntil(() => ui.editors.length); + ok(true, "The UI contains style sheets."); + + const rootEl = panel.panelWindow.document.getElementById( + "style-editor-chrome" + ); + ok(!rootEl.classList.contains("loading"), "The loading indicator is hidden"); + + const notifBox = toolbox.getNotificationBox(); + await waitUntil(() => notifBox.getCurrentNotification()); + const notif = notifBox.getCurrentNotification(); + ok(notif, "The notification box contains a message"); + ok( + notif.label.includes("Style sheet could not be loaded"), + "The error message is the correct one" + ); + ok( + notif.label.includes("missing-stylesheet.css"), + "The error message contains the missing stylesheet's URL" + ); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_navigate.js b/devtools/client/styleeditor/test/browser_styleeditor_navigate.js new file mode 100644 index 0000000000..68f76915e4 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_navigate.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that selected sheet and cursor position is reset during navigation. + +const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html"; +const NEW_URI = TEST_BASE_HTTPS + "media.html"; + +const LINE_NO = 5; +const COL_NO = 3; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 2, "Two sheets present after load."); + + info("Selecting the second editor"); + await ui.selectStyleSheet(ui.editors[1].styleSheet, LINE_NO, COL_NO); + + await navigateToAndWaitForStyleSheets(NEW_URI, ui, 2); + + info("Waiting for source editor to be ready."); + await ui.editors[0].getSourceEditor(); + + is(ui.selectedEditor, ui.editors[0], "first editor is selected"); + + const { line, ch } = ui.selectedEditor.sourceEditor.getCursor(); + is(line, 0, "first line is selected"); + is(ch, 0, "first column is selected"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_new.js b/devtools/client/styleeditor/test/browser_styleeditor_new.js new file mode 100644 index 0000000000..f583759c00 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_new.js @@ -0,0 +1,106 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that new sheets can be added and edited. + +const TESTCASE_URI = TEST_BASE_HTTP + "simple.html"; + +const TESTCASE_CSS_SOURCE = "body{background-color:red;"; + +add_task(async function () { + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); + + const editor = await createNewStyleSheet(ui, panel.panelWindow); + await testInitialState(editor); + + const originalHref = editor.styleSheet.href; + const waitForPropertyChange = onPropertyChange(editor); + + await typeInEditor(editor, panel.panelWindow); + + await waitForPropertyChange; + + await testUpdated(editor, originalHref); +}); + +function onPropertyChange(editor) { + return new Promise(resolve => { + editor.on("property-change", function onProp(property) { + // wait for text to be entered fully + const text = editor.sourceEditor.getText(); + if (property == "ruleCount" && text == TESTCASE_CSS_SOURCE + "}") { + editor.off("property-change", onProp); + resolve(); + } + }); + }); +} + +async function testInitialState(editor) { + info("Testing the initial state of the new editor"); + + let summary = editor.summary; + + ok(editor.sourceLoaded, "new editor is loaded when attached"); + ok(editor.isNew, "new editor has isNew flag"); + + if (!editor.sourceEditor.hasFocus()) { + info("Waiting for stylesheet editor to gain focus"); + await editor.sourceEditor.once("focus"); + } + ok(editor.sourceEditor.hasFocus(), "new editor has focus"); + + summary = editor.summary; + const ruleCount = summary.querySelector(".stylesheet-rule-count").textContent; + is(parseInt(ruleCount, 10), 0, "new editor initially shows 0 rules"); + + const color = await getComputedStyleProperty({ + selector: "body", + name: "background-color", + }); + is( + color, + "rgb(255, 255, 255)", + "content's background color is initially white" + ); +} + +function typeInEditor(editor, panelWindow) { + return new Promise(resolve => { + waitForFocus(function () { + for (const c of TESTCASE_CSS_SOURCE) { + EventUtils.synthesizeKey(c, {}, panelWindow); + } + ok(editor.unsaved, "new editor has unsaved flag"); + + resolve(); + }, panelWindow); + }); +} + +async function testUpdated(editor, originalHref) { + info("Testing the state of the new editor after editing it"); + + is( + editor.sourceEditor.getText(), + TESTCASE_CSS_SOURCE + "}", + "rule bracket has been auto-closed" + ); + + const count = await waitFor(() => { + const int = parseInt( + editor.summary.querySelector(".stylesheet-rule-count").textContent, + 10 + ); + if (int == 0) { + return false; + } + + return int; + }); + + is(count, 1, "new editor shows 1 rule after modification"); + + is(editor.styleSheet.href, originalHref, "style sheet href did not change"); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_nostyle.js b/devtools/client/styleeditor/test/browser_styleeditor_nostyle.js new file mode 100644 index 0000000000..c8a2134135 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_nostyle.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that 'no styles' indicator is shown if a page doesn't contain any style +// sheets. + +const TESTCASE_URI = TEST_BASE_HTTP + "nostyle.html"; + +add_task(async function () { + // Make enough room for the "append style sheet" link to not wrap, + // as it messes up with EvenEventUtils.synthesizeMouse + await pushPref("devtools.styleeditor.navSidebarWidth", 500); + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); + const { panelWindow } = panel; + + ok( + !getRootElement(panel).classList.contains("loading"), + "style editor root element does not have 'loading' class name anymore" + ); + + const newButton = panelWindow.document.querySelector( + "toolbarbutton.style-editor-newButton" + ); + ok(!newButton.hasAttribute("disabled"), "new style sheet button is enabled"); + + const importButton = panelWindow.document.querySelector( + ".style-editor-importButton" + ); + ok(!importButton.hasAttribute("disabled"), "import button is enabled"); + + const emptyPlaceHolderEl = + getRootElement(panel).querySelector(".empty.placeholder"); + isnot( + emptyPlaceHolderEl.ownerGlobal.getComputedStyle(emptyPlaceHolderEl).display, + "none", + "showing 'no style' indicator" + ); + + info( + "Check that clicking on the append new stylesheet link do add a stylesheet" + ); + const onEditorAdded = ui.once("editor-added"); + const newLink = emptyPlaceHolderEl.querySelector("a.style-editor-newButton"); + + // Use synthesizeMouse to also check that the element is visible + EventUtils.synthesizeMouseAtCenter(newLink, {}, newLink.ownerGlobal); + await onEditorAdded; + + ok(true, "A stylesheet was added"); + is( + emptyPlaceHolderEl.ownerGlobal.getComputedStyle(emptyPlaceHolderEl).display, + "none", + "The empty placeholder element is now hidden" + ); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_opentab.js b/devtools/client/styleeditor/test/browser_styleeditor_opentab.js new file mode 100644 index 0000000000..55494f4166 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_opentab.js @@ -0,0 +1,133 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// A test to check the 'Open Link in new tab' functionality in the +// context menu item for stylesheets (bug 992947). +const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html"; + +add_task(async function () { + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); + + const openLinkNewTabItem = panel.panelWindow.document.getElementById( + "context-openlinknewtab" + ); + + let menu = await rightClickStyleSheet(panel, ui.editors[0]); + is( + openLinkNewTabItem.getAttribute("disabled"), + "false", + "The menu item is not disabled" + ); + ok(!openLinkNewTabItem.hidden, "The menu item is not hidden"); + + const url = TEST_BASE_HTTPS + "simple.css"; + + const browserWindow = Services.wm.getMostRecentWindow( + gDevTools.chromeWindowType + ); + const originalOpenWebLinkIn = browserWindow.openWebLinkIn; + const tabOpenedDefer = new Promise(resolve => { + browserWindow.openWebLinkIn = newUrl => { + // Reset the actual openWebLinkIn function before proceeding. + browserWindow.openWebLinkIn = originalOpenWebLinkIn; + + is(newUrl, url, "The correct tab has been opened"); + resolve(); + }; + }); + + const hidden = onPopupHide(menu); + + menu.activateItem(openLinkNewTabItem); + + info(`Waiting for a tab to open - ${url}`); + await tabOpenedDefer; + + await hidden; + + menu = await rightClickInlineStyleSheet(panel, ui.editors[1]); + is( + openLinkNewTabItem.getAttribute("disabled"), + "true", + "The menu item is disabled" + ); + ok(!openLinkNewTabItem.hidden, "The menu item should not be hidden"); + menu.hidePopup(); + + menu = await rightClickNoStyleSheet(panel); + ok(openLinkNewTabItem.hidden, "The menu item should be hidden"); + menu.hidePopup(); +}); + +function onPopupShow(contextMenu) { + return new Promise(resolve => { + contextMenu.addEventListener( + "popupshown", + function () { + resolve(); + }, + { once: true } + ); + }); +} + +function onPopupHide(contextMenu) { + return new Promise(resolve => { + contextMenu.addEventListener( + "popuphidden", + function () { + resolve(); + }, + { once: true } + ); + }); +} + +function rightClickStyleSheet(panel, editor) { + const contextMenu = getContextMenuElement(panel); + return new Promise(resolve => { + onPopupShow(contextMenu).then(() => { + resolve(contextMenu); + }); + + EventUtils.synthesizeMouseAtCenter( + editor.summary.querySelector(".stylesheet-name"), + { button: 2, type: "contextmenu" }, + panel.panelWindow + ); + }); +} + +function rightClickInlineStyleSheet(panel, editor) { + const contextMenu = getContextMenuElement(panel); + return new Promise(resolve => { + onPopupShow(contextMenu).then(() => { + resolve(contextMenu); + }); + + EventUtils.synthesizeMouseAtCenter( + editor.summary.querySelector(".stylesheet-name"), + { button: 2, type: "contextmenu" }, + panel.panelWindow + ); + }); +} + +function rightClickNoStyleSheet(panel) { + const contextMenu = getContextMenuElement(panel); + return new Promise(resolve => { + onPopupShow(contextMenu).then(() => { + resolve(contextMenu); + }); + + EventUtils.synthesizeMouseAtCenter( + panel.panelWindow.document.querySelector( + "#splitview-tpl-summary-stylesheet" + ), + { button: 2, type: "contextmenu" }, + panel.panelWindow + ); + }); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_pretty.js b/devtools/client/styleeditor/test/browser_styleeditor_pretty.js new file mode 100644 index 0000000000..e8208cb7d6 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_pretty.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that only minified sheets are automatically prettified, +// and that the pretty print button behaves as expected. + +const TESTCASE_URI = TEST_BASE_HTTP + "minified.html"; + +const PRETTIFIED_CSS_TEXT = ` +body { + background:white; +} +div { + font-size:4em; + color:red +} +span { + color:green; + @media screen { + background: blue; + &>.myClass { + padding: 1em + } + } +} +`.trimStart(); + +const INLINE_STYLESHEET_ORIGINAL_CSS_TEXT = ` +body { background: red; } +div { +font-size: 5em; +color: red; +}`.trimStart(); + +const INLINE_STYLESHEET_PRETTIFIED_CSS_TEXT = ` +body { + background: red; +} +div { + font-size: 5em; + color: red; +} +`.trimStart(); + +add_task(async function () { + // Use 2 spaces for indent + await pushPref("devtools.editor.expandtab", true); + await pushPref("devtools.editor.tabsize", 2); + + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); + is(ui.editors.length, 3, "Three sheets present."); + + info("Testing minified style sheet."); + const minifiedEditor = await ui.editors[0].getSourceEditor(); + + is( + minifiedEditor.sourceEditor.getText(), + PRETTIFIED_CSS_TEXT, + "minified source has been prettified automatically" + ); + + info("Selecting second, non-minified style sheet."); + await ui.selectStyleSheet(ui.editors[1].styleSheet); + + const inlineEditor = ui.editors[1]; + is( + inlineEditor.sourceEditor.getText(), + INLINE_STYLESHEET_ORIGINAL_CSS_TEXT, + "non-minified source has been left untouched" + ); + + const prettyPrintButton = panel.panelWindow.document.querySelector( + ".style-editor-prettyPrintButton" + ); + ok(prettyPrintButton, "Pretty print button is displayed"); + ok( + !prettyPrintButton.hasAttribute("disabled"), + "Pretty print button is enabled" + ); + is( + prettyPrintButton.getAttribute("title"), + "Pretty print style sheet", + "Pretty print button has the expected title attribute" + ); + + const onEditorChange = inlineEditor.sourceEditor.once("changes"); + EventUtils.synthesizeMouseAtCenter(prettyPrintButton, {}, panel.panelWindow); + await onEditorChange; + + is( + inlineEditor.sourceEditor.getText(), + INLINE_STYLESHEET_PRETTIFIED_CSS_TEXT, + "inline stylesheet was prettified as expected when clicking on pretty print button" + ); + + info("Selecting original style sheet."); + await ui.selectStyleSheet(ui.editors[2].styleSheet); + ok( + prettyPrintButton.hasAttribute("disabled"), + "Pretty print button is disabled when selecting an original file" + ); + await waitFor( + () => + prettyPrintButton.getAttribute("title") === + "Can only pretty print CSS files" + ); + ok( + true, + "Pretty print button has the expected title attribute when it's disabled" + ); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_private_perwindowpb.js b/devtools/client/styleeditor/test/browser_styleeditor_private_perwindowpb.js new file mode 100644 index 0000000000..e372f54ba2 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_private_perwindowpb.js @@ -0,0 +1,76 @@ +/* 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/. */ + +"use strict"; + +// This test makes sure that the style editor does not store any +// content CSS files in the permanent cache when opened from a private window tab. + +const TEST_URL = `http://${TEST_HOST}/browser/devtools/client/styleeditor/test/test_private.html`; + +add_task(async function () { + info("Opening a new private window"); + const win = await BrowserTestUtils.openNewBrowserWindow({ private: true }); + + info("Clearing the browser cache"); + Services.cache2.clear(); + + const { toolbox, ui } = await openStyleEditorForURL(TEST_URL, win); + + is(ui.editors.length, 1, "The style editor contains one sheet."); + const editor = ui.editors[0]; + is( + editor.friendlyName, + "test_private.css", + "The style editor contains the expected stylesheet" + ); + + await editor.getSourceEditor(); + + await checkDiskCacheFor(editor.friendlyName); + + await toolbox.destroy(); + + const onUnload = new Promise(done => { + win.addEventListener("unload", function listener(event) { + if (event.target == win.document) { + win.removeEventListener("unload", listener); + done(); + } + }); + }); + win.close(); + await onUnload; +}); + +function checkDiskCacheFor(fileName) { + let foundPrivateData = false; + + return new Promise(resolve => { + Visitor.prototype = { + onCacheStorageInfo(num) { + info("disk storage contains " + num + " entries"); + }, + onCacheEntryInfo(uri) { + const urispec = uri.asciiSpec; + info(urispec); + foundPrivateData = foundPrivateData || urispec.includes(fileName); + }, + onCacheEntryVisitCompleted() { + is(foundPrivateData, false, "web content present in disk cache"); + resolve(); + }, + }; + function Visitor() {} + + const storage = Services.cache2.diskCacheStorage( + Services.loadContextInfo.default + ); + storage.asyncVisitStorage( + new Visitor(), + /* Do walk entries */ + true + ); + }); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_reload.js b/devtools/client/styleeditor/test/browser_styleeditor_reload.js new file mode 100644 index 0000000000..f568b3f40f --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_reload.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that selected sheet and cursor position persists during reload. + +const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html"; + +const LINE_NO = 5; +const COL_NO = 3; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 2, "Two sheets present after load."); + + info("Selecting the second editor"); + await ui.selectStyleSheet(ui.editors[1].styleSheet, LINE_NO, COL_NO); + const selectedStyleSheetIndex = ui.editors[1].styleSheet.styleSheetIndex; + + await reloadPageAndWaitForStyleSheets(ui, 2); + + info("Waiting for source editor to be ready."); + const newEditor = findEditor(ui, selectedStyleSheetIndex); + await newEditor.getSourceEditor(); + + is( + ui.selectedEditor, + newEditor, + "Editor of stylesheet that has styleSheetIndex we selected is selected after reload" + ); + + const { line, ch } = ui.selectedEditor.sourceEditor.getCursor(); + is(line, LINE_NO, "correct line selected"); + is(ch, COL_NO, "correct column selected"); +}); + +function findEditor(ui, styleSheetIndex) { + return ui.editors.find( + editor => editor.styleSheet.styleSheetIndex === styleSheetIndex + ); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_remove_stylesheet.js b/devtools/client/styleeditor/test/browser_styleeditor_remove_stylesheet.js new file mode 100644 index 0000000000..69a23c0947 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_remove_stylesheet.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that a removed style sheet don't show up in the style editor anymore. + +const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 2, "Two sheets present after load."); + + info("Removing the +
Styled Node
+`; + +const TESTCASE_CSS_SOURCE = "#testid { color: chartreuse; }"; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + + info("Open the inspector and select the node we want to add style to"); + const { inspector, view } = await openRuleView(); + await selectNode("#testid", inspector); + + info("Open the StyleEditor"); + const { panel, ui } = await openStyleEditor(); + + const editor = await ui.editors[0].getSourceEditor(); + await new Promise(res => waitForFocus(res, panel.panelWindow)); + + info("Type new rule in stylesheet"); + editor.focus(); + EventUtils.sendString(TESTCASE_CSS_SOURCE, panel.panelWindow); + ok(editor.unsaved, "new editor has unsaved flag"); + + info("Wait for ruleview to update"); + await inspector.toolbox.selectTool("inspector"); + await waitFor(() => getRuleViewRule(view, "#testid")); + + info("Check that edits were synced to rule view"); + const value = getRuleViewPropertyValue(view, "#testid", "color"); + is(value, "chartreuse", "Got the expected color property"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_transition_rule.js b/devtools/client/styleeditor/test/browser_styleeditor_transition_rule.js new file mode 100644 index 0000000000..7fd1c0bd1b --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_transition_rule.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html"; + +const NEW_RULE = "body { background-color: purple; }"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 2, "correct number of editors"); + + const editor = ui.editors[0]; + await openEditor(editor); + + // Set text twice in a row + const styleChanges = listenForStyleChange(editor); + + editor.sourceEditor.setText(NEW_RULE); + editor.sourceEditor.setText(NEW_RULE + " "); + + await styleChanges; + + const rules = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [0], + async function (index) { + const sheet = content.document.styleSheets[index]; + return [...sheet.cssRules].map(rule => rule.cssText); + } + ); + + // Test that we removed the transition rule, but kept the rule we added + is(rules.length, 1, "only one rule in stylesheet"); + is(rules[0], NEW_RULE, "stylesheet only contains rule we added"); +}); + +/* Helpers */ + +function openEditor(editor) { + const link = editor.summary.querySelector(".stylesheet-name"); + link.click(); + + return editor.getSourceEditor(); +} + +function listenForStyleChange(editor) { + return editor.once("style-applied"); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_xul.js b/devtools/client/styleeditor/test/browser_styleeditor_xul.js new file mode 100644 index 0000000000..e5843b9e79 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_xul.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that the style-editor initializes correctly for XUL windows. + +"use strict"; + +waitForExplicitFinish(); + +const TEST_URL = TEST_BASE + "doc_xulpage.xhtml"; + +add_task(async function () { + const tab = await addTab(TEST_URL); + const toolbox = await gDevTools.showToolboxForTab(tab, { + toolId: "styleeditor", + }); + const panel = toolbox.getCurrentPanel(); + + ok( + panel, + "The style-editor panel did initialize correctly for the XUL window" + ); +}); diff --git a/devtools/client/styleeditor/test/browser_toolbox_styleeditor.js b/devtools/client/styleeditor/test/browser_toolbox_styleeditor.js new file mode 100644 index 0000000000..716946b67d --- /dev/null +++ b/devtools/client/styleeditor/test/browser_toolbox_styleeditor.js @@ -0,0 +1,104 @@ +/* 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/. */ + +// Test that stylesheets from parent and content processes are displayed in the styleeditor. + +"use strict"; + +requestLongerTimeout(4); + +const TEST_URI = `data:text/html, + + + + + Test browser toolbox`; + +/* global gToolbox */ +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/browser-toolbox/test/helpers-browser-toolbox.js", + this +); + +add_task(async function () { + await pushPref("devtools.browsertoolbox.scope", "everything"); + await pushPref("devtools.styleeditor.transitions", false); + await addTab(TEST_URI); + const ToolboxTask = await initBrowserToolboxTask(); + + await ToolboxTask.importFunctions({ + waitUntil, + }); + + await ToolboxTask.spawn(null, async () => { + await gToolbox.selectTool("styleeditor"); + const panel = gToolbox.getCurrentPanel(); + + function getStyleEditorItems() { + return Array.from( + panel.panelWindow.document.querySelectorAll(".splitview-nav li") + ); + } + + info(`check if "parent process" stylesheets are displayed`); + const isUAStyleSheet = el => + el.querySelector(".stylesheet-name label").value == "ua.css"; + await waitUntil(() => getStyleEditorItems().find(isUAStyleSheet)); + ok(true, "Found ua.css stylesheet"); + + info("check if content page stylesheets are displayed"); + const isTabStyleSheet = el => + el.querySelector(".stylesheet-name label").value == "simple.css"; + await waitUntil(() => getStyleEditorItems().find(isTabStyleSheet)); + ok(true, "Found simple.css tab stylesheet"); + + info("Select the stylesheet and update its content"); + const contentStylesheetSummaryEl = + getStyleEditorItems().find(isTabStyleSheet); + + let tabStyleSheetEditor; + if (panel.UI.selectedEditor.friendlyName === "simple.css") { + // simple.css might be selected by default, depending on the order in + // which the stylesheets have been loaded in the style editor. + tabStyleSheetEditor = panel.UI.selectedEditor; + } else { + // We might get events for the initial, default selected stylesheet, so wait until + // we get the one for the simple.css stylesheet. + const onTabStyleSheetEditorSelected = new Promise(resolve => { + const onEditorSelected = editor => { + if (editor.summary == contentStylesheetSummaryEl) { + resolve(editor); + panel.UI.off("editor-selected", onEditorSelected); + } + }; + panel.UI.on("editor-selected", onEditorSelected); + }); + panel.UI.setActiveSummary(contentStylesheetSummaryEl); + tabStyleSheetEditor = await onTabStyleSheetEditorSelected; + } + + info("Wait for sourceEditor to be available"); + await waitUntil(() => tabStyleSheetEditor.sourceEditor); + + const onStyleApplied = tabStyleSheetEditor.once("style-applied"); + tabStyleSheetEditor.sourceEditor.setText( + tabStyleSheetEditor.sourceEditor.getText() + "\n body {color: red;}" + ); + await onStyleApplied; + }); + + info("Check that the edit done in the style editor were applied to the page"); + const bodyColorStyle = await getComputedStyleProperty({ + selector: "body", + name: "color", + }); + + is( + bodyColorStyle, + "rgb(255, 0, 0)", + "Changes made to simple.css were applied to the page" + ); + + await ToolboxTask.destroy(); +}); diff --git a/devtools/client/styleeditor/test/bug_1405342_serviceworker_iframes.html b/devtools/client/styleeditor/test/bug_1405342_serviceworker_iframes.html new file mode 100644 index 0000000000..7bcbcf875c --- /dev/null +++ b/devtools/client/styleeditor/test/bug_1405342_serviceworker_iframes.html @@ -0,0 +1,10 @@ + + + + + Bug 1405342 + + +